-#!/usr/bin/env sh
+#!/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'
}
fi
}
+_usage() {
+ __red "$@" >&2
+ printf "\n" >&2
+}
+
_upper_case() {
# shellcheck disable=SC2018,SC2019
tr 'a-z' 'A-Z'
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"
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
_ret="$?"
if [ "$_ret" != "0" ]; then
- _err "Please refer to https://curl.haxx.se/libcurl/c/libcurl-errors.html for error code: $_ret"
+ 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="$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
fi
ret=$?
if [ "$ret" != "0" ]; then
- _err "Please refer to https://curl.haxx.se/libcurl/c/libcurl-errors.html for error code: $ret"
+ 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
}
}
_log() {
- return
+ return 0
}
_info() {
# key
_readaccountconf() {
- echo "$1"
+ echo "${!1}"
}
# key
# no-ops:
_clearaccountconf() {
- return
+ return 0
}
_cleardomainconf() {
- return
+ return 0
}
_debug() {
- return
+ if [[ $DEBUG -eq 0 ]]; then
+ return
+ fi
+ printf -- "%s" "[$(date)] " >&1
+ echo "$1 $2"
}
_debug2() {
- return
+ _debug $1 $2
}
_debug3() {
- return
+ _debug $1 $2
}
_secure_debug() {
- return
+ _debug $1 $2
}
_secure_debug2() {
- return
+ _debug $1 $2
}
_secure_debug3() {
- return
+ _debug $1 $2
}
_saveaccountconf() {
- return
+ return 0
}
_saveaccountconf_mutable() {
- return
+ return 0
}
_save_conf() {
- return
+ return 0
}
_savedomainconf() {
- return
+ return 0
}
_source_plugin_config() {
- return
+ return 0
}
# Proxmox implementation to inject the DNSAPI variables
_load_plugin_config() {
- tmp_str="${plugin_conf_string//[^,]}"
- index="$(_math ${#tmp_str} + 1)"
- while [ "$index" -gt "0" ]
- do
- field=$(_getfield $plugin_conf_string "$index" ",")
- ADDR=(${field/=/ })
+ while IFS= read -r line; do
+ ADDR=(${line/=/ })
key="${ADDR[0]}"
value="${ADDR[1]}"
- # decode base64 encoded values
- value=$(echo $value | /usr/bin/openssl base64 -d -A)
-
# acme.sh uses eval insted of export
- export "$key"="$value"
- index="$(_math "$index" - 1)"
+ 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
+}
+
+"$@"