dynamic-ip/src/dynamic-ip

501 lines
11 KiB
Bash
Executable File

#!/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
echo -n "$var"
}
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
}
valid_ipv4() {
local ip=$1
local stat=1
if [[ $ip =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then
OIFS=$IFS
IFS='.'
ip=($ip)
IFS=$OIFS
[[ ${ip[0]} -le 255 && ${ip[1]} -le 255 \
&& ${ip[2]} -le 255 && ${ip[3]} -le 255 ]]
stat=$?
fi
return $stat
}
valid_ipv6() {
local ip=$1
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
return 0
else
return 1
fi
}
getRecords() {
local file=$1
local r
if [[ ! -r "$file" ]]; then
return 1
else
while read r;
do
r=$(trim "$r")
[[ ${r:0:1} == '#' ]] && continue
[[ -z "$r" ]] && continue
echo "$r"
done <"$file"
fi
}
getIPv4() {
local result
local err
local -a checks
if [[ ! -f "${script_dir}/checks.lst" ]]
then
return 1
else
mapfile -t checks < "${script_dir}/checks.lst"
fi
for i in "${checks[@]}"
do
i=$(trim "$i")
[[ ${i:0:1} == '#' ]] && continue
[[ -z "$i" ]] && continue
result=$($i)
err=$?
if [[ $err -ne 0 ]]
then
continue
fi
if valid_ipv4 "$result"
then
echo "$result"
return 0
fi
done
return 1
}
getIPv6() {
local result
local err
result=$(/sbin/ip -6 addr | grep inet6 | awk -F '[ \t]+|/' '{print $3}' | grep -v ^::1 | grep -v ^fe80)
err=$?
if [[ $err -ne 0 ]]; then
return 1
elif valid_ipv6 "$result"; then
echo "$result"
return 0
else
return 0
fi
}
getDnsNS() {
local rec=$1
local domain=$(sed 's/[^.]*\.\([^.]*\..*\)/\1/' <<<"$rec")
local result
local err
result=$(dig +short @8.8.8.8 NS $domain | sed -e 's/.$//')
err=$?
if [[ $err -eq 0 ]]
then
for d in $result
do
echo "$d"
done
else
logerr "ERROR"
return $err
fi
}
getDnsRecord() {
local rec=$1
local rectype=$2
local result
local err
for i in $(getDnsNS "$rec")
do
[[ "$i" = "ERROR" ]] && return 1
result=$(dig +short @${i} ${rectype} ${rec} | head -n1)
err=$?
if [[ $err -ne 0 ]] || [[ -z "$result" ]]; then
logerr "ERROR: DNS lookup for $i"
continue
elif valid_ip "$result"; then
echo "$result"
return 0
else
logerr "ERROR: Lookup failed with: $result"
continue
fi
done
return 1
}
getIPv6Prefix() {
local rec=$1
local result
local err
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
local err
result=$(awk -F "=" '/^Server/ { print $2 }' /etc/zabbix/zabbix_proxy.conf)
err=$?
if [[ $err -ne 0 ]]
then
return 1
elif valid_ip "$result"
then
echo "$result"
return 0
else
return 1
fi
}
getShorewallIP() {
local result
local err
result=$(awk -F "=" '/HOME_IP/ { print $2 }' /etc/shorewall/params)
err=$?
if [[ $err -ne 0 ]]
then
return 1
elif valid_ip "$result"
then
echo "$result"
return 0
else
return 1
fi
}
getCachedIP() {
local result
local err
result=$(<"$HOME/.cached_ip")
err=$?
if [[ $err -ne 0 ]]; then
return 1
elif valid_ip "$result"; then
echo "$result"
return 0
else
return 0
fi
}
log() {
echo "$*"
}
logerr() {
>&2 log $*
}
run-parts() {
# Ignore *~ and *, scripts
for i in $(LC_ALL=C; echo ${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
shift
if [[ ! -d "${hook_dir}" ]]; then
return 0
fi
while read s
do
log "Running agent: $(basename "$s")"
DIP_FUNCTIONS="$(readlink -f "$0")" \
DIP_AGENT_NAME="$(basename "$(readlink -f "$s")")" \
DIP_AGENT_EXEC="$(basename "$s")" \
DIP_AGENT_DIR="$(dirname "$(readlink -f "$s")")" \
DIP_BASE_DIR="${script_dir}" \
DIP_CUR_IP="$1" \
DIP_OLD_IP="$2" \
DIP_RECORD="$3" \
"$s" $*
err=$?
if [[ $err -ne 0 ]]; then
let errors++
fi
done < <(run-parts "${hook_dir}")
return $errors
}
run-update() {
local cip=$1
local eip=$2
local rec=$3
if [[ "$cip" != "$eip" ]]
then
run-hook "${script_dir}/update.d" "$cip" "$eip" "$rec"
err=$?
if [[ $err -gt 0 ]]; then
return $err
fi
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
[[ -z "$iptype" ]] && return 1
[[ -z "$record" ]] && return 1
getCurrentIP $iptype $record
err=$?
case $err in
0) if [[ "$currentip" != "$externalip" ]]; then
log "Updates found: $externalip is not $currentip"
log "Running Agents for $record"
run-update "$currentip" "$externalip" "$record"
err=$?
if [[ $err -ne 0 ]]; then
logerr "WARNING: Agents had $err errors"
else
log "Agents ran successfully"
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"
eclogerrho "(${BASH_SOURCE}:${LINENO}): ${FUNCNAME[0]:+${FUNCNAME[0]}(): }"
exit $err
;;
esac
}
prog_lock() {
## Locking
if [[ "$USER" = "root" ]]; then
LOCKFILE="/var/lock/$(basename "$0")"
else
LOCKFILE="/tmp/${USER}/lock/$(basename "$0")"
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")")
if [[ -z "$1" ]]; then
logerr "ERROR: Need to provide a DNS record or file to look-up"
exit 1
else
if [[ -r "$1" ]]; then
record_file=$1
else
record=$1
fi
fi
prog_lock
# Main
if [[ -r "${script_dir}/plugins/${1}" ]]; then
source "${script_dir}/plugins/${1}"
plugin_name=${1,,}
else
logerr "ERROR: Unknown plugin '${1}'"
fi
fi