summaryrefslogtreecommitdiffstats
path: root/net/ddns-scripts/files/usr/lib/ddns/update_aliyun_com.sh
blob: bef6c84324172866c928ad0cdb0baedc359feaff (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
#!/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