#!/bin/bash

#----------------------------------------------------------------------------#
# Do you have a .nu domain with a Dynamic DNS? Have you stopped using your   #
# favorite DynDNS service because Oracle bought it? Want to use NuNames own  #
# DNS solution called InstantDNS but found there are no softwares to keep    #
# the Dynamic IP updated? Then this script is for you!                       #
#                                                                            #
# NuNames insists that this solution is a very simple stage but I've found   #
# that it is perfectly fine for my .nu domain with some A, CNAMES, TXT and   #
# MX records. Plus its extremely nice to have both the registrar and DNS     #
# management in the same location.                                           #
#                                                                            #
# Version 1.0                                                                #
#                                                                            #
# What you need to do/change below.                                          #
# Log in to the DNS Manager. Take note of the zone_id in the URL. You need   #
# to set this URL/Zone ID in the FetchURL setting.                           #
#                                                                            #
# NunamesUser and NunamePass should be your email and password to log in to  #
# the dashboard.                                                             #
#                                                                            #
# Set hostname to the NAME of the TYPE A record you want to check/update.    #
# Do not specify the trailing dot that is required at nunames.               #
#                                                                            #
# The script checks with checkip.dyndns.org what your current external IP is.#
# If you have a better/other method to check this, change the                #
# ExternalIPCommand to what to execute to give you your external IP.         #
# The result of the command should be the IP and only that.                  #
#                                                                            #
# Once configured, run it. It will not actually update anything while        #
# TESTMODE is set to TRUE. In this state, debug mode is always enabled.      #
# When you are happy with the result, set TESTMODE="FALSE"                   #
#                                                                            #
# Set permissions on the script so only you can read it.                     #
# Perhaps: chmod 700 <scriptname.sh>                                         #
#                                                                            #
# Run script with "debug" as the first and only argument to see what it is   #
# doing.                                                                     #
#                                                                            #
# (c) Turranius 2023. https://grandis.nu                                     #
#                                                                            #
#--[ Settings ]--------------------------------------------------------------#

## URL to log in to. Should not have to change this.
LoginURL="https://my.nudomain.nu/dologin.php"

## URL to get your data from. Log in to the DNS manager and check your zone_id. 0000 must be changed.
FetchURL="https://my.nudomain.nu/index.php?m=DNSManager2&mg-action=editZone&zone_id=0000"

## URL to use when changing the IP. Should not need to be changed.
ChangeURL="https://my.nudomain.nu/index.php?m=DNSManager2&json=1&mg-page=dashboard"

## Your username for nunames
NunamesUser="my@nameis.nu"

## Your password.
NunamesPass="wordpass"

## Hostname to check/change in your NAME. Case sensitive. Do not specify the final dot (.).
hostname="mysweetdomain.nu"

## Command to get your current external IP. Should not need to change this unless you have a better method.
ExternalIPCommand="curl -s http://checkip.dyndns.org | tr -d ' ' | cut -d ':' -f2 | cut -d '<' -f1"

## Set this to FALSE to actually do the change.
TESTMODE="TRUE"

#--[ Script Start ]----------------------------------------------------------#

if [ "$TESTMODE" = "TRUE" ]; then
  printf "TESTMODE is not FALSE. Not actually doing any changes.\n\n"
  DEBUG="TRUE"
fi

## Verify that curl is installed and in the path.
if [ -z "$(which curl)" ]; then
  printf "Error. This script requires curl. Make sure its in your path.\n"
  exit 1
fi

## Enable debug mode. If first argument is "debug" it will say what its doing.
if [ "$1" = "debug" ]; then
  DEBUG="TRUE"
else
  curlparm="-s"
fi

## Procedure to output text if DEBUG is TRUE (First arg was "debug").
proc_debug() {
  if [ "$DEBUG" = "TRUE" ]; then
    printf "$@\n"
  fi
}

## Procedure to verify that a string is an actual IP. Used for security to make sure we get/set the correct information.
proc_valid_ip()
{
    local  IPA1=$1
    local  stat=1
    if [[ $IPA1 =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]];
    then
        OIFS=$IFS # Save the actual IFS in a var named OIFS
        IFS='.'   # IFS (Internal Field Separator) set to .
        ip=($ip)  # Converts $ip into an array saving ip fields on it
        IFS=$OIFS # Restore the old IFS

        [[ ${ip[0]} -le 255 && ${ip[1]} -le 255 && ${ip[2]} -le 255 && ${ip[3]} -le 255 ]]  # If $ip[0], $ip[1], $ip[2] and $ip[3] are minor or equal than 255 then

        stat=$? # $stat is equal to 0 if is a valid IP or 1 if it isn't

    fi # End if
    echo "$stat"
}

## Procedure to clean up before quitting.
proc_cleanup() {
  Exitcode=$1
  proc_debug "\nExecuting proc_cleanup to clean up files."
  if [ -f "/tmp/nunamescjar" ]; then
    rm -f "/tmp/nunamescjar"
  fi
  if [ -f "/tmp/nunames_fetchurl.html" ]; then
    rm -f "/tmp/nunames_fetchurl.html"
  fi
  if [ -f "/tmp/nunames_fetchurl_verify.html" ]; then
    rm -f "/tmp/nunames_fetchurl_verify.html"
  fi
  exit $Exitcode
}

## Get current external IP by running the ExternalIPCommand.
CurrentIP="$(eval ${ExternalIPCommand})"
if [ -z "$CurrentIP" ]; then
  proc_debug "Failed to get your current IP from running ExternalIPCommand - Pausing 5 seconds and trying again."
  sleep 5
  CurrentIP="$(eval ${ExternalIPCommand})"
  if [ -z "$CurrentIP" ]; then
    echo "Failed to get your current IP from running ExternalIPCommand - Aborting."
    proc_cleanup 1
  fi
else
  proc_debug "Current IP: $CurrentIP"
fi

## Validate that the $CurrentIP returned is indeed a valid IP number so we got the right information.
if [ "$(proc_valid_ip "${CurrentIP}")" = "0" ]; then
  proc_debug "Validated that ${CurrentIP} is a valid IP.\n"
else
  echo "Not a valid IP: ${CurrentIP} - Aborting."
  proc_cleanup 1
fi

## Is $LoginURL up?
res=$( curl --connect-timeout 10 -w %{http_code} -s --output /dev/null $LoginURL)
if [ "$res" != "200" ] && [ "$res" != "302" ] ; then
  echo "Website seems down (Status:$res). checked: $LoginURL"
  proc_cleanup 1
fi

## Initialize cookie
curl --cookie-jar /tmp/nunamescjar --output /dev/null $curlparm $LoginURL
## Log on to site. Store in cookie.
curl --cookie-jar /tmp/nunamescjar $curlparm --data "username=${NunamesUser}" --data "password=${NunamesPass}" --data 'do=login' --data 's=""' --data 'securitytoken=guest' --data 'cookieuser=1' --output /dev/null $LoginURL

if [ ! -f "/tmp/nunamescjar" ]; then
  echo "Error. Cookie was not created correctly. Login OK?"
  proc_cleanup 1
fi

## Grab the FetchURL, using the logged in cookie. This is the entire dashboard with all the information.
curl --cookie /tmp/nunamescjar --cookie-jar /tmp/nunamescjar $curlparm --output /tmp/nunames_fetchurl.html $FetchURL

## Check that the file is there. Cleanup and quit if its not.
if [ ! -f "/tmp/nunames_fetchurl.html" ]; then
  echo "nunamesDNS.sh error. File /tmp/nunames_fetchurl.html does not exist after download."
  proc_cleanup 1
fi

## Get the record number of the hostname. This will be used to find the other values below. Only get the first A record.
RecordNumber="$(grep -A4 "\[name\]\"\ value=\"${hostname}\.\"" "/tmp/nunames_fetchurl.html" | grep "\[type\]\"\ value=\"A\"\/>" | cut -d '[' -f2 | cut -d ']' -f1 | head -1)"
if [ -z "$RecordNumber" ]; then
  echo "Failed to get record number for ${hostname}. Aborting."
  echo "Make sure your username and password are correct."
  proc_cleanup 1
else
  proc_debug "\nRecord number for $hostname found: ${RecordNumber}"
fi

## Get the line number of the host.
LineNumber="$(grep "name=\"record\[${RecordNumber}\]\[line\]\""  /tmp/nunames_fetchurl.html | tr -s ' ' | cut -d '"' -f6)"
if [ -z "$LineNumber" ]; then
  echo "Failed to get line number (record[${RecordNumber}][line]) for ${hostname}. Aborting."
  proc_cleanup 1
