Created
December 24, 2017 01:40
-
-
Save tavinus/3568d6cba5282ecfd8abf4820a57553d to your computer and use it in GitHub Desktop.
Open ports for dynamic host names
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #! /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