Forráskód Böngészése

Merge pull request #5310 from acmesh-official/dev

sync
neil 1 éve
szülő
commit
eaf11009d1

+ 13 - 2
.github/workflows/dockerhub.yml

@@ -15,6 +15,8 @@ concurrency:
   group: ${{ github.workflow }}-${{ github.ref }}
   cancel-in-progress: true
 
+env:
+  DOCKER_IMAGE: neilpang/acme.sh
 
 jobs:
   CheckToken:
@@ -44,6 +46,11 @@ jobs:
         uses: actions/checkout@v4
       - name: Set up QEMU
         uses: docker/setup-qemu-action@v2
+      - name: Extract Docker metadata
+        id: meta
+        uses: docker/[email protected]
+        with:
+          images: ${DOCKER_IMAGE}
       - name: Set up Docker Buildx
         uses: docker/setup-buildx-action@v2
       - name: login to docker hub
@@ -51,8 +58,6 @@ jobs:
           echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u "${{ secrets.DOCKER_USERNAME }}" --password-stdin
       - name: build and push the image
         run: |
-          DOCKER_IMAGE=neilpang/acme.sh
-
           if [[ $GITHUB_REF == refs/tags/* ]]; then
             DOCKER_IMAGE_TAG=${GITHUB_REF#refs/tags/}
           fi
@@ -66,8 +71,14 @@ jobs:
             fi
           fi
 
+          DOCKER_LABELS=()
+          while read -r label; do
+            DOCKER_LABELS+=(--label "${label}")
+          done <<<"${DOCKER_METADATA_OUTPUT_LABELS}"
+
           docker buildx build \
             --tag ${DOCKER_IMAGE}:${DOCKER_IMAGE_TAG} \
+            "${DOCKER_LABELS[@]}" \
             --output "type=image,push=true" \
             --build-arg AUTO_UPGRADE=${AUTO_UPGRADE} \
             --platform linux/arm64/v8,linux/amd64,linux/arm/v6,linux/arm/v7,linux/386,linux/ppc64le,linux/s390x .

+ 19 - 1
acme.sh

@@ -1,6 +1,6 @@
 #!/usr/bin/env sh
 
-VER=3.0.9
+VER=3.1.0
 
 PROJECT_NAME="acme.sh"
 
@@ -672,8 +672,10 @@ _hex_dump() {
 #0  1  2  3  4  5  6  7  8  9  -  _  .  ~
 #30 31 32 33 34 35 36 37 38 39 2d 5f 2e 7e
 
+#_url_encode [upper-hex]  the encoded hex will be upper-case if the argument upper-hex is followed
 #stdin stdout
 _url_encode() {
+  _upper_hex=$1
   _hex_str=$(_hex_dump)
   _debug3 "_url_encode"
   _debug3 "_hex_str" "$_hex_str"
@@ -883,6 +885,9 @@ _url_encode() {
       ;;
     #other hex
     *)
+      if [ "$_upper_hex" = "upper-hex" ]; then
+        _hex_code=$(printf "%s" "$_hex_code" | _upper_case)
+      fi
       printf '%%%s' "$_hex_code"
       ;;
     esac
@@ -5111,6 +5116,19 @@ $_authorizations_map"
         _on_issue_err "$_post_hook" "$vlist"
         return 1
       fi
+      _retryafter=$(echo "$responseHeaders" | grep -i "^Retry-After *: *[0-9]\+ *" | cut -d : -f 2 | tr -d ' ' | tr -d '\r')
+      _sleep_overload_retry_sec=$_retryafter
+      if [ "$_sleep_overload_retry_sec" ]; then
+        if [ $_sleep_overload_retry_sec -le 600 ]; then
+          _sleep $_sleep_overload_retry_sec
+        else
+          _info "The retryafter=$_retryafter value is too large (> 600), will not retry anymore."
+          _clearupwebbroot "$_currentRoot" "$removelevel" "$token"
+          _clearup
+          _on_issue_err "$_post_hook" "$vlist"
+          return 1
+        fi
+      fi
     done
 
   done

+ 19 - 88
deploy/ali_cdn.sh

@@ -1,17 +1,23 @@
 #!/usr/bin/env sh
+# shellcheck disable=SC2034,SC2154
 
 # Script to create certificate to Alibaba Cloud CDN
 #
+# Docs: https://github.com/acmesh-official/acme.sh/wiki/deployhooks#33-deploy-your-certificate-to-cdn-or-dcdn-of-alibaba-cloud-aliyun
+#
 # This deployment required following variables
 # export Ali_Key="ALIACCESSKEY"
 # export Ali_Secret="ALISECRETKEY"
+# The credentials are shared with all the Alibaba Cloud deploy hooks and dnsapi
+#
+# To specify the CDN domain that is different from the certificate CN, usually used for multi-domain or wildcard certificates
 # export DEPLOY_ALI_CDN_DOMAIN="cdn.example.com"
-# If you have more than one domain, just
+# If you have multiple CDN domains using the same certificate, just
 # export DEPLOY_ALI_CDN_DOMAIN="cdn1.example.com cdn2.example.com"
 #
-# The credentials are shared with all domains, also shared with dns_ali api
+# For DCDN, see ali_dcdn deploy hook
 
-Ali_API="https://cdn.aliyuncs.com/"
+Ali_CDN_API="https://cdn.aliyuncs.com/"
 
 ali_cdn_deploy() {
   _cdomain="$1"
@@ -26,18 +32,16 @@ ali_cdn_deploy() {
   _debug _cca "$_cca"
   _debug _cfullchain "$_cfullchain"
 
-  Ali_Key="${Ali_Key:-$(_readaccountconf_mutable Ali_Key)}"
-  Ali_Secret="${Ali_Secret:-$(_readaccountconf_mutable Ali_Secret)}"
-  if [ -z "$Ali_Key" ] || [ -z "$Ali_Secret" ]; then
-    Ali_Key=""
-    Ali_Secret=""
-    _err "You don't specify aliyun api key and secret yet."
+  # Load dnsapi/dns_ali.sh to reduce the duplicated codes
+  # https://github.com/acmesh-official/acme.sh/pull/5205#issuecomment-2357867276
+  dnsapi_ali="$(_findHook "$_cdomain" "$_SUB_FOLDER_DNSAPI" dns_ali)"
+  # shellcheck source=/dev/null
+  if ! . "$dnsapi_ali"; then
+    _err "Error loading file $dnsapi_ali. Please check your API file and try again."
     return 1
   fi
 
-  #save the api key and secret to the account conf file.
-  _saveaccountconf_mutable Ali_Key "$Ali_Key"
-  _saveaccountconf_mutable Ali_Secret "$Ali_Secret"
+  _prepare_ali_credentials || return 1
 
   _getdeployconf DEPLOY_ALI_CDN_DOMAIN
   if [ "$DEPLOY_ALI_CDN_DOMAIN" ]; then
@@ -47,8 +51,8 @@ ali_cdn_deploy() {
   fi
 
   # read cert and key files and urlencode both
-  _cert=$(_url_encode_upper <"$_cfullchain")
-  _key=$(_url_encode_upper <"$_ckey")
+  _cert=$(_url_encode upper-hex <"$_cfullchain")
+  _key=$(_url_encode upper-hex <"$_ckey")
 
   _debug2 _cert "$_cert"
   _debug2 _key "$_key"
@@ -64,82 +68,9 @@ ali_cdn_deploy() {
   return 0
 }
 
-####################  Private functions below ##################################
-
-# act ign mtd
-_ali_rest() {
-  act="$1"
-  ign="$2"
-  mtd="$3"
-
-  signature=$(printf "%s" "$mtd&%2F&$(_ali_urlencode "$query")" | _hmac "sha1" "$(printf "%s" "$Ali_Secret&" | _hex_dump | tr -d " ")" | _base64)
-  signature=$(_ali_urlencode "$signature")
-  url="$Ali_API?$query&Signature=$signature"
-
-  if [ "$mtd" = "GET" ]; then
-    response="$(_get "$url")"
-  else
-    # post payload is not supported yet because of signature
-    response="$(_post "" "$url")"
-  fi
-
-  _ret="$?"
-  _debug2 response "$response"
-  if [ "$_ret" != "0" ]; then
-    _err "Error <$act>"
-    return 1
-  fi
-
-  if [ -z "$ign" ]; then
-    message="$(echo "$response" | _egrep_o "\"Message\":\"[^\"]*\"" | cut -d : -f 2 | tr -d \")"
-    if [ "$message" ]; then
-      _err "$message"
-      return 1
-    fi
-  fi
-}
-
-_ali_urlencode() {
-  _str="$1"
-  _str_len=${#_str}
-  _u_i=1
-  while [ "$_u_i" -le "$_str_len" ]; do
-    _str_c="$(printf "%s" "$_str" | cut -c "$_u_i")"
-    case $_str_c in [a-zA-Z0-9.~_-])
-      printf "%s" "$_str_c"
-      ;;
-    *)
-      printf "%%%02X" "'$_str_c"
-      ;;
-    esac
-    _u_i="$(_math "$_u_i" + 1)"
-  done
-}
-
-_ali_nonce() {
-  #_head_n 1 </dev/urandom | _digest "sha256" hex | cut -c 1-31
-  #Not so good...
-  date +"%s%N" | sed 's/%N//g'
-}
-
-_timestamp() {
-  date -u +"%Y-%m-%dT%H%%3A%M%%3A%SZ"
-}
-
-# stdin stdout
-_url_encode_upper() {
-  encoded=$(_url_encode)
-
-  for match in $(echo "$encoded" | _egrep_o '%..' | sort -u); do
-    upper=$(echo "$match" | _upper_case)
-    encoded=$(echo "$encoded" | sed "s/$match/$upper/g")
-  done
-
-  echo "$encoded"
-}
-
 # domain pub pri
 _set_cdn_domain_ssl_certificate_query() {
+  endpoint=$Ali_CDN_API
   query=''
   query=$query'AccessKeyId='$Ali_Key
   query=$query'&Action=SetCdnDomainSSLCertificate'

+ 88 - 0
deploy/ali_dcdn.sh

@@ -0,0 +1,88 @@
+#!/usr/bin/env sh
+# shellcheck disable=SC2034,SC2154
+
+# Script to create certificate to Alibaba Cloud DCDN
+#
+# Docs: https://github.com/acmesh-official/acme.sh/wiki/deployhooks#33-deploy-your-certificate-to-cdn-or-dcdn-of-alibaba-cloud-aliyun
+#
+# This deployment required following variables
+# export Ali_Key="ALIACCESSKEY"
+# export Ali_Secret="ALISECRETKEY"
+# The credentials are shared with all the Alibaba Cloud deploy hooks and dnsapi
+#
+# To specify the DCDN domain that is different from the certificate CN, usually used for multi-domain or wildcard certificates
+# export DEPLOY_ALI_DCDN_DOMAIN="dcdn.example.com"
+# If you have multiple CDN domains using the same certificate, just
+# export DEPLOY_ALI_DCDN_DOMAIN="dcdn1.example.com dcdn2.example.com"
+#
+# For regular CDN, see ali_cdn deploy hook
+
+Ali_DCDN_API="https://dcdn.aliyuncs.com/"
+
+ali_dcdn_deploy() {
+  _cdomain="$1"
+  _ckey="$2"
+  _ccert="$3"
+  _cca="$4"
+  _cfullchain="$5"
+
+  _debug _cdomain "$_cdomain"
+  _debug _ckey "$_ckey"
+  _debug _ccert "$_ccert"
+  _debug _cca "$_cca"
+  _debug _cfullchain "$_cfullchain"
+
+  # Load dnsapi/dns_ali.sh to reduce the duplicated codes
+  # https://github.com/acmesh-official/acme.sh/pull/5205#issuecomment-2357867276
+  dnsapi_ali="$(_findHook "$_cdomain" "$_SUB_FOLDER_DNSAPI" dns_ali)"
+  # shellcheck source=/dev/null
+  if ! . "$dnsapi_ali"; then
+    _err "Error loading file $dnsapi_ali. Please check your API file and try again."
+    return 1
+  fi
+
+  _prepare_ali_credentials || return 1
+
+  _getdeployconf DEPLOY_ALI_DCDN_DOMAIN
+  if [ "$DEPLOY_ALI_DCDN_DOMAIN" ]; then
+    _savedeployconf DEPLOY_ALI_DCDN_DOMAIN "$DEPLOY_ALI_DCDN_DOMAIN"
+  else
+    DEPLOY_ALI_DCDN_DOMAIN="$_cdomain"
+  fi
+
+  # read cert and key files and urlencode both
+  _cert=$(_url_encode upper-hex <"$_cfullchain")
+  _key=$(_url_encode upper-hex <"$_ckey")
+
+  _debug2 _cert "$_cert"
+  _debug2 _key "$_key"
+
+  ## update domain ssl config
+  for domain in $DEPLOY_ALI_DCDN_DOMAIN; do
+    _set_dcdn_domain_ssl_certificate_query "$domain" "$_cert" "$_key"
+    if _ali_rest "Set DCDN domain SSL certificate for $domain" "" POST; then
+      _info "Domain $domain certificate has been deployed successfully"
+    fi
+  done
+
+  return 0
+}
+
+# domain pub pri
+_set_dcdn_domain_ssl_certificate_query() {
+  endpoint=$Ali_DCDN_API
+  query=''
+  query=$query'AccessKeyId='$Ali_Key
+  query=$query'&Action=SetDcdnDomainSSLCertificate'
+  query=$query'&CertType=upload'
+  query=$query'&DomainName='$1
+  query=$query'&Format=json'
+  query=$query'&SSLPri='$3
+  query=$query'&SSLProtocol=on'
+  query=$query'&SSLPub='$2
+  query=$query'&SignatureMethod=HMAC-SHA1'
+  query=$query"&SignatureNonce=$(_ali_nonce)"
+  query=$query'&SignatureVersion=1.0'
+  query=$query'&Timestamp='$(_timestamp)
+  query=$query'&Version=2018-01-15'
+}

+ 72 - 61
dnsapi/dns_ali.sh

@@ -9,25 +9,19 @@ Options:
  Ali_Secret API Secret
 '
 
-Ali_API="https://alidns.aliyuncs.com/"
+# NOTICE:
+# This file is referenced by Alibaba Cloud Services deploy hooks
+# https://github.com/acmesh-official/acme.sh/pull/5205#issuecomment-2357867276
+# Be careful when modifying this file, especially when making breaking changes for common functions
+
+Ali_DNS_API="https://alidns.aliyuncs.com/"
 
 #Usage: dns_ali_add   _acme-challenge.www.domain.com   "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
 dns_ali_add() {
   fulldomain=$1
   txtvalue=$2
 
-  Ali_Key="${Ali_Key:-$(_readaccountconf_mutable Ali_Key)}"
-  Ali_Secret="${Ali_Secret:-$(_readaccountconf_mutable Ali_Secret)}"
-  if [ -z "$Ali_Key" ] || [ -z "$Ali_Secret" ]; then
-    Ali_Key=""
-    Ali_Secret=""
-    _err "You don't specify aliyun api key and secret yet."
-    return 1
-  fi
-
-  #save the api key and secret to the account conf file.
-  _saveaccountconf_mutable Ali_Key "$Ali_Key"
-  _saveaccountconf_mutable Ali_Secret "$Ali_Secret"
+  _prepare_ali_credentials || return 1
 
   _debug "First detect the root zone"
   if ! _get_root "$fulldomain"; then
@@ -52,7 +46,67 @@ dns_ali_rm() {
   _clean
 }
 
-####################  Private functions below ##################################
+####################  Alibaba Cloud common functions below  ####################
+
+_prepare_ali_credentials() {
+  Ali_Key="${Ali_Key:-$(_readaccountconf_mutable Ali_Key)}"
+  Ali_Secret="${Ali_Secret:-$(_readaccountconf_mutable Ali_Secret)}"
+  if [ -z "$Ali_Key" ] || [ -z "$Ali_Secret" ]; then
+    Ali_Key=""
+    Ali_Secret=""
+    _err "You don't specify aliyun api key and secret yet."
+    return 1
+  fi
+
+  #save the api key and secret to the account conf file.
+  _saveaccountconf_mutable Ali_Key "$Ali_Key"
+  _saveaccountconf_mutable Ali_Secret "$Ali_Secret"
+}
+
+# act ign mtd
+_ali_rest() {
+  act="$1"
+  ign="$2"
+  mtd="${3:-GET}"
+
+  signature=$(printf "%s" "$mtd&%2F&$(printf "%s" "$query" | _url_encode upper-hex)" | _hmac "sha1" "$(printf "%s" "$Ali_Secret&" | _hex_dump | tr -d " ")" | _base64)
+  signature=$(printf "%s" "$signature" | _url_encode upper-hex)
+  url="$endpoint?Signature=$signature"
+
+  if [ "$mtd" = "GET" ]; then
+    url="$url&$query"
+    response="$(_get "$url")"
+  else
+    response="$(_post "$query" "$url" "" "$mtd" "application/x-www-form-urlencoded")"
+  fi
+
+  _ret="$?"
+  _debug2 response "$response"
+  if [ "$_ret" != "0" ]; then
+    _err "Error <$act>"
+    return 1
+  fi
+
+  if [ -z "$ign" ]; then
+    message="$(echo "$response" | _egrep_o "\"Message\":\"[^\"]*\"" | cut -d : -f 2 | tr -d \")"
+    if [ "$message" ]; then
+      _err "$message"
+      return 1
+    fi
+  fi
+}
+
+_ali_nonce() {
+  #_head_n 1 </dev/urandom | _digest "sha256" hex | cut -c 1-31
+  #Not so good...
+  date +"%s%N" | sed 's/%N//g'
+}
+
+_timestamp() {
+  date -u +"%Y-%m-%dT%H%%3A%M%%3A%SZ"
+}
+
+####################  Private functions below  ####################
 
 _get_root() {
   domain=$1
@@ -83,52 +137,10 @@ _get_root() {
   return 1
 }
 
-_ali_rest() {
-  signature=$(printf "%s" "GET&%2F&$(_ali_urlencode "$query")" | _hmac "sha1" "$(printf "%s" "$Ali_Secret&" | _hex_dump | tr -d " ")" | _base64)
-  signature=$(_ali_urlencode "$signature")
-  url="$Ali_API?$query&Signature=$signature"
-
-  if ! response="$(_get "$url")"; then
-    _err "Error <$1>"
-    return 1
-  fi
-
-  _debug2 response "$response"
-  if [ -z "$2" ]; then
-    message="$(echo "$response" | _egrep_o "\"Message\":\"[^\"]*\"" | cut -d : -f 2 | tr -d \")"
-    if [ "$message" ]; then
-      _err "$message"
-      return 1
-    fi
-  fi
-}
-
-_ali_urlencode() {
-  _str="$1"
-  _str_len=${#_str}
-  _u_i=1
-  while [ "$_u_i" -le "$_str_len" ]; do
-    _str_c="$(printf "%s" "$_str" | cut -c "$_u_i")"
-    case $_str_c in [a-zA-Z0-9.~_-])
-      printf "%s" "$_str_c"
-      ;;
-    *)
-      printf "%%%02X" "'$_str_c"
-      ;;
-    esac
-    _u_i="$(_math "$_u_i" + 1)"
-  done
-}
-
-_ali_nonce() {
-  #_head_n 1 </dev/urandom | _digest "sha256" hex | cut -c 1-31
-  #Not so good...
-  date +"%s%N" | sed 's/%N//g'
-}
-
 _check_exist_query() {
   _qdomain="$1"
   _qsubdomain="$2"
+  endpoint=$Ali_DNS_API
   query=''
   query=$query'AccessKeyId='$Ali_Key
   query=$query'&Action=DescribeDomainRecords'
@@ -144,6 +156,7 @@ _check_exist_query() {
 }
 
 _add_record_query() {
+  endpoint=$Ali_DNS_API
   query=''
   query=$query'AccessKeyId='$Ali_Key
   query=$query'&Action=AddDomainRecord'
@@ -160,6 +173,7 @@ _add_record_query() {
 }
 
 _delete_record_query() {
+  endpoint=$Ali_DNS_API
   query=''
   query=$query'AccessKeyId='$Ali_Key
   query=$query'&Action=DeleteDomainRecord'
@@ -173,6 +187,7 @@ _delete_record_query() {
 }
 
 _describe_records_query() {
+  endpoint=$Ali_DNS_API
   query=''
   query=$query'AccessKeyId='$Ali_Key
   query=$query'&Action=DescribeDomainRecords'
@@ -203,7 +218,3 @@ _clean() {
   fi
 
 }
-
-_timestamp() {
-  date -u +"%Y-%m-%dT%H%%3A%M%%3A%SZ"
-}

+ 1 - 2
dnsapi/dns_doapi.sh

@@ -2,7 +2,6 @@
 # shellcheck disable=SC2034
 dns_doapi_info='Domain-Offensive do.de
  Official LetsEncrypt API for do.de / Domain-Offensive.
- This is different from the dns_do adapter, because dns_do is only usable for enterprise customers.
  This API is also available to private customers/individuals.
 Site: do.de
 Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_doapi
@@ -11,7 +10,7 @@ Options:
 Issues: github.com/acmesh-official/acme.sh/issues/2057
 '
 
-DO_API="https://www.do.de/api/letsencrypt"
+DO_API="https://my.do.de/api/letsencrypt"
 
 ########  Public functions #####################
 

+ 1 - 1
dnsapi/dns_gcore.sh

@@ -28,7 +28,7 @@ dns_gcore_add() {
   fi
 
   #save the api key to the account conf file.
-  _saveaccountconf_mutable GCORE_Key "$GCORE_Key"
+  _saveaccountconf_mutable GCORE_Key "$GCORE_Key" "base64"
 
   _debug "First detect the zone name"
   if ! _get_root "$fulldomain"; then

+ 2 - 2
dnsapi/dns_openprovider.sh

@@ -68,7 +68,7 @@ dns_openprovider_add() {
         new_item="$(echo "$item" | sed -n 's/.*<item>.*\(<name>\(.*\)'"$_domain_name"'\.'"$_domain_extension"'<\/name>.*\(<type>.*<\/type>\).*\(<value>.*<\/value>\).*\(<prio>.*<\/prio>\).*\(<ttl>.*<\/ttl>\)\).*<\/item>.*/<item><name>\2<\/name>\3\4\5\6<\/item>/p')"
       fi
 
-      if [ -z "$(echo "$new_item" | _egrep_o ".*<type>(A|AAAA|CNAME|MX|SPF|SRV|TXT|TLSA|SSHFP|CAA|NS)<\/type>.*")" ]; then
+      if [ -z "$(echo "$new_item" | _egrep_o ".*<type>(A|AAAA|CNAME|MX|SPF|SRV|TXT|TLSA|SSHFP|CAA)<\/type>.*")" ]; then
         _debug "not an allowed record type, skipping" "$new_item"
         continue
       fi
@@ -152,7 +152,7 @@ dns_openprovider_rm() {
         new_item="$(echo "$item" | sed -n 's/.*<item>.*\(<name>\(.*\)'"$_domain_name"'\.'"$_domain_extension"'<\/name>.*\(<type>.*<\/type>\).*\(<value>.*<\/value>\).*\(<prio>.*<\/prio>\).*\(<ttl>.*<\/ttl>\)\).*<\/item>.*/<item><name>\2<\/name>\3\4\5\6<\/item>/p')"
       fi
 
-      if [ -z "$(echo "$new_item" | _egrep_o ".*<type>(A|AAAA|CNAME|MX|SPF|SRV|TXT|TLSA|SSHFP|CAA|NS)<\/type>.*")" ]; then
+      if [ -z "$(echo "$new_item" | _egrep_o ".*<type>(A|AAAA|CNAME|MX|SPF|SRV|TXT|TLSA|SSHFP|CAA)<\/type>.*")" ]; then
         _debug "not an allowed record type, skipping" "$new_item"
         continue
       fi

+ 15 - 14
dnsapi/dns_timeweb.sh

@@ -110,9 +110,9 @@ _timeweb_split_acme_fqdn() {
 
   TW_Page_Limit=100
   TW_Page_Offset=0
+  TW_Domains_Returned=""
 
-  while [ -z "$TW_Domains_Total" ] ||
-    [ "$((TW_Domains_Total + TW_Page_Limit))" -gt "$((TW_Page_Offset + TW_Page_Limit))" ]; do
+  while [ -z "$TW_Domains_Returned" ] || [ "$TW_Domains_Returned" -ge "$TW_Page_Limit" ]; do
 
     _timeweb_list_domains "$TW_Page_Limit" "$TW_Page_Offset" || return 1
 
@@ -160,9 +160,10 @@ _timeweb_get_dns_txt() {
 
   TW_Page_Limit=100
   TW_Page_Offset=0
+  TW_Dns_Records_Returned=""
+
+  while [ -z "$TW_Dns_Records_Returned" ] || [ "$TW_Dns_Records_Returned" -ge "$TW_Page_Limit" ]; do
 
-  while [ -z "$TW_Dns_Records_Total" ] ||
-    [ "$((TW_Dns_Records_Total + TW_Page_Limit))" -gt "$((TW_Page_Offset + TW_Page_Limit))" ]; do
     _timeweb_list_dns_records "$TW_Page_Limit" "$TW_Page_Offset" || return 1
 
     while
@@ -195,7 +196,7 @@ _timeweb_get_dns_txt() {
 # Param 2: Offset for domains list.
 #
 # Sets the "TW_Domains" variable.
-# Sets the "TW_Domains_Total" variable.
+# Sets the "TW_Domains_Returned" variable.
 _timeweb_list_domains() {
   _debug "Listing domains via Timeweb Cloud API. Limit: $1, offset: $2."
 
@@ -211,22 +212,22 @@ _timeweb_list_domains() {
     return 1
   }
 
-  TW_Domains_Total=$(
+  TW_Domains_Returned=$(
     echo "$TW_Domains" |
       sed 's/.*"meta":{"total":\([0-9]*\)[^0-9].*/\1/'
   )
 
-  [ -z "$TW_Domains_Total" ] && {
+  [ -z "$TW_Domains_Returned" ] && {
     _err "Failed to extract the total count of domains."
     return 1
   }
 
-  [ "$TW_Domains_Total" -eq "0" ] && {
+  [ "$TW_Domains_Returned" -eq "0" ] && {
     _err "Domains are missing."
     return 1
   }
 
-  _debug "Total count of domains in the Timeweb Cloud account: $TW_Domains_Total."
+  _debug "Domains returned by Timeweb Cloud API: $TW_Domains_Returned."
 }
 
 # Lists domain DNS records via the Timeweb Cloud API.
@@ -235,7 +236,7 @@ _timeweb_list_domains() {
 # Param 2: Offset for DNS records list.
 #
 # Sets the "TW_Dns_Records" variable.
-# Sets the "TW_Dns_Records_Total" variable.
+# Sets the "TW_Dns_Records_Returned" variable.
 _timeweb_list_dns_records() {
   _debug "Listing domain DNS records via the Timeweb Cloud API. Limit: $1, offset: $2."
 
@@ -251,22 +252,22 @@ _timeweb_list_dns_records() {
     return 1
   }
 
-  TW_Dns_Records_Total=$(
+  TW_Dns_Records_Returned=$(
     echo "$TW_Dns_Records" |
       sed 's/.*"meta":{"total":\([0-9]*\)[^0-9].*/\1/'
   )
 
-  [ -z "$TW_Dns_Records_Total" ] && {
+  [ -z "$TW_Dns_Records_Returned" ] && {
     _err "Failed to extract the total count of DNS records."
     return 1
   }
 
-  [ "$TW_Dns_Records_Total" -eq "0" ] && {
+  [ "$TW_Dns_Records_Returned" -eq "0" ] && {
     _err "DNS records are missing."
     return 1
   }
 
-  _debug "Total count of DNS records: $TW_Dns_Records_Total."
+  _debug "DNS records returned by Timeweb Cloud API: $TW_Dns_Records_Returned."
 }
 
 # Verifies whether the domain is the primary domain for the ACME DNS-01 challenge FQDN.