Mercurial Hosting > luan
changeset 2015:61b0cc7db09c default tip fixssl
Manually specify letsencrypt server due to acme default server change (to zerossl)
author | Violet7 |
---|---|
date | Sat, 27 Sep 2025 13:37:11 -0700 |
parents | 1cdc12cdcfa2 |
children | |
files | host/acme.sh host/update.sh src/luan/host/https.luan |
diffstat | 3 files changed, 1890 insertions(+), 1223 deletions(-) [+] |
line wrap: on
line diff
--- a/host/acme.sh Thu Sep 25 23:41:41 2025 -0600 +++ b/host/acme.sh Sat Sep 27 13:37:11 2025 -0700 @@ -1,6 +1,6 @@ -#!/usr/bin/env sh - -VER=2.8.8 +#!/bin/bash + +VER=3.1.2 PROJECT_NAME="acme.sh" @@ -20,35 +20,36 @@ _SUB_FOLDERS="$_SUB_FOLDER_DNSAPI $_SUB_FOLDER_DEPLOY $_SUB_FOLDER_NOTIFY" -LETSENCRYPT_CA_V1="https://acme-v01.api.letsencrypt.org/directory" -LETSENCRYPT_STAGING_CA_V1="https://acme-staging.api.letsencrypt.org/directory" - CA_LETSENCRYPT_V2="https://acme-v02.api.letsencrypt.org/directory" CA_LETSENCRYPT_V2_TEST="https://acme-staging-v02.api.letsencrypt.org/directory" -CA_BUYPASS="https://api.buypass.com/acme/directory" -CA_BUYPASS_TEST="https://api.test4.buypass.no/acme/directory" - CA_ZEROSSL="https://acme.zerossl.com/v2/DV90" -_ZERO_EAB_ENDPOINT="http://api.zerossl.com/acme/eab-credentials-email" - -DEFAULT_CA=$CA_LETSENCRYPT_V2 +_ZERO_EAB_ENDPOINT="https://api.zerossl.com/acme/eab-credentials-email" + +CA_SSLCOM_RSA="https://acme.ssl.com/sslcom-dv-rsa" +CA_SSLCOM_ECC="https://acme.ssl.com/sslcom-dv-ecc" + +CA_GOOGLE="https://dv.acme-v02.api.pki.goog/directory" +CA_GOOGLE_TEST="https://dv.acme-v02.test-api.pki.goog/directory" + +DEFAULT_CA=$CA_ZEROSSL DEFAULT_STAGING_CA=$CA_LETSENCRYPT_V2_TEST CA_NAMES=" +ZeroSSL.com,zerossl LetsEncrypt.org,letsencrypt LetsEncrypt.org_test,letsencrypt_test,letsencrypttest -BuyPass.com,buypass -BuyPass.com_test,buypass_test,buypasstest -ZeroSSL.com,zerossl +SSL.com,sslcom +Google.com,google +Google.com_test,googletest,google_test " -CA_SERVERS="$CA_LETSENCRYPT_V2,$CA_LETSENCRYPT_V2_TEST,$CA_BUYPASS,$CA_BUYPASS_TEST,$CA_ZEROSSL" +CA_SERVERS="$CA_ZEROSSL,$CA_LETSENCRYPT_V2,$CA_LETSENCRYPT_V2_TEST,$CA_SSLCOM_RSA,$CA_GOOGLE,$CA_GOOGLE_TEST" DEFAULT_USER_AGENT="$PROJECT_NAME/$VER ($PROJECT)" -DEFAULT_ACCOUNT_KEY_LENGTH=2048 -DEFAULT_DOMAIN_KEY_LENGTH=2048 +DEFAULT_ACCOUNT_KEY_LENGTH=ec-256 +DEFAULT_DOMAIN_KEY_LENGTH=ec-256 DEFAULT_OPENSSL_BIN="openssl" @@ -56,6 +57,9 @@ VTYPE_DNS="dns-01" VTYPE_ALPN="tls-alpn-01" +ID_TYPE_DNS="dns" +ID_TYPE_IP="ip" + LOCAL_ANY_ADDRESS="0.0.0.0" DEFAULT_RENEW=60 @@ -74,14 +78,15 @@ NGINX_START="#ACME_NGINX_START" NGINX_END="#ACME_NGINX_END" -BEGIN_CSR="-----BEGIN CERTIFICATE REQUEST-----" -END_CSR="-----END CERTIFICATE REQUEST-----" +BEGIN_CSR="-----BEGIN [NEW ]\{0,4\}CERTIFICATE REQUEST-----" +END_CSR="-----END [NEW ]\{0,4\}CERTIFICATE REQUEST-----" BEGIN_CERT="-----BEGIN CERTIFICATE-----" END_CERT="-----END CERTIFICATE-----" CONTENT_TYPE_JSON="application/jose+json" RENEW_SKIP=2 +CODE_DNS_MANUAL=3 B64CONF_START="__ACME_BASE64__START_" B64CONF_END="__ACME_BASE64__END_" @@ -92,16 +97,18 @@ LOG_LEVEL_1=1 LOG_LEVEL_2=2 LOG_LEVEL_3=3 -DEFAULT_LOG_LEVEL="$LOG_LEVEL_1" +DEFAULT_LOG_LEVEL="$LOG_LEVEL_2" DEBUG_LEVEL_1=1 DEBUG_LEVEL_2=2 DEBUG_LEVEL_3=3 -DEBUG_LEVEL_DEFAULT=$DEBUG_LEVEL_1 +DEBUG_LEVEL_DEFAULT=$DEBUG_LEVEL_2 DEBUG_LEVEL_NONE=0 DOH_CLOUDFLARE=1 DOH_GOOGLE=2 +DOH_ALI=3 +DOH_DP=4 HIDDEN_VALUE="[hidden](please add '--output-insecure' to see this value)" @@ -136,6 +143,8 @@ NOTIFY_MODE_DEFAULT=$NOTIFY_MODE_BULK +_BASE64_ENCODED_CFGS="Le_PreHook Le_PostHook Le_RenewHook Le_Preferred_Chain Le_ReloadCmd" + _DEBUG_WIKI="https://github.com/acmesh-official/acme.sh/wiki/How-to-debug-acme.sh" _PREPARE_LINK="https://github.com/acmesh-official/acme.sh/wiki/Install-preparations" @@ -156,10 +165,16 @@ _ZEROSSL_WIKI="https://github.com/acmesh-official/acme.sh/wiki/ZeroSSL.com-CA" +_SSLCOM_WIKI="https://github.com/acmesh-official/acme.sh/wiki/SSL.com-CA" + _SERVER_WIKI="https://github.com/acmesh-official/acme.sh/wiki/Server" _PREFERRED_CHAIN_WIKI="https://github.com/acmesh-official/acme.sh/wiki/Preferred-Chain" +_VALIDITY_WIKI="https://github.com/acmesh-official/acme.sh/wiki/Validity" + +_DNSCHECK_WIKI="https://github.com/acmesh-official/acme.sh/wiki/dnscheck" + _DNS_MANUAL_ERR="The dns manual mode can not renew automatically, you must issue it again manually. You'd better use the other modes instead." _DNS_MANUAL_WARN="It seems that you are using dns manual mode. please take care: $_DNS_MANUAL_ERR" @@ -211,11 +226,11 @@ echo "$ACME_OPENSSL_BIN doesn't exist." fi - echo "apache:" + echo "Apache:" if [ "$_APACHECTL" ] && _exists "$_APACHECTL"; then $_APACHECTL -V 2>&1 else - echo "apache doesn't exist." + echo "Apache doesn't exist." fi echo "nginx:" @@ -416,20 +431,34 @@ 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() { - # shellcheck disable=SC2018,SC2019 - tr 'a-z' 'A-Z' + 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() { - # shellcheck disable=SC2018,SC2019 - tr 'A-Z' 'a-z' + 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 + echo "$_str" | grep -- "^$_sub" >/dev/null 2>&1 } _endswith() { @@ -560,8 +589,16 @@ fi _h2b() { - if _exists xxd && xxd -r -p 2>/dev/null; then - return + 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) @@ -644,8 +681,10 @@ #0 1 2 3 4 5 6 7 8 9 - _ . ~ #30 31 32 33 34 35 36 37 38 39 2d 5f 2e 7e +#_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" @@ -855,6 +894,9 @@ ;; #other hex *) + if [ "$_upper_hex" = "upper-hex" ]; then + _hex_code=$(printf "%s" "$_hex_code" | _upper_case) + fi printf '%%%s' "$_hex_code" ;; esac @@ -888,6 +930,9 @@ if sed -h 2>&1 | grep "\-i\[SUFFIX]" >/dev/null 2>&1; then _debug "Using sed -i" sed -i "$options" "$filename" + elif sed -h 2>&1 | grep "\-i extension" >/dev/null 2>&1; then + _debug "Using FreeBSD sed -i" + sed -i "" "$options" "$filename" else _debug "No -i support in sed" text="$(cat "$filename")" @@ -895,8 +940,16 @@ fi } +if [ "$(echo abc | egrep -o b 2>/dev/null)" = "b" ]; then + __USE_EGREP=1 +else + __USE_EGREP="" +fi + _egrep_o() { - if ! egrep -o "$1" 2>/dev/null; then + if [ "$__USE_EGREP" ]; then + egrep -o -- "$1" 2>/dev/null + else sed -n 's/.*\('"$1"'\).*/\1/p' fi } @@ -913,7 +966,7 @@ i="$(grep -n -- "$startline" "$filename" | cut -d : -f 1)" if [ -z "$i" ]; then - _err "Can not find start line: $startline" + _err "Cannot find start line: $startline" return 1 fi i="$(_math "$i" + 1)" @@ -921,7 +974,7 @@ j="$(grep -n -- "$endline" "$filename" | cut -d : -f 1)" if [ -z "$j" ]; then - _err "Can not find end line: $endline" + _err "Cannot find end line: $endline" return 1 fi j="$(_math "$j" - 1)" @@ -946,9 +999,9 @@ #Usage: multiline _dbase64() { if [ "$1" ]; then - ${ACME_OPENSSL_BIN:-openssl} base64 -d -A + ${ACME_OPENSSL_BIN:-openssl} base64 -d else - ${ACME_OPENSSL_BIN:-openssl} base64 -d + ${ACME_OPENSSL_BIN:-openssl} base64 -d -A fi } @@ -1023,13 +1076,13 @@ _sign_openssl="${ACME_OPENSSL_BIN:-openssl} dgst -sign $keyfile " - if grep "BEGIN RSA PRIVATE KEY" "$keyfile" >/dev/null 2>&1 || grep "BEGIN PRIVATE KEY" "$keyfile" >/dev/null 2>&1; then + if _isRSA "$keyfile" >/dev/null 2>&1; then $_sign_openssl -$alg | _base64 - elif grep "BEGIN EC PRIVATE KEY" "$keyfile" >/dev/null 2>&1; then + 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" + _err "Key content: $(wc -l <"$keyfile") lines" return 1 fi _debug3 "_signedECText" "$_signedECText" @@ -1109,37 +1162,47 @@ length=2048 fi - _debug "Use length $length" - - if ! touch "$f" >/dev/null 2>&1; then - _f_path="$(dirname "$f")" - _debug _f_path "$_f_path" - if ! mkdir -p "$_f_path"; then - _err "Can not create path: $_f_path" + _debug "Using length $length" + + if ! [ -e "$f" ]; then + if ! touch "$f" >/dev/null 2>&1; then + _f_path="$(dirname "$f")" + _debug _f_path "$_f_path" + if ! mkdir -p "$_f_path"; then + _err "Cannot create path: $_f_path" + return 1 + fi + fi + if ! touch "$f" >/dev/null 2>&1; then return 1 fi + chmod 600 "$f" fi if _isEccKey "$length"; then - _debug "Using ec name: $eccname" - if _opkey="$(${ACME_OPENSSL_BIN:-openssl} ecparam -name "$eccname" -genkey 2>/dev/null)"; then + _debug "Using EC name: $eccname" + if _opkey="$(${ACME_OPENSSL_BIN:-openssl} ecparam -name "$eccname" -noout -genkey 2>/dev/null)"; then echo "$_opkey" >"$f" else - _err "error ecc key name: $eccname" + _err "Error encountered for ECC key named $eccname" return 1 fi else _debug "Using RSA: $length" - if _opkey="$(${ACME_OPENSSL_BIN:-openssl} genrsa "$length" 2>/dev/null)"; then + __traditional="" + if _contains "$(${ACME_OPENSSL_BIN:-openssl} help genrsa 2>&1)" "-traditional"; then + __traditional="-traditional" + fi + if _opkey="$(${ACME_OPENSSL_BIN:-openssl} genrsa $__traditional "$length" 2>/dev/null)"; then echo "$_opkey" >"$f" else - _err "error rsa key: $length" + _err "Error encountered for RSA key of length $length" return 1 fi fi if [ "$?" != "0" ]; then - _err "Create key error." + _err "Key creation error." return 1 fi } @@ -1148,7 +1211,7 @@ _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 '*.,-_') + _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" ] } @@ -1197,23 +1260,39 @@ _debug2 csr "$csr" _debug2 csrconf "$csrconf" - printf "[ req_distinguished_name ]\n[ req ]\ndistinguished_name = req_distinguished_name\nreq_extensions = v3_req\n[ v3_req ]\n\nkeyUsage = nonRepudiation, digitalSignature, keyEncipherment" >"$csrconf" + printf "[ req_distinguished_name ]\n[ req ]\ndistinguished_name = req_distinguished_name\nreq_extensions = v3_req\n[ v3_req ]" >"$csrconf" + + if [ "$Le_ExtKeyUse" ]; then + _savedomainconf Le_ExtKeyUse "$Le_ExtKeyUse" + printf "\nextendedKeyUsage=$Le_ExtKeyUse\n" >>"$csrconf" + else + printf "\nextendedKeyUsage=serverAuth,clientAuth\n" >>"$csrconf" + fi if [ "$acmeValidationv1" ]; then domainlist="$(_idn "$domainlist")" - printf -- "\nsubjectAltName=DNS:$domainlist" >>"$csrconf" + _debug2 domainlist "$domainlist" + alt="" + for dl in $(echo "$domainlist" | tr "," ' '); do + if [ "$alt" ]; then + alt="$alt,$(_getIdType "$dl" | _upper_case):$dl" + else + alt="$(_getIdType "$dl" | _upper_case):$dl" + fi + done + printf -- "\nsubjectAltName=$alt" >>"$csrconf" elif [ -z "$domainlist" ] || [ "$domainlist" = "$NO_VALUE" ]; then #single domain _info "Single domain" "$domain" - printf -- "\nsubjectAltName=DNS:$(_idn "$domain")" >>"$csrconf" + printf -- "\nsubjectAltName=$(_getIdType "$domain" | _upper_case):$(_idn "$domain")" >>"$csrconf" else domainlist="$(_idn "$domainlist")" _debug2 domainlist "$domainlist" - if _contains "$domainlist" ","; then - alt="DNS:$(_idn "$domain"),DNS:$(echo "$domainlist" | sed "s/,,/,/g" | sed "s/,/,DNS:/g")" - else - alt="DNS:$(_idn "$domain"),DNS:$domainlist" - fi + alt="$(_getIdType "$domain" | _upper_case):$(_idn "$domain")" + for dl in $(echo "'$domainlist'" | sed "s/,/' '/g"); do + dl=$(echo "$dl" | tr -d "'") + alt="$alt,$(_getIdType "$dl" | _upper_case):$dl" + done #multi _info "Multi domain" "$alt" printf -- "\nsubjectAltName=$alt" >>"$csrconf" @@ -1230,9 +1309,17 @@ _csr_cn="$(_idn "$domain")" _debug2 _csr_cn "$_csr_cn" if _contains "$(uname -a)" "MINGW"; then - ${ACME_OPENSSL_BIN:-openssl} req -new -sha256 -key "$csrkey" -subj "//CN=$_csr_cn" -config "$csrconf" -out "$csr" + if _isIP "$_csr_cn"; then + ${ACME_OPENSSL_BIN:-openssl} req -new -sha256 -key "$csrkey" -subj "//O=$PROJECT_NAME" -config "$csrconf" -out "$csr" + else + ${ACME_OPENSSL_BIN:-openssl} req -new -sha256 -key "$csrkey" -subj "//CN=$_csr_cn" -config "$csrconf" -out "$csr" + fi else - ${ACME_OPENSSL_BIN:-openssl} req -new -sha256 -key "$csrkey" -subj "/CN=$_csr_cn" -config "$csrconf" -out "$csr" + if _isIP "$_csr_cn"; then + ${ACME_OPENSSL_BIN:-openssl} req -new -sha256 -key "$csrkey" -subj "/O=$PROJECT_NAME" -config "$csrconf" -out "$csr" + else + ${ACME_OPENSSL_BIN:-openssl} req -new -sha256 -key "$csrkey" -subj "/CN=$_csr_cn" -config "$csrconf" -out "$csr" + fi fi } @@ -1323,6 +1410,12 @@ return 0 fi + if [ "$(uname)" = "AIX" ]; then + _debug "Using: AIX netstat" + netstat -an | grep "^tcp" | grep "LISTEN" | grep "\.$_port " + return 0 + fi + if _exists "netstat"; then _debug "Using: netstat" if netstat -help 2>&1 | grep "\-p proto" >/dev/null; then @@ -1367,6 +1460,9 @@ else ${ACME_OPENSSL_BIN:-openssl} pkcs12 -export -out "$_cpfx" -inkey "$_ckey" -in "$_ccert" -certfile "$_cca" fi + if [ "$?" = "0" ]; then + _savedomainconf "Le_PFXPassword" "$pfxPassword" + fi } @@ -1386,7 +1482,7 @@ _toPkcs "$CERT_PFX_PATH" "$CERT_KEY_PATH" "$CERT_PATH" "$CA_CERT_PATH" "$pfxPassword" if [ "$?" = "0" ]; then - _info "Success, Pfx is exported to: $CERT_PFX_PATH" + _info "Success, PFX has been exported to: $CERT_PFX_PATH" fi } @@ -1430,7 +1526,7 @@ length=$1 if [ -z "$length" ] || [ "$length" = "$NO_VALUE" ]; then - _debug "Use default length $DEFAULT_ACCOUNT_KEY_LENGTH" + _debug "Using default length $DEFAULT_ACCOUNT_KEY_LENGTH" length="$DEFAULT_ACCOUNT_KEY_LENGTH" fi @@ -1439,16 +1535,15 @@ mkdir -p "$CA_DIR" if [ -s "$ACCOUNT_KEY_PATH" ]; then - _info "Account key exists, skip" + _info "Account key exists, skipping" return 0 else #generate account key if _createkey "$length" "$ACCOUNT_KEY_PATH"; then - chmod 600 "$ACCOUNT_KEY_PATH" - _info "Create account key ok." + _info "Account key creation OK." return 0 else - _err "Create account key error." + _err "Account key creation error." return 1 fi fi @@ -1467,7 +1562,7 @@ _cdl=$2 if [ -z "$_cdl" ]; then - _debug "Use DEFAULT_DOMAIN_KEY_LENGTH=$DEFAULT_DOMAIN_KEY_LENGTH" + _debug "Using DEFAULT_DOMAIN_KEY_LENGTH=$DEFAULT_DOMAIN_KEY_LENGTH" _cdl="$DEFAULT_DOMAIN_KEY_LENGTH" fi @@ -1479,16 +1574,16 @@ _info "The domain key is here: $(__green $CERT_KEY_PATH)" return 0 else - _err "Can not create domain key" + _err "Cannot create domain key" return 1 fi else if [ "$_ACME_IS_RENEW" ]; then - _info "Domain key exists, skip" + _info "Domain key exists, skipping" return 0 else - _err "Domain key exists, do you want to overwrite the key?" - _err "Add '--force', and try again." + _err "Domain key exists, do you want to overwrite it?" + _err "If so, add '--force' and try again." return 1 fi fi @@ -1497,9 +1592,9 @@ # domain domainlist isEcc createCSR() { - _info "Creating csr" + _info "Creating CSR" if [ -z "$1" ]; then - _usage "Usage: $PROJECT_ENTRY --create-csr --domain <domain.tld> [--domain <domain2.tld> ...]" + _usage "Usage: $PROJECT_ENTRY --create-csr --domain <domain.tld> [--domain <domain2.tld> ...] [--ecc]" return fi @@ -1510,13 +1605,13 @@ _initpath "$domain" "$_isEcc" if [ -f "$CSR_PATH" ] && [ "$_ACME_IS_RENEW" ] && [ -z "$FORCE" ]; then - _info "CSR exists, skip" + _info "CSR exists, skipping" return fi if [ ! -f "$CERT_KEY_PATH" ]; then - _err "The key file is not found: $CERT_KEY_PATH" - _err "Please create the key file first." + _err "This key file was not found: $CERT_KEY_PATH" + _err "Please create it first." return 1 fi _createcsr "$domain" "$domainlist" "$CERT_KEY_PATH" "$CSR_PATH" "$DOMAIN_SSL_CONF" @@ -1542,23 +1637,27 @@ _time2str() { #BSD - if date -u -r "$1" 2>/dev/null; then + if date -u -r "$1" -j "+%Y-%m-%dT%H:%M:%SZ" 2>/dev/null; then return fi #Linux - if date -u -d@"$1" 2>/dev/null; then + if date -u --date=@"$1" "+%Y-%m-%dT%H:%M:%SZ" 2>/dev/null; then + return + fi + + #Omnios + if date -u -r "$1" +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null; then return fi #Solaris - if _exists adb; then - _t_s_a=$(echo "0t${1}=Y" | adb) - echo "$_t_s_a" + if printf "%(%Y-%m-%dT%H:%M:%SZ)T\n" $1 2>/dev/null; then + return fi #Busybox - if echo "$1" | awk '{ print strftime("%c", $0); }' 2>/dev/null; then + if echo "$1" | awk '{ print strftime("%Y-%m-%dT%H:%M:%SZ", $0); }' 2>/dev/null; then return fi } @@ -1582,6 +1681,24 @@ } #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 +} + +#keyfile _calcjwk() { keyfile="$1" if [ -z "$keyfile" ]; then @@ -1594,7 +1711,7 @@ return 0 fi - if grep "BEGIN RSA PRIVATE KEY" "$keyfile" >/dev/null 2>&1; then + if _isRSA "$keyfile"; then _debug "RSA key" pub_exp=$(${ACME_OPENSSL_BIN:-openssl} rsa -in "$keyfile" -noout -text | grep "^publicExponent:" | cut -d '(' -f 2 | cut -d 'x' -f 2 | cut -d ')' -f 1) if [ "${#pub_exp}" = "5" ]; then @@ -1616,7 +1733,7 @@ JWK_HEADER='{"alg": "RS256", "jwk": '$jwk'}' JWK_HEADERPLACE_PART1='{"nonce": "' JWK_HEADERPLACE_PART2='", "alg": "RS256"' - elif grep "BEGIN EC PRIVATE KEY" "$keyfile" >/dev/null 2>&1; then + elif _isEcc "$keyfile"; then _debug "EC key" crv="$(${ACME_OPENSSL_BIN:-openssl} ec -in "$keyfile" -noout -text 2>/dev/null | grep "^NIST CURVE:" | cut -d ":" -f 2 | tr -d " \r\n")" _debug3 crv "$crv" @@ -1643,7 +1760,7 @@ __ECC_KEY_LEN=512 ;; *) - _err "ECC oid : $crv_oid" + _err "ECC oid: $crv_oid" return 1 ;; esac @@ -1673,7 +1790,7 @@ _debug3 x64 "$x64" xend=$(_math "$xend" + 1) - y="$(printf "%s" "$pubtext" | cut -d : -f "$xend"-10000)" + y="$(printf "%s" "$pubtext" | cut -d : -f "$xend"-2048)" _debug3 y "$y" y64="$(printf "%s" "$y" | tr -d : | _h2b | _base64 | _url_replace)" @@ -1686,7 +1803,7 @@ JWK_HEADERPLACE_PART1='{"nonce": "' JWK_HEADERPLACE_PART2='", "alg": "ES'$__ECC_KEY_LEN'"' else - _err "Only RSA or EC key is supported. keyfile=$keyfile" + _err "Only RSA or EC keys are supported. keyfile=$keyfile" _debug2 "$(cat "$keyfile")" return 1 fi @@ -1699,6 +1816,35 @@ date -u "+%s" } +#support 2 formats: +# 2022-04-01 08:10:33 to 1648800633 +#or 2022-04-01T08:10:33Z to 1648800633 +_date2time() { + #Mac/BSD + if date -u -j -f "%Y-%m-%d %H:%M:%S" "$(echo "$1" | tr -d "Z" | tr "T" ' ')" +"%s" 2>/dev/null; then + return + fi + #Linux + if date -u -d "$(echo "$1" | tr -d "Z" | tr "T" ' ')" +"%s" 2>/dev/null; then + return + fi + + #Solaris + if gdate -u -d "$(echo "$1" | tr -d "Z" | tr "T" ' ')" +"%s" 2>/dev/null; then + return + fi + #Omnios + if python3 -c "import datetime; print(int(datetime.datetime.strptime(\"$1\", \"%Y-%m-%d %H:%M:%S\").replace(tzinfo=datetime.timezone.utc).timestamp()))" 2>/dev/null; then + return + fi + #Omnios + if python3 -c "import datetime; print(int(datetime.datetime.strptime(\"$1\", \"%Y-%m-%dT%H:%M:%SZ\").replace(tzinfo=datetime.timezone.utc).timestamp()))" 2>/dev/null; then + return + fi + _err "Cannot parse _date2time $1" + return 1 +} + _utc_date() { date -u "+%Y-%m-%d %H:%M:%S" } @@ -1719,7 +1865,15 @@ echo "/$LE_TEMP_DIR/wefADf24sf.$(_time).tmp" return 0 fi - _err "Can not create temp file." + _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() { @@ -1737,8 +1891,11 @@ fi if [ -z "$_ACME_CURL" ] && _exists "curl"; then - _ACME_CURL="curl -L --silent --dump-header $HTTP_HEADER " - if [ "$DEBUG" ] && [ "$DEBUG" -ge "2" ]; 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 @@ -1749,26 +1906,37 @@ _ACME_CURL="$_ACME_CURL --cacert $CA_BUNDLE " fi - if _contains "$(curl --help 2>&1)" "--globoff"; then + 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 - _ACME_WGET="$_ACME_WGET -d " + 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 - fi - - #from wget 1.14: do not skip body on 404 error - if [ "$_ACME_WGET" ] && _contains "$($_ACME_WGET --help 2>&1)" "--content-on-error"; then - _ACME_WGET="$_ACME_WGET --content-on-error " + + #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 @@ -1886,15 +2054,21 @@ _ret="$?" if [ "$_ret" = "8" ]; then _ret=0 - _debug "wget returns 8, the server returns a 'Bad request' response, lets process the response later." + _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 - _sed_i "s/^ *//g" "$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" else _ret="$?" - _err "Neither curl nor wget is found, can not do $httpmethod." + _err "Neither curl nor wget have been found, cannot make $httpmethod request." fi _debug "_ret" "$_ret" printf "%s" "$response" @@ -1944,21 +2118,33 @@ fi _debug "_WGET" "$_WGET" if [ "$onlyheader" ]; then - $_WGET --user-agent="$USER_AGENT" --header "$_H5" --header "$_H4" --header "$_H3" --header "$_H2" --header "$_H1" -S -O /dev/null "$url" 2>&1 | sed 's/^[ ]*//g' + _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" -O - "$url" + $_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 returns 8, the server returns a 'Bad request' response, lets process the response later." + _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 is found, can not do GET." + _err "Neither curl nor wget have been found, cannot make GET request." fi _debug "ret" "$ret" return $ret @@ -1969,10 +2155,16 @@ } _tail_n() { - if ! tail -n "$1" 2>/dev/null; then + if _is_solaris; then #fix for solaris tail -"$1" - fi + else + tail -n "$1" + fi +} + +_tail_c() { + tail -c "$1" 2>/dev/null || tail -"$1"c } # url payload needbase64 keyfile @@ -1984,6 +2176,7 @@ if [ -z "$keyfile" ]; then keyfile="$ACCOUNT_KEY_PATH" fi + _debug "=======Sending Signed Request=======" _debug url "$url" _debug payload "$payload" @@ -2010,7 +2203,7 @@ if _post "" "$nonceurl" "" "HEAD" "$__request_conent_type" >/dev/null; then _headers="$(cat "$HTTP_HEADER")" _debug2 _headers "$_headers" - _CACHED_NONCE="$(echo "$_headers" | grep -i "Replay-Nonce:" | _head_n 1 | tr -d "\r\n " | cut -d ':' -f 2)" + _CACHED_NONCE="$(echo "$_headers" | grep -i "Replay-Nonce:" | _head_n 1 | tr -d "\r\n " | cut -d ':' -f 2 | cut -d , -f 1)" fi fi if [ -z "$_CACHED_NONCE" ]; then @@ -2027,9 +2220,8 @@ _debug2 _headers "$_headers" _CACHED_NONCE="$(echo "$_headers" | grep -i "Replay-Nonce:" | _head_n 1 | tr -d "\r\n " | cut -d ':' -f 2)" fi - _debug2 _CACHED_NONCE "$_CACHED_NONCE" if [ "$?" != "0" ]; then - _err "Can not connect to $nonceurl to get nonce." + _err "Cannot connect to $nonceurl to get nonce." return 1 fi else @@ -2042,17 +2234,15 @@ _sleep 2 continue fi - if [ "$ACME_VERSION" = "2" ]; then - if [ "$url" = "$ACME_NEW_ACCOUNT" ]; then - protected="$JWK_HEADERPLACE_PART1$nonce\", \"url\": \"${url}$JWK_HEADERPLACE_PART2, \"jwk\": $jwk"'}' - elif [ "$url" = "$ACME_REVOKE_CERT" ] && [ "$keyfile" != "$ACCOUNT_KEY_PATH" ]; then - protected="$JWK_HEADERPLACE_PART1$nonce\", \"url\": \"${url}$JWK_HEADERPLACE_PART2, \"jwk\": $jwk"'}' - else - protected="$JWK_HEADERPLACE_PART1$nonce\", \"url\": \"${url}$JWK_HEADERPLACE_PART2, \"kid\": \"${ACCOUNT_URL}\""'}' - fi + + if [ "$url" = "$ACME_NEW_ACCOUNT" ]; then + protected="$JWK_HEADERPLACE_PART1$nonce\", \"url\": \"${url}$JWK_HEADERPLACE_PART2, \"jwk\": $jwk"'}' + elif [ "$url" = "$ACME_REVOKE_CERT" ] && [ "$keyfile" != "$ACCOUNT_KEY_PATH" ]; then + protected="$JWK_HEADERPLACE_PART1$nonce\", \"url\": \"${url}$JWK_HEADERPLACE_PART2, \"jwk\": $jwk"'}' else - protected="$JWK_HEADERPLACE_PART1$nonce\", \"url\": \"${url}$JWK_HEADERPLACE_PART2, \"jwk\": $jwk"'}' - fi + protected="$JWK_HEADERPLACE_PART1$nonce\", \"url\": \"${url}$JWK_HEADERPLACE_PART2, \"kid\": \"${ACCOUNT_URL}\""'}' + fi + _debug3 protected "$protected" protected64="$(printf "%s" "$protected" | _base64 | _url_replace)" @@ -2074,7 +2264,7 @@ _CACHED_NONCE="" if [ "$?" != "0" ]; then - _err "Can not post to $url" + _err "Cannot make POST request to $url" return 1 fi @@ -2086,11 +2276,11 @@ _debug2 original "$response" if echo "$responseHeaders" | grep -i "Content-Type: *application/json" >/dev/null 2>&1; then - response="$(echo "$response" | _normalizeJson | _json_decode)" + response="$(echo "$response" | _json_decode | _normalizeJson)" fi _debug2 response "$response" - _CACHED_NONCE="$(echo "$responseHeaders" | grep -i "Replay-Nonce:" | _head_n 1 | tr -d "\r\n " | cut -d ':' -f 2)" + _CACHED_NONCE="$(echo "$responseHeaders" | grep -i "Replay-Nonce:" | _head_n 1 | tr -d "\r\n " | cut -d ':' -f 2 | cut -d , -f 1)" if ! _startswith "$code" "2"; then _body="$response" @@ -2099,8 +2289,28 @@ _debug3 _body "$_body" fi + _retryafter=$(echo "$responseHeaders" | grep -i "^Retry-After *: *[0-9]\+ *" | cut -d : -f 2 | tr -d ' ' | tr -d '\r') + if [ "$code" = '503' ]; then + _sleep_overload_retry_sec=$_retryafter + if [ -z "$_sleep_overload_retry_sec" ]; then + _sleep_overload_retry_sec=5 + fi + if [ $_sleep_overload_retry_sec -le 600 ]; then + _info "It seems the CA server is currently overloaded, let's wait and retry. Sleeping for $_sleep_overload_retry_sec seconds." + _sleep $_sleep_overload_retry_sec + continue + else + _info "The retryafter=$_retryafter value is too large (> 600), will not retry anymore." + fi + fi if _contains "$_body" "JWS has invalid anti-replay nonce" || _contains "$_body" "JWS has an invalid anti-replay nonce"; then - _info "It seems the CA server is busy now, let's wait and retry. Sleeping $_sleep_retry_sec seconds." + _info "It seems the CA server is busy now, let's wait and retry. Sleeping for $_sleep_retry_sec seconds." + _CACHED_NONCE="" + _sleep $_sleep_retry_sec + continue + fi + if _contains "$_body" "The Replay Nonce is not recognized"; then + _info "The replay nonce is not valid, let's get a new one. Sleeping for $_sleep_retry_sec seconds." _CACHED_NONCE="" _sleep $_sleep_retry_sec continue @@ -2127,12 +2337,18 @@ 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" @@ -2140,6 +2356,9 @@ 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" @@ -2163,7 +2382,7 @@ if [ "$_s_c_f" ]; then _setopt "$_s_c_f" "$_sdkey" "=" "'$_sdvalue'" else - _err "config file is empty, can not save $_sdkey=$_sdvalue" + _err "Config file is empty, cannot save $_sdkey=$_sdvalue" fi } @@ -2173,9 +2392,9 @@ _sdkey="$2" if [ "$_c_c_f" ]; then _conf_data="$(cat "$_c_c_f")" - echo "$_conf_data" | sed "s/^$_sdkey *=.*$//" >"$_c_c_f" + echo "$_conf_data" | sed "/^$_sdkey *=.*$/d" >"$_c_c_f" else - _err "config file is empty, can not clear" + _err "Config file is empty, cannot clear" fi } @@ -2193,7 +2412,7 @@ fi printf "%s" "$_sdv" else - _debug "config file is empty, can not read $_sdkey" + _debug "Config file is empty, cannot read $_sdkey" fi } @@ -2213,6 +2432,31 @@ _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" @@ -2227,12 +2471,14 @@ if [ "$_rac_value" ]; then if _startswith "$_rac_value" '"' && _endswith "$_rac_value" '"'; then _debug2 "trim quotation marks" - eval "export $_rac_key=$_rac_value" + eval $_rac_key=$_rac_value + export $_rac_key fi return 0 # do nothing fi - _saved=$(_readdomainconf "SAVED_$_rac_key") - eval "export $_rac_key=\"$_saved\"" + _saved="$(_readdomainconf "SAVED_$_rac_key")" + eval $_rac_key=\$_saved + export $_rac_key } #_saveaccountconf key value base64encode @@ -2263,6 +2509,13 @@ _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" @@ -2292,17 +2545,21 @@ _debug Le_Listen_V6 "$Le_Listen_V6" _NC="socat" - if [ "$Le_Listen_V4" ]; then + if [ "$Le_Listen_V6" ]; then + _NC="$_NC -6" + SOCAT_OPTIONS=TCP6-LISTEN + elif [ "$Le_Listen_V4" ]; then _NC="$_NC -4" - elif [ "$Le_Listen_V6" ]; then - _NC="$_NC -6" + SOCAT_OPTIONS=TCP4-LISTEN + else + SOCAT_OPTIONS=TCP-LISTEN fi if [ "$DEBUG" ] && [ "$DEBUG" -gt "1" ]; then _NC="$_NC -d -d -v" fi - SOCAT_OPTIONS=TCP-LISTEN:$Le_HTTPPort,crlf,reuseaddr,fork + SOCAT_OPTIONS=$SOCAT_OPTIONS:$Le_HTTPPort,crlf,reuseaddr,fork #Adding bind to local-address if [ "$ncaddr" ]; then @@ -2312,22 +2569,34 @@ _content_len="$(printf "%s" "$content" | wc -c)" _debug _content_len "$_content_len" _debug "_NC" "$_NC $SOCAT_OPTIONS" + export _SOCAT_ERR="$(_mktemp)" $_NC $SOCAT_OPTIONS SYSTEM:"sleep 1; \ echo 'HTTP/1.0 200 OK'; \ echo 'Content-Length\: $_content_len'; \ echo ''; \ -printf -- '$content';" & +printf '%s' '$content';" 2>"$_SOCAT_ERR" & serverproc="$!" + if [ -f "$_SOCAT_ERR" ]; then + if grep "Permission denied" "$_SOCAT_ERR" >/dev/null; then + _err "socat: $(cat $_SOCAT_ERR)" + _err "Can not listen for user: $(whoami)" + _err "Maybe try with root again?" + rm -f "$_SOCAT_ERR" + return 1 + fi + fi } _stopserver() { pid="$1" _debug "pid" "$pid" if [ -z "$pid" ]; then + rm -f "$_SOCAT_ERR" return fi kill $pid + rm -f "$_SOCAT_ERR" } @@ -2365,7 +2634,7 @@ #create key TLS_KEY if ! _createkey "2048" "$TLS_KEY"; then - _err "Create tls validation key error." + _err "Error creating TLS validation key." return 1 fi @@ -2375,13 +2644,13 @@ alt="$alt,$san_b" fi if ! _createcsr "tls.acme.sh" "$alt" "$TLS_KEY" "$TLS_CSR" "$TLS_CONF" "$acmeValidationv1"; then - _err "Create tls validation csr error." + _err "Error creating TLS validation CSR." return 1 fi #self signed if ! _signcsr "$TLS_KEY" "$TLS_CSR" "$TLS_CONF" "$TLS_CERT"; then - _err "Create tls validation cert error." + _err "Error creating TLS validation cert." return 1 fi @@ -2435,16 +2704,16 @@ __initHome() { if [ -z "$_SCRIPT_HOME" ]; then if _exists readlink && _exists dirname; then - _debug "Lets find script dir." + _debug "Let's find the script directory." _debug "_SCRIPT_" "$_SCRIPT_" _script="$(_readlink "$_SCRIPT_")" _debug "_script" "$_script" _script_home="$(dirname "$_script")" _debug "_script_home" "$_script_home" if [ -d "$_script_home" ]; then - _SCRIPT_HOME="$_script_home" + export _SCRIPT_HOME="$_script_home" else - _err "It seems the script home is not correct:$_script_home" + _err "It seems the script home is not correct: $_script_home" fi fi fi @@ -2459,7 +2728,7 @@ # fi if [ -z "$LE_WORKING_DIR" ]; then - _debug "Using default home:$DEFAULT_INSTALL_HOME" + _debug "Using default home: $DEFAULT_INSTALL_HOME" LE_WORKING_DIR="$DEFAULT_INSTALL_HOME" fi export LE_WORKING_DIR @@ -2467,7 +2736,7 @@ if [ -z "$LE_CONFIG_HOME" ]; then LE_CONFIG_HOME="$LE_WORKING_DIR" fi - _debug "Using config home:$LE_CONFIG_HOME" + _debug "Using config home: $LE_CONFIG_HOME" export LE_CONFIG_HOME _DEFAULT_ACCOUNT_CONF_PATH="$LE_CONFIG_HOME/account.conf" @@ -2491,76 +2760,56 @@ fi } +_clearAPI() { + ACME_NEW_ACCOUNT="" + ACME_KEY_CHANGE="" + ACME_NEW_AUTHZ="" + ACME_NEW_ORDER="" + ACME_REVOKE_CERT="" + ACME_NEW_NONCE="" + ACME_AGREEMENT="" +} + #server _initAPI() { _api_server="${1:-$ACME_DIRECTORY}" - _debug "_init api for server: $_api_server" - - if [ -z "$ACME_NEW_ACCOUNT" ]; then - response=$(_get "$_api_server") + _debug "_init API for server: $_api_server" + + MAX_API_RETRY_TIMES=10 + _sleep_retry_sec=10 + _request_retry_times=0 + while [ -z "$ACME_NEW_ACCOUNT" ] && [ "${_request_retry_times}" -lt "$MAX_API_RETRY_TIMES" ]; do + _request_retry_times=$(_math "$_request_retry_times" + 1) + response=$(_get "$_api_server" "" 10) if [ "$?" != "0" ]; then _debug2 "response" "$response" - _err "Can not init api." - return 1 + _info "Cannot init API for: $_api_server." + _info "Sleeping for $_sleep_retry_sec seconds and retrying." + _sleep "$_sleep_retry_sec" + continue fi response=$(echo "$response" | _json_decode) _debug2 "response" "$response" - ACME_KEY_CHANGE=$(echo "$response" | _egrep_o 'key-change" *: *"[^"]*"' | cut -d '"' -f 3) - if [ -z "$ACME_KEY_CHANGE" ]; then - ACME_KEY_CHANGE=$(echo "$response" | _egrep_o 'keyChange" *: *"[^"]*"' | cut -d '"' -f 3) - fi + ACME_KEY_CHANGE=$(echo "$response" | _egrep_o 'keyChange" *: *"[^"]*"' | cut -d '"' -f 3) export ACME_KEY_CHANGE - ACME_NEW_AUTHZ=$(echo "$response" | _egrep_o 'new-authz" *: *"[^"]*"' | cut -d '"' -f 3) - if [ -z "$ACME_NEW_AUTHZ" ]; then - ACME_NEW_AUTHZ=$(echo "$response" | _egrep_o 'newAuthz" *: *"[^"]*"' | cut -d '"' -f 3) - fi + ACME_NEW_AUTHZ=$(echo "$response" | _egrep_o 'newAuthz" *: *"[^"]*"' | cut -d '"' -f 3) export ACME_NEW_AUTHZ - ACME_NEW_ORDER=$(echo "$response" | _egrep_o 'new-cert" *: *"[^"]*"' | cut -d '"' -f 3) - ACME_NEW_ORDER_RES="new-cert" - if [ -z "$ACME_NEW_ORDER" ]; then - ACME_NEW_ORDER=$(echo "$response" | _egrep_o 'new-order" *: *"[^"]*"' | cut -d '"' -f 3) - ACME_NEW_ORDER_RES="new-order" - if [ -z "$ACME_NEW_ORDER" ]; then - ACME_NEW_ORDER=$(echo "$response" | _egrep_o 'newOrder" *: *"[^"]*"' | cut -d '"' -f 3) - fi - fi + ACME_NEW_ORDER=$(echo "$response" | _egrep_o 'newOrder" *: *"[^"]*"' | cut -d '"' -f 3) export ACME_NEW_ORDER - export ACME_NEW_ORDER_RES - - ACME_NEW_ACCOUNT=$(echo "$response" | _egrep_o 'new-reg" *: *"[^"]*"' | cut -d '"' -f 3) - ACME_NEW_ACCOUNT_RES="new-reg" - if [ -z "$ACME_NEW_ACCOUNT" ]; then - ACME_NEW_ACCOUNT=$(echo "$response" | _egrep_o 'new-account" *: *"[^"]*"' | cut -d '"' -f 3) - ACME_NEW_ACCOUNT_RES="new-account" - if [ -z "$ACME_NEW_ACCOUNT" ]; then - ACME_NEW_ACCOUNT=$(echo "$response" | _egrep_o 'newAccount" *: *"[^"]*"' | cut -d '"' -f 3) - if [ "$ACME_NEW_ACCOUNT" ]; then - export ACME_VERSION=2 - fi - fi - fi + + ACME_NEW_ACCOUNT=$(echo "$response" | _egrep_o 'newAccount" *: *"[^"]*"' | cut -d '"' -f 3) export ACME_NEW_ACCOUNT - export ACME_NEW_ACCOUNT_RES - - ACME_REVOKE_CERT=$(echo "$response" | _egrep_o 'revoke-cert" *: *"[^"]*"' | cut -d '"' -f 3) - if [ -z "$ACME_REVOKE_CERT" ]; then - ACME_REVOKE_CERT=$(echo "$response" | _egrep_o 'revokeCert" *: *"[^"]*"' | cut -d '"' -f 3) - fi + + ACME_REVOKE_CERT=$(echo "$response" | _egrep_o 'revokeCert" *: *"[^"]*"' | cut -d '"' -f 3) export ACME_REVOKE_CERT - ACME_NEW_NONCE=$(echo "$response" | _egrep_o 'new-nonce" *: *"[^"]*"' | cut -d '"' -f 3) - if [ -z "$ACME_NEW_NONCE" ]; then - ACME_NEW_NONCE=$(echo "$response" | _egrep_o 'newNonce" *: *"[^"]*"' | cut -d '"' -f 3) - fi + ACME_NEW_NONCE=$(echo "$response" | _egrep_o 'newNonce" *: *"[^"]*"' | cut -d '"' -f 3) export ACME_NEW_NONCE - ACME_AGREEMENT=$(echo "$response" | _egrep_o 'terms-of-service" *: *"[^"]*"' | cut -d '"' -f 3) - if [ -z "$ACME_AGREEMENT" ]; then - ACME_AGREEMENT=$(echo "$response" | _egrep_o 'termsOfService" *: *"[^"]*"' | cut -d '"' -f 3) - fi + ACME_AGREEMENT=$(echo "$response" | _egrep_o 'termsOfService" *: *"[^"]*"' | cut -d '"' -f 3) export ACME_AGREEMENT _debug "ACME_KEY_CHANGE" "$ACME_KEY_CHANGE" @@ -2570,9 +2819,23 @@ _debug "ACME_REVOKE_CERT" "$ACME_REVOKE_CERT" _debug "ACME_AGREEMENT" "$ACME_AGREEMENT" _debug "ACME_NEW_NONCE" "$ACME_NEW_NONCE" - _debug "ACME_VERSION" "$ACME_VERSION" - - fi + if [ "$ACME_NEW_ACCOUNT" ] && [ "$ACME_NEW_ORDER" ]; then + return 0 + fi + _info "Sleeping for $_sleep_retry_sec seconds and retrying." + _sleep "$_sleep_retry_sec" + done + if [ "$ACME_NEW_ACCOUNT" ] && [ "$ACME_NEW_ORDER" ]; then + return 0 + fi + _err "Cannot init API for $_api_server" + return 1 +} + +_clearCA() { + export CA_CONF= + export ACCOUNT_KEY_PATH= + export ACCOUNT_JSON_PATH= } #[domain] [keylength or isEcc flag] @@ -2616,15 +2879,44 @@ _ACME_SERVER_HOST="$(echo "$ACME_DIRECTORY" | cut -d : -f 2 | tr -s / | cut -d / -f 2)" _debug2 "_ACME_SERVER_HOST" "$_ACME_SERVER_HOST" - CA_DIR="$CA_HOME/$_ACME_SERVER_HOST" - + _ACME_SERVER_PATH="$(echo "$ACME_DIRECTORY" | cut -d : -f 2- | tr -s / | cut -d / -f 3-)" + _debug2 "_ACME_SERVER_PATH" "$_ACME_SERVER_PATH" + + CA_DIR="$CA_HOME/$_ACME_SERVER_HOST/$_ACME_SERVER_PATH" _DEFAULT_CA_CONF="$CA_DIR/ca.conf" - if [ -z "$CA_CONF" ]; then CA_CONF="$_DEFAULT_CA_CONF" fi _debug3 CA_CONF "$CA_CONF" + _OLD_CADIR="$CA_HOME/$_ACME_SERVER_HOST" + _OLD_ACCOUNT_KEY="$_OLD_CADIR/account.key" + _OLD_ACCOUNT_JSON="$_OLD_CADIR/account.json" + _OLD_CA_CONF="$_OLD_CADIR/ca.conf" + + _DEFAULT_ACCOUNT_KEY_PATH="$CA_DIR/account.key" + _DEFAULT_ACCOUNT_JSON_PATH="$CA_DIR/account.json" + if [ -z "$ACCOUNT_KEY_PATH" ]; then + ACCOUNT_KEY_PATH="$_DEFAULT_ACCOUNT_KEY_PATH" + if [ -f "$_OLD_ACCOUNT_KEY" ] && ! [ -f "$ACCOUNT_KEY_PATH" ]; then + mkdir -p "$CA_DIR" + mv "$_OLD_ACCOUNT_KEY" "$ACCOUNT_KEY_PATH" + fi + fi + + if [ -z "$ACCOUNT_JSON_PATH" ]; then + ACCOUNT_JSON_PATH="$_DEFAULT_ACCOUNT_JSON_PATH" + if [ -f "$_OLD_ACCOUNT_JSON" ] && ! [ -f "$ACCOUNT_JSON_PATH" ]; then + mkdir -p "$CA_DIR" + mv "$_OLD_ACCOUNT_JSON" "$ACCOUNT_JSON_PATH" + fi + fi + + if [ -f "$_OLD_CA_CONF" ] && ! [ -f "$CA_CONF" ]; then + mkdir -p "$CA_DIR" + mv "$_OLD_CA_CONF" "$CA_CONF" + fi + if [ -f "$CA_CONF" ]; then . "$CA_CONF" fi @@ -2645,19 +2937,6 @@ HTTP_HEADER="$LE_CONFIG_HOME/http.header" fi - _OLD_ACCOUNT_KEY="$LE_WORKING_DIR/account.key" - _OLD_ACCOUNT_JSON="$LE_WORKING_DIR/account.json" - - _DEFAULT_ACCOUNT_KEY_PATH="$CA_DIR/account.key" - _DEFAULT_ACCOUNT_JSON_PATH="$CA_DIR/account.json" - if [ -z "$ACCOUNT_KEY_PATH" ]; then - ACCOUNT_KEY_PATH="$_DEFAULT_ACCOUNT_KEY_PATH" - fi - - if [ -z "$ACCOUNT_JSON_PATH" ]; then - ACCOUNT_JSON_PATH="$_DEFAULT_ACCOUNT_JSON_PATH" - fi - _DEFAULT_CERT_HOME="$LE_CONFIG_HOME" if [ -z "$CERT_HOME" ]; then CERT_HOME="$_DEFAULT_CERT_HOME" @@ -2679,12 +2958,14 @@ if _isEccKey "$_ilength"; then DOMAIN_PATH="$domainhomeecc" - else + elif [ -z "$__SELECTED_RSA_KEY" ]; then if [ ! -d "$domainhome" ] && [ -d "$domainhomeecc" ]; then - _info "The domain '$domain' seems to have a ECC cert already, please add '$(__red "--ecc")' parameter if you want to use that cert." + _info "The domain '$domain' seems to already have an ECC cert, let's use it." + DOMAIN_PATH="$domainhomeecc" fi fi _debug DOMAIN_PATH "$DOMAIN_PATH" + export DOMAIN_PATH fi if [ -z "$DOMAIN_BACKUP_PATH" ]; then @@ -2736,36 +3017,19 @@ } -_exec() { - if [ -z "$_EXEC_TEMP_ERR" ]; then - _EXEC_TEMP_ERR="$(_mktemp)" - fi - - if [ "$_EXEC_TEMP_ERR" ]; then - eval "$@ 2>>$_EXEC_TEMP_ERR" - else - eval "$@" - fi -} - -_exec_err() { - [ "$_EXEC_TEMP_ERR" ] && _err "$(cat "$_EXEC_TEMP_ERR")" && echo "" >"$_EXEC_TEMP_ERR" -} - _apachePath() { _APACHECTL="apachectl" if ! _exists apachectl; then if _exists apache2ctl; then _APACHECTL="apache2ctl" else - _err "'apachectl not found. It seems that apache is not installed, or you are not root user.'" + _err "'apachectl not found. It seems that Apache is not installed or you are not root.'" _err "Please use webroot mode to try again." return 1 fi fi - if ! _exec $_APACHECTL -V >/dev/null; then - _exec_err + if ! $_APACHECTL -V >/dev/null; then return 1 fi @@ -2778,7 +3042,7 @@ _debug httpdconfname "$httpdconfname" if [ -z "$httpdconfname" ]; then - _err "Can not read apache config file." + _err "Cannot read Apache config file." return 1 fi @@ -2795,7 +3059,7 @@ _debug httpdconf "$httpdconf" _debug httpdconfname "$httpdconfname" if [ ! -f "$httpdconf" ]; then - _err "Apache Config file not found" "$httpdconf" + _err "Apache config file not found" "$httpdconf" return 1 fi return 0 @@ -2817,9 +3081,8 @@ cat "$APACHE_CONF_BACKUP_DIR/$httpdconfname" >"$httpdconf" _debug "Restored: $httpdconf." - if ! _exec $_APACHECTL -t; then - _exec_err - _err "Sorry, restore apache config error, please contact me." + if ! $_APACHECTL -t; then + _err "Sorry, there's been an error restoring the Apache config. Please ask for support on $PROJECT." return 1 fi _debug "Restored successfully." @@ -2834,27 +3097,26 @@ fi #test the conf first - _info "Checking if there is an error in the apache config file before starting." - - if ! _exec "$_APACHECTL" -t >/dev/null; then - _exec_err - _err "The apache config file has error, please fix it first, then try again." - _err "Don't worry, there is nothing changed to your system." + _info "Checking if there is an error in the Apache config file before starting." + + if ! $_APACHECTL -t >/dev/null; then + _err "The Apache config file has errors, please fix them first then try again." + _err "Don't worry, no changes to your system have been made." return 1 else _info "OK" fi #backup the conf - _debug "Backup apache config file" "$httpdconf" + _debug "Backing up Apache config file" "$httpdconf" if ! cp "$httpdconf" "$APACHE_CONF_BACKUP_DIR/"; then - _err "Can not backup apache config file, so abort. Don't worry, the apache config is not changed." - _err "This might be a bug of $PROJECT_NAME , please report issue: $PROJECT" + _err "Cannot backup Apache config file, aborting. Don't worry, the Apache config has not been changed." + _err "This might be an $PROJECT_NAME bug, please open an issue on $PROJECT" return 1 fi - _info "JFYI, Config file $httpdconf is backuped to $APACHE_CONF_BACKUP_DIR/$httpdconfname" - _info "In case there is an error that can not be restored automatically, you may try restore it yourself." - _info "The backup file will be deleted on success, just forget it." + _info "Config file $httpdconf has been backed up to $APACHE_CONF_BACKUP_DIR/$httpdconfname" + _info "In case an error causes it to not be restored automatically, you can restore it yourself." + _info "You do not need to do anything on success, as the backup file will automatically be deleted." #add alias @@ -2866,6 +3128,7 @@ if [ "$apacheVer" ] && [ "$apacheMajor$apacheMinor" -ge "24" ]; then echo " Alias /.well-known/acme-challenge $ACME_DIR + <Directory $ACME_DIR > Require all granted </Directory> @@ -2873,6 +3136,7 @@ else echo " Alias /.well-known/acme-challenge $ACME_DIR + <Directory $ACME_DIR > Order allow,deny Allow from all @@ -2882,11 +3146,11 @@ _msg="$($_APACHECTL -t 2>&1)" if [ "$?" != "0" ]; then - _err "Sorry, apache config error" + _err "Sorry, an Apache config error has occurred" if _restoreApache; then - _err "The apache config file is restored." + _err "The Apache config file has been restored." else - _err "Sorry, the apache config file can not be restored, please report bug." + _err "Sorry, the Apache config file cannot be restored, please open an issue on $PROJECT." fi return 1 fi @@ -2896,9 +3160,8 @@ chmod 755 "$ACME_DIR" fi - if ! _exec "$_APACHECTL" graceful; then - _exec_err - _err "$_APACHECTL graceful error, please contact me." + if ! $_APACHECTL graceful; then + _err "$_APACHECTL graceful error, please open an issue on $PROJECT." _restoreApache return 1 fi @@ -2922,18 +3185,18 @@ _start_f="$(echo "$_croot" | cut -d : -f 2)" _debug _start_f "$_start_f" if [ -z "$_start_f" ]; then - _debug "find start conf from nginx command" + _debug "Finding config using the nginx command" if [ -z "$NGINX_CONF" ]; then if ! _exists "nginx"; then - _err "nginx command is not found." + _err "nginx command not found." return 1 fi - NGINX_CONF="$(nginx -V 2>&1 | _egrep_o "--conf-path=[^ ]* " | tr -d " ")" + NGINX_CONF="$(nginx -V 2>&1 | _egrep_o "\-\-conf-path=[^ ]* " | tr -d " ")" _debug NGINX_CONF "$NGINX_CONF" NGINX_CONF="$(echo "$NGINX_CONF" | cut -d = -f 2)" _debug NGINX_CONF "$NGINX_CONF" if [ -z "$NGINX_CONF" ]; then - _err "Can not find nginx conf." + _err "Cannot find nginx config." NGINX_CONF="" return 1 fi @@ -2942,16 +3205,16 @@ NGINX_CONF="" return 1 fi - _debug "Found nginx conf file:$NGINX_CONF" + _debug "Found nginx config file: $NGINX_CONF" fi _start_f="$NGINX_CONF" fi - _debug "Start detect nginx conf for $_d from:$_start_f" + _debug "Detecting nginx conf for $_d from: $_start_f" if ! _checkConf "$_d" "$_start_f"; then - _err "Can not find conf file for domain $d" + _err "Cannot find config file for domain $d" return 1 fi - _info "Found conf file: $FOUND_REAL_NGINX_CONF" + _info "Found config file: $FOUND_REAL_NGINX_CONF" _ln=$FOUND_REAL_NGINX_CONF_LN _debug "_ln" "$_ln" @@ -2961,7 +3224,7 @@ _start_tag="$(sed -n "$_lnn,${_lnn}p" "$FOUND_REAL_NGINX_CONF")" _debug "_start_tag" "$_start_tag" if [ "$_start_tag" = "$NGINX_START" ]; then - _info "The domain $_d is already configured, skip" + _info "The domain $_d is already configured, skipping" FOUND_REAL_NGINX_CONF="" return 0 fi @@ -2970,28 +3233,28 @@ _backup_conf="$DOMAIN_BACKUP_PATH/$_d.nginx.conf" _debug _backup_conf "$_backup_conf" BACKUP_NGINX_CONF="$_backup_conf" - _info "Backup $FOUND_REAL_NGINX_CONF to $_backup_conf" + _info "Backing $FOUND_REAL_NGINX_CONF up to $_backup_conf" if ! cp "$FOUND_REAL_NGINX_CONF" "$_backup_conf"; then - _err "backup error." + _err "Backup error." FOUND_REAL_NGINX_CONF="" return 1 fi if ! _exists "nginx"; then - _err "nginx command is not found." + _err "nginx command not found." return 1 fi - _info "Check the nginx conf before setting up." - if ! _exec "nginx -t" >/dev/null; then - _exec_err + _info "Checking the nginx config before setting up." + if ! nginx -t >/dev/null 2>&1; then + _err "It seems that the nginx config is not correct, cannot continue." return 1 fi - _info "OK, Set up nginx config file" + _info "OK, setting up the nginx config file" if ! sed -n "1,${_ln}p" "$_backup_conf" >"$FOUND_REAL_NGINX_CONF"; then cat "$_backup_conf" >"$FOUND_REAL_NGINX_CONF" - _err "write nginx conf error, but don't worry, the file is restored to the original version." + _err "Error writing nginx config. Restoring it to its original version." return 1 fi @@ -3005,22 +3268,20 @@ if ! sed -n "${_lnn},99999p" "$_backup_conf" >>"$FOUND_REAL_NGINX_CONF"; then cat "$_backup_conf" >"$FOUND_REAL_NGINX_CONF" - _err "write nginx conf error, but don't worry, the file is restored." + _err "Error writing nginx config. Restoring it to its original version." return 1 fi _debug3 "Modified config:$(cat $FOUND_REAL_NGINX_CONF)" - _info "nginx conf is done, let's check it again." - if ! _exec "nginx -t" >/dev/null; then - _exec_err - _err "It seems that nginx conf was broken, let's restore." + _info "nginx config has been written, let's check it again." + if ! nginx -t >/dev/null 2>&1; then + _err "There seems to be a problem with the nginx config, let's restore it to its original version." cat "$_backup_conf" >"$FOUND_REAL_NGINX_CONF" return 1 fi - _info "Reload nginx" - if ! _exec "nginx -s reload" >/dev/null; then - _exec_err - _err "It seems that nginx reload error, let's restore." + _info "Reloading nginx" + if ! nginx -s reload >/dev/null 2>&1; then + _err "There seems to be a problem with the nginx config, let's restore it to its original version." cat "$_backup_conf" >"$FOUND_REAL_NGINX_CONF" return 1 fi @@ -3032,7 +3293,7 @@ _checkConf() { _d="$1" _c_file="$2" - _debug "Start _checkConf from:$_c_file" + _debug "Starting _checkConf from: $_c_file" if [ ! -f "$2" ] && ! echo "$2" | grep '*$' >/dev/null && echo "$2" | grep '*' >/dev/null; then _debug "wildcard" for _w_f in $2; do @@ -3045,14 +3306,19 @@ elif [ -f "$2" ]; then _debug "single" if _isRealNginxConf "$1" "$2"; then - _debug "$2 is found." + _debug "$2 found." FOUND_REAL_NGINX_CONF="$2" return 0 fi if cat "$2" | tr "\t" " " | grep "^ *include *.*;" >/dev/null; then - _debug "Try include files" + _debug "Trying include files" for included in $(cat "$2" | tr "\t" " " | grep "^ *include *.*;" | sed "s/include //" | tr -d " ;"); do - _debug "check included $included" + _debug "Checking included $included" + if ! _startswith "$included" "/" && _exists dirname; then + _relpath="$(dirname "$2")" + _debug "_relpath" "$_relpath" + included="$_relpath/$included" + fi if _checkConf "$1" "$included"; then return 0 fi @@ -3123,7 +3389,7 @@ #restore all the nginx conf _restoreNginx() { if [ -z "$NGINX_RESTORE_VLIST" ]; then - _debug "No need to restore nginx, skip." + _debug "No need to restore nginx config, skipping." return fi _debug "_restoreNginx" @@ -3138,10 +3404,9 @@ cat "$_ngbackupconf" >"$_ngconf" done - _info "Reload nginx" - if ! _exec "nginx -s reload" >/dev/null; then - _exec_err - _err "It seems that nginx reload error, please report bug." + _info "Reloading nginx" + if ! nginx -s reload >/dev/null; then + _err "An error occurred while reloading nginx, please open an issue on $PROJECT." return 1 fi return 0 @@ -3166,7 +3431,7 @@ _debug "dns_entries" "$dns_entries" if [ -z "$dns_entries" ]; then - _debug "skip dns." + _debug "Skipping dns." return fi _info "Removing DNS records." @@ -3189,7 +3454,7 @@ fi if [ -z "$d_api" ]; then - _info "Not Found domain api file: $d_api" + _info "Domain API file was not found: $d_api" continue fi @@ -3199,21 +3464,21 @@ ( if ! . "$d_api"; then - _err "Load file $d_api error. Please check your api file and try again." + _err "Error loading file $d_api. Please check your API file and try again." return 1 fi rmcommand="${_currentRoot}_rm" if ! _exists "$rmcommand"; then - _err "It seems that your api file doesn't define $rmcommand" + _err "It seems that your API file doesn't define $rmcommand" return 1 fi _info "Removing txt: $txt for domain: $txtdomain" if ! $rmcommand "$txtdomain" "$txt"; then - _err "Error removing txt for domain:$txtdomain" + _err "Error removing txt for domain: $txtdomain" return 1 fi - _info "Removed: Success" + _info "Successfully removed" ) done @@ -3223,7 +3488,7 @@ _clearupwebbroot() { __webroot="$1" if [ -z "$__webroot" ]; then - _debug "no webroot specified, skip" + _debug "No webroot specified, skipping" return 0 fi @@ -3235,12 +3500,12 @@ elif [ "$2" = '3' ]; then _rmpath="$__webroot/.well-known/acme-challenge/$3" else - _debug "Skip for removelevel:$2" + _debug "Skipping for removelevel: $2" fi if [ "$_rmpath" ]; then if [ "$DEBUG" ]; then - _debug "Debugging, skip removing: $_rmpath" + _debug "Debugging, not removing: $_rmpath" else rm -rf "$_rmpath" fi @@ -3261,11 +3526,13 @@ _debug _chk_alt_domains "$_chk_alt_domains" #run pre hook if [ "$_chk_pre_hook" ]; then - _info "Run pre hook:'$_chk_pre_hook'" + _info "Running pre hook:'$_chk_pre_hook'" if ! ( + export Le_Domain="$_chk_main_domain" + export Le_Alt="$_chk_alt_domains" cd "$DOMAIN_PATH" && eval "$_chk_pre_hook" ); then - _err "Error when run pre hook." + _err "Error occurred when running pre hook." return 1 fi fi @@ -3290,7 +3557,7 @@ if [ -z "$d" ]; then break fi - _debug "Check for domain" "$d" + _debug "Checking for domain" "$d" _currentRoot="$(_getfield "$_chk_web_roots" $_index)" _debug "_currentRoot" "$_currentRoot" _index=$(_math $_index + 1) @@ -3324,7 +3591,7 @@ _netprc="$(_ss "$_checkport" | grep "$_checkport")" netprc="$(echo "$_netprc" | grep "$_checkaddr")" if [ -z "$netprc" ]; then - netprc="$(echo "$_netprc" | grep "$LOCAL_ANY_ADDRESS")" + netprc="$(echo "$_netprc" | grep "$LOCAL_ANY_ADDRESS:$_checkport")" fi if [ "$netprc" ]; then _err "$netprc" @@ -3337,7 +3604,7 @@ if _hasfield "$_chk_web_roots" "apache"; then if ! _setApache; then - _err "set up apache error. Report error to me." + _err "Error setting up Apache. Please open an issue on $PROJECT." return 1 fi else @@ -3354,17 +3621,17 @@ if [ "$LOG_FILE" ]; then _err "Please check log file for more details: $LOG_FILE" else - _err "Please add '--debug' or '--log' to check more details." + _err "Please add '--debug' or '--log' to see more information." _err "See: $_DEBUG_WIKI" fi #run the post hook if [ "$_chk_post_hook" ]; then - _info "Run post hook:'$_chk_post_hook'" + _info "Running post hook: '$_chk_post_hook'" if ! ( cd "$DOMAIN_PATH" && eval "$_chk_post_hook" ); then - _err "Error when run post hook." + _err "Error encountered while running post hook." return 1 fi fi @@ -3403,7 +3670,7 @@ #run the post hook if [ "$_chk_post_hook" ]; then - _info "Run post hook:'$_chk_post_hook'" + _info "Running post hook:'$_chk_post_hook'" if ! ( export CERT_PATH export CERT_KEY_PATH @@ -3412,14 +3679,14 @@ export Le_Domain="$_main_domain" cd "$DOMAIN_PATH" && eval "$_chk_post_hook" ); then - _err "Error when run post hook." + _err "Error encountered while running post hook." return 1 fi fi #run renew hook if [ "$_ACME_IS_RENEW" ] && [ "$_chk_renew_hook" ]; then - _info "Run renew hook:'$_chk_renew_hook'" + _info "Running renew hook: '$_chk_renew_hook'" if ! ( export CERT_PATH export CERT_KEY_PATH @@ -3428,7 +3695,7 @@ export Le_Domain="$_main_domain" cd "$DOMAIN_PATH" && eval "$_chk_renew_hook" ); then - _err "Error when run renew hook." + _err "Error encountered while running renew hook." return 1 fi fi @@ -3481,19 +3748,10 @@ _initAPI mkdir -p "$CA_DIR" - if [ ! -f "$ACCOUNT_KEY_PATH" ] && [ -f "$_OLD_ACCOUNT_KEY" ]; then - _info "mv $_OLD_ACCOUNT_KEY to $ACCOUNT_KEY_PATH" - mv "$_OLD_ACCOUNT_KEY" "$ACCOUNT_KEY_PATH" - fi - - if [ ! -f "$ACCOUNT_JSON_PATH" ] && [ -f "$_OLD_ACCOUNT_JSON" ]; then - _info "mv $_OLD_ACCOUNT_JSON to $ACCOUNT_JSON_PATH" - mv "$_OLD_ACCOUNT_JSON" "$ACCOUNT_JSON_PATH" - fi if [ ! -f "$ACCOUNT_KEY_PATH" ]; then if ! _create_account_key "$_reg_length"; then - _err "Create account key error." + _err "Error creating account key." return 1 fi fi @@ -3513,73 +3771,71 @@ if [ "$_email" ]; then _savecaconf "CA_EMAIL" "$_email" fi - if [ "$ACME_VERSION" = "2" ]; then - if [ "$ACME_DIRECTORY" = "$CA_ZEROSSL" ]; then - if [ -z "$_eab_id" ] || [ -z "$_eab_hmac_key" ]; then - _info "No EAB credentials found for ZeroSSL, let's get one" - if [ -z "$_email" ]; then - _err "Please provide a email address for ZeroSSL account." - _err "See ZeroSSL usage: $_ZEROSSL_WIKI" - return 1 - fi - _eabresp=$(_post "email=$_email" $_ZERO_EAB_ENDPOINT) - if [ "$?" != "0" ]; then - _debug2 "$_eabresp" - _err "Can not get EAB credentials from ZeroSSL." - return 1 - fi - _eab_id="$(echo "$_eabresp" | tr ',}' '\n' | grep '"eab_kid"' | cut -d : -f 2 | tr -d '"')" - if [ -z "$_eab_id" ]; then - _err "Can not resolve _eab_id" - return 1 - fi - _eab_hmac_key="$(echo "$_eabresp" | tr ',}' '\n' | grep '"eab_hmac_key"' | cut -d : -f 2 | tr -d '"')" - if [ -z "$_eab_hmac_key" ]; then - _err "Can not resolve _eab_hmac_key" - return 1 - fi - _savecaconf CA_EAB_KEY_ID "$_eab_id" - _savecaconf CA_EAB_HMAC_KEY "$_eab_hmac_key" - fi - fi - if [ "$_eab_id" ] && [ "$_eab_hmac_key" ]; then - eab_protected="{\"alg\":\"HS256\",\"kid\":\"$_eab_id\",\"url\":\"${ACME_NEW_ACCOUNT}\"}" - _debug3 eab_protected "$eab_protected" - - eab_protected64=$(printf "%s" "$eab_protected" | _base64 | _url_replace) - _debug3 eab_protected64 "$eab_protected64" - - eab_payload64=$(printf "%s" "$jwk" | _base64 | _url_replace) - _debug3 eab_payload64 "$eab_payload64" - - eab_sign_t="$eab_protected64.$eab_payload64" - _debug3 eab_sign_t "$eab_sign_t" - - key_hex="$(_durl_replace_base64 "$_eab_hmac_key" | _dbase64 | _hex_dump | tr -d ' ')" - _debug3 key_hex "$key_hex" - - eab_signature=$(printf "%s" "$eab_sign_t" | _hmac sha256 $key_hex | _base64 | _url_replace) - _debug3 eab_signature "$eab_signature" - - externalBinding=",\"externalAccountBinding\":{\"protected\":\"$eab_protected64\", \"payload\":\"$eab_payload64\", \"signature\":\"$eab_signature\"}" - _debug3 externalBinding "$externalBinding" - fi - if [ "$_email" ]; then - email_sg="\"contact\": [\"mailto:$_email\"], " - fi - regjson="{$email_sg\"termsOfServiceAgreed\": true$externalBinding}" - else - _reg_res="$ACME_NEW_ACCOUNT_RES" - regjson='{"resource": "'$_reg_res'", "terms-of-service-agreed": true, "agreement": "'$ACME_AGREEMENT'"}' - if [ "$_email" ]; then - regjson='{"resource": "'$_reg_res'", "contact": ["mailto:'$_email'"], "terms-of-service-agreed": true, "agreement": "'$ACME_AGREEMENT'"}' - fi - fi + + if [ "$ACME_DIRECTORY" = "$CA_ZEROSSL" ]; then + if [ -z "$_eab_id" ] || [ -z "$_eab_hmac_key" ]; then + _info "No EAB credentials found for ZeroSSL, let's obtain them" + if [ -z "$_email" ]; then + _info "$(__green "$PROJECT_NAME is using ZeroSSL as default CA now.")" + _info "$(__green "Please update your account with an email address first.")" + _info "$(__green "$PROJECT_ENTRY --register-account -m my@example.com")" + _info "See: $(__green "$_ZEROSSL_WIKI")" + return 1 + fi + _eabresp=$(_post "email=$_email" $_ZERO_EAB_ENDPOINT) + if [ "$?" != "0" ]; then + _debug2 "$_eabresp" + _err "Cannot get EAB credentials from ZeroSSL." + return 1 + fi + _secure_debug2 _eabresp "$_eabresp" + _eab_id="$(echo "$_eabresp" | tr ',}' '\n\n' | grep '"eab_kid"' | cut -d : -f 2 | tr -d '"')" + _secure_debug2 _eab_id "$_eab_id" + if [ -z "$_eab_id" ]; then + _err "Cannot resolve _eab_id" + return 1 + fi + _eab_hmac_key="$(echo "$_eabresp" | tr ',}' '\n\n' | grep '"eab_hmac_key"' | cut -d : -f 2 | tr -d '"')" + _secure_debug2 _eab_hmac_key "$_eab_hmac_key" + if [ -z "$_eab_hmac_key" ]; then + _err "Cannot resolve _eab_hmac_key" + return 1 + fi + _savecaconf CA_EAB_KEY_ID "$_eab_id" + _savecaconf CA_EAB_HMAC_KEY "$_eab_hmac_key" + fi + fi + if [ "$_eab_id" ] && [ "$_eab_hmac_key" ]; then + eab_protected="{\"alg\":\"HS256\",\"kid\":\"$_eab_id\",\"url\":\"${ACME_NEW_ACCOUNT}\"}" + _debug3 eab_protected "$eab_protected" + + eab_protected64=$(printf "%s" "$eab_protected" | _base64 | _url_replace) + _debug3 eab_protected64 "$eab_protected64" + + eab_payload64=$(printf "%s" "$jwk" | _base64 | _url_replace) + _debug3 eab_payload64 "$eab_payload64" + + eab_sign_t="$eab_protected64.$eab_payload64" + _debug3 eab_sign_t "$eab_sign_t" + + key_hex="$(_durl_replace_base64 "$_eab_hmac_key" | _dbase64 | _hex_dump | tr -d ' ')" + _debug3 key_hex "$key_hex" + + eab_signature=$(printf "%s" "$eab_sign_t" | _hmac sha256 $key_hex | _base64 | _url_replace) + _debug3 eab_signature "$eab_signature" + + externalBinding=",\"externalAccountBinding\":{\"protected\":\"$eab_protected64\", \"payload\":\"$eab_payload64\", \"signature\":\"$eab_signature\"}" + _debug3 externalBinding "$externalBinding" + fi + if [ "$_email" ]; then + email_sg="\"contact\": [\"mailto:$_email\"], " + fi + regjson="{$email_sg\"termsOfServiceAgreed\": true$externalBinding}" _info "Registering account: $ACME_DIRECTORY" if ! _send_signed_request "${ACME_NEW_ACCOUNT}" "$regjson"; then - _err "Register account Error: $response" + _err "Error registering account: $response" return 1 fi @@ -3590,10 +3846,10 @@ elif [ "$code" = '409' ] || [ "$code" = '200' ]; then _info "Already registered" elif [ "$code" = '400' ] && _contains "$response" 'The account is not awaiting external account binding'; then - _info "Already register EAB." + _info "EAB already registered" _eabAlreadyBound=1 else - _err "Register account Error: $response" + _err "Account registration error: $response" return 1 fi @@ -3602,7 +3858,7 @@ _accUri="$(echo "$responseHeaders" | grep -i "^Location:" | _head_n 1 | cut -d ':' -f 2- | tr -d "\r\n ")" _debug "_accUri" "$_accUri" if [ -z "$_accUri" ]; then - _err "Can not find account id url." + _err "Cannot find account id url." _err "$responseHeaders" return 1 fi @@ -3617,7 +3873,7 @@ _savecaconf CA_KEY_HASH "$CA_KEY_HASH" if [ "$code" = '403' ]; then - _err "It seems that the account key is already deactivated, please use a new account key." + _err "It seems that the account key has been deactivated, please use a new account key." return 1 fi @@ -3629,18 +3885,8 @@ updateaccount() { _initpath - if [ ! -f "$ACCOUNT_KEY_PATH" ] && [ -f "$_OLD_ACCOUNT_KEY" ]; then - _info "mv $_OLD_ACCOUNT_KEY to $ACCOUNT_KEY_PATH" - mv "$_OLD_ACCOUNT_KEY" "$ACCOUNT_KEY_PATH" - fi - - if [ ! -f "$ACCOUNT_JSON_PATH" ] && [ -f "$_OLD_ACCOUNT_JSON" ]; then - _info "mv $_OLD_ACCOUNT_JSON to $ACCOUNT_JSON_PATH" - mv "$_OLD_ACCOUNT_JSON" "$ACCOUNT_JSON_PATH" - fi - if [ ! -f "$ACCOUNT_KEY_PATH" ]; then - _err "Account key is not found at: $ACCOUNT_KEY_PATH" + _err "Account key not found at: $ACCOUNT_KEY_PATH" return 1 fi @@ -3648,8 +3894,7 @@ _debug _accUri "$_accUri" if [ -z "$_accUri" ]; then - _err "The account url is empty, please run '--update-account' first to update the account info first," - _err "Then try again." + _err "The account URL is empty, please run '--update-account' first to update the account info, then try again." return 1 fi @@ -3659,27 +3904,23 @@ _initAPI _email="$(_getAccountEmail)" - if [ "$ACME_VERSION" = "2" ]; then - if [ "$ACCOUNT_EMAIL" ]; then - updjson='{"contact": ["mailto:'$_email'"]}' - else - updjson='{"contact": []}' - fi + + if [ "$_email" ]; then + updjson='{"contact": ["mailto:'$_email'"]}' else - # ACMEv1: Updates happen the same way a registration is done. - # https://tools.ietf.org/html/draft-ietf-acme-acme-01#section-6.3 - _regAccount - return - fi - - # this part handles ACMEv2 account updates. + updjson='{"contact": []}' + fi + _send_signed_request "$_accUri" "$updjson" if [ "$code" = '200' ]; then echo "$response" >"$ACCOUNT_JSON_PATH" - _info "account update success for $_accUri." + _info "Account update success for $_accUri." + + ACCOUNT_THUMBPRINT="$(__calc_account_thumbprint)" + _info "ACCOUNT_THUMBPRINT" "$ACCOUNT_THUMBPRINT" else - _info "Error. The account was not updated." + _info "An error occurred and the account was not updated." return 1 fi } @@ -3688,18 +3929,8 @@ deactivateaccount() { _initpath - if [ ! -f "$ACCOUNT_KEY_PATH" ] && [ -f "$_OLD_ACCOUNT_KEY" ]; then - _info "mv $_OLD_ACCOUNT_KEY to $ACCOUNT_KEY_PATH" - mv "$_OLD_ACCOUNT_KEY" "$ACCOUNT_KEY_PATH" - fi - - if [ ! -f "$ACCOUNT_JSON_PATH" ] && [ -f "$_OLD_ACCOUNT_JSON" ]; then - _info "mv $_OLD_ACCOUNT_JSON to $ACCOUNT_JSON_PATH" - mv "$_OLD_ACCOUNT_JSON" "$ACCOUNT_JSON_PATH" - fi - if [ ! -f "$ACCOUNT_KEY_PATH" ]; then - _err "Account key is not found at: $ACCOUNT_KEY_PATH" + _err "Account key not found at: $ACCOUNT_KEY_PATH" return 1 fi @@ -3707,8 +3938,7 @@ _debug _accUri "$_accUri" if [ -z "$_accUri" ]; then - _err "The account url is empty, please run '--update-account' first to update the account info first," - _err "Then try again." + _err "The account URL is empty, please run '--update-account' first to update the account info, then try again." return 1 fi @@ -3717,19 +3947,16 @@ fi _initAPI - if [ "$ACME_VERSION" = "2" ]; then - _djson="{\"status\":\"deactivated\"}" - else - _djson="{\"resource\": \"reg\", \"status\":\"deactivated\"}" - fi + _djson="{\"status\":\"deactivated\"}" + if _send_signed_request "$_accUri" "$_djson" && _contains "$response" '"deactivated"'; then - _info "Deactivate account success for $_accUri." + _info "Successfully deactivated account $_accUri." _accid=$(echo "$response" | _egrep_o "\"id\" *: *[^,]*," | cut -d : -f 2 | tr -d ' ,') elif [ "$code" = "403" ]; then _info "The account is already deactivated." _accid=$(_getfield "$_accUri" "999" "/") else - _err "Deactivate: account failed for $_accUri." + _err "Account deactivation failed for $_accUri." return 1 fi @@ -3743,7 +3970,7 @@ mv "$ACCOUNT_JSON_PATH" "$_deactivated_account_path/" mv "$ACCOUNT_KEY_PATH" "$_deactivated_account_path/" else - _err "Can not create dir: $_deactivated_account_path, try to remove the deactivated account key." + _err "Cannot create dir: $_deactivated_account_path, try to remove the deactivated account key." rm -f "$CA_CONF" rm -f "$ACCOUNT_JSON_PATH" rm -f "$ACCOUNT_KEY_PATH" @@ -3786,28 +4013,28 @@ _Max_new_authz_retry_times=5 _authz_i=0 while [ "$_authz_i" -lt "$_Max_new_authz_retry_times" ]; do - _debug "Try new-authz for the $_authz_i time." + _debug "Trying new-authz, attempt number $_authz_i." if ! _send_signed_request "${ACME_NEW_AUTHZ}" "{\"resource\": \"new-authz\", \"identifier\": {\"type\": \"dns\", \"value\": \"$(_idn "$_gdnd")\"}}"; then - _err "Can not get domain new authz." + _err "Cannot get new authz for domain." return 1 fi if _contains "$response" "No registration exists matching provided key"; then - _err "It seems there is an error, but it's recovered now, please try again." - _err "If you see this message for a second time, please report bug: $(__green "$PROJECT")" + _err "There has been an error, but it might now be resolved, please try again." + _err "If you see this message for a second time, please report this as a bug: $(__green "$PROJECT")" _clearcaconf "CA_KEY_HASH" break fi if ! _contains "$response" "An error occurred while processing your request"; then - _info "The new-authz request is ok." + _info "new-authz request successful." break fi _authz_i="$(_math "$_authz_i" + 1)" - _info "The server is busy, Sleep $_authz_i to retry." + _info "The server is busy, sleeping for $_authz_i seconds and retrying." _sleep "$_authz_i" done if [ "$_authz_i" = "$_Max_new_authz_retry_times" ]; then - _err "new-authz retry reach the max $_Max_new_authz_retry_times times." + _err "new-authz has been retried $_Max_new_authz_retry_times times, stopping." fi if [ "$code" ] && [ "$code" != '201' ]; then @@ -3826,11 +4053,9 @@ _debug2 _t_key_authz "$_t_key_authz" _t_vtype="$3" _debug2 _t_vtype "$_t_vtype" - if [ "$ACME_VERSION" = "2" ]; then - _send_signed_request "$_t_url" "{}" - else - _send_signed_request "$_t_url" "{\"resource\": \"challenge\", \"type\": \"$_t_vtype\", \"keyAuthorization\": \"$_t_key_authz\"}" - fi + + _send_signed_request "$_t_url" "{}" + } #endpoint domain type @@ -3865,7 +4090,7 @@ _ns_purge_cf() { _cf_d="$1" _cf_d_type="$2" - _debug "Cloudflare purge $_cf_d_type record for domain $_cf_d" + _debug "Purging Cloudflare $_cf_d_type record for domain $_cf_d" _cf_purl="https://cloudflare-dns.com/api/v1/purge?domain=$_cf_d&type=$_cf_d_type" response="$(_post "" "$_cf_purl")" _debug2 response "$response" @@ -3873,7 +4098,15 @@ #checks if cf server is available _ns_is_available_cf() { - if _get "https://cloudflare-dns.com" >/dev/null 2>&1; then + if _get "https://cloudflare-dns.com" "" 10 >/dev/null; then + return 0 + else + return 1 + fi +} + +_ns_is_available_google() { + if _get "https://dns.google" "" 10 >/dev/null; then return 0 else return 1 @@ -3888,23 +4121,72 @@ _ns_lookup_impl "$_cf_ep" "$_cf_ld" "$_cf_ld_type" } +_ns_is_available_ali() { + if _get "https://dns.alidns.com" "" 10 >/dev/null; then + return 0 + else + return 1 + fi +} + +#domain, type +_ns_lookup_ali() { + _cf_ld="$1" + _cf_ld_type="$2" + _cf_ep="https://dns.alidns.com/resolve" + _ns_lookup_impl "$_cf_ep" "$_cf_ld" "$_cf_ld_type" +} + +_ns_is_available_dp() { + if _get "https://doh.pub" "" 10 >/dev/null; then + return 0 + else + return 1 + fi +} + +#dnspod +_ns_lookup_dp() { + _cf_ld="$1" + _cf_ld_type="$2" + _cf_ep="https://doh.pub/dns-query" + _ns_lookup_impl "$_cf_ep" "$_cf_ld" "$_cf_ld_type" +} + +_ns_select_doh() { + if [ -z "$DOH_USE" ]; then + _debug "Detecting DNS server first." + if _ns_is_available_cf; then + _debug "Using Cloudflare doh server" + export DOH_USE=$DOH_CLOUDFLARE + elif _ns_is_available_google; then + _debug "Using Google DOH server" + export DOH_USE=$DOH_GOOGLE + elif _ns_is_available_ali; then + _debug "Using Aliyun DOH server" + export DOH_USE=$DOH_ALI + elif _ns_is_available_dp; then + _debug "Using DNS POD DOH server" + export DOH_USE=$DOH_DP + else + _err "No DOH" + fi + fi +} + #domain, type _ns_lookup() { - if [ -z "$DOH_USE" ]; then - _debug "Detect dns server first." - if _ns_is_available_cf; then - _debug "Use cloudflare doh server" - export DOH_USE=$DOH_CLOUDFLARE - else - _debug "Use google doh server" - export DOH_USE=$DOH_GOOGLE - fi - fi - + _ns_select_doh if [ "$DOH_USE" = "$DOH_CLOUDFLARE" ] || [ -z "$DOH_USE" ]; then _ns_lookup_cf "$@" + elif [ "$DOH_USE" = "$DOH_GOOGLE" ]; then + _ns_lookup_google "$@" + elif [ "$DOH_USE" = "$DOH_ALI" ]; then + _ns_lookup_ali "$@" + elif [ "$DOH_USE" = "$DOH_DP" ]; then + _ns_lookup_dp "$@" else - _ns_lookup_google "$@" + _err "Unknown DOH provider: DOH_USE=$DOH_USE" fi } @@ -3917,6 +4199,7 @@ _debug "_c_txtdomain" "$_c_txtdomain" _debug "_c_aliasdomain" "$_c_aliasdomain" _debug "_c_txt" "$_c_txt" + _ns_select_doh _answers="$(_ns_lookup "$_c_aliasdomain" TXT)" _contains "$_answers" "$_c_txt" @@ -3929,7 +4212,7 @@ if [ "$DOH_USE" = "$DOH_CLOUDFLARE" ] || [ -z "$DOH_USE" ]; then _ns_purge_cf "$_p_txtdomain" "TXT" else - _debug "no purge api for google dns api, just sleep 5 secs" + _debug "No purge API for this DOH API, just sleeping for 5 seconds" _sleep 5 fi @@ -3942,6 +4225,8 @@ _end_time="$(_math "$_end_time" + 1200)" #let's check no more than 20 minutes. while [ "$(_time)" -le "$_end_time" ]; do + _info "You can use '--dnssleep' to disable public dns checks." + _info "See: $_DNSCHECK_WIKI" _left="" for entry in $dns_entries; do d=$(_getfield "$entry" 1) @@ -3958,17 +4243,17 @@ _debug "d_api" "$d_api" _info "Checking $d for $aliasDomain" if _contains "$_success_txt" ",$txt,"; then - _info "Already success, continue next one." + _info "Already succeeded, continuing." continue fi if __check_txt "$txtdomain" "$aliasDomain" "$txt"; then - _info "Domain $d '$aliasDomain' success." + _info "Success for domain $d '$aliasDomain'." _success_txt="$_success_txt,$txt," continue fi _left=1 - _info "Not valid yet, let's wait 10 seconds and check next one." + _info "Not valid yet, let's wait for 10 seconds then check the next one." __purge_txt "$txtdomain" if [ "$txtdomain" != "$aliasDomain" ]; then __purge_txt "$aliasDomain" @@ -3976,10 +4261,10 @@ _sleep 10 done if [ "$_left" ]; then - _info "Let's wait 10 seconds and check again". + _info "Let's wait for 10 seconds and check again". _sleep 10 else - _info "All success, let's return" + _info "All checks succeeded" return 0 fi done @@ -3989,12 +4274,42 @@ } #file -_get_cert_issuers() { +_get_chain_issuers() { _cfile="$1" - if _contains "$(${ACME_OPENSSL_BIN:-openssl} help crl2pkcs7 2>&1)" "Usage: crl2pkcs7"; then - ${ACME_OPENSSL_BIN:-openssl} crl2pkcs7 -nocrl -certfile $_cfile | ${ACME_OPENSSL_BIN:-openssl} pkcs7 -print_certs -text -noout | grep 'Issuer:' | _egrep_o "CN *=[^,]*" | cut -d = -f 2 + if _contains "$(${ACME_OPENSSL_BIN:-openssl} help crl2pkcs7 2>&1)" "Usage: crl2pkcs7" || _contains "$(${ACME_OPENSSL_BIN:-openssl} crl2pkcs7 -help 2>&1)" "Usage: crl2pkcs7" || _contains "$(${ACME_OPENSSL_BIN:-openssl} crl2pkcs7 help 2>&1)" "unknown option help"; then + ${ACME_OPENSSL_BIN:-openssl} crl2pkcs7 -nocrl -certfile $_cfile | ${ACME_OPENSSL_BIN:-openssl} pkcs7 -print_certs -text -noout | grep -i 'Issuer:' | _egrep_o "CN *=[^,]*" | cut -d = -f 2 else - ${ACME_OPENSSL_BIN:-openssl} x509 -in $_cfile -text -noout | grep 'Issuer:' | _egrep_o "CN *=[^,]*" | cut -d = -f 2 + _cindex=1 + for _startn in $(grep -n -- "$BEGIN_CERT" "$_cfile" | cut -d : -f 1); do + _endn="$(grep -n -- "$END_CERT" "$_cfile" | cut -d : -f 1 | _head_n $_cindex | _tail_n 1)" + _debug2 "_startn" "$_startn" + _debug2 "_endn" "$_endn" + if [ "$DEBUG" ]; then + _debug2 "cert$_cindex" "$(sed -n "$_startn,${_endn}p" "$_cfile")" + fi + sed -n "$_startn,${_endn}p" "$_cfile" | ${ACME_OPENSSL_BIN:-openssl} x509 -text -noout | grep 'Issuer:' | _egrep_o "CN *=[^,]*" | cut -d = -f 2 | sed "s/ *\(.*\)/\1/" + _cindex=$(_math $_cindex + 1) + done + fi +} + +# +_get_chain_subjects() { + _cfile="$1" + if _contains "$(${ACME_OPENSSL_BIN:-openssl} help crl2pkcs7 2>&1)" "Usage: crl2pkcs7" || _contains "$(${ACME_OPENSSL_BIN:-openssl} crl2pkcs7 -help 2>&1)" "Usage: crl2pkcs7" || _contains "$(${ACME_OPENSSL_BIN:-openssl} crl2pkcs7 help 2>&1)" "unknown option help"; then + ${ACME_OPENSSL_BIN:-openssl} crl2pkcs7 -nocrl -certfile $_cfile | ${ACME_OPENSSL_BIN:-openssl} pkcs7 -print_certs -text -noout | grep -i 'Subject:' | _egrep_o "CN *=[^,]*" | cut -d = -f 2 + else + _cindex=1 + for _startn in $(grep -n -- "$BEGIN_CERT" "$_cfile" | cut -d : -f 1); do + _endn="$(grep -n -- "$END_CERT" "$_cfile" | cut -d : -f 1 | _head_n $_cindex | _tail_n 1)" + _debug2 "_startn" "$_startn" + _debug2 "_endn" "$_endn" + if [ "$DEBUG" ]; then + _debug2 "cert$_cindex" "$(sed -n "$_startn,${_endn}p" "$_cfile")" + fi + sed -n "$_startn,${_endn}p" "$_cfile" | ${ACME_OPENSSL_BIN:-openssl} x509 -text -noout | grep -i 'Subject:' | _egrep_o "CN *=[^,]*" | cut -d = -f 2 | sed "s/ *\(.*\)/\1/" + _cindex=$(_math $_cindex + 1) + done fi } @@ -4002,14 +4317,81 @@ _match_issuer() { _cfile="$1" _missuer="$2" - _fissuers="$(_get_cert_issuers $_cfile)" + _fissuers="$(_get_chain_issuers $_cfile)" _debug2 _fissuers "$_fissuers" - if _contains "$_fissuers" "$_missuer"; then - return 0 - fi - _fissuers="$(echo "$_fissuers" | _lower_case)" + _rootissuer="$(echo "$_fissuers" | _lower_case | _tail_n 1)" + _debug2 _rootissuer "$_rootissuer" _missuer="$(echo "$_missuer" | _lower_case)" - _contains "$_fissuers" "$_missuer" + _contains "$_rootissuer" "$_missuer" +} + +#ip +_isIPv4() { + for seg in $(echo "$1" | tr '.' ' '); do + _debug2 seg "$seg" + if [ "$(echo "$seg" | tr -d '[0-9]')" ]; then + #not all number + return 1 + fi + if [ $seg -ge 0 ] && [ $seg -lt 256 ]; then + continue + fi + return 1 + done + return 0 +} + +#ip6 +_isIPv6() { + _contains "$1" ":" +} + +#ip +_isIP() { + _isIPv4 "$1" || _isIPv6 "$1" +} + +#identifier +_getIdType() { + if _isIP "$1"; then + echo "$ID_TYPE_IP" + else + echo "$ID_TYPE_DNS" + fi +} + +# beginTime dateTo +# beginTime is full string format("2022-04-01T08:10:33Z"), beginTime can be empty, to use current time +# dateTo can be ether in full string format("2022-04-01T08:10:33Z") or in delta format(+5d or +20h) +_convertValidaty() { + _beginTime="$1" + _dateTo="$2" + _debug2 "_beginTime" "$_beginTime" + _debug2 "_dateTo" "$_dateTo" + + if _startswith "$_dateTo" "+"; then + _v_begin=$(_time) + if [ "$_beginTime" ]; then + _v_begin="$(_date2time "$_beginTime")" + fi + _debug2 "_v_begin" "$_v_begin" + if _endswith "$_dateTo" "h"; then + _v_end=$(_math "$_v_begin + 60 * 60 * $(echo "$_dateTo" | tr -d '+h')") + elif _endswith "$_dateTo" "d"; then + _v_end=$(_math "$_v_begin + 60 * 60 * 24 * $(echo "$_dateTo" | tr -d '+d')") + else + _err "Unrecognized format for _dateTo: $_dateTo" + return 1 + fi + _debug2 "_v_end" "$_v_end" + _time2str "$_v_end" + else + if [ "$(_time)" -gt "$(_date2time "$_dateTo")" ]; then + _err "The validity end date is in the past: _dateTo = $_dateTo" + return 1 + fi + echo "$_dateTo" + fi } #webroot, domain domainlist keylength @@ -4045,10 +4427,16 @@ _local_addr="${13}" _challenge_alias="${14}" _preferred_chain="${15}" + _valid_from="${16}" + _valid_to="${17}" if [ -z "$_ACME_IS_RENEW" ]; then _initpath "$_main_domain" "$_key_length" mkdir -p "$DOMAIN_PATH" + elif ! _hasfield "$_web_roots" "$W_DNS"; then + Le_OrderFinalize="" + Le_LinkOrder="" + Le_LinkCert="" fi if _hasfield "$_web_roots" "$W_DNS" && [ -z "$FORCE_DNS_MANUAL" ]; then @@ -4056,22 +4444,31 @@ return 1 fi - _debug "Using ACME_DIRECTORY: $ACME_DIRECTORY" - - _initAPI - if [ -f "$DOMAIN_CONF" ]; then Le_NextRenewTime=$(_readdomainconf Le_NextRenewTime) _debug Le_NextRenewTime "$Le_NextRenewTime" if [ -z "$FORCE" ] && [ "$Le_NextRenewTime" ] && [ "$(_time)" -lt "$Le_NextRenewTime" ]; then + _valid_to_saved=$(_readdomainconf Le_Valid_to) + if [ "$_valid_to_saved" ] && ! _startswith "$_valid_to_saved" "+"; then + _info "The domain is set to be valid to: $_valid_to_saved" + _info "It cannot be renewed automatically" + _info "See: $_VALIDITY_WIKI" + return $RENEW_SKIP + fi _saved_domain=$(_readdomainconf Le_Domain) _debug _saved_domain "$_saved_domain" _saved_alt=$(_readdomainconf Le_Alt) _debug _saved_alt "$_saved_alt" - if [ "$_saved_domain,$_saved_alt" = "$_main_domain,$_alt_domains" ]; then + _normized_saved_domains="$(echo "$_saved_domain,$_saved_alt" | tr "," "\n" | sort | tr '\n' ',')" + _debug _normized_saved_domains "$_normized_saved_domains" + + _normized_domains="$(echo "$_main_domain,$_alt_domains" | tr "," "\n" | sort | tr '\n' ',')" + _debug _normized_domains "$_normized_domains" + + if [ "$_normized_saved_domains" = "$_normized_domains" ]; then _info "Domains not changed." - _info "Skip, Next renewal time is: $(__green "$(_readdomainconf Le_NextRenewTimeStr)")" - _info "Add '$(__red '--force')' to force to renew." + _info "Skipping. Next renewal time is: $(__green "$(_readdomainconf Le_NextRenewTimeStr)")" + _info "Add '$(__red '--force')' to force renewal." return $RENEW_SKIP else _info "Domains have changed." @@ -4079,6 +4476,11 @@ fi fi + _debug "Using ACME_DIRECTORY: $ACME_DIRECTORY" + if ! _initAPI; then + return 1 + fi + _savedomainconf "Le_Domain" "$_main_domain" _savedomainconf "Le_Alt" "$_alt_domains" _savedomainconf "Le_Webroot" "$_web_roots" @@ -4111,12 +4513,9 @@ _alt_domains="" fi - if [ "$_key_length" = "$NO_VALUE" ]; then - _key_length="" - fi - if ! _on_before_issue "$_web_roots" "$_main_domain" "$_alt_domains" "$_pre_hook" "$_local_addr"; then _err "_on_before_issue." + _on_issue_err "$_post_hook" return 1 fi @@ -4129,25 +4528,48 @@ return 1 fi else - _debug "_saved_account_key_hash is not changed, skip register account." - fi - + _debug "_saved_account_key_hash was not changed, skipping account registration." + fi + + export Le_Next_Domain_Key="$CERT_KEY_PATH.next" if [ -f "$CSR_PATH" ] && [ ! -f "$CERT_KEY_PATH" ]; then _info "Signing from existing CSR." else + # When renewing from an old version, the empty Le_Keylength means 2048. + # Note, do not use DEFAULT_DOMAIN_KEY_LENGTH as that value may change over + # time but an empty value implies 2048 specifically. _key=$(_readdomainconf Le_Keylength) - _debug "Read key length:$_key" + if [ -z "$_key" ]; then + _key=2048 + fi + _debug "Read key length: $_key" if [ ! -f "$CERT_KEY_PATH" ] || [ "$_key_length" != "$_key" ] || [ "$Le_ForceNewDomainKey" = "1" ]; then - if ! createDomainKey "$_main_domain" "$_key_length"; then - _err "Create domain key error." - _clearup - _on_issue_err "$_post_hook" + if [ "$Le_ForceNewDomainKey" = "1" ] && [ -f "$Le_Next_Domain_Key" ]; then + _info "Using pre-generated key: $Le_Next_Domain_Key" + cat "$Le_Next_Domain_Key" >"$CERT_KEY_PATH" + echo "" >"$Le_Next_Domain_Key" + else + if ! createDomainKey "$_main_domain" "$_key_length"; then + _err "Error creating domain key." + _clearup + _on_issue_err "$_post_hook" + return 1 + fi + fi + fi + if [ "$Le_ForceNewDomainKey" ]; then + _info "Generating next pre-generate key." + if [ ! -e "$Le_Next_Domain_Key" ]; then + touch "$Le_Next_Domain_Key" + chmod 600 "$Le_Next_Domain_Key" + fi + if ! _createkey "$_key_length" "$Le_Next_Domain_Key"; then + _err "Cannot pre-generate domain key" return 1 fi fi - if ! _createcsr "$_main_domain" "$_alt_domains" "$CERT_KEY_PATH" "$CSR_PATH" "$DOMAIN_SSL_CONF"; then - _err "Create CSR error." + _err "Error creating CSR." _clearup _on_issue_err "$_post_hook" return 1 @@ -4158,78 +4580,137 @@ vlist="$Le_Vlist" _cleardomainconf "Le_Vlist" - _info "Getting domain auth token for each domain" + _debug "Getting domain auth token for each domain" sep='#' dvsep=',' if [ -z "$vlist" ]; then - if [ "$ACME_VERSION" = "2" ]; then - #make new order request - _identifiers="{\"type\":\"dns\",\"value\":\"$(_idn "$_main_domain")\"}" - _w_index=1 - while true; do - d="$(echo "$_alt_domains," | cut -d , -f "$_w_index")" - _w_index="$(_math "$_w_index" + 1)" - _debug d "$d" - if [ -z "$d" ]; then - break - fi - _identifiers="$_identifiers,{\"type\":\"dns\",\"value\":\"$(_idn "$d")\"}" - done - _debug2 _identifiers "$_identifiers" - if ! _send_signed_request "$ACME_NEW_ORDER" "{\"identifiers\": [$_identifiers]}"; then - _err "Create new order error." - _clearup - _on_issue_err "$_post_hook" + #make new order request + _identifiers="{\"type\":\"$(_getIdType "$_main_domain")\",\"value\":\"$(_idn "$_main_domain")\"}" + _w_index=1 + while true; do + d="$(echo "$_alt_domains," | cut -d , -f "$_w_index")" + _w_index="$(_math "$_w_index" + 1)" + _debug d "$d" + if [ -z "$d" ]; then + break + fi + _identifiers="$_identifiers,{\"type\":\"$(_getIdType "$d")\",\"value\":\"$(_idn "$d")\"}" + done + _debug2 _identifiers "$_identifiers" + _notBefore="" + _notAfter="" + + if [ "$_valid_from" ]; then + _savedomainconf "Le_Valid_From" "$_valid_from" + _debug2 "_valid_from" "$_valid_from" + _notBefore="$(_convertValidaty "" "$_valid_from")" + if [ "$?" != "0" ]; then + _err "Cannot parse _valid_from: $_valid_from" return 1 fi - Le_LinkOrder="$(echo "$responseHeaders" | grep -i '^Location.*$' | _tail_n 1 | tr -d "\r\n " | cut -d ":" -f 2-)" - _debug Le_LinkOrder "$Le_LinkOrder" - Le_OrderFinalize="$(echo "$response" | _egrep_o '"finalize" *: *"[^"]*"' | cut -d '"' -f 4)" - _debug Le_OrderFinalize "$Le_OrderFinalize" - if [ -z "$Le_OrderFinalize" ]; then - _err "Create new order error. Le_OrderFinalize not found. $response" + if [ "$(_time)" -gt "$(_date2time "$_notBefore")" ]; then + _notBefore="" + fi + else + _cleardomainconf "Le_Valid_From" + fi + _debug2 _notBefore "$_notBefore" + + if [ "$_valid_to" ]; then + _debug2 "_valid_to" "$_valid_to" + _savedomainconf "Le_Valid_To" "$_valid_to" + _notAfter="$(_convertValidaty "$_notBefore" "$_valid_to")" + if [ "$?" != "0" ]; then + _err "Cannot parse _valid_to: $_valid_to" + return 1 + fi + else + _cleardomainconf "Le_Valid_To" + fi + _debug2 "_notAfter" "$_notAfter" + + _newOrderObj="{\"identifiers\": [$_identifiers]" + if [ "$_notBefore" ]; then + _newOrderObj="$_newOrderObj,\"notBefore\": \"$_notBefore\"" + fi + if [ "$_notAfter" ]; then + _newOrderObj="$_newOrderObj,\"notAfter\": \"$_notAfter\"" + fi + _debug "STEP 1, Ordering a Certificate" + if ! _send_signed_request "$ACME_NEW_ORDER" "$_newOrderObj}"; then + _err "Error creating new order." + _clearup + _on_issue_err "$_post_hook" + return 1 + fi + if _contains "$response" "invalid"; then + if echo "$response" | _normalizeJson | grep '"status":"invalid"' >/dev/null 2>&1; then + _err "Create new order with invalid status." + _err "$response" _clearup _on_issue_err "$_post_hook" return 1 fi - - #for dns manual mode - _savedomainconf "Le_OrderFinalize" "$Le_OrderFinalize" - - _authorizations_seg="$(echo "$response" | _json_decode | _egrep_o '"authorizations" *: *\[[^\[]*\]' | cut -d '[' -f 2 | tr -d ']' | tr -d '"')" - _debug2 _authorizations_seg "$_authorizations_seg" - if [ -z "$_authorizations_seg" ]; then - _err "_authorizations_seg not found." + fi + + Le_LinkOrder="$(echo "$responseHeaders" | grep -i '^Location.*$' | _tail_n 1 | tr -d "\r\n " | cut -d ":" -f 2-)" + _debug Le_LinkOrder "$Le_LinkOrder" + Le_OrderFinalize="$(echo "$response" | _egrep_o '"finalize" *: *"[^"]*"' | cut -d '"' -f 4)" + _debug Le_OrderFinalize "$Le_OrderFinalize" + if [ -z "$Le_OrderFinalize" ]; then + _err "Error creating new order. Le_OrderFinalize not found. $response" + _clearup + _on_issue_err "$_post_hook" + return 1 + fi + + #for dns manual mode + _savedomainconf "Le_OrderFinalize" "$Le_OrderFinalize" + + _authorizations_seg="$(echo "$response" | _json_decode | _egrep_o '"authorizations" *: *\[[^\[]*\]' | cut -d '[' -f 2 | tr -d ']' | tr -d '"')" + _debug2 _authorizations_seg "$_authorizations_seg" + if [ -z "$_authorizations_seg" ]; then + _err "_authorizations_seg not found." + _clearup + _on_issue_err "$_post_hook" + return 1 + fi + + _debug "STEP 2, Get the authorizations of each domain" + #domain and authz map + _authorizations_map="" + for _authz_url in $(echo "$_authorizations_seg" | tr ',' ' '); do + _debug2 "_authz_url" "$_authz_url" + if ! _send_signed_request "$_authz_url"; then + _err "Error getting authz." + _err "_authorizations_seg" "$_authorizations_seg" + _err "_authz_url" "$_authz_url" + _err "$response" _clearup _on_issue_err "$_post_hook" return 1 fi - #domain and authz map - _authorizations_map="" - for _authz_url in $(echo "$_authorizations_seg" | tr ',' ' '); do - _debug2 "_authz_url" "$_authz_url" - if ! _send_signed_request "$_authz_url"; then - _err "get to authz error." - _err "_authorizations_seg" "$_authorizations_seg" - _err "_authz_url" "$_authz_url" - _clearup - _on_issue_err "$_post_hook" - return 1 - fi - - response="$(echo "$response" | _normalizeJson)" - _debug2 response "$response" - _d="$(echo "$response" | _egrep_o '"value" *: *"[^"]*"' | cut -d : -f 2 | tr -d ' "')" - if _contains "$response" "\"wildcard\" *: *true"; then - _d="*.$_d" - fi - _debug2 _d "$_d" - _authorizations_map="$_d,$response + response="$(echo "$response" | _normalizeJson)" + _debug2 response "$response" + if echo "$response" | grep '"status":"invalid"' >/dev/null 2>&1; then + _err "get authz objec with invalid status, please try again later." + _err "_authorizations_seg" "$_authorizations_seg" + _err "$response" + _clearup + _on_issue_err "$_post_hook" + return 1 + fi + _d="$(echo "$response" | _egrep_o '"value" *: *"[^"]*"' | cut -d : -f 2- | tr -d ' "')" + if _contains "$response" "\"wildcard\" *: *true"; then + _d="*.$_d" + fi + _debug2 _d "$_d" + _authorizations_map="$_d,$response#$_authz_url $_authorizations_map" - done - _debug2 _authorizations_map "$_authorizations_map" - fi + done + + _debug2 _authorizations_map "$_authorizations_map" _index=0 _currentRoot="" @@ -4260,61 +4741,53 @@ vtype="$VTYPE_ALPN" fi - if [ "$ACME_VERSION" = "2" ]; then - _idn_d="$(_idn "$d")" - _candidates="$(echo "$_authorizations_map" | grep -i "^$_idn_d,")" - _debug2 _candidates "$_candidates" - if [ "$(echo "$_candidates" | wc -l)" -gt 1 ]; then - for _can in $_candidates; do - if _startswith "$(echo "$_can" | tr '.' '|')" "$(echo "$_idn_d" | tr '.' '|'),"; then - _candidates="$_can" - break - fi - done - fi - response="$(echo "$_candidates" | sed "s/$_idn_d,//")" - _debug2 "response" "$response" - if [ -z "$response" ]; then - _err "get to authz error." - _err "_authorizations_map" "$_authorizations_map" - _clearup - _on_issue_err "$_post_hook" - return 1 - fi - else - if ! __get_domain_new_authz "$d"; then - _clearup - _on_issue_err "$_post_hook" - return 1 - fi - fi - + _idn_d="$(_idn "$d")" + _candidates="$(echo "$_authorizations_map" | grep -i "^$_idn_d,")" + _debug2 _candidates "$_candidates" + if [ "$(echo "$_candidates" | wc -l)" -gt 1 ]; then + for _can in $_candidates; do + if _startswith "$(echo "$_can" | tr '.' '|')" "$(echo "$_idn_d" | tr '.' '|'),"; then + _candidates="$_can" + break + fi + done + fi + response="$(echo "$_candidates" | sed "s/$_idn_d,//")" + _debug2 "response" "$response" + if [ -z "$response" ]; then + _err "Error getting authz." + _err "_authorizations_map" "$_authorizations_map" + _clearup + _on_issue_err "$_post_hook" + return 1 + fi + _authz_url="$(echo "$_candidates" | sed "s/$_idn_d,//" | _egrep_o "#.*" | sed "s/^#//")" + _debug _authz_url "$_authz_url" if [ -z "$thumbprint" ]; then thumbprint="$(__calc_account_thumbprint)" fi - entry="$(echo "$response" | _egrep_o '[^\{]*"type":"'$vtype'"[^\}]*')" + keyauthorization="" + + if echo "$response" | grep '"status":"valid"' >/dev/null 2>&1; then + _debug "$d is already valid." + keyauthorization="$STATE_VERIFIED" + _debug keyauthorization "$keyauthorization" + fi + + # Fix for empty error objects in response which mess up the original code, adapted from fix suggested here: https://github.com/acmesh-official/acme.sh/issues/4933#issuecomment-1870499018 + entry="$(echo "$response" | sed s/'"error":{}'/'"error":null'/ | _egrep_o '[^\{]*"type":"'$vtype'"[^\}]*')" _debug entry "$entry" - keyauthorization="" - if [ -z "$entry" ]; then - if ! _startswith "$d" '*.'; then - _debug "Not a wildcard domain, lets check whether the validation is already valid." - if echo "$response" | grep '"status":"valid"' >/dev/null 2>&1; then - _debug "$d is already valid." - keyauthorization="$STATE_VERIFIED" - _debug keyauthorization "$keyauthorization" - fi + + if [ -z "$keyauthorization" -a -z "$entry" ]; then + _err "Cannot get domain token entry $d for $vtype" + _supported_vtypes="$(echo "$response" | _egrep_o "\"challenges\":\[[^]]*]" | tr '{' "\n" | grep type | cut -d '"' -f 4 | tr "\n" ' ')" + if [ "$_supported_vtypes" ]; then + _err "Supported validation types are: $_supported_vtypes, but you specified: $vtype" fi - if [ -z "$keyauthorization" ]; then - _err "Error, can not get domain token entry $d for $vtype" - _supported_vtypes="$(echo "$response" | _egrep_o "\"challenges\":\[[^]]*]" | tr '{' "\n" | grep type | cut -d '"' -f 4 | tr "\n" ' ')" - if [ "$_supported_vtypes" ]; then - _err "The supported validation types are: $_supported_vtypes, but you specified: $vtype" - fi - _clearup - _on_issue_err "$_post_hook" - return 1 - fi + _clearup + _on_issue_err "$_post_hook" + return 1 fi if [ -z "$keyauthorization" ]; then @@ -4322,35 +4795,27 @@ _debug token "$token" if [ -z "$token" ]; then - _err "Error, can not get domain token $entry" + _err "Cannot get domain token $entry" _clearup _on_issue_err "$_post_hook" return 1 fi - if [ "$ACME_VERSION" = "2" ]; then - uri="$(echo "$entry" | _egrep_o '"url":"[^"]*' | cut -d '"' -f 4 | _head_n 1)" - else - uri="$(echo "$entry" | _egrep_o '"uri":"[^"]*' | cut -d '"' -f 4)" - fi + + uri="$(echo "$entry" | _egrep_o '"url":"[^"]*' | cut -d '"' -f 4 | _head_n 1)" + _debug uri "$uri" if [ -z "$uri" ]; then - _err "Error, can not get domain uri. $entry" + _err "Cannot get domain URI $entry" _clearup _on_issue_err "$_post_hook" return 1 fi keyauthorization="$token.$thumbprint" _debug keyauthorization "$keyauthorization" - - if printf "%s" "$response" | grep '"status":"valid"' >/dev/null 2>&1; then - _debug "$d is already verified." - keyauthorization="$STATE_VERIFIED" - _debug keyauthorization "$keyauthorization" - fi - fi - - dvlist="$d$sep$keyauthorization$sep$uri$sep$vtype$sep$_currentRoot" + fi + + dvlist="$d$sep$keyauthorization$sep$uri$sep$vtype$sep$_currentRoot$sep$_authz_url" _debug dvlist "$dvlist" vlist="$vlist$dvlist$dvsep" @@ -4367,9 +4832,10 @@ keyauthorization=$(echo "$ventry" | cut -d "$sep" -f 2) vtype=$(echo "$ventry" | cut -d "$sep" -f 4) _currentRoot=$(echo "$ventry" | cut -d "$sep" -f 5) + _authz_url=$(echo "$ventry" | cut -d "$sep" -f 6) _debug d "$d" if [ "$keyauthorization" = "$STATE_VERIFIED" ]; then - _debug "$d is already verified, skip $vtype." + _debug "$d has already been verified, skipping $vtype." _alias_index="$(_math "$_alias_index" + 1)" continue fi @@ -4381,6 +4847,7 @@ _dns_root_d="$(echo "$_dns_root_d" | sed 's/*.//')" fi _d_alias="$(_getfield "$_challenge_alias" "$_alias_index")" + test "$_d_alias" = "$NO_VALUE" && _d_alias="" _alias_index="$(_math "$_alias_index" + 1)" _debug "_d_alias" "$_d_alias" if [ "$_d_alias" ]; then @@ -4405,37 +4872,37 @@ dns_entry="$dns_entry$dvsep$txt${dvsep}$d_api" _debug2 dns_entry "$dns_entry" if [ "$d_api" ]; then - _debug "Found domain api file: $d_api" + _debug "Found domain API file: $d_api" else if [ "$_currentRoot" != "$W_DNS" ]; then - _err "Can not find dns api hook for: $_currentRoot" - _info "You need to add the txt record manually." + _err "Cannot find DNS API hook for: $_currentRoot" + _info "You need to add the TXT record manually." fi _info "$(__red "Add the following TXT record:")" _info "$(__red "Domain: '$(__green "$txtdomain")'")" _info "$(__red "TXT value: '$(__green "$txt")'")" - _info "$(__red "Please be aware that you prepend _acme-challenge. before your domain")" - _info "$(__red "so the resulting subdomain will be: $txtdomain")" + _info "$(__red "Please make sure to prepend '_acme-challenge.' to your domain")" + _info "$(__red "so that the resulting subdomain is: $txtdomain")" continue fi ( if ! . "$d_api"; then - _err "Load file $d_api error. Please check your api file and try again." + _err "Error loading file $d_api. Please check your API file and try again." return 1 fi addcommand="${_currentRoot}_add" if ! _exists "$addcommand"; then - _err "It seems that your api file is not correct, it must have a function named: $addcommand" + _err "It seems that your API file is incorrect. Make sure it has a function named: $addcommand" return 1 fi - _info "Adding txt value: $txt for domain: $txtdomain" + _info "Adding TXT value: $txt for domain: $txtdomain" if ! $addcommand "$txtdomain" "$txt"; then - _err "Error add txt for domain:$txtdomain" + _err "Error adding TXT record to domain: $txtdomain" return 1 fi - _info "The txt record is added: Success." + _info "The TXT record has been successfully added." ) if [ "$?" != "0" ]; then @@ -4452,34 +4919,36 @@ if [ "$dnsadded" = '0' ]; then _savedomainconf "Le_Vlist" "$vlist" - _debug "Dns record not added yet, so, save to $DOMAIN_CONF and exit." + _debug "DNS record not yet added. Will save to $DOMAIN_CONF and exit." _err "Please add the TXT records to the domains, and re-run with --renew." _on_issue_err "$_post_hook" _clearup - return 1 + # If asked to be in manual DNS mode, flag this exit with a separate + # error so it can be distinguished from other failures. + return $CODE_DNS_MANUAL fi fi if [ "$dns_entries" ]; then if [ -z "$Le_DNSSleep" ]; then - _info "Let's check each DNS record now. Sleep 20 seconds first." + _info "Let's check each DNS record now. Sleeping for 20 seconds first." _sleep 20 if ! _check_dns_entries; then - _err "check dns error." + _err "Error checking DNS." _on_issue_err "$_post_hook" _clearup return 1 fi else _savedomainconf "Le_DNSSleep" "$Le_DNSSleep" - _info "Sleep $(__green $Le_DNSSleep) seconds for the txt records to take effect" + _info "Sleeping for $(__green $Le_DNSSleep) seconds to wait for the the TXT records to take effect" _sleep "$Le_DNSSleep" fi fi NGINX_RESTORE_VLIST="" - _debug "ok, let's start to verify" + _debug "OK, let's start verification" _ncIndex=1 ventries=$(echo "$vlist" | tr "$dvsep" ' ') @@ -4489,9 +4958,9 @@ uri=$(echo "$ventry" | cut -d "$sep" -f 3) vtype=$(echo "$ventry" | cut -d "$sep" -f 4) _currentRoot=$(echo "$ventry" | cut -d "$sep" -f 5) - + _authz_url=$(echo "$ventry" | cut -d "$sep" -f 6) if [ "$keyauthorization" = "$STATE_VERIFIED" ]; then - _info "$d is already verified, skip $vtype." + _info "$d is already verified, skipping $vtype." continue fi @@ -4499,6 +4968,7 @@ _debug "d" "$d" _debug "keyauthorization" "$keyauthorization" _debug "uri" "$uri" + _debug "_authz_url" "$_authz_url" removelevel="" token="$(printf "%s" "$keyauthorization" | cut -d '.' -f 1)" @@ -4518,10 +4988,10 @@ sleep 1 _debug serverproc "$serverproc" elif [ "$_currentRoot" = "$MODE_STATELESS" ]; then - _info "Stateless mode for domain:$d" + _info "Stateless mode for domain: $d" _sleep 1 elif _startswith "$_currentRoot" "$NGINX"; then - _info "Nginx mode for domain:$d" + _info "Nginx mode for domain: $d" #set up nginx server FOUND_REAL_NGINX_CONF="" BACKUP_NGINX_CONF="" @@ -4554,36 +5024,28 @@ _debug wellknown_path "$wellknown_path" - _debug "writing token:$token to $wellknown_path/$token" - - mkdir -p "$wellknown_path" - - if ! printf "%s" "$keyauthorization" >"$wellknown_path/$token"; then - _err "$d:Can not write token to file : $wellknown_path/$token" + _debug "Writing token: $token to $wellknown_path/$token" + + # Ensure .well-known is visible to web server user/group + # https://github.com/Neilpang/acme.sh/pull/32 + if ! (umask ugo+rx && + mkdir -p "$wellknown_path" && + printf "%s" "$keyauthorization" >"$wellknown_path/$token"); then + _err "$d: Cannot write token to file: $wellknown_path/$token" _clearupwebbroot "$_currentRoot" "$removelevel" "$token" _clearup _on_issue_err "$_post_hook" "$vlist" return 1 fi - - if [ ! "$usingApache" ]; then - if webroot_owner=$(_stat "$_currentRoot"); then - _debug "Changing owner/group of .well-known to $webroot_owner" - if ! _exec "chown -R \"$webroot_owner\" \"$_currentRoot/.well-known\""; then - _debug "$(cat "$_EXEC_TEMP_ERR")" - _exec_err >/dev/null 2>&1 - fi - else - _debug "not changing owner/group of webroot" - fi + if ! chmod a+r "$wellknown_path/$token"; then + _debug "chmod failed, will just continue." fi - fi elif [ "$vtype" = "$VTYPE_ALPN" ]; then acmevalidationv1="$(printf "%s" "$keyauthorization" | _digest "sha256" "hex")" _debug acmevalidationv1 "$acmevalidationv1" if ! _starttlsserver "$d" "" "$Le_TLSPort" "$keyauthorization" "$_ncaddr" "$acmevalidationv1"; then - _err "Start tls server error." + _err "Error starting TLS server." _clearupwebbroot "$_currentRoot" "$removelevel" "$token" _clearup _on_issue_err "$_post_hook" "$vlist" @@ -4592,7 +5054,7 @@ fi if ! __trigger_validation "$uri" "$keyauthorization" "$vtype"; then - _err "$d:Can not get challenge: $response" + _err "$d: Cannot get challenge: $response" _clearupwebbroot "$_currentRoot" "$removelevel" "$token" _clearup _on_issue_err "$_post_hook" "$vlist" @@ -4601,9 +5063,9 @@ if [ "$code" ] && [ "$code" != '202' ]; then if [ "$code" = '200' ]; then - _debug "trigger validation code: $code" + _debug "Trigger validation code: $code" else - _err "$d:Challenge error: $response" + _err "$d: Challenge error: $response" _clearupwebbroot "$_currentRoot" "$removelevel" "$token" _clearup _on_issue_err "$_post_hook" "$vlist" @@ -4616,58 +5078,37 @@ MAX_RETRY_TIMES=30 fi + _debug "Let's check the authz status" while true; do waittimes=$(_math "$waittimes" + 1) if [ "$waittimes" -ge "$MAX_RETRY_TIMES" ]; then - _err "$d:Timeout" + _err "$d: Timeout" _clearupwebbroot "$_currentRoot" "$removelevel" "$token" _clearup _on_issue_err "$_post_hook" "$vlist" return 1 fi - _debug "sleep 2 secs to verify" - sleep 2 - _debug "checking" - if [ "$ACME_VERSION" = "2" ]; then - _send_signed_request "$uri" - else - response="$(_get "$uri")" - fi - if [ "$?" != "0" ]; then - _err "$d:Verify error:$response" - _clearupwebbroot "$_currentRoot" "$removelevel" "$token" - _clearup - _on_issue_err "$_post_hook" "$vlist" - return 1 - fi _debug2 original "$response" response="$(echo "$response" | _normalizeJson)" _debug2 response "$response" status=$(echo "$response" | _egrep_o '"status":"[^"]*' | cut -d : -f 2 | tr -d '"') - if [ "$status" = "valid" ]; then - _info "$(__green Success)" - _stopserver "$serverproc" - serverproc="" - _clearupwebbroot "$_currentRoot" "$removelevel" "$token" - break - fi - - if [ "$status" = "invalid" ]; then + _debug2 status "$status" + if _contains "$status" "invalid"; then error="$(echo "$response" | _egrep_o '"error":\{[^\}]*')" _debug2 error "$error" errordetail="$(echo "$error" | _egrep_o '"detail": *"[^"]*' | cut -d '"' -f 4)" _debug2 errordetail "$errordetail" if [ "$errordetail" ]; then - _err "$d:Verify error:$errordetail" + _err "$d: Invalid status. Verification error details: $errordetail" else - _err "$d:Verify error:$error" + _err "$d: Invalid status, Verification error: $error" fi if [ "$DEBUG" ]; then if [ "$vtype" = "$VTYPE_HTTP" ]; then - _debug "Debug: get token url." + _debug "Debug: GET token URL." _get "http://$d/.well-known/acme-challenge/$token" "" 1 fi fi @@ -4677,158 +5118,182 @@ return 1 fi - if [ "$status" = "pending" ]; then - _info "Pending" - elif [ "$status" = "processing" ]; then - _info "Processing" + if _contains "$status" "valid"; then + _info "$(__green Success)" + _stopserver "$serverproc" + serverproc="" + _clearupwebbroot "$_currentRoot" "$removelevel" "$token" + break + fi + + if _contains "$status" "pending"; then + _info "Pending. The CA is processing your order, please wait. ($waittimes/$MAX_RETRY_TIMES)" + elif _contains "$status" "processing"; then + _info "Processing. The CA is processing your order, please wait. ($waittimes/$MAX_RETRY_TIMES)" else - _err "$d:Verify error:$response" + _err "$d: Unknown status: $status. Verification error: $response" _clearupwebbroot "$_currentRoot" "$removelevel" "$token" _clearup _on_issue_err "$_post_hook" "$vlist" return 1 fi - + _debug "Sleep 2 seconds before verifying again" + _sleep 2 + _debug "Checking" + + _send_signed_request "$_authz_url" + + if [ "$?" != "0" ]; then + _err "$d: Invalid code. Verification error: $response" + _clearupwebbroot "$_currentRoot" "$removelevel" "$token" + _clearup + _on_issue_err "$_post_hook" "$vlist" + return 1 + fi + _retryafter=$(echo "$responseHeaders" | grep -i "^Retry-After *: *[0-9]\+ *" | cut -d : -f 2 | tr -d ' ' | tr -d '\r') + _sleep_overload_retry_sec=$_retryafter + if [ "$_sleep_overload_retry_sec" ]; then + if [ $_sleep_overload_retry_sec -le 600 ]; then + _sleep $_sleep_overload_retry_sec + else + _info "The retryafter=$_retryafter value is too large (> 600), will not retry anymore." + _clearupwebbroot "$_currentRoot" "$removelevel" "$token" + _clearup + _on_issue_err "$_post_hook" "$vlist" + return 1 + fi + fi done done _clearup - _info "Verify finished, start to sign." + _info "Verification finished, beginning signing." der="$(_getfile "${CSR_PATH}" "${BEGIN_CSR}" "${END_CSR}" | tr -d "\r\n" | _url_replace)" - if [ "$ACME_VERSION" = "2" ]; then - _info "Lets finalize the order." - _info "Le_OrderFinalize" "$Le_OrderFinalize" - if ! _send_signed_request "${Le_OrderFinalize}" "{\"csr\": \"$der\"}"; then - _err "Sign failed." - _on_issue_err "$_post_hook" - return 1 - fi - if [ "$code" != "200" ]; then - _err "Sign failed, finalize code is not 200." - _err "$response" - _on_issue_err "$_post_hook" - return 1 - fi - if [ -z "$Le_LinkOrder" ]; then - Le_LinkOrder="$(echo "$responseHeaders" | grep -i '^Location.*$' | _tail_n 1 | tr -d "\r\n \t" | cut -d ":" -f 2-)" - fi - - _savedomainconf "Le_LinkOrder" "$Le_LinkOrder" - - _link_cert_retry=0 - _MAX_CERT_RETRY=30 - while [ "$_link_cert_retry" -lt "$_MAX_CERT_RETRY" ]; do - if _contains "$response" "\"status\":\"valid\""; then - _debug "Order status is valid." - Le_LinkCert="$(echo "$response" | _egrep_o '"certificate" *: *"[^"]*"' | cut -d '"' -f 4)" - _debug Le_LinkCert "$Le_LinkCert" - if [ -z "$Le_LinkCert" ]; then - _err "Sign error, can not find Le_LinkCert" - _err "$response" - _on_issue_err "$_post_hook" - return 1 - fi - break - elif _contains "$response" "\"processing\""; then - _info "Order status is processing, lets sleep and retry." - _retryafter=$(echo "$responseHeaders" | grep -i "^Retry-After *:" | cut -d : -f 2 | tr -d ' ' | tr -d '\r') - _debug "_retryafter" "$_retryafter" - if [ "$_retryafter" ]; then - _info "Retry after: $_retryafter" - _sleep $_retryafter - else - _sleep 2 - fi - else - _err "Sign error, wrong status" - _err "$response" - _on_issue_err "$_post_hook" - return 1 - fi - #the order is processing, so we are going to poll order status - if [ -z "$Le_LinkOrder" ]; then - _err "Sign error, can not get order link location header" - _err "responseHeaders" "$responseHeaders" - _on_issue_err "$_post_hook" - return 1 - fi - _info "Polling order status: $Le_LinkOrder" - if ! _send_signed_request "$Le_LinkOrder"; then - _err "Sign failed, can not post to Le_LinkOrder cert:$Le_LinkOrder." + _info "Let's finalize the order." + _info "Le_OrderFinalize" "$Le_OrderFinalize" + if ! _send_signed_request "${Le_OrderFinalize}" "{\"csr\": \"$der\"}"; then + _err "Signing failed." + _on_issue_err "$_post_hook" + return 1 + fi + if [ "$code" != "200" ]; then + _err "Signing failed. Finalize code was not 200." + _err "$response" + _on_issue_err "$_post_hook" + return 1 + fi + if [ -z "$Le_LinkOrder" ]; then + Le_LinkOrder="$(echo "$responseHeaders" | grep -i '^Location.*$' | _tail_n 1 | tr -d "\r\n \t" | cut -d ":" -f 2-)" + fi + + _savedomainconf "Le_LinkOrder" "$Le_LinkOrder" + + _link_cert_retry=0 + _MAX_CERT_RETRY=30 + while [ "$_link_cert_retry" -lt "$_MAX_CERT_RETRY" ]; do + if _contains "$response" "\"status\":\"valid\""; then + _debug "Order status is valid." + Le_LinkCert="$(echo "$response" | _egrep_o '"certificate" *: *"[^"]*"' | cut -d '"' -f 4)" + _debug Le_LinkCert "$Le_LinkCert" + if [ -z "$Le_LinkCert" ]; then + _err "A signing error occurred: could not find Le_LinkCert" _err "$response" _on_issue_err "$_post_hook" return 1 fi - _link_cert_retry="$(_math $_link_cert_retry + 1)" - done - - if [ -z "$Le_LinkCert" ]; then - _err "Sign failed, can not get Le_LinkCert, retry time limit." + break + elif _contains "$response" "\"processing\""; then + _info "Order status is 'processing', let's sleep and retry." + _retryafter=$(echo "$responseHeaders" | grep -i "^Retry-After *:" | cut -d : -f 2 | tr -d ' ' | tr -d '\r') + _debug "_retryafter" "$_retryafter" + if [ "$_retryafter" ]; then + _info "Sleeping for $_retryafter seconds then retrying" + _sleep $_retryafter + else + _sleep 2 + fi + else + _err "Signing error: wrong status" _err "$response" _on_issue_err "$_post_hook" return 1 fi - _info "Downloading cert." - _info "Le_LinkCert" "$Le_LinkCert" - if ! _send_signed_request "$Le_LinkCert"; then - _err "Sign failed, can not download cert:$Le_LinkCert." + #the order is processing, so we are going to poll order status + if [ -z "$Le_LinkOrder" ]; then + _err "Signing error: could not get order link location header" + _err "responseHeaders" "$responseHeaders" + _on_issue_err "$_post_hook" + return 1 + fi + _info "Polling order status: $Le_LinkOrder" + if ! _send_signed_request "$Le_LinkOrder"; then + _err "Signing failed. Could not make POST request to Le_LinkOrder for cert: $Le_LinkOrder." _err "$response" _on_issue_err "$_post_hook" return 1 fi - - echo "$response" >"$CERT_PATH" - _split_cert_chain "$CERT_PATH" "$CERT_FULLCHAIN_PATH" "$CA_CERT_PATH" - - if [ "$_preferred_chain" ] && [ -f "$CERT_FULLCHAIN_PATH" ]; then - if ! _match_issuer "$CERT_FULLCHAIN_PATH" "$_preferred_chain"; then - rels="$(echo "$responseHeaders" | tr -d ' <>' | grep -i "^link:" | grep -i 'rel="alternate"' | cut -d : -f 2- | cut -d ';' -f 1)" - _debug2 "rels" "$rels" - for rel in $rels; do - _info "Try rel: $rel" - if ! _send_signed_request "$rel"; then - _err "Sign failed, can not download cert:$rel" - _err "$response" - continue - fi - _relcert="$CERT_PATH.alt" - _relfullchain="$CERT_FULLCHAIN_PATH.alt" - _relca="$CA_CERT_PATH.alt" - echo "$response" >"$_relcert" - _split_cert_chain "$_relcert" "$_relfullchain" "$_relca" - if _match_issuer "$_relfullchain" "$_preferred_chain"; then - _info "Matched issuer in: $rel" - cat $_relcert >"$CERT_PATH" - cat $_relfullchain >"$CERT_FULLCHAIN_PATH" - cat $_relca >"$CA_CERT_PATH" - break - fi - done - fi - fi - else - if ! _send_signed_request "${ACME_NEW_ORDER}" "{\"resource\": \"$ACME_NEW_ORDER_RES\", \"csr\": \"$der\"}" "needbase64"; then - _err "Sign failed. $response" - _on_issue_err "$_post_hook" - return 1 - fi - _rcert="$response" - Le_LinkCert="$(grep -i '^Location.*$' "$HTTP_HEADER" | _tail_n 1 | tr -d "\r\n" | cut -d " " -f 2)" - echo "$BEGIN_CERT" >"$CERT_PATH" - - #if ! _get "$Le_LinkCert" | _base64 "multiline" >> "$CERT_PATH" ; then - # _debug "Get cert failed. Let's try last response." - # printf -- "%s" "$_rcert" | _dbase64 "multiline" | _base64 "multiline" >> "$CERT_PATH" - #fi - - if ! printf -- "%s" "$_rcert" | _dbase64 "multiline" | _base64 "multiline" >>"$CERT_PATH"; then - _debug "Try cert link." - _get "$Le_LinkCert" | _base64 "multiline" >>"$CERT_PATH" - fi - - echo "$END_CERT" >>"$CERT_PATH" + _link_cert_retry="$(_math $_link_cert_retry + 1)" + done + + if [ -z "$Le_LinkCert" ]; then + _err "Signing failed. Could not get Le_LinkCert, and stopped retrying after reaching the retry limit." + _err "$response" + _on_issue_err "$_post_hook" + return 1 + fi + _info "Downloading cert." + _info "Le_LinkCert" "$Le_LinkCert" + if ! _send_signed_request "$Le_LinkCert"; then + _err "Signing failed. Could not download cert: $Le_LinkCert." + _err "$response" + _on_issue_err "$_post_hook" + return 1 + fi + + echo "$response" >"$CERT_PATH" + _split_cert_chain "$CERT_PATH" "$CERT_FULLCHAIN_PATH" "$CA_CERT_PATH" + if [ -z "$_preferred_chain" ]; then + _preferred_chain=$(_readcaconf DEFAULT_PREFERRED_CHAIN) + fi + if [ "$_preferred_chain" ] && [ -f "$CERT_FULLCHAIN_PATH" ]; then + if [ "$DEBUG" ]; then + _debug "Default chain issuers: " "$(_get_chain_issuers "$CERT_FULLCHAIN_PATH")" + fi + if ! _match_issuer "$CERT_FULLCHAIN_PATH" "$_preferred_chain"; then + rels="$(echo "$responseHeaders" | tr -d ' <>' | grep -i "^link:" | grep -i 'rel="alternate"' | cut -d : -f 2- | cut -d ';' -f 1)" + _debug2 "rels" "$rels" + for rel in $rels; do + _info "Trying rel: $rel" + if ! _send_signed_request "$rel"; then + _err "Signing failed, could not download cert: $rel" + _err "$response" + continue + fi + _relcert="$CERT_PATH.alt" + _relfullchain="$CERT_FULLCHAIN_PATH.alt" + _relca="$CA_CERT_PATH.alt" + echo "$response" >"$_relcert" + _split_cert_chain "$_relcert" "$_relfullchain" "$_relca" + if [ "$DEBUG" ]; then + _debug "rel chain issuers: " "$(_get_chain_issuers "$_relfullchain")" + fi + if _match_issuer "$_relfullchain" "$_preferred_chain"; then + _info "Matched issuer in: $rel" + cat $_relcert >"$CERT_PATH" + cat $_relfullchain >"$CERT_FULLCHAIN_PATH" + cat $_relca >"$CA_CERT_PATH" + rm -f "$_relcert" + rm -f "$_relfullchain" + rm -f "$_relca" + break + fi + rm -f "$_relcert" + rm -f "$_relfullchain" + rm -f "$_relca" + done + fi fi _debug "Le_LinkCert" "$Le_LinkCert" @@ -4836,7 +5301,7 @@ if [ -z "$Le_LinkCert" ] || ! _checkcert "$CERT_PATH"; then response="$(echo "$response" | _dbase64 "multiline" | tr -d '\0' | _normalizeJson)" - _err "Sign failed: $(echo "$response" | _egrep_o '"detail":"[^"]*"')" + _err "Signing failed: $(echo "$response" | _egrep_o '"detail":"[^"]*"')" _on_issue_err "$_post_hook" return 1 fi @@ -4845,10 +5310,10 @@ _info "$(__green "Cert success.")" cat "$CERT_PATH" - _info "Your cert is in $(__green " $CERT_PATH ")" + _info "Your cert is in: $(__green "$CERT_PATH")" if [ -f "$CERT_KEY_PATH" ]; then - _info "Your cert key is in $(__green " $CERT_KEY_PATH ")" + _info "Your cert key is in: $(__green "$CERT_KEY_PATH")" fi if [ ! "$USER_PATH" ] || [ ! "$_ACME_IN_CRON" ]; then @@ -4857,60 +5322,16 @@ fi fi - if [ "$ACME_VERSION" = "2" ]; then - _debug "v2 chain." - else - cp "$CERT_PATH" "$CERT_FULLCHAIN_PATH" - Le_LinkIssuer=$(grep -i '^Link' "$HTTP_HEADER" | _head_n 1 | cut -d " " -f 2 | cut -d ';' -f 1 | tr -d '<>') - - if [ "$Le_LinkIssuer" ]; then - if ! _contains "$Le_LinkIssuer" ":"; then - _info "$(__red "Relative issuer link found.")" - Le_LinkIssuer="$_ACME_SERVER_HOST$Le_LinkIssuer" - fi - _debug Le_LinkIssuer "$Le_LinkIssuer" - _savedomainconf "Le_LinkIssuer" "$Le_LinkIssuer" - - _link_issuer_retry=0 - _MAX_ISSUER_RETRY=5 - while [ "$_link_issuer_retry" -lt "$_MAX_ISSUER_RETRY" ]; do - _debug _link_issuer_retry "$_link_issuer_retry" - if [ "$ACME_VERSION" = "2" ]; then - if _send_signed_request "$Le_LinkIssuer"; then - echo "$response" >"$CA_CERT_PATH" - break - fi - else - if _get "$Le_LinkIssuer" >"$CA_CERT_PATH.der"; then - echo "$BEGIN_CERT" >"$CA_CERT_PATH" - _base64 "multiline" <"$CA_CERT_PATH.der" >>"$CA_CERT_PATH" - echo "$END_CERT" >>"$CA_CERT_PATH" - if ! _checkcert "$CA_CERT_PATH"; then - _err "Can not get the ca cert." - break - fi - cat "$CA_CERT_PATH" >>"$CERT_FULLCHAIN_PATH" - rm -f "$CA_CERT_PATH.der" - break - fi - fi - _link_issuer_retry=$(_math $_link_issuer_retry + 1) - _sleep "$_link_issuer_retry" - done - if [ "$_link_issuer_retry" = "$_MAX_ISSUER_RETRY" ]; then - _err "Max retry for issuer ca cert is reached." - fi - else - _debug "No Le_LinkIssuer header found." - fi - fi - [ -f "$CA_CERT_PATH" ] && _info "The intermediate CA cert is in $(__green " $CA_CERT_PATH ")" - [ -f "$CERT_FULLCHAIN_PATH" ] && _info "And the full chain certs is there: $(__green " $CERT_FULLCHAIN_PATH ")" + [ -f "$CA_CERT_PATH" ] && _info "The intermediate CA cert is in: $(__green "$CA_CERT_PATH")" + [ -f "$CERT_FULLCHAIN_PATH" ] && _info "And the full-chain cert is in: $(__green "$CERT_FULLCHAIN_PATH")" + if [ "$Le_ForceNewDomainKey" ] && [ -e "$Le_Next_Domain_Key" ]; then + _info "Your pre-generated key for future cert key changes is in: $(__green "$Le_Next_Domain_Key")" + fi Le_CertCreateTime=$(_time) _savedomainconf "Le_CertCreateTime" "$Le_CertCreateTime" - Le_CertCreateTimeStr=$(date -u) + Le_CertCreateTimeStr=$(_time2str "$Le_CertCreateTime") _savedomainconf "Le_CertCreateTimeStr" "$Le_CertCreateTimeStr" if [ -z "$Le_RenewalDays" ] || [ "$Le_RenewalDays" -lt "0" ]; then @@ -4950,15 +5371,42 @@ else _cleardomainconf Le_ForceNewDomainKey fi - - Le_NextRenewTime=$(_math "$Le_CertCreateTime" + "$Le_RenewalDays" \* 24 \* 60 \* 60) - - Le_NextRenewTimeStr=$(_time2str "$Le_NextRenewTime") + if [ "$_notAfter" ]; then + Le_NextRenewTime=$(_date2time "$_notAfter") + Le_NextRenewTimeStr="$_notAfter" + if [ "$_valid_to" ] && ! _startswith "$_valid_to" "+"; then + _info "The domain is set to be valid until: $_valid_to" + _info "It cannot be renewed automatically" + _info "See: $_VALIDITY_WIKI" + else + _now=$(_time) + _debug2 "_now" "$_now" + _lifetime=$(_math $Le_NextRenewTime - $_now) + _debug2 "_lifetime" "$_lifetime" + if [ $_lifetime -gt 86400 ]; then + #if lifetime is logner than one day, it will renew one day before + Le_NextRenewTime=$(_math $Le_NextRenewTime - 86400) + Le_NextRenewTimeStr=$(_time2str "$Le_NextRenewTime") + else + #if lifetime is less than 24 hours, it will renew one hour before + Le_NextRenewTime=$(_math $Le_NextRenewTime - 3600) + Le_NextRenewTimeStr=$(_time2str "$Le_NextRenewTime") + fi + fi + else + Le_NextRenewTime=$(_math "$Le_CertCreateTime" + "$Le_RenewalDays" \* 24 \* 60 \* 60) + Le_NextRenewTime=$(_math "$Le_NextRenewTime" - 86400) + Le_NextRenewTimeStr=$(_time2str "$Le_NextRenewTime") + fi _savedomainconf "Le_NextRenewTimeStr" "$Le_NextRenewTimeStr" - - Le_NextRenewTime=$(_math "$Le_NextRenewTime" - 86400) _savedomainconf "Le_NextRenewTime" "$Le_NextRenewTime" + #convert to pkcs12 + if [ "$Le_PFXPassword" ]; then + _toPkcs "$CERT_PFX_PATH" "$CERT_KEY_PATH" "$CERT_PATH" "$CA_CERT_PATH" "$Le_PFXPassword" + fi + export CERT_PFX_PATH + if [ "$_real_cert$_real_key$_real_ca$_reload_cmd$_real_fullchain" ]; then _savedomainconf "Le_RealCertPath" "$_real_cert" _savedomainconf "Le_RealCACertPath" "$_real_ca" @@ -4971,7 +5419,7 @@ fi if ! _on_issue_success "$_post_hook" "$_renew_hook"; then - _err "Call hook error." + _err "Error calling hook." return 1 fi } @@ -4992,21 +5440,24 @@ fi } -#domain [isEcc] +#domain [isEcc] [server] renew() { Le_Domain="$1" if [ -z "$Le_Domain" ]; then - _usage "Usage: $PROJECT_ENTRY --renew --domain <domain.tld> [--ecc]" + _usage "Usage: $PROJECT_ENTRY --renew --domain <domain.tld> [--ecc] [--server server]" return 1 fi _isEcc="$2" + _renewServer="$3" + _debug "_renewServer" "$_renewServer" _initpath "$Le_Domain" "$_isEcc" - _info "$(__green "Renew: '$Le_Domain'")" + _set_level=${NOTIFY_LEVEL:-$NOTIFY_LEVEL_DEFAULT} + _info "$(__green "Renewing: '$Le_Domain'")" if [ ! -f "$DOMAIN_CONF" ]; then - _info "'$Le_Domain' is not an issued domain, skip." + _info "'$Le_Domain' is not an issued domain, skipping." return $RENEW_SKIP fi @@ -5017,33 +5468,43 @@ . "$DOMAIN_CONF" _debug Le_API "$Le_API" - if [ "$Le_API" = "$LETSENCRYPT_CA_V1" ]; then - _cleardomainconf Le_API - Le_API="$DEFAULT_CA" - fi - if [ "$Le_API" = "$LETSENCRYPT_STAGING_CA_V1" ]; then - _cleardomainconf Le_API - Le_API="$DEFAULT_STAGING_CA" - fi - - if [ "$Le_API" ]; then - export ACME_DIRECTORY="$Le_API" - #reload ca configs - ACCOUNT_KEY_PATH="" - ACCOUNT_JSON_PATH="" - CA_CONF="" - _debug3 "initpath again." - _initpath "$Le_Domain" "$_isEcc" - fi + case "$Le_API" in + "$CA_LETSENCRYPT_V2_TEST") + _info "Switching back to $CA_LETSENCRYPT_V2" + Le_API="$CA_LETSENCRYPT_V2" + ;; + "$CA_GOOGLE_TEST") + _info "Switching back to $CA_GOOGLE" + Le_API="$CA_GOOGLE" + ;; + esac + + if [ "$_server" ]; then + Le_API="$_server" + fi + _info "Renewing using Le_API=$Le_API" + + _clearAPI + _clearCA + export ACME_DIRECTORY="$Le_API" + + #reload ca configs + _debug2 "initpath again." + _initpath "$Le_Domain" "$_isEcc" if [ -z "$FORCE" ] && [ "$Le_NextRenewTime" ] && [ "$(_time)" -lt "$Le_NextRenewTime" ]; then - _info "Skip, Next renewal time is: $(__green "$Le_NextRenewTimeStr")" - _info "Add '$(__red '--force')' to force to renew." + _info "Skipping. Next renewal time is: $(__green "$Le_NextRenewTimeStr")" + _info "Add '$(__red '--force')' to force renewal." + if [ -z "$_ACME_IN_RENEWALL" ]; then + if [ $_set_level -ge $NOTIFY_LEVEL_SKIP ]; then + _send_notify "Renew $Le_Domain skipped" "Good, the cert is skipped." "$NOTIFY_HOOK" "$RENEW_SKIP" + fi + fi return "$RENEW_SKIP" fi if [ "$_ACME_IN_CRON" = "1" ] && [ -z "$Le_CertCreateTime" ]; then - _info "Skip invalid cert for: $Le_Domain" + _info "Skipping invalid cert for: $Le_Domain" return $RENEW_SKIP fi @@ -5053,7 +5514,21 @@ Le_PostHook="$(_readdomainconf Le_PostHook)" Le_RenewHook="$(_readdomainconf Le_RenewHook)" Le_Preferred_Chain="$(_readdomainconf Le_Preferred_Chain)" - issue "$Le_Webroot" "$Le_Domain" "$Le_Alt" "$Le_Keylength" "$Le_RealCertPath" "$Le_RealKeyPath" "$Le_RealCACertPath" "$Le_ReloadCmd" "$Le_RealFullChainPath" "$Le_PreHook" "$Le_PostHook" "$Le_RenewHook" "$Le_LocalAddress" "$Le_ChallengeAlias" "$Le_Preferred_Chain" + # When renewing from an old version, the empty Le_Keylength means 2048. + # Note, do not use DEFAULT_DOMAIN_KEY_LENGTH as that value may change over + # time but an empty value implies 2048 specifically. + Le_Keylength="$(_readdomainconf Le_Keylength)" + if [ -z "$Le_Keylength" ]; then + Le_Keylength=2048 + fi + if [ "$CA_LETSENCRYPT_V2" = "$Le_API" ]; then + #letsencrypt doesn't support ocsp anymore + if [ "$Le_OCSP_Staple" ]; then + export Le_OCSP_Staple="" + _cleardomainconf Le_OCSP_Staple + fi + fi + issue "$Le_Webroot" "$Le_Domain" "$Le_Alt" "$Le_Keylength" "$Le_RealCertPath" "$Le_RealKeyPath" "$Le_RealCACertPath" "$Le_ReloadCmd" "$Le_RealFullChainPath" "$Le_PreHook" "$Le_PostHook" "$Le_RenewHook" "$Le_LocalAddress" "$Le_ChallengeAlias" "$Le_Preferred_Chain" "$Le_Valid_From" "$Le_Valid_To" res="$?" if [ "$res" != "0" ]; then return "$res" @@ -5065,15 +5540,31 @@ fi _ACME_IS_RENEW="" + if [ -z "$_ACME_IN_RENEWALL" ]; then + if [ "$res" = "0" ]; then + if [ $_set_level -ge $NOTIFY_LEVEL_RENEW ]; then + _send_notify "Renew $d success" "Good, the cert is renewed." "$NOTIFY_HOOK" 0 + fi + else + if [ $_set_level -ge $NOTIFY_LEVEL_ERROR ]; then + _send_notify "Renew $d error" "There is an error." "$NOTIFY_HOOK" 1 + fi + fi + fi return "$res" } -#renewAll [stopRenewOnError] +#renewAll [stopRenewOnError] [server] renewAll() { _initpath + _clearCA _stopRenewOnError="$1" _debug "_stopRenewOnError" "$_stopRenewOnError" + + _server="$2" + _debug "_server" "$_server" + _ret="0" _success_msg="" _error_msg="" @@ -5082,10 +5573,11 @@ _notify_code=$RENEW_SKIP _set_level=${NOTIFY_LEVEL:-$NOTIFY_LEVEL_DEFAULT} _debug "_set_level" "$_set_level" + export _ACME_IN_RENEWALL=1 for di in "${CERT_HOME}"/*.*/; do _debug di "$di" if ! [ -d "$di" ]; then - _debug "Not a directory, skip: $di" + _debug "Not a directory, skipping: $di" continue fi d=$(basename "$di") @@ -5095,7 +5587,7 @@ _isEcc=$(echo "$d" | cut -d "$ECC_SEP" -f 2) d=$(echo "$d" | cut -d "$ECC_SEP" -f 1) fi - renew "$d" "$_isEcc" + renew "$d" "$_isEcc" "$_server" ) rc="$?" _debug "Return code: $rc" @@ -5104,13 +5596,13 @@ _error_level="$NOTIFY_LEVEL_RENEW" _notify_code=0 fi - if [ "$_ACME_IN_CRON" ]; then - if [ $_set_level -ge $NOTIFY_LEVEL_RENEW ]; then - if [ "$NOTIFY_MODE" = "$NOTIFY_MODE_CERT" ]; then - _send_notify "Renew $d success" "Good, the cert is renewed." "$NOTIFY_HOOK" 0 - fi + + if [ $_set_level -ge $NOTIFY_LEVEL_RENEW ]; then + if [ "$NOTIFY_MODE" = "$NOTIFY_MODE_CERT" ]; then + _send_notify "Renew $d success" "Good, the cert is renewed." "$NOTIFY_HOOK" 0 fi fi + _success_msg="${_success_msg} $d " elif [ "$rc" = "$RENEW_SKIP" ]; then @@ -5118,13 +5610,13 @@ _error_level="$NOTIFY_LEVEL_SKIP" _notify_code=$RENEW_SKIP fi - if [ "$_ACME_IN_CRON" ]; then - if [ $_set_level -ge $NOTIFY_LEVEL_SKIP ]; then - if [ "$NOTIFY_MODE" = "$NOTIFY_MODE_CERT" ]; then - _send_notify "Renew $d skipped" "Good, the cert is skipped." "$NOTIFY_HOOK" "$RENEW_SKIP" - fi + + if [ $_set_level -ge $NOTIFY_LEVEL_SKIP ]; then + if [ "$NOTIFY_MODE" = "$NOTIFY_MODE_CERT" ]; then + _send_notify "Renew $d skipped" "Good, the cert is skipped." "$NOTIFY_HOOK" "$RENEW_SKIP" fi fi + _info "Skipped $d" _skipped_msg="${_skipped_msg} $d " @@ -5133,39 +5625,39 @@ _error_level="$NOTIFY_LEVEL_ERROR" _notify_code=1 fi - if [ "$_ACME_IN_CRON" ]; then - if [ $_set_level -ge $NOTIFY_LEVEL_ERROR ]; then - if [ "$NOTIFY_MODE" = "$NOTIFY_MODE_CERT" ]; then - _send_notify "Renew $d error" "There is an error." "$NOTIFY_HOOK" 1 - fi + + if [ $_set_level -ge $NOTIFY_LEVEL_ERROR ]; then + if [ "$NOTIFY_MODE" = "$NOTIFY_MODE_CERT" ]; then + _send_notify "Renew $d error" "There is an error." "$NOTIFY_HOOK" 1 fi fi + _error_msg="${_error_msg} $d " if [ "$_stopRenewOnError" ]; then - _err "Error renew $d, stop now." + _err "Error renewing $d, stopping." _ret="$rc" break else _ret="$rc" - _err "Error renew $d." + _err "Error renewing $d." fi fi done _debug _error_level "$_error_level" _debug _set_level "$_set_level" - if [ "$_ACME_IN_CRON" ] && [ $_error_level -le $_set_level ]; then + if [ $_error_level -le $_set_level ]; then if [ -z "$NOTIFY_MODE" ] || [ "$NOTIFY_MODE" = "$NOTIFY_MODE_BULK" ]; then _msg_subject="Renew" if [ "$_error_msg" ]; then _msg_subject="${_msg_subject} Error" - _msg_data="Error certs: + _msg_data="Errored certs: ${_error_msg} " fi if [ "$_success_msg" ]; then _msg_subject="${_msg_subject} Success" - _msg_data="${_msg_data}Success certs: + _msg_data="${_msg_data}Successful certs: ${_success_msg} " fi @@ -5202,21 +5694,22 @@ _renew_hook="${10}" _local_addr="${11}" _challenge_alias="${12}" + _preferred_chain="${13}" _csrsubj=$(_readSubjectFromCSR "$_csrfile") if [ "$?" != "0" ]; then - _err "Can not read subject from csr: $_csrfile" + _err "Cannot read subject from CSR: $_csrfile" return 1 fi _debug _csrsubj "$_csrsubj" if _contains "$_csrsubj" ' ' || ! _contains "$_csrsubj" '.'; then - _info "It seems that the subject: $_csrsubj is not a valid domain name. Drop it." + _info "It seems that the subject $_csrsubj is not a valid domain name. Dropping it." _csrsubj="" fi _csrdomainlist=$(_readSubjectAltNamesFromCSR "$_csrfile") if [ "$?" != "0" ]; then - _err "Can not read domain list from csr: $_csrfile" + _err "Cannot read domain list from CSR: $_csrfile" return 1 fi _debug "_csrdomainlist" "$_csrdomainlist" @@ -5229,26 +5722,23 @@ fi if [ -z "$_csrsubj" ]; then - _err "Can not read subject from csr: $_csrfile" + _err "Cannot read subject from CSR: $_csrfile" return 1 fi _csrkeylength=$(_readKeyLengthFromCSR "$_csrfile") if [ "$?" != "0" ] || [ -z "$_csrkeylength" ]; then - _err "Can not read key length from csr: $_csrfile" + _err "Cannot read key length from CSR: $_csrfile" return 1 fi - if [ -z "$ACME_VERSION" ] && _contains "$_csrsubj,$_csrdomainlist" "*."; then - export ACME_VERSION=2 - fi _initpath "$_csrsubj" "$_csrkeylength" mkdir -p "$DOMAIN_PATH" - _info "Copy csr to: $CSR_PATH" + _info "Copying CSR to: $CSR_PATH" cp "$_csrfile" "$CSR_PATH" - issue "$_csrW" "$_csrsubj" "$_csrdomainlist" "$_csrkeylength" "$_real_cert" "$_real_key" "$_real_ca" "$_reload_cmd" "$_real_fullchain" "$_pre_hook" "$_post_hook" "$_renew_hook" "$_local_addr" "$_challenge_alias" + issue "$_csrW" "$_csrsubj" "$_csrdomainlist" "$_csrkeylength" "$_real_cert" "$_real_key" "$_real_ca" "$_reload_cmd" "$_real_fullchain" "$_pre_hook" "$_post_hook" "$_renew_hook" "$_local_addr" "$_challenge_alias" "$_preferred_chain" } @@ -5263,16 +5753,19 @@ _initpath _csrsubj=$(_readSubjectFromCSR "$_csrfile") - if [ "$?" != "0" ] || [ -z "$_csrsubj" ]; then - _err "Can not read subject from csr: $_csrfile" + if [ "$?" != "0" ]; then + _err "Cannot read subject from CSR: $_csrfile" return 1 fi + if [ -z "$_csrsubj" ]; then + _info "The subject is empty" + fi _info "Subject=$_csrsubj" _csrdomainlist=$(_readSubjectAltNamesFromCSR "$_csrfile") if [ "$?" != "0" ]; then - _err "Can not read domain list from csr: $_csrfile" + _err "Cannot read domain list from CSR: $_csrfile" return 1 fi _debug "_csrdomainlist" "$_csrdomainlist" @@ -5281,7 +5774,7 @@ _csrkeylength=$(_readKeyLengthFromCSR "$_csrfile") if [ "$?" != "0" ] || [ -z "$_csrkeylength" ]; then - _err "Can not read key length from csr: $_csrfile" + _err "Cannot read key length from CSR: $_csrfile" return 1 fi _info "KeyLength=$_csrkeylength" @@ -5337,29 +5830,29 @@ for _d_api in $(echo "$_hooks" | tr ',' " "); do _deployApi="$(_findHook "$_d" $_SUB_FOLDER_DEPLOY "$_d_api")" if [ -z "$_deployApi" ]; then - _err "The deploy hook $_d_api is not found." + _err "The deploy hook $_d_api was not found." return 1 fi _debug _deployApi "$_deployApi" if ! ( if ! . "$_deployApi"; then - _err "Load file $_deployApi error. Please check your api file and try again." + _err "Error loading file $_deployApi. Please check your API file and try again." return 1 fi d_command="${_d_api}_deploy" if ! _exists "$d_command"; then - _err "It seems that your api file is not correct, it must have a function named: $d_command" + _err "It seems that your API file is not correct. Make sure it has a function named: $d_command" return 1 fi - if ! $d_command "$_d" "$CERT_KEY_PATH" "$CERT_PATH" "$CA_CERT_PATH" "$CERT_FULLCHAIN_PATH"; then - _err "Error deploy for domain:$_d" + if ! $d_command "$_d" "$CERT_KEY_PATH" "$CERT_PATH" "$CA_CERT_PATH" "$CERT_FULLCHAIN_PATH" "$CERT_PFX_PATH"; then + _err "Error deploying for domain: $_d" return 1 fi ); then - _err "Deploy error." + _err "Error encountered while deploying." return 1 else _info "$(__green Success)" @@ -5380,10 +5873,11 @@ _initpath "$_d" "$_isEcc" if [ ! -d "$DOMAIN_PATH" ]; then _err "The domain '$_d' is not a cert name. You must use the cert name to specify the cert to install." - _err "Can not find path:'$DOMAIN_PATH'" + _err "Cannot find path: '$DOMAIN_PATH'" return 1 fi + _debug2 DOMAIN_CONF "$DOMAIN_CONF" . "$DOMAIN_CONF" _savedomainconf Le_DeployHook "$_hooks" @@ -5408,7 +5902,7 @@ _initpath "$_main_domain" "$_isEcc" if [ ! -d "$DOMAIN_PATH" ]; then _err "The domain '$_main_domain' is not a cert name. You must use the cert name to specify the cert to install." - _err "Can not find path:'$DOMAIN_PATH'" + _err "Cannot find path: '$DOMAIN_PATH'" return 1 fi @@ -5417,7 +5911,8 @@ _savedomainconf "Le_RealKeyPath" "$_real_key" _savedomainconf "Le_ReloadCmd" "$_reload_cmd" "base64" _savedomainconf "Le_RealFullChainPath" "$_real_fullchain" - + export Le_ForceNewDomainKey="$(_readdomainconf Le_ForceNewDomainKey)" + export Le_Next_Domain_Key _installcert "$_main_domain" "$_real_cert" "$_real_key" "$_real_ca" "$_real_fullchain" "$_reload_cmd" } @@ -5451,15 +5946,17 @@ mkdir -p "$_backup_path" if [ "$_real_cert" ]; then - _info "Installing cert to:$_real_cert" + _info "Installing cert to: $_real_cert" if [ -f "$_real_cert" ] && [ ! "$_ACME_IS_RENEW" ]; then cp "$_real_cert" "$_backup_path/cert.bak" fi - cat "$CERT_PATH" >"$_real_cert" || return 1 + if [ "$CERT_PATH" != "$_real_cert" ]; then + cat "$CERT_PATH" >"$_real_cert" || return 1 + fi fi if [ "$_real_ca" ]; then - _info "Installing CA to:$_real_ca" + _info "Installing CA to: $_real_ca" if [ "$_real_ca" = "$_real_cert" ]; then echo "" >>"$_real_ca" cat "$CA_CERT_PATH" >>"$_real_ca" || return 1 @@ -5467,44 +5964,53 @@ if [ -f "$_real_ca" ] && [ ! "$_ACME_IS_RENEW" ]; then cp "$_real_ca" "$_backup_path/ca.bak" fi - cat "$CA_CERT_PATH" >"$_real_ca" || return 1 + if [ "$CA_CERT_PATH" != "$_real_ca" ]; then + cat "$CA_CERT_PATH" >"$_real_ca" || return 1 + fi fi fi if [ "$_real_key" ]; then - _info "Installing key to:$_real_key" + _info "Installing key to: $_real_key" if [ -f "$_real_key" ] && [ ! "$_ACME_IS_RENEW" ]; then cp "$_real_key" "$_backup_path/key.bak" fi - if [ -f "$_real_key" ]; then - cat "$CERT_KEY_PATH" >"$_real_key" || return 1 - else - cat "$CERT_KEY_PATH" >"$_real_key" || return 1 - chmod 600 "$_real_key" + if [ "$CERT_KEY_PATH" != "$_real_key" ]; then + if [ -f "$_real_key" ]; then + cat "$CERT_KEY_PATH" >"$_real_key" || return 1 + else + touch "$_real_key" || return 1 + chmod 600 "$_real_key" + cat "$CERT_KEY_PATH" >"$_real_key" || return 1 + fi fi fi if [ "$_real_fullchain" ]; then - _info "Installing full chain to:$_real_fullchain" + _info "Installing full chain to: $_real_fullchain" if [ -f "$_real_fullchain" ] && [ ! "$_ACME_IS_RENEW" ]; then cp "$_real_fullchain" "$_backup_path/fullchain.bak" fi - cat "$CERT_FULLCHAIN_PATH" >"$_real_fullchain" || return 1 + if [ "$_real_fullchain" != "$CERT_FULLCHAIN_PATH" ]; then + cat "$CERT_FULLCHAIN_PATH" >"$_real_fullchain" || return 1 + fi fi if [ "$_reload_cmd" ]; then - _info "Run reload cmd: $_reload_cmd" + _info "Running reload cmd: $_reload_cmd" if ( export CERT_PATH export CERT_KEY_PATH export CA_CERT_PATH export CERT_FULLCHAIN_PATH export Le_Domain="$_main_domain" + export Le_ForceNewDomainKey + export Le_Next_Domain_Key cd "$DOMAIN_PATH" && eval "$_reload_cmd" ); then - _info "$(__green "Reload success")" + _info "$(__green "Reload successful")" else - _err "Reload error for :$Le_Domain" + _err "Reload error for: $_main_domain" fi fi @@ -5532,25 +6038,25 @@ return 1 fi if ! _exists schtasks; then - _err "schtasks.exe is not found, are you on Windows?" + _err "schtasks.exe was not found, are you on Windows?" return 1 fi _winbash="$(cygpath -w $(which bash))" _debug _winbash "$_winbash" if [ -z "$_winbash" ]; then - _err "can not find bash path" + _err "Cannot find bash path" return 1 fi _myname="$(whoami)" _debug "_myname" "$_myname" if [ -z "$_myname" ]; then - _err "can not find my user name" + _err "Can not find own username" return 1 fi _debug "_lesh" "$_lesh" - _info "To install scheduler task in your Windows account, you must input your windows password." - _info "$PROJECT_NAME doesn't save your password." + _info "To install the scheduler task to your Windows account, you must input your Windows password." + _info "$PROJECT_NAME will not save your password." _info "Please input your Windows password for: $(__green "$_myname")" _password="$(__read_password)" #SCHTASKS.exe '/create' '/SC' 'DAILY' '/TN' "$_WINDOWS_SCHEDULER_NAME" '/F' '/ST' "00:$_randomminute" '/RU' "$_myname" '/RP' "$_password" '/TR' "$_winbash -l -c '$_lesh --cron --home \"$LE_WORKING_DIR\" $_centry'" >/dev/null @@ -5561,11 +6067,11 @@ _uninstall_win_taskscheduler() { if ! _exists schtasks; then - _err "schtasks.exe is not found, are you on Windows?" + _err "schtasks.exe was not found, are you on Windows?" return 1 fi if ! echo SCHTASKS /query /tn "$_WINDOWS_SCHEDULER_NAME" | cmd.exe >/dev/null; then - _debug "scheduler $_WINDOWS_SCHEDULER_NAME is not found." + _debug "scheduler $_WINDOWS_SCHEDULER_NAME was not found." else _info "Removing $_WINDOWS_SCHEDULER_NAME" echo SCHTASKS /delete /f /tn "$_WINDOWS_SCHEDULER_NAME" | cmd.exe >/dev/null @@ -5580,14 +6086,23 @@ if [ -f "$LE_WORKING_DIR/$PROJECT_ENTRY" ]; then lesh="\"$LE_WORKING_DIR\"/$PROJECT_ENTRY" else - _err "Can not install cronjob, $PROJECT_ENTRY not found." - return 1 + _debug "_SCRIPT_" "$_SCRIPT_" + _script="$(_readlink "$_SCRIPT_")" + _debug _script "$_script" + if [ -f "$_script" ]; then + _info "Using the current script from: $_script" + lesh="$_script" + else + _err "Cannot install cronjob, $PROJECT_ENTRY not found." + return 1 + fi fi if [ "$_c_home" ]; then _c_entry="--config-home \"$_c_home\" " fi _t=$(_time) random_minute=$(_math $_t % 60) + random_hour=$(_math $_t / 60 % 24) if ! _exists "$_CRONTAB" && _exists "fcrontab"; then _CRONTAB="fcrontab" @@ -5595,37 +6110,35 @@ if ! _exists "$_CRONTAB"; then if _exists cygpath && _exists schtasks.exe; then - _info "It seems you are on Windows, let's install Windows scheduler task." + _info "It seems you are on Windows, let's install the Windows scheduler task." if _install_win_taskscheduler "$lesh" "$_c_entry" "$random_minute"; then - _info "Install Windows scheduler task success." + _info "Successfully installed Windows scheduler task." return 0 else - _err "Install Windows scheduler task failed." + _err "Failed to install Windows scheduler task." return 1 fi fi - _err "crontab/fcrontab doesn't exist, so, we can not install cron jobs." - _err "All your certs will not be renewed automatically." - _err "You must add your own cron job to call '$PROJECT_ENTRY --cron' everyday." + _err "crontab/fcrontab doesn't exist, so we cannot install cron jobs." + _err "Your certs will not be renewed automatically." + _err "You must add your own cron job to call '$PROJECT_ENTRY --cron' every day." return 1 fi _info "Installing cron job" if ! $_CRONTAB -l | grep "$PROJECT_ENTRY --cron"; then if _exists uname && uname -a | grep SunOS >/dev/null; then - $_CRONTAB -l | { - cat - echo "$random_minute 0 * * * $lesh --cron --home \"$LE_WORKING_DIR\" $_c_entry> /dev/null" - } | $_CRONTAB -- + _CRONTAB_STDIN="$_CRONTAB --" else - $_CRONTAB -l | { - cat - echo "$random_minute 0 * * * $lesh --cron --home \"$LE_WORKING_DIR\" $_c_entry> /dev/null" - } | $_CRONTAB - - fi + _CRONTAB_STDIN="$_CRONTAB -" + fi + $_CRONTAB -l | { + cat + echo "$random_minute $random_hour * * * $lesh --cron --home \"$LE_WORKING_DIR\" $_c_entry> /dev/null" + } | $_CRONTAB_STDIN fi if [ "$?" != "0" ]; then - _err "Install cron job failed. You need to manually renew your certs." - _err "Or you can add cronjob by yourself:" + _err "Failed to install cron job. You need to manually renew your certs." + _err "Alternatively, you can add a cron job by yourself:" _err "$lesh --cron --home \"$LE_WORKING_DIR\" > /dev/null" return 1 fi @@ -5639,12 +6152,12 @@ if ! _exists "$_CRONTAB"; then if _exists cygpath && _exists schtasks.exe; then - _info "It seems you are on Windows, let's uninstall Windows scheduler task." + _info "It seems you are on Windows, let's uninstall the Windows scheduler task." if _uninstall_win_taskscheduler; then - _info "Uninstall Windows scheduler task success." + _info "Successfully uninstalled Windows scheduler task." return 0 else - _err "Uninstall Windows scheduler task failed." + _err "Failed to uninstall Windows scheduler task." return 1 fi fi @@ -5653,7 +6166,7 @@ _info "Removing cron job" cr="$($_CRONTAB -l | grep "$PROJECT_ENTRY --cron")" if [ "$cr" ]; then - if _exists uname && uname -a | grep solaris >/dev/null; then + if _exists uname && uname -a | grep SunOS >/dev/null; then $_CRONTAB -l | sed "/$PROJECT_ENTRY --cron/d" | $_CRONTAB -- else $_CRONTAB -l | sed "/$PROJECT_ENTRY --cron/d" | $_CRONTAB - @@ -5684,59 +6197,76 @@ fi _initpath "$Le_Domain" "$_isEcc" if [ ! -f "$DOMAIN_CONF" ]; then - _err "$Le_Domain is not a issued domain, skip." + _err "$Le_Domain is not an issued domain, skipping." return 1 fi if [ ! -f "$CERT_PATH" ]; then - _err "Cert for $Le_Domain $CERT_PATH is not found, skip." + _err "Cert for $Le_Domain $CERT_PATH was not found, skipping." return 1 fi + . "$DOMAIN_CONF" + _debug Le_API "$Le_API" + + if [ "$Le_API" ]; then + if [ "$Le_API" != "$ACME_DIRECTORY" ]; then + _clearAPI + fi + export ACME_DIRECTORY="$Le_API" + #reload ca configs + ACCOUNT_KEY_PATH="" + ACCOUNT_JSON_PATH="" + CA_CONF="" + _debug3 "initpath again." + _initpath "$Le_Domain" "$_isEcc" + _initAPI + fi + cert="$(_getfile "${CERT_PATH}" "${BEGIN_CERT}" "${END_CERT}" | tr -d "\r\n" | _url_replace)" if [ -z "$cert" ]; then - _err "Cert for $Le_Domain is empty found, skip." + _err "Cert for $Le_Domain is empty, skipping." return 1 fi _initAPI - if [ "$ACME_VERSION" = "2" ]; then - data="{\"certificate\": \"$cert\",\"reason\":$_reason}" - else - data="{\"resource\": \"revoke-cert\", \"certificate\": \"$cert\"}" - fi + data="{\"certificate\": \"$cert\",\"reason\":$_reason}" + uri="${ACME_REVOKE_CERT}" + _info "Trying account key first." + if _send_signed_request "$uri" "$data" "" "$ACCOUNT_KEY_PATH"; then + if [ -z "$response" ]; then + _info "Successfully revoked." + rm -f "$CERT_PATH" + cat "$CERT_KEY_PATH" >"$CERT_KEY_PATH.revoked" + cat "$CSR_PATH" >"$CSR_PATH.revoked" + return 0 + else + _err "Error revoking." + _debug "$response" + fi + fi + if [ -f "$CERT_KEY_PATH" ]; then - _info "Try domain key first." + _info "Trying domain key." if _send_signed_request "$uri" "$data" "" "$CERT_KEY_PATH"; then if [ -z "$response" ]; then - _info "Revoke success." + _info "Successfully revoked." rm -f "$CERT_PATH" + cat "$CERT_KEY_PATH" >"$CERT_KEY_PATH.revoked" + cat "$CSR_PATH" >"$CSR_PATH.revoked" return 0 else - _err "Revoke error by domain key." + _err "Error revoking using domain key." _err "$response" fi fi else _info "Domain key file doesn't exist." fi - - _info "Try account key." - - if _send_signed_request "$uri" "$data" "" "$ACCOUNT_KEY_PATH"; then - if [ -z "$response" ]; then - _info "Revoke success." - rm -f "$CERT_PATH" - return 0 - else - _err "Revoke error." - _debug "$response" - fi - fi return 1 } @@ -5754,19 +6284,19 @@ _removed_conf="$DOMAIN_CONF.removed" if [ ! -f "$DOMAIN_CONF" ]; then if [ -f "$_removed_conf" ]; then - _err "$Le_Domain is already removed, You can remove the folder by yourself: $DOMAIN_PATH" + _err "$Le_Domain has already been removed. You can remove the folder by yourself: $DOMAIN_PATH" else - _err "$Le_Domain is not a issued domain, skip." + _err "$Le_Domain is not an issued domain, skipping." fi return 1 fi if mv "$DOMAIN_CONF" "$_removed_conf"; then - _info "$Le_Domain is removed, the key and cert files are in $(__green $DOMAIN_PATH)" + _info "$Le_Domain has been removed. The key and cert files are in $(__green $DOMAIN_PATH)" _info "You can remove them by yourself." return 0 else - _err "Remove $Le_Domain failed." + _err "Failed to remove $Le_Domain." return 1 fi } @@ -5775,64 +6305,67 @@ _deactivate() { _d_domain="$1" _d_type="$2" - _initpath - - if [ "$ACME_VERSION" = "2" ]; then - _identifiers="{\"type\":\"dns\",\"value\":\"$_d_domain\"}" - if ! _send_signed_request "$ACME_NEW_ORDER" "{\"identifiers\": [$_identifiers]}"; then - _err "Can not get domain new order." - return 1 - fi - _authorizations_seg="$(echo "$response" | _egrep_o '"authorizations" *: *\[[^\]*\]' | cut -d '[' -f 2 | tr -d ']' | tr -d '"')" - _debug2 _authorizations_seg "$_authorizations_seg" - if [ -z "$_authorizations_seg" ]; then - _err "_authorizations_seg not found." - _clearup - _on_issue_err "$_post_hook" - return 1 - fi - - authzUri="$_authorizations_seg" - _debug2 "authzUri" "$authzUri" - if ! _send_signed_request "$authzUri"; then - _err "get to authz error." - _err "_authorizations_seg" "$_authorizations_seg" - _err "authzUri" "$authzUri" - _clearup - _on_issue_err "$_post_hook" - return 1 - fi - - response="$(echo "$response" | _normalizeJson)" - _debug2 response "$response" - _URL_NAME="url" - else - if ! __get_domain_new_authz "$_d_domain"; then - _err "Can not get domain new authz token." - return 1 - fi - - authzUri="$(echo "$responseHeaders" | grep "^Location:" | _head_n 1 | cut -d ':' -f 2- | tr -d "\r\n")" - _debug "authzUri" "$authzUri" - if [ "$code" ] && [ ! "$code" = '201' ]; then - _err "new-authz error: $response" - return 1 - fi - _URL_NAME="uri" - fi - - entries="$(echo "$response" | _egrep_o "[^{]*\"type\":\"[^\"]*\", *\"status\": *\"valid\", *\"$_URL_NAME\"[^}]*")" + _initpath "$_d_domain" "$_d_type" + + . "$DOMAIN_CONF" + _debug Le_API "$Le_API" + + if [ "$Le_API" ]; then + if [ "$Le_API" != "$ACME_DIRECTORY" ]; then + _clearAPI + fi + export ACME_DIRECTORY="$Le_API" + #reload ca configs + ACCOUNT_KEY_PATH="" + ACCOUNT_JSON_PATH="" + CA_CONF="" + _debug3 "initpath again." + _initpath "$Le_Domain" "$_d_type" + _initAPI + fi + + _identifiers="{\"type\":\"$(_getIdType "$_d_domain")\",\"value\":\"$_d_domain\"}" + if ! _send_signed_request "$ACME_NEW_ORDER" "{\"identifiers\": [$_identifiers]}"; then + _err "Cannot get new order for domain." + return 1 + fi + _authorizations_seg="$(echo "$response" | _egrep_o '"authorizations" *: *\[[^\]*\]' | cut -d '[' -f 2 | tr -d ']' | tr -d '"')" + _debug2 _authorizations_seg "$_authorizations_seg" + if [ -z "$_authorizations_seg" ]; then + _err "_authorizations_seg not found." + _clearup + _on_issue_err "$_post_hook" + return 1 + fi + + authzUri="$_authorizations_seg" + _debug2 "authzUri" "$authzUri" + if ! _send_signed_request "$authzUri"; then + _err "Error making GET request for authz." + _err "_authorizations_seg" "$_authorizations_seg" + _err "authzUri" "$authzUri" + _clearup + _on_issue_err "$_post_hook" + return 1 + fi + + response="$(echo "$response" | _normalizeJson)" + _debug2 response "$response" + _URL_NAME="url" + + entries="$(echo "$response" | tr '][' '==' | _egrep_o "challenges\": *=[^=]*=" | tr '}{' '\n\n' | grep "\"status\": *\"valid\"")" if [ -z "$entries" ]; then _info "No valid entries found." if [ -z "$thumbprint" ]; then thumbprint="$(__calc_account_thumbprint)" fi _debug "Trigger validation." - vtype="$VTYPE_DNS" - entry="$(echo "$response" | _egrep_o '[^\{]*"type":"'$vtype'"[^\}]*')" + vtype="$(_getIdType "$_d_domain")" + # Fix for empty error objects in response which mess up the original code, adapted from fix suggested here: https://github.com/acmesh-official/acme.sh/issues/4933#issuecomment-1870499018 + entry="$(echo "$response" | sed s/'"error":{}'/'"error":null'/ | _egrep_o '[^\{]*"type":"'$vtype'"[^\}]*')" _debug entry "$entry" if [ -z "$entry" ]; then - _err "Error, can not get domain token $d" + _err "$d: Cannot get domain token" return 1 fi token="$(echo "$entry" | _egrep_o '"token":"[^"]*' | cut -d : -f 2 | tr -d '"')" @@ -5850,13 +6383,13 @@ _d_i=0 _d_max_retry=$(echo "$entries" | wc -l) while [ "$_d_i" -lt "$_d_max_retry" ]; do - _info "Deactivate: $_d_domain" + _info "Deactivating $_d_domain" _d_i="$(_math $_d_i + 1)" entry="$(echo "$entries" | sed -n "${_d_i}p")" _debug entry "$entry" if [ -z "$entry" ]; then - _info "No more valid entry found." + _info "No more valid entries found." break fi @@ -5864,35 +6397,31 @@ _debug _vtype "$_vtype" _info "Found $_vtype" - uri="$(echo "$entry" | _egrep_o "\"$_URL_NAME\":\"[^\"]*" | cut -d : -f 2,3 | tr -d '"')" + uri="$(echo "$entry" | _egrep_o "\"$_URL_NAME\":\"[^\"]*\"" | tr -d '" ' | cut -d : -f 2-)" _debug uri "$uri" if [ "$_d_type" ] && [ "$_d_type" != "$_vtype" ]; then - _info "Skip $_vtype" + _info "Skipping $_vtype" continue fi - _info "Deactivate: $_vtype" - - if [ "$ACME_VERSION" = "2" ]; then - _djson="{\"status\":\"deactivated\"}" - else - _djson="{\"resource\": \"authz\", \"status\":\"deactivated\"}" - fi + _info "Deactivating $_vtype" + + _djson="{\"status\":\"deactivated\"}" if _send_signed_request "$authzUri" "$_djson" && _contains "$response" '"deactivated"'; then - _info "Deactivate: $_vtype success." + _info "Successfully deactivated $_vtype." else - _err "Can not deactivate $_vtype." + _err "Could not deactivate $_vtype." break fi done _debug "$_d_i" if [ "$_d_i" -eq "$_d_max_retry" ]; then - _info "Deactivated success!" + _info "Successfully deactivated!" else - _err "Deactivate failed." + _err "Deactivation failed." fi } @@ -5956,10 +6485,14 @@ _initpath if [ ! -f "$ACCOUNT_CONF_PATH" ]; then echo " + #LOG_FILE=\"$DEFAULT_LOG_FILE\" #LOG_LEVEL=1 + #AUTO_UPGRADE=\"1\" + #NO_TIMESTAMP=1 + " >"$ACCOUNT_CONF_PATH" fi } @@ -5969,17 +6502,17 @@ _nocron="$1" if ! _exists "curl" && ! _exists "wget"; then - _err "Please install curl or wget first, we need to access http resources." + _err "Please install curl or wget first to enable access to HTTP resources." return 1 fi if [ -z "$_nocron" ]; then if ! _exists "crontab" && ! _exists "fcrontab"; then if _exists cygpath && _exists schtasks.exe; then - _info "It seems you are on Windows, we will install Windows scheduler task." + _info "It seems you are on Windows, we will install the Windows scheduler task." else - _err "It is recommended to install crontab first. try to install 'cron, crontab, crontabs or vixie-cron'." - _err "We need to set cron job to renew the certs automatically." + _err "It is recommended to install crontab first. Try to install 'cron', 'crontab', 'crontabs' or 'vixie-cron'." + _err "We need to set a cron job to renew the certs automatically." _err "Otherwise, your certs will not be able to be renewed automatically." if [ -z "$FORCE" ]; then _err "Please add '--force' and try install again to go without crontab." @@ -5998,8 +6531,8 @@ if ! _exists "socat"; then _err "It is recommended to install socat first." - _err "We use socat for standalone server if you use standalone mode." - _err "If you don't use standalone mode, just ignore this warning." + _err "We use socat for the standalone server, which is used for standalone mode." + _err "If you don't want to use standalone mode, you may ignore this warning." fi return 0 @@ -6047,9 +6580,9 @@ _debug "Found profile: $_profile" _info "Installing alias to '$_profile'" _setopt "$_profile" ". \"$_envfile\"" - _info "OK, Close and reopen your terminal to start using $PROJECT_NAME" + _info "Close and reopen your terminal to start using $PROJECT_NAME" else - _info "No profile is found, you will need to go into $LE_WORKING_DIR to use $PROJECT_NAME" + _info "No profile has been found, you will need to change your working directory to $LE_WORKING_DIR to use $PROJECT_NAME" fi #for csh @@ -6081,7 +6614,7 @@ } -# nocron confighome noprofile +# nocron confighome noprofile accountemail install() { if [ -z "$LE_WORKING_DIR" ]; then @@ -6091,17 +6624,19 @@ _nocron="$1" _c_home="$2" _noprofile="$3" + _accountemail="$4" + if ! _initpath; then _err "Install failed." return 1 fi if [ "$_nocron" ]; then - _debug "Skip install cron job" + _debug "Skipping cron job installation" fi if [ "$_ACME_IN_CRON" != "1" ]; then if ! _precheck "$_nocron"; then - _err "Pre-check failed, can not install." + _err "Pre-check failed, cannot install." return 1 fi fi @@ -6131,7 +6666,7 @@ if [ ! -d "$LE_WORKING_DIR" ]; then if ! mkdir -p "$LE_WORKING_DIR"; then - _err "Can not create working dir: $LE_WORKING_DIR" + _err "Cannot create working dir: $LE_WORKING_DIR" return 1 fi @@ -6140,7 +6675,7 @@ if [ ! -d "$LE_CONFIG_HOME" ]; then if ! mkdir -p "$LE_CONFIG_HOME"; then - _err "Can not create config dir: $LE_CONFIG_HOME" + _err "Cannot create config dir: $LE_CONFIG_HOME" return 1 fi @@ -6150,7 +6685,7 @@ cp "$PROJECT_ENTRY" "$LE_WORKING_DIR/" && chmod +x "$LE_WORKING_DIR/$PROJECT_ENTRY" if [ "$?" != "0" ]; then - _err "Install failed, can not copy $PROJECT_ENTRY" + _err "Installation failed, cannot copy $PROJECT_ENTRY" return 1 fi @@ -6196,7 +6731,7 @@ fi fi if [ "$_bash_path" ]; then - _info "Good, bash is found, so change the shebang to use bash as preferred." + _info "bash has been found. Changing the shebang to use bash as preferred." _shebang='#!'"$_bash_path" _setShebang "$LE_WORKING_DIR/$PROJECT_ENTRY" "$_shebang" for subf in $_SUB_FOLDERS; do @@ -6209,6 +6744,10 @@ fi fi + if [ "$_accountemail" ]; then + _saveaccountconf "ACCOUNT_EMAIL" "$_accountemail" + fi + _saveaccountconf "UPGRADE_HASH" "$(_getUpgradeHash)" _info OK } @@ -6223,7 +6762,7 @@ _uninstallalias rm -f "$LE_WORKING_DIR/$PROJECT_ENTRY" - _info "The keys and certs are in \"$(__green "$LE_CONFIG_HOME")\", you can remove them by yourself." + _info "The keys and certs are in \"$(__green "$LE_CONFIG_HOME")\". You can remove them by yourself." } @@ -6261,7 +6800,7 @@ export LE_WORKING_DIR ( if ! upgrade; then - _err "Cron:Upgrade failed!" + _err "Cron: Upgrade failed!" return 1 fi ) @@ -6271,7 +6810,7 @@ __INTERACTIVE="1" fi - _info "Auto upgraded to: $VER" + _info "Automatically upgraded to: $VER" fi renewAll _ret="$?" @@ -6293,44 +6832,51 @@ _nerror="$4" if [ "$NOTIFY_LEVEL" = "$NOTIFY_LEVEL_DISABLE" ]; then - _debug "The NOTIFY_LEVEL is $NOTIFY_LEVEL, disabled, just return." + _debug "The NOTIFY_LEVEL is $NOTIFY_LEVEL, which means it's disabled, so will just return." return 0 fi if [ -z "$_nhooks" ]; then - _debug "The NOTIFY_HOOK is empty, just return." + _debug "The NOTIFY_HOOK is empty, will just return." return 0 fi + _nsource="$NOTIFY_SOURCE" + if [ -z "$_nsource" ]; then + _nsource="$(uname -n)" + fi + + _nsubject="$_nsubject by $_nsource" + _send_err=0 for _n_hook in $(echo "$_nhooks" | tr ',' " "); do _n_hook_file="$(_findHook "" $_SUB_FOLDER_NOTIFY "$_n_hook")" _info "Sending via: $_n_hook" _debug "Found $_n_hook_file for $_n_hook" if [ -z "$_n_hook_file" ]; then - _err "Can not find the hook file for $_n_hook" + _err "Cannot find the hook file for $_n_hook" continue fi if ! ( if ! . "$_n_hook_file"; then - _err "Load file $_n_hook_file error. Please check your api file and try again." + _err "Error loading file $_n_hook_file. Please check your API file and try again." return 1 fi d_command="${_n_hook}_send" if ! _exists "$d_command"; then - _err "It seems that your api file is not correct, it must have a function named: $d_command" + _err "It seems that your API file is not correct. Make sure it has a function named: $d_command" return 1 fi if ! $d_command "$_nsubject" "$_ncontent" "$_nerror"; then - _err "Error send message by $d_command" + _err "Error sending message using $d_command" return 1 fi return 0 ); then - _err "Set $_n_hook_file error." + _err "Error setting $_n_hook_file." _send_err=1 else _info "$_n_hook $(__green Success)" @@ -6356,11 +6902,12 @@ _nhook="$1" _nlevel="$2" _nmode="$3" + _nsource="$4" _initpath - if [ -z "$_nhook$_nlevel$_nmode" ]; then - _usage "Usage: $PROJECT_ENTRY --set-notify [--notify-hook <hookname>] [--notify-level <0|1|2|3>] [--notify-mode <0|1>]" + if [ -z "$_nhook$_nlevel$_nmode$_nsource" ]; then + _usage "Usage: $PROJECT_ENTRY --set-notify [--notify-hook <hookname>] [--notify-level <0|1|2|3>] [--notify-mode <0|1>] [--notify-source <hostname>]" _usage "$_NOTIFY_WIKI" return 1 fi @@ -6377,10 +6924,16 @@ _saveaccountconf "NOTIFY_MODE" "$NOTIFY_MODE" fi + if [ "$_nsource" ]; then + _info "Set notify source to: $_nsource" + export "NOTIFY_SOURCE=$_nsource" + _saveaccountconf "NOTIFY_SOURCE" "$NOTIFY_SOURCE" + fi + if [ "$_nhook" ]; then _info "Set notify hook to: $_nhook" if [ "$_nhook" = "$NO_VALUE" ]; then - _info "Clear notify hook" + _info "Clearing notify hook" _clearaccountconf "NOTIFY_HOOK" else if _set_notify_hook "$_nhook"; then @@ -6388,7 +6941,7 @@ _saveaccountconf "NOTIFY_HOOK" "$NOTIFY_HOOK" return 0 else - _err "Can not set notify hook to: $_nhook" + _err "Cannot set notify hook to: $_nhook" return 1 fi fi @@ -6408,12 +6961,13 @@ --upgrade Upgrade $PROJECT_NAME to the latest code from $PROJECT. --issue Issue a cert. --deploy Deploy the cert to your server. - -i, --install-cert Install the issued cert to apache/nginx or any other server. + -i, --install-cert Install the issued cert to Apache/nginx or any other server. -r, --renew Renew a cert. --renew-all Renew all the certs. --revoke Revoke a cert. --remove Remove the cert from list of certs known to $PROJECT_NAME. --list List all the certs. + --info Show the $PROJECT_NAME configs, or the configs for a domain with [-d domain] parameter. --to-pkcs12 Export the certificate and key to a pfx file. --to-pkcs8 Convert to pkcs8 format. --sign-csr Issue a cert from an existing csr. @@ -6431,51 +6985,70 @@ --deactivate Deactivate the domain authz, professional use. --set-default-ca Used with '--server', Set the default CA to use. See: $_SERVER_WIKI + --set-default-chain Set the default preferred chain for a CA. + See: $_PREFERRED_CHAIN_WIKI + + Parameters: -d, --domain <domain.tld> Specifies a domain, used to issue, renew or revoke etc. --challenge-alias <domain.tld> The challenge domain alias for DNS alias mode. - See: $_DNS_ALIAS_WIKI + See: $_DNS_ALIAS_WIKI + --domain-alias <domain.tld> The domain alias for DNS alias mode. - See: $_DNS_ALIAS_WIKI + See: $_DNS_ALIAS_WIKI + --preferred-chain <chain> If the CA offers multiple certificate chains, prefer the chain with an issuer matching this Subject Common Name. - If no match, the default offered chain will be used. (default: empty) - See: $_PREFERRED_CHAIN_WIKI + If no match, the default offered chain will be used. (default: empty) + See: $_PREFERRED_CHAIN_WIKI + + --valid-to <date-time> Request the NotAfter field of the cert. + See: $_VALIDITY_WIKI + --valid-from <date-time> Request the NotBefore field of the cert. + See: $_VALIDITY_WIKI + -f, --force Force install, force cert renewal or override sudo restrictions. --staging, --test Use staging server, for testing. - --debug [0|1|2|3] Output debug info. Defaults to 1 if argument is omitted. + --debug [0|1|2|3] Output debug info. Defaults to $DEBUG_LEVEL_DEFAULT if argument is omitted. --output-insecure Output all the sensitive messages. - By default all the credentials/sensitive messages are hidden from the output/debug/log for security. + By default all the credentials/sensitive messages are hidden from the output/debug/log for security. -w, --webroot <directory> Specifies the web root folder for web root mode. --standalone Use standalone mode. --alpn Use standalone alpn mode. --stateless Use stateless mode. - See: $_STATELESS_WIKI - --apache Use apache mode. + See: $_STATELESS_WIKI + + --apache Use Apache mode. --dns [dns_hook] Use dns manual mode or dns api. Defaults to manual mode when argument is omitted. - See: $_DNS_API_WIKI + See: $_DNS_API_WIKI + --dnssleep <seconds> The time in seconds to wait for all the txt records to propagate in dns api mode. - It's not necessary to use this by default, $PROJECT_NAME polls dns status by DOH automatically. + It's not necessary to use this by default, $PROJECT_NAME polls dns status by DOH automatically. -k, --keylength <bits> Specifies the domain key length: 2048, 3072, 4096, 8192 or ec-256, ec-384, ec-521. -ak, --accountkeylength <bits> Specifies the account key length: 2048, 3072, 4096 --log [file] Specifies the log file. Defaults to \"$DEFAULT_LOG_FILE\" if argument is omitted. - --log-level <1|2> Specifies the log level, default is 1. + --log-level <1|2> Specifies the log level, default is $DEFAULT_LOG_LEVEL. --syslog <0|3|6|7> Syslog level, 0: disable syslog, 3: error, 6: info, 7: debug. --eab-kid <eab_key_id> Key Identifier for External Account Binding. --eab-hmac-key <eab_hmac_key> HMAC key for External Account Binding. - These parameters are to install the cert to nginx/apache or any other server after issue/renew a cert: - --cert-file <file> Path to copy the cert file to after issue/renew.. + + + These parameters are to install the cert to nginx/Apache or any other server after issue/renew a cert: + + --cert-file <file> Path to copy the cert file to after issue/renew. --key-file <file> Path to copy the key file to after issue/renew. --ca-file <file> Path to copy the intermediate cert file to after issue/renew. --fullchain-file <file> Path to copy the fullchain cert file to after issue/renew. --reloadcmd <command> Command to execute after issue/renew to reload the server. + --server <server_uri> ACME Directory Resource URI. (default: $DEFAULT_CA) - See: $_SERVER_WIKI + See: $_SERVER_WIKI + --accountconf <file> Specifies a customized account config file. --home <directory> Specifies the home dir for $PROJECT_NAME. - --cert-home <directory> Specifies the home dir to save all the certs, only valid for '--install' command. + --cert-home <directory> Specifies the home dir to save all the certs. --config-home <directory> Specifies the home dir to save all the configurations. --useragent <string> Specifies the user agent string. it will be saved for future use too. - -m, --accountemail <email> Specifies the account email, only valid for the '--install' and '--update-account' command. + -m, --email <email> Specifies the account email, only valid for the '--install' and '--update-account' command. --accountkey <file> Specifies the account key path, only valid for the '--install' command. --days <ndays> Specifies the days to renew the cert when using '--issue' command. The default value is $DEFAULT_RENEW days. --httpport <port> Specifies the standalone listening port. Only valid if the server is behind a reverse proxy or load balancer. @@ -6486,17 +7059,19 @@ --insecure Do not check the server certificate, in some devices, the api server's certificate may not be trusted. --ca-bundle <file> Specifies the path to the CA certificate bundle to verify api server's certificate. --ca-path <directory> Specifies directory containing CA certificates in PEM format, used by wget or curl. - --nocron Only valid for '--install' command, which means: do not install the default cron job. - In this case, the certs will not be renewed automatically. - --noprofile Only valid for '--install' command, which means: do not install aliases to user profile. + --no-cron Only valid for '--install' command, which means: do not install the default cron job. + In this case, the certs will not be renewed automatically. + --no-profile Only valid for '--install' command, which means: do not install aliases to user profile. --no-color Do not output color text. --force-color Force output of color text. Useful for non-interactive use with the aha tool for HTML E-Mails. - --ecc Specifies to use the ECC cert. Valid for '--install-cert', '--renew', '--revoke', '--to-pkcs12' and '--create-csr' + --ecc Specifies use of the ECC cert. Only valid for '--install-cert', '--renew', '--remove ', '--revoke', + '--deploy', '--to-pkcs8', '--to-pkcs12' and '--create-csr'. --csr <file> Specifies the input csr. --pre-hook <command> Command to be run before obtaining any certificates. --post-hook <command> Command to be run after attempting to obtain/renew certificates. Runs regardless of whether obtain/renew succeeded or failed. --renew-hook <command> Command to be run after each successfully renewed certificate. --deploy-hook <hookname> The hook file to deploy cert + --extended-key-usage <string> Manually define the CSR extended key usage value. The default is serverAuth,clientAuth. --ocsp, --ocsp-must-staple Generate OCSP-Must-Staple extension. --always-force-new-domain-key Generate new domain key on renewal. Otherwise, the domain key is not changed by default. --auto-upgrade [0|1] Valid for '--upgrade' command, indicating whether to upgrade automatically in future. Defaults to 1 if argument is omitted. @@ -6505,35 +7080,39 @@ --openssl-bin <file> Specifies a custom openssl bin location. --use-wget Force to use wget, if you have both curl and wget installed. --yes-I-know-dns-manual-mode-enough-go-ahead-please Force use of dns manual mode. - See: $_DNS_MANUAL_WIKI + See: $_DNS_MANUAL_WIKI + -b, --branch <branch> Only valid for '--upgrade' command, specifies the branch name to upgrade to. --notify-level <0|1|2|3> Set the notification level: Default value is $NOTIFY_LEVEL_DEFAULT. - 0: disabled, no notification will be sent. - 1: send notifications only when there is an error. - 2: send notifications when a cert is successfully renewed, or there is an error. - 3: send notifications when a cert is skipped, renewed, or error. + 0: disabled, no notification will be sent. + 1: send notifications only when there is an error. + 2: send notifications when a cert is successfully renewed, or there is an error. + 3: send notifications when a cert is skipped, renewed, or error. --notify-mode <0|1> Set notification mode. Default value is $NOTIFY_MODE_DEFAULT. - 0: Bulk mode. Send all the domain's notifications in one message(mail). - 1: Cert mode. Send a message for every single cert. + 0: Bulk mode. Send all the domain's notifications in one message(mail). + 1: Cert mode. Send a message for every single cert. --notify-hook <hookname> Set the notify hook + --notify-source <server name> Set the server name in the notification message --revoke-reason <0-10> The reason for revocation, can be used in conjunction with the '--revoke' command. - See: $_REVOKE_WIKI + See: $_REVOKE_WIKI + --password <password> Add a password to exported pfx file. Use with --to-pkcs12. + + " } -# nocron noprofile -_installOnline() { +installOnline() { _info "Installing from online archive." - _nocron="$1" - _noprofile="$2" - if [ ! "$BRANCH" ]; then - BRANCH="master" - fi - - target="$PROJECT/archive/$BRANCH.tar.gz" + + _branch="$BRANCH" + if [ -z "$_branch" ]; then + _branch="master" + fi + + target="$PROJECT/archive/$_branch.tar.gz" _info "Downloading $target" - localname="$BRANCH.tar.gz" + localname="$_branch.tar.gz" if ! _get "$target" >$localname; then _err "Download error." return 1 @@ -6545,17 +7124,15 @@ exit 1 fi - cd "$PROJECT_NAME-$BRANCH" + cd "$PROJECT_NAME-$_branch" chmod +x $PROJECT_ENTRY - if ./$PROJECT_ENTRY install "$_nocron" "" "$_noprofile"; then + if ./$PROJECT_ENTRY --install "$@"; then _info "Install success!" - _initpath - _saveaccountconf "UPGRADE_HASH" "$(_getUpgradeHash)" fi cd .. - rm -rf "$PROJECT_NAME-$BRANCH" + rm -rf "$PROJECT_NAME-$_branch" rm -f "$localname" ) } @@ -6563,8 +7140,8 @@ _getRepoHash() { _hash_path=$1 shift - _hash_url="https://api.github.com/repos/acmesh-official/$PROJECT_NAME/git/refs/$_hash_path" - _get $_hash_url | tr -d "\r\n" | tr '{},' '\n' | grep '"sha":' | cut -d '"' -f 4 + _hash_url="${PROJECT_API:-https://api.github.com/repos/acmesh-official}/$PROJECT_NAME/git/refs/$_hash_path" + _get "$_hash_url" "" 30 | tr -d "\r\n" | tr '{},' '\n\n\n' | grep '"sha":' | cut -d '"' -f 4 } _getUpgradeHash() { @@ -6580,12 +7157,12 @@ upgrade() { if ( _initpath - [ -z "$FORCE" ] && [ "$(_getUpgradeHash)" = "$(_readaccountconf "UPGRADE_HASH")" ] && _info "Already uptodate!" && exit 0 + [ -z "$FORCE" ] && [ "$(_getUpgradeHash)" = "$(_readaccountconf "UPGRADE_HASH")" ] && _info "Already up to date!" && exit 0 export LE_WORKING_DIR cd "$LE_WORKING_DIR" - _installOnline "nocron" "noprofile" + installOnline "--nocron" "--noprofile" ); then - _info "Upgrade success!" + _info "Upgrade successful!" exit 0 else _err "Upgrade failed!" @@ -6621,14 +7198,18 @@ } _checkSudo() { + if [ -z "$__INTERACTIVE" ]; then + #don't check if it's not in an interactive shell + return 0 + fi if [ "$SUDO_GID" ] && [ "$SUDO_COMMAND" ] && [ "$SUDO_USER" ] && [ "$SUDO_UID" ]; then if [ "$SUDO_USER" = "root" ] && [ "$SUDO_UID" = "0" ]; then #it's root using sudo, no matter it's using sudo or not, just fine return 0 fi if [ -n "$SUDO_COMMAND" ]; then - #it's a normal user doing "sudo su", or `sudo -i` or `sudo -s` - _endswith "$SUDO_COMMAND" /bin/su || grep "^$SUDO_COMMAND\$" /etc/shells >/dev/null 2>&1 + #it's a normal user doing "sudo su", or `sudo -i` or `sudo -s`, or `sudo su acmeuser1` + _endswith "$SUDO_COMMAND" /bin/su || _contains "$SUDO_COMMAND" "/bin/su " || grep "^$SUDO_COMMAND\$" /etc/shells >/dev/null 2>&1 return $? fi #otherwise @@ -6637,9 +7218,10 @@ return 0 } -#server +#server #keylength _selectServer() { _server="$1" + _skeylength="$2" _server_lower="$(echo "$_server" | _lower_case)" _sindex=0 for snames in $CA_NAMES; do @@ -6650,6 +7232,9 @@ if [ "$_server_lower" = "$sname" ]; then _debug2 "_selectServer match $sname" _serverdir="$(_getfield "$CA_SERVERS" $_sindex)" + if [ "$_serverdir" = "$CA_SSLCOM_RSA" ] && _isEccKey "$_skeylength"; then + _serverdir="$CA_SSLCOM_ECC" + fi _debug "Selected server: $_serverdir" ACME_DIRECTORY="$_serverdir" export ACME_DIRECTORY @@ -6665,7 +7250,12 @@ _getCAShortName() { caurl="$1" if [ -z "$caurl" ]; then - caurl="$DEFAULT_CA" + #use letsencrypt as default value if the Le_API is empty + #this case can only come from the old upgrading. + caurl="$CA_LETSENCRYPT_V2" + fi + if [ "$CA_SSLCOM_ECC" = "$caurl" ]; then + caurl="$CA_SSLCOM_RSA" #just hack to get the short name fi caurl_lower="$(echo $caurl | _lower_case)" _sindex=0 @@ -6688,21 +7278,55 @@ #set default ca to $ACME_DIRECTORY setdefaultca() { if [ -z "$ACME_DIRECTORY" ]; then - _err "Please give a --server parameter." + _err "Please provide a --server parameter." return 1 fi _saveaccountconf "DEFAULT_ACME_SERVER" "$ACME_DIRECTORY" _info "Changed default CA to: $(__green "$ACME_DIRECTORY")" } +#preferred-chain +setdefaultchain() { + _initpath + _preferred_chain="$1" + if [ -z "$_preferred_chain" ]; then + _err "Please provide a value for '--preferred-chain'." + return 1 + fi + mkdir -p "$CA_DIR" + _savecaconf "DEFAULT_PREFERRED_CHAIN" "$_preferred_chain" +} + +#domain ecc +info() { + _domain="$1" + _ecc="$2" + _initpath + if [ -z "$_domain" ]; then + _debug "Show global configs" + echo "LE_WORKING_DIR=$LE_WORKING_DIR" + echo "LE_CONFIG_HOME=$LE_CONFIG_HOME" + cat "$ACCOUNT_CONF_PATH" + else + _debug "Show domain configs" + ( + _initpath "$_domain" "$_ecc" + echo "DOMAIN_CONF=$DOMAIN_CONF" + for seg in $(cat $DOMAIN_CONF | cut -d = -f 1); do + echo "$seg=$(_readdomainconf "$seg")" + done + ) + fi +} + _process() { _CMD="" _domain="" _altdomains="$NO_VALUE" _webroot="" _challenge_alias="" - _keylength="" - _accountkeylength="" + _keylength="$DEFAULT_DOMAIN_KEY_LENGTH" + _accountkeylength="$DEFAULT_ACCOUNT_KEY_LENGTH" _cert_file="" _key_file="" _ca_file="" @@ -6745,10 +7369,13 @@ _notify_hook="" _notify_level="" _notify_mode="" + _notify_source="" _revoke_reason="" _eab_kid="" _eab_hmac_key="" _preferred_chain="" + _valid_from="" + _valid_to="" while [ ${#} -gt 0 ]; do case "${1}" in @@ -6763,6 +7390,11 @@ --install) _CMD="install" ;; + --install-online) + shift + installOnline "$@" + return + ;; --uninstall) _CMD="uninstall" ;; @@ -6799,6 +7431,9 @@ --list) _CMD="list" ;; + --info) + _CMD="info" + ;; --install-cronjob | --installcronjob) _CMD="installcronjob" ;; @@ -6841,6 +7476,9 @@ --set-default-ca) _CMD="setdefaultca" ;; + --set-default-chain) + _CMD="setdefaultchain" + ;; -d | --domain) _dvalue="$2" @@ -6850,14 +7488,10 @@ return 1 fi if _is_idn "$_dvalue" && ! _exists idn; then - _err "It seems that $_dvalue is an IDN( Internationalized Domain Names), please install 'idn' command first." + _err "It seems that $_dvalue is an IDN (Internationalized Domain Names), please install the 'idn' command first." return 1 fi - if _startswith "$_dvalue" "*."; then - _debug "Wildcard domain" - export ACME_VERSION=2 - fi if [ -z "$_domain" ]; then _domain="$_dvalue" else @@ -6880,7 +7514,6 @@ ;; --server) _server="$2" - _selectServer "$_server" shift ;; --debug) @@ -6979,16 +7612,17 @@ Le_DNSSleep="$_dnssleep" shift ;; - --keylength | -k) _keylength="$2" shift + if [ "$_keylength" ] && ! _isEccKey "$_keylength"; then + export __SELECTED_RSA_KEY=1 + fi ;; -ak | --accountkeylength) _accountkeylength="$2" shift ;; - --cert-file | --certpath) _cert_file="$2" shift @@ -7019,17 +7653,17 @@ shift ;; --home) - LE_WORKING_DIR="$2" + export LE_WORKING_DIR="$(echo "$2" | sed 's|/$||')" shift ;; --cert-home | --certhome) _certhome="$2" - CERT_HOME="$_certhome" + export CERT_HOME="$_certhome" shift ;; --config-home) _confighome="$2" - LE_CONFIG_HOME="$_confighome" + export LE_CONFIG_HOME="$_confighome" shift ;; --useragent) @@ -7037,9 +7671,9 @@ USER_AGENT="$_useragent" shift ;; - -m | --accountemail) + -m | --email | --accountemail) _accountemail="$2" - ACCOUNT_EMAIL="$_accountemail" + export ACCOUNT_EMAIL="$_accountemail" shift ;; --accountkey) @@ -7052,6 +7686,14 @@ Le_RenewalDays="$_days" shift ;; + --valid-from) + _valid_from="$2" + shift + ;; + --valid-to) + _valid_to="$2" + shift + ;; --httpport) _httpport="$2" Le_HTTPPort="$_httpport" @@ -7082,10 +7724,10 @@ CA_PATH="$_ca_path" shift ;; - --nocron) + --no-cron | --nocron) _nocron="1" ;; - --noprofile) + --no-profile | --noprofile) _noprofile="1" ;; --no-color) @@ -7121,6 +7763,10 @@ _deploy_hook="$_deploy_hook$2," shift ;; + --extended-key-usage) + Le_ExtKeyUse="$2" + shift + ;; --ocsp-must-staple | --ocsp) Le_OCSP_Staple="1" ;; @@ -7208,7 +7854,7 @@ --notify-level) _nlevel="$2" if _startswith "$_nlevel" "-"; then - _err "'$_nlevel' is not a integer for '$1'" + _err "'$_nlevel' is not an integer for '$1'" return 1 fi _notify_level="$_nlevel" @@ -7217,16 +7863,25 @@ --notify-mode) _nmode="$2" if _startswith "$_nmode" "-"; then - _err "'$_nmode' is not a integer for '$1'" + _err "'$_nmode' is not an integer for '$1'" return 1 fi _notify_mode="$_nmode" shift ;; + --notify-source) + _nsource="$2" + if _startswith "$_nsource" "-"; then + _err "'$_nsource' is not a valid host name for '$1'" + return 1 + fi + _notify_source="$_nsource" + shift + ;; --revoke-reason) _revoke_reason="$2" if _startswith "$_revoke_reason" "-"; then - _err "'$_revoke_reason' is not a integer for '$1'" + _err "'$_revoke_reason' is not an integer for '$1'" return 1 fi shift @@ -7244,7 +7899,7 @@ shift ;; *) - _err "Unknown parameter : $1" + _err "Unknown parameter: $1" return 1 ;; esac @@ -7252,11 +7907,16 @@ shift 1 done + if [ "$_server" ]; then + _selectServer "$_server" "${_ecc:-$_keylength}" + _server="$ACME_DIRECTORY" + fi + if [ "${_CMD}" != "install" ]; then if [ "$__INTERACTIVE" ] && ! _checkSudo; then if [ -z "$FORCE" ]; then #Use "echo" here, instead of _info. it's too early - echo "It seems that you are using sudo, please read this link first:" + echo "It seems that you are using sudo, please read this page first:" echo "$_SUDO_WIKI" return 1 fi @@ -7286,7 +7946,7 @@ fi SYS_LOG="$_syslog" else - _err "The 'logger' command is not found, can not enable syslog." + _err "The 'logger' command was not found, cannot enable syslog." _clearaccountconf "SYS_LOG" SYS_LOG="" fi @@ -7305,17 +7965,17 @@ fi _debug "Running cmd: ${_CMD}" case "${_CMD}" in - install) install "$_nocron" "$_confighome" "$_noprofile" ;; + install) install "$_nocron" "$_confighome" "$_noprofile" "$_accountemail" ;; uninstall) uninstall "$_nocron" ;; upgrade) upgrade ;; issue) - issue "$_webroot" "$_domain" "$_altdomains" "$_keylength" "$_cert_file" "$_key_file" "$_ca_file" "$_reloadcmd" "$_fullchain_file" "$_pre_hook" "$_post_hook" "$_renew_hook" "$_local_address" "$_challenge_alias" "$_preferred_chain" + issue "$_webroot" "$_domain" "$_altdomains" "$_keylength" "$_cert_file" "$_key_file" "$_ca_file" "$_reloadcmd" "$_fullchain_file" "$_pre_hook" "$_post_hook" "$_renew_hook" "$_local_address" "$_challenge_alias" "$_preferred_chain" "$_valid_from" "$_valid_to" ;; deploy) deploy "$_domain" "$_deploy_hook" "$_ecc" ;; signcsr) - signcsr "$_csr" "$_webroot" "$_cert_file" "$_key_file" "$_ca_file" "$_reloadcmd" "$_fullchain_file" "$_pre_hook" "$_post_hook" "$_renew_hook" "$_local_address" "$_challenge_alias" + signcsr "$_csr" "$_webroot" "$_cert_file" "$_key_file" "$_ca_file" "$_reloadcmd" "$_fullchain_file" "$_pre_hook" "$_post_hook" "$_renew_hook" "$_local_address" "$_challenge_alias" "$_preferred_chain" ;; showcsr) showcsr "$_csr" "$_domain" @@ -7324,10 +7984,10 @@ installcert "$_domain" "$_cert_file" "$_key_file" "$_ca_file" "$_reloadcmd" "$_fullchain_file" "$_ecc" ;; renew) - renew "$_domain" "$_ecc" + renew "$_domain" "$_ecc" "$_server" ;; renewAll) - renewAll "$_stopRenewOnError" + renewAll "$_stopRenewOnError" "$_server" ;; revoke) revoke "$_domain" "$_ecc" "$_revoke_reason" @@ -7350,6 +8010,9 @@ list) list "$_listraw" "$_domain" ;; + info) + info "$_domain" "$_ecc" + ;; installcronjob) installcronjob "$_confighome" ;; uninstallcronjob) uninstallcronjob ;; cron) cron ;; @@ -7369,11 +8032,14 @@ createCSR "$_domain" "$_altdomains" "$_ecc" ;; setnotify) - setnotify "$_notify_hook" "$_notify_level" "$_notify_mode" + setnotify "$_notify_hook" "$_notify_level" "$_notify_mode" "$_notify_source" ;; setdefaultca) setdefaultca ;; + setdefaultchain) + setdefaultchain "$_preferred_chain" + ;; *) if [ "$_CMD" ]; then _err "Invalid command: $_CMD" @@ -7407,7 +8073,7 @@ _saveaccountconf "SYS_LOG" "$_syslog" fi else - _err "The 'logger' command is not found, can not enable syslog." + _err "The 'logger' command was not found, cannot enable syslog." _clearaccountconf "SYS_LOG" SYS_LOG="" fi @@ -7418,12 +8084,6 @@ } -if [ "$INSTALLONLINE" ]; then - INSTALLONLINE="" - _installOnline - exit -fi - main() { [ -z "$1" ] && showhelp && return if _startswith "$1" '-'; then _process "$@"; else "$@"; fi
--- a/host/update.sh Thu Sep 25 23:41:41 2025 -0600 +++ b/host/update.sh Sat Sep 27 13:37:11 2025 -0700 @@ -16,12 +16,13 @@ if [ ! -f local/leKey.txt ]; then echo Register letsencrypt - ./acme.sh --register-account --cert-home ./sites --config-home local/letsencrypt/config 2>&1 | grep 'ACCOUNT_THUMBPRINT' | awk -F "'" '{print $2}' > local/leKey.txt; -fi; + ./acme.sh --set-default-ca --server letsencrypt + ./acme.sh --register-account --server letsencrypt --cert-home ./sites --config-home local/letsencrypt/config 2>&1 | grep 'ACCOUNT_THUMBPRINT' | awk -F "'" '{print $2}' >local/leKey.txt +fi -cp startup/nginx/mime.types local/mime.types; -luan startup/nginx/nginx.conf.luan $(pwd) $(whoami) $(id -gn) > local/nginx.conf -luan startup/nginx/nginx.default.conf.luan $(pwd) $(cat local/leKey.txt) > local/nginx.default.conf +cp startup/nginx/mime.types local/mime.types +luan startup/nginx/nginx.conf.luan $(pwd) $(whoami) $(id -gn) >local/nginx.conf +luan startup/nginx/nginx.default.conf.luan $(pwd) $(cat local/leKey.txt) >local/nginx.default.conf echo Starting... ./start.sh
--- a/src/luan/host/https.luan Thu Sep 25 23:41:41 2025 -0600 +++ b/src/luan/host/https.luan Sat Sep 27 13:37:11 2025 -0700 @@ -14,6 +14,7 @@ local Logging = require "luan:logging/Logging.luan" local logger = Logging.logger "https" +logger.info("Hello test") function Hosted.set_https(is_https) if Http.did_init() then @@ -39,10 +40,14 @@ local s = uri("bash:"..cmd).read_text() logger.info("issue local certificate") else - local cmd = [[ -./acme.sh --debug --issue -d "]]..domain..[[" --stateless --cert-home "]]..top_dir..[[/sites" --config-home "]]..top_dir..[[/local/letsencrypt/config"; -]] - local s = uri("bash:"..cmd).read_text() + local cmd = [[ + ./acme.sh --debug --issue -d "]]..domain..[[" --stateless --server letsencrypt \ + --config-home "]]..top_dir..[[/local/letsencrypt/config" \ + --key-file "]]..key_file.canonical().to_string()..[[" \ + --ca-file "]]..local_ca_file.canonical().to_string()..[[" \ + --fullchain-file "]]..local_cer_file.canonical().to_string()..[[" ; + ]] + local s = uri("bash:"..cmd).read_text() logger.info("issue certificate "..s) end if key_file.exists() and local_cer_file.exists() then @@ -76,3 +81,4 @@ --logger.info "done" end Hosted.set_https = Boot.no_security(Hosted.set_https) +