#!/usr/bin/env bash
#
# Deyhdrated hook script for using LuaDNS
#
# (c) Greg Brackley 2017
# v1.00
#
set -e
set -u
set -o pipefail

if [ -f /etc/dehydrated/hook.conf ]; then
    . /etc/dehydrated/hook.conf
fi
export LUA_API_URL=${LUA_API_URL:-"https://api.luadns.com/v1"}
DIG=/usr/bin/dig
CURL=/usr/bin/curl

#
#  Attempt to follow a CNAME record for the certificate. This allows the DNS
#  zone in use to be in a different domain (or owned/managed by a third party).
#
#  Return the FQDN with a trailing dot.
#
function resolve_cname {
    local domain="$1"
    local cname=$( ${DIG} '+short' ${domain} CNAME )
    echo "${cname:-${domain%.}.}"
}

#
# Get the zone id from LuaDNS where the zone is the longest match for the
# provided zone name.
#
# The zone name parameter should not have a trailing dot
#
function luaDnsGetZone {
   local zone="$1"
  
   ${CURL} --silent \
	-u ${LUADNS_USERNAME}:${LUADNS_TOKEN} \
	-H 'Accept: application/json' ${LUA_API_URL}/zones | \
   jq --arg domain "${zone}" ' 
                map({ name: .name | ascii_downcase, id, nameLength: .name | length }  )                                      
                | map(select(. as $entry | $domain | endswith( $entry.name ))) 
                | sort_by(.nameLength) 
                | last
                | .id' 
}

#
#  Create a new record in the given zone
#
#  Return: the 'id' of the newly created record.
#
function luaDnsCreateRecord {
   local zoneId="$1" recordName="$2" recordContent="$3" recordTtl="$4"

   recordData=$( echo "{}" | jq --compact-output \
		--arg recordName "${recordName}" \
		--arg recordContent "${recordContent}"  \
		--arg recordTtl "$4" \
                '{ name: $recordName, type: "TXT", content: $recordContent, ttl: $recordTtl | tonumber }' )
   echo "Create record at ${LUA_API_URL}/zones/${zoneId}/records, ${recordData}" 1>&2

   record=$( ${CURL} --silent \
	-u ${LUADNS_USERNAME}:${LUADNS_TOKEN} \
	-H 'Accept: application/json' \
	-H "Content-Type: application/json" \
	--data "${recordData}" \
	-X POST \
	${LUA_API_URL}/zones/${zoneId}/records )
   echo "Record ${record}" 1>&2
   echo ${record} |  jq '."id"?'
}

#
# Get the id of a given txt records (if any).
#
function luaDnsListZoneRecords {
   local zoneId="$1" recordName="$2"
  
   ${CURL} --silent \
	-u ${LUADNS_USERNAME}:${LUADNS_TOKEN} \
	-H 'Accept: application/json' ${LUA_API_URL}/zones/${zoneId}/records | \
   jq --arg recordName "${recordName}" ' 
                map({ id, name: .name | ascii_downcase, type }  )                                      
                | map(select( .name == $recordName and .type == "TXT" )) 
                | .[]
                | .id' 
}

#
# Delete a zone record by id. Returns the id of the record deleted.
#
function luaDnsDeleteRecord {
   local zoneId="$1" recordId="$2"
   echo "Removing TXT record for '$2'" 1>&2 
   ${CURL} --silent \
	-u ${LUADNS_USERNAME}:${LUADNS_TOKEN} \
	-H 'Accept: application/json' \
	-X DELETE ${LUA_API_URL}/zones/${zoneId}/records/${recordId} | \
   jq '.id' 
}


#
#  Deploy the challenge by adding TXT record.
#
# Note: the token filename is not used for DNS-01 validation.
#
function deploy_challenge {
    local DOMAIN="${1}" TOKEN_FILENAME="${2}" TOKEN_VALUE="${3}"
    echo "deploy_challenge: ${DOMAIN}, ${TOKEN_FILENAME}, ${TOKEN_VALUE}"

    local dnsrecord=$( resolve_cname "_acme-challenge.${DOMAIN}" )

    zoneId=$( luaDnsGetZone ${dnsrecord%%.} )
    if [ -n ${zoneId} ] ; then
      echo "LuaDNS zone for '${dnsrecord}' is ${zoneId}"
        # 5 minutes is the minimum supported ttl by LuaDNS
        recordId=$( luaDnsCreateRecord ${zoneId} ${dnsrecord} ${TOKEN_VALUE} 300 )
        if [ -n ${recordId} ] ; then
           echo "Record ${recordId} created"
        else
          echo "Failed to find LuaDNS zone name for '${dnsrecord}'"
        fi
    else
      echo "Failed to find LuaDNS zone for '${dnsrecord}'"
    fi
}

#
#  Delete all acme TXT records for the given domain. If the cleanup operation
#  didn't previously complete cleaning up, then this should leave the DNS provider
#  with no records for the given acme record.
#
function clean_challenge {
    local DOMAIN="${1}" TOKEN_FILENAME="${2}" TOKEN_VALUE="${3}"

    echo "clean_challenge: ${DOMAIN}, ${TOKEN_FILENAME}, ${TOKEN_VALUE}"

    local dnsrecord=$( resolve_cname "_acme-challenge.${DOMAIN}" )
    zoneId=$( luaDnsGetZone ${dnsrecord%%.} )
    if [ -n ${zoneId} ] ; then
      echo "LuaDNS zone for '${dnsrecord}' is ${zoneId}"
        recordIds=$( luaDnsListZoneRecords ${zoneId} ${dnsrecord} )
        echo "${recordIds}" | while IFS= read -r recordId; do
            luaDnsDeleteRecord ${zoneId} ${recordId}
        done
    else
      echo "Failed to find LuaDNS zone for '${dnsrecord}'"
    fi
}

function deploy_cert {
    local DOMAIN="${1}" KEYFILE="${2}" CERTFILE="${3}" FULLCHAINFILE="${4}" CHAINFILE="${5}"
    echo "deploy_cert: ${DOMAIN}, ${KEYFILE}, ${CERTFILE}, ${FULLCHAINFILE}, ${CHAINFILE}"
}

function unchanged_cert {
    local DOMAIN="${1}" KEYFILE="${2}" CERTFILE="${3}" FULLCHAINFILE="${4}" CHAINFILE="${5}"

    echo "unchanged_cert called: ${DOMAIN}, ${KEYFILE}, ${CERTFILE}, ${FULLCHAINFILE}, ${CHAINFILE}"
}

function invalid_challenge {
    local DOMAIN="${1}" RESPONSE="${2}"
}

function request_failure {
    local STATUSCODE="${1}" REASON="${2}" REQTYPE="${3}"
}


function exit_hook {
  # This hook is called at the end of a dehydrated command and can be used
  # to do some final (cleanup or other) tasks.
  :  
}


HANDLER="$1"; shift
if [[ "${HANDLER}" =~ ^(deploy_challenge|clean_challenge|deploy_cert|unchanged_cert|invalid_challenge|request_failure|exit_hook)$ ]]; then
  "$HANDLER" "$@"
fi


