define Package/acme-acmesh-dnsapi
SECTION:=net
CATEGORY:=Network
- DEPENDS:=+acme-common +PACKAGE_uacme:curl
+ DEPENDS:=+acme
TITLE:=DNS API integration for ACME (Letsencrypt) client
PKGARCH:=all
endef
PKG_LICENSE:=GPL-3.0-or-later
PKG_LICENSE_FILES:=COPYING
-PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)-upstream-$(PKG_VERSION)
+PKG_BUILD_DIR:=$(BUILD_DIR)/uacme-upstream-$(PKG_VERSION)
PKG_INSTALL:=1
PKG_BUILD_PARALLEL:=1
$(call Package/uacme/Default)
SECTION:=net
CATEGORY:=Network
- DEPENDS:=+libcurl +LIBCURL_WOLFSSL:libmbedtls
+ DEPENDS:=+libcurl +LIBCURL_WOLFSSL:libmbedtls +acme-common
TITLE:=lightweight client for ACMEv2
Menu:=1
endef
URL:=https://github.com/ndilieto/uacme
endef
+define Package/uacme-dnsapi-adapter
+ $(call Package/uacme/Default)
+ DEPENDS:= +uacme +acme-acmesh-dnsapi +curl
+ TITLE:=adapter script for use acme.sh dnsapi with uacme
+endef
+
define Package/uacme/Default/description
lightweight client for the RFC8555 ACMEv2 protocol, written in plain C code
with minimal dependencies (libcurl and one of GnuTLS, OpenSSL or mbedTLS).
$(INSTALL_DIR) \
$(1)/usr/sbin \
$(1)/etc/acme \
- $(1)/etc/config \
- $(1)/etc/init.d \
- $(1)/usr/share/uacme
+ $(1)/usr/share/uacme \
+ $(1)/usr/lib/acme/client
+ $(INSTALL_BIN) ./files/hook.sh $(1)/usr/lib/acme/hook
+ $(INSTALL_BIN) ./files/httpchalhook.sh $(1)/usr/lib/acme/client/httpchalhook.sh
$(INSTALL_BIN) $(PKG_INSTALL_DIR)/usr/bin/uacme $(1)/usr/sbin/uacme
- $(INSTALL_BIN) $(PKG_INSTALL_DIR)/usr/share/uacme/uacme.sh $(1)/usr/share/uacme/
- $(SED) '/^CHALLENGE_PATH=/d' $(1)/usr/share/uacme/uacme.sh
- $(INSTALL_CONF) ./files/acme.config $(1)/etc/config/acme
- $(INSTALL_BIN) ./files/run.sh $(1)/usr/share/uacme/run-uacme
- $(INSTALL_BIN) ./files/acme.init $(1)/etc/init.d/acme
endef
define Package/uacme-ualpn/install
$(INSTALL_DIR) \
$(1)/usr/sbin \
- $(1)/usr/share/uacme
+ $(1)/usr/share/uacme \
+ $(1)/usr/lib/acme/client
$(INSTALL_BIN) $(PKG_INSTALL_DIR)/usr/bin/ualpn $(1)/usr/sbin/ualpn
- $(INSTALL_BIN) $(PKG_BUILD_DIR)/ualpn.sh $(1)/usr/share/uacme/
+ $(INSTALL_BIN) $(PKG_BUILD_DIR)/ualpn.sh $(1)/usr/lib/acme/client/ualpn.sh
endef
-define Package/uacme/prerm
-#!/bin/sh
-sed -i '/\/etc\/init\.d\/acme start/d' /etc/crontabs/root
+define Package/uacme-dnsapi-adapter/install
+ $(INSTALL_DIR) \
+ $(1)/usr/lib/acme/client
+
+ $(INSTALL_BIN) ./files/dnschalhook.sh $(1)/usr/lib/acme/client/dnschalhook.sh
+ $(INSTALL_BIN) ./files/dnsapi_helper.sh $(1)/usr/lib/acme/client/dnsapi_helper.sh
endef
$(eval $(call BuildPackage,uacme))
+++ /dev/null
-config acme
- option state_dir '/etc/acme'
- option account_email 'email@example.org'
- option debug 0
-
-config cert 'example'
- option enabled 0
- option use_staging 1
- option keylength 2048
- option update_uhttpd 1
- option update_nginx 1
- option update_haproxy 1
- option webroot "/www/.well-known/acme-challenge"
- # option user_setup "path-to-custom-setup.script"
- # option user_cleanup "path-to-custom-cleanup.script"
- list domains example.org
+++ /dev/null
-#!/bin/sh /etc/rc.common
-
-USE_PROCD=1
-
-START=50
-SCRIPT=/usr/share/uacme/run-uacme
-
-start_service()
-{
- procd_open_instance
- procd_set_param command $SCRIPT
- procd_set_param file /etc/config/acme
- procd_set_param stdout 1
- procd_set_param stderr 1
- procd_close_instance
-}
-
-reload_service() {
- rc_procd start_service "$@"
- return 0
-}
-
-stop_service() {
- return 0
-}
-
-boot() {
- touch "/var/run/uacme_boot"
- start
-}
-
-service_triggers()
-{
- procd_add_reload_trigger acme
-}
--- /dev/null
+#!/bin/sh
+#functions from acme.sh, GPLv3 applies
+#utility functions acme.sh provieded for DNS API(and itself),
+#some implementations dffer because uacme/acme.sh difference.
+
+# color functions are ignored because it didn't sent to interactive shell
+__green() {
+ printf -- "%b" "$1"
+}
+
+__red() {
+ printf -- "%b" "$1"
+}
+
+_usage() {
+ __red "$@" >&2
+ printf "\n" >&2
+}
+
+_sleep() {
+ if [ -n "$1" ]; then
+ sleep "$1"
+ fi
+}
+
+_log() {
+ prio="$1"
+ shift
+ if [ "$prio" != debug ] || [ "$debug" = 1 ]; then
+ logger -t "$LOG_TAG" -s -p "daemon.$prio" -- "$@"
+ fi
+}
+_err() {
+ _log err $@
+}
+_info() {
+ _log info $@
+}
+_debug() {
+ if [ $UACME_VERBOSE -ge 1 ]; then
+ _log debug $@
+ fi
+}
+_debug2() {
+ if [ $UACME_VERBOSE -ge 2 ]; then
+ _log debug $@
+ fi
+}
+_debug3() {
+ if [ $UACME_VERBOSE -ge 3 ]; then
+ _log debug $@
+ fi
+}
+
+__USE_TR_TAG=""
+if [ "$(echo "abc" | LANG=C tr a-z A-Z 2>/dev/null)" != "ABC" ]; then
+ __USE_TR_TAG="1"
+fi
+export __USE_TR_TAG
+
+_upper_case() {
+ if [ "$__USE_TR_TAG" ]; then
+ LANG=C tr '[:lower:]' '[:upper:]'
+ else
+ # shellcheck disable=SC2018,SC2019
+ LANG=C tr '[a-z]' '[A-Z]'
+ fi
+}
+
+_lower_case() {
+ if [ "$__USE_TR_TAG" ]; then
+ LANG=C tr '[:upper:]' '[:lower:]'
+ else
+ # shellcheck disable=SC2018,SC2019
+ LANG=C tr '[A-Z]' '[a-z]'
+ fi
+}
+
+_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
+}
+
+_hasfield() {
+ _str="$1"
+ _field="$2"
+ _sep="$3"
+ if [ -z "$_field" ]; then
+ _usage "Usage: str field [sep]"
+ return 1
+ fi
+
+ if [ -z "$_sep" ]; then
+ _sep=","
+ fi
+
+ for f in $(echo "$_str" | tr "$_sep" ' '); do
+ if [ "$f" = "$_field" ]; then
+ _debug2 "'$_str' contains '$_field'"
+ return 0 #contains ok
+ fi
+ done
+ _debug2 "'$_str' does not contain '$_field'"
+ return 1 #not contains
+}
+
+# str index [sep]
+_getfield() {
+ _str="$1"
+ _findex="$2"
+ _sep="$3"
+
+ if [ -z "$_findex" ]; then
+ _usage "Usage: str field [sep]"
+ return 1
+ fi
+
+ 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 [ -z "$cmd" ]; then
+ _usage "Usage: _exists cmd"
+ return 1
+ fi
+
+ if eval type type >/dev/null 2>&1; then
+ eval type "$cmd" >/dev/null 2>&1
+ elif command >/dev/null 2>&1; then
+ command -v "$cmd" >/dev/null 2>&1
+ else
+ which "$cmd" >/dev/null 2>&1
+ fi
+ ret="$?"
+ _debug3 "$cmd exists=$ret"
+ return $ret
+}
+
+if [ "$(echo abc | egrep -o b 2>/dev/null)" = "b" ]; then
+ __USE_EGREP=1
+else
+ __USE_EGREP=""
+fi
+
+_egrep_o() {
+ if [ "$__USE_EGREP" ]; then
+ egrep -o -- "$1" 2>/dev/null
+ else
+ sed -n 's/.*\('"$1"'\).*/\1/p'
+ fi
+}
+
+#options file
+_sed_i() {
+ options="$1"
+ filename="$2"
+ sed -i "$options" "$filename"
+}
+
+_math() {
+ _m_opts="$@"
+ printf "%s" "$(($_m_opts))"
+}
+
+#stdin output hexstr splited by one space
+#input:"abc"
+#output: " 61 62 63"
+_hex_dump() {
+ hexdump -v -e '/1 ""' -e '/1 " %02x" ""'
+}
+
+#url encode, no-preserved chars : see same named function in acme.sh
+#_url_encode [upper-hex] the encoded hex will be upper-case if the argument upper-hex is followed
+#stdin stdout
+_url_encode() {
+ _upper_hex=$1
+ _hex_str=$(_hex_dump)
+ _debug3 "_url_encode"
+ _debug3 "_hex_str" "$_hex_str"
+ 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: multiline
+_base64() {
+ [ "" ] #urgly
+ if _exists ucode; then
+ # I hope throw single line into multiline doesn't break any code
+ ucode -p "b64enc(\"$(cat -)\");"
+ else
+ if [ "$1" ]; then
+ _debug3 "base64 multiline:'$1'"
+ ${ACME_OPENSSL_BIN:-openssl} base64 -e
+ else
+ _debug3 "base64 single line."
+ ${ACME_OPENSSL_BIN:-openssl} base64 -e | tr -d '\r\n'
+ fi
+ fi
+}
+
+#Usage: multiline
+_dbase64() {
+ if _exists ucode; then
+ ucode -p "b64dec(\"$(cat -)\");"
+ else
+ if [ "$1" ]; then
+ ${ACME_OPENSSL_BIN:-openssl} base64 -d
+ else
+ ${ACME_OPENSSL_BIN:-openssl} base64 -d -A
+ fi
+ fi
+}
+
+#Usage: hashalg [outputhex]
+#Output Base64-encoded digest
+#currnetly only hex option is supported
+_digest() {
+ alg="$1"
+ if [ -z "$alg" ]; then
+ _usage "Usage: _digest hashalg"
+ return 1
+ fi
+
+ outputhex="$2"
+ if _exists ${ACME_OPENSSL_BIN:-openssl}; then
+ if [ "$alg" = "sha256" ] || [ "$alg" = "sha1" ] || [ "$alg" = "md5" ]; then
+ if [ "$outputhex" ]; then
+ ${ACME_OPENSSL_BIN:-openssl} dgst -"$alg" -hex | cut -d = -f 2 | tr -d ' '
+ else
+ ${ACME_OPENSSL_BIN:-openssl} dgst -"$alg" -binary | _base64
+ fi
+ else
+ _err "$alg is not supported yet"
+ return 1
+ fi
+ return 0
+ else
+ if [ "$outputhex" ]; then
+ case "$alg" in
+ "md5")
+ md5sum | cut -d ' ' -f 1
+ return 0
+ ;;
+ "sha256")
+ sha256sum | cut ' ' -f 1
+ return 0
+ ;;
+ *)
+ _err "$alg is not supported yet"
+ return 1
+ ;;
+ esac
+ else
+ _err "binary mode not supported without Openssl"
+ fi
+ fi
+}
+
+#Usage: hashalg secret_hex [outputhex]
+#Output binary hmac
+_hmac() {
+ alg="$1"
+ secret_hex="$2"
+ outputhex="$3"
+
+ if [ -z "$secret_hex" ]; then
+ _usage "Usage: _hmac hashalg secret [outputhex]"
+ return 1
+ fi
+
+ if [ "$alg" = "sha256" ] || [ "$alg" = "sha1" ]; then
+ if [ "$outputhex" ]; then
+ (${ACME_OPENSSL_BIN:-openssl} dgst -"$alg" -mac HMAC -macopt "hexkey:$secret_hex" 2>/dev/null || ${ACME_OPENSSL_BIN:-openssl} dgst -"$alg" -hmac "$(printf "%s" "$secret_hex" | _h2b)") | cut -d = -f 2 | tr -d ' '
+ else
+ ${ACME_OPENSSL_BIN:-openssl} dgst -"$alg" -mac HMAC -macopt "hexkey:$secret_hex" -binary 2>/dev/null || ${ACME_OPENSSL_BIN:-openssl} dgst -"$alg" -hmac "$(printf "%s" "$secret_hex" | _h2b)" -binary
+ fi
+ else
+ _err "$alg is not supported yet"
+ return 1
+ fi
+
+}
+
+#keyfile
+_isRSA() {
+ keyfile=$1
+ if grep "BEGIN RSA PRIVATE KEY" "$keyfile" >/dev/null 2>&1 || ${ACME_OPENSSL_BIN:-openssl} rsa -in "$keyfile" -noout -text 2>&1 | grep "^publicExponent:" 2>&1 >/dev/null; 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:" 2>&1 >/dev/null; then
+ return 0
+ fi
+ return 1
+}
+
+#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 _isRSA "$keyfile" >/dev/null 2>&1; then
+ $_sign_openssl -$alg | _base64
+ elif _isEcc "$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
+
+}
+
+_utc_date() {
+ date -u "+%Y-%m-%d %H:%M:%S"
+}
+
+_time() {
+ date -u "+%s"
+}
+
+_mktemp() {
+ if _exists mktemp; then
+ if mktemp 2>/dev/null; then
+ return 0
+ elif _contains "$(mktemp 2>&1)" "-t prefix" && mktemp -t "$PROJECT_NAME" 2>/dev/null; then
+ #for Mac osx
+ return 0
+ fi
+ fi
+ if [ -d "/tmp" ]; then
+ echo "/tmp/${PROJECT_NAME}wefADf24sf.$(_time).tmp"
+ return 0
+ elif [ "$LE_TEMP_DIR" ] && mkdir -p "$LE_TEMP_DIR"; then
+ echo "/$LE_TEMP_DIR/wefADf24sf.$(_time).tmp"
+ return 0
+ fi
+ _err "Cannot create temp file."
+}
+
+#clear all the https envs to cause _inithttp() to run next time.
+_resethttp() {
+ __HTTP_INITIALIZED=""
+ _ACME_CURL=""
+ _ACME_WGET=""
+ ACME_HTTP_NO_REDIRECTS=""
+}
+
+_inithttp() {
+
+ if [ -z "$HTTP_HEADER" ] || ! touch "$HTTP_HEADER"; then
+ HTTP_HEADER="$(_mktemp)"
+ _debug2 HTTP_HEADER "$HTTP_HEADER"
+ fi
+
+ if [ "$__HTTP_INITIALIZED" ]; then
+ if [ "$_ACME_CURL$_ACME_WGET" ]; then
+ _debug2 "Http already initialized."
+ return 0
+ fi
+ fi
+
+ if [ -z "$_ACME_CURL" ] && _exists "curl"; then
+ _ACME_CURL="curl --silent --dump-header $HTTP_HEADER "
+ if [ -z "$ACME_HTTP_NO_REDIRECTS" ]; then
+ _ACME_CURL="$_ACME_CURL -L "
+ fi
+ if [ "$DEBUG" ] && [ "$DEBUG" -ge 2 ]; then
+ _CURL_DUMP="$(_mktemp)"
+ _ACME_CURL="$_ACME_CURL --trace-ascii $_CURL_DUMP "
+ fi
+
+ if [ "$CA_PATH" ]; then
+ _ACME_CURL="$_ACME_CURL --capath $CA_PATH "
+ elif [ "$CA_BUNDLE" ]; then
+ _ACME_CURL="$_ACME_CURL --cacert $CA_BUNDLE "
+ fi
+
+ if _contains "$(curl --help 2>&1)" "--globoff" || _contains "$(curl --help curl 2>&1)" "--globoff"; then
+ _ACME_CURL="$_ACME_CURL -g "
+ fi
+
+ #don't use --fail-with-body
+ ##from curl 7.76: return fail on HTTP errors but keep the body
+ #if _contains "$(curl --help http 2>&1)" "--fail-with-body"; then
+ # _ACME_CURL="$_ACME_CURL --fail-with-body "
+ #fi
+ fi
+
+ if [ -z "$_ACME_WGET" ] && _exists "wget"; then
+ _ACME_WGET="wget -q"
+ if [ "$ACME_HTTP_NO_REDIRECTS" ]; then
+ _ACME_WGET="$_ACME_WGET --max-redirect 0 "
+ fi
+ if [ "$DEBUG" ] && [ "$DEBUG" -ge "2" ]; then
+ if [ "$_ACME_WGET" ] && _contains "$($_ACME_WGET --help 2>&1)" "--debug"; then
+ _ACME_WGET="$_ACME_WGET -d "
+ fi
+ fi
+ if [ "$CA_PATH" ]; then
+ _ACME_WGET="$_ACME_WGET --ca-directory=$CA_PATH "
+ elif [ "$CA_BUNDLE" ]; then
+ _ACME_WGET="$_ACME_WGET --ca-certificate=$CA_BUNDLE "
+ fi
+
+ #from wget 1.14: do not skip body on 404 error
+ if _contains "$(wget --help 2>&1)" "--content-on-error"; then
+ _ACME_WGET="$_ACME_WGET --content-on-error "
+ fi
+ fi
+
+ __HTTP_INITIALIZED=1
+
+}
+
+# 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
+ _debug $httpmethod
+ _debug "_post_url" "$_post_url"
+ _debug2 "body" "$body"
+ _debug2 "_postContentType" "$_postContentType"
+
+ _inithttp
+
+ if [ "$_ACME_CURL" ] && [ "${ACME_USE_WGET:-0}" = "0" ]; then
+ _CURL="$_ACME_CURL"
+ if [ "$HTTPS_INSECURE" ]; then
+ _CURL="$_CURL --insecure "
+ fi
+ if [ "$httpmethod" = "HEAD" ]; then
+ _CURL="$_CURL -I "
+ fi
+ _debug "_CURL" "$_CURL"
+ 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"
+ if [ "$DEBUG" ] && [ "$DEBUG" -ge "2" ]; then
+ _err "Here is the curl dump log:"
+ _err "$(cat "$_CURL_DUMP")"
+ fi
+ fi
+ elif [ "$_ACME_WGET" ]; then
+ _WGET="$_ACME_WGET"
+ if [ "$HTTPS_INSECURE" ]; then
+ _WGET="$_WGET --no-check-certificate "
+ fi
+ if [ "$httpmethod" = "HEAD" ]; then
+ _WGET="$_WGET --read-timeout=3.0 --tries=2 "
+ fi
+ _debug "_WGET" "$_WGET"
+ if [ "$needbase64" ]; then
+ if [ "$httpmethod" = "POST" ]; then
+ if [ "$_postContentType" ]; then
+ response="$($_WGET -S -O - --user-agent="$USER_AGENT" --header "$_H5" --header "$_H4" --header "$_H3" --header "$_H2" --header "$_H1" --header "Content-Type: $_postContentType" --post-data="$body" "$_post_url" 2>"$HTTP_HEADER" | _base64)"
+ else
+ response="$($_WGET -S -O - --user-agent="$USER_AGENT" --header "$_H5" --header "$_H4" --header "$_H3" --header "$_H2" --header "$_H1" --post-data="$body" "$_post_url" 2>"$HTTP_HEADER" | _base64)"
+ fi
+ else
+ if [ "$_postContentType" ]; then
+ response="$($_WGET -S -O - --user-agent="$USER_AGENT" --header "$_H5" --header "$_H4" --header "$_H3" --header "$_H2" --header "$_H1" --header "Content-Type: $_postContentType" --method $httpmethod --body-data="$body" "$_post_url" 2>"$HTTP_HEADER" | _base64)"
+ else
+ response="$($_WGET -S -O - --user-agent="$USER_AGENT" --header "$_H5" --header "$_H4" --header "$_H3" --header "$_H2" --header "$_H1" --method $httpmethod --body-data="$body" "$_post_url" 2>"$HTTP_HEADER" | _base64)"
+ fi
+ fi
+ else
+ if [ "$httpmethod" = "POST" ]; then
+ if [ "$_postContentType" ]; then
+ response="$($_WGET -S -O - --user-agent="$USER_AGENT" --header "$_H5" --header "$_H4" --header "$_H3" --header "$_H2" --header "$_H1" --header "Content-Type: $_postContentType" --post-data="$body" "$_post_url" 2>"$HTTP_HEADER")"
+ else
+ response="$($_WGET -S -O - --user-agent="$USER_AGENT" --header "$_H5" --header "$_H4" --header "$_H3" --header "$_H2" --header "$_H1" --post-data="$body" "$_post_url" 2>"$HTTP_HEADER")"
+ fi
+ elif [ "$httpmethod" = "HEAD" ]; then
+ if [ "$_postContentType" ]; then
+ response="$($_WGET --spider -S -O - --user-agent="$USER_AGENT" --header "$_H5" --header "$_H4" --header "$_H3" --header "$_H2" --header "$_H1" --header "Content-Type: $_postContentType" --post-data="$body" "$_post_url" 2>"$HTTP_HEADER")"
+ else
+ response="$($_WGET --spider -S -O - --user-agent="$USER_AGENT" --header "$_H5" --header "$_H4" --header "$_H3" --header "$_H2" --header "$_H1" --post-data="$body" "$_post_url" 2>"$HTTP_HEADER")"
+ fi
+ else
+ if [ "$_postContentType" ]; then
+ response="$($_WGET -S -O - --user-agent="$USER_AGENT" --header "$_H5" --header "$_H4" --header "$_H3" --header "$_H2" --header "$_H1" --header "Content-Type: $_postContentType" --method $httpmethod --body-data="$body" "$_post_url" 2>"$HTTP_HEADER")"
+ else
+ response="$($_WGET -S -O - --user-agent="$USER_AGENT" --header "$_H5" --header "$_H4" --header "$_H3" --header "$_H2" --header "$_H1" --method $httpmethod --body-data="$body" "$_post_url" 2>"$HTTP_HEADER")"
+ fi
+ fi
+ fi
+ _ret="$?"
+ if [ "$_ret" = "8" ]; then
+ _ret=0
+ _debug "wget returned 8 as the server returned a 'Bad Request' response. Let's process the response later."
+ fi
+ if [ "$_ret" != "0" ]; then
+ _err "Please refer to https://www.gnu.org/software/wget/manual/html_node/Exit-Status.html for error code: $_ret"
+ fi
+ if _contains "$_WGET" " -d "; then
+ # Demultiplex wget debug output
+ cat "$HTTP_HEADER" >&2
+ _sed_i '/^[^ ][^ ]/d; /^ *$/d' "$HTTP_HEADER"
+ fi
+ # remove leading whitespaces from header to match curl format
+ _sed_i 's/^ //g' "$HTTP_HEADER"
+ else
+ _ret="$?"
+ _err "Neither curl nor wget have been found, cannot make $httpmethod request."
+ fi
+ _debug "_ret" "$_ret"
+ printf "%s" "$response"
+ return $_ret
+}
+
+# url getheader timeout
+_get() {
+ _debug GET
+ url="$1"
+ onlyheader="$2"
+ t="$3"
+ _debug url "$url"
+ _debug "timeout=$t"
+
+ _inithttp
+
+ if [ "$_ACME_CURL" ] && [ "${ACME_USE_WGET:-0}" = "0" ]; then
+ _CURL="$_ACME_CURL"
+ if [ "$HTTPS_INSECURE" ]; then
+ _CURL="$_CURL --insecure "
+ fi
+ if [ "$t" ]; then
+ _CURL="$_CURL --connect-timeout $t"
+ fi
+ _debug "_CURL" "$_CURL"
+ 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"
+ if [ "$DEBUG" ] && [ "$DEBUG" -ge "2" ]; then
+ _err "Here is the curl dump log:"
+ _err "$(cat "$_CURL_DUMP")"
+ fi
+ fi
+ elif [ "$_ACME_WGET" ]; then
+ _WGET="$_ACME_WGET"
+ if [ "$HTTPS_INSECURE" ]; then
+ _WGET="$_WGET --no-check-certificate "
+ fi
+ if [ "$t" ]; then
+ _WGET="$_WGET --timeout=$t"
+ fi
+ _debug "_WGET" "$_WGET"
+ if [ "$onlyheader" ]; then
+ _wget_out="$($_WGET --user-agent="$USER_AGENT" --header "$_H5" --header "$_H4" --header "$_H3" --header "$_H2" --header "$_H1" -S -O /dev/null "$url" 2>&1)"
+ if _contains "$_WGET" " -d "; then
+ # Demultiplex wget debug output
+ echo "$_wget_out" >&2
+ echo "$_wget_out" | sed '/^[^ ][^ ]/d; /^ *$/d; s/^ //g' -
+ fi
+ else
+ $_WGET --user-agent="$USER_AGENT" --header "$_H5" --header "$_H4" --header "$_H3" --header "$_H2" --header "$_H1" -S -O - "$url" 2>"$HTTP_HEADER"
+ if _contains "$_WGET" " -d "; then
+ # Demultiplex wget debug output
+ cat "$HTTP_HEADER" >&2
+ _sed_i '/^[^ ][^ ]/d; /^ *$/d' "$HTTP_HEADER"
+ fi
+ # remove leading whitespaces from header to match curl format
+ _sed_i 's/^ //g' "$HTTP_HEADER"
+ fi
+ ret=$?
+ if [ "$ret" = "8" ]; then
+ ret=0
+ _debug "wget returned 8 as the server returned a 'Bad Request' response. Let's process the response later."
+ fi
+ if [ "$ret" != "0" ]; then
+ _err "Please refer to https://www.gnu.org/software/wget/manual/html_node/Exit-Status.html for error code: $ret"
+ fi
+ else
+ ret=$?
+ _err "Neither curl nor wget have been found, cannot make GET request."
+ fi
+ _debug "ret" "$ret"
+ return $ret
+}
+
+
+_h_char_2_dec() {
+ _ch=$1
+ case "${_ch}" in
+ a | A)
+ printf "10"
+ ;;
+ b | B)
+ printf "11"
+ ;;
+ c | C)
+ printf "12"
+ ;;
+ d | D)
+ printf "13"
+ ;;
+ e | E)
+ printf "14"
+ ;;
+ f | F)
+ printf "15"
+ ;;
+ *)
+ printf "%s" "$_ch"
+ ;;
+ esac
+
+}
+
+#openwrt have xargs in busybox
+_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
+
+}
+
+_head_n() {
+ head -n "$1"
+}
+
+_is_solaris() {
+ _contains "${__OS__:=$(uname -a)}" "solaris" || _contains "${__OS__:=$(uname -a)}" "SunOS"
+}
+
+_tail_n() {
+ if _is_solaris; then
+ #fix for solaris
+ tail -"$1"
+ else
+ tail -n "$1"
+ fi
+}
+
+_tail_c() {
+ tail -c "$1" 2>/dev/null || tail -"$1"c
+}
+
+#domain
+_is_idn() {
+ _is_idn_d="$1"
+ _debug2 _is_idn_d "$_is_idn_d"
+ _idn_temp=$(printf "%s" "$_is_idn_d" | tr -d '[0-9]' | tr -d '[a-z]' | tr -d '[A-Z]' | tr -d '*.,-_')
+ _debug2 _idn_temp "$_idn_temp"
+ [ "$_idn_temp" ]
+}
+
+#aa.com
+#aa.com,bb.com,cc.com
+_idn() {
+ __idn_d="$1"
+ if ! _is_idn "$__idn_d"; then
+ printf "%s" "$__idn_d"
+ return 0
+ fi
+
+ if _exists idn; then
+ if _contains "$__idn_d" ','; then
+ _i_first="1"
+ for f in $(echo "$__idn_d" | tr ',' ' '); do
+ [ -z "$f" ] && continue
+ if [ -z "$_i_first" ]; then
+ printf "%s" ","
+ else
+ _i_first=""
+ fi
+ idn --quiet "$f" | tr -d "\r\n"
+ done
+ else
+ idn "$__idn_d" | tr -d "\r\n"
+ fi
+ else
+ _err "Please install idn to process IDN names."
+ fi
+}
+
+_url_replace() {
+ tr '/+' '_-' | tr -d '= '
+}
+
+_normalizeJson() {
+ sed "s/\" *: *\([\"{\[]\)/\":\1/g" | sed "s/^ *\([^ ]\)/\1/" | tr -d "\r\n"
+}
+
+#setopt "file" "opt" "=" "value" [";"]
+_setopt() {
+ __conf="$1"
+ __opt="$2"
+ __sep="$3"
+ __val="$4"
+ __end="$5"
+ if [ -z "$__opt" ]; then
+ _usage usage: _setopt '"file" "opt" "=" "value" [";"]'
+ return
+ fi
+ if [ ! -f "$__conf" ]; then
+ touch "$__conf"
+ fi
+ if [ -n "$(_tail_c 1 <"$__conf")" ]; then
+ echo >>"$__conf"
+ fi
+
+ if grep -n "^$__opt$__sep" "$__conf" >/dev/null; then
+ _debug3 OK
+ if _contains "$__val" "&"; then
+ __val="$(echo "$__val" | sed 's/&/\\&/g')"
+ fi
+ if _contains "$__val" "|"; then
+ __val="$(echo "$__val" | sed 's/|/\\|/g')"
+ fi
+ text="$(cat "$__conf")"
+ printf -- "%s\n" "$text" | sed "s|^$__opt$__sep.*$|$__opt$__sep$__val$__end|" >"$__conf"
+
+ elif grep -n "^#$__opt$__sep" "$__conf" >/dev/null; then
+ if _contains "$__val" "&"; then
+ __val="$(echo "$__val" | sed 's/&/\\&/g')"
+ fi
+ if _contains "$__val" "|"; then
+ __val="$(echo "$__val" | sed 's/|/\\|/g')"
+ fi
+ text="$(cat "$__conf")"
+ printf -- "%s\n" "$text" | sed "s|^#$__opt$__sep.*$|$__opt$__sep$__val$__end|" >"$__conf"
+
+ else
+ _debug3 APP
+ echo "$__opt$__sep$__val$__end" >>"$__conf"
+ fi
+ _debug3 "$(grep -n "^$__opt$__sep" "$__conf")"
+}
+
+#config file related function: most dns scripts still reads form env variables too though
+#_save_conf file key value base64encode
+#save to conf
+_save_conf() {
+ _s_c_f="$1"
+ _sdkey="$2"
+ _sdvalue="$3"
+ _b64encode="$4"
+
+ if [ "$_sdvalue" ] && [ "$_b64encode" ]; then
+ _sdvalue="${B64CONF_START}$(printf "%s" "${_sdvalue}" | _base64)${B64CONF_END}"
+ fi
+ if [ "$_s_c_f" ]; then
+ _setopt "$_s_c_f" "$_sdkey" "=" "'$_sdvalue'"
+ else
+ _err "Config file is empty, cannot save $_sdkey=$_sdvalue"
+ fi
+}
+
+#_clear_conf file key
+_clear_conf() {
+ _c_c_f="$1"
+ _sdkey="$2"
+ if [ "$_c_c_f" ]; then
+ _conf_data="$(cat "$_c_c_f")"
+ echo "$_conf_data" | sed "/^$_sdkey *=.*$/d" >"$_c_c_f"
+ else
+ _err "Config file is empty, cannot clear"
+ fi
+}
+
+#_read_conf file key
+_read_conf() {
+ _r_c_f="$1"
+ _sdkey="$2"
+ if [ -f "$_r_c_f" ]; then
+ _sdv="$(
+ eval "$(grep "^$_sdkey *=" "$_r_c_f")"
+ eval "printf \"%s\" \"\$$_sdkey\""
+ )"
+ if _startswith "$_sdv" "${B64CONF_START}" && _endswith "$_sdv" "${B64CONF_END}"; then
+ _sdv="$(echo "$_sdv" | sed "s/${B64CONF_START}//" | sed "s/${B64CONF_END}//" | _dbase64)"
+ fi
+ printf "%s" "$_sdv"
+ else
+ _debug "Config file is empty, cannot read $_sdkey"
+ fi
+}
+
+#_savedomainconf key value base64encode
+#save to domain.conf
+_savedomainconf() {
+ _save_conf "$DOMAIN_CONF" "$@"
+}
+
+#_cleardomainconf key
+_cleardomainconf() {
+ _clear_conf "$DOMAIN_CONF" "$1"
+}
+
+#_readdomainconf key
+_readdomainconf() {
+ _read_conf "$DOMAIN_CONF" "$1"
+}
+
+#_migratedomainconf oldkey newkey base64encode
+_migratedomainconf() {
+ _old_key="$1"
+ _new_key="$2"
+ _b64encode="$3"
+ _old_value=$(_readdomainconf "$_old_key")
+ _cleardomainconf "$_old_key"
+ if [ -z "$_old_value" ]; then
+ return 1 # migrated failed: old value is empty
+ fi
+ _new_value=$(_readdomainconf "$_new_key")
+ if [ -n "$_new_value" ]; then
+ _debug "Domain config new key exists, old key $_old_key='$_old_value' has been removed."
+ return 1 # migrated failed: old value replaced by new value
+ fi
+ _savedomainconf "$_new_key" "$_old_value" "$_b64encode"
+ _debug "Domain config $_old_key has been migrated to $_new_key."
+}
+
+#_migratedeployconf oldkey newkey base64encode
+_migratedeployconf() {
+ _migratedomainconf "$1" "SAVED_$2" "$3" ||
+ _migratedomainconf "SAVED_$1" "SAVED_$2" "$3" # try only when oldkey itself is not found
+}
+
+#key value base64encode
+_savedeployconf() {
+ _savedomainconf "SAVED_$1" "$2" "$3"
+ #remove later
+ _cleardomainconf "$1"
+}
+
+#key
+_getdeployconf() {
+ _rac_key="$1"
+ _rac_value="$(eval echo \$"$_rac_key")"
+ if [ "$_rac_value" ]; then
+ if _startswith "$_rac_value" '"' && _endswith "$_rac_value" '"'; then
+ _debug2 "trim quotation marks"
+ eval $_rac_key=$_rac_value
+ export $_rac_key
+ fi
+ return 0 # do nothing
+ fi
+ _saved="$(_readdomainconf "SAVED_$_rac_key")"
+ eval $_rac_key=\$_saved
+ export $_rac_key
+}
+
+#_saveaccountconf key value base64encode
+_saveaccountconf() {
+ _save_conf "$ACCOUNT_CONF_PATH" "$@"
+}
+
+#key value base64encode
+_saveaccountconf_mutable() {
+ _save_conf "$ACCOUNT_CONF_PATH" "SAVED_$1" "$2" "$3"
+ #remove later
+ _clearaccountconf "$1"
+}
+
+#key
+_readaccountconf() {
+ _read_conf "$ACCOUNT_CONF_PATH" "$1"
+}
+
+#key
+_readaccountconf_mutable() {
+ _rac_key="$1"
+ _readaccountconf "SAVED_$_rac_key"
+}
+
+#_clearaccountconf key
+_clearaccountconf() {
+ _clear_conf "$ACCOUNT_CONF_PATH" "$1"
+}
+
+#key
+_clearaccountconf_mutable() {
+ _clearaccountconf "SAVED_$1"
+ #remove later
+ _clearaccountconf "$1"
+}
+
+#_savecaconf key value
+_savecaconf() {
+ _save_conf "$CA_CONF" "$1" "$2"
+}
+
+#_readcaconf key
+_readcaconf() {
+ _read_conf "$CA_CONF" "$1"
+}
+
+#_clearaccountconf key
+_clearcaconf() {
+ _clear_conf "$CA_CONF" "$1"
+}
\ No newline at end of file
--- /dev/null
+#!/bin/sh
+# Copyright (C) 2019-2024 Nicola Di Lieto <nicola.dilieto@gmail.com>
+#
+# This file is part of uacme.
+#
+# uacme is free software: you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# uacme is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# Part of this is copied from acme.sh
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+ARGS=5
+E_BADARGS=85
+LOG_TAG=acme-uacme-dnshook
+
+if test $# -ne "$ARGS"
+then
+ echo "Usage: $(basename "$0") method type ident token auth" 1>&2
+ exit $E_BADARGS
+fi
+
+METHOD=$1
+TYPE=$2
+IDENT=$3
+TOKEN=$4
+AUTH=$5
+
+if [ "$TYPE" != "dns-01" ]; then
+ echo "skipping $TYPE" 1>&2
+ exit 1
+fi
+
+# shellcheck source=net/acme/files/functions.sh
+. /usr/lib/acme/functions.sh
+. /usr/lib/acme/client/dnsapi_helper.sh
+ACCOUNT_CONF_PATH=$UACME_CONFDIR/accounts.conf
+DOMAIN_CONF_DIR=$UACME_CONFDIR/$IDENT
+DOMAIN_CONF=$DOMAIN_CONF_DIR/dnsapi.conf
+ACMESH_DNSSCIRPT_DIR=${ACMESH_DNSSCIRPT_DIR:-/usr/lib/acme/client/dnsapi}
+
+#import dns hook script
+dns=${dns:-$(head -n 1 $DOMAIN_CONF_DIR/selected_api)} # use different file to not hurt acme.sh config file struct
+if [ ! -f "$ACMESH_DNSSCIRPT_DIR/$dns.sh" ]; then
+ echo "dns file $dns doesn't exit" 1>&2
+ exit 1
+fi
+. /usr/lib/acme/client/dnsapi/$dns.sh
+echo $dns > "$DOMAIN_CONF_DIR/selected_api"
+case "$METHOD" in
+ "begin")
+ (umask 077 ; touch -a "$DOMAIN_CONF")
+ log info logging $DOMAIN_CONF
+ ${dns}_add _acme-challenge.$IDENT $AUTH
+ RESULT=$?
+ if [ $RESULT -eq 0 ]; then
+ sleep ${dns_wait:-"30s"}
+ exit 0
+ else
+ exit $RESULT
+ fi
+ ;;
+ "done"|"failed")
+ ${dns}_rm _acme-challenge.$IDENT $AUTH
+ exit $?
+ ;;
+ *)
+ echo "$0: invalid method" 1>&2
+ exit 1
+ ;;
+esac
--- /dev/null
+#!/bin/sh
+# Wrapper for uacme to work on openwrt.
+
+set -u
+ACME=/usr/sbin/uacme
+HPROGRAM=/usr/share/uacme/uacme.sh
+LOG_TAG=acme-uacme
+NOTIFY=/usr/lib/acme/notify
+HOOKDIR=/usr/lib/acme
+
+# shellcheck source=net/acme/files/functions.sh
+. /usr/lib/acme/functions.sh
+
+link_certs() {
+ local main_domain
+ local domain_dir
+ domain_dir="$1"
+ main_domain="$2"
+#uacme saves only fullchain as cert.pem
+ (
+ umask 077
+ cat "$domain_dir/cert.pem" "$state_dir/private/$main_domain/key.pem" >"$domain_dir/combined.cer"
+ sed -n '1,/-----END CERTIFICATE-----/p' "$domain_dir/cert.pem" >"$domain_dir/leaf_cert.pem"
+ sed '1,/-----END CERTIFICATE-----/d' "$domain_dir/cert.pem" >"$domain_dir/chain.crt"
+ )
+
+ if [ ! -e "$CERT_DIR/$main_domain.crt" ]; then
+ ln -s "$domain_dir/leaf_cert.pem" "$CERT_DIR/$main_domain.crt"
+ fi
+#uacme doesn't rotate key, and it saves ../private/$main_domain dir
+ if [ ! -e "$CERT_DIR/$main_domain.key" ]; then
+ ln -s "$state_dir/private/$main_domain/key.pem" "$CERT_DIR/$main_domain.key"
+ fi
+ if [ ! -e "$CERT_DIR/$main_domain.fullchain.crt" ]; then
+ ln -s "$domain_dir/cert.pem" "$CERT_DIR/$main_domain.fullchain.crt"
+ fi
+ if [ ! -e "$CERT_DIR/$main_domain.combined.crt" ]; then
+ ln -s "$domain_dir/combined.cer" "$CERT_DIR/$main_domain.combined.crt"
+ fi
+ if [ ! -e "$CERT_DIR/$main_domain.chain.crt" ]; then
+ ln -s "$domain_dir/chain.crt" "$CERT_DIR/$main_domain.chain.crt"
+ fi
+}
+
+#expand acme server short alias
+case $acme_server in
+ letsencrypt)
+ unset acme_server
+ ;;
+ letsencrypt_test)
+ acme_server=https://acme-staging-v02.api.letsencrypt.org/directory
+ ;;
+ zerossl)
+ acme_server=https://acme.zerossl.com/v2/DV90
+ ;;
+ google)
+ acme_server=https://dv.acme-v02.api.pki.goog/directory
+ ;;
+ actalis)
+ acme_server=https://acme-api.actalis.com/acme/directory
+ ;;
+ *)
+ ;;
+ esac
+
+case $1 in
+get)
+ #uacme doesn't save account per ca nor it make new account when not registered
+ #while server doesn't care, we record which CAs we have account to reduce noise
+ #using staging var for default letsencrypts.
+ if grep -q "^${acme_server:-$staging}$" $state_dir/accounts; then
+ :
+ else
+ #not found
+ if [ "$acme_server" ]; then
+ $ACME new $account_email -t EC -y --confdir "$state_dir" -a $acme_server
+ echo $acme_server >> $state_dir/accounts
+ elif [ "$staging" = 1 ]; then
+ $ACME new $account_email -t EC -y --confdir "$state_dir" -s
+ echo $staging >> $state_dir/accounts
+ else
+ $ACME new $account_email -t EC -y --confdir "$state_dir"
+ echo $staging >> $state_dir/accounts
+ fi
+ fi
+ set --
+ [ "$debug" = 1 ] && set -- "$@" -v
+#uacme doesn't rotate privkey
+ case $key_type in
+ ec*)
+ keylength=${key_type#ec}
+ domain_dir="$state_dir/$main_domain"
+ set -- "$@" -t EC
+ ;;
+ rsa*)
+ keylength=${key_type#rsa}
+ domain_dir="$state_dir/$main_domain"
+ ;;
+ esac
+
+ set -- "$@" --bits "$keylength"
+
+ if [ "$acme_server" ]; then
+ set -- "$@" --acme-url "$acme_server"
+ elif [ "$staging" = 1 ]; then
+ set -- "$@" --staging
+ else
+ set -- "$@"
+ fi
+
+ log info "Running ACME for $main_domain with validation_method $validation_method"
+
+ staging_moved=0
+ is_renew=0
+ if [ -e "$domain_dir" ]; then
+ if [ "$staging" = 0 ] && [ -e $domain_dir/this_is_staging ]; then
+ mv "$domain_dir" "$domain_dir.staging"
+ mv "$state_dir/private/$main_domain" "$state_dir/private/$main_domain.staging"
+ log info "Certificates are previously issued from a staging server, but staging option is disabled, moved to $domain_dir.staging."
+ staging_moved=1
+ else
+ #this is renewal
+ is_renew=1
+ fi
+ else
+ log info no prv certificate remembered
+ fi
+
+ if [ "$days" ]; then
+ set -- "$@" --days "$days"
+ fi
+
+ # uacme handles challange select by hook script
+ case "$validation_method" in
+ "alpn")
+ log info "using already running ualpn, it's user's duty to config ualpn server deamon"
+ set -- "$@" -h "$HOOKDIR/client/ualpn.sh"
+ ;;
+ "dns")
+ export dns
+ set -- "$@" -h "$HOOKDIR/client/dnschalhook.sh"
+ if [ "$dalias" ]; then
+ set -- "$@" --domain-alias "$dalias"
+ if [ "$calias" ]; then
+ log err "Both domain and challenge aliases are defined. Ignoring the challenge alias."
+ fi
+ elif [ "$calias" ]; then
+ set -- "$@" --challenge-alias "$calias"
+ fi
+ if [ "$dns_wait" ]; then
+ export dns_wait
+ fi
+ ;;
+ "standalone")
+ set -- "$@" --standalone --listen-v6
+ log err "standalone server is not implmented for uacme"
+ exit 1
+ ;;
+ "webroot")
+ mkdir -p "$CHALLENGE_DIR"
+ export CHALLENGE_DIR
+ set -- "$@" -h "$HOOKDIR/client/httpchalhook.sh"
+ ;;
+ *)
+ log err "Unsupported validation_method $validation_method"
+ ;;
+ esac
+
+ set -- "$@" --confdir "$state_dir" issue
+ for d in $domains; do
+ set -- "$@" "$d"
+ done
+
+ log info "$ACME $*"
+ trap '$NOTIFY issue-failed;exit 1' INT
+ "$ACME" "$@" 2>&1
+ status=$?
+ trap - INT
+
+ case $status in
+ 0)
+ link_certs "$domain_dir" "$main_domain"
+ if [ "$is_renew" = 1 ]; then
+ $NOTIFY renewed
+ else
+ $NOTIFY issued
+ if [ "$staging" = 1 ]; then
+ touch $domain_dir/this_is_staging
+ fi
+ fi
+ ;;
+ 1)
+ #cert is not due to renewl so don't do anything
+ if [ "$staging_moved" = 1 ]; then
+ log err "Staging certificate '$domain_dir' restored"
+ mv "$domain_dir.staging" "$domain_dir"
+ fi
+ log debug "not due to renewal"
+ ;;
+ *)
+ if [ "$is_renew" = 1 ]; then
+ $NOTIFY renew-failed
+ exit 1;
+ fi
+ if [ "$staging_moved" = 1 ]; then
+ log err "Staging certificate '$domain_dir' restored"
+ mv "$domain_dir.staging" "$domain_dir"
+ mv "$state_dir/private/$main_domain.staging" "$state_dir/private/$main_domain"
+ elif [ -d "$domain_dir" ]; then
+ failed_dir="$domain_dir.failed-$(date +%s)"
+ mv "$domain_dir" "$failed_dir"
+ log err "State moved to $failed_dir"
+ fi
+ $NOTIFY issue-failed
+ ;;
+ esac
+ ;;
+esac
--- /dev/null
+#!/bin/sh
+# Copyright (C) 2019-2024 Nicola Di Lieto <nicola.dilieto@gmail.com>
+#
+# This file is part of uacme.
+#
+# uacme is free software: you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# uacme is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+CHALLENGE_PATH="${CHALLENGE_DIR:-/var/run/acme/challenge}"
+mkdir -p "${CHALLENGE_PATH}/.well-known/acme-challenge"
+ARGS=5
+E_BADARGS=85
+
+if test $# -ne "$ARGS"
+then
+ echo "Usage: $(basename "$0") method type ident token auth" 1>&2
+ exit $E_BADARGS
+fi
+
+METHOD=$1
+TYPE=$2
+IDENT=$3
+TOKEN=$4
+AUTH=$5
+
+case "$METHOD" in
+ "begin")
+ case "$TYPE" in
+ http-01)
+ printf "%s" "${AUTH}" > "${CHALLENGE_PATH}/.well-known/acme-challenge/${TOKEN}"
+ exit $?
+ ;;
+ *)
+ exit 1
+ ;;
+ esac
+ ;;
+
+ "done"|"failed")
+ case "$TYPE" in
+ http-01)
+ rm "${CHALLENGE_PATH}/.well-known/acme-challenge/${TOKEN}"
+ exit $?
+ ;;
+ *)
+ exit 1
+ ;;
+ esac
+ ;;
+
+ *)
+ echo "$0: invalid method" 1>&2
+ exit 1
+esac
+++ /dev/null
-#!/bin/sh
-# Wrapper for uacme to work on openwrt.
-#
-# This program is free software; you can redistribute it and/or modify it under
-# the terms of the GNU General Public License as published by the Free Software
-# Foundation; either version 3 of the License, or (at your option) any later
-# version.
-#
-# Initial Author: Toke Høiland-Jørgensen <toke@toke.dk>
-# Adapted for uacme: Lucian Cristian <lucian.cristian@gmail.com>
-# Adapted for custom CA and TLS-ALPN-01: Peter Putzer <openwrt@mundschenk.at>
-
-CHECK_CRON=$1
-
-#check for installed packages, for now, support only one
-if [ -e "/usr/lib/acme/acme.sh" ]; then
- ACME=/usr/lib/acme/acme.sh
- APP=acme
-elif [ -e "/usr/sbin/uacme" ]; then
- ACME=/usr/sbin/uacme
- HPROGRAM=/usr/share/uacme/uacme.sh
- APP=uacme
-else
- echo "Please install ACME or uACME package"
- return 1
-fi
-
-export CURL_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt
-export NO_TIMESTAMP=1
-
-UHTTPD_LISTEN_HTTP=
-PRODUCTION_STATE_DIR='/etc/acme'
-STAGING_STATE_DIR='/etc/acme/staging'
-
-ACCOUNT_EMAIL=
-DEBUG=0
-NGINX_WEBSERVER=0
-UPDATE_NGINX=0
-UPDATE_UHTTPD=0
-UPDATE_HAPROXY=0
-FW_RULE=
-USER_CLEANUP=
-ACME_URL=
-ACME_STAGING_URL=
-
-. /lib/functions.sh
-
-check_cron()
-{
- [ -f "/etc/crontabs/root" ] && grep -q '/etc/init.d/acme' /etc/crontabs/root && return
- echo "0 0 * * * /etc/init.d/acme start" >> /etc/crontabs/root
- /etc/init.d/cron start
-}
-
-log()
-{
- logger -t $APP -s -p daemon.info "$@"
-}
-
-err()
-{
- logger -t $APP -s -p daemon.err "$@"
-}
-
-debug()
-{
- [ "$DEBUG" -eq "1" ] && logger -t $APP -s -p daemon.debug "$@"
-}
-
-get_listeners() {
- local proto rq sq listen remote state program
- netstat -nptl 2>/dev/null | while read proto listen program; do
- case "$proto#$listen#$program" in
- tcp#*:80#[0-9]*/*) echo -n "${program%% *} " ;;
- esac
- done
-}
-
-pre_checks()
-{
- main_domain="$1"
-
- log "Running pre checks for $main_domain."
-
- listeners="$(get_listeners)"
-
- debug "port80 listens: $listeners"
-
- for listener in $(get_listeners); do
- pid="${listener%/*}"
- cmd="${listener#*/}"
-
- case "$cmd" in
- uhttpd)
- debug "Found uhttpd listening on port 80"
- if [ "$APP" = "acme" ]; then
- UHTTPD_LISTEN_HTTP=$(uci get uhttpd.main.listen_http)
- if [ -z "$UHTTPD_LISTEN_HTTP" ]; then
- err "$main_domain: Unable to find uhttpd listen config."
- err "Manually disable uhttpd or set webroot to continue."
- return 1
- fi
- uci set uhttpd.main.listen_http=''
- uci commit uhttpd || return 1
- if ! /etc/init.d/uhttpd reload ; then
- uci set uhttpd.main.listen_http="$UHTTPD_LISTEN_HTTP"
- uci commit uhttpd
- return 1
- fi
- fi
- ;;
- nginx*)
- debug "Found nginx listening on port 80"
- NGINX_WEBSERVER=1
- if [ "$APP" = "acme" ]; then
- local tries=0
- while grep -sq "$cmd" "/proc/$pid/cmdline" && kill -0 "$pid"; do
- /etc/init.d/nginx stop
- if [ $tries -gt 10 ]; then
- debug "Can't stop nginx. Terminating script."
- return 1
- fi
- debug "Waiting for nginx to stop..."
- tries=$((tries + 1))
- sleep 1
- done
- fi
- ;;
- "")
- err "Nothing listening on port 80."
- err "Standalone mode not supported, setup uhttpd or nginx"
- return 1
- ;;
- *)
- err "$main_domain: unsupported (apache/haproxy?) daemon is listening on port 80."
- err "if webroot is set on your current webserver comment line 132 (return 1) from this script."
- return 1
- ;;
- esac
- done
-
- FW_RULE=$(uci add firewall rule) || return 1
- uci set firewall."$FW_RULE".name='uacme: temporarily allow incoming http'
- uci set firewall."$FW_RULE".enabled='1'
- uci set firewall."$FW_RULE".target='ACCEPT'
- uci set firewall."$FW_RULE".src='wan'
- uci set firewall."$FW_RULE".proto='tcp'
- uci set firewall."$FW_RULE".dest_port='80'
- uci commit firewall
- /etc/init.d/firewall reload
-
- debug "added firewall rule: $FW_RULE"
- return 0
-}
-
-post_checks()
-{
- log "Running post checks (cleanup)."
- # $FW_RULE contains the string to identify firewall rule created earlier
- if [ -n "$FW_RULE" ]; then
- uci delete firewall."$FW_RULE"
- uci commit firewall
- /etc/init.d/firewall reload
- fi
-
- if [ -e /etc/init.d/uhttpd ] && [ "$UPDATE_UHTTPD" -eq 1 ]; then
- uci commit uhttpd
- /etc/init.d/uhttpd reload
- log "Restarting uhttpd..."
- fi
-
- if [ -e /etc/init.d/nginx ] && ( [ "$NGINX_WEBSERVER" -eq 1 ] || [ "$UPDATE_NGINX" -eq 1 ]; ); then
- NGINX_WEBSERVER=0
- /etc/init.d/nginx restart
- log "Restarting nginx..."
- fi
-
- if [ -e /etc/init.d/haproxy ] && [ "$UPDATE_HAPROXY" -eq 1 ]; then
- /etc/init.d/haproxy restart
- log "Restarting haproxy..."
- fi
-
- if [ -n "$USER_CLEANUP" ] && [ -f "$USER_CLEANUP" ]; then
- log "Running user-provided cleanup script from $USER_CLEANUP."
- "$USER_CLEANUP" || return 1
- fi
-}
-
-err_out()
-{
- post_checks
- exit 1
-}
-
-int_out()
-{
- post_checks
- trap - INT
- kill -INT $$
-}
-
-is_staging()
-{
- local main_domain="$1"
-
- grep -q "acme-staging" "$STATE_DIR/$main_domain/${main_domain}.conf"
- return $?
-}
-
-issue_cert()
-{
- local section="$1"
- local acme_args=
- local debug=
- local enabled
- local use_staging
- local update_uhttpd
- local update_nginx
- local update_haproxy
- local keylength
- local domains
- local main_domain
- local failed_dir
- local webroot
- local dns
- local tls
- local user_setup
- local user_cleanup
- local ret
- local staging=
- local HOOK=
-
- # reload uci values, as the value of use_staging may have changed
- config_load acme
- config_get_bool enabled "$section" enabled 0
- config_get_bool use_staging "$section" use_staging
- config_get_bool update_uhttpd "$section" update_uhttpd
- config_get_bool update_nginx "$section" update_nginx
- config_get_bool update_haproxy "$section" update_haproxy
- config_get domains "$section" domains
- config_get keylength "$section" keylength
- config_get webroot "$section" webroot
- config_get dns "$section" dns
- config_get tls "$section" tls
- config_get user_setup "$section" user_setup
- config_get user_cleanup "$section" user_cleanup
-
- UPDATE_NGINX=$update_nginx
- UPDATE_UHTTPD=$update_uhttpd
- UPDATE_HAPROXY=$update_haproxy
- USER_CLEANUP=$user_cleanup
-
- [ "$enabled" -eq "1" ] || return 0
-
- if [ "$APP" = "uacme" ]; then
- [ "$DEBUG" -eq "1" ] && debug="--verbose --verbose"
- [ "$tls" -eq "1" ] && HPROGRAM=/usr/share/uacme/ualpn.sh
- elif [ "$APP" = "acme" ]; then
- [ "$DEBUG" -eq "1" ] && acme_args="$acme_args --debug"
- fi
- if [ "$use_staging" -eq "1" ]; then
- STATE_DIR="$STAGING_STATE_DIR";
-
- # Check if we should use a custom stagin URL
- if [ "$APP" = "uacme" -a -n "$ACME_STAGING_URL" ]; then
- ACME="$ACME --acme-url $ACME_STAGING_URL"
- else
- staging="--staging";
- fi
- else
- STATE_DIR="$PRODUCTION_STATE_DIR";
- staging="";
-
- if [ "$APP" = "uacme" -a -n "$ACME_URL" ]; then
- ACME="$ACME --acme-url $ACME_URL"
- fi
- fi
-
- set -- $domains
- main_domain=$1
-
- if [ -n "$user_setup" ] && [ -f "$user_setup" ]; then
- log "Running user-provided setup script from $user_setup."
- "$user_setup" "$main_domain" || return 2
- else
- [ -n "$webroot" ] || [ -n "$dns" ] || [ -n "$tls" ] || pre_checks "$main_domain" || return 2
- fi
-
- log "Running $APP for $main_domain"
-
- if [ "$APP" = "uacme" ]; then
- if [ ! -f "$STATE_DIR/private/key.pem" ]; then
- log "Create a new ACME account with email $ACCOUNT_EMAIL use staging=$use_staging"
- $ACME $debug --confdir "$STATE_DIR" $staging --yes new $ACCOUNT_EMAIL
- fi
-
- if [ -f "$STATE_DIR/$main_domain/cert.pem" ]; then
- log "Found previous cert config, use staging=$use_staging. Issuing renew."
- export CHALLENGE_PATH="$webroot"
- $ACME $debug --confdir "$STATE_DIR" $staging --never-create issue $domains --hook=$HPROGRAM; ret=$?
- post_checks
- return $ret
- fi
- fi
- if [ "$APP" = "acme" ]; then
- handle_credentials() {
- local credential="$1"
- eval export "$credential"
- }
- config_list_foreach "$section" credentials handle_credentials
-
- if [ -e "$STATE_DIR/$main_domain" ]; then
- if [ "$use_staging" -eq "0" ] && is_staging "$main_domain"; then
- log "Found previous cert issued using staging server. Moving it out of the way."
- mv "$STATE_DIR/$main_domain" "$STATE_DIR/$main_domain.staging"
- else
- log "Found previous cert config. Issuing renew."
- $ACME --home "$STATE_DIR" --renew -d "$main_domain" "$acme_args"; ret=$?
- post_checks
- return $ret
- fi
- fi
- fi
-
- acme_args="$acme_args --bits $keylength"
- acme_args="$acme_args $(for d in $domains; do echo -n " $d "; done)"
- if [ "$APP" = "acme" ]; then
- [ -n "$ACCOUNT_EMAIL" ] && acme_args="$acme_args --accountemail $ACCOUNT_EMAIL"
- [ "$use_staging" -eq "1" ] && acme_args="$acme_args --staging"
- fi
- if [ -n "$dns" ]; then
-#TO-DO
- if [ "$APP" = "acme" ]; then
- log "Using dns mode"
- acme_args="$acme_args --dns $dns"
- else
- log "Using dns mode, dns-01 is not wrapped yet"
- return 2
-# uacme_args="$uacme_args --dns $dns"
- fi
- elif [ -n "$tls" ]; then
- if [ "$APP" = "uacme" ]; then
- log "Using TLS mode"
- else
- log "TLS not supported by $APP"
- return 2
- fi
- elif [ -z "$webroot" ]; then
- if [ "$APP" = "acme" ]; then
- log "Using standalone mode"
- acme_args="$acme_args --standalone --listen-v6"
- else
- log "Standalone not supported by $APP"
- return 2
- fi
- else
- if [ ! -d "$webroot" ]; then
- err "$main_domain: Webroot dir '$webroot' does not exist!"
- post_checks
- return 2
- fi
- log "Using webroot dir: $webroot"
- if [ "$APP" = "uacme" ]; then
- export CHALLENGE_PATH="$webroot"
- else
- acme_args="$acme_args --webroot $webroot"
- fi
- fi
-
- if [ "$APP" = "uacme" ]; then
- workdir="--confdir"
- HOOK="--hook=$HPROGRAM"
- else
- workdir="--home"
- fi
-
- $ACME $debug $workdir "$STATE_DIR" $staging issue $acme_args $HOOK; ret=$?
- if [ "$ret" -ne 0 ]; then
- failed_dir="$STATE_DIR/${main_domain}.failed-$(date +%s)"
- err "Issuing cert for $main_domain failed. Moving state to $failed_dir"
- [ -d "$STATE_DIR/$main_domain" ] && mv "$STATE_DIR/$main_domain" "$failed_dir"
- [ -d "$STATE_DIR/private/$main_domain" ] && mv "$STATE_DIR/private/$main_domain" "$failed_dir"
- post_checks
- return $ret
- fi
-
- if [ -e /etc/init.d/uhttpd ] && [ "$update_uhttpd" -eq "1" ]; then
- if [ "$APP" = "uacme" ]; then
- uci set uhttpd.main.key="$STATE_DIR/private/${main_domain}/key.pem"
- uci set uhttpd.main.cert="$STATE_DIR/${main_domain}/cert.pem"
- else
- uci set uhttpd.main.key="$STATE_DIR/${main_domain}/${main_domain}.key"
- uci set uhttpd.main.cert="$STATE_DIR/${main_domain}/fullchain.cer"
- fi
- # commit and reload is in post_checks
- fi
-
- local nginx_updated
- nginx_updated=0
- if command -v nginx-util 2>/dev/null && [ "$update_nginx" -eq "1" ]; then
- nginx_updated=1
- for domain in $domains; do
- if [ "$APP" = "uacme" ]; then
- nginx-util add_ssl "${domain}" uacme "$STATE_DIR/${main_domain}/cert.pem" \
- "$STATE_DIR/private/${main_domain}/key.pem" || nginx_updated=0
- else
- nginx-util add_ssl "${domain}" acme "$STATE_DIR/${main_domain}/fullchain.cer" \
- "$STATE_DIR/${main_domain}/${main_domain}.key" || nginx_updated=0
- fi
- done
- # reload is in post_checks
- fi
-
- if [ "$nginx_updated" -eq "0" ] && [ -w /etc/nginx/nginx.conf ] && [ "$update_nginx" -eq "1" ]; then
- if [ "$APP" = "uacme" ]; then
- sed -i "s#ssl_certificate\ .*#ssl_certificate $STATE_DIR/${main_domain}/cert.pem;#g" /etc/nginx/nginx.conf
- sed -i "s#ssl_certificate_key\ .*#ssl_certificate_key $STATE_DIR/private/${main_domain}/key.pem;#g" /etc/nginx/nginx.conf
- else
- sed -i "s#ssl_certificate\ .*#ssl_certificate $STATE_DIR/${main_domain}/fullchain.cer;#g" /etc/nginx/nginx.conf
- sed -i "s#ssl_certificate_key\ .*#ssl_certificate_key $STATE_DIR/${main_domain}/${main_domain}.key;#g" /etc/nginx/nginx.conf
- fi
- # commit and reload is in post_checks
- fi
-
- if [ -e /etc/init.d/haproxy ] && [ "$update_haproxy" -eq 1 ]; then
- if [ "$APP" = "uacme" ]; then
- cat $STATE_DIR/${main_domain}/cert.pem $STATE_DIR/private/${main_domain}/key.pem > $STATE_DIR/${main_domain}/full_haproxy.pem
- else
- cat $STATE_DIR/${main_domain}/fullchain.cer $STATE_DIR/${main_domain}/${main_domain}.key > $STATE_DIR/${main_domain}/full_haproxy.pem
- fi
- fi
-
- post_checks
-}
-
-issue_cert_with_retries() {
- local section="$1"
- local use_staging
- local retries
- local use_auto_staging
- local infinite_retries
- config_get_bool use_staging "$section" use_staging
- config_get_bool use_auto_staging "$section" use_auto_staging
- config_get_bool enabled "$section" enabled
- config_get retries "$section" retries
-
- [ -z "$retries" ] && retries=1
- [ -z "$use_auto_staging" ] && use_auto_staging=0
- [ "$retries" -eq "0" ] && infinite_retries=1
- [ "$enabled" -eq "1" ] || return 0
-
- while true; do
- issue_cert "$1"; ret=$?
-
- if [ "$ret" -eq "2" ]; then
- # An error occurred while retrieving the certificate.
- retries="$((retries-1))"
-
- if [ "$use_auto_staging" -eq "1" ] && [ "$use_staging" -eq "0" ]; then
- log "Production certificate could not be obtained. Switching to staging server."
- use_staging=1
- uci set "acme.$1.use_staging=1"
- uci commit acme
- fi
-
- if [ -z "$infinite_retries" ] && [ "$retries" -lt "1" ]; then
- log "An error occurred while retrieving the certificate. Retries exceeded."
- return "$ret"
- fi
-
- if [ "$use_staging" -eq "1" ]; then
- # The "Failed Validations" limit of LetsEncrypt is 60 per hour. This
- # means one failure every minute. Here we wait 2 minutes to be within
- # limits for sure.
- sleeptime=120
- else
- # There is a "Failed Validation" limit of LetsEncrypt is 5 failures per
- # account, per hostname, per hour. This means one failure every 12
- # minutes. Here we wait 25 minutes to be within limits for sure.
- sleeptime=1500
- fi
-
- log "An error occurred while retrieving the certificate. Retrying in $sleeptime seconds."
- sleep "$sleeptime"
- continue
- else
- if [ "$use_auto_staging" -eq "1" ]; then
- if [ "$use_staging" -eq "0" ]; then
- log "Production certificate obtained. Exiting."
- else
- log "Staging certificate obtained. Continuing with production server."
- use_staging=0
- uci set "acme.$1.use_staging=0"
- uci commit acme
- continue
- fi
- fi
-
- return "$ret"
- fi
- done
-}
-
-load_vars()
-{
- local section="$1"
-
- PRODUCTION_STATE_DIR=$(config_get "$section" state_dir)
- STAGING_STATE_DIR=$PRODUCTION_STATE_DIR/staging
- ACCOUNT_EMAIL=$(config_get "$section" account_email)
- DEBUG=$(config_get "$section" debug)
- ACME_URL=$(config_get "$section" acme_url)
- ACME_STAGING_URL=$(config_get "$section" acme_staging_url)
-}
-
-if [ -z "$INCLUDE_ONLY" ]; then
- check_cron
- [ -n "$CHECK_CRON" ] && exit 0
- [ -e "/var/run/acme_boot" ] && rm -f "/var/run/acme_boot" && exit 0
-fi
-
-config_load acme
-config_foreach load_vars acme
-
-if [ -z "$PRODUCTION_STATE_DIR" ] || [ -z "$ACCOUNT_EMAIL" ]; then
- err "state_dir and account_email must be set"
- exit 1
-fi
-
-[ -d "$PRODUCTION_STATE_DIR" ] || mkdir -p "$PRODUCTION_STATE_DIR"
-[ -d "$STAGING_STATE_DIR" ] || mkdir -p "$STAGING_STATE_DIR"
-
-trap err_out HUP TERM
-trap int_out INT
-
-if [ -z "$INCLUDE_ONLY" ]; then
- config_foreach issue_cert_with_retries cert
-
- exit 0
-fi