petrified/petrified

237 lines
6 KiB
Bash
Executable file

#!/bin/bash
#
## petrified - bash client to update dynamic DNS at freedns.afraid.org
## Copyright (c) 2014 Troy Engel
## Version: 1.0.4
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
## One of these must exist - '-c <config>' ignores global/local entirely
CONF_GLOBAL=/etc/petrified.conf
CONF_LOCAL=~/.petrifiedrc
CONF_NAMED=""
# Check our arguments for a config file
while getopts ":c:" opt; do
case $opt in
c)
CONF_NAMED="$OPTARG"
;;
:)
echo "Option -$OPTARG requires the name of a config file." >&2
exit 1
;;
esac
done
## How to update DNS - DDNS_KEY is unique per domain
DDNS_URL=https://freedns.afraid.org/dynamic/update.php
DDNS_KEY=""
## Which URL to use to get an IP
# IPv4 icanhazip (http://major.io/icanhazip-com-faq/)
DDNS_CHECK="http://4.icanhazip.com"
# IPv6 icanhazip (http://major.io/icanhazip-com-faq/)
#DDNS_CHECK="http://6.icanhazip.com"
## How many seconds should curl wait when either checking your IP or
## trying to update the remote DNS
CURL_WAIT=10
## Report an internal IP instead of the public IP - useful if you're
## using dynamic DNS for machines on the internal network
LOCAL_MODE=0
# Specify which interface
LOCAL_IF=eth0
# Should we use IPv4 or IPv6
LOCAL_IV=4
## How to log - multiple supported
USE_JOURNAL=1
USE_SYSLOG=0
USE_STDOUT=0
USE_LOGFILE=0
## If USE_LOGFILE is 1, where to log
PET_LOG=/var/log/petrified.log
## Prevent race conditions, i.e. stuck crons piling up
USE_PID=1
PET_PID=/run/petrified.pid
## Save the IP from our last check
USE_LIP=1
PET_LIP=/var/cache/petrified/lastip.dat
## Dependencies:
# logger (util-linux)
# kill (util-linux)
# printf (coreutils)
# touch (coreutils)
# date (coreutils)
# stat (coreutils)
# cat (coreutils)
# rm (coreutils)
# bash (bash)
# ip (iproute2)
# curl (curl)
####
# Read in our configs
if [[ -z "${CONF_NAMED}" ]]; then
[[ -r "${CONF_GLOBAL}" ]] && source "${CONF_GLOBAL}"
[[ -r "${CONF_LOCAL}" ]] && source "${CONF_LOCAL}"
else
[[ -r "${CONF_NAMED}" ]] && source "${CONF_NAMED}"
fi
# Check that we have all the needed variables
if [[ -z "${DDNS_KEY}" ]]; then
echo "DDNS_KEY must be configured, exiting."
exit 1
fi
# Make sure that PET_LOG will work if required
if (( ${USE_LOGFILE} == 1 )); then
if [[ -z "${PET_LOG}" ]]; then
echo "USE_LOGFILE=1 but PET_LOG is an empty string, exiting."
exit 1
elif (( $(touch "${PET_LOG}" 2>/dev/null; echo $?;) != 0 )); then
echo "USE_LOGFILE=1 but cannot write to ${PET_LOG}, exiting."
exit 1
fi
fi
# Logging
logmsg () {
LMSG=$1
if (( ${USE_JOURNAL} == 1 )); then
printf "%s\n%s\n" "SYSLOG_IDENTIFIER=petrified" "MESSAGE=${LMSG}" | \
logger --journald
fi
if (( ${USE_SYSLOG} == 1 )); then
echo "${LMSG}" | logger -t petrified
fi
if (( ${USE_STDOUT} == 1 )); then
DTS=$(date +"%Y-%m-%d %H:%M:%S")
echo "[${DTS}] [petrified] ${LMSG}"
fi
if (( ${USE_LOGFILE} == 1 )); then
DTS=$(date +"%Y-%m-%d %H:%M:%S")
echo "[${DTS}] [petrified] ${LMSG}" >> "${PET_LOG}"
fi
return
}
# Security warning
statchk () {
_CFILE=$1
if [[ -f "${_CFILE}" ]] && [[ -r "${_CFILE}" ]]; then
if [[ $(stat -c "%a" "${_CFILE}") != 600 ]]; then
logmsg "Security warning: ${_CFILE} is readable but not mode 0600"
fi
fi
}
[[ -n "${CONF_GLOBAL}" ]] && statchk "${CONF_GLOBAL}"
[[ -n "${CONF_LOCAL}" ]] && statchk "${CONF_LOCAL}"
[[ -n "${CONF_NAMED}" ]] && statchk "${CONF_NAMED}"
# Make sure that PET_PID will work if required
if (( ${USE_PID} == 1 )); then
if [[ -z "${PET_PID}" ]]; then
logmsg "USE_PID=1 but PET_PID is an empty string, exiting."
exit 1
elif (( $(touch "${PET_PID}" 2>/dev/null; echo $?;) != 0 )); then
logmsg "USE_PID=1 but cannot write to ${PET_PID}, exiting."
exit 1
fi
fi
# PID actions
if (( ${USE_PID} == 1 )); then
PIDNUM=$(cat "${PET_PID}" 2>/dev/null)
# check if we have a number
if [[ ${PIDMUM} =~ ^-?[0-9]+$ ]]; then
kill -0 ${PIDNUM}
if (( $? == 0 )); then
logmsg "Detected a running process ${PIDNUM}, exiting."
exit 1
else
logmsg "Stale PID ${PIDNUM} detected, overwriting."
fi
fi
echo ${BASHPID} > "${PET_PID}"
fi
# Cleanup actions
cleanup () {
(( ${USE_PID} == 1 )) && (rm -f "${PET_PID}" 1>/dev/null 2>&1)
(( ${USE_LIP} == 0 )) && (rm -f "${PET_LIP}" 1>/dev/null 2>&1)
return
}
# Trap the basic signals
sigexit () {
logmsg "Trapped a kill signal, exiting."
cleanup
exit 3
}
trap sigexit SIGHUP SIGINT SIGTERM
# Get our IP address
NEWIP=""
if (( ${LOCAL_MODE} == 1 )); then
NEWIP=$(ip -${LOCAL_IV} -o addr show dev ${LOCAL_IF} primary 2>/dev/null)
NEWIP=${NEWIP%%/*}
NEWIP=${NEWIP##* }
else
NEWIP=$(curl -m ${CURL_WAIT} -s ${DDNS_CHECK} 2>/dev/null)
fi
if [[ -z "${NEWIP}" ]]; then
logmsg "Error getting an IP address, exiting."
cleanup
exit 1
fi
# Check our saved IP against the new IP
OLDIP="0"
if (( ${USE_LIP} == 1 )); then
OLDIP=$(cat "${PET_LIP}" 2>/dev/null)
fi
# If they don't match, tell upstream
if [[ "${OLDIP}" != "${NEWIP}" ]]; then
UPDURL="${DDNS_URL}?${DDNS_KEY}&address=${NEWIP}"
RESULT=$(curl -m ${CURL_WAIT} -sk "${UPDURL}" 2>/dev/null)
logmsg "${RESULT}"
else
logmsg "IP ${NEWIP} hasn't changed, not updating."
fi
# Save the new IP if configured
if (( ${USE_LIP} == 1 )); then
if (( $(touch "${PET_LIP}" 2>/dev/null; echo $?;) != 0 )); then
logmsg "USE_LIP=1 but cannot write to ${PET_LIP}."
else
echo "${NEWIP}" > "${PET_LIP}"
fi
fi
# Take out the trash
cleanup
exit 0