#!/usr/bin/env sh
-VER=3.0.3
+VER=3.0.5
PROJECT_NAME="acme.sh"
_SUB_FOLDERS="$_SUB_FOLDER_DNSAPI $_SUB_FOLDER_DEPLOY $_SUB_FOLDER_NOTIFY"
-CA_LETSENCRYPT_V1="https://acme-v01.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"
CONTENT_TYPE_JSON="application/jose+json"
RENEW_SKIP=2
+CODE_DNS_MANUAL=3
B64CONF_START="__ACME_BASE64__START_"
B64CONF_END="__ACME_BASE64__END_"
}
_upper_case() {
- if _is_solaris; then
- tr '[:lower:]' '[:upper:]'
- else
- # shellcheck disable=SC2018,SC2019
- tr 'a-z' 'A-Z'
- fi
+ # shellcheck disable=SC2018,SC2019
+ tr '[a-z]' '[A-Z]'
}
_lower_case() {
- if _is_solaris; then
- tr '[:upper:]' '[:lower:]'
- else
- # shellcheck disable=SC2018,SC2019
- tr 'A-Z' 'a-z'
- fi
+ # shellcheck disable=SC2018,SC2019
+ tr '[A-Z]' '[a-z]'
}
_startswith() {
#Usage: multiline
_dbase64() {
if [ "$1" ]; then
- ${ACME_OPENSSL_BIN:-openssl} base64 -d -A
- else
${ACME_OPENSSL_BIN:-openssl} base64 -d
+ else
+ ${ACME_OPENSSL_BIN:-openssl} base64 -d -A
fi
}
_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" ]
}
return 1
}
+_clearCA() {
+ export CA_CONF=
+ export ACCOUNT_KEY_PATH=
+ export ACCOUNT_JSON_PATH=
+}
+
#[domain] [keylength or isEcc flag]
_initpath() {
domain="$1"
_isIPv4() {
for seg in $(echo "$1" | tr '.' ' '); do
_debug2 seg "$seg"
- if [ "$(echo "$seg" | tr -d [0-9])" ]; then
+ if [ "$(echo "$seg" | tr -d '[0-9]')" ]; then
#not all number
return 1
fi
_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."
return 1
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)
+ 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
response="$(echo "$response" | _normalizeJson)"
_debug2 response "$response"
- _d="$(echo "$response" | _egrep_o '"value" *: *"[^"]*"' | cut -d : -f 2 | tr -d ' "')"
+ _d="$(echo "$response" | _egrep_o '"value" *: *"[^"]*"' | cut -d : -f 2- | tr -d ' "')"
if _contains "$response" "\"wildcard\" *: *true"; then
_d="*.$_d"
fi
_dns_root_d="$(echo "$_dns_root_d" | sed 's/*.//')"
fi
_d_alias="$(_getfield "$_challenge_alias" "$_alias_index")"
- test "$_d_alias" = "false" && _d_alias=""
+ test "$_d_alias" = "$NO_VALUE" && _d_alias=""
_alias_index="$(_math "$_alias_index" + 1)"
_debug "_d_alias" "$_d_alias"
if [ "$_d_alias" ]; then
_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
return 1
fi
_debug "sleep 2 secs to verify again"
- sleep 2
+ _sleep 2
_debug "checking"
_send_signed_request "$uri"
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
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"
+
_set_level=${NOTIFY_LEVEL:-$NOTIFY_LEVEL_DEFAULT}
_info "$(__green "Renew: '$Le_Domain'")"
if [ ! -f "$DOMAIN_CONF" ]; then
. "$DOMAIN_CONF"
_debug Le_API "$Le_API"
- if [ -z "$Le_API" ] || [ "$CA_LETSENCRYPT_V1" = "$Le_API" ]; then
- #if this is from an old version, Le_API is empty,
- #so, we force to use letsencrypt server
- Le_API="$CA_LETSENCRYPT_V2"
- fi
- #revert from staging CAs back to production CAs
- if [ -z "$ACME_DIRECTORY" ]; then
- case "$Le_API" in
+ case "$Le_API" in
+ "$CA_LETSENCRYPT_V2_TEST")
+ _info "Switching back to $CA_LETSENCRYPT_V2"
+ Le_API="$CA_LETSENCRYPT_V2"
+ ;;
+ "$CA_BUYPASS_TEST")
+ _info "Switching back to $CA_BUYPASS"
+ Le_API="$CA_BUYPASS"
+ ;;
+ "$CA_GOOGLE_TEST")
+ _info "Switching back to $CA_GOOGLE"
+ Le_API="$CA_GOOGLE"
+ ;;
+ esac
- "$CA_LETSENCRYPT_V2_TEST")
- _info "Switching back to $CA_LETSENCRYPT_V2"
- Le_API="$CA_LETSENCRYPT_V2"
- ;;
- "$CA_BUYPASS_TEST")
- _info "Switching back to $CA_BUYPASS"
- Le_API="$CA_BUYPASS"
- ;;
- "$CA_GOOGLE_TEST")
- _info "Switching back to $CA_GOOGLE"
- Le_API="$CA_GOOGLE"
- ;;
- esac
+ if [ "$_server" ]; then
+ Le_API="$_server"
fi
+ _info "Renew to 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"
- fi
+ _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")"
Le_PostHook="$(_readdomainconf Le_PostHook)"
Le_RenewHook="$(_readdomainconf Le_RenewHook)"
Le_Preferred_Chain="$(_readdomainconf 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
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"
}
-#renewAll [stopRenewOnError]
+#renewAll [stopRenewOnError] [server]
renewAll() {
_initpath
+ _clearCA
_stopRenewOnError="$1"
_debug "_stopRenewOnError" "$_stopRenewOnError"
+
+ _server="$2"
+ _debug "_server" "$_server"
+
_ret="0"
_success_msg=""
_error_msg=""
_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"
_initpath
_csrsubj=$(_readSubjectFromCSR "$_csrfile")
- if [ "$?" != "0" ] || [ -z "$_csrsubj" ]; then
+ if [ "$?" != "0" ]; then
_err "Can not read subject from csr: $_csrfile"
return 1
fi
+ if [ -z "$_csrsubj" ]; then
+ _info "The Subject is empty"
+ fi
_info "Subject=$_csrsubj"
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
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 [ -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
- touch "$_real_key" || return 1
- chmod 600 "$_real_key"
- cat "$CERT_KEY_PATH" >"$_real_key" || return 1
+ 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 [ -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
}
_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
_altdomains="$NO_VALUE"
_webroot=""
_challenge_alias=""
- _keylength=""
- _accountkeylength=""
+ _keylength="$DEFAULT_DOMAIN_KEY_LENGTH"
+ _accountkeylength="$DEFAULT_ACCOUNT_KEY_LENGTH"
_cert_file=""
_key_file=""
_ca_file=""
if [ "$_server" ]; then
_selectServer "$_server" "${_ecc:-$_keylength}"
+ _server="$ACME_DIRECTORY"
fi
if [ "${_CMD}" != "install" ]; then
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"