The _url_encode function of acme.sh takes an optional 'upper-hex' argument, which makes it output the percent-encoded characters with upper-case hex digits. Our copy of the function in proxmox-acme lacks it and always outputs lower-case hex. The Aliyun DNS plugin of the bundled acme.sh version uses this argument for the signature of its API requests, as the Aliyun API requires upper-case hex there. So the signature never matched, and adding the TXT record always failed with "SignatureDoesNotMatch". Add the argument the same way acme.sh implements it, as suggested in the bug. Upstream changed the Aliyun plugin in the meantime to not rely on the argument anymore, because of bundled copies like ours, but newer plugins, like dns_baidu.sh and dns_hw.sh, use it too. Signed-off-by: Michal Fox <me@dualfroz.com>
1005 lines
20 KiB
Bash
1005 lines
20 KiB
Bash
#!/bin/bash
|
|
|
|
VER=1.0
|
|
|
|
PROJECT_NAME="ProxmoxACME"
|
|
|
|
USER_AGENT="$PROJECT_NAME/$VER"
|
|
|
|
DNS_PLUGIN_PATH="/usr/share/proxmox-acme/dnsapi"
|
|
HTTP_HEADER="$(mktemp)"
|
|
|
|
DEBUG="0"
|
|
|
|
_base64() {
|
|
openssl base64 -e | tr -d '\r\n'
|
|
}
|
|
|
|
_dbase64() {
|
|
openssl base64 -d
|
|
}
|
|
|
|
# Usage: hashalg [outputhex]
|
|
# Output Base64-encoded digest
|
|
_digest() {
|
|
alg="$1"
|
|
|
|
if [ "$alg" = "sha256" ] || [ "$alg" = "sha1" ] || [ "$alg" = "md5" ]; then
|
|
if [ "$2" ]; then
|
|
openssl dgst -"$alg" -hex | cut -d = -f 2 | tr -d ' '
|
|
else
|
|
openssl dgst -"$alg" -binary | _base64
|
|
fi
|
|
fi
|
|
}
|
|
|
|
_usage() {
|
|
__red "$@" >&2
|
|
printf "\n" >&2
|
|
}
|
|
|
|
_upper_case() {
|
|
# shellcheck disable=SC2018,SC2019
|
|
tr 'a-z' 'A-Z'
|
|
}
|
|
|
|
_lower_case() {
|
|
# shellcheck disable=SC2018,SC2019
|
|
tr 'A-Z' 'a-z'
|
|
}
|
|
|
|
_startswith() {
|
|
_str="$1"
|
|
_sub="$2"
|
|
echo "$_str" | grep "^$_sub" >/dev/null 2>&1
|
|
}
|
|
|
|
_endswith() {
|
|
_str="$1"
|
|
_sub="$2"
|
|
echo "$_str" | grep -- "$_sub\$" >/dev/null 2>&1
|
|
}
|
|
|
|
_contains() {
|
|
_str="$1"
|
|
_sub="$2"
|
|
echo "$_str" | grep -- "$_sub" >/dev/null 2>&1
|
|
}
|
|
|
|
# str index [sep]
|
|
_getfield() {
|
|
_str="$1"
|
|
_findex="$2"
|
|
_sep="$3"
|
|
|
|
if [ -z "$_sep" ]; then
|
|
_sep=","
|
|
fi
|
|
|
|
_ffi="$_findex"
|
|
while [ "$_ffi" -gt "0" ]; do
|
|
_fv="$(echo "$_str" | cut -d "$_sep" -f "$_ffi")"
|
|
if [ "$_fv" ]; then
|
|
printf -- "%s" "$_fv"
|
|
return 0
|
|
fi
|
|
_ffi="$(_math "$_ffi" - 1)"
|
|
done
|
|
|
|
printf -- "%s" "$_str"
|
|
|
|
}
|
|
|
|
_exists() {
|
|
cmd="$1"
|
|
if eval type type >/dev/null 2>&1; then
|
|
type "$cmd" >/dev/null 2>&1
|
|
else command
|
|
command -v "$cmd" >/dev/null 2>&1
|
|
fi
|
|
ret="$?"
|
|
return $ret
|
|
}
|
|
|
|
# a + b
|
|
_math() {
|
|
_m_opts="$@"
|
|
printf "%s" "$(($_m_opts))"
|
|
}
|
|
|
|
_egrep_o() {
|
|
if ! egrep -o "$1" 2>/dev/null; then
|
|
sed -n 's/.*\('"$1"'\).*/\1/p'
|
|
fi
|
|
}
|
|
|
|
_h2b() {
|
|
if _exists xxd; then
|
|
if _contains "$(xxd --help 2>&1)" "assumes -c30"; then
|
|
if xxd -r -p -c 9999 2>/dev/null; then
|
|
return
|
|
fi
|
|
else
|
|
if xxd -r -p 2>/dev/null; then
|
|
return
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
hex=$(cat)
|
|
ic=""
|
|
jc=""
|
|
_debug2 _URGLY_PRINTF "$_URGLY_PRINTF"
|
|
if [ -z "$_URGLY_PRINTF" ]; then
|
|
if [ "$_ESCAPE_XARGS" ] && _exists xargs; then
|
|
_debug2 "xargs"
|
|
echo "$hex" | _upper_case | sed 's/\([0-9A-F]\{2\}\)/\\\\\\x\1/g' | xargs printf
|
|
else
|
|
for h in $(echo "$hex" | _upper_case | sed 's/\([0-9A-F]\{2\}\)/ \1/g'); do
|
|
if [ -z "$h" ]; then
|
|
break
|
|
fi
|
|
printf "\x$h%s"
|
|
done
|
|
fi
|
|
else
|
|
for c in $(echo "$hex" | _upper_case | sed 's/\([0-9A-F]\)/ \1/g'); do
|
|
if [ -z "$ic" ]; then
|
|
ic=$c
|
|
continue
|
|
fi
|
|
jc=$c
|
|
ic="$(_h_char_2_dec "$ic")"
|
|
jc="$(_h_char_2_dec "$jc")"
|
|
printf '\'"$(printf "%o" "$(_math "$ic" \* 16 + $jc)")""%s"
|
|
ic=""
|
|
jc=""
|
|
done
|
|
fi
|
|
|
|
}
|
|
|
|
#Usage: keyfile hashalg
|
|
#Output: Base64-encoded signature value
|
|
_sign() {
|
|
keyfile="$1"
|
|
alg="$2"
|
|
if [ -z "$alg" ]; then
|
|
_usage "Usage: _sign keyfile hashalg"
|
|
return 1
|
|
fi
|
|
|
|
_sign_openssl="${ACME_OPENSSL_BIN:-openssl} dgst -sign $keyfile "
|
|
|
|
if grep "BEGIN RSA PRIVATE KEY" "$keyfile" >/dev/null 2>&1 || grep "BEGIN PRIVATE KEY" "$keyfile" >/dev/null 2>&1; then
|
|
$_sign_openssl -$alg | _base64
|
|
elif grep "BEGIN EC PRIVATE KEY" "$keyfile" >/dev/null 2>&1; then
|
|
if ! _signedECText="$($_sign_openssl -sha$__ECC_KEY_LEN | ${ACME_OPENSSL_BIN:-openssl} asn1parse -inform DER)"; then
|
|
_err "Sign failed: $_sign_openssl"
|
|
_err "Key file: $keyfile"
|
|
_err "Key content:$(wc -l <"$keyfile") lines"
|
|
return 1
|
|
fi
|
|
_debug3 "_signedECText" "$_signedECText"
|
|
_ec_r="$(echo "$_signedECText" | _head_n 2 | _tail_n 1 | cut -d : -f 4 | tr -d "\r\n")"
|
|
_ec_s="$(echo "$_signedECText" | _head_n 3 | _tail_n 1 | cut -d : -f 4 | tr -d "\r\n")"
|
|
if [ "$__ECC_KEY_LEN" -eq "256" ]; then
|
|
while [ "${#_ec_r}" -lt "64" ]; do
|
|
_ec_r="0${_ec_r}"
|
|
done
|
|
while [ "${#_ec_s}" -lt "64" ]; do
|
|
_ec_s="0${_ec_s}"
|
|
done
|
|
fi
|
|
if [ "$__ECC_KEY_LEN" -eq "384" ]; then
|
|
while [ "${#_ec_r}" -lt "96" ]; do
|
|
_ec_r="0${_ec_r}"
|
|
done
|
|
while [ "${#_ec_s}" -lt "96" ]; do
|
|
_ec_s="0${_ec_s}"
|
|
done
|
|
fi
|
|
if [ "$__ECC_KEY_LEN" -eq "512" ]; then
|
|
while [ "${#_ec_r}" -lt "132" ]; do
|
|
_ec_r="0${_ec_r}"
|
|
done
|
|
while [ "${#_ec_s}" -lt "132" ]; do
|
|
_ec_s="0${_ec_s}"
|
|
done
|
|
fi
|
|
_debug3 "_ec_r" "$_ec_r"
|
|
_debug3 "_ec_s" "$_ec_s"
|
|
printf "%s" "$_ec_r$_ec_s" | _h2b | _base64
|
|
else
|
|
_err "Unknown key file format."
|
|
return 1
|
|
fi
|
|
|
|
}
|
|
|
|
#dummy function because proxmox-acme does not call inithttp
|
|
_resethttp() {
|
|
:
|
|
}
|
|
|
|
_HTTP_MAX_RETRY=8
|
|
|
|
# body url [needbase64] [POST|PUT|DELETE] [ContentType]
|
|
_post() {
|
|
body="$1"
|
|
_post_url="$2"
|
|
needbase64="$3"
|
|
httpmethod="$4"
|
|
_postContentType="$5"
|
|
_sleep_retry_sec=1
|
|
_http_retry_times=0
|
|
_hcode=0
|
|
while [ "${_http_retry_times}" -le "$_HTTP_MAX_RETRY" ]; do
|
|
[ "$_http_retry_times" = "$_HTTP_MAX_RETRY" ]
|
|
_lastHCode="$?"
|
|
_debug "Retrying post"
|
|
_post_impl "$body" "$_post_url" "$needbase64" "$httpmethod" "$_postContentType" "$_lastHCode"
|
|
_hcode="$?"
|
|
_debug _hcode "$_hcode"
|
|
if [ "$_hcode" = "0" ]; then
|
|
break
|
|
fi
|
|
_http_retry_times=$(_math $_http_retry_times + 1)
|
|
_sleep $_sleep_retry_sec
|
|
done
|
|
return $_hcode
|
|
}
|
|
|
|
# body url [needbase64] [POST|PUT|DELETE] [ContentType] [displayError]
|
|
_post_impl() {
|
|
body="$1"
|
|
_post_url="$2"
|
|
needbase64="$3"
|
|
httpmethod="$4"
|
|
_postContentType="$5"
|
|
displayError="$6"
|
|
|
|
if [ -z "$httpmethod" ]; then
|
|
httpmethod="POST"
|
|
fi
|
|
|
|
_CURL="curl -L --silent --dump-header $HTTP_HEADER -g "
|
|
if [ "$HTTPS_INSECURE" ]; then
|
|
_CURL="$_CURL --insecure "
|
|
fi
|
|
if [ "$httpmethod" = "HEAD" ]; then
|
|
_CURL="$_CURL -I "
|
|
fi
|
|
if [ "$needbase64" ]; then
|
|
if [ "$body" ]; then
|
|
if [ "$_postContentType" ]; then
|
|
response="$($_CURL --user-agent "$USER_AGENT" -X $httpmethod -H "Content-Type: $_postContentType" -H "$_H1" -H "$_H2" -H "$_H3" -H "$_H4" -H "$_H5" --data "$body" "$_post_url" | _base64)"
|
|
else
|
|
response="$($_CURL --user-agent "$USER_AGENT" -X $httpmethod -H "$_H1" -H "$_H2" -H "$_H3" -H "$_H4" -H "$_H5" --data "$body" "$_post_url" | _base64)"
|
|
fi
|
|
else
|
|
if [ "$_postContentType" ]; then
|
|
response="$($_CURL --user-agent "$USER_AGENT" -X $httpmethod -H "Content-Type: $_postContentType" -H "$_H1" -H "$_H2" -H "$_H3" -H "$_H4" -H "$_H5" "$_post_url" | _base64)"
|
|
else
|
|
response="$($_CURL --user-agent "$USER_AGENT" -X $httpmethod -H "$_H1" -H "$_H2" -H "$_H3" -H "$_H4" -H "$_H5" "$_post_url" | _base64)"
|
|
fi
|
|
fi
|
|
else
|
|
if [ "$body" ]; then
|
|
if [ "$_postContentType" ]; then
|
|
response="$($_CURL --user-agent "$USER_AGENT" -X $httpmethod -H "Content-Type: $_postContentType" -H "$_H1" -H "$_H2" -H "$_H3" -H "$_H4" -H "$_H5" --data "$body" "$_post_url")"
|
|
else
|
|
response="$($_CURL --user-agent "$USER_AGENT" -X $httpmethod -H "$_H1" -H "$_H2" -H "$_H3" -H "$_H4" -H "$_H5" --data "$body" "$_post_url")"
|
|
fi
|
|
else
|
|
if [ "$_postContentType" ]; then
|
|
response="$($_CURL --user-agent "$USER_AGENT" -X $httpmethod -H "Content-Type: $_postContentType" -H "$_H1" -H "$_H2" -H "$_H3" -H "$_H4" -H "$_H5" "$_post_url")"
|
|
else
|
|
response="$($_CURL --user-agent "$USER_AGENT" -X $httpmethod -H "$_H1" -H "$_H2" -H "$_H3" -H "$_H4" -H "$_H5" "$_post_url")"
|
|
fi
|
|
fi
|
|
fi
|
|
_ret="$?"
|
|
if [ "$_ret" != "0" ]; then
|
|
if [ -z "$displayError" ] || [ "$displayError" = "0" ]; then
|
|
_err "Please refer to https://curl.haxx.se/libcurl/c/libcurl-errors.html for error code: $_ret"
|
|
fi
|
|
fi
|
|
printf "%s" "$response"
|
|
return $_ret
|
|
}
|
|
|
|
# url getheader timeout
|
|
_get() {
|
|
url="$1"
|
|
onlyheader="$2"
|
|
t="$3"
|
|
_sleep_retry_sec=1
|
|
_http_retry_times=0
|
|
_hcode=0
|
|
while [ "${_http_retry_times}" -le "$_HTTP_MAX_RETRY" ]; do
|
|
[ "$_http_retry_times" = "$_HTTP_MAX_RETRY" ]
|
|
_lastHCode="$?"
|
|
_debug "Retrying GET"
|
|
_get_impl "$url" "$onlyheader" "$t" "$_lastHCode"
|
|
_hcode="$?"
|
|
_debug _hcode "$_hcode"
|
|
if [ "$_hcode" = "0" ]; then
|
|
break
|
|
fi
|
|
_http_retry_times=$(_math $_http_retry_times + 1)
|
|
_sleep $_sleep_retry_sec
|
|
done
|
|
return $_hcode
|
|
}
|
|
|
|
# url getheader timeout displayError
|
|
_get_impl() {
|
|
url="$1"
|
|
onlyheader="$2"
|
|
t="$3"
|
|
displayError="$4"
|
|
|
|
_CURL="curl -L --silent --dump-header $HTTP_HEADER -g "
|
|
if [ "$HTTPS_INSECURE" ]; then
|
|
_CURL="$_CURL --insecure "
|
|
fi
|
|
if [ "$t" ]; then
|
|
_CURL="$_CURL --connect-timeout $t"
|
|
fi
|
|
if [ "$onlyheader" ]; then
|
|
$_CURL -I --user-agent "USER_AGENT" -H "$_H1" -H "$_H2" -H "$_H3" -H "$_H4" -H "$_H5" "$url"
|
|
else
|
|
$_CURL --user-agent "USER_AGENT" -H "$_H1" -H "$_H2" -H "$_H3" -H "$_H4" -H "$_H5" "$url"
|
|
fi
|
|
ret=$?
|
|
if [ "$ret" != "0" ]; then
|
|
if [ -z "$displayError" ] || [ "$displayError" = "0" ]; then
|
|
_err "Please refer to https://curl.haxx.se/libcurl/c/libcurl-errors.html for error code: $ret"
|
|
fi
|
|
fi
|
|
return $ret
|
|
}
|
|
|
|
#endpoint domain type
|
|
_ns_lookup_impl() {
|
|
_ns_ep="$1"
|
|
_ns_domain="$2"
|
|
_ns_type="$3"
|
|
_debug2 "_ns_ep" "$_ns_ep"
|
|
_debug2 "_ns_domain" "$_ns_domain"
|
|
_debug2 "_ns_type" "$_ns_type"
|
|
|
|
response="$(_H1="accept: application/dns-json" _get "$_ns_ep?name=$_ns_domain&type=$_ns_type")"
|
|
_ret=$?
|
|
_debug2 "response" "$response"
|
|
if [ "$_ret" != "0" ]; then
|
|
return $_ret
|
|
fi
|
|
_answers="$(echo "$response" | tr '{}' '<>' | _egrep_o '"Answer":\[[^]]*]' | tr '<>' '\n\n')"
|
|
_debug2 "_answers" "$_answers"
|
|
echo "$_answers"
|
|
}
|
|
|
|
#domain, type
|
|
_ns_lookup_cf() {
|
|
_cf_ld="$1"
|
|
_cf_ld_type="$2"
|
|
_cf_ep="https://cloudflare-dns.com/dns-query"
|
|
_ns_lookup_impl "$_cf_ep" "$_cf_ld" "$_cf_ld_type"
|
|
}
|
|
|
|
#domain, type
|
|
_ns_purge_cf() {
|
|
_cf_d="$1"
|
|
_cf_d_type="$2"
|
|
_debug "Purging Cloudflare $_cf_d_type record for domain $_cf_d"
|
|
_cf_purl="https://cloudflare-dns.com/api/v1/purge?domain=$_cf_d&type=$_cf_d_type"
|
|
response="$(_post "" "$_cf_purl")"
|
|
_debug2 response "$response"
|
|
}
|
|
|
|
#checks if cf server is available
|
|
_ns_is_available_cf() {
|
|
if _get "https://cloudflare-dns.com" "" 10 >/dev/null; then
|
|
return 0
|
|
else
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
_ns_is_available_google() {
|
|
if _get "https://dns.google" "" 10 >/dev/null; then
|
|
return 0
|
|
else
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
#domain, type
|
|
_ns_lookup_google() {
|
|
_cf_ld="$1"
|
|
_cf_ld_type="$2"
|
|
_cf_ep="https://dns.google/resolve"
|
|
_ns_lookup_impl "$_cf_ep" "$_cf_ld" "$_cf_ld_type"
|
|
}
|
|
|
|
_ns_is_available_ali() {
|
|
if _get "https://dns.alidns.com" "" 10 >/dev/null; then
|
|
return 0
|
|
else
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
#domain, type
|
|
_ns_lookup_ali() {
|
|
_cf_ld="$1"
|
|
_cf_ld_type="$2"
|
|
_cf_ep="https://dns.alidns.com/resolve"
|
|
_ns_lookup_impl "$_cf_ep" "$_cf_ld" "$_cf_ld_type"
|
|
}
|
|
|
|
_ns_is_available_dp() {
|
|
if _get "https://doh.pub" "" 10 >/dev/null; then
|
|
return 0
|
|
else
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
#dnspod
|
|
_ns_lookup_dp() {
|
|
_cf_ld="$1"
|
|
_cf_ld_type="$2"
|
|
_cf_ep="https://doh.pub/dns-query"
|
|
_ns_lookup_impl "$_cf_ep" "$_cf_ld" "$_cf_ld_type"
|
|
}
|
|
|
|
_ns_select_doh() {
|
|
if [ -z "$DOH_USE" ]; then
|
|
_debug "Detecting DNS server first."
|
|
if _ns_is_available_cf; then
|
|
_debug "Using Cloudflare doh server"
|
|
export DOH_USE=$DOH_CLOUDFLARE
|
|
elif _ns_is_available_google; then
|
|
_debug "Using Google DOH server"
|
|
export DOH_USE=$DOH_GOOGLE
|
|
elif _ns_is_available_ali; then
|
|
_debug "Using Aliyun DOH server"
|
|
export DOH_USE=$DOH_ALI
|
|
elif _ns_is_available_dp; then
|
|
_debug "Using DNS POD DOH server"
|
|
export DOH_USE=$DOH_DP
|
|
else
|
|
_err "No DOH"
|
|
fi
|
|
fi
|
|
}
|
|
|
|
#domain, type
|
|
_ns_lookup() {
|
|
_ns_select_doh
|
|
if [ "$DOH_USE" = "$DOH_CLOUDFLARE" ] || [ -z "$DOH_USE" ]; then
|
|
_ns_lookup_cf "$@"
|
|
elif [ "$DOH_USE" = "$DOH_GOOGLE" ]; then
|
|
_ns_lookup_google "$@"
|
|
elif [ "$DOH_USE" = "$DOH_ALI" ]; then
|
|
_ns_lookup_ali "$@"
|
|
elif [ "$DOH_USE" = "$DOH_DP" ]; then
|
|
_ns_lookup_dp "$@"
|
|
else
|
|
_err "Unknown DOH provider: DOH_USE=$DOH_USE"
|
|
fi
|
|
|
|
}
|
|
|
|
_head_n() {
|
|
head -n "$1"
|
|
}
|
|
|
|
_tail_n() {
|
|
tail -n "$1"
|
|
}
|
|
|
|
# stdin output hexstr splited by one space
|
|
# input:"abc"
|
|
# output: " 61 62 63"
|
|
_hex_dump() {
|
|
od -A n -v -t x1 | tr -s " " | sed 's/ $//' | tr -d "\r\t\n"
|
|
}
|
|
|
|
# stdin stdout
|
|
#_url_encode [upper-hex] the encoded hex will be upper-case if the argument upper-hex is followed
|
|
_url_encode() {
|
|
_upper_hex=$1
|
|
_hex_str=$(_hex_dump)
|
|
for _hex_code in $_hex_str; do
|
|
#upper case
|
|
case "${_hex_code}" in
|
|
"41")
|
|
printf "%s" "A"
|
|
;;
|
|
"42")
|
|
printf "%s" "B"
|
|
;;
|
|
"43")
|
|
printf "%s" "C"
|
|
;;
|
|
"44")
|
|
printf "%s" "D"
|
|
;;
|
|
"45")
|
|
printf "%s" "E"
|
|
;;
|
|
"46")
|
|
printf "%s" "F"
|
|
;;
|
|
"47")
|
|
printf "%s" "G"
|
|
;;
|
|
"48")
|
|
printf "%s" "H"
|
|
;;
|
|
"49")
|
|
printf "%s" "I"
|
|
;;
|
|
"4a")
|
|
printf "%s" "J"
|
|
;;
|
|
"4b")
|
|
printf "%s" "K"
|
|
;;
|
|
"4c")
|
|
printf "%s" "L"
|
|
;;
|
|
"4d")
|
|
printf "%s" "M"
|
|
;;
|
|
"4e")
|
|
printf "%s" "N"
|
|
;;
|
|
"4f")
|
|
printf "%s" "O"
|
|
;;
|
|
"50")
|
|
printf "%s" "P"
|
|
;;
|
|
"51")
|
|
printf "%s" "Q"
|
|
;;
|
|
"52")
|
|
printf "%s" "R"
|
|
;;
|
|
"53")
|
|
printf "%s" "S"
|
|
;;
|
|
"54")
|
|
printf "%s" "T"
|
|
;;
|
|
"55")
|
|
printf "%s" "U"
|
|
;;
|
|
"56")
|
|
printf "%s" "V"
|
|
;;
|
|
"57")
|
|
printf "%s" "W"
|
|
;;
|
|
"58")
|
|
printf "%s" "X"
|
|
;;
|
|
"59")
|
|
printf "%s" "Y"
|
|
;;
|
|
"5a")
|
|
printf "%s" "Z"
|
|
;;
|
|
|
|
#lower case
|
|
"61")
|
|
printf "%s" "a"
|
|
;;
|
|
"62")
|
|
printf "%s" "b"
|
|
;;
|
|
"63")
|
|
printf "%s" "c"
|
|
;;
|
|
"64")
|
|
printf "%s" "d"
|
|
;;
|
|
"65")
|
|
printf "%s" "e"
|
|
;;
|
|
"66")
|
|
printf "%s" "f"
|
|
;;
|
|
"67")
|
|
printf "%s" "g"
|
|
;;
|
|
"68")
|
|
printf "%s" "h"
|
|
;;
|
|
"69")
|
|
printf "%s" "i"
|
|
;;
|
|
"6a")
|
|
printf "%s" "j"
|
|
;;
|
|
"6b")
|
|
printf "%s" "k"
|
|
;;
|
|
"6c")
|
|
printf "%s" "l"
|
|
;;
|
|
"6d")
|
|
printf "%s" "m"
|
|
;;
|
|
"6e")
|
|
printf "%s" "n"
|
|
;;
|
|
"6f")
|
|
printf "%s" "o"
|
|
;;
|
|
"70")
|
|
printf "%s" "p"
|
|
;;
|
|
"71")
|
|
printf "%s" "q"
|
|
;;
|
|
"72")
|
|
printf "%s" "r"
|
|
;;
|
|
"73")
|
|
printf "%s" "s"
|
|
;;
|
|
"74")
|
|
printf "%s" "t"
|
|
;;
|
|
"75")
|
|
printf "%s" "u"
|
|
;;
|
|
"76")
|
|
printf "%s" "v"
|
|
;;
|
|
"77")
|
|
printf "%s" "w"
|
|
;;
|
|
"78")
|
|
printf "%s" "x"
|
|
;;
|
|
"79")
|
|
printf "%s" "y"
|
|
;;
|
|
"7a")
|
|
printf "%s" "z"
|
|
;;
|
|
|
|
#numbers
|
|
"30")
|
|
printf "%s" "0"
|
|
;;
|
|
"31")
|
|
printf "%s" "1"
|
|
;;
|
|
"32")
|
|
printf "%s" "2"
|
|
;;
|
|
"33")
|
|
printf "%s" "3"
|
|
;;
|
|
"34")
|
|
printf "%s" "4"
|
|
;;
|
|
"35")
|
|
printf "%s" "5"
|
|
;;
|
|
"36")
|
|
printf "%s" "6"
|
|
;;
|
|
"37")
|
|
printf "%s" "7"
|
|
;;
|
|
"38")
|
|
printf "%s" "8"
|
|
;;
|
|
"39")
|
|
printf "%s" "9"
|
|
;;
|
|
"2d")
|
|
printf "%s" "-"
|
|
;;
|
|
"5f")
|
|
printf "%s" "_"
|
|
;;
|
|
"2e")
|
|
printf "%s" "."
|
|
;;
|
|
"7e")
|
|
printf "%s" "~"
|
|
;;
|
|
|
|
#other hex
|
|
*)
|
|
if [ "$_upper_hex" = "upper-hex" ]; then
|
|
_hex_code=$(printf "%s" "$_hex_code" | _upper_case)
|
|
fi
|
|
printf '%%%s' "$_hex_code"
|
|
;;
|
|
esac
|
|
done
|
|
}
|
|
|
|
# Usage: hashalg secret_hex [outputhex]
|
|
# Output binary hmac
|
|
_hmac() {
|
|
alg="$1"
|
|
secret_hex="$2"
|
|
outputhex="$3"
|
|
|
|
if [ "$alg" = "sha256" ] || [ "$alg" = "sha1" ]; then
|
|
if [ "$outputhex" ]; then
|
|
(openssl dgst -"$alg" -mac HMAC -macopt "hexkey:$secret_hex" 2>/dev/null || openssl dgst -"$alg" -hmac "$(printf "%s" "$secret_hex" | _h2b)") | cut -d = -f 2 | tr -d ' '
|
|
else
|
|
openssl dgst -"$alg" -mac HMAC -macopt "hexkey:$secret_hex" -binary 2>/dev/null || openssl dgst -"$alg" -hmac "$(printf "%s" "$secret_hex" | _h2b)" -binary
|
|
fi
|
|
fi
|
|
}
|
|
|
|
# domain
|
|
_is_idn() {
|
|
_is_idn_d="$1"
|
|
_idn_temp=$(printf "%s" "$_is_idn_d" | tr -d '0-9' | tr -d 'a-z' | tr -d 'A-Z' | tr -d '*.,-_')
|
|
[ "$_idn_temp" ]
|
|
}
|
|
|
|
# aa.com
|
|
_idn() {
|
|
__idn_d="$1"
|
|
if ! _is_idn "$__idn_d"; then
|
|
printf "%s" "$__idn_d"
|
|
return 0
|
|
fi
|
|
|
|
if _exists idn; then
|
|
idn "$__idn_d" | tr -d "\r\n"
|
|
else
|
|
_err "Please install idn to process IDN names."
|
|
fi
|
|
}
|
|
|
|
_normalizeJson() {
|
|
sed "s/\" *: *\([\"{\[]\)/\":\1/g" | sed "s/^ *\([^ ]\)/\1/" | tr -d "\r\n"
|
|
}
|
|
|
|
# options file
|
|
_sed_i() {
|
|
sed -i "$1" "$2"
|
|
}
|
|
|
|
# sleep sec
|
|
_sleep() {
|
|
sleep "$1"
|
|
}
|
|
|
|
_stat() {
|
|
stat -c '%U:%G' "$1" 2>/dev/null
|
|
}
|
|
|
|
#keyfile
|
|
_isRSA() {
|
|
keyfile=$1
|
|
if grep "BEGIN RSA PRIVATE KEY" "$keyfile" >/dev/null 2>&1 || ${ACME_OPENSSL_BIN:-openssl} rsa -in "$keyfile" -noout -text | grep "^publicExponent:" >/dev/null 2>&1; then
|
|
return 0
|
|
fi
|
|
return 1
|
|
}
|
|
|
|
#keyfile
|
|
_isEcc() {
|
|
keyfile=$1
|
|
if grep "BEGIN EC PRIVATE KEY" "$keyfile" >/dev/null 2>&1 || ${ACME_OPENSSL_BIN:-openssl} ec -in "$keyfile" -noout -text 2>/dev/null | grep "^NIST CURVE:" >/dev/null 2>&1; then
|
|
return 0
|
|
fi
|
|
return 1
|
|
}
|
|
|
|
_time() {
|
|
date -u "+%s"
|
|
}
|
|
|
|
_utc_date() {
|
|
date -u "+%Y-%m-%d %H:%M:%S"
|
|
}
|
|
|
|
# stubbed/aliased:
|
|
__green() {
|
|
printf -- "%b" "$1"
|
|
}
|
|
|
|
__red() {
|
|
printf -- "%b" "$1"
|
|
}
|
|
|
|
_log() {
|
|
return 0
|
|
}
|
|
|
|
_info() {
|
|
printf -- "%s" "[$(date)] " >&1
|
|
echo "$1"
|
|
}
|
|
|
|
_err() {
|
|
printf -- "%s" "[$(date)] " >&2
|
|
if [ -z "$2" ]; then
|
|
__red "$1" >&2
|
|
else
|
|
__red "$1='$2'" >&2
|
|
fi
|
|
printf "\n" >&2
|
|
return 1
|
|
}
|
|
|
|
# key
|
|
_readaccountconf() {
|
|
echo "${!1}"
|
|
}
|
|
|
|
# key
|
|
_readaccountconf_mutable() {
|
|
_readaccountconf "$1"
|
|
}
|
|
|
|
# key
|
|
_readdomainconf() {
|
|
echo "${!1}"
|
|
}
|
|
|
|
# no-ops:
|
|
_clearaccountconf() {
|
|
return 0
|
|
}
|
|
|
|
_clearaccountconf_mutable() {
|
|
return 0
|
|
}
|
|
|
|
_cleardomainconf() {
|
|
return 0
|
|
}
|
|
|
|
_debug() {
|
|
if [[ $DEBUG -eq 0 ]]; then
|
|
return
|
|
fi
|
|
printf -- "%s" "[$(date)] " >&1
|
|
echo "$1 $2"
|
|
}
|
|
|
|
_debug2() {
|
|
_debug $1 $2
|
|
}
|
|
|
|
_debug3() {
|
|
_debug $1 $2
|
|
}
|
|
|
|
_secure_debug() {
|
|
_debug $1 $2
|
|
}
|
|
|
|
_secure_debug2() {
|
|
_debug $1 $2
|
|
}
|
|
|
|
_secure_debug3() {
|
|
_debug $1 $2
|
|
}
|
|
|
|
_saveaccountconf() {
|
|
return 0
|
|
}
|
|
|
|
_saveaccountconf_mutable() {
|
|
return 0
|
|
}
|
|
|
|
_save_conf() {
|
|
return 0
|
|
}
|
|
|
|
_savedomainconf() {
|
|
return 0
|
|
}
|
|
|
|
_source_plugin_config() {
|
|
return 0
|
|
}
|
|
|
|
# this is for de-escaping a JSON-encoded *string*, e.g. from: http:\/\/ to http://
|
|
_json_decode() {
|
|
_j_str="$(sed 's#\\/#/#g')"
|
|
_debug3 "_json_decode"
|
|
_debug3 "_j_str" "$_j_str"
|
|
echo "$_j_str"
|
|
}
|
|
|
|
# Proxmox implementation to inject the DNSAPI variables
|
|
_load_plugin_config() {
|
|
while IFS= read -r line; do
|
|
ADDR=(${line/=/ })
|
|
key="${ADDR[0]}"
|
|
value="${ADDR[1]}"
|
|
|
|
# acme.sh uses eval insted of export
|
|
if [ -n "$key" ]; then
|
|
export "$key"="$value"
|
|
fi
|
|
done
|
|
}
|
|
|
|
# call setup and teardown direct
|
|
# the parameter must be set in the correct order
|
|
# $1 <String> DNS Plugin name
|
|
# $2 <String> Fully Qualified Domain Name
|
|
# $3 <String> value for TXT record
|
|
# $4 <String> DNS plugin auth and config parameter separated by ","
|
|
# $5 <Integer> 0 is off, and the default all others are on.
|
|
|
|
setup() {
|
|
dns_plugin="dns_$1"
|
|
dns_plugin_path="${DNS_PLUGIN_PATH}/${dns_plugin}.sh"
|
|
fqdn="_acme-challenge.$2"
|
|
DEBUG=$3
|
|
IFS= read -r txtvalue
|
|
plugin_conf_string=$4
|
|
|
|
_load_plugin_config
|
|
|
|
if ! . "$dns_plugin_path"; then
|
|
_err "Load file $dns_plugin error."
|
|
return 1
|
|
fi
|
|
|
|
addcommand="${dns_plugin}_add"
|
|
if ! _exists "$addcommand"; then
|
|
_err "It seems that your api file is not correct, it must have a function named: $addcommand"
|
|
return 1
|
|
fi
|
|
|
|
if ! $addcommand "$fqdn" "$txtvalue"; then
|
|
_err "Error add txt for domain:$fulldomain"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
teardown() {
|
|
dns_plugin="dns_$1"
|
|
dns_plugin_path="${DNS_PLUGIN_PATH}/${dns_plugin}.sh"
|
|
fqdn="_acme-challenge.$2"
|
|
DEBUG=$3
|
|
IFS= read -r txtvalue
|
|
|
|
_load_plugin_config
|
|
|
|
if ! . "$dns_plugin_path"; then
|
|
_err "Load file $dns_plugin error."
|
|
return 1
|
|
fi
|
|
|
|
rmcommand="${dns_plugin}_rm"
|
|
if ! _exists "$rmcommand"; then
|
|
_err "It seems that your api file is not correct, it must have a function named: $rmcommand"
|
|
return 1
|
|
fi
|
|
|
|
if ! $rmcommand "$fqdn" "$txtvalue"; then
|
|
_err "Error add txt for domain:$fulldomain"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
"$@"
|