From 59a97d7f8b765a9ff0a27bc408b939903b46e551 Mon Sep 17 00:00:00 2001 From: neil Date: Fri, 17 Jul 2026 12:38:14 +0800 Subject: [PATCH] fix bug for solaris. dnsapi/deploy: remove POSIX character classes from sed/grep patterns Solaris /usr/bin/sed and /usr/bin/grep parse [[:space:]] etc. as a literal bracket set and silently mis-match. Replace with [ ]* for JSON matching, a printf-tab bracket for user-input trimming, and [0-9] for digits; also drop GNU-only sed -r/-E in rage4, selfhost and selectel, and reuse _strip_blank_lines in byteplus_alb. --- .github/workflows/vtag.yml | 32 ++++++++++++++++++++++++++++++++ acme.sh | 5 ++++- deploy/byteplus_alb.sh | 4 ++-- dnsapi/dns_bhosted.sh | 6 +++--- dnsapi/dns_creoline.sh | 8 ++++---- dnsapi/dns_czechia.sh | 13 ++++++++----- dnsapi/dns_hostup.sh | 8 ++++---- dnsapi/dns_infoblox_uddi.sh | 4 ++-- dnsapi/dns_poweradmin.sh | 2 +- dnsapi/dns_rage4.sh | 2 +- dnsapi/dns_selectel.sh | 2 +- dnsapi/dns_selfhost.sh | 7 +++++-- dnsapi/dns_udr.sh | 4 ++-- dnsapi/dns_yandex360.sh | 4 ++-- 14 files changed, 71 insertions(+), 30 deletions(-) create mode 100644 .github/workflows/vtag.yml diff --git a/.github/workflows/vtag.yml b/.github/workflows/vtag.yml new file mode 100644 index 00000000..e9f7e8df --- /dev/null +++ b/.github/workflows/vtag.yml @@ -0,0 +1,32 @@ +name: Mirror version tag + +# Historical release tags are plain version numbers ("3.1.3") and cannot be +# renamed. When a plain version tag is pushed (including the tag created by +# publishing a GitHub release), mirror it as a "v"-prefixed tag ("v3.1.3") +# pointing to the same object, so both forms exist. +# No retrigger loop: the tag filter never matches a "v"-prefixed tag, and +# refs created with GITHUB_TOKEN do not fire workflows anyway. + +on: + push: + tags: + - '[0-9]*' + +permissions: + contents: write + +jobs: + vtag: + if: github.repository == 'acmesh-official/acme.sh' + runs-on: ubuntu-latest + steps: + - name: Create the v-prefixed tag + env: + GH_TOKEN: ${{ github.token }} + run: | + if gh api "repos/${{ github.repository }}/git/ref/tags/v${{ github.ref_name }}" >/dev/null 2>&1; then + echo "Tag v${{ github.ref_name }} already exists, nothing to do." + exit 0 + fi + gh api "repos/${{ github.repository }}/git/refs" -f ref="refs/tags/v${{ github.ref_name }}" -f sha="${{ github.sha }}" + echo "Created tag v${{ github.ref_name }} -> ${{ github.sha }}" diff --git a/acme.sh b/acme.sh index 5fa6882a..753b1159 100755 --- a/acme.sh +++ b/acme.sh @@ -6078,7 +6078,10 @@ $_authorizations_map" #some devices and APIs reject them, so the certs are stored back to back. #https://github.com/acmesh-official/acme.sh/issues/1940 _strip_blank_lines() { - sed '/^[[:space:]]*$/d' + #spell out space and tab: Solaris sed treats [[:space:]] as a literal + #bracket set and silently stops matching the blank lines + _sbl_tab="$(printf '\t')" + sed "/^[ $_sbl_tab]*\$/d" } _split_cert_chain() { diff --git a/deploy/byteplus_alb.sh b/deploy/byteplus_alb.sh index 8443bb99..394b431f 100644 --- a/deploy/byteplus_alb.sh +++ b/deploy/byteplus_alb.sh @@ -163,8 +163,8 @@ byteplus_alb_deploy() { # ── 3. Read cert and key ───────────────────────────────────────────────────── # BytePlus requires NO blank lines between PEM blocks in the certificate chain - _public_key=$(sed '/^[[:space:]]*$/d' "$_cfullchain" | tr -d '\r') - _private_key=$(sed '/^[[:space:]]*$/d' "$_ckey" | tr -d '\r') + _public_key=$(_strip_blank_lines <"$_cfullchain" | tr -d '\r') + _private_key=$(_strip_blank_lines <"$_ckey" | tr -d '\r') if [ -z "$_public_key" ] || [ -z "$_private_key" ]; then _err "Failed to read certificate or key file." diff --git a/dnsapi/dns_bhosted.sh b/dnsapi/dns_bhosted.sh index 1493c60a..46ddd5dd 100644 --- a/dnsapi/dns_bhosted.sh +++ b/dnsapi/dns_bhosted.sh @@ -323,21 +323,21 @@ _bhosted_extract_id() { fi # JSON: "id":12345 - _id="$(printf "%s" "$_resp" | _egrep_o '"id"[[:space:]]*:[[:space:]]*[0-9]+' | _head_n 1 | tr -cd '0-9')" + _id="$(printf "%s" "$_resp" | _egrep_o '"id"[ ]*:[ ]*[0-9]+' | _head_n 1 | tr -cd '0-9')" if [ -n "$_id" ]; then printf "%s" "$_id" return 0 fi # key=value: id=12345 - _id="$(printf "%s" "$_resp" | _egrep_o '(^|[[:space:][:punct:]])id[[:space:]]*=[[:space:]]*[0-9]+' | _head_n 1 | tr -cd '0-9')" + _id="$(printf "%s" "$_resp" | _egrep_o '(^|[^0-9a-zA-Z])id[ ]*=[ ]*[0-9]+' | _head_n 1 | tr -cd '0-9')" if [ -n "$_id" ]; then printf "%s" "$_id" return 0 fi # "record id 12345" / "recordid 12345" - _id="$(printf "%s" "$_resp" | _egrep_o '(record[[:space:]]*id|recordid)[^0-9]*[0-9]+' | _head_n 1 | tr -cd '0-9')" + _id="$(printf "%s" "$_resp" | _egrep_o '(record[ ]*id|recordid)[^0-9]*[0-9]+' | _head_n 1 | tr -cd '0-9')" if [ -n "$_id" ]; then printf "%s" "$_id" return 0 diff --git a/dnsapi/dns_creoline.sh b/dnsapi/dns_creoline.sh index 9d04af4f..f4d76f8e 100644 --- a/dnsapi/dns_creoline.sh +++ b/dnsapi/dns_creoline.sh @@ -75,7 +75,7 @@ dns_creoline_rm() { return 1 fi - record_id=$(echo "$response" | _egrep_o "\"id\"[[:space:]]*:[[:space:]]*[0-9]+" | cut -d : -f 2 | tr -d \" | _head_n 1 | tr -d " ") + record_id=$(echo "$response" | _egrep_o "\"id\"[ ]*:[ ]*[0-9]+" | cut -d : -f 2 | tr -d \" | _head_n 1 | tr -d " ") _debug "record_id" "$record_id" if [ -z "$record_id" ]; then @@ -108,10 +108,10 @@ _get_root() { return 1 fi - _sub_domain=$(echo "$response" | _egrep_o "\"subDomain\"[[:space:]]*:[[:space:]]*\"[^\"]+\"" | cut -d : -f 2 | tr -d \" | _head_n 1 | tr -d " ") + _sub_domain=$(echo "$response" | _egrep_o "\"subDomain\"[ ]*:[ ]*\"[^\"]+\"" | cut -d : -f 2 | tr -d \" | _head_n 1 | tr -d " ") _debug _sub_domain "$_sub_domain" - _domain=$(echo "$response" | _egrep_o "\"domain\"[[:space:]]*:[[:space:]]*\"[^\"]+\"" | cut -d : -f 2 | tr -d \" | _head_n 1 | tr -d " ") + _domain=$(echo "$response" | _egrep_o "\"domain\"[ ]*:[ ]*\"[^\"]+\"" | cut -d : -f 2 | tr -d \" | _head_n 1 | tr -d " ") _debug _domain "$_domain" if [ -z "$_domain" ] || [ -z "$_sub_domain" ]; then @@ -171,7 +171,7 @@ _creoline_rest() { _err "URI:$uri" return 1 elif _contains "$response" "message"; then - message=$(echo "$response" | _egrep_o "\"message\"[[:space:]]*:[[:space:]]*\"[^\"]+\"" | cut -d : -f 2 | tr -d \") + message=$(echo "$response" | _egrep_o "\"message\"[ ]*:[ ]*\"[^\"]+\"" | cut -d : -f 2 | tr -d \") _err "Error: $message" _err "URI:$uri" return 1 diff --git a/dnsapi/dns_czechia.sh b/dnsapi/dns_czechia.sh index e2ffcf50..6ad60442 100644 --- a/dnsapi/dns_czechia.sh +++ b/dnsapi/dns_czechia.sh @@ -30,8 +30,9 @@ dns_czechia_add() { return 1 fi - _cz=$(printf "%s" "$_current_zone" | _lower_case | sed 's/[[:space:]]//g; s/\.$//') - _tk=$(printf "%s" "$CZ_AuthorizationToken" | sed 's/^[[:space:]]*//; s/[[:space:]]*$//') + _czechia_tab="$(printf '\t')" + _cz=$(printf "%s" "$_current_zone" | _lower_case | sed "s/[ $_czechia_tab]//g; s/\.\$//") + _tk=$(printf "%s" "$CZ_AuthorizationToken" | sed "s/^[ $_czechia_tab]*//; s/[ $_czechia_tab]*\$//") if [ -z "$_cz" ] || [ -z "$_tk" ]; then _err "Missing zone or CZ_AuthorizationToken." @@ -108,8 +109,9 @@ dns_czechia_rm() { return 1 fi - _cz=$(printf "%s" "$_current_zone" | _lower_case | sed 's/[[:space:]]//g; s/\.$//') - _tk=$(printf "%s" "$CZ_AuthorizationToken" | sed 's/^[[:space:]]*//; s/[[:space:]]*$//') + _czechia_tab="$(printf '\t')" + _cz=$(printf "%s" "$_current_zone" | _lower_case | sed "s/[ $_czechia_tab]//g; s/\.\$//") + _tk=$(printf "%s" "$CZ_AuthorizationToken" | sed "s/^[ $_czechia_tab]*//; s/[ $_czechia_tab]*\$//") if [ -z "$_cz" ] || [ -z "$_tk" ]; then _err "Missing zone or CZ_AuthorizationToken." @@ -180,12 +182,13 @@ _czechia_load_conf() { } _czechia_pick_zone() { + _czechia_pz_tab="$(printf '\t')" _fd=$(printf "%s" "$1" | _lower_case | sed 's/\.$//') _best_zone="" _zones_space=$(printf "%s" "$CZ_Zones" | sed 's/,/ /g') for _z in $_zones_space; do - _clean_z=$(printf "%s" "$_z" | _lower_case | sed 's/[[:space:]]//g; s/\.$//') + _clean_z=$(printf "%s" "$_z" | _lower_case | sed "s/[ $_czechia_pz_tab]//g; s/\.\$//") [ -z "$_clean_z" ] && continue case "$_fd" in diff --git a/dnsapi/dns_hostup.sh b/dnsapi/dns_hostup.sh index 73189189..a3d9174a 100644 --- a/dnsapi/dns_hostup.sh +++ b/dnsapi/dns_hostup.sh @@ -441,18 +441,18 @@ _hostup_json_extract() { input="${2:-$line}" # First try to extract quoted values (strings) - quoted_match="$(printf "%s" "$input" | _egrep_o "\"$key\"[[:space:]]*:[[:space:]]*\"[^\"]*\"" | _head_n 1)" + quoted_match="$(printf "%s" "$input" | _egrep_o "\"$key\"[ ]*:[ ]*\"[^\"]*\"" | _head_n 1)" if [ -n "$quoted_match" ]; then printf "%s" "$quoted_match" | cut -d : -f2- | - sed 's/^[[:space:]]*"//' | - sed 's/"[[:space:]]*$//' | + sed 's/^[ ]*"//' | + sed 's/"[ ]*$//' | sed 's/\\"/"/g' return 0 fi # Fallback for unquoted values (e.g., numeric IDs) - unquoted_match="$(printf "%s" "$input" | _egrep_o "\"$key\"[[:space:]]*:[[:space:]]*[^,}]*" | _head_n 1)" + unquoted_match="$(printf "%s" "$input" | _egrep_o "\"$key\"[ ]*:[ ]*[^,}]*" | _head_n 1)" if [ -n "$unquoted_match" ]; then printf "%s" "$unquoted_match" | cut -d : -f2- | diff --git a/dnsapi/dns_infoblox_uddi.sh b/dnsapi/dns_infoblox_uddi.sh index 4b15088a..902cc700 100644 --- a/dnsapi/dns_infoblox_uddi.sh +++ b/dnsapi/dns_infoblox_uddi.sh @@ -117,7 +117,7 @@ dns_infoblox_uddi_rm() { return 0 fi - record_id=$(echo "$response" | _egrep_o '"id":[[:space:]]*"[^"]*"' | _head_n 1 | cut -d '"' -f 4) + record_id=$(echo "$response" | _egrep_o '"id":[ ]*"[^"]*"' | _head_n 1 | cut -d '"' -f 4) _debug "record_id" "$record_id" if [ -z "$record_id" ]; then @@ -178,7 +178,7 @@ _get_root() { # Check if response contains results (even if empty) if _contains "$response" '"results"'; then # Extract zone ID - must match the pattern dns/auth_zone/... - zone_id=$(echo "$response" | _egrep_o '"id":[[:space:]]*"dns/auth_zone/[^"]*"' | _head_n 1 | cut -d '"' -f 4) + zone_id=$(echo "$response" | _egrep_o '"id":[ ]*"dns/auth_zone/[^"]*"' | _head_n 1 | cut -d '"' -f 4) if [ -n "$zone_id" ]; then # Found the zone _domain="$h" diff --git a/dnsapi/dns_poweradmin.sh b/dnsapi/dns_poweradmin.sh index db31fa4f..a4c81835 100644 --- a/dnsapi/dns_poweradmin.sh +++ b/dnsapi/dns_poweradmin.sh @@ -227,7 +227,7 @@ _poweradmin_rest() { return 1 fi - if printf '%s' "$response" | grep -q '"success"[[:space:]]*:[[:space:]]*false'; then + if printf '%s' "$response" | grep -q '"success"[ ]*:[ ]*false'; then _err "API reported failure on $method $ep" _debug "Response: $response" return 1 diff --git a/dnsapi/dns_rage4.sh b/dnsapi/dns_rage4.sh index c27fbc5f..b9abff17 100755 --- a/dnsapi/dns_rage4.sh +++ b/dnsapi/dns_rage4.sh @@ -71,7 +71,7 @@ dns_rage4_rm() { _debug "Getting txt records" _rage4_rest "getrecords/?id=${_domain_id}" - _record_id=$(echo "$response" | tr '{' '\n' | grep '"TXT"' | grep "\"$txtvalue" | sed -rn 's/.*"id":([[:digit:]]+),.*/\1/p') + _record_id=$(echo "$response" | tr '{' '\n' | grep '"TXT"' | grep "\"$txtvalue" | sed -n 's/.*"id":\([0-9][0-9]*\),.*/\1/p') if [ -z "$_record_id" ]; then _err "error retrieving the record_id of the new TXT record in order to delete it, got: '$_record_id'." return 1 diff --git a/dnsapi/dns_selectel.sh b/dnsapi/dns_selectel.sh index 565f541b..8ba9a4fb 100644 --- a/dnsapi/dns_selectel.sh +++ b/dnsapi/dns_selectel.sh @@ -368,7 +368,7 @@ _get_auth_token() { _data_auth="{\"auth\":{\"identity\":{\"methods\":[\"password\"],\"password\":{\"user\":{\"name\":\"${SL_Login_Name}\",\"domain\":{\"name\":\"${SL_Login_ID}\"},\"password\":\"${SL_Pswd}\"}}},\"scope\":{\"project\":{\"name\":\"${SL_Project_Name}\",\"domain\":{\"name\":\"${SL_Login_ID}\"}}}}}" export _H1="Content-Type: application/json" _result=$(_post "$_data_auth" "$auth_uri") - _token_keystone=$(grep 'x-subject-token' "$HTTP_HEADER" | sed -nE "s/[[:space:]]*x-subject-token:[[:space:]]*([[:print:]]*)(\r*)/\1/p") + _token_keystone=$(grep 'x-subject-token' "$HTTP_HEADER" | cut -d ':' -f 2- | tr -d ' \t\r') _dt_curr=$(date +%s) SL_Token_V2="${SL_Login_Name}${_sl_sep}${_token_keystone}${_sl_sep}${SL_Login_ID}${_sl_sep}${SL_Project_Name}${_sl_sep}${_dt_curr}" _saveaccountconf_mutable SL_Token_V2 "$SL_Token_V2" diff --git a/dnsapi/dns_selfhost.sh b/dnsapi/dns_selfhost.sh index 782a5d5f..25130146 100644 --- a/dnsapi/dns_selfhost.sh +++ b/dnsapi/dns_selfhost.sh @@ -42,7 +42,10 @@ dns_selfhost_add() { # only match full domains (at the beginning of the string or with a leading whitespace), # e.g. don't match mytest.example.com or sub.test.example.com for test.example.com # if the domain is defined multiple times only the last occurance will be matched - mapEntry=$(echo "$SELFHOSTDNS_MAP" | sed -n -E "s/(^|^.*[[:space:]])($fulldomain)(:[[:digit:]]+)([:]?[[:digit:]]*)(.*)/\2\3\4/p") + # prepend a space to each line so "start of line" and "after whitespace" + # can both be matched as "after a space/tab" (portable BRE, no ERE (^|..)) + _selfhost_tab="$(printf '\t')" + mapEntry=$(echo "$SELFHOSTDNS_MAP" | sed 's/^/ /' | sed -n "s/.*[ $_selfhost_tab]\($fulldomain:[0-9][0-9]*:\{0,1\}[0-9]*\).*/\1/p") _debug2 mapEntry "$mapEntry" if test -z "$mapEntry"; then _err "SELFHOSTDNS_MAP must contain the fulldomain incl. prefix and at least one RID" @@ -54,7 +57,7 @@ dns_selfhost_add() { rid2=$(echo "$mapEntry" | cut -d: -f3) # read last used rid domain - lastUsedRidForDomainEntry=$(echo "$SELFHOSTDNS_MAP_LAST_USED_INTERNAL" | sed -n -E "s/(^|^.*[[:space:]])($fulldomain:[[:digit:]]+)(.*)/\2/p") + lastUsedRidForDomainEntry=$(echo "$SELFHOSTDNS_MAP_LAST_USED_INTERNAL" | sed 's/^/ /' | sed -n "s/.*[ $_selfhost_tab]\($fulldomain:[0-9][0-9]*\).*/\1/p") _debug2 lastUsedRidForDomainEntry "$lastUsedRidForDomainEntry" lastUsedRidForDomain=$(echo "$lastUsedRidForDomainEntry" | cut -d: -f2) diff --git a/dnsapi/dns_udr.sh b/dnsapi/dns_udr.sh index 656a0557..dbc959d6 100644 --- a/dnsapi/dns_udr.sh +++ b/dnsapi/dns_udr.sh @@ -145,8 +145,8 @@ _udr_rest() { _debug data "${data}" response="$(_post "${data}" "${UDR_API}?s_login=${UDR_USER}&s_pw=${UDR_PASS}" "" "POST")" - _code=$(echo "$response" | _egrep_o "code = ([0-9]+)" | _head_n 1 | cut -d = -f 2 | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//') - _description=$(echo "$response" | _egrep_o "description = .*" | _head_n 1 | cut -d = -f 2 | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//') + _code=$(echo "$response" | _egrep_o "code = ([0-9]+)" | _head_n 1 | cut -d = -f 2 | tr -d ' \t\r') + _description=$(echo "$response" | _egrep_o "description = .*" | _head_n 1 | cut -d = -f 2 | tr -d '\r' | sed -e 's/^[ ]*//' -e 's/[ ]*$//') _debug response_code "$_code" _debug response_description "$_description" diff --git a/dnsapi/dns_yandex360.sh b/dnsapi/dns_yandex360.sh index 18d01361..98841d6b 100644 --- a/dnsapi/dns_yandex360.sh +++ b/dnsapi/dns_yandex360.sh @@ -149,7 +149,7 @@ _check_variables() { org_response="$(echo "$org_response" | _normalizeJson)" YANDEX360_ORG_ID=$( echo "$org_response" | - _egrep_o '"id":[[:space:]]*[0-9]+' | + _egrep_o '"id":[ ]*[0-9]+' | cut -d':' -f2 ) _debug 'Automatically retrieved YANDEX360_ORG_ID' "$YANDEX360_ORG_ID" @@ -216,7 +216,7 @@ _get_token() { interval=$( echo "$response" | - _egrep_o '"interval":[[:space:]]*[0-9]+' | + _egrep_o '"interval":[ ]*[0-9]+' | cut -d':' -f2 ) _debug 'Polling interval' "$interval"