#!/bin/sh
#
#.Distributed under the terms of the GNU General Public License (GPL) version 2.0
# 2023 ren dong <lml256@foxmail.com>
#
# Aliyun DNS Documentation at https://help.aliyun.com/document_detail/29742.html
#
# This script is parsed by dynamic_dns_functions.sh inside send_update() function
#
# using following options from /etc/config/ddns
# option username - AccessKeyID generated by Aliyun
# option password - AccessKeySecret generated by Aliyun
# option domain   - "hostname@yourdomain.TLD" or "@yourdomain.TLD"
#
# variable __IP already defined with the ip-address to use for update
#

# set API URL base
__URLBASE="https://alidns.aliyuncs.com/?"

# check parameters
[ -z "$CURL" ] && [ -z "$CURL_SSL" ] && write_log 14 "Communication require cURL with SSL support. Please install"
[ -z "$username" ] && write_log 14 "Service section not configured correctly! Missing key as 'username'"
[ -z "$password" ] && write_log 14 "Service section not configured correctly! Missing secret as 'password'"

. /usr/share/libubox/jshn.sh

local __RR __HOST __DOMAIN __TYPE

# split __RR __DOMAIN from $domain
__RR=${domain%%@*}
__DOMAIN=${domain##*@}

if [ -z "$__RR" ]; then
	__RR="@" && __HOST="$__DOMAIN"
else 
	__HOST="$__RR.$__DOMAIN"
fi

# set record type
[ "$use_ipv6" -eq 0 ] && __TYPE="A" || __TYPE="AAAA"

# encode params using RFC3986 rule
encode_url_component() {
	local str1 str2 index
	str1=$(printf -- '%s' "$1" | $CURL -Gso /dev/null -w '%{url_effective}' --data-urlencode @- "aliyun.com" | cut -d "?" -f 2)
	str2=""
	index=0
	# convert the two hex numbers after '%' to uppercase
	# we need uppercase hex, and use the above code is enough on other linux platform. but
	# the curl of openwrt is a little bit different to other versions, i dont know why
	while [ "$index" -lt ${#str1} ]; do
		if [ "${str1:$index:1}" = "%" ]; then
			str2="$str2$(printf -- '%s' "${str1:$index:3}" | tr [a-z] [A-Z])" && index=$((index + 3))
		else
			str2="$str2${str1:$index:1}" && index=$((index + 1))
		fi
	done
	printf -- '%s' "$str2"
}

do_request() {
	local common_params canonicalized_query_string string_to_sign signature
	local program http_code err
	common_params="Format=JSON
				  Version=2015-01-09
				  AccessKeyId=$username
				  SignatureMethod=HMAC-SHA1
				  SignatureVersion=1.0
				  Timestamp=$(encode_url_component "$(date -u +"%Y-%m-%dT%H:%M:%SZ")")
				  SignatureNonce=$(head /dev/urandom | tr -dc '0123456789' | head -c16)"

	# build canonicalized query string, notice we use ascii order when sorting
	canonicalized_query_string="$(printf -- '%s' "$common_params $*" | sed 's/\s\+/\n/g' | LC_COLLATE=C sort | xargs | sed 's/\s/\&/g')"

	# calculate signature
	string_to_sign="GET&$(encode_url_component "/")&$(encode_url_component "$canonicalized_query_string")"
	signature="$(printf -- '%s' "$string_to_sign" | openssl sha1 -binary -hmac "$password&" | openssl base64)"
	signature="Signature=$(encode_url_component "$signature")"

	program="$CURL -sSL -o $DATFILE --stderr $ERRFILE -w '%{http_code}' \"$__URLBASE$canonicalized_query_string&$signature\""

	write_log 7 "Run command #> $program"
	http_code=$(eval "$program")
	err=$?
	[ "$err" -eq 0 ] && [ "$http_code" -eq 200 ] || {
		write_log 3 "Run command got error, curl err: $err, http_code: $http_code"
		write_log 7 "DATFILE: $(cat "$DATFILE") ERRFILE $(cat "$ERRFILE")"
		return 1
	}
}

do_request "Action=DescribeSubDomainRecords" \
	"DomainName=$(encode_url_component "$__DOMAIN")" \
	"Type=$__TYPE" \
	"SubDomain=$(encode_url_component "$__HOST")" || return 1

# load record id and record value from the response
json_load_file "$DATFILE"
json_get_var __RECORD_COUNT TotalCount

# if no record found, report error
[ "$__RECORD_COUNT" -eq 0 ] && {
	write_log 7 "DNS record of $__HOST is not exist."
	return 1
}

# extract RecordId from parameters
extract_record_id() {
    local param_enc="$1"
    local extracted_id=""
    
    # Extract RecordId from parameters safely
    if [ -n "$(echo "${param_enc}" | grep RecordId)" ]; then
        extracted_id=$(echo "$param_enc" | grep -o 'RecordId=[^&]*' | cut -d'=' -f2)
    fi
    
    echo "$extracted_id"
}

# find matching record by RecordId
find_matching_record() {
    local target_id="$1"
    local found_match=false
    
    if [ -n "$target_id" ]; then
        write_log 7 "specRecordId: ${target_id}"
        local idx=1
        while json_is_a $idx object; do
            json_select $idx
            json_get_var tmp RecordId
            write_log 7 "The $idx Domain RecordId: ${tmp}"
            if [ "$tmp" = "$target_id" ]; then
                __RECORD_ID=$target_id
                json_get_var __RECORD_VALUE Value
                write_log 7 "The $idx Domain Record Value: ${__RECORD_VALUE}"
                found_match=true
                json_select ..
                break
            fi
            idx=$((idx+1))
            json_select ..
        done
    fi
    
    if [ "$found_match" = true ]; then
        return 0
    else
        return 1
    fi
}

# select the first record as fallback
select_first_record() {
    write_log 7 "Using default logic to select record"
    
    # Check if __RECORD_COUNT is set before using it
    if [ "$__RECORD_COUNT" -gt 1 ]; then
        write_log 4 "WARNING: found multiple records of $__HOST, only use the first one"
    fi
    
    # Select the first DNS record
    json_select 1
    # Get the record id of the first DNS record
    json_get_var __RECORD_ID RecordId
    json_get_var __RECORD_VALUE Value
}

json_select DomainRecords
json_select Record

# Log the original parameter
write_log 7 "param_enc: `echo ${param_enc}`"
paramEnc=${param_enc}

# Try to extract RecordId from parameters
specRecordId=$(extract_record_id "$paramEnc")

# If RecordId is successfully extracted, try to match it
if [ -n "$specRecordId" ] && find_matching_record "$specRecordId"; then
    write_log 7 "Found matching record for ID: $specRecordId"
else
    # No matching record found or no RecordId to match, use default logic
    select_first_record
fi


# dont update if the ip has not changed
[ "$__RECORD_VALUE" = "$__IP" ] && {
	write_log 7 "DNS record is up to date"
	return 0
}

do_request "Action=UpdateDomainRecord" \
	"RR=$(encode_url_component "$__RR")" \
	"RecordId=$__RECORD_ID" \
	"Type=$__TYPE" \
	"Value=$(encode_url_component "$__IP")" || return 1

return 0
