uze (Update ZoneEdit)

Nombre: uze (Update ZoneEdit)
Autor: Patricio Silva (Pato Silva)
Lenguaje: bash
Licencia: GPL
Dependencias: lynx, awk, md5sum
Propósito: Actualizar los registros A de los servidores dns dinámicos de zoneedit para uno o mas hosts y registrar los sucesos en un archivo de log independiente. Minimiza las actualizaciones haciendo un chequeo previo de la dirección IP pública por lo que es muy eficiente. Posee detección de errores de actualización. No se ejecuta como demonio, debe hacerse la ejecución desde un cron o manualmente, personalmente lo ejecuto desde un cron cada 2 minutos.

Código:

#!/bin/sh
#
# Versión 0.4
# Actualiza los dns dinámicos de zoneedit
# Por defecto toma la configuración del archivo cuyo nombre es pasado como primer parámetro
# Si no se especifica ninguno intentará tomarla de /etc/updatezoneedit.conf
# Si de esto se cuple termina con código de salida 1

CONF="/etc/zoneeditupdate.conf"
test $# -eq 1 && CONF=$1

# Chequea que el archivo de configuracion exista
test -e $CONF || exit 1
. $CONF

# Inicializa
IP='123456789'
LISTO=NO
PID=999999
INIT=0
MD5='123456789'

# Toma la ultima IP y las variables del archivo STATE, si existe
if [ -e $STATE ]; then
  . $STATE
  IP=$IP_ADDR
  # Si LISTO es NO el proceso se está ejecutando, se espera TTL segundos
  # y si aún no termino se mata el proceso
  if [ +$LISTO = "+NO" ]; then
    let AUX=${INIT}+${TTL}
    if [ `date +%s` -gt $AUX ]; then
      # Hay que matar el proceso
      echo `date "+%d %b %r"`"$$ ATENCION: Segun el archivo $STATE el proceso $0 (PID ${PID}) se encuentra aún activo. Lleva demasiado tiempo en ejecucion, terminandolo" >> $LOG
      if [ $PID -eq "`pgrep -o $0`" ]; then
        kill -9 $PID
      else
        echo `date "+%d %b %r"`"$$ ATENCION: Imposible matar el proceso $0 (PID ${PID}): El proceso no existe o su nombre no es $0 " >> $LOG
      fi
    else
      echo `date "+%d %b %r"`"$$ El proceso ya se está ejecutando, saliendo" >> $LOG
      exit 0
    fi
  fi
fi

# Obtiene la IP Actual desde dyndns
IP_ADDR=`lynx -dump checkip.dyndns.com | grep "Current IP Address:" | awk '{ print $4}' | grep  -E '([0-9]{1,3}\.){3}[0-9]{1,3}'`

# Obtiene el hash md5 del archivo de configuracion
MD5_NUEVO=`md5sum $CONF | awk '{ print $1}'`

# Si la IP publica no se pudo determinar se loguea y se sale
test -z $IP_ADDR && echo `date "+%d %b %r"`"$$ No se pudo determinar la dirección IP actual" >> $LOG && exit 0

# Sobreescribe el archivo STATE
echo 'PID='$$ > $STATE
echo 'INIT='`date +%s` >> $STATE
echo 'IP_ADDR='${IP_ADDR} >> $STATE
echo 'MD5='${MD5_NUEVO} >> $STATE

# Si el archivo de configuracion fue modificado se cambia el estado LISTO a MD5
test +$MD5 != +$MD5_NUEVO && LISTO=MD5

# Chequea si la respuesta de dyndns ha cambiado, si no es asi sale
if [ "$IP_ADDR" = "$IP" -a +$LISTO = '+SI' -a +$MD5 = +$MD5_NUEVO ]; then
  echo `date "+%d %b %r"`"$$ La IP pública es: ${IP_ADDR}, no es necesario actualizar registros" >> $LOG
  echo 'LISTO=SI' >> $STATE
  exit 0
fi

if [ +$LISTO = '+SI' ]; then
  echo `date "+%d %b %r"`"$$ La IP pública es: ${IP_ADDR}, es necesario actualizar los registros" >> $LOG
fi

if [ +$LISTO = '+FALLO' ]; then
  echo `date "+%d %b %r"`"$$ La IP pública es: ${IP_ADDR}, la última actualización no fue exitosa, forzando la actualización de registros" >> $LOG
fi

if [ +$LISTO = '+MD5' ]; then
  echo `date "+%d %b %r"`"$$ La IP pública es: ${IP_ADDR}. \"$CONF\" ha cambiado, forzando la actualización de registros" >> $LOG
fi

ERROR=0
# Actualizaciones
for HOST in ${HOSTS}; do
  OUT=`lynx -source -auth=${USER}:${PASSWORD} ${WEB}?host=$HOST`
  AUX=$(echo $OUT | awk '{print $2}')
  case "$AUX" in
  'CODE="200"')
    echo `date "+%d %b %r"`"$$ Actualizando ${HOST}: registro actualizado correctamente" >> $LOG
  ;;
  'CODE="201"')
         echo `date "+%d %b %r"`"$$ Actualizando ${HOST}: no fue necesario actualizar el registro" >> $LOG
  ;;
  *)
    ERROR=1
    echo `date "+%d %b %r"`"$$ Actualizando ${HOST}: Falló la actualización del registro" >> $LOG
  ;;
  esac
done

test $ERROR -eq 0 && echo 'LISTO=SI' >> $STATE && exit 0
echo 'LISTO=FALLO' >> $STATE
echo `date "+%d %b %r"`"$$ La actualización no fue exitosa, se forzará la actualización de registros en la próxima ejecución" >> $LOG
exit 1

La ejecución presupone la existencia de un archivo de configuración con un contenido similar al siguiente:

# Archivo de configuracion para actualizar los dns en zoneedit
#
# /etc/zoneeditupdate.conf
#
# Archivo STATE contiene informacion acerca del estado de ejecucion del proceso
# El pid, el momento en que empezo a ejecutarse y la ultima IP conocida
#
# TTL es el tiempo maximo en segundos que el proceso puede estar ejecutandose
# Si el proceso se mantuviera en ejecucion por mas tiempo una nueva instancia del mismo
# asumirá que se encuentra zombie y lo terminará, el valor depende de la cantidad
# de host a actualizar, un valor recomendable es 900 (15 minutos)
#
# LOG es el archivo donde se registraran los eventos
#
# USER y PASSWORD son un usuario y clave de acceso valido en zoneedit
#
# WEB es la direccion a la cual se accedera, autenticandose mediante
# USER y PASSWORD, para actualizar los registros A en el DNS dinamico
#
# HOSTS Es la lista de nombres de dominio, separado por espacios y
# encerrado entre comillas, que se actualizaran

STATE="/home/user/.zoneeditupdate.state"
TTL=900
LOG="/var/log/zoneeditupdate.log"
USER='usuario_zoneedit'
PASSWORD='password_zoneedit'
WEB="http://dynamic.zoneedit.com/auth/dynamic.html"
HOSTS="www.deban.org www.esdebian.org host.dominio.zona"

Ejemplo de uso:

./uze
./uze archivo.conf

La ruta al archivo de configuración se pasa como único parámetro, si no fuera así intentará tomar la configuración del archivo /etc/updatezoneedit.conf, si nada de lo anterior se cumple terminará con código de salida 1.

Agregar como comentario que funciona, lo utilizo actualmente y nuca tuve problemas, el propósito para hacerlo fue lo problemático e inestable que me ha resultado ddclient