434 lines
10 KiB
Bash
Executable file
434 lines
10 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
|
|
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" \
|
|
"$hook_script" "$@"
|
|
then
|
|
logerr "WARNING: Agent $(basename "$hook_script") had errors"
|
|
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" || 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
|
|
|
|
[[ -z "$iptype" ]] && return 1
|
|
[[ -z "$record" ]] && return 1
|
|
|
|
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"
|
|
if run-update "$currentip" "$externalip" "$record"; 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")")
|
|
|
|
|
|
# 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
|