gratisdns.sh – A GratisDNS.dk DDNSd module for Synology DSM
March 4th, 2012
2 comments
#!/bin/sh
# gratisdns.sh - A GratisDNS.dk DDNSd module for Synology DSM
#
# Author:
# Brian Schmidt Pedersen (http://briped.net)
#
# Version:
# 1.2.5
#
# Description:
# This is a custom DDNSd module, for use with my Synology DiskStation. It
# allows me to use the DSM interface for GratisDNS.dk DDNS service.
#
# To use this script, put it in the "/sbin" folder of the DiskStation,
# i.e. "/sbin/gratisdns.sh"
#
# Then edit the "/etc.defaults/ddnsd_provider.conf" file and add the
# following section at the end of that file:
#
# [GratisDNS.dk]
# modulepath=/sbin/gratisdns.sh
# queryurl=https://ssl.gratisdns.dk/ddns.phtml?u=__USERNAME__&p=__PASSWORD__&d=__DOMAIN__&h=__HOSTNAME__&i=__MYIP__
#
#
# Changelist:
# 1.2.5
# * Changed update_ca_bundle() a bit, since the age checking didn't
# seem to work before.
# 1.2.4
# * Fixed script to return "nochg" when IP isn't changed from IPcache
# 1.2.3
# * Fixed so the IP cache is also updated, even when DDNS provider
# responds with 'nochg', since obivously then the cached IP were
# wrong.
# 1.2.2
# * Fixed some return code checking.
# 1.2.1
# + Added more debug logging.
# 1.2.0
# + Moved the entire update ddns routine into its own function.
# + Moved the CA bundle checking and updating into its own function.
# 1.1.0
# + Changed the cURL update query to use CA bundle, for better
# security.
# 1.0.0
# + Release.
#
# Todo:
# Add support for multiple DDNS hosts, either through a conf file, or
# through a delimited string from DSM. Only with same admin user though.
# F.ex.: sub1.example.com|sub2.example.com
#
# Specify the target interface. This would probably be a hack that won't
# be visible through DSM, since it appears DSM only uses the LAN1/eth0
# interface for DDNS.
#
###############################################################################
# Set variables based on parameters.
__USERNAME__="$(echo ${@} | cut -d' ' -f1)"
__PASSWORD__="$(echo ${@} | cut -d' ' -f2)"
__HOSTNAME__="$(echo ${@} | cut -d' ' -f3)"
__DOMAIN__="$(echo ${__HOSTNAME__} | cut -d. -f2-3)"
__MYIP__="$(echo ${@} | cut -d' ' -f4)"
# Where to store the IP used for the last DNS update:
__IP_CACHE__="/tmp/gratisdns_ddnsd_ip.txt"
# Where to store the logfile:
__LOGFILE__="/var/log/gratisdns_ddnsd.log"
# Where to store the CA bundle:
__CA_BUNDLE__="/tmp/cacert.pem"
log() {
# Severity Levels:
# 0 Emergency System is unusable.
# A "panic" condition usually affecting multiple apps/servers/sites.
# At this level it would usually notify all tech staff on call.
# 1 Alert Action must be taken immediately.
# Should be corrected immediately, therefore notify staff who can fix
# the problem.An example would be the loss of a backup ISP connection
# 2 Critical Critical conditions.
# Should be corrected immediately, but indicates failure in a primary
# system, an example is a loss of primary ISP connection.
# 3 Error Error conditions.
# Non-urgent failures, these should be relayed to developers or
# admins; each item must be resolved within a given time.
# 4 Warning Warning conditions.
# Warning messages, not an error, but indication that an error will
# occur if action is not taken, e.g. file system 85% full - each item
# must be resolved within a given time.
# 5 Notice Normal but significant condition.
# Events that are unusual but not error conditions - might be
# summarized in an email to developers or admins to spot potential
# problems - no immediate action required.
# 6 Informational Informational messages.
# Normal operational messages - may be harvested for reporting,
# measuring throughput, etc - no action required.
# 7 Debug Debug-level messages.
# Info useful to developers for debugging the application, not useful
# during operations.
__LOGTIME__=$(date +"%b %e %T")
if [ "${#}" -lt 1 ]; then
false
else
__LOGMSG__="${1}"
fi
if [ "${#}" -lt 2 ]; then
__LOGPRIO__=7
else
__LOGPRIO__=${2}
fi
logger -p ${__LOGPRIO__} -t "$(basename ${0})" "${__LOGMSG__}"
echo "${__LOGTIME__} $(basename ${0}) (${__LOGPRIO__}): ${__LOGMSG__}" >> ${__LOGFILE__}
}
update_ca_bundle() {
if [ ! -z "${1}" ]; then
__CA_BUNDLE__="${1}"
else
__CA_BUNDLE__="${__CA_BUNDLE__}"
fi
log "update_ca_bundle(): __CA_BUNDLE__=${__CA_BUNDLE__}" 7
if [ ! -z "${2}" ]; then
__MAXAGE__="${2}"
else
__MAXAGE__="7"
fi
log "update_ca_bundle(): __MAXAGE__=${__MAXAGE__}" 7
local download="0"
if [ ! -f "${__CA_BUNDLE__}" ]; then
log "update_ca_bundle(): Local CA bundle doesn't exist. Set to download." 7
download="1"
fi
if [ "$(find '${__CA_BUNDLE__}' -type f -mtime +${__MAXAGE__})" = "${__CA_BUNDLE__}" ]; then
log "update_ca_bundle(): Local CA bundle older than ${__MAXAGE__} days. Set to download." 7
download="1"
fi
if [ "${download}" = "1" ]; then
curl --silent --output "${__CA_BUNDLE__}" "http://curl.haxx.se/ca/cacert.pem"
if [ "${?}" -ne "0" ]; then
log "update_ca_bundle: cURL returned error code ${?} while trying to download CA bundle." 4
fi
fi
}
update_ddns() {
if [ -z "${1}" ]; then
log "update_ddns(): Missing Argument. URL string required." 3
false
else
__URL__="${1}"
fi
# Update DNS record:
if [ -f "${__CA_BUNDLE__}" ]; then
log "update_ddns(): Updating using --cacert \"${__CA_BUNDLE__}\"." 7
__RESPONSE__=$(curl --silent --cacert "${__CA_BUNDLE__}" "${__URL__}")
else
log "update_ddns(): Updating using --insecure." 7
__RESPONSE__=$(curl --silent --insecure "${__URL__}")
fi
if [ "${?}" -ne "0" ]; then
log "update_ddns(): cURL returned error code ${?} while trying to update DDNS." 3
false
fi
log "update_ddns(): __RESPONSE__=${__RESPONSE__}" 7
# Output:
# When you write your own module, you can use the following words to tell user what happen by print it.
# You can use your own message, but there is no multiple-language support.
#
# good - Update successfully.
# nochg - Update successfully but the IP address have not changed.
# nohost - The hostname specified does not exist in this user account.
# abuse - The hostname specified is blocked for update abuse.
# notfqdn - The hostname specified is not a fully-qualified domain name.
# badauth - Authenticate failed.
# 911 - There is a problem or scheduled maintenance on provider side
# badagent - The user agent sent bad request(like HTTP method/parameters is not permitted)
# badresolv - Failed to connect to because failed to resolve provider address.
# badconn - Failed to connect to provider because connection timeout.
case ${__RESPONSE__} in
'OK<br>')
echo ${__MYIP__} > ${__IP_CACHE__}
__STATUS__='good'
true
;;
'OK<br>Opdateret i forvejen')
echo ${__MYIP__} > ${__IP_CACHE__}
__STATUS__='nochg'
true
;;
'Forkerte værdier, opdatering kan ikke laves.<br><br>A record findes ikke.'|'Domæne kan IKKE administreres af bruger')
__STATUS__='nohost'
false
;;
'Forkerte værdier, opdatering kan ikke laves.<br><br>A record findes ikke.Hostnavn er ulovligt.')
__STATUS__='notfqdn'
false
;;
'Bruger login: 1Fejl i kodeord, prøv igen. Husk serveren ser forskel på STORE Og små BOGstAvER.'|'Bruger login: Bruger eksistere ikke, husk serveren ser forskel på STORE Og smÅ BOGstAvER.')
__STATUS__='badauth'
false
;;
'Bruger login: MD5 invalid')
# The error from the provider doesn't really match up with the
# status I forward to DDNSd. But it seems that a malformed URL,
# such as if the URL isn't enclosed in quotes or contains spaces
# etc. will give this error.
__STATUS__='badagent'
false
;;
*)
__STATUS__="${__RESPONSE__}"
false
;;
esac
log "DDNSd Status: ${__STATUS__}" 6
}
# Get the current IP
#__INTERFACE_IP__="$(ifconfig ${__INTERFACE__} | sed '/inet\ /!d;s/.*r://g;s/\ .*//g')"
#__MYIP__=$(curl --silent --interface ${__INTERFACE__} "http://automation.whatismyip.com/n09230945.asp")
# Get the last known DDNS IP
if [ ! -f ${__IP_CACHE__} ]; then
# If the file wasn't found, create it to avoid errors.
echo "127.0.0.1" > ${__IP_CACHE__}
fi
__OLDIP__=$(cat ${__IP_CACHE__})
if [ "${__OLDIP__}" == "${__MYIP__}" ]; then
log "IP not changed. ${__MYIP__}. Not updating." 6
__STATUS__="nochg"
else
update_ca_bundle "${__CA_BUNDLE__}" 7
__URL__="https://ssl.gratisdns.dk/ddns.phtml?u=${__USERNAME__}&p=${__PASSWORD__}&d=${__DOMAIN__}&h=${__HOSTNAME__}&i=${__MYIP__}"
# Set the response and status variables, so they're available globally.
# That way I can read them outside the update_ddns() function.
__RESPONSE__="No response returned from DDNS provider."
__STATUS__="No status returned from update_ddns()."
log "IP changed. ${__OLDIP__} > ${__MYIP__}. Attempting to update DNS." 6
update_ddns "${__URL__}"
if [ "${?}" -ne "0" ]; then
log "update_dns(): ${__RESPONSE__}" 3
else
log "update_dns(): ${__RESPONSE__}" 6
fi
fi
printf "%s" "${__STATUS__}"
# gratisdns.sh - A GratisDNS.dk DDNSd module for Synology DSM
#
# Author:
# Brian Schmidt Pedersen (http://briped.net)
#
# Version:
# 1.2.5
#
# Description:
# This is a custom DDNSd module, for use with my Synology DiskStation. It
# allows me to use the DSM interface for GratisDNS.dk DDNS service.
#
# To use this script, put it in the "/sbin" folder of the DiskStation,
# i.e. "/sbin/gratisdns.sh"
#
# Then edit the "/etc.defaults/ddnsd_provider.conf" file and add the
# following section at the end of that file:
#
# [GratisDNS.dk]
# modulepath=/sbin/gratisdns.sh
# queryurl=https://ssl.gratisdns.dk/ddns.phtml?u=__USERNAME__&p=__PASSWORD__&d=__DOMAIN__&h=__HOSTNAME__&i=__MYIP__
#
#
# Changelist:
# 1.2.5
# * Changed update_ca_bundle() a bit, since the age checking didn't
# seem to work before.
# 1.2.4
# * Fixed script to return "nochg" when IP isn't changed from IPcache
# 1.2.3
# * Fixed so the IP cache is also updated, even when DDNS provider
# responds with 'nochg', since obivously then the cached IP were
# wrong.
# 1.2.2
# * Fixed some return code checking.
# 1.2.1
# + Added more debug logging.
# 1.2.0
# + Moved the entire update ddns routine into its own function.
# + Moved the CA bundle checking and updating into its own function.
# 1.1.0
# + Changed the cURL update query to use CA bundle, for better
# security.
# 1.0.0
# + Release.
#
# Todo:
# Add support for multiple DDNS hosts, either through a conf file, or
# through a delimited string from DSM. Only with same admin user though.
# F.ex.: sub1.example.com|sub2.example.com
#
# Specify the target interface. This would probably be a hack that won't
# be visible through DSM, since it appears DSM only uses the LAN1/eth0
# interface for DDNS.
#
###############################################################################
# Set variables based on parameters.
__USERNAME__="$(echo ${@} | cut -d' ' -f1)"
__PASSWORD__="$(echo ${@} | cut -d' ' -f2)"
__HOSTNAME__="$(echo ${@} | cut -d' ' -f3)"
__DOMAIN__="$(echo ${__HOSTNAME__} | cut -d. -f2-3)"
__MYIP__="$(echo ${@} | cut -d' ' -f4)"
# Where to store the IP used for the last DNS update:
__IP_CACHE__="/tmp/gratisdns_ddnsd_ip.txt"
# Where to store the logfile:
__LOGFILE__="/var/log/gratisdns_ddnsd.log"
# Where to store the CA bundle:
__CA_BUNDLE__="/tmp/cacert.pem"
log() {
# Severity Levels:
# 0 Emergency System is unusable.
# A "panic" condition usually affecting multiple apps/servers/sites.
# At this level it would usually notify all tech staff on call.
# 1 Alert Action must be taken immediately.
# Should be corrected immediately, therefore notify staff who can fix
# the problem.An example would be the loss of a backup ISP connection
# 2 Critical Critical conditions.
# Should be corrected immediately, but indicates failure in a primary
# system, an example is a loss of primary ISP connection.
# 3 Error Error conditions.
# Non-urgent failures, these should be relayed to developers or
# admins; each item must be resolved within a given time.
# 4 Warning Warning conditions.
# Warning messages, not an error, but indication that an error will
# occur if action is not taken, e.g. file system 85% full - each item
# must be resolved within a given time.
# 5 Notice Normal but significant condition.
# Events that are unusual but not error conditions - might be
# summarized in an email to developers or admins to spot potential
# problems - no immediate action required.
# 6 Informational Informational messages.
# Normal operational messages - may be harvested for reporting,
# measuring throughput, etc - no action required.
# 7 Debug Debug-level messages.
# Info useful to developers for debugging the application, not useful
# during operations.
__LOGTIME__=$(date +"%b %e %T")
if [ "${#}" -lt 1 ]; then
false
else
__LOGMSG__="${1}"
fi
if [ "${#}" -lt 2 ]; then
__LOGPRIO__=7
else
__LOGPRIO__=${2}
fi
logger -p ${__LOGPRIO__} -t "$(basename ${0})" "${__LOGMSG__}"
echo "${__LOGTIME__} $(basename ${0}) (${__LOGPRIO__}): ${__LOGMSG__}" >> ${__LOGFILE__}
}
update_ca_bundle() {
if [ ! -z "${1}" ]; then
__CA_BUNDLE__="${1}"
else
__CA_BUNDLE__="${__CA_BUNDLE__}"
fi
log "update_ca_bundle(): __CA_BUNDLE__=${__CA_BUNDLE__}" 7
if [ ! -z "${2}" ]; then
__MAXAGE__="${2}"
else
__MAXAGE__="7"
fi
log "update_ca_bundle(): __MAXAGE__=${__MAXAGE__}" 7
local download="0"
if [ ! -f "${__CA_BUNDLE__}" ]; then
log "update_ca_bundle(): Local CA bundle doesn't exist. Set to download." 7
download="1"
fi
if [ "$(find '${__CA_BUNDLE__}' -type f -mtime +${__MAXAGE__})" = "${__CA_BUNDLE__}" ]; then
log "update_ca_bundle(): Local CA bundle older than ${__MAXAGE__} days. Set to download." 7
download="1"
fi
if [ "${download}" = "1" ]; then
curl --silent --output "${__CA_BUNDLE__}" "http://curl.haxx.se/ca/cacert.pem"
if [ "${?}" -ne "0" ]; then
log "update_ca_bundle: cURL returned error code ${?} while trying to download CA bundle." 4
fi
fi
}
update_ddns() {
if [ -z "${1}" ]; then
log "update_ddns(): Missing Argument. URL string required." 3
false
else
__URL__="${1}"
fi
# Update DNS record:
if [ -f "${__CA_BUNDLE__}" ]; then
log "update_ddns(): Updating using --cacert \"${__CA_BUNDLE__}\"." 7
__RESPONSE__=$(curl --silent --cacert "${__CA_BUNDLE__}" "${__URL__}")
else
log "update_ddns(): Updating using --insecure." 7
__RESPONSE__=$(curl --silent --insecure "${__URL__}")
fi
if [ "${?}" -ne "0" ]; then
log "update_ddns(): cURL returned error code ${?} while trying to update DDNS." 3
false
fi
log "update_ddns(): __RESPONSE__=${__RESPONSE__}" 7
# Output:
# When you write your own module, you can use the following words to tell user what happen by print it.
# You can use your own message, but there is no multiple-language support.
#
# good - Update successfully.
# nochg - Update successfully but the IP address have not changed.
# nohost - The hostname specified does not exist in this user account.
# abuse - The hostname specified is blocked for update abuse.
# notfqdn - The hostname specified is not a fully-qualified domain name.
# badauth - Authenticate failed.
# 911 - There is a problem or scheduled maintenance on provider side
# badagent - The user agent sent bad request(like HTTP method/parameters is not permitted)
# badresolv - Failed to connect to because failed to resolve provider address.
# badconn - Failed to connect to provider because connection timeout.
case ${__RESPONSE__} in
'OK<br>')
echo ${__MYIP__} > ${__IP_CACHE__}
__STATUS__='good'
true
;;
'OK<br>Opdateret i forvejen')
echo ${__MYIP__} > ${__IP_CACHE__}
__STATUS__='nochg'
true
;;
'Forkerte værdier, opdatering kan ikke laves.<br><br>A record findes ikke.'|'Domæne kan IKKE administreres af bruger')
__STATUS__='nohost'
false
;;
'Forkerte værdier, opdatering kan ikke laves.<br><br>A record findes ikke.Hostnavn er ulovligt.')
__STATUS__='notfqdn'
false
;;
'Bruger login: 1Fejl i kodeord, prøv igen. Husk serveren ser forskel på STORE Og små BOGstAvER.'|'Bruger login: Bruger eksistere ikke, husk serveren ser forskel på STORE Og smÅ BOGstAvER.')
__STATUS__='badauth'
false
;;
'Bruger login: MD5 invalid')
# The error from the provider doesn't really match up with the
# status I forward to DDNSd. But it seems that a malformed URL,
# such as if the URL isn't enclosed in quotes or contains spaces
# etc. will give this error.
__STATUS__='badagent'
false
;;
*)
__STATUS__="${__RESPONSE__}"
false
;;
esac
log "DDNSd Status: ${__STATUS__}" 6
}
# Get the current IP
#__INTERFACE_IP__="$(ifconfig ${__INTERFACE__} | sed '/inet\ /!d;s/.*r://g;s/\ .*//g')"
#__MYIP__=$(curl --silent --interface ${__INTERFACE__} "http://automation.whatismyip.com/n09230945.asp")
# Get the last known DDNS IP
if [ ! -f ${__IP_CACHE__} ]; then
# If the file wasn't found, create it to avoid errors.
echo "127.0.0.1" > ${__IP_CACHE__}
fi
__OLDIP__=$(cat ${__IP_CACHE__})
if [ "${__OLDIP__}" == "${__MYIP__}" ]; then
log "IP not changed. ${__MYIP__}. Not updating." 6
__STATUS__="nochg"
else
update_ca_bundle "${__CA_BUNDLE__}" 7
__URL__="https://ssl.gratisdns.dk/ddns.phtml?u=${__USERNAME__}&p=${__PASSWORD__}&d=${__DOMAIN__}&h=${__HOSTNAME__}&i=${__MYIP__}"
# Set the response and status variables, so they're available globally.
# That way I can read them outside the update_ddns() function.
__RESPONSE__="No response returned from DDNS provider."
__STATUS__="No status returned from update_ddns()."
log "IP changed. ${__OLDIP__} > ${__MYIP__}. Attempting to update DNS." 6
update_ddns "${__URL__}"
if [ "${?}" -ne "0" ]; then
log "update_dns(): ${__RESPONSE__}" 3
else
log "update_dns(): ${__RESPONSE__}" 6
fi
fi
printf "%s" "${__STATUS__}"