diff --git a/net/acme-acmesh/Makefile b/net/acme-acmesh/Makefile index 637899749f..fcbb563fce 100644 --- a/net/acme-acmesh/Makefile +++ b/net/acme-acmesh/Makefile @@ -51,7 +51,7 @@ endef 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 diff --git a/net/uacme/Makefile b/net/uacme/Makefile index 01ed9886a6..7f348613df 100644 --- a/net/uacme/Makefile +++ b/net/uacme/Makefile @@ -19,7 +19,7 @@ PKG_MAINTAINER:=Lucian Cristian 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 @@ -46,7 +46,7 @@ define Package/uacme $(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 @@ -58,6 +58,12 @@ define Package/uacme-ualpn 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). @@ -100,30 +106,30 @@ define Package/uacme/install $(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)) diff --git a/net/uacme/files/acme.config b/net/uacme/files/acme.config deleted file mode 100644 index f79b907192..0000000000 --- a/net/uacme/files/acme.config +++ /dev/null @@ -1,16 +0,0 @@ -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 diff --git a/net/uacme/files/acme.init b/net/uacme/files/acme.init deleted file mode 100644 index f32a6ad183..0000000000 --- a/net/uacme/files/acme.init +++ /dev/null @@ -1,35 +0,0 @@ -#!/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 -} diff --git a/net/uacme/files/dnsapi_helper.sh b/net/uacme/files/dnsapi_helper.sh new file mode 100755 index 0000000000..2dd68b6b5c --- /dev/null +++ b/net/uacme/files/dnsapi_helper.sh @@ -0,0 +1,1260 @@ +#!/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 diff --git a/net/uacme/files/dnschalhook.sh b/net/uacme/files/dnschalhook.sh new file mode 100755 index 0000000000..e9ca8aea38 --- /dev/null +++ b/net/uacme/files/dnschalhook.sh @@ -0,0 +1,79 @@ +#!/bin/sh +# Copyright (C) 2019-2024 Nicola Di Lieto +# +# 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 . + +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 diff --git a/net/uacme/files/hook.sh b/net/uacme/files/hook.sh new file mode 100755 index 0000000000..e090312ca4 --- /dev/null +++ b/net/uacme/files/hook.sh @@ -0,0 +1,218 @@ +#!/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 diff --git a/net/uacme/files/httpchalhook.sh b/net/uacme/files/httpchalhook.sh new file mode 100755 index 0000000000..7e63fabb0c --- /dev/null +++ b/net/uacme/files/httpchalhook.sh @@ -0,0 +1,64 @@ +#!/bin/sh +# Copyright (C) 2019-2024 Nicola Di Lieto +# +# 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 . + +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 diff --git a/net/uacme/files/run.sh b/net/uacme/files/run.sh deleted file mode 100755 index ce82af6b9b..0000000000 --- a/net/uacme/files/run.sh +++ /dev/null @@ -1,540 +0,0 @@ -#!/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 -# Adapted for uacme: Lucian Cristian -# Adapted for custom CA and TLS-ALPN-01: Peter Putzer - -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