Eric Renfro
402420472e
This includes a bug fix found in the ipv6 agent for AWS SG's, along with better IPv6 detection to get the current active source IPv6 address.
431 lines
10 KiB
Bash
Executable file
431 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
|
|
|
|
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
|
|
}
|
|
|
|
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
|