#!/bin/bash # Functions trim() { local extglobWasOff=1 local var=$1 shopt extglob >/dev/null && extglobWasOff=0 (( extglobWasOff )) && shopt -s extglob var=${var##+([[:space:]])} var=${var%%+([[:space:]])} (( extglobWasOff )) && shopt -u extglob var=${var%%\#*} printf '%s' "$var" } valid_ipv4() { local ip=$1 local stat=1 local ipaddr if [[ $ip =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then IFS='.' read -ra ipaddr <<< "$ip" [[ $((10#${ipaddr[0]})) -le 255 \ && $((10#${ipaddr[0]})) -gt 0 \ && $((10#${ipaddr[1]})) -le 255 \ && $((10#${ipaddr[1]})) -gt -1 \ && $((10#${ipaddr[2]})) -le 255 \ && $((10#${ipaddr[2]})) -gt -1 \ && $((10#${ipaddr[3]})) -le 255 \ && $((10#${ipaddr[3]})) -gt -1 ]] stat=$? fi return "$stat" } valid_ipv6() { local ip=$1 local regex='^([[:xdigit:]]{0,4}:){1,7}[[:xdigit:]]{0,4}$' #local regex='^$|^[0-9a-fA-F]{1,4}:[0-9a-fA-F]{1,4}:[0-9a-fA-F]{1,4}:[0-9a-fA-F]{1,4}:[0-9a-fA-F]{1,4}:[0-9a-fA-F]{1,4}:[0-9a-fA-F]{1,4}:[0-9a-fA-F]{1,4}$' #if [[ $ip =~ ^$|^[0-9a-fA-F]{1,4}:[0-9a-fA-F]{1,4}:[0-9a-fA-F]{1,4}:[0-9a-fA-F]{1,4}:[0-9a-fA-F]{1,4}:[0-9a-fA-F]{1,4}:[0-9a-fA-F]{1,4}:[0-9a-fA-F]{1,4}$ ]]; then if [[ $ip =~ $regex ]]; then return 0 else return 1 fi } detect_ip_version() { local ip=$1 if valid_ipv4 "$ip"; then return 4; elif valid_ipv6 "$ip"; then return 6 else return 0; fi } valid_ip() { local ip=$1 local iptype detect_ip_version "$ip" iptype=$? case $iptype in 4) return 0;; 6) return 0;; *) return 1;; esac } getRecords() { local file=$1 local r if [[ ! -r "$file" ]]; then return 1 else while read -r r; do r=$(trim "$r") [[ ${r:0:1} == '#' ]] && continue [[ -z "$r" ]] && continue echo "$r" done <"$file" fi } getIPv4() { local result local cmd [[ ! -r "${script_dir}/checks.lst" ]] && return 1 while read -r cmd; do cmd=$(trim "$cmd") [[ "$cmd" == \#* ]] && continue [[ -n "$cmd" ]] || continue result=$($cmd) || continue if valid_ipv4 "$result"; then echo "$result" return 0 fi done <"${script_dir}/checks.lst" return 1 } getIPv6() { local result #result=$(/sbin/ip -6 addr | grep inet6 | awk -F '[ \t]+|/' '{print $3}' | grep -v ^::1 | grep -v ^fe80) || return 1 result=$(ip -6 route get to 2001:4860:4860::8888 | sed 's/^.*src \([^ ]*\).*$/\1/;q') || return 1 if valid_ipv6 "$result"; then echo "$result" fi } getDnsNS() { local rec=$1 local domain local result domain=$(sed 's/[^.]*\.\([^.]*\..*\)/\1/' <<<"$rec") while read -r result; do echo "$result" done < <(dig +short @8.8.8.8 NS "$domain" | sed -e 's/.$//') } getDnsRecord() { local rec=$1 local rectype=$2 local result local getrecord while read -r getrecord; do [[ "$getrecord" == "ERROR" ]] && return 1 result=$(dig +short @"$getrecord" "$rectype" "$rec" | head -n1) || continue [[ -z "$result" ]] && continue if valid_ip "$result"; then echo "$result" return 0 else continue fi done < <(getDnsNS "$rec") return 1 } getIPv6Prefix() { local rec=$1 local result if valid_ipv6 "$rec" then result=$(echo "$rec" | cut -d':' -f1-4) result="${result}::/64" echo "$result" else logerr "Not IPv6" return 1 fi } getZabbixProxyIP() { local result result=$(awk -F "=" '/^Server/ { print $2 }' /etc/zabbix/zabbix_proxy.conf) || return 1 if valid_ip "$result"; then echo "$result" else return 1 fi } getShorewallIP() { local result result=$(awk -F "=" '/HOME_IP/ { print $2 }' /etc/shorewall/params) || return 1 if valid_ip "$result"; then echo "$result" else return 1 fi } getCachedIP() { local result result=$(<"$HOME/.cached_ip") || return 1 if valid_ip "$result"; then echo "$result" fi } log() { echo "$*" } logerr() { >&2 log "$@" } run-parts() { # Ignore *~ and *, scripts for i in ${1%/}/*[^~]; do [[ -d "$i" ]] && continue # Don't run *.{rpmsave,rpmorig,rpmnew,swp,cfsaved} scripts [[ "${i%.cfsaved}" != "${i}" ]] && continue [[ "${i%.rpmsave}" != "${i}" ]] && continue [[ "${i%.rpmorig}" != "${i}" ]] && continue [[ "${i%.rpmnew}" != "${i}" ]] && continue [[ "${i%.swp}" != "${i}" ]] && continue [[ "${i%,v}" != "${i}" ]] && continue # jobs.deny prevents specific files from being executed # jobs.allow prohibits all non-named jobs from being run. # can be used in conjunction but there's no reason to do so. if [[ -r "${1}/jobs.deny" ]]; then grep -q "^$(basename "$i")$" "${1}/jobs.deny" && continue fi if [[ -r "${1}/jobs.allow" ]]; then grep -q "^$(basename "$i")$" "${1}/jobs.allow" || continue fi if [[ -e $i ]]; then if [[ -r "${1}/whitelist" ]]; then grep -q "^$(basename "$i")$" "${1}/whitelist" && continue fi if [[ -x "$i" ]]; then # run executable files echo "$i" fi fi done } run-hook() { local hook_dir=$1 local errors=0 local hook_script shift if [[ ! -d "${hook_dir}" ]]; then return 0 fi while read -r hook_script; do log "Running agent: $(basename "$hook_script")" if ! \ DIP_FUNCTIONS="$(readlink -f "$0")" \ DIP_AGENT_NAME="$(basename "$(readlink -f "$hook_script")")" \ DIP_AGENT_EXEC="$(basename "$hook_script")" \ DIP_AGENT_DIR="$(dirname "$(readlink -f "$hook_script")")" \ DIP_BASE_DIR="${script_dir}" \ DIP_CUR_IP="$1" \ DIP_OLD_IP="$2" \ DIP_RECORD="$3" \ DIP_DOMAIN="$4" \ "$hook_script" "$@" then logerr "WARNING: Agent $(basename "$hook_script") had errors" (( errors++ )) || true fi done < <(run-parts "${hook_dir}") return "$errors" } run-update() { local cip=$1 local eip=$2 local rec=$3 local dom=$4 if [[ "$cip" != "$eip" ]]; then run-hook "${script_dir}/update.d" "$cip" "$eip" "$rec" "$dom" || return $? else log "No change detected" fi } # Caches current local IP so it doesn't have to every time. getCurrentLocalIP() { local iptype=$1 local cip if [[ -z "$currentip" ]]; then case "$iptype" in 4) cip=$(getIPv4) || return 1;; 6) cip=$(getIPv6) || return 1;; *) return 3;; esac echo "$cip" else case "$iptype" in 4) valid_ipv4 "$currentip" || return 1;; 6) valid_ipv6 "$currentip" || return 1;; *) return 3;; esac echo "$currentip" fi } getCurrentIP() { local iptype=$1 local record=$2 [[ -z "$iptype" ]] && return 1 [[ -z "$record" ]] && return 1 case "$iptype" in 4) log "Checking if internet IP has changed for $record" currentip=$(getCurrentLocalIP 4) || return "$?" externalip=$(getDnsRecord "$record" "A") || return 2 return 0 ;; 6) log "Checking if internet IPv6 has changed for $record" currentip=$(getCurrentLocalIP 6) || return "$?" externalip=$(getDnsRecord "$record" "AAAA") || return 2 return 0 ;; *) logerr "FATAL"; return 3 ;; esac } check-update() { local iptype=$1 local record=$2 local domain [[ -z "$iptype" ]] && return 1 [[ -z "$record" ]] && return 1 if [[ "$record" == *":"* ]]; then domain="${record##*:}" #record="${record%%."$domain"}" # FIXME record="${record%%:*}" fi getCurrentIP "$iptype" "$record" local status=$? case $status in 0) if [[ "$currentip" != "$externalip" ]]; then log "Updates found: $externalip is not $currentip" log "Running Agents for ${record}" #${domain:+."$domain"} if run-update "$currentip" "$externalip" "$record" "$domain"; then log "Agents ran successfully" else logerr "WARNING: Agents had $? errors" fi else log "No change detected" fi ;; 1) logerr "ERROR: Failed to determine current public IP" logerr "DEBUG: dig output '$currentip'" exit 4 ;; 2) logerr "ERROR: Failed to determine external DNS IP" logerr "DEBUG: dig output '$externalip'" ;; *) logerr "Unknown fatal error occurred" logerr "(${BASH_SOURCE[0]}:${LINENO}): ${FUNCNAME[0]:+${FUNCNAME[0]}(): }" exit $status ;; esac } prog_lock() { local lock_name=$1 ## Locking if [[ "$USER" == "root" ]]; then LOCKFILE="/var/lock/${lock_name}" else LOCKFILE="/tmp/${USER}/lock/${lock_name}" mkdir -p "/tmp/${USER}/lock" fi LOCKFD=99 # PRIVATE _lock() { flock -"$1" $LOCKFD; } _no_more_locking() { _lock u; _lock xn && rm -f "$LOCKFILE"; } _prepare_locking() { eval "exec $LOCKFD>\"$LOCKFILE\""; trap _no_more_locking EXIT; } # ON START _prepare_locking # PUBLIC exlock_now() { _lock xn; } # obtain an exclusive lock immediately or fail exlock() { _lock x; } # obtain an exclusive lock shlock() { _lock s; } # obtain a shared lock unlock() { _lock u; } # drop a lock ### BEGIN OF SCRIPT ### # Simplest example is avoiding running multiple instances of script. exlock_now || exit 1 # Remember! Lock file is removed when one of the scripts exits and it is # the only script holding the lock or lock is not acquired at all. } # This section doesn't run if this script was sourced. if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then # Internal Initialization prog_name=$(basename "$0") #script_dir=$(dirname "$(readlink -f "$0")") script_dir=$(readlink -f "$(dirname "$0")") # Main if [[ -r "${script_dir}/plugins/${1}" ]]; then prog_lock "${prog_name}-${1}" # shellcheck source=plugins/update-ipv4 source "${script_dir}/plugins/${1}" else logerr "ERROR: Unknown plugin '${1}'" fi fi