#!/usr/bin/env sh VER=0.9 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 } _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 } # body url [needbase64] [POST|PUT|DELETE] [ContentType] _post() { body="$1" _post_url="$2" needbase64="$3" httpmethod="$4" _postContentType="$5" 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 _err "Please refer to https://curl.haxx.se/libcurl/c/libcurl-errors.html for error code: $_ret" fi printf "%s" "$response" return $_ret } # url getheader timeout _get() { url="$1" onlyheader="$2" t="$3" _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 _err "Please refer to https://curl.haxx.se/libcurl/c/libcurl-errors.html for error code: $ret" fi return $ret } _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() { _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 *) 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 } _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 } _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" } # no-ops: _clearaccountconf() { return } _cleardomainconf() { return } _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 } _saveaccountconf_mutable() { return } _save_conf() { return } _savedomainconf() { return } _source_plugin_config() { return } # 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/=/ }) 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)" done } # call setup and teardown direct # the parameter must be set in the correct order # $1 DNS Plugin name # $2 Fully Qualified Domain Name # $3 value for TXT record # $4 DNS plugin auth and config parameter separated by "," # $5 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" txtvalue=$3 plugin_conf_string=$4 DEBUG=$5 if [ -n "$plugin_conf_string" ]; then _load_plugin_config fi 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" txtvalue=$3 plugin_conf_string=$4 DEBUG=$5 if [ -n "$plugin_conf_string" ]; then _load_plugin_config fi 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 } "$@"