#!/bin/sh # #.Distributed under the terms of the GNU General Public License (GPL) version 2.0 # 2023 ren dong # # 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