]> git.proxmox.com Git - proxmox-acme.git/blobdiff - src/proxmox-acme
add support for proxies
[proxmox-acme.git] / src / proxmox-acme
index 662c39a0535d18276b3bcb5b8fd9b4a03213085c..a00d23a2a120e39e05c93837780e490eb2c5e916 100644 (file)
@@ -1,7 +1,16 @@
-#!/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'
 }
@@ -24,6 +33,11 @@ _digest() {
   fi
 }
 
+_usage() {
+  __red "$@" >&2
+  printf "\n" >&2
+}
+
 _upper_case() {
   # shellcheck disable=SC2018,SC2019
   tr 'a-z' 'A-Z'
@@ -99,6 +113,117 @@ _egrep_o() {
   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"
@@ -106,6 +231,33 @@ _post() {
   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"
@@ -149,7 +301,9 @@ _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
@@ -160,6 +314,31 @@ _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
@@ -175,7 +354,9 @@ _get() {
   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
 }
@@ -486,7 +667,7 @@ __red() {
 }
 
 _log() {
-    return
+    return 0
 }
 
 _info() {
@@ -507,7 +688,7 @@ _err() {
 
 # key
 _readaccountconf() {
-  echo "$1"
+  echo "${!1}"
 }
 
 # key
@@ -517,54 +698,134 @@ _readaccountconf_mutable() {
 
 # 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() {
+    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
 }
 
+"$@"