#!/bin/bash
# vim: set expandtab softtabstop=2 shiftwidth=2:

# Author: Torsten Förtsch <torsten.foertsch@gmx.net>
#         Antoine Delvaux <antoine_at_delvaux_dot_net>
# Licensed under the GPL License 
VERSION="dynip.sh/0.6a"

##############        Usage        ##############
# You can give this script an argument being an IP address
# If so, the script will try to update this IP with the
# dynamic DNS services configured.
# If not, then it will try to guess it's own public IP
# and post that one the dynamic DNS services configured.

############## start configuration ##############

# Parameters left blank are not sent to the Dynamic DNS system
# Mandatory parameters are 'SYSTEM', 'SYSTEM_URL', 'USER', 'PW' and 'HOST'
# All others are optional.
# See https://www.dyndns.org/developers/specs/syntax.html
# for details.
#
# For DynDNS use
# SYSTEM_URL=https://members.dyndns.org/nic/update?
# For OpenDNS use
# SYSTEM_URL=https://updates.opendns.com/account/ddns.php?

SYSTEM=([1]=DynDNS [2]=OpenDNS)
SYSTEM_URL=([1]=https://members.dyndns.org/nic/update? [2]=https://updates.opendns.com/account/ddns.php?)
USER=([1]=login1 [2]=login2)
PW=([1]=pw1 [2]=pw2)
HOST=([1]=yourhost.dyndns.org [2]=YourOpenDNSNetworkName)
WILDCARD=
MX=([1]=yourhost.dyndns.org)
BACKMX=
OFFLINE=

# Maximum time an IP can be cached by dynip
MAX_CACHED_TIME=2419200

# In such a setup DynDNS' IP address autodetection is not used. If your
# address stays constant over a significant period of time and DynDNS is
# uselessly frequently updated DynDNS considers it to be abused. To avoid
# that a cache can be used. If the address has not changed no DynDNS update
# will happen.
#
# possible values are:
#  file:/path/to/file
#    /path/to/file is used as cache. The colon and file name can be omitted.
#    /var/run/dynip is used in that case.
#  dns
#    A DNS request using the "host" command is made to fetch the IP address
#    assigned with the host.
#  off
#    The caching feature is turned off. The DynDNS update is made
#    unconditionally.
#IP_ADDR_CACHE=off
IP_ADDR_CACHE=file:/var/run/dynip
#IP_ADDR_CACHE=dns

# if the connection to the DynDNS server could not be established dynip.sh
# can retry this operation. Give here the max. number of tries.
RETRY=3

# between each try to connect to the DynDNS server dynip.sh will pause this
# number of seconds.
PAUSE=3

# Curl options
# We put curl in silence mode '-s'
# If you add onther dynamic DNS system, you might want to turn it off
# and go for versbose mode instead, then use '-v'
# Newer curls require the server certificate to be
# trusted. Hence either install dyndns' server certificate or that
# certificate's signer certificate in your curl certificate pack or 
# specify the certificate with --cacert option or give the -k option
# to override trust. For more information: "man curl" or "curl -man"
CURLOPT="-s -k"

# The server certificates signer is unforunately not installed in curl's
# default certificate package. You can obtain it from
# https://www.geotrust.com/resources/root_certificates/certificates/Equifax_Secure_Global_eBusiness_CA-1.cer
# to add it to your certificate bundle you first have to check it's
# location on your disk. Curl helps you by means of the "-v" option.
# Calling an arbitrary https Url says for example:
#
# $ curl -v https://bahn.de
# * About to connect() to bahn.de port 443
# * Connected to bahn.de (81.200.194.40) port 443
# * successfully set certificate verify locations:
# *   CAfile: /usr/share/curl/curl-ca-bundle.crt
#   CApath: none
# * SSL certificate problem, verify that the CA cert is OK
# * Closing connection #0
# curl: (60) SSL certificate problem, verify that the CA cert is OK
# More details here: http://curl.haxx.se/docs/sslcerts.html
# ...
#
# The interesting lines are "CAfile: ..." and "CApath: ...". CAfile is a
# text file containing certificates. Here you simply append the cert
# from geotrust. CApath is a directory where you can store the cert in a
# extra file. Refer to the openssl docs to learn how.

# Logging
# 0 = no logging
# 1 = logging to syslog
# 2 = turn on logging to stderr
# 3 = turn on logging to syslog and stderr
VERBOSE=3

# If a failure notice should be given to syslog give here a priority
# see the logger(1) manual page (option -p)
SYSLOG=USER.notice

############## end   configuration ##############

# Logging function
log () {
  case $VERBOSE in
    1 ) logger -p "$SYSLOG" -t dynip "$@" ;;
    2 ) echo >&2 "$@" ;;
    3 ) logger -s -p "$SYSLOG" -t dynip "$@" ;;
  esac
}

# Check if an IP was given by argument
if [ -n "$1" ]; then
  MYIP=$(expr "echo $1" : '.*[^0-9]\([0-9]*\.[0-9]*\.[0-9]*\.[0-9]*\)')
else
  # Read our public IP
  MYIP=$(expr "$(curl -qs http://checkip.dyndns.org)" : '.*[^0-9]\([0-9]*\.[0-9]*\.[0-9]*\.[0-9]*\)')
fi
if [ ${MYIP:=""} != "" ]; then
  log "Current IP address is $MYIP"
else
  log "No IP address found!"
  exit 1
fi

# Check whether DynDNS really needs to be updated
case "$IP_ADDR_CACHE" in
  file* )
    CACHEFILE=${IP_ADDR_CACHE#*file:}
    NOW=`date +%s`;
    CACHEDIP=`grep ip $CACHEFILE 2>/dev/null`
    CACHEDIP=${CACHEDIP#*ip=}
    CACHEDTIME=`grep time $CACHEFILE 2>/dev/null`
    CACHEDTIME=${CACHEDTIME#*time=}
    DIFFTIME=$(($NOW-${CACHEDTIME:=0}))
    if [ $DIFFTIME -lt $MAX_CACHED_TIME ] && [ "$MYIP" = "$CACHEDIP" ]; then
      log "Cache (${CACHEFILE}): up to date (${CACHEDIP}, ${DIFFTIME}s old) -- exiting."
      exit 0
    fi
    if [ -r $CACHEFILE ]; then
      log "Cache ($CACHEFILE): $CACHEDIP not up to date or older than $MAX_CACHED_TIME seconds."
    else
      log "Cache ($CACHEFILE): do not exist or is not readable."
    fi
    UPDATE_CACHE="(rm $CACHEFILE; (echo "ip=$MYIP"; echo "time=$NOW") > $CACHEFILE;) 2>/dev/null"
    ;;
  dns* )
    DNSREPLY=`host $HOST`
    if [ "$MYIP" == "${dnsreply##* }" ]; then
      log "Cache ($CACHEFILE): up to date ({$CACHEDIP}) -- exiting."
      exit 0
    fi
    ;;
esac

# Update each SYSTEM configured
index=1
for SYSTEM in ${SYSTEM[@]:0}; do
  echo $index
  # Build HTTP query string
  PARAMS="HOSTNAME=${HOST[$index]}&MYIP=$MYIP"
  for PARAM in WILDCARD MX BACKMX OFFLINE; do
    VALUE="$(eval echo \${$PARAM[$index]})"
    if [ "$VALUE" != "" ]; then
      PARAMS="${PARAMS}&${PARAM}=$VALUE"
    fi
  done

  # Call curl to update DNS settings
  CMD="curl $CURLOPT -A $VERSION -u ${USER[$index]}:${PW[$index]} ${SYSTEM_URL[$index]}${PARAMS}"
  trials=$RETRY
  while [ -z "$RC" -a $((trials--)) -gt 0 ]; do
    log "${CMD#${PW[$index]}}"
    RC=`$CMD`
    log "curl says: '$RC'"
    [ -z "$RC" ] && sleep $PAUSE
  done
  [ -z "$RC" ] && rc="DynDNS connection timeout"

  if [ "$RC" != "nochg $MYIP" ] && [ "$RC" != "good $MYIP" ]; then
    log "Got unexpected result from curl"
    log "'good $MYIP' or 'nochg $MYIP' expected"
    log "${SYSTEM[$index]} Update failed"
    ERROR=1
  else
    log "${SYSTEM[$index]} up to date with $MYIP"
  fi
  log "End of $((index++)) update out of $((${#SYSTEM[@]}-1))"
  unset RC
done

if [ ${ERROR:=0} = 0 ]; then
  [ "$UPDATE_CACHE" ] && log "Updating Cache: $UPDATE_CACHE"
  eval $UPDATE_CACHE
  exit 0
else
  log "At least one error occured"
  exit 1
fi