else
  proc_debug "Line number for $hostname found: ${LineNumber}"
fi

## Get the IP that is currently defined for this hostname.
DefinedIP="$(grep "name=\"record\[${RecordNumber}\]\[field\]\[address\]\""  /tmp/nunames_fetchurl.html | tr -s ' ' | cut -d ' ' -f7 | cut -d '"' -f2)"
if [ -z "$DefinedIP" ]; then
  echo "Failed to get IP for ${hostname}. Aborting."
  proc_cleanup 1
else
  proc_debug "Currently set to: ${DefinedIP}"
fi

## Validate that the information we got is actually an IP number.
if [ "$(proc_valid_ip "${DefinedIP}")" = "0" ]; then
  proc_debug "Validated that ${DefinedIP} is a valid IP."
else
  echo "Not a valid IP: ${DefinedIP} - Aborting."
  proc_cleanup 1
fi

## Check if the actual IP matches the IP on record.
if [ "${DefinedIP}" == "${CurrentIP}" ]; then
  proc_debug "Match. All OK so aborting."
  proc_cleanup 0
else

  proc_debug "NO MATCH - ${DefinedIP} <> ${CurrentIP}"

  ## Get the ZoneID from the FetchURL.
  ZoneID="$(echo "${FetchURL}" | cut -d '&' -f3)"
  proc_debug "ZoneID to use: ${ZoneID}"

  ## Build the URL to update the IP to the current one.
  DataToUse="${ZoneID}&edit_record[${RecordNumber}][name]=${hostname}.&edit_record[${RecordNumber}][type]=A&edit_record[${RecordNumber}][ttl]=14440&edit_record[${RecordNumber}][field][address]=${CurrentIP}&edit_record[${RecordNumber}][line]=${LineNumber}&mg-action=editZoneSave"

  proc_debug "Final data to send: $DataToUse\n"

  ## Actually do the update.
  if [ $TESTMODE = "FALSE" ]; then
    curl "${ChangeURL}" --cookie /tmp/nunamescjar --cookie-jar /tmp/cnamescjar -H 'Sec-GPC: 1' --data-raw "${DataToUse}" --output /dev/null
  else
    proc_debug "NOT ACTUALLY UPDATING ANYTHING AS TESTMODE IS NOT FALSE."
    proc_debug "THE VERIFICATION BELOW WILL FAIL. THAT IS OK WHEN TESTING.\n"
  fi

  ##### Validate that the IP was changed section #####

  proc_debug "Starting verification phase...\n"

  ## Grab the FetchURL, using the logged in cookie. This is the entire dashboard with all the information. We already have the cookie and everything.
  curl --cookie /tmp/nunamescjar --cookie-jar /tmp/nunamescjar $curlparm --output /tmp/nunames_fetchurl_verify.html $FetchURL

  ## Check that the file is there. Cleanup and quit if its not.
  if [ ! -f "/tmp/nunames_fetchurl_verify.html" ]; then
    echo "nunamesDNS.sh error. File /tmp/nunames_fetchurl_verify.html does not exist after download."
    proc_cleanup 1
  fi

  ## Get the IP that is currently defined for this hostname. We already have the RecordNumber.
  VerifyIP="$(grep "name=\"record\[${RecordNumber}\]\[field\]\[address\]\""  /tmp/nunames_fetchurl_verify.html | tr -s ' ' | cut -d ' ' -f7 | cut -d '"' -f2)"
  if [ -z "$VerifyIP" ]; then
    echo "Failed to get IP for ${hostname} when verifying. Aborting."
    proc_cleanup 1
  else
    proc_debug "\nWhen verifying, the set IP is: ${VerifyIP}"
  fi

  ## Validate that the information we got is actually an IP number.
  if [ "$(proc_valid_ip "${VerifyIP}")" = "0" ]; then
    proc_debug "Validated that ${VerifyIP} is a valid IP."
  else
    echo "Not a valid IP: ${VerifyIP} - Aborting."
    proc_cleanup 1
  fi

  if [ "${VerifyIP}" == "${CurrentIP}" ]; then
    proc_debug "Match. Change of IP completed."
  else
    echo "Error verifying that the new IP was set correctly. Should be ${CurrentIP} but its currently set to ${VerifyIP} - Something went wrong or TESTMODE is not set to FALSE"
    proc_cleanup 1
  fi

fi

proc_cleanup 0
