mirror of
https://github.com/acmesh-official/acme.sh.git
synced 2025-04-30 06:52:45 +00:00
Ubiquiti removed keytool (and java) from recent releases of Unifi OS. This moves from keytool to openssl's native pkcs12. Tested on Unifi Dream Machine which runs Unifi OS and a built-in Unifi controller. Also added backup of existing files prior to change in case anything goes wrong, and update system configuration with compatible ciphers.
8034 lines
220 KiB
Bash
Executable File
8034 lines
220 KiB
Bash
Executable File
#!/usr/bin/env sh
|
|
|
|
VER=3.0.8
|
|
|
|
PROJECT_NAME="acme.sh"
|
|
|
|
PROJECT_ENTRY="acme.sh"
|
|
|
|
PROJECT="https://github.com/acmesh-official/$PROJECT_NAME"
|
|
|
|
DEFAULT_INSTALL_HOME="$HOME/.$PROJECT_NAME"
|
|
|
|
_WINDOWS_SCHEDULER_NAME="$PROJECT_NAME.cron"
|
|
|
|
_SCRIPT_="$0"
|
|
|
|
_SUB_FOLDER_NOTIFY="notify"
|
|
_SUB_FOLDER_DNSAPI="dnsapi"
|
|
_SUB_FOLDER_DEPLOY="deploy"
|
|
|
|
_SUB_FOLDERS="$_SUB_FOLDER_DNSAPI $_SUB_FOLDER_DEPLOY $_SUB_FOLDER_NOTIFY"
|
|
|
|
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="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
|
|
SSL.com,sslcom
|
|
Google.com,google
|
|
Google.com_test,googletest,google_test
|
|
"
|
|
|
|
CA_SERVERS="$CA_ZEROSSL,$CA_LETSENCRYPT_V2,$CA_LETSENCRYPT_V2_TEST,$CA_BUYPASS,$CA_BUYPASS_TEST,$CA_SSLCOM_RSA,$CA_GOOGLE,$CA_GOOGLE_TEST"
|
|
|
|
DEFAULT_USER_AGENT="$PROJECT_NAME/$VER ($PROJECT)"
|
|
|
|
DEFAULT_ACCOUNT_KEY_LENGTH=ec-256
|
|
DEFAULT_DOMAIN_KEY_LENGTH=ec-256
|
|
|
|
DEFAULT_OPENSSL_BIN="openssl"
|
|
|
|
VTYPE_HTTP="http-01"
|
|
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
|
|
|
|
NO_VALUE="no"
|
|
|
|
W_DNS="dns"
|
|
W_ALPN="alpn"
|
|
DNS_ALIAS_PREFIX="="
|
|
|
|
MODE_STATELESS="stateless"
|
|
|
|
STATE_VERIFIED="verified_ok"
|
|
|
|
NGINX="nginx:"
|
|
NGINX_START="#ACME_NGINX_START"
|
|
NGINX_END="#ACME_NGINX_END"
|
|
|
|
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_"
|
|
|
|
ECC_SEP="_"
|
|
ECC_SUFFIX="${ECC_SEP}ecc"
|
|
|
|
LOG_LEVEL_1=1
|
|
LOG_LEVEL_2=2
|
|
LOG_LEVEL_3=3
|
|
DEFAULT_LOG_LEVEL="$LOG_LEVEL_2"
|
|
|
|
DEBUG_LEVEL_1=1
|
|
DEBUG_LEVEL_2=2
|
|
DEBUG_LEVEL_3=3
|
|
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)"
|
|
|
|
SYSLOG_ERROR="user.error"
|
|
SYSLOG_INFO="user.info"
|
|
SYSLOG_DEBUG="user.debug"
|
|
|
|
#error
|
|
SYSLOG_LEVEL_ERROR=3
|
|
#info
|
|
SYSLOG_LEVEL_INFO=6
|
|
#debug
|
|
SYSLOG_LEVEL_DEBUG=7
|
|
#debug2
|
|
SYSLOG_LEVEL_DEBUG_2=8
|
|
#debug3
|
|
SYSLOG_LEVEL_DEBUG_3=9
|
|
|
|
SYSLOG_LEVEL_DEFAULT=$SYSLOG_LEVEL_ERROR
|
|
#none
|
|
SYSLOG_LEVEL_NONE=0
|
|
|
|
NOTIFY_LEVEL_DISABLE=0
|
|
NOTIFY_LEVEL_ERROR=1
|
|
NOTIFY_LEVEL_RENEW=2
|
|
NOTIFY_LEVEL_SKIP=3
|
|
|
|
NOTIFY_LEVEL_DEFAULT=$NOTIFY_LEVEL_RENEW
|
|
|
|
NOTIFY_MODE_BULK=0
|
|
NOTIFY_MODE_CERT=1
|
|
|
|
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"
|
|
|
|
_STATELESS_WIKI="https://github.com/acmesh-official/acme.sh/wiki/Stateless-Mode"
|
|
|
|
_DNS_ALIAS_WIKI="https://github.com/acmesh-official/acme.sh/wiki/DNS-alias-mode"
|
|
|
|
_DNS_MANUAL_WIKI="https://github.com/acmesh-official/acme.sh/wiki/dns-manual-mode"
|
|
|
|
_DNS_API_WIKI="https://github.com/acmesh-official/acme.sh/wiki/dnsapi"
|
|
|
|
_NOTIFY_WIKI="https://github.com/acmesh-official/acme.sh/wiki/notify"
|
|
|
|
_SUDO_WIKI="https://github.com/acmesh-official/acme.sh/wiki/sudo"
|
|
|
|
_REVOKE_WIKI="https://github.com/acmesh-official/acme.sh/wiki/revokecert"
|
|
|
|
_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"
|
|
|
|
_DNS_MANUAL_ERROR="It seems that you are using dns manual mode. Read this link first: $_DNS_MANUAL_WIKI"
|
|
|
|
__INTERACTIVE=""
|
|
if [ -t 1 ]; then
|
|
__INTERACTIVE="1"
|
|
fi
|
|
|
|
__green() {
|
|
if [ "${__INTERACTIVE}${ACME_NO_COLOR:-0}" = "10" -o "${ACME_FORCE_COLOR}" = "1" ]; then
|
|
printf '\33[1;32m%b\33[0m' "$1"
|
|
return
|
|
fi
|
|
printf -- "%b" "$1"
|
|
}
|
|
|
|
__red() {
|
|
if [ "${__INTERACTIVE}${ACME_NO_COLOR:-0}" = "10" -o "${ACME_FORCE_COLOR}" = "1" ]; then
|
|
printf '\33[1;31m%b\33[0m' "$1"
|
|
return
|
|
fi
|
|
printf -- "%b" "$1"
|
|
}
|
|
|
|
_printargs() {
|
|
_exitstatus="$?"
|
|
if [ -z "$NO_TIMESTAMP" ] || [ "$NO_TIMESTAMP" = "0" ]; then
|
|
printf -- "%s" "[$(date)] "
|
|
fi
|
|
if [ -z "$2" ]; then
|
|
printf -- "%s" "$1"
|
|
else
|
|
printf -- "%s" "$1='$2'"
|
|
fi
|
|
printf "\n"
|
|
# return the saved exit status
|
|
return "$_exitstatus"
|
|
}
|
|
|
|
_dlg_versions() {
|
|
echo "Diagnosis versions: "
|
|
echo "openssl:$ACME_OPENSSL_BIN"
|
|
if _exists "${ACME_OPENSSL_BIN:-openssl}"; then
|
|
${ACME_OPENSSL_BIN:-openssl} version 2>&1
|
|
else
|
|
echo "$ACME_OPENSSL_BIN doesn't exist."
|
|
fi
|
|
|
|
echo "Apache:"
|
|
if [ "$_APACHECTL" ] && _exists "$_APACHECTL"; then
|
|
$_APACHECTL -V 2>&1
|
|
else
|
|
echo "Apache doesn't exist."
|
|
fi
|
|
|
|
echo "nginx:"
|
|
if _exists "nginx"; then
|
|
nginx -V 2>&1
|
|
else
|
|
echo "nginx doesn't exist."
|
|
fi
|
|
|
|
echo "socat:"
|
|
if _exists "socat"; then
|
|
socat -V 2>&1
|
|
else
|
|
_debug "socat doesn't exist."
|
|
fi
|
|
}
|
|
|
|
#class
|
|
_syslog() {
|
|
_exitstatus="$?"
|
|
if [ "${SYS_LOG:-$SYSLOG_LEVEL_NONE}" = "$SYSLOG_LEVEL_NONE" ]; then
|
|
return
|
|
fi
|
|
_logclass="$1"
|
|
shift
|
|
if [ -z "$__logger_i" ]; then
|
|
if _contains "$(logger --help 2>&1)" "-i"; then
|
|
__logger_i="logger -i"
|
|
else
|
|
__logger_i="logger"
|
|
fi
|
|
fi
|
|
$__logger_i -t "$PROJECT_NAME" -p "$_logclass" "$(_printargs "$@")" >/dev/null 2>&1
|
|
return "$_exitstatus"
|
|
}
|
|
|
|
_log() {
|
|
[ -z "$LOG_FILE" ] && return
|
|
_printargs "$@" >>"$LOG_FILE"
|
|
}
|
|
|
|
_info() {
|
|
_log "$@"
|
|
if [ "${SYS_LOG:-$SYSLOG_LEVEL_NONE}" -ge "$SYSLOG_LEVEL_INFO" ]; then
|
|
_syslog "$SYSLOG_INFO" "$@"
|
|
fi
|
|
_printargs "$@"
|
|
}
|
|
|
|
_err() {
|
|
_syslog "$SYSLOG_ERROR" "$@"
|
|
_log "$@"
|
|
if [ -z "$NO_TIMESTAMP" ] || [ "$NO_TIMESTAMP" = "0" ]; then
|
|
printf -- "%s" "[$(date)] " >&2
|
|
fi
|
|
if [ -z "$2" ]; then
|
|
__red "$1" >&2
|
|
else
|
|
__red "$1='$2'" >&2
|
|
fi
|
|
printf "\n" >&2
|
|
return 1
|
|
}
|
|
|
|
_usage() {
|
|
__red "$@" >&2
|
|
printf "\n" >&2
|
|
}
|
|
|
|
__debug_bash_helper() {
|
|
# At this point only do for --debug 3
|
|
if [ "${DEBUG:-$DEBUG_LEVEL_NONE}" -lt "$DEBUG_LEVEL_3" ]; then
|
|
return
|
|
fi
|
|
# Return extra debug info when running with bash, otherwise return empty
|
|
# string.
|
|
if [ -z "${BASH_VERSION}" ]; then
|
|
return
|
|
fi
|
|
# We are a bash shell at this point, return the filename, function name, and
|
|
# line number as a string
|
|
_dbh_saveIFS=$IFS
|
|
IFS=" "
|
|
# Must use eval or syntax error happens under dash. The eval should use
|
|
# single quotes as older versions of busybox had a bug with double quotes and
|
|
# eval.
|
|
# Use 'caller 1' as we want one level up the stack as we should be called
|
|
# by one of the _debug* functions
|
|
eval '_dbh_called=($(caller 1))'
|
|
IFS=$_dbh_saveIFS
|
|
eval '_dbh_file=${_dbh_called[2]}'
|
|
if [ -n "${_script_home}" ]; then
|
|
# Trim off the _script_home directory name
|
|
eval '_dbh_file=${_dbh_file#$_script_home/}'
|
|
fi
|
|
eval '_dbh_function=${_dbh_called[1]}'
|
|
eval '_dbh_lineno=${_dbh_called[0]}'
|
|
printf "%-40s " "$_dbh_file:${_dbh_function}:${_dbh_lineno}"
|
|
}
|
|
|
|
_debug() {
|
|
if [ "${LOG_LEVEL:-$DEFAULT_LOG_LEVEL}" -ge "$LOG_LEVEL_1" ]; then
|
|
_log "$@"
|
|
fi
|
|
if [ "${SYS_LOG:-$SYSLOG_LEVEL_NONE}" -ge "$SYSLOG_LEVEL_DEBUG" ]; then
|
|
_syslog "$SYSLOG_DEBUG" "$@"
|
|
fi
|
|
if [ "${DEBUG:-$DEBUG_LEVEL_NONE}" -ge "$DEBUG_LEVEL_1" ]; then
|
|
_bash_debug=$(__debug_bash_helper)
|
|
_printargs "${_bash_debug}$@" >&2
|
|
fi
|
|
}
|
|
|
|
#output the sensitive messages
|
|
_secure_debug() {
|
|
if [ "${LOG_LEVEL:-$DEFAULT_LOG_LEVEL}" -ge "$LOG_LEVEL_1" ]; then
|
|
if [ "$OUTPUT_INSECURE" = "1" ]; then
|
|
_log "$@"
|
|
else
|
|
_log "$1" "$HIDDEN_VALUE"
|
|
fi
|
|
fi
|
|
if [ "${SYS_LOG:-$SYSLOG_LEVEL_NONE}" -ge "$SYSLOG_LEVEL_DEBUG" ]; then
|
|
_syslog "$SYSLOG_DEBUG" "$1" "$HIDDEN_VALUE"
|
|
fi
|
|
if [ "${DEBUG:-$DEBUG_LEVEL_NONE}" -ge "$DEBUG_LEVEL_1" ]; then
|
|
if [ "$OUTPUT_INSECURE" = "1" ]; then
|
|
_printargs "$@" >&2
|
|
else
|
|
_printargs "$1" "$HIDDEN_VALUE" >&2
|
|
fi
|
|
fi
|
|
}
|
|
|
|
_debug2() {
|
|
if [ "${LOG_LEVEL:-$DEFAULT_LOG_LEVEL}" -ge "$LOG_LEVEL_2" ]; then
|
|
_log "$@"
|
|
fi
|
|
if [ "${SYS_LOG:-$SYSLOG_LEVEL_NONE}" -ge "$SYSLOG_LEVEL_DEBUG_2" ]; then
|
|
_syslog "$SYSLOG_DEBUG" "$@"
|
|
fi
|
|
if [ "${DEBUG:-$DEBUG_LEVEL_NONE}" -ge "$DEBUG_LEVEL_2" ]; then
|
|
_bash_debug=$(__debug_bash_helper)
|
|
_printargs "${_bash_debug}$@" >&2
|
|
fi
|
|
}
|
|
|
|
_secure_debug2() {
|
|
if [ "${LOG_LEVEL:-$DEFAULT_LOG_LEVEL}" -ge "$LOG_LEVEL_2" ]; then
|
|
if [ "$OUTPUT_INSECURE" = "1" ]; then
|
|
_log "$@"
|
|
else
|
|
_log "$1" "$HIDDEN_VALUE"
|
|
fi
|
|
fi
|
|
if [ "${SYS_LOG:-$SYSLOG_LEVEL_NONE}" -ge "$SYSLOG_LEVEL_DEBUG_2" ]; then
|
|
_syslog "$SYSLOG_DEBUG" "$1" "$HIDDEN_VALUE"
|
|
fi
|
|
if [ "${DEBUG:-$DEBUG_LEVEL_NONE}" -ge "$DEBUG_LEVEL_2" ]; then
|
|
if [ "$OUTPUT_INSECURE" = "1" ]; then
|
|
_printargs "$@" >&2
|
|
else
|
|
_printargs "$1" "$HIDDEN_VALUE" >&2
|
|
fi
|
|
fi
|
|
}
|
|
|
|
_debug3() {
|
|
if [ "${LOG_LEVEL:-$DEFAULT_LOG_LEVEL}" -ge "$LOG_LEVEL_3" ]; then
|
|
_log "$@"
|
|
fi
|
|
if [ "${SYS_LOG:-$SYSLOG_LEVEL_NONE}" -ge "$SYSLOG_LEVEL_DEBUG_3" ]; then
|
|
_syslog "$SYSLOG_DEBUG" "$@"
|
|
fi
|
|
if [ "${DEBUG:-$DEBUG_LEVEL_NONE}" -ge "$DEBUG_LEVEL_3" ]; then
|
|
_bash_debug=$(__debug_bash_helper)
|
|
_printargs "${_bash_debug}$@" >&2
|
|
fi
|
|
}
|
|
|
|
_secure_debug3() {
|
|
if [ "${LOG_LEVEL:-$DEFAULT_LOG_LEVEL}" -ge "$LOG_LEVEL_3" ]; then
|
|
if [ "$OUTPUT_INSECURE" = "1" ]; then
|
|
_log "$@"
|
|
else
|
|
_log "$1" "$HIDDEN_VALUE"
|
|
fi
|
|
fi
|
|
if [ "${SYS_LOG:-$SYSLOG_LEVEL_NONE}" -ge "$SYSLOG_LEVEL_DEBUG_3" ]; then
|
|
_syslog "$SYSLOG_DEBUG" "$1" "$HIDDEN_VALUE"
|
|
fi
|
|
if [ "${DEBUG:-$DEBUG_LEVEL_NONE}" -ge "$DEBUG_LEVEL_3" ]; then
|
|
if [ "$OUTPUT_INSECURE" = "1" ]; then
|
|
_printargs "$@" >&2
|
|
else
|
|
_printargs "$1" "$HIDDEN_VALUE" >&2
|
|
fi
|
|
fi
|
|
}
|
|
|
|
_upper_case() {
|
|
# shellcheck disable=SC2018,SC2019
|
|
tr '[a-z]' '[A-Z]'
|
|
}
|
|
|
|
_lower_case() {
|
|
# shellcheck disable=SC2018,SC2019
|
|
tr '[A-Z]' '[a-z]'
|
|
}
|
|
|
|
_startswith() {
|
|
_str="$1"
|
|
_sub="$2"
|
|
echo "$_str" | grep -- "^$_sub" >/dev/null 2>&1
|
|
}
|
|
|
|
_endswith() {
|
|
_str="$1"
|
|
_sub="$2"
|
|
echo "$_str" | grep -- "$_sub\$" >/dev/null 2>&1
|
|
}
|
|
|
|
_contains() {
|
|
_str="$1"
|
|
_sub="$2"
|
|
echo "$_str" | grep -- "$_sub" >/dev/null 2>&1
|
|
}
|
|
|
|
_hasfield() {
|
|
_str="$1"
|
|
_field="$2"
|
|
_sep="$3"
|
|
if [ -z "$_field" ]; then
|
|
_usage "Usage: str field [sep]"
|
|
return 1
|
|
fi
|
|
|
|
if [ -z "$_sep" ]; then
|
|
_sep=","
|
|
fi
|
|
|
|
for f in $(echo "$_str" | tr "$_sep" ' '); do
|
|
if [ "$f" = "$_field" ]; then
|
|
_debug2 "'$_str' contains '$_field'"
|
|
return 0 #contains ok
|
|
fi
|
|
done
|
|
_debug2 "'$_str' does not contain '$_field'"
|
|
return 1 #not contains
|
|
}
|
|
|
|
# str index [sep]
|
|
_getfield() {
|
|
_str="$1"
|
|
_findex="$2"
|
|
_sep="$3"
|
|
|
|
if [ -z "$_findex" ]; then
|
|
_usage "Usage: str field [sep]"
|
|
return 1
|
|
fi
|
|
|
|
if [ -z "$_sep" ]; then
|
|
_sep=","
|
|
fi
|
|
|
|
_ffi="$_findex"
|
|
while [ "$_ffi" -gt "0" ]; do
|
|
_fv="$(echo "$_str" | cut -d "$_sep" -f "$_ffi")"
|
|
if [ "$_fv" ]; then
|
|
printf -- "%s" "$_fv"
|
|
return 0
|
|
fi
|
|
_ffi="$(_math "$_ffi" - 1)"
|
|
done
|
|
|
|
printf -- "%s" "$_str"
|
|
|
|
}
|
|
|
|
_exists() {
|
|
cmd="$1"
|
|
if [ -z "$cmd" ]; then
|
|
_usage "Usage: _exists cmd"
|
|
return 1
|
|
fi
|
|
|
|
if eval type type >/dev/null 2>&1; then
|
|
eval type "$cmd" >/dev/null 2>&1
|
|
elif command >/dev/null 2>&1; then
|
|
command -v "$cmd" >/dev/null 2>&1
|
|
else
|
|
which "$cmd" >/dev/null 2>&1
|
|
fi
|
|
ret="$?"
|
|
_debug3 "$cmd exists=$ret"
|
|
return $ret
|
|
}
|
|
|
|
#a + b
|
|
_math() {
|
|
_m_opts="$@"
|
|
printf "%s" "$(($_m_opts))"
|
|
}
|
|
|
|
_h_char_2_dec() {
|
|
_ch=$1
|
|
case "${_ch}" in
|
|
a | A)
|
|
printf "10"
|
|
;;
|
|
b | B)
|
|
printf "11"
|
|
;;
|
|
c | C)
|
|
printf "12"
|
|
;;
|
|
d | D)
|
|
printf "13"
|
|
;;
|
|
e | E)
|
|
printf "14"
|
|
;;
|
|
f | F)
|
|
printf "15"
|
|
;;
|
|
*)
|
|
printf "%s" "$_ch"
|
|
;;
|
|
esac
|
|
|
|
}
|
|
|
|
_URGLY_PRINTF=""
|
|
if [ "$(printf '\x41')" != 'A' ]; then
|
|
_URGLY_PRINTF=1
|
|
fi
|
|
|
|
_ESCAPE_XARGS=""
|
|
if _exists xargs && [ "$(printf %s '\\x41' | xargs printf)" = 'A' ]; then
|
|
_ESCAPE_XARGS=1
|
|
fi
|
|
|
|
_h2b() {
|
|
if _exists xxd; then
|
|
if _contains "$(xxd --help 2>&1)" "assumes -c30"; then
|
|
if xxd -r -p -c 9999 2>/dev/null; then
|
|
return
|
|
fi
|
|
else
|
|
if xxd -r -p 2>/dev/null; then
|
|
return
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
hex=$(cat)
|
|
ic=""
|
|
jc=""
|
|
_debug2 _URGLY_PRINTF "$_URGLY_PRINTF"
|
|
if [ -z "$_URGLY_PRINTF" ]; then
|
|
if [ "$_ESCAPE_XARGS" ] && _exists xargs; then
|
|
_debug2 "xargs"
|
|
echo "$hex" | _upper_case | sed 's/\([0-9A-F]\{2\}\)/\\\\\\x\1/g' | xargs printf
|
|
else
|
|
for h in $(echo "$hex" | _upper_case | sed 's/\([0-9A-F]\{2\}\)/ \1/g'); do
|
|
if [ -z "$h" ]; then
|
|
break
|
|
fi
|
|
printf "\x$h%s"
|
|
done
|
|
fi
|
|
else
|
|
for c in $(echo "$hex" | _upper_case | sed 's/\([0-9A-F]\)/ \1/g'); do
|
|
if [ -z "$ic" ]; then
|
|
ic=$c
|
|
continue
|
|
fi
|
|
jc=$c
|
|
ic="$(_h_char_2_dec "$ic")"
|
|
jc="$(_h_char_2_dec "$jc")"
|
|
printf '\'"$(printf "%o" "$(_math "$ic" \* 16 + $jc)")""%s"
|
|
ic=""
|
|
jc=""
|
|
done
|
|
fi
|
|
|
|
}
|
|
|
|
_is_solaris() {
|
|
_contains "${__OS__:=$(uname -a)}" "solaris" || _contains "${__OS__:=$(uname -a)}" "SunOS"
|
|
}
|
|
|
|
#_ascii_hex str
|
|
#this can only process ascii chars, should only be used when od command is missing as a backup way.
|
|
_ascii_hex() {
|
|
_debug2 "Using _ascii_hex"
|
|
_str="$1"
|
|
_str_len=${#_str}
|
|
_h_i=1
|
|
while [ "$_h_i" -le "$_str_len" ]; do
|
|
_str_c="$(printf "%s" "$_str" | cut -c "$_h_i")"
|
|
printf " %02x" "'$_str_c"
|
|
_h_i="$(_math "$_h_i" + 1)"
|
|
done
|
|
}
|
|
|
|
#stdin output hexstr splited by one space
|
|
#input:"abc"
|
|
#output: " 61 62 63"
|
|
_hex_dump() {
|
|
if _exists od; then
|
|
od -A n -v -t x1 | tr -s " " | sed 's/ $//' | tr -d "\r\t\n"
|
|
elif _exists hexdump; then
|
|
_debug3 "using hexdump"
|
|
hexdump -v -e '/1 ""' -e '/1 " %02x" ""'
|
|
elif _exists xxd; then
|
|
_debug3 "using xxd"
|
|
xxd -ps -c 20 -i | sed "s/ 0x/ /g" | tr -d ",\n" | tr -s " "
|
|
else
|
|
_debug3 "using _ascii_hex"
|
|
str=$(cat)
|
|
_ascii_hex "$str"
|
|
fi
|
|
}
|
|
|
|
#url encode, no-preserved chars
|
|
#A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
|
|
#41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f 50 51 52 53 54 55 56 57 58 59 5a
|
|
|
|
#a b c d e f g h i j k l m n o p q r s t u v w x y z
|
|
#61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f 70 71 72 73 74 75 76 77 78 79 7a
|
|
|
|
#0 1 2 3 4 5 6 7 8 9 - _ . ~
|
|
#30 31 32 33 34 35 36 37 38 39 2d 5f 2e 7e
|
|
|
|
#stdin stdout
|
|
_url_encode() {
|
|
_hex_str=$(_hex_dump)
|
|
_debug3 "_url_encode"
|
|
_debug3 "_hex_str" "$_hex_str"
|
|
for _hex_code in $_hex_str; do
|
|
#upper case
|
|
case "${_hex_code}" in
|
|
"41")
|
|
printf "%s" "A"
|
|
;;
|
|
"42")
|
|
printf "%s" "B"
|
|
;;
|
|
"43")
|
|
printf "%s" "C"
|
|
;;
|
|
"44")
|
|
printf "%s" "D"
|
|
;;
|
|
"45")
|
|
printf "%s" "E"
|
|
;;
|
|
"46")
|
|
printf "%s" "F"
|
|
;;
|
|
"47")
|
|
printf "%s" "G"
|
|
;;
|
|
"48")
|
|
printf "%s" "H"
|
|
;;
|
|
"49")
|
|
printf "%s" "I"
|
|
;;
|
|
"4a")
|
|
printf "%s" "J"
|
|
;;
|
|
"4b")
|
|
printf "%s" "K"
|
|
;;
|
|
"4c")
|
|
printf "%s" "L"
|
|
;;
|
|
"4d")
|
|
printf "%s" "M"
|
|
;;
|
|
"4e")
|
|
printf "%s" "N"
|
|
;;
|
|
"4f")
|
|
printf "%s" "O"
|
|
;;
|
|
"50")
|
|
printf "%s" "P"
|
|
;;
|
|
"51")
|
|
printf "%s" "Q"
|
|
;;
|
|
"52")
|
|
printf "%s" "R"
|
|
;;
|
|
"53")
|
|
printf "%s" "S"
|
|
;;
|
|
"54")
|
|
printf "%s" "T"
|
|
;;
|
|
"55")
|
|
printf "%s" "U"
|
|
;;
|
|
"56")
|
|
printf "%s" "V"
|
|
;;
|
|
"57")
|
|
printf "%s" "W"
|
|
;;
|
|
"58")
|
|
printf "%s" "X"
|
|
;;
|
|
"59")
|
|
printf "%s" "Y"
|
|
;;
|
|
"5a")
|
|
printf "%s" "Z"
|
|
;;
|
|
|
|
#lower case
|
|
"61")
|
|
printf "%s" "a"
|
|
;;
|
|
"62")
|
|
printf "%s" "b"
|
|
;;
|
|
"63")
|
|
printf "%s" "c"
|
|
;;
|
|
"64")
|
|
printf "%s" "d"
|
|
;;
|
|
"65")
|
|
printf "%s" "e"
|
|
;;
|
|
"66")
|
|
printf "%s" "f"
|
|
;;
|
|
"67")
|
|
printf "%s" "g"
|
|
;;
|
|
"68")
|
|
printf "%s" "h"
|
|
;;
|
|
"69")
|
|
printf "%s" "i"
|
|
;;
|
|
"6a")
|
|
printf "%s" "j"
|
|
;;
|
|
"6b")
|
|
printf "%s" "k"
|
|
;;
|
|
"6c")
|
|
printf "%s" "l"
|
|
;;
|
|
"6d")
|
|
printf "%s" "m"
|
|
;;
|
|
"6e")
|
|
printf "%s" "n"
|
|
;;
|
|
"6f")
|
|
printf "%s" "o"
|
|
;;
|
|
"70")
|
|
printf "%s" "p"
|
|
;;
|
|
"71")
|
|
printf "%s" "q"
|
|
;;
|
|
"72")
|
|
printf "%s" "r"
|
|
;;
|
|
"73")
|
|
printf "%s" "s"
|
|
;;
|
|
"74")
|
|
printf "%s" "t"
|
|
;;
|
|
"75")
|
|
printf "%s" "u"
|
|
;;
|
|
"76")
|
|
printf "%s" "v"
|
|
;;
|
|
"77")
|
|
printf "%s" "w"
|
|
;;
|
|
"78")
|
|
printf "%s" "x"
|
|
;;
|
|
"79")
|
|
printf "%s" "y"
|
|
;;
|
|
"7a")
|
|
printf "%s" "z"
|
|
;;
|
|
#numbers
|
|
"30")
|
|
printf "%s" "0"
|
|
;;
|
|
"31")
|
|
printf "%s" "1"
|
|
;;
|
|
"32")
|
|
printf "%s" "2"
|
|
;;
|
|
"33")
|
|
printf "%s" "3"
|
|
;;
|
|
"34")
|
|
printf "%s" "4"
|
|
;;
|
|
"35")
|
|
printf "%s" "5"
|
|
;;
|
|
"36")
|
|
printf "%s" "6"
|
|
;;
|
|
"37")
|
|
printf "%s" "7"
|
|
;;
|
|
"38")
|
|
printf "%s" "8"
|
|
;;
|
|
"39")
|
|
printf "%s" "9"
|
|
;;
|
|
"2d")
|
|
printf "%s" "-"
|
|
;;
|
|
"5f")
|
|
printf "%s" "_"
|
|
;;
|
|
"2e")
|
|
printf "%s" "."
|
|
;;
|
|
"7e")
|
|
printf "%s" "~"
|
|
;;
|
|
#other hex
|
|
*)
|
|
printf '%%%s' "$_hex_code"
|
|
;;
|
|
esac
|
|
done
|
|
}
|
|
|
|
_json_encode() {
|
|
_j_str="$(sed 's/"/\\"/g' | sed "s/\r/\\r/g")"
|
|
_debug3 "_json_encode"
|
|
_debug3 "_j_str" "$_j_str"
|
|
echo "$_j_str" | _hex_dump | _lower_case | sed 's/0a/5c 6e/g' | tr -d ' ' | _h2b | tr -d "\r\n"
|
|
}
|
|
|
|
#from: http:\/\/ to http://
|
|
_json_decode() {
|
|
_j_str="$(sed 's#\\/#/#g')"
|
|
_debug3 "_json_decode"
|
|
_debug3 "_j_str" "$_j_str"
|
|
echo "$_j_str"
|
|
}
|
|
|
|
#options file
|
|
_sed_i() {
|
|
options="$1"
|
|
filename="$2"
|
|
if [ -z "$filename" ]; then
|
|
_usage "Usage:_sed_i options filename"
|
|
return 1
|
|
fi
|
|
_debug2 options "$options"
|
|
if sed -h 2>&1 | grep "\-i\[SUFFIX]" >/dev/null 2>&1; then
|
|
_debug "Using sed -i"
|
|
sed -i "$options" "$filename"
|
|
else
|
|
_debug "No -i support in sed"
|
|
text="$(cat "$filename")"
|
|
echo "$text" | sed "$options" >"$filename"
|
|
fi
|
|
}
|
|
|
|
if [ "$(echo abc | egrep -o b 2>/dev/null)" = "b" ]; then
|
|
__USE_EGREP=1
|
|
else
|
|
__USE_EGREP=""
|
|
fi
|
|
|
|
_egrep_o() {
|
|
if [ "$__USE_EGREP" ]; then
|
|
egrep -o -- "$1" 2>/dev/null
|
|
else
|
|
sed -n 's/.*\('"$1"'\).*/\1/p'
|
|
fi
|
|
}
|
|
|
|
#Usage: file startline endline
|
|
_getfile() {
|
|
filename="$1"
|
|
startline="$2"
|
|
endline="$3"
|
|
if [ -z "$endline" ]; then
|
|
_usage "Usage: file startline endline"
|
|
return 1
|
|
fi
|
|
|
|
i="$(grep -n -- "$startline" "$filename" | cut -d : -f 1)"
|
|
if [ -z "$i" ]; then
|
|
_err "Cannot find start line: $startline"
|
|
return 1
|
|
fi
|
|
i="$(_math "$i" + 1)"
|
|
_debug i "$i"
|
|
|
|
j="$(grep -n -- "$endline" "$filename" | cut -d : -f 1)"
|
|
if [ -z "$j" ]; then
|
|
_err "Cannot find end line: $endline"
|
|
return 1
|
|
fi
|
|
j="$(_math "$j" - 1)"
|
|
_debug j "$j"
|
|
|
|
sed -n "$i,${j}p" "$filename"
|
|
|
|
}
|
|
|
|
#Usage: multiline
|
|
_base64() {
|
|
[ "" ] #urgly
|
|
if [ "$1" ]; then
|
|
_debug3 "base64 multiline:'$1'"
|
|
${ACME_OPENSSL_BIN:-openssl} base64 -e
|
|
else
|
|
_debug3 "base64 single line."
|
|
${ACME_OPENSSL_BIN:-openssl} base64 -e | tr -d '\r\n'
|
|
fi
|
|
}
|
|
|
|
#Usage: multiline
|
|
_dbase64() {
|
|
if [ "$1" ]; then
|
|
${ACME_OPENSSL_BIN:-openssl} base64 -d
|
|
else
|
|
${ACME_OPENSSL_BIN:-openssl} base64 -d -A
|
|
fi
|
|
}
|
|
|
|
#file
|
|
_checkcert() {
|
|
_cf="$1"
|
|
if [ "$DEBUG" ]; then
|
|
${ACME_OPENSSL_BIN:-openssl} x509 -noout -text -in "$_cf"
|
|
else
|
|
${ACME_OPENSSL_BIN:-openssl} x509 -noout -text -in "$_cf" >/dev/null 2>&1
|
|
fi
|
|
}
|
|
|
|
#Usage: hashalg [outputhex]
|
|
#Output Base64-encoded digest
|
|
_digest() {
|
|
alg="$1"
|
|
if [ -z "$alg" ]; then
|
|
_usage "Usage: _digest hashalg"
|
|
return 1
|
|
fi
|
|
|
|
outputhex="$2"
|
|
|
|
if [ "$alg" = "sha256" ] || [ "$alg" = "sha1" ] || [ "$alg" = "md5" ]; then
|
|
if [ "$outputhex" ]; then
|
|
${ACME_OPENSSL_BIN:-openssl} dgst -"$alg" -hex | cut -d = -f 2 | tr -d ' '
|
|
else
|
|
${ACME_OPENSSL_BIN:-openssl} dgst -"$alg" -binary | _base64
|
|
fi
|
|
else
|
|
_err "$alg is not supported yet"
|
|
return 1
|
|
fi
|
|
|
|
}
|
|
|
|
#Usage: hashalg secret_hex [outputhex]
|
|
#Output binary hmac
|
|
_hmac() {
|
|
alg="$1"
|
|
secret_hex="$2"
|
|
outputhex="$3"
|
|
|
|
if [ -z "$secret_hex" ]; then
|
|
_usage "Usage: _hmac hashalg secret [outputhex]"
|
|
return 1
|
|
fi
|
|
|
|
if [ "$alg" = "sha256" ] || [ "$alg" = "sha1" ]; then
|
|
if [ "$outputhex" ]; then
|
|
(${ACME_OPENSSL_BIN:-openssl} dgst -"$alg" -mac HMAC -macopt "hexkey:$secret_hex" 2>/dev/null || ${ACME_OPENSSL_BIN:-openssl} dgst -"$alg" -hmac "$(printf "%s" "$secret_hex" | _h2b)") | cut -d = -f 2 | tr -d ' '
|
|
else
|
|
${ACME_OPENSSL_BIN:-openssl} dgst -"$alg" -mac HMAC -macopt "hexkey:$secret_hex" -binary 2>/dev/null || ${ACME_OPENSSL_BIN:-openssl} dgst -"$alg" -hmac "$(printf "%s" "$secret_hex" | _h2b)" -binary
|
|
fi
|
|
else
|
|
_err "$alg is not supported yet"
|
|
return 1
|
|
fi
|
|
|
|
}
|
|
|
|
#Usage: keyfile hashalg
|
|
#Output: Base64-encoded signature value
|
|
_sign() {
|
|
keyfile="$1"
|
|
alg="$2"
|
|
if [ -z "$alg" ]; then
|
|
_usage "Usage: _sign keyfile hashalg"
|
|
return 1
|
|
fi
|
|
|
|
_sign_openssl="${ACME_OPENSSL_BIN:-openssl} dgst -sign $keyfile "
|
|
|
|
if _isRSA "$keyfile" >/dev/null 2>&1; then
|
|
$_sign_openssl -$alg | _base64
|
|
elif _isEcc "$keyfile" >/dev/null 2>&1; then
|
|
if ! _signedECText="$($_sign_openssl -sha$__ECC_KEY_LEN | ${ACME_OPENSSL_BIN:-openssl} asn1parse -inform DER)"; then
|
|
_err "Sign failed: $_sign_openssl"
|
|
_err "Key file: $keyfile"
|
|
_err "Key content: $(wc -l <"$keyfile") lines"
|
|
return 1
|
|
fi
|
|
_debug3 "_signedECText" "$_signedECText"
|
|
_ec_r="$(echo "$_signedECText" | _head_n 2 | _tail_n 1 | cut -d : -f 4 | tr -d "\r\n")"
|
|
_ec_s="$(echo "$_signedECText" | _head_n 3 | _tail_n 1 | cut -d : -f 4 | tr -d "\r\n")"
|
|
if [ "$__ECC_KEY_LEN" -eq "256" ]; then
|
|
while [ "${#_ec_r}" -lt "64" ]; do
|
|
_ec_r="0${_ec_r}"
|
|
done
|
|
while [ "${#_ec_s}" -lt "64" ]; do
|
|
_ec_s="0${_ec_s}"
|
|
done
|
|
fi
|
|
if [ "$__ECC_KEY_LEN" -eq "384" ]; then
|
|
while [ "${#_ec_r}" -lt "96" ]; do
|
|
_ec_r="0${_ec_r}"
|
|
done
|
|
while [ "${#_ec_s}" -lt "96" ]; do
|
|
_ec_s="0${_ec_s}"
|
|
done
|
|
fi
|
|
if [ "$__ECC_KEY_LEN" -eq "512" ]; then
|
|
while [ "${#_ec_r}" -lt "132" ]; do
|
|
_ec_r="0${_ec_r}"
|
|
done
|
|
while [ "${#_ec_s}" -lt "132" ]; do
|
|
_ec_s="0${_ec_s}"
|
|
done
|
|
fi
|
|
_debug3 "_ec_r" "$_ec_r"
|
|
_debug3 "_ec_s" "$_ec_s"
|
|
printf "%s" "$_ec_r$_ec_s" | _h2b | _base64
|
|
else
|
|
_err "Unknown key file format."
|
|
return 1
|
|
fi
|
|
|
|
}
|
|
|
|
#keylength or isEcc flag (empty str => not ecc)
|
|
_isEccKey() {
|
|
_length="$1"
|
|
|
|
if [ -z "$_length" ]; then
|
|
return 1
|
|
fi
|
|
|
|
[ "$_length" != "1024" ] &&
|
|
[ "$_length" != "2048" ] &&
|
|
[ "$_length" != "3072" ] &&
|
|
[ "$_length" != "4096" ] &&
|
|
[ "$_length" != "8192" ]
|
|
}
|
|
|
|
# _createkey 2048|ec-256 file
|
|
_createkey() {
|
|
length="$1"
|
|
f="$2"
|
|
_debug2 "_createkey for file:$f"
|
|
eccname="$length"
|
|
if _startswith "$length" "ec-"; then
|
|
length=$(printf "%s" "$length" | cut -d '-' -f 2-100)
|
|
|
|
if [ "$length" = "256" ]; then
|
|
eccname="prime256v1"
|
|
fi
|
|
if [ "$length" = "384" ]; then
|
|
eccname="secp384r1"
|
|
fi
|
|
if [ "$length" = "521" ]; then
|
|
eccname="secp521r1"
|
|
fi
|
|
|
|
fi
|
|
|
|
if [ -z "$length" ]; then
|
|
length=2048
|
|
fi
|
|
|
|
_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" -noout -genkey 2>/dev/null)"; then
|
|
echo "$_opkey" >"$f"
|
|
else
|
|
_err "Error encountered for ECC key named $eccname"
|
|
return 1
|
|
fi
|
|
else
|
|
_debug "Using RSA: $length"
|
|
__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 encountered for RSA key of length $length"
|
|
return 1
|
|
fi
|
|
fi
|
|
|
|
if [ "$?" != "0" ]; then
|
|
_err "Key creation error."
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
#domain
|
|
_is_idn() {
|
|
_is_idn_d="$1"
|
|
_debug2 _is_idn_d "$_is_idn_d"
|
|
_idn_temp=$(printf "%s" "$_is_idn_d" | tr -d '[0-9]' | tr -d '[a-z]' | tr -d '[A-Z]' | tr -d '*.,-_')
|
|
_debug2 _idn_temp "$_idn_temp"
|
|
[ "$_idn_temp" ]
|
|
}
|
|
|
|
#aa.com
|
|
#aa.com,bb.com,cc.com
|
|
_idn() {
|
|
__idn_d="$1"
|
|
if ! _is_idn "$__idn_d"; then
|
|
printf "%s" "$__idn_d"
|
|
return 0
|
|
fi
|
|
|
|
if _exists idn; then
|
|
if _contains "$__idn_d" ','; then
|
|
_i_first="1"
|
|
for f in $(echo "$__idn_d" | tr ',' ' '); do
|
|
[ -z "$f" ] && continue
|
|
if [ -z "$_i_first" ]; then
|
|
printf "%s" ","
|
|
else
|
|
_i_first=""
|
|
fi
|
|
idn --quiet "$f" | tr -d "\r\n"
|
|
done
|
|
else
|
|
idn "$__idn_d" | tr -d "\r\n"
|
|
fi
|
|
else
|
|
_err "Please install idn to process IDN names."
|
|
fi
|
|
}
|
|
|
|
#_createcsr cn san_list keyfile csrfile conf acmeValidationv1
|
|
_createcsr() {
|
|
_debug _createcsr
|
|
domain="$1"
|
|
domainlist="$2"
|
|
csrkey="$3"
|
|
csr="$4"
|
|
csrconf="$5"
|
|
acmeValidationv1="$6"
|
|
_debug2 domain "$domain"
|
|
_debug2 domainlist "$domainlist"
|
|
_debug2 csrkey "$csrkey"
|
|
_debug2 csr "$csr"
|
|
_debug2 csrconf "$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")"
|
|
_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=$(_getIdType "$domain" | _upper_case):$(_idn "$domain")" >>"$csrconf"
|
|
else
|
|
domainlist="$(_idn "$domainlist")"
|
|
_debug2 domainlist "$domainlist"
|
|
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"
|
|
fi
|
|
if [ "$Le_OCSP_Staple" = "1" ]; then
|
|
_savedomainconf Le_OCSP_Staple "$Le_OCSP_Staple"
|
|
printf -- "\nbasicConstraints = CA:FALSE\n1.3.6.1.5.5.7.1.24=DER:30:03:02:01:05" >>"$csrconf"
|
|
fi
|
|
|
|
if [ "$acmeValidationv1" ]; then
|
|
printf "\n1.3.6.1.5.5.7.1.31=critical,DER:04:20:${acmeValidationv1}" >>"${csrconf}"
|
|
fi
|
|
|
|
_csr_cn="$(_idn "$domain")"
|
|
_debug2 _csr_cn "$_csr_cn"
|
|
if _contains "$(uname -a)" "MINGW"; then
|
|
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
|
|
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
|
|
}
|
|
|
|
#_signcsr key csr conf cert
|
|
_signcsr() {
|
|
key="$1"
|
|
csr="$2"
|
|
conf="$3"
|
|
cert="$4"
|
|
_debug "_signcsr"
|
|
|
|
_msg="$(${ACME_OPENSSL_BIN:-openssl} x509 -req -days 365 -in "$csr" -signkey "$key" -extensions v3_req -extfile "$conf" -out "$cert" 2>&1)"
|
|
_ret="$?"
|
|
_debug "$_msg"
|
|
return $_ret
|
|
}
|
|
|
|
#_csrfile
|
|
_readSubjectFromCSR() {
|
|
_csrfile="$1"
|
|
if [ -z "$_csrfile" ]; then
|
|
_usage "_readSubjectFromCSR mycsr.csr"
|
|
return 1
|
|
fi
|
|
${ACME_OPENSSL_BIN:-openssl} req -noout -in "$_csrfile" -subject | tr ',' "\n" | _egrep_o "CN *=.*" | cut -d = -f 2 | cut -d / -f 1 | tr -d ' \n'
|
|
}
|
|
|
|
#_csrfile
|
|
#echo comma separated domain list
|
|
_readSubjectAltNamesFromCSR() {
|
|
_csrfile="$1"
|
|
if [ -z "$_csrfile" ]; then
|
|
_usage "_readSubjectAltNamesFromCSR mycsr.csr"
|
|
return 1
|
|
fi
|
|
|
|
_csrsubj="$(_readSubjectFromCSR "$_csrfile")"
|
|
_debug _csrsubj "$_csrsubj"
|
|
|
|
_dnsAltnames="$(${ACME_OPENSSL_BIN:-openssl} req -noout -text -in "$_csrfile" | grep "^ *DNS:.*" | tr -d ' \n')"
|
|
_debug _dnsAltnames "$_dnsAltnames"
|
|
|
|
if _contains "$_dnsAltnames," "DNS:$_csrsubj,"; then
|
|
_debug "AltNames contains subject"
|
|
_excapedAlgnames="$(echo "$_dnsAltnames" | tr '*' '#')"
|
|
_debug _excapedAlgnames "$_excapedAlgnames"
|
|
_escapedSubject="$(echo "$_csrsubj" | tr '*' '#')"
|
|
_debug _escapedSubject "$_escapedSubject"
|
|
_dnsAltnames="$(echo "$_excapedAlgnames," | sed "s/DNS:$_escapedSubject,//g" | tr '#' '*' | sed "s/,\$//g")"
|
|
_debug _dnsAltnames "$_dnsAltnames"
|
|
else
|
|
_debug "AltNames doesn't contain subject"
|
|
fi
|
|
|
|
echo "$_dnsAltnames" | sed "s/DNS://g"
|
|
}
|
|
|
|
#_csrfile
|
|
_readKeyLengthFromCSR() {
|
|
_csrfile="$1"
|
|
if [ -z "$_csrfile" ]; then
|
|
_usage "_readKeyLengthFromCSR mycsr.csr"
|
|
return 1
|
|
fi
|
|
|
|
_outcsr="$(${ACME_OPENSSL_BIN:-openssl} req -noout -text -in "$_csrfile")"
|
|
_debug2 _outcsr "$_outcsr"
|
|
if _contains "$_outcsr" "Public Key Algorithm: id-ecPublicKey"; then
|
|
_debug "ECC CSR"
|
|
echo "$_outcsr" | tr "\t" " " | _egrep_o "^ *ASN1 OID:.*" | cut -d ':' -f 2 | tr -d ' '
|
|
else
|
|
_debug "RSA CSR"
|
|
_rkl="$(echo "$_outcsr" | tr "\t" " " | _egrep_o "^ *Public.Key:.*" | cut -d '(' -f 2 | cut -d ' ' -f 1)"
|
|
if [ "$_rkl" ]; then
|
|
echo "$_rkl"
|
|
else
|
|
echo "$_outcsr" | tr "\t" " " | _egrep_o "RSA Public.Key:.*" | cut -d '(' -f 2 | cut -d ' ' -f 1
|
|
fi
|
|
fi
|
|
}
|
|
|
|
_ss() {
|
|
_port="$1"
|
|
|
|
if _exists "ss"; then
|
|
_debug "Using: ss"
|
|
ss -ntpl 2>/dev/null | grep ":$_port "
|
|
return 0
|
|
fi
|
|
|
|
if _exists "netstat"; then
|
|
_debug "Using: netstat"
|
|
if netstat -help 2>&1 | grep "\-p proto" >/dev/null; then
|
|
#for windows version netstat tool
|
|
netstat -an -p tcp | grep "LISTENING" | grep ":$_port "
|
|
else
|
|
if netstat -help 2>&1 | grep "\-p protocol" >/dev/null; then
|
|
netstat -an -p tcp | grep LISTEN | grep ":$_port "
|
|
elif netstat -help 2>&1 | grep -- '-P protocol' >/dev/null; then
|
|
#for solaris
|
|
netstat -an -P tcp | grep "\.$_port " | grep "LISTEN"
|
|
elif netstat -help 2>&1 | grep "\-p" >/dev/null; then
|
|
#for full linux
|
|
netstat -ntpl | grep ":$_port "
|
|
else
|
|
#for busybox (embedded linux; no pid support)
|
|
netstat -ntl 2>/dev/null | grep ":$_port "
|
|
fi
|
|
fi
|
|
return 0
|
|
fi
|
|
|
|
return 1
|
|
}
|
|
|
|
#outfile key cert cacert [password [name [caname]]]
|
|
_toPkcs() {
|
|
_cpfx="$1"
|
|
_ckey="$2"
|
|
_ccert="$3"
|
|
_cca="$4"
|
|
pfxPassword="$5"
|
|
pfxName="$6"
|
|
pfxCaname="$7"
|
|
|
|
if [ "$pfxCaname" ]; then
|
|
${ACME_OPENSSL_BIN:-openssl} pkcs12 -export -out "$_cpfx" -inkey "$_ckey" -in "$_ccert" -certfile "$_cca" -password "pass:$pfxPassword" -name "$pfxName" -caname "$pfxCaname"
|
|
elif [ "$pfxName" ]; then
|
|
${ACME_OPENSSL_BIN:-openssl} pkcs12 -export -out "$_cpfx" -inkey "$_ckey" -in "$_ccert" -certfile "$_cca" -password "pass:$pfxPassword" -name "$pfxName"
|
|
elif [ "$pfxPassword" ]; then
|
|
${ACME_OPENSSL_BIN:-openssl} pkcs12 -export -out "$_cpfx" -inkey "$_ckey" -in "$_ccert" -certfile "$_cca" -password "pass:$pfxPassword"
|
|
else
|
|
${ACME_OPENSSL_BIN:-openssl} pkcs12 -export -out "$_cpfx" -inkey "$_ckey" -in "$_ccert" -certfile "$_cca"
|
|
fi
|
|
if [ "$?" == "0" ]; then
|
|
_savedomainconf "Le_PFXPassword" "$pfxPassword"
|
|
fi
|
|
|
|
}
|
|
|
|
#domain [password] [isEcc]
|
|
toPkcs() {
|
|
domain="$1"
|
|
pfxPassword="$2"
|
|
if [ -z "$domain" ]; then
|
|
_usage "Usage: $PROJECT_ENTRY --to-pkcs12 --domain <domain.tld> [--password <password>] [--ecc]"
|
|
return 1
|
|
fi
|
|
|
|
_isEcc="$3"
|
|
|
|
_initpath "$domain" "$_isEcc"
|
|
|
|
_toPkcs "$CERT_PFX_PATH" "$CERT_KEY_PATH" "$CERT_PATH" "$CA_CERT_PATH" "$pfxPassword"
|
|
|
|
if [ "$?" = "0" ]; then
|
|
_info "Success, PFX has been exported to: $CERT_PFX_PATH"
|
|
fi
|
|
|
|
}
|
|
|
|
#domain [isEcc]
|
|
toPkcs8() {
|
|
domain="$1"
|
|
|
|
if [ -z "$domain" ]; then
|
|
_usage "Usage: $PROJECT_ENTRY --to-pkcs8 --domain <domain.tld> [--ecc]"
|
|
return 1
|
|
fi
|
|
|
|
_isEcc="$2"
|
|
|
|
_initpath "$domain" "$_isEcc"
|
|
|
|
${ACME_OPENSSL_BIN:-openssl} pkcs8 -topk8 -inform PEM -outform PEM -nocrypt -in "$CERT_KEY_PATH" -out "$CERT_PKCS8_PATH"
|
|
|
|
if [ "$?" = "0" ]; then
|
|
_info "Success, $CERT_PKCS8_PATH"
|
|
fi
|
|
|
|
}
|
|
|
|
#[2048]
|
|
createAccountKey() {
|
|
_info "Creating account key"
|
|
if [ -z "$1" ]; then
|
|
_usage "Usage: $PROJECT_ENTRY --create-account-key [--accountkeylength <bits>]"
|
|
return
|
|
fi
|
|
|
|
length=$1
|
|
_create_account_key "$length"
|
|
|
|
}
|
|
|
|
_create_account_key() {
|
|
|
|
length=$1
|
|
|
|
if [ -z "$length" ] || [ "$length" = "$NO_VALUE" ]; then
|
|
_debug "Using default length $DEFAULT_ACCOUNT_KEY_LENGTH"
|
|
length="$DEFAULT_ACCOUNT_KEY_LENGTH"
|
|
fi
|
|
|
|
_debug length "$length"
|
|
_initpath
|
|
|
|
mkdir -p "$CA_DIR"
|
|
if [ -s "$ACCOUNT_KEY_PATH" ]; then
|
|
_info "Account key exists, skipping"
|
|
return 0
|
|
else
|
|
#generate account key
|
|
if _createkey "$length" "$ACCOUNT_KEY_PATH"; then
|
|
_info "Account key creation OK."
|
|
return 0
|
|
else
|
|
_err "Account key creation error."
|
|
return 1
|
|
fi
|
|
fi
|
|
|
|
}
|
|
|
|
#domain [length]
|
|
createDomainKey() {
|
|
_info "Creating domain key"
|
|
if [ -z "$1" ]; then
|
|
_usage "Usage: $PROJECT_ENTRY --create-domain-key --domain <domain.tld> [--keylength <bits>]"
|
|
return
|
|
fi
|
|
|
|
domain=$1
|
|
_cdl=$2
|
|
|
|
if [ -z "$_cdl" ]; then
|
|
_debug "Using DEFAULT_DOMAIN_KEY_LENGTH=$DEFAULT_DOMAIN_KEY_LENGTH"
|
|
_cdl="$DEFAULT_DOMAIN_KEY_LENGTH"
|
|
fi
|
|
|
|
_initpath "$domain" "$_cdl"
|
|
|
|
if [ ! -f "$CERT_KEY_PATH" ] || [ ! -s "$CERT_KEY_PATH" ] || ([ "$FORCE" ] && ! [ "$_ACME_IS_RENEW" ]) || [ "$Le_ForceNewDomainKey" = "1" ]; then
|
|
if _createkey "$_cdl" "$CERT_KEY_PATH"; then
|
|
_savedomainconf Le_Keylength "$_cdl"
|
|
_info "The domain key is here: $(__green $CERT_KEY_PATH)"
|
|
return 0
|
|
else
|
|
_err "Cannot create domain key"
|
|
return 1
|
|
fi
|
|
else
|
|
if [ "$_ACME_IS_RENEW" ]; then
|
|
_info "Domain key exists, skipping"
|
|
return 0
|
|
else
|
|
_err "Domain key exists, do you want to overwrite it?"
|
|
_err "If so, add '--force' and try again."
|
|
return 1
|
|
fi
|
|
fi
|
|
|
|
}
|
|
|
|
# domain domainlist isEcc
|
|
createCSR() {
|
|
_info "Creating CSR"
|
|
if [ -z "$1" ]; then
|
|
_usage "Usage: $PROJECT_ENTRY --create-csr --domain <domain.tld> [--domain <domain2.tld> ...] [--ecc]"
|
|
return
|
|
fi
|
|
|
|
domain="$1"
|
|
domainlist="$2"
|
|
_isEcc="$3"
|
|
|
|
_initpath "$domain" "$_isEcc"
|
|
|
|
if [ -f "$CSR_PATH" ] && [ "$_ACME_IS_RENEW" ] && [ -z "$FORCE" ]; then
|
|
_info "CSR exists, skipping"
|
|
return
|
|
fi
|
|
|
|
if [ ! -f "$CERT_KEY_PATH" ]; then
|
|
_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"
|
|
|
|
}
|
|
|
|
_url_replace() {
|
|
tr '/+' '_-' | tr -d '= '
|
|
}
|
|
|
|
#base64 string
|
|
_durl_replace_base64() {
|
|
_l=$((${#1} % 4))
|
|
if [ $_l -eq 2 ]; then
|
|
_s="$1"'=='
|
|
elif [ $_l -eq 3 ]; then
|
|
_s="$1"'='
|
|
else
|
|
_s="$1"
|
|
fi
|
|
echo "$_s" | tr '_-' '/+'
|
|
}
|
|
|
|
_time2str() {
|
|
#BSD
|
|
if date -u -r "$1" -j "+%Y-%m-%dT%H:%M:%SZ" 2>/dev/null; then
|
|
return
|
|
fi
|
|
|
|
#Linux
|
|
if date -u --date=@"$1" "+%Y-%m-%dT%H:%M:%SZ" 2>/dev/null; then
|
|
return
|
|
fi
|
|
|
|
#Solaris
|
|
if printf "%(%Y-%m-%dT%H:%M:%SZ)T\n" $1 2>/dev/null; then
|
|
return
|
|
fi
|
|
|
|
#Busybox
|
|
if echo "$1" | awk '{ print strftime("%Y-%m-%dT%H:%M:%SZ", $0); }' 2>/dev/null; then
|
|
return
|
|
fi
|
|
}
|
|
|
|
_normalizeJson() {
|
|
sed "s/\" *: *\([\"{\[]\)/\":\1/g" | sed "s/^ *\([^ ]\)/\1/" | tr -d "\r\n"
|
|
}
|
|
|
|
_stat() {
|
|
#Linux
|
|
if stat -c '%U:%G' "$1" 2>/dev/null; then
|
|
return
|
|
fi
|
|
|
|
#BSD
|
|
if stat -f '%Su:%Sg' "$1" 2>/dev/null; then
|
|
return
|
|
fi
|
|
|
|
return 1 #error, 'stat' not found
|
|
}
|
|
|
|
#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
|
|
_usage "Usage: _calcjwk keyfile"
|
|
return 1
|
|
fi
|
|
|
|
if [ "$JWK_HEADER" ] && [ "$__CACHED_JWK_KEY_FILE" = "$keyfile" ]; then
|
|
_debug2 "Use cached jwk for file: $__CACHED_JWK_KEY_FILE"
|
|
return 0
|
|
fi
|
|
|
|
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
|
|
pub_exp=0$pub_exp
|
|
fi
|
|
_debug3 pub_exp "$pub_exp"
|
|
|
|
e=$(echo "$pub_exp" | _h2b | _base64)
|
|
_debug3 e "$e"
|
|
|
|
modulus=$(${ACME_OPENSSL_BIN:-openssl} rsa -in "$keyfile" -modulus -noout | cut -d '=' -f 2)
|
|
_debug3 modulus "$modulus"
|
|
n="$(printf "%s" "$modulus" | _h2b | _base64 | _url_replace)"
|
|
_debug3 n "$n"
|
|
|
|
jwk='{"e": "'$e'", "kty": "RSA", "n": "'$n'"}'
|
|
_debug3 jwk "$jwk"
|
|
|
|
JWK_HEADER='{"alg": "RS256", "jwk": '$jwk'}'
|
|
JWK_HEADERPLACE_PART1='{"nonce": "'
|
|
JWK_HEADERPLACE_PART2='", "alg": "RS256"'
|
|
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"
|
|
__ECC_KEY_LEN=$(echo "$crv" | cut -d "-" -f 2)
|
|
if [ "$__ECC_KEY_LEN" = "521" ]; then
|
|
__ECC_KEY_LEN=512
|
|
fi
|
|
_debug3 __ECC_KEY_LEN "$__ECC_KEY_LEN"
|
|
if [ -z "$crv" ]; then
|
|
_debug "Let's try ASN1 OID"
|
|
crv_oid="$(${ACME_OPENSSL_BIN:-openssl} ec -in "$keyfile" -noout -text 2>/dev/null | grep "^ASN1 OID:" | cut -d ":" -f 2 | tr -d " \r\n")"
|
|
_debug3 crv_oid "$crv_oid"
|
|
case "${crv_oid}" in
|
|
"prime256v1")
|
|
crv="P-256"
|
|
__ECC_KEY_LEN=256
|
|
;;
|
|
"secp384r1")
|
|
crv="P-384"
|
|
__ECC_KEY_LEN=384
|
|
;;
|
|
"secp521r1")
|
|
crv="P-521"
|
|
__ECC_KEY_LEN=512
|
|
;;
|
|
*)
|
|
_err "ECC oid: $crv_oid"
|
|
return 1
|
|
;;
|
|
esac
|
|
_debug3 crv "$crv"
|
|
fi
|
|
|
|
pubi="$(${ACME_OPENSSL_BIN:-openssl} ec -in "$keyfile" -noout -text 2>/dev/null | grep -n pub: | cut -d : -f 1)"
|
|
pubi=$(_math "$pubi" + 1)
|
|
_debug3 pubi "$pubi"
|
|
|
|
pubj="$(${ACME_OPENSSL_BIN:-openssl} ec -in "$keyfile" -noout -text 2>/dev/null | grep -n "ASN1 OID:" | cut -d : -f 1)"
|
|
pubj=$(_math "$pubj" - 1)
|
|
_debug3 pubj "$pubj"
|
|
|
|
pubtext="$(${ACME_OPENSSL_BIN:-openssl} ec -in "$keyfile" -noout -text 2>/dev/null | sed -n "$pubi,${pubj}p" | tr -d " \n\r")"
|
|
_debug3 pubtext "$pubtext"
|
|
|
|
xlen="$(printf "%s" "$pubtext" | tr -d ':' | wc -c)"
|
|
xlen=$(_math "$xlen" / 4)
|
|
_debug3 xlen "$xlen"
|
|
|
|
xend=$(_math "$xlen" + 1)
|
|
x="$(printf "%s" "$pubtext" | cut -d : -f 2-"$xend")"
|
|
_debug3 x "$x"
|
|
|
|
x64="$(printf "%s" "$x" | tr -d : | _h2b | _base64 | _url_replace)"
|
|
_debug3 x64 "$x64"
|
|
|
|
xend=$(_math "$xend" + 1)
|
|
y="$(printf "%s" "$pubtext" | cut -d : -f "$xend"-2048)"
|
|
_debug3 y "$y"
|
|
|
|
y64="$(printf "%s" "$y" | tr -d : | _h2b | _base64 | _url_replace)"
|
|
_debug3 y64 "$y64"
|
|
|
|
jwk='{"crv": "'$crv'", "kty": "EC", "x": "'$x64'", "y": "'$y64'"}'
|
|
_debug3 jwk "$jwk"
|
|
|
|
JWK_HEADER='{"alg": "ES'$__ECC_KEY_LEN'", "jwk": '$jwk'}'
|
|
JWK_HEADERPLACE_PART1='{"nonce": "'
|
|
JWK_HEADERPLACE_PART2='", "alg": "ES'$__ECC_KEY_LEN'"'
|
|
else
|
|
_err "Only RSA or EC keys are supported. keyfile=$keyfile"
|
|
_debug2 "$(cat "$keyfile")"
|
|
return 1
|
|
fi
|
|
|
|
_debug3 JWK_HEADER "$JWK_HEADER"
|
|
__CACHED_JWK_KEY_FILE="$keyfile"
|
|
}
|
|
|
|
_time() {
|
|
date -u "+%s"
|
|
}
|
|
|
|
#support 2 formats:
|
|
# 2022-04-01 08:10:33 to 1648800633
|
|
#or 2022-04-01T08:10:33Z to 1648800633
|
|
_date2time() {
|
|
#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
|
|
#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
|
|
#Omnios
|
|
if da="$(echo "$1" | tr -d "Z" | tr "T" ' ')" perl -MTime::Piece -e 'print Time::Piece->strptime($ENV{da}, "%Y-%m-%d %H:%M:%S")->epoch, "\n";' 2>/dev/null; then
|
|
return
|
|
fi
|
|
_err "Cannot parse _date2time $1"
|
|
return 1
|
|
}
|
|
|
|
_utc_date() {
|
|
date -u "+%Y-%m-%d %H:%M:%S"
|
|
}
|
|
|
|
_mktemp() {
|
|
if _exists mktemp; then
|
|
if mktemp 2>/dev/null; then
|
|
return 0
|
|
elif _contains "$(mktemp 2>&1)" "-t prefix" && mktemp -t "$PROJECT_NAME" 2>/dev/null; then
|
|
#for Mac osx
|
|
return 0
|
|
fi
|
|
fi
|
|
if [ -d "/tmp" ]; then
|
|
echo "/tmp/${PROJECT_NAME}wefADf24sf.$(_time).tmp"
|
|
return 0
|
|
elif [ "$LE_TEMP_DIR" ] && mkdir -p "$LE_TEMP_DIR"; then
|
|
echo "/$LE_TEMP_DIR/wefADf24sf.$(_time).tmp"
|
|
return 0
|
|
fi
|
|
_err "Cannot create temp file."
|
|
}
|
|
|
|
#clear all the https envs to cause _inithttp() to run next time.
|
|
_resethttp() {
|
|
__HTTP_INITIALIZED=""
|
|
_ACME_CURL=""
|
|
_ACME_WGET=""
|
|
ACME_HTTP_NO_REDIRECTS=""
|
|
}
|
|
|
|
_inithttp() {
|
|
|
|
if [ -z "$HTTP_HEADER" ] || ! touch "$HTTP_HEADER"; then
|
|
HTTP_HEADER="$(_mktemp)"
|
|
_debug2 HTTP_HEADER "$HTTP_HEADER"
|
|
fi
|
|
|
|
if [ "$__HTTP_INITIALIZED" ]; then
|
|
if [ "$_ACME_CURL$_ACME_WGET" ]; then
|
|
_debug2 "Http already initialized."
|
|
return 0
|
|
fi
|
|
fi
|
|
|
|
if [ -z "$_ACME_CURL" ] && _exists "curl"; then
|
|
_ACME_CURL="curl --silent --dump-header $HTTP_HEADER "
|
|
if [ -z "$ACME_HTTP_NO_REDIRECTS" ]; then
|
|
_ACME_CURL="$_ACME_CURL -L "
|
|
fi
|
|
if [ "$DEBUG" ] && [ "$DEBUG" -ge 2 ]; then
|
|
_CURL_DUMP="$(_mktemp)"
|
|
_ACME_CURL="$_ACME_CURL --trace-ascii $_CURL_DUMP "
|
|
fi
|
|
|
|
if [ "$CA_PATH" ]; then
|
|
_ACME_CURL="$_ACME_CURL --capath $CA_PATH "
|
|
elif [ "$CA_BUNDLE" ]; then
|
|
_ACME_CURL="$_ACME_CURL --cacert $CA_BUNDLE "
|
|
fi
|
|
|
|
if _contains "$(curl --help 2>&1)" "--globoff" || _contains "$(curl --help curl 2>&1)" "--globoff"; then
|
|
_ACME_CURL="$_ACME_CURL -g "
|
|
fi
|
|
|
|
#don't use --fail-with-body
|
|
##from curl 7.76: return fail on HTTP errors but keep the body
|
|
#if _contains "$(curl --help http 2>&1)" "--fail-with-body"; then
|
|
# _ACME_CURL="$_ACME_CURL --fail-with-body "
|
|
#fi
|
|
fi
|
|
|
|
if [ -z "$_ACME_WGET" ] && _exists "wget"; then
|
|
_ACME_WGET="wget -q"
|
|
if [ "$ACME_HTTP_NO_REDIRECTS" ]; then
|
|
_ACME_WGET="$_ACME_WGET --max-redirect 0 "
|
|
fi
|
|
if [ "$DEBUG" ] && [ "$DEBUG" -ge "2" ]; then
|
|
if [ "$_ACME_WGET" ] && _contains "$($_ACME_WGET --help 2>&1)" "--debug"; then
|
|
_ACME_WGET="$_ACME_WGET -d "
|
|
fi
|
|
fi
|
|
if [ "$CA_PATH" ]; then
|
|
_ACME_WGET="$_ACME_WGET --ca-directory=$CA_PATH "
|
|
elif [ "$CA_BUNDLE" ]; then
|
|
_ACME_WGET="$_ACME_WGET --ca-certificate=$CA_BUNDLE "
|
|
fi
|
|
|
|
#from wget 1.14: do not skip body on 404 error
|
|
if _contains "$(wget --help 2>&1)" "--content-on-error"; then
|
|
_ACME_WGET="$_ACME_WGET --content-on-error "
|
|
fi
|
|
fi
|
|
|
|
__HTTP_INITIALIZED=1
|
|
|
|
}
|
|
|
|
# body url [needbase64] [POST|PUT|DELETE] [ContentType]
|
|
_post() {
|
|
body="$1"
|
|
_post_url="$2"
|
|
needbase64="$3"
|
|
httpmethod="$4"
|
|
_postContentType="$5"
|
|
|
|
if [ -z "$httpmethod" ]; then
|
|
httpmethod="POST"
|
|
fi
|
|
_debug $httpmethod
|
|
_debug "_post_url" "$_post_url"
|
|
_debug2 "body" "$body"
|
|
_debug2 "_postContentType" "$_postContentType"
|
|
|
|
_inithttp
|
|
|
|
if [ "$_ACME_CURL" ] && [ "${ACME_USE_WGET:-0}" = "0" ]; then
|
|
_CURL="$_ACME_CURL"
|
|
if [ "$HTTPS_INSECURE" ]; then
|
|
_CURL="$_CURL --insecure "
|
|
fi
|
|
if [ "$httpmethod" = "HEAD" ]; then
|
|
_CURL="$_CURL -I "
|
|
fi
|
|
_debug "_CURL" "$_CURL"
|
|
if [ "$needbase64" ]; then
|
|
if [ "$body" ]; then
|
|
if [ "$_postContentType" ]; then
|
|
response="$($_CURL --user-agent "$USER_AGENT" -X $httpmethod -H "Content-Type: $_postContentType" -H "$_H1" -H "$_H2" -H "$_H3" -H "$_H4" -H "$_H5" --data "$body" "$_post_url" | _base64)"
|
|
else
|
|
response="$($_CURL --user-agent "$USER_AGENT" -X $httpmethod -H "$_H1" -H "$_H2" -H "$_H3" -H "$_H4" -H "$_H5" --data "$body" "$_post_url" | _base64)"
|
|
fi
|
|
else
|
|
if [ "$_postContentType" ]; then
|
|
response="$($_CURL --user-agent "$USER_AGENT" -X $httpmethod -H "Content-Type: $_postContentType" -H "$_H1" -H "$_H2" -H "$_H3" -H "$_H4" -H "$_H5" "$_post_url" | _base64)"
|
|
else
|
|
response="$($_CURL --user-agent "$USER_AGENT" -X $httpmethod -H "$_H1" -H "$_H2" -H "$_H3" -H "$_H4" -H "$_H5" "$_post_url" | _base64)"
|
|
fi
|
|
fi
|
|
else
|
|
if [ "$body" ]; then
|
|
if [ "$_postContentType" ]; then
|
|
response="$($_CURL --user-agent "$USER_AGENT" -X $httpmethod -H "Content-Type: $_postContentType" -H "$_H1" -H "$_H2" -H "$_H3" -H "$_H4" -H "$_H5" --data "$body" "$_post_url")"
|
|
else
|
|
response="$($_CURL --user-agent "$USER_AGENT" -X $httpmethod -H "$_H1" -H "$_H2" -H "$_H3" -H "$_H4" -H "$_H5" --data "$body" "$_post_url")"
|
|
fi
|
|
else
|
|
if [ "$_postContentType" ]; then
|
|
response="$($_CURL --user-agent "$USER_AGENT" -X $httpmethod -H "Content-Type: $_postContentType" -H "$_H1" -H "$_H2" -H "$_H3" -H "$_H4" -H "$_H5" "$_post_url")"
|
|
else
|
|
response="$($_CURL --user-agent "$USER_AGENT" -X $httpmethod -H "$_H1" -H "$_H2" -H "$_H3" -H "$_H4" -H "$_H5" "$_post_url")"
|
|
fi
|
|
fi
|
|
fi
|
|
_ret="$?"
|
|
if [ "$_ret" != "0" ]; then
|
|
_err "Please refer to https://curl.haxx.se/libcurl/c/libcurl-errors.html for error code: $_ret"
|
|
if [ "$DEBUG" ] && [ "$DEBUG" -ge "2" ]; then
|
|
_err "Here is the curl dump log:"
|
|
_err "$(cat "$_CURL_DUMP")"
|
|
fi
|
|
fi
|
|
elif [ "$_ACME_WGET" ]; then
|
|
_WGET="$_ACME_WGET"
|
|
if [ "$HTTPS_INSECURE" ]; then
|
|
_WGET="$_WGET --no-check-certificate "
|
|
fi
|
|
if [ "$httpmethod" = "HEAD" ]; then
|
|
_WGET="$_WGET --read-timeout=3.0 --tries=2 "
|
|
fi
|
|
_debug "_WGET" "$_WGET"
|
|
if [ "$needbase64" ]; then
|
|
if [ "$httpmethod" = "POST" ]; then
|
|
if [ "$_postContentType" ]; then
|
|
response="$($_WGET -S -O - --user-agent="$USER_AGENT" --header "$_H5" --header "$_H4" --header "$_H3" --header "$_H2" --header "$_H1" --header "Content-Type: $_postContentType" --post-data="$body" "$_post_url" 2>"$HTTP_HEADER" | _base64)"
|
|
else
|
|
response="$($_WGET -S -O - --user-agent="$USER_AGENT" --header "$_H5" --header "$_H4" --header "$_H3" --header "$_H2" --header "$_H1" --post-data="$body" "$_post_url" 2>"$HTTP_HEADER" | _base64)"
|
|
fi
|
|
else
|
|
if [ "$_postContentType" ]; then
|
|
response="$($_WGET -S -O - --user-agent="$USER_AGENT" --header "$_H5" --header "$_H4" --header "$_H3" --header "$_H2" --header "$_H1" --header "Content-Type: $_postContentType" --method $httpmethod --body-data="$body" "$_post_url" 2>"$HTTP_HEADER" | _base64)"
|
|
else
|
|
response="$($_WGET -S -O - --user-agent="$USER_AGENT" --header "$_H5" --header "$_H4" --header "$_H3" --header "$_H2" --header "$_H1" --method $httpmethod --body-data="$body" "$_post_url" 2>"$HTTP_HEADER" | _base64)"
|
|
fi
|
|
fi
|
|
else
|
|
if [ "$httpmethod" = "POST" ]; then
|
|
if [ "$_postContentType" ]; then
|
|
response="$($_WGET -S -O - --user-agent="$USER_AGENT" --header "$_H5" --header "$_H4" --header "$_H3" --header "$_H2" --header "$_H1" --header "Content-Type: $_postContentType" --post-data="$body" "$_post_url" 2>"$HTTP_HEADER")"
|
|
else
|
|
response="$($_WGET -S -O - --user-agent="$USER_AGENT" --header "$_H5" --header "$_H4" --header "$_H3" --header "$_H2" --header "$_H1" --post-data="$body" "$_post_url" 2>"$HTTP_HEADER")"
|
|
fi
|
|
elif [ "$httpmethod" = "HEAD" ]; then
|
|
if [ "$_postContentType" ]; then
|
|
response="$($_WGET --spider -S -O - --user-agent="$USER_AGENT" --header "$_H5" --header "$_H4" --header "$_H3" --header "$_H2" --header "$_H1" --header "Content-Type: $_postContentType" --post-data="$body" "$_post_url" 2>"$HTTP_HEADER")"
|
|
else
|
|
response="$($_WGET --spider -S -O - --user-agent="$USER_AGENT" --header "$_H5" --header "$_H4" --header "$_H3" --header "$_H2" --header "$_H1" --post-data="$body" "$_post_url" 2>"$HTTP_HEADER")"
|
|
fi
|
|
else
|
|
if [ "$_postContentType" ]; then
|
|
response="$($_WGET -S -O - --user-agent="$USER_AGENT" --header "$_H5" --header "$_H4" --header "$_H3" --header "$_H2" --header "$_H1" --header "Content-Type: $_postContentType" --method $httpmethod --body-data="$body" "$_post_url" 2>"$HTTP_HEADER")"
|
|
else
|
|
response="$($_WGET -S -O - --user-agent="$USER_AGENT" --header "$_H5" --header "$_H4" --header "$_H3" --header "$_H2" --header "$_H1" --method $httpmethod --body-data="$body" "$_post_url" 2>"$HTTP_HEADER")"
|
|
fi
|
|
fi
|
|
fi
|
|
_ret="$?"
|
|
if [ "$_ret" = "8" ]; then
|
|
_ret=0
|
|
_debug "wget returned 8 as the server returned a 'Bad Request' response. Let's process the response later."
|
|
fi
|
|
if [ "$_ret" != "0" ]; then
|
|
_err "Please refer to https://www.gnu.org/software/wget/manual/html_node/Exit-Status.html for error code: $_ret"
|
|
fi
|
|
if _contains "$_WGET" " -d "; then
|
|
# Demultiplex wget debug output
|
|
cat "$HTTP_HEADER" >&2
|
|
_sed_i '/^[^ ][^ ]/d; /^ *$/d' "$HTTP_HEADER"
|
|
fi
|
|
# remove leading whitespaces from header to match curl format
|
|
_sed_i 's/^ //g' "$HTTP_HEADER"
|
|
else
|
|
_ret="$?"
|
|
_err "Neither curl nor wget have been found, cannot make $httpmethod request."
|
|
fi
|
|
_debug "_ret" "$_ret"
|
|
printf "%s" "$response"
|
|
return $_ret
|
|
}
|
|
|
|
# url getheader timeout
|
|
_get() {
|
|
_debug GET
|
|
url="$1"
|
|
onlyheader="$2"
|
|
t="$3"
|
|
_debug url "$url"
|
|
_debug "timeout=$t"
|
|
|
|
_inithttp
|
|
|
|
if [ "$_ACME_CURL" ] && [ "${ACME_USE_WGET:-0}" = "0" ]; then
|
|
_CURL="$_ACME_CURL"
|
|
if [ "$HTTPS_INSECURE" ]; then
|
|
_CURL="$_CURL --insecure "
|
|
fi
|
|
if [ "$t" ]; then
|
|
_CURL="$_CURL --connect-timeout $t"
|
|
fi
|
|
_debug "_CURL" "$_CURL"
|
|
if [ "$onlyheader" ]; then
|
|
$_CURL -I --user-agent "$USER_AGENT" -H "$_H1" -H "$_H2" -H "$_H3" -H "$_H4" -H "$_H5" "$url"
|
|
else
|
|
$_CURL --user-agent "$USER_AGENT" -H "$_H1" -H "$_H2" -H "$_H3" -H "$_H4" -H "$_H5" "$url"
|
|
fi
|
|
ret=$?
|
|
if [ "$ret" != "0" ]; then
|
|
_err "Please refer to https://curl.haxx.se/libcurl/c/libcurl-errors.html for error code: $ret"
|
|
if [ "$DEBUG" ] && [ "$DEBUG" -ge "2" ]; then
|
|
_err "Here is the curl dump log:"
|
|
_err "$(cat "$_CURL_DUMP")"
|
|
fi
|
|
fi
|
|
elif [ "$_ACME_WGET" ]; then
|
|
_WGET="$_ACME_WGET"
|
|
if [ "$HTTPS_INSECURE" ]; then
|
|
_WGET="$_WGET --no-check-certificate "
|
|
fi
|
|
if [ "$t" ]; then
|
|
_WGET="$_WGET --timeout=$t"
|
|
fi
|
|
_debug "_WGET" "$_WGET"
|
|
if [ "$onlyheader" ]; then
|
|
_wget_out="$($_WGET --user-agent="$USER_AGENT" --header "$_H5" --header "$_H4" --header "$_H3" --header "$_H2" --header "$_H1" -S -O /dev/null "$url" 2>&1)"
|
|
if _contains "$_WGET" " -d "; then
|
|
# Demultiplex wget debug output
|
|
echo "$_wget_out" >&2
|
|
echo "$_wget_out" | sed '/^[^ ][^ ]/d; /^ *$/d; s/^ //g' -
|
|
fi
|
|
else
|
|
$_WGET --user-agent="$USER_AGENT" --header "$_H5" --header "$_H4" --header "$_H3" --header "$_H2" --header "$_H1" -S -O - "$url" 2>"$HTTP_HEADER"
|
|
if _contains "$_WGET" " -d "; then
|
|
# Demultiplex wget debug output
|
|
cat "$HTTP_HEADER" >&2
|
|
_sed_i '/^[^ ][^ ]/d; /^ *$/d' "$HTTP_HEADER"
|
|
fi
|
|
# remove leading whitespaces from header to match curl format
|
|
_sed_i 's/^ //g' "$HTTP_HEADER"
|
|
fi
|
|
ret=$?
|
|
if [ "$ret" = "8" ]; then
|
|
ret=0
|
|
_debug "wget returned 8 as the server returned a 'Bad Request' response. Let's process the response later."
|
|
fi
|
|
if [ "$ret" != "0" ]; then
|
|
_err "Please refer to https://www.gnu.org/software/wget/manual/html_node/Exit-Status.html for error code: $ret"
|
|
fi
|
|
else
|
|
ret=$?
|
|
_err "Neither curl nor wget have been found, cannot make GET request."
|
|
fi
|
|
_debug "ret" "$ret"
|
|
return $ret
|
|
}
|
|
|
|
_head_n() {
|
|
head -n "$1"
|
|
}
|
|
|
|
_tail_n() {
|
|
if _is_solaris; then
|
|
#fix for solaris
|
|
tail -"$1"
|
|
else
|
|
tail -n "$1"
|
|
fi
|
|
}
|
|
|
|
_tail_c() {
|
|
tail -c "$1" 2>/dev/null || tail -"$1"c
|
|
}
|
|
|
|
# url payload needbase64 keyfile
|
|
_send_signed_request() {
|
|
url=$1
|
|
payload=$2
|
|
needbase64=$3
|
|
keyfile=$4
|
|
if [ -z "$keyfile" ]; then
|
|
keyfile="$ACCOUNT_KEY_PATH"
|
|
fi
|
|
_debug "=======Sending Signed Request======="
|
|
_debug url "$url"
|
|
_debug payload "$payload"
|
|
|
|
if ! _calcjwk "$keyfile"; then
|
|
return 1
|
|
fi
|
|
|
|
__request_conent_type="$CONTENT_TYPE_JSON"
|
|
|
|
payload64=$(printf "%s" "$payload" | _base64 | _url_replace)
|
|
_debug3 payload64 "$payload64"
|
|
|
|
MAX_REQUEST_RETRY_TIMES=20
|
|
_sleep_retry_sec=1
|
|
_request_retry_times=0
|
|
while [ "${_request_retry_times}" -lt "$MAX_REQUEST_RETRY_TIMES" ]; do
|
|
_request_retry_times=$(_math "$_request_retry_times" + 1)
|
|
_debug3 _request_retry_times "$_request_retry_times"
|
|
if [ -z "$_CACHED_NONCE" ]; then
|
|
_headers=""
|
|
if [ "$ACME_NEW_NONCE" ]; then
|
|
_debug2 "Get nonce with HEAD. ACME_NEW_NONCE" "$ACME_NEW_NONCE"
|
|
nonceurl="$ACME_NEW_NONCE"
|
|
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 | cut -d , -f 1)"
|
|
fi
|
|
fi
|
|
if [ -z "$_CACHED_NONCE" ]; then
|
|
_debug2 "Get nonce with GET. ACME_DIRECTORY" "$ACME_DIRECTORY"
|
|
nonceurl="$ACME_DIRECTORY"
|
|
_headers="$(_get "$nonceurl" "onlyheader")"
|
|
_debug2 _headers "$_headers"
|
|
_CACHED_NONCE="$(echo "$_headers" | grep -i "Replay-Nonce:" | _head_n 1 | tr -d "\r\n " | cut -d ':' -f 2)"
|
|
fi
|
|
if [ -z "$_CACHED_NONCE" ] && [ "$ACME_NEW_NONCE" ]; then
|
|
_debug2 "Get nonce with GET. ACME_NEW_NONCE" "$ACME_NEW_NONCE"
|
|
nonceurl="$ACME_NEW_NONCE"
|
|
_headers="$(_get "$nonceurl" "onlyheader")"
|
|
_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 "Cannot connect to $nonceurl to get nonce."
|
|
return 1
|
|
fi
|
|
else
|
|
_debug2 "Use _CACHED_NONCE" "$_CACHED_NONCE"
|
|
fi
|
|
nonce="$_CACHED_NONCE"
|
|
_debug2 nonce "$nonce"
|
|
if [ -z "$nonce" ]; then
|
|
_info "Could not get nonce, let's try again."
|
|
_sleep 2
|
|
continue
|
|
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, \"kid\": \"${ACCOUNT_URL}\""'}'
|
|
fi
|
|
|
|
_debug3 protected "$protected"
|
|
|
|
protected64="$(printf "%s" "$protected" | _base64 | _url_replace)"
|
|
_debug3 protected64 "$protected64"
|
|
|
|
if ! _sig_t="$(printf "%s" "$protected64.$payload64" | _sign "$keyfile" "sha256")"; then
|
|
_err "Sign request failed."
|
|
return 1
|
|
fi
|
|
_debug3 _sig_t "$_sig_t"
|
|
|
|
sig="$(printf "%s" "$_sig_t" | _url_replace)"
|
|
_debug3 sig "$sig"
|
|
|
|
body="{\"protected\": \"$protected64\", \"payload\": \"$payload64\", \"signature\": \"$sig\"}"
|
|
_debug3 body "$body"
|
|
|
|
response="$(_post "$body" "$url" "$needbase64" "POST" "$__request_conent_type")"
|
|
_CACHED_NONCE=""
|
|
|
|
if [ "$?" != "0" ]; then
|
|
_err "Cannot make POST request to $url"
|
|
return 1
|
|
fi
|
|
|
|
responseHeaders="$(cat "$HTTP_HEADER")"
|
|
_debug2 responseHeaders "$responseHeaders"
|
|
|
|
code="$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d " " -f 2 | tr -d "\r\n")"
|
|
_debug code "$code"
|
|
|
|
_debug2 original "$response"
|
|
if echo "$responseHeaders" | grep -i "Content-Type: *application/json" >/dev/null 2>&1; then
|
|
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 | cut -d , -f 1)"
|
|
|
|
if ! _startswith "$code" "2"; then
|
|
_body="$response"
|
|
if [ "$needbase64" ]; then
|
|
_body="$(echo "$_body" | _dbase64 multiline)"
|
|
_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 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
|
|
fi
|
|
fi
|
|
return 0
|
|
done
|
|
_info "Giving up sending to CA server after $MAX_REQUEST_RETRY_TIMES retries."
|
|
return 1
|
|
|
|
}
|
|
|
|
#setopt "file" "opt" "=" "value" [";"]
|
|
_setopt() {
|
|
__conf="$1"
|
|
__opt="$2"
|
|
__sep="$3"
|
|
__val="$4"
|
|
__end="$5"
|
|
if [ -z "$__opt" ]; then
|
|
_usage usage: _setopt '"file" "opt" "=" "value" [";"]'
|
|
return
|
|
fi
|
|
if [ ! -f "$__conf" ]; then
|
|
touch "$__conf"
|
|
fi
|
|
if [ -n "$(_tail_c 1 <"$__conf")" ]; then
|
|
echo >>"$__conf"
|
|
fi
|
|
|
|
if grep -n "^$__opt$__sep" "$__conf" >/dev/null; then
|
|
_debug3 OK
|
|
if _contains "$__val" "&"; then
|
|
__val="$(echo "$__val" | sed 's/&/\\&/g')"
|
|
fi
|
|
if _contains "$__val" "|"; then
|
|
__val="$(echo "$__val" | sed 's/|/\\|/g')"
|
|
fi
|
|
text="$(cat "$__conf")"
|
|
printf -- "%s\n" "$text" | sed "s|^$__opt$__sep.*$|$__opt$__sep$__val$__end|" >"$__conf"
|
|
|
|
elif grep -n "^#$__opt$__sep" "$__conf" >/dev/null; then
|
|
if _contains "$__val" "&"; then
|
|
__val="$(echo "$__val" | sed 's/&/\\&/g')"
|
|
fi
|
|
if _contains "$__val" "|"; then
|
|
__val="$(echo "$__val" | sed 's/|/\\|/g')"
|
|
fi
|
|
text="$(cat "$__conf")"
|
|
printf -- "%s\n" "$text" | sed "s|^#$__opt$__sep.*$|$__opt$__sep$__val$__end|" >"$__conf"
|
|
|
|
else
|
|
_debug3 APP
|
|
echo "$__opt$__sep$__val$__end" >>"$__conf"
|
|
fi
|
|
_debug3 "$(grep -n "^$__opt$__sep" "$__conf")"
|
|
}
|
|
|
|
#_save_conf file key value base64encode
|
|
#save to conf
|
|
_save_conf() {
|
|
_s_c_f="$1"
|
|
_sdkey="$2"
|
|
_sdvalue="$3"
|
|
_b64encode="$4"
|
|
if [ "$_sdvalue" ] && [ "$_b64encode" ]; then
|
|
_sdvalue="${B64CONF_START}$(printf "%s" "${_sdvalue}" | _base64)${B64CONF_END}"
|
|
fi
|
|
if [ "$_s_c_f" ]; then
|
|
_setopt "$_s_c_f" "$_sdkey" "=" "'$_sdvalue'"
|
|
else
|
|
_err "Config file is empty, cannot save $_sdkey=$_sdvalue"
|
|
fi
|
|
}
|
|
|
|
#_clear_conf file key
|
|
_clear_conf() {
|
|
_c_c_f="$1"
|
|
_sdkey="$2"
|
|
if [ "$_c_c_f" ]; then
|
|
_conf_data="$(cat "$_c_c_f")"
|
|
echo "$_conf_data" | sed "s/^$_sdkey *=.*$//" >"$_c_c_f"
|
|
else
|
|
_err "Config file is empty, cannot clear"
|
|
fi
|
|
}
|
|
|
|
#_read_conf file key
|
|
_read_conf() {
|
|
_r_c_f="$1"
|
|
_sdkey="$2"
|
|
if [ -f "$_r_c_f" ]; then
|
|
_sdv="$(
|
|
eval "$(grep "^$_sdkey *=" "$_r_c_f")"
|
|
eval "printf \"%s\" \"\$$_sdkey\""
|
|
)"
|
|
if _startswith "$_sdv" "${B64CONF_START}" && _endswith "$_sdv" "${B64CONF_END}"; then
|
|
_sdv="$(echo "$_sdv" | sed "s/${B64CONF_START}//" | sed "s/${B64CONF_END}//" | _dbase64)"
|
|
fi
|
|
printf "%s" "$_sdv"
|
|
else
|
|
_debug "Config file is empty, cannot read $_sdkey"
|
|
fi
|
|
}
|
|
|
|
#_savedomainconf key value base64encode
|
|
#save to domain.conf
|
|
_savedomainconf() {
|
|
_save_conf "$DOMAIN_CONF" "$@"
|
|
}
|
|
|
|
#_cleardomainconf key
|
|
_cleardomainconf() {
|
|
_clear_conf "$DOMAIN_CONF" "$1"
|
|
}
|
|
|
|
#_readdomainconf key
|
|
_readdomainconf() {
|
|
_read_conf "$DOMAIN_CONF" "$1"
|
|
}
|
|
|
|
#_migratedomainconf oldkey newkey base64encode
|
|
_migratedomainconf() {
|
|
_old_key="$1"
|
|
_new_key="$2"
|
|
_b64encode="$3"
|
|
_old_value=$(_readdomainconf "$_old_key")
|
|
_cleardomainconf "$_old_key"
|
|
if [ -z "$_old_value" ]; then
|
|
return 1 # migrated failed: old value is empty
|
|
fi
|
|
_new_value=$(_readdomainconf "$_new_key")
|
|
if [ -n "$_new_value" ]; then
|
|
_debug "Domain config new key exists, old key $_old_key='$_old_value' has been removed."
|
|
return 1 # migrated failed: old value replaced by new value
|
|
fi
|
|
_savedomainconf "$_new_key" "$_old_value" "$_b64encode"
|
|
_debug "Domain config $_old_key has been migrated to $_new_key."
|
|
}
|
|
|
|
#_migratedeployconf oldkey newkey base64encode
|
|
_migratedeployconf() {
|
|
_migratedomainconf "$1" "SAVED_$2" "$3" ||
|
|
_migratedomainconf "SAVED_$1" "SAVED_$2" "$3" # try only when oldkey itself is not found
|
|
}
|
|
|
|
#key value base64encode
|
|
_savedeployconf() {
|
|
_savedomainconf "SAVED_$1" "$2" "$3"
|
|
#remove later
|
|
_cleardomainconf "$1"
|
|
}
|
|
|
|
#key
|
|
_getdeployconf() {
|
|
_rac_key="$1"
|
|
_rac_value="$(eval echo \$"$_rac_key")"
|
|
if [ "$_rac_value" ]; then
|
|
if _startswith "$_rac_value" '"' && _endswith "$_rac_value" '"'; then
|
|
_debug2 "trim quotation marks"
|
|
eval $_rac_key=$_rac_value
|
|
export $_rac_key
|
|
fi
|
|
return 0 # do nothing
|
|
fi
|
|
_saved="$(_readdomainconf "SAVED_$_rac_key")"
|
|
eval $_rac_key=\$_saved
|
|
export $_rac_key
|
|
}
|
|
|
|
#_saveaccountconf key value base64encode
|
|
_saveaccountconf() {
|
|
_save_conf "$ACCOUNT_CONF_PATH" "$@"
|
|
}
|
|
|
|
#key value base64encode
|
|
_saveaccountconf_mutable() {
|
|
_save_conf "$ACCOUNT_CONF_PATH" "SAVED_$1" "$2" "$3"
|
|
#remove later
|
|
_clearaccountconf "$1"
|
|
}
|
|
|
|
#key
|
|
_readaccountconf() {
|
|
_read_conf "$ACCOUNT_CONF_PATH" "$1"
|
|
}
|
|
|
|
#key
|
|
_readaccountconf_mutable() {
|
|
_rac_key="$1"
|
|
_readaccountconf "SAVED_$_rac_key"
|
|
}
|
|
|
|
#_clearaccountconf key
|
|
_clearaccountconf() {
|
|
_clear_conf "$ACCOUNT_CONF_PATH" "$1"
|
|
}
|
|
|
|
#key
|
|
_clearaccountconf_mutable() {
|
|
_clearaccountconf "SAVED_$1"
|
|
#remove later
|
|
_clearaccountconf "$1"
|
|
}
|
|
|
|
#_savecaconf key value
|
|
_savecaconf() {
|
|
_save_conf "$CA_CONF" "$1" "$2"
|
|
}
|
|
|
|
#_readcaconf key
|
|
_readcaconf() {
|
|
_read_conf "$CA_CONF" "$1"
|
|
}
|
|
|
|
#_clearaccountconf key
|
|
_clearcaconf() {
|
|
_clear_conf "$CA_CONF" "$1"
|
|
}
|
|
|
|
# content localaddress
|
|
_startserver() {
|
|
content="$1"
|
|
ncaddr="$2"
|
|
_debug "content" "$content"
|
|
_debug "ncaddr" "$ncaddr"
|
|
|
|
_debug "startserver: $$"
|
|
|
|
_debug Le_HTTPPort "$Le_HTTPPort"
|
|
_debug Le_Listen_V4 "$Le_Listen_V4"
|
|
_debug Le_Listen_V6 "$Le_Listen_V6"
|
|
|
|
_NC="socat"
|
|
if [ "$Le_Listen_V6" ]; then
|
|
_NC="$_NC -6"
|
|
else
|
|
_NC="$_NC -4"
|
|
fi
|
|
|
|
if [ "$DEBUG" ] && [ "$DEBUG" -gt "1" ]; then
|
|
_NC="$_NC -d -d -v"
|
|
fi
|
|
|
|
SOCAT_OPTIONS=TCP-LISTEN:$Le_HTTPPort,crlf,reuseaddr,fork
|
|
|
|
#Adding bind to local-address
|
|
if [ "$ncaddr" ]; then
|
|
SOCAT_OPTIONS="$SOCAT_OPTIONS,bind=${ncaddr}"
|
|
fi
|
|
|
|
_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 '%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"
|
|
|
|
}
|
|
|
|
# sleep sec
|
|
_sleep() {
|
|
_sleep_sec="$1"
|
|
if [ "$__INTERACTIVE" ]; then
|
|
_sleep_c="$_sleep_sec"
|
|
while [ "$_sleep_c" -ge "0" ]; do
|
|
printf "\r \r"
|
|
__green "$_sleep_c"
|
|
_sleep_c="$(_math "$_sleep_c" - 1)"
|
|
sleep 1
|
|
done
|
|
printf "\r"
|
|
else
|
|
sleep "$_sleep_sec"
|
|
fi
|
|
}
|
|
|
|
# _starttlsserver san_a san_b port content _ncaddr acmeValidationv1
|
|
_starttlsserver() {
|
|
_info "Starting tls server."
|
|
san_a="$1"
|
|
san_b="$2"
|
|
port="$3"
|
|
content="$4"
|
|
opaddr="$5"
|
|
acmeValidationv1="$6"
|
|
|
|
_debug san_a "$san_a"
|
|
_debug san_b "$san_b"
|
|
_debug port "$port"
|
|
_debug acmeValidationv1 "$acmeValidationv1"
|
|
|
|
#create key TLS_KEY
|
|
if ! _createkey "2048" "$TLS_KEY"; then
|
|
_err "Error creating TLS validation key."
|
|
return 1
|
|
fi
|
|
|
|
#create csr
|
|
alt="$san_a"
|
|
if [ "$san_b" ]; then
|
|
alt="$alt,$san_b"
|
|
fi
|
|
if ! _createcsr "tls.acme.sh" "$alt" "$TLS_KEY" "$TLS_CSR" "$TLS_CONF" "$acmeValidationv1"; then
|
|
_err "Error creating TLS validation CSR."
|
|
return 1
|
|
fi
|
|
|
|
#self signed
|
|
if ! _signcsr "$TLS_KEY" "$TLS_CSR" "$TLS_CONF" "$TLS_CERT"; then
|
|
_err "Error creating TLS validation cert."
|
|
return 1
|
|
fi
|
|
|
|
__S_OPENSSL="${ACME_OPENSSL_BIN:-openssl} s_server -www -cert $TLS_CERT -key $TLS_KEY "
|
|
if [ "$opaddr" ]; then
|
|
__S_OPENSSL="$__S_OPENSSL -accept $opaddr:$port"
|
|
else
|
|
__S_OPENSSL="$__S_OPENSSL -accept $port"
|
|
fi
|
|
|
|
_debug Le_Listen_V4 "$Le_Listen_V4"
|
|
_debug Le_Listen_V6 "$Le_Listen_V6"
|
|
if [ "$Le_Listen_V4" ]; then
|
|
__S_OPENSSL="$__S_OPENSSL -4"
|
|
elif [ "$Le_Listen_V6" ]; then
|
|
__S_OPENSSL="$__S_OPENSSL -6"
|
|
fi
|
|
|
|
if [ "$acmeValidationv1" ]; then
|
|
__S_OPENSSL="$__S_OPENSSL -alpn acme-tls/1"
|
|
fi
|
|
|
|
_debug "$__S_OPENSSL"
|
|
if [ "$DEBUG" ] && [ "$DEBUG" -ge "2" ]; then
|
|
$__S_OPENSSL -tlsextdebug &
|
|
else
|
|
$__S_OPENSSL >/dev/null 2>&1 &
|
|
fi
|
|
|
|
serverproc="$!"
|
|
sleep 1
|
|
_debug serverproc "$serverproc"
|
|
}
|
|
|
|
#file
|
|
_readlink() {
|
|
_rf="$1"
|
|
if ! readlink -f "$_rf" 2>/dev/null; then
|
|
if _startswith "$_rf" "/"; then
|
|
echo "$_rf"
|
|
return 0
|
|
fi
|
|
echo "$(pwd)/$_rf" | _conapath
|
|
fi
|
|
}
|
|
|
|
_conapath() {
|
|
sed "s#/\./#/#g"
|
|
}
|
|
|
|
__initHome() {
|
|
if [ -z "$_SCRIPT_HOME" ]; then
|
|
if _exists readlink && _exists dirname; then
|
|
_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
|
|
export _SCRIPT_HOME="$_script_home"
|
|
else
|
|
_err "It seems the script home is not correct: $_script_home"
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
# if [ -z "$LE_WORKING_DIR" ]; then
|
|
# if [ -f "$DEFAULT_INSTALL_HOME/account.conf" ]; then
|
|
# _debug "It seems that $PROJECT_NAME is already installed in $DEFAULT_INSTALL_HOME"
|
|
# LE_WORKING_DIR="$DEFAULT_INSTALL_HOME"
|
|
# else
|
|
# LE_WORKING_DIR="$_SCRIPT_HOME"
|
|
# fi
|
|
# fi
|
|
|
|
if [ -z "$LE_WORKING_DIR" ]; then
|
|
_debug "Using default home: $DEFAULT_INSTALL_HOME"
|
|
LE_WORKING_DIR="$DEFAULT_INSTALL_HOME"
|
|
fi
|
|
export LE_WORKING_DIR
|
|
|
|
if [ -z "$LE_CONFIG_HOME" ]; then
|
|
LE_CONFIG_HOME="$LE_WORKING_DIR"
|
|
fi
|
|
_debug "Using config home: $LE_CONFIG_HOME"
|
|
export LE_CONFIG_HOME
|
|
|
|
_DEFAULT_ACCOUNT_CONF_PATH="$LE_CONFIG_HOME/account.conf"
|
|
|
|
if [ -z "$ACCOUNT_CONF_PATH" ]; then
|
|
if [ -f "$_DEFAULT_ACCOUNT_CONF_PATH" ]; then
|
|
. "$_DEFAULT_ACCOUNT_CONF_PATH"
|
|
fi
|
|
fi
|
|
|
|
if [ -z "$ACCOUNT_CONF_PATH" ]; then
|
|
ACCOUNT_CONF_PATH="$_DEFAULT_ACCOUNT_CONF_PATH"
|
|
fi
|
|
_debug3 ACCOUNT_CONF_PATH "$ACCOUNT_CONF_PATH"
|
|
DEFAULT_LOG_FILE="$LE_CONFIG_HOME/$PROJECT_NAME.log"
|
|
|
|
DEFAULT_CA_HOME="$LE_CONFIG_HOME/ca"
|
|
|
|
if [ -z "$LE_TEMP_DIR" ]; then
|
|
LE_TEMP_DIR="$LE_CONFIG_HOME/tmp"
|
|
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"
|
|
|
|
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")
|
|
if [ "$?" != "0" ]; then
|
|
_debug2 "response" "$response"
|
|
_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 'keyChange" *: *"[^"]*"' | cut -d '"' -f 3)
|
|
export ACME_KEY_CHANGE
|
|
|
|
ACME_NEW_AUTHZ=$(echo "$response" | _egrep_o 'newAuthz" *: *"[^"]*"' | cut -d '"' -f 3)
|
|
export ACME_NEW_AUTHZ
|
|
|
|
ACME_NEW_ORDER=$(echo "$response" | _egrep_o 'newOrder" *: *"[^"]*"' | cut -d '"' -f 3)
|
|
export ACME_NEW_ORDER
|
|
|
|
ACME_NEW_ACCOUNT=$(echo "$response" | _egrep_o 'newAccount" *: *"[^"]*"' | cut -d '"' -f 3)
|
|
export ACME_NEW_ACCOUNT
|
|
|
|
ACME_REVOKE_CERT=$(echo "$response" | _egrep_o 'revokeCert" *: *"[^"]*"' | cut -d '"' -f 3)
|
|
export ACME_REVOKE_CERT
|
|
|
|
ACME_NEW_NONCE=$(echo "$response" | _egrep_o 'newNonce" *: *"[^"]*"' | cut -d '"' -f 3)
|
|
export ACME_NEW_NONCE
|
|
|
|
ACME_AGREEMENT=$(echo "$response" | _egrep_o 'termsOfService" *: *"[^"]*"' | cut -d '"' -f 3)
|
|
export ACME_AGREEMENT
|
|
|
|
_debug "ACME_KEY_CHANGE" "$ACME_KEY_CHANGE"
|
|
_debug "ACME_NEW_AUTHZ" "$ACME_NEW_AUTHZ"
|
|
_debug "ACME_NEW_ORDER" "$ACME_NEW_ORDER"
|
|
_debug "ACME_NEW_ACCOUNT" "$ACME_NEW_ACCOUNT"
|
|
_debug "ACME_REVOKE_CERT" "$ACME_REVOKE_CERT"
|
|
_debug "ACME_AGREEMENT" "$ACME_AGREEMENT"
|
|
_debug "ACME_NEW_NONCE" "$ACME_NEW_NONCE"
|
|
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]
|
|
_initpath() {
|
|
domain="$1"
|
|
_ilength="$2"
|
|
|
|
__initHome
|
|
|
|
if [ -f "$ACCOUNT_CONF_PATH" ]; then
|
|
. "$ACCOUNT_CONF_PATH"
|
|
fi
|
|
|
|
if [ "$_ACME_IN_CRON" ]; then
|
|
if [ ! "$_USER_PATH_EXPORTED" ]; then
|
|
_USER_PATH_EXPORTED=1
|
|
export PATH="$USER_PATH:$PATH"
|
|
fi
|
|
fi
|
|
|
|
if [ -z "$CA_HOME" ]; then
|
|
CA_HOME="$DEFAULT_CA_HOME"
|
|
fi
|
|
|
|
if [ -z "$ACME_DIRECTORY" ]; then
|
|
if [ "$STAGE" ]; then
|
|
ACME_DIRECTORY="$DEFAULT_STAGING_CA"
|
|
_info "Using ACME_DIRECTORY: $ACME_DIRECTORY"
|
|
else
|
|
default_acme_server=$(_readaccountconf "DEFAULT_ACME_SERVER")
|
|
_debug default_acme_server "$default_acme_server"
|
|
if [ "$default_acme_server" ]; then
|
|
ACME_DIRECTORY="$default_acme_server"
|
|
else
|
|
ACME_DIRECTORY="$DEFAULT_CA"
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
_debug ACME_DIRECTORY "$ACME_DIRECTORY"
|
|
_ACME_SERVER_HOST="$(echo "$ACME_DIRECTORY" | cut -d : -f 2 | tr -s / | cut -d / -f 2)"
|
|
_debug2 "_ACME_SERVER_HOST" "$_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
|
|
|
|
if [ -z "$ACME_DIR" ]; then
|
|
ACME_DIR="/home/.acme"
|
|
fi
|
|
|
|
if [ -z "$APACHE_CONF_BACKUP_DIR" ]; then
|
|
APACHE_CONF_BACKUP_DIR="$LE_CONFIG_HOME"
|
|
fi
|
|
|
|
if [ -z "$USER_AGENT" ]; then
|
|
USER_AGENT="$DEFAULT_USER_AGENT"
|
|
fi
|
|
|
|
if [ -z "$HTTP_HEADER" ]; then
|
|
HTTP_HEADER="$LE_CONFIG_HOME/http.header"
|
|
fi
|
|
|
|
_DEFAULT_CERT_HOME="$LE_CONFIG_HOME"
|
|
if [ -z "$CERT_HOME" ]; then
|
|
CERT_HOME="$_DEFAULT_CERT_HOME"
|
|
fi
|
|
|
|
if [ -z "$ACME_OPENSSL_BIN" ] || [ ! -f "$ACME_OPENSSL_BIN" ] || [ ! -x "$ACME_OPENSSL_BIN" ]; then
|
|
ACME_OPENSSL_BIN="$DEFAULT_OPENSSL_BIN"
|
|
fi
|
|
|
|
if [ -z "$domain" ]; then
|
|
return 0
|
|
fi
|
|
|
|
if [ -z "$DOMAIN_PATH" ]; then
|
|
domainhome="$CERT_HOME/$domain"
|
|
domainhomeecc="$CERT_HOME/$domain$ECC_SUFFIX"
|
|
|
|
DOMAIN_PATH="$domainhome"
|
|
|
|
if _isEccKey "$_ilength"; then
|
|
DOMAIN_PATH="$domainhomeecc"
|
|
elif [ -z "$__SELECTED_RSA_KEY" ]; then
|
|
if [ ! -d "$domainhome" ] && [ -d "$domainhomeecc" ]; then
|
|
_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
|
|
DOMAIN_BACKUP_PATH="$DOMAIN_PATH/backup"
|
|
fi
|
|
|
|
if [ -z "$DOMAIN_CONF" ]; then
|
|
DOMAIN_CONF="$DOMAIN_PATH/$domain.conf"
|
|
fi
|
|
|
|
if [ -z "$DOMAIN_SSL_CONF" ]; then
|
|
DOMAIN_SSL_CONF="$DOMAIN_PATH/$domain.csr.conf"
|
|
fi
|
|
|
|
if [ -z "$CSR_PATH" ]; then
|
|
CSR_PATH="$DOMAIN_PATH/$domain.csr"
|
|
fi
|
|
if [ -z "$CERT_KEY_PATH" ]; then
|
|
CERT_KEY_PATH="$DOMAIN_PATH/$domain.key"
|
|
fi
|
|
if [ -z "$CERT_PATH" ]; then
|
|
CERT_PATH="$DOMAIN_PATH/$domain.cer"
|
|
fi
|
|
if [ -z "$CA_CERT_PATH" ]; then
|
|
CA_CERT_PATH="$DOMAIN_PATH/ca.cer"
|
|
fi
|
|
if [ -z "$CERT_FULLCHAIN_PATH" ]; then
|
|
CERT_FULLCHAIN_PATH="$DOMAIN_PATH/fullchain.cer"
|
|
fi
|
|
if [ -z "$CERT_PFX_PATH" ]; then
|
|
CERT_PFX_PATH="$DOMAIN_PATH/$domain.pfx"
|
|
fi
|
|
if [ -z "$CERT_PKCS8_PATH" ]; then
|
|
CERT_PKCS8_PATH="$DOMAIN_PATH/$domain.pkcs8"
|
|
fi
|
|
|
|
if [ -z "$TLS_CONF" ]; then
|
|
TLS_CONF="$DOMAIN_PATH/tls.validation.conf"
|
|
fi
|
|
if [ -z "$TLS_CERT" ]; then
|
|
TLS_CERT="$DOMAIN_PATH/tls.validation.cert"
|
|
fi
|
|
if [ -z "$TLS_KEY" ]; then
|
|
TLS_KEY="$DOMAIN_PATH/tls.validation.key"
|
|
fi
|
|
if [ -z "$TLS_CSR" ]; then
|
|
TLS_CSR="$DOMAIN_PATH/tls.validation.csr"
|
|
fi
|
|
|
|
}
|
|
|
|
_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.'"
|
|
_err "Please use webroot mode to try again."
|
|
return 1
|
|
fi
|
|
fi
|
|
|
|
if ! $_APACHECTL -V >/dev/null; then
|
|
return 1
|
|
fi
|
|
|
|
if [ "$APACHE_HTTPD_CONF" ]; then
|
|
_saveaccountconf APACHE_HTTPD_CONF "$APACHE_HTTPD_CONF"
|
|
httpdconf="$APACHE_HTTPD_CONF"
|
|
httpdconfname="$(basename "$httpdconfname")"
|
|
else
|
|
httpdconfname="$($_APACHECTL -V | grep SERVER_CONFIG_FILE= | cut -d = -f 2 | tr -d '"')"
|
|
_debug httpdconfname "$httpdconfname"
|
|
|
|
if [ -z "$httpdconfname" ]; then
|
|
_err "Cannot read Apache config file."
|
|
return 1
|
|
fi
|
|
|
|
if _startswith "$httpdconfname" '/'; then
|
|
httpdconf="$httpdconfname"
|
|
httpdconfname="$(basename "$httpdconfname")"
|
|
else
|
|
httpdroot="$($_APACHECTL -V | grep HTTPD_ROOT= | cut -d = -f 2 | tr -d '"')"
|
|
_debug httpdroot "$httpdroot"
|
|
httpdconf="$httpdroot/$httpdconfname"
|
|
httpdconfname="$(basename "$httpdconfname")"
|
|
fi
|
|
fi
|
|
_debug httpdconf "$httpdconf"
|
|
_debug httpdconfname "$httpdconfname"
|
|
if [ ! -f "$httpdconf" ]; then
|
|
_err "Apache config file not found" "$httpdconf"
|
|
return 1
|
|
fi
|
|
return 0
|
|
}
|
|
|
|
_restoreApache() {
|
|
if [ -z "$usingApache" ]; then
|
|
return 0
|
|
fi
|
|
_initpath
|
|
if ! _apachePath; then
|
|
return 1
|
|
fi
|
|
|
|
if [ ! -f "$APACHE_CONF_BACKUP_DIR/$httpdconfname" ]; then
|
|
_debug "No config file to restore."
|
|
return 0
|
|
fi
|
|
|
|
cat "$APACHE_CONF_BACKUP_DIR/$httpdconfname" >"$httpdconf"
|
|
_debug "Restored: $httpdconf."
|
|
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."
|
|
rm -f "$APACHE_CONF_BACKUP_DIR/$httpdconfname"
|
|
return 0
|
|
}
|
|
|
|
_setApache() {
|
|
_initpath
|
|
if ! _apachePath; then
|
|
return 1
|
|
fi
|
|
|
|
#test the conf first
|
|
_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 "Backing up Apache config file" "$httpdconf"
|
|
if ! cp "$httpdconf" "$APACHE_CONF_BACKUP_DIR/"; then
|
|
_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 "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
|
|
|
|
apacheVer="$($_APACHECTL -V | grep "Server version:" | cut -d : -f 2 | cut -d " " -f 2 | cut -d '/' -f 2)"
|
|
_debug "apacheVer" "$apacheVer"
|
|
apacheMajor="$(echo "$apacheVer" | cut -d . -f 1)"
|
|
apacheMinor="$(echo "$apacheVer" | cut -d . -f 2)"
|
|
|
|
if [ "$apacheVer" ] && [ "$apacheMajor$apacheMinor" -ge "24" ]; then
|
|
echo "
|
|
Alias /.well-known/acme-challenge $ACME_DIR
|
|
|
|
<Directory $ACME_DIR >
|
|
Require all granted
|
|
</Directory>
|
|
" >>"$httpdconf"
|
|
else
|
|
echo "
|
|
Alias /.well-known/acme-challenge $ACME_DIR
|
|
|
|
<Directory $ACME_DIR >
|
|
Order allow,deny
|
|
Allow from all
|
|
</Directory>
|
|
" >>"$httpdconf"
|
|
fi
|
|
|
|
_msg="$($_APACHECTL -t 2>&1)"
|
|
if [ "$?" != "0" ]; then
|
|
_err "Sorry, an Apache config error has occurred"
|
|
if _restoreApache; then
|
|
_err "The Apache config file has been restored."
|
|
else
|
|
_err "Sorry, the Apache config file cannot be restored, please open an issue on $PROJECT."
|
|
fi
|
|
return 1
|
|
fi
|
|
|
|
if [ ! -d "$ACME_DIR" ]; then
|
|
mkdir -p "$ACME_DIR"
|
|
chmod 755 "$ACME_DIR"
|
|
fi
|
|
|
|
if ! $_APACHECTL graceful; then
|
|
_err "$_APACHECTL graceful error, please open an issue on $PROJECT."
|
|
_restoreApache
|
|
return 1
|
|
fi
|
|
usingApache="1"
|
|
return 0
|
|
}
|
|
|
|
#find the real nginx conf file
|
|
#backup
|
|
#set the nginx conf
|
|
#returns the real nginx conf file
|
|
_setNginx() {
|
|
_d="$1"
|
|
_croot="$2"
|
|
_thumbpt="$3"
|
|
|
|
FOUND_REAL_NGINX_CONF=""
|
|
FOUND_REAL_NGINX_CONF_LN=""
|
|
BACKUP_NGINX_CONF=""
|
|
_debug _croot "$_croot"
|
|
_start_f="$(echo "$_croot" | cut -d : -f 2)"
|
|
_debug _start_f "$_start_f"
|
|
if [ -z "$_start_f" ]; then
|
|
_debug "Finding config using the nginx command"
|
|
if [ -z "$NGINX_CONF" ]; then
|
|
if ! _exists "nginx"; then
|
|
_err "nginx command not found."
|
|
return 1
|
|
fi
|
|
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 "Cannot find nginx config."
|
|
NGINX_CONF=""
|
|
return 1
|
|
fi
|
|
if [ ! -f "$NGINX_CONF" ]; then
|
|
_err "'$NGINX_CONF' doesn't exist."
|
|
NGINX_CONF=""
|
|
return 1
|
|
fi
|
|
_debug "Found nginx config file: $NGINX_CONF"
|
|
fi
|
|
_start_f="$NGINX_CONF"
|
|
fi
|
|
_debug "Detecting nginx conf for $_d from: $_start_f"
|
|
if ! _checkConf "$_d" "$_start_f"; then
|
|
_err "Cannot find config file for domain $d"
|
|
return 1
|
|
fi
|
|
_info "Found config file: $FOUND_REAL_NGINX_CONF"
|
|
|
|
_ln=$FOUND_REAL_NGINX_CONF_LN
|
|
_debug "_ln" "$_ln"
|
|
|
|
_lnn=$(_math $_ln + 1)
|
|
_debug _lnn "$_lnn"
|
|
_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, skipping"
|
|
FOUND_REAL_NGINX_CONF=""
|
|
return 0
|
|
fi
|
|
|
|
mkdir -p "$DOMAIN_BACKUP_PATH"
|
|
_backup_conf="$DOMAIN_BACKUP_PATH/$_d.nginx.conf"
|
|
_debug _backup_conf "$_backup_conf"
|
|
BACKUP_NGINX_CONF="$_backup_conf"
|
|
_info "Backing $FOUND_REAL_NGINX_CONF up to $_backup_conf"
|
|
if ! cp "$FOUND_REAL_NGINX_CONF" "$_backup_conf"; then
|
|
_err "Backup error."
|
|
FOUND_REAL_NGINX_CONF=""
|
|
return 1
|
|
fi
|
|
|
|
if ! _exists "nginx"; then
|
|
_err "nginx command not found."
|
|
return 1
|
|
fi
|
|
_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, 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 "Error writing nginx config. Restoring it to its original version."
|
|
return 1
|
|
fi
|
|
|
|
echo "$NGINX_START
|
|
location ~ \"^/\.well-known/acme-challenge/([-_a-zA-Z0-9]+)\$\" {
|
|
default_type text/plain;
|
|
return 200 \"\$1.$_thumbpt\";
|
|
}
|
|
#NGINX_START
|
|
" >>"$FOUND_REAL_NGINX_CONF"
|
|
|
|
if ! sed -n "${_lnn},99999p" "$_backup_conf" >>"$FOUND_REAL_NGINX_CONF"; then
|
|
cat "$_backup_conf" >"$FOUND_REAL_NGINX_CONF"
|
|
_err "Error writing nginx config. Restoring it to its original version."
|
|
return 1
|
|
fi
|
|
_debug3 "Modified config:$(cat $FOUND_REAL_NGINX_CONF)"
|
|
_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 "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
|
|
|
|
return 0
|
|
}
|
|
|
|
#d , conf
|
|
_checkConf() {
|
|
_d="$1"
|
|
_c_file="$2"
|
|
_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
|
|
if [ -f "$_w_f" ] && _checkConf "$1" "$_w_f"; then
|
|
return 0
|
|
fi
|
|
done
|
|
#not found
|
|
return 1
|
|
elif [ -f "$2" ]; then
|
|
_debug "single"
|
|
if _isRealNginxConf "$1" "$2"; then
|
|
_debug "$2 found."
|
|
FOUND_REAL_NGINX_CONF="$2"
|
|
return 0
|
|
fi
|
|
if cat "$2" | tr "\t" " " | grep "^ *include *.*;" >/dev/null; then
|
|
_debug "Trying include files"
|
|
for included in $(cat "$2" | tr "\t" " " | grep "^ *include *.*;" | sed "s/include //" | tr -d " ;"); do
|
|
_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
|
|
done
|
|
fi
|
|
return 1
|
|
else
|
|
_debug "$2 not found."
|
|
return 1
|
|
fi
|
|
return 1
|
|
}
|
|
|
|
#d , conf
|
|
_isRealNginxConf() {
|
|
_debug "_isRealNginxConf $1 $2"
|
|
if [ -f "$2" ]; then
|
|
for _fln in $(tr "\t" ' ' <"$2" | grep -n "^ *server_name.* $1" | cut -d : -f 1); do
|
|
_debug _fln "$_fln"
|
|
if [ "$_fln" ]; then
|
|
_start=$(tr "\t" ' ' <"$2" | _head_n "$_fln" | grep -n "^ *server *" | grep -v server_name | _tail_n 1)
|
|
_debug "_start" "$_start"
|
|
_start_n=$(echo "$_start" | cut -d : -f 1)
|
|
_start_nn=$(_math $_start_n + 1)
|
|
_debug "_start_n" "$_start_n"
|
|
_debug "_start_nn" "$_start_nn"
|
|
|
|
_left="$(sed -n "${_start_nn},99999p" "$2")"
|
|
_debug2 _left "$_left"
|
|
_end="$(echo "$_left" | tr "\t" ' ' | grep -n "^ *server *" | grep -v server_name | _head_n 1)"
|
|
_debug "_end" "$_end"
|
|
if [ "$_end" ]; then
|
|
_end_n=$(echo "$_end" | cut -d : -f 1)
|
|
_debug "_end_n" "$_end_n"
|
|
_seg_n=$(echo "$_left" | sed -n "1,${_end_n}p")
|
|
else
|
|
_seg_n="$_left"
|
|
fi
|
|
|
|
_debug "_seg_n" "$_seg_n"
|
|
|
|
_skip_ssl=1
|
|
for _listen_i in $(echo "$_seg_n" | tr "\t" ' ' | grep "^ *listen" | tr -d " "); do
|
|
if [ "$_listen_i" ]; then
|
|
if [ "$(echo "$_listen_i" | _egrep_o "listen.*ssl")" ]; then
|
|
_debug2 "$_listen_i is ssl"
|
|
else
|
|
_debug2 "$_listen_i is plain text"
|
|
_skip_ssl=""
|
|
break
|
|
fi
|
|
fi
|
|
done
|
|
|
|
if [ "$_skip_ssl" = "1" ]; then
|
|
_debug "ssl on, skip"
|
|
else
|
|
FOUND_REAL_NGINX_CONF_LN=$_fln
|
|
_debug3 "found FOUND_REAL_NGINX_CONF_LN" "$FOUND_REAL_NGINX_CONF_LN"
|
|
return 0
|
|
fi
|
|
fi
|
|
done
|
|
fi
|
|
return 1
|
|
}
|
|
|
|
#restore all the nginx conf
|
|
_restoreNginx() {
|
|
if [ -z "$NGINX_RESTORE_VLIST" ]; then
|
|
_debug "No need to restore nginx config, skipping."
|
|
return
|
|
fi
|
|
_debug "_restoreNginx"
|
|
_debug "NGINX_RESTORE_VLIST" "$NGINX_RESTORE_VLIST"
|
|
|
|
for ng_entry in $(echo "$NGINX_RESTORE_VLIST" | tr "$dvsep" ' '); do
|
|
_debug "ng_entry" "$ng_entry"
|
|
_nd=$(echo "$ng_entry" | cut -d "$sep" -f 1)
|
|
_ngconf=$(echo "$ng_entry" | cut -d "$sep" -f 2)
|
|
_ngbackupconf=$(echo "$ng_entry" | cut -d "$sep" -f 3)
|
|
_info "Restoring from $_ngbackupconf to $_ngconf"
|
|
cat "$_ngbackupconf" >"$_ngconf"
|
|
done
|
|
|
|
_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
|
|
}
|
|
|
|
_clearup() {
|
|
_stopserver "$serverproc"
|
|
serverproc=""
|
|
_restoreApache
|
|
_restoreNginx
|
|
_clearupdns
|
|
if [ -z "$DEBUG" ]; then
|
|
rm -f "$TLS_CONF"
|
|
rm -f "$TLS_CERT"
|
|
rm -f "$TLS_KEY"
|
|
rm -f "$TLS_CSR"
|
|
fi
|
|
}
|
|
|
|
_clearupdns() {
|
|
_debug "_clearupdns"
|
|
_debug "dns_entries" "$dns_entries"
|
|
|
|
if [ -z "$dns_entries" ]; then
|
|
_debug "Skipping dns."
|
|
return
|
|
fi
|
|
_info "Removing DNS records."
|
|
|
|
for entry in $dns_entries; do
|
|
d=$(_getfield "$entry" 1)
|
|
txtdomain=$(_getfield "$entry" 2)
|
|
aliasDomain=$(_getfield "$entry" 3)
|
|
_currentRoot=$(_getfield "$entry" 4)
|
|
txt=$(_getfield "$entry" 5)
|
|
d_api=$(_getfield "$entry" 6)
|
|
_debug "d" "$d"
|
|
_debug "txtdomain" "$txtdomain"
|
|
_debug "aliasDomain" "$aliasDomain"
|
|
_debug "_currentRoot" "$_currentRoot"
|
|
_debug "txt" "$txt"
|
|
_debug "d_api" "$d_api"
|
|
if [ "$d_api" = "$txt" ]; then
|
|
d_api=""
|
|
fi
|
|
|
|
if [ -z "$d_api" ]; then
|
|
_info "Domain API file was not found: $d_api"
|
|
continue
|
|
fi
|
|
|
|
if [ "$aliasDomain" ]; then
|
|
txtdomain="$aliasDomain"
|
|
fi
|
|
|
|
(
|
|
if ! . "$d_api"; then
|
|
_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"
|
|
return 1
|
|
fi
|
|
_info "Removing txt: $txt for domain: $txtdomain"
|
|
if ! $rmcommand "$txtdomain" "$txt"; then
|
|
_err "Error removing txt for domain: $txtdomain"
|
|
return 1
|
|
fi
|
|
_info "Successfully removed"
|
|
)
|
|
|
|
done
|
|
}
|
|
|
|
# webroot removelevel tokenfile
|
|
_clearupwebbroot() {
|
|
__webroot="$1"
|
|
if [ -z "$__webroot" ]; then
|
|
_debug "No webroot specified, skipping"
|
|
return 0
|
|
fi
|
|
|
|
_rmpath=""
|
|
if [ "$2" = '1' ]; then
|
|
_rmpath="$__webroot/.well-known"
|
|
elif [ "$2" = '2' ]; then
|
|
_rmpath="$__webroot/.well-known/acme-challenge"
|
|
elif [ "$2" = '3' ]; then
|
|
_rmpath="$__webroot/.well-known/acme-challenge/$3"
|
|
else
|
|
_debug "Skipping for removelevel: $2"
|
|
fi
|
|
|
|
if [ "$_rmpath" ]; then
|
|
if [ "$DEBUG" ]; then
|
|
_debug "Debugging, not removing: $_rmpath"
|
|
else
|
|
rm -rf "$_rmpath"
|
|
fi
|
|
fi
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
_on_before_issue() {
|
|
_chk_web_roots="$1"
|
|
_chk_main_domain="$2"
|
|
_chk_alt_domains="$3"
|
|
_chk_pre_hook="$4"
|
|
_chk_local_addr="$5"
|
|
_debug _on_before_issue
|
|
_debug _chk_main_domain "$_chk_main_domain"
|
|
_debug _chk_alt_domains "$_chk_alt_domains"
|
|
#run pre hook
|
|
if [ "$_chk_pre_hook" ]; then
|
|
_info "Runing 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 occurred when running pre hook."
|
|
return 1
|
|
fi
|
|
fi
|
|
|
|
if _hasfield "$_chk_web_roots" "$NO_VALUE"; then
|
|
if ! _exists "socat"; then
|
|
_err "Please install socat tools first."
|
|
return 1
|
|
fi
|
|
fi
|
|
|
|
_debug Le_LocalAddress "$_chk_local_addr"
|
|
|
|
_index=1
|
|
_currentRoot=""
|
|
_addrIndex=1
|
|
_w_index=1
|
|
while true; do
|
|
d="$(echo "$_chk_main_domain,$_chk_alt_domains," | cut -d , -f "$_w_index")"
|
|
_w_index="$(_math "$_w_index" + 1)"
|
|
_debug d "$d"
|
|
if [ -z "$d" ]; then
|
|
break
|
|
fi
|
|
_debug "Checking for domain" "$d"
|
|
_currentRoot="$(_getfield "$_chk_web_roots" $_index)"
|
|
_debug "_currentRoot" "$_currentRoot"
|
|
_index=$(_math $_index + 1)
|
|
_checkport=""
|
|
if [ "$_currentRoot" = "$NO_VALUE" ]; then
|
|
_info "Standalone mode."
|
|
if [ -z "$Le_HTTPPort" ]; then
|
|
Le_HTTPPort=80
|
|
_cleardomainconf "Le_HTTPPort"
|
|
else
|
|
_savedomainconf "Le_HTTPPort" "$Le_HTTPPort"
|
|
fi
|
|
_checkport="$Le_HTTPPort"
|
|
elif [ "$_currentRoot" = "$W_ALPN" ]; then
|
|
_info "Standalone alpn mode."
|
|
if [ -z "$Le_TLSPort" ]; then
|
|
Le_TLSPort=443
|
|
else
|
|
_savedomainconf "Le_TLSPort" "$Le_TLSPort"
|
|
fi
|
|
_checkport="$Le_TLSPort"
|
|
fi
|
|
|
|
if [ "$_checkport" ]; then
|
|
_debug _checkport "$_checkport"
|
|
_checkaddr="$(_getfield "$_chk_local_addr" $_addrIndex)"
|
|
_debug _checkaddr "$_checkaddr"
|
|
|
|
_addrIndex="$(_math $_addrIndex + 1)"
|
|
|
|
_netprc="$(_ss "$_checkport" | grep "$_checkport")"
|
|
netprc="$(echo "$_netprc" | grep "$_checkaddr")"
|
|
if [ -z "$netprc" ]; then
|
|
netprc="$(echo "$_netprc" | grep "$LOCAL_ANY_ADDRESS:$_checkport")"
|
|
fi
|
|
if [ "$netprc" ]; then
|
|
_err "$netprc"
|
|
_err "tcp port $_checkport is already used by $(echo "$netprc" | cut -d : -f 4)"
|
|
_err "Please stop it first"
|
|
return 1
|
|
fi
|
|
fi
|
|
done
|
|
|
|
if _hasfield "$_chk_web_roots" "apache"; then
|
|
if ! _setApache; then
|
|
_err "Error setting up Apache. Please open an issue on $PROJECT."
|
|
return 1
|
|
fi
|
|
else
|
|
usingApache=""
|
|
fi
|
|
|
|
}
|
|
|
|
_on_issue_err() {
|
|
_chk_post_hook="$1"
|
|
_chk_vlist="$2"
|
|
_debug _on_issue_err
|
|
|
|
if [ "$LOG_FILE" ]; then
|
|
_err "Please check log file for more details: $LOG_FILE"
|
|
else
|
|
_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 "Running post hook: '$_chk_post_hook'"
|
|
if ! (
|
|
cd "$DOMAIN_PATH" && eval "$_chk_post_hook"
|
|
); then
|
|
_err "Error encountered while running post hook."
|
|
return 1
|
|
fi
|
|
fi
|
|
|
|
#trigger the validation to flush the pending authz
|
|
_debug2 "_chk_vlist" "$_chk_vlist"
|
|
if [ "$_chk_vlist" ]; then
|
|
(
|
|
_debug2 "start to deactivate authz"
|
|
ventries=$(echo "$_chk_vlist" | tr "$dvsep" ' ')
|
|
for ventry in $ventries; do
|
|
d=$(echo "$ventry" | cut -d "$sep" -f 1)
|
|
keyauthorization=$(echo "$ventry" | cut -d "$sep" -f 2)
|
|
uri=$(echo "$ventry" | cut -d "$sep" -f 3)
|
|
vtype=$(echo "$ventry" | cut -d "$sep" -f 4)
|
|
_currentRoot=$(echo "$ventry" | cut -d "$sep" -f 5)
|
|
__trigger_validation "$uri" "$keyauthorization"
|
|
done
|
|
)
|
|
fi
|
|
|
|
if [ "$_ACME_IS_RENEW" = "1" ] && _hasfield "$Le_Webroot" "$W_DNS"; then
|
|
_err "$_DNS_MANUAL_ERR"
|
|
fi
|
|
|
|
if [ "$DEBUG" ] && [ "$DEBUG" -gt "0" ]; then
|
|
_debug "$(_dlg_versions)"
|
|
fi
|
|
|
|
}
|
|
|
|
_on_issue_success() {
|
|
_chk_post_hook="$1"
|
|
_chk_renew_hook="$2"
|
|
_debug _on_issue_success
|
|
|
|
#run the post hook
|
|
if [ "$_chk_post_hook" ]; then
|
|
_info "Running post hook:'$_chk_post_hook'"
|
|
if ! (
|
|
export CERT_PATH
|
|
export CERT_KEY_PATH
|
|
export CA_CERT_PATH
|
|
export CERT_FULLCHAIN_PATH
|
|
export Le_Domain="$_main_domain"
|
|
cd "$DOMAIN_PATH" && eval "$_chk_post_hook"
|
|
); then
|
|
_err "Error encountered while running post hook."
|
|
return 1
|
|
fi
|
|
fi
|
|
|
|
#run renew hook
|
|
if [ "$_ACME_IS_RENEW" ] && [ "$_chk_renew_hook" ]; then
|
|
_info "Running renew hook: '$_chk_renew_hook'"
|
|
if ! (
|
|
export CERT_PATH
|
|
export CERT_KEY_PATH
|
|
export CA_CERT_PATH
|
|
export CERT_FULLCHAIN_PATH
|
|
export Le_Domain="$_main_domain"
|
|
cd "$DOMAIN_PATH" && eval "$_chk_renew_hook"
|
|
); then
|
|
_err "Error encountered while running renew hook."
|
|
return 1
|
|
fi
|
|
fi
|
|
|
|
if _hasfield "$Le_Webroot" "$W_DNS" && [ -z "$FORCE_DNS_MANUAL" ]; then
|
|
_err "$_DNS_MANUAL_WARN"
|
|
fi
|
|
|
|
}
|
|
|
|
#account_key_length eab-kid eab-hmac-key
|
|
registeraccount() {
|
|
_account_key_length="$1"
|
|
_eab_id="$2"
|
|
_eab_hmac_key="$3"
|
|
_initpath
|
|
_regAccount "$_account_key_length" "$_eab_id" "$_eab_hmac_key"
|
|
}
|
|
|
|
__calcAccountKeyHash() {
|
|
[ -f "$ACCOUNT_KEY_PATH" ] && _digest sha256 <"$ACCOUNT_KEY_PATH"
|
|
}
|
|
|
|
__calc_account_thumbprint() {
|
|
printf "%s" "$jwk" | tr -d ' ' | _digest "sha256" | _url_replace
|
|
}
|
|
|
|
_getAccountEmail() {
|
|
if [ "$ACCOUNT_EMAIL" ]; then
|
|
echo "$ACCOUNT_EMAIL"
|
|
return 0
|
|
fi
|
|
if [ -z "$CA_EMAIL" ]; then
|
|
CA_EMAIL="$(_readcaconf CA_EMAIL)"
|
|
fi
|
|
if [ "$CA_EMAIL" ]; then
|
|
echo "$CA_EMAIL"
|
|
return 0
|
|
fi
|
|
_readaccountconf "ACCOUNT_EMAIL"
|
|
}
|
|
|
|
#keylength
|
|
_regAccount() {
|
|
_initpath
|
|
_reg_length="$1"
|
|
_eab_id="$2"
|
|
_eab_hmac_key="$3"
|
|
_debug3 _regAccount "$_regAccount"
|
|
_initAPI
|
|
|
|
mkdir -p "$CA_DIR"
|
|
|
|
if [ ! -f "$ACCOUNT_KEY_PATH" ]; then
|
|
if ! _create_account_key "$_reg_length"; then
|
|
_err "Error creating account key."
|
|
return 1
|
|
fi
|
|
fi
|
|
|
|
if ! _calcjwk "$ACCOUNT_KEY_PATH"; then
|
|
return 1
|
|
fi
|
|
if [ "$_eab_id" ] && [ "$_eab_hmac_key" ]; then
|
|
_savecaconf CA_EAB_KEY_ID "$_eab_id"
|
|
_savecaconf CA_EAB_HMAC_KEY "$_eab_hmac_key"
|
|
fi
|
|
_eab_id=$(_readcaconf "CA_EAB_KEY_ID")
|
|
_eab_hmac_key=$(_readcaconf "CA_EAB_HMAC_KEY")
|
|
_secure_debug3 _eab_id "$_eab_id"
|
|
_secure_debug3 _eab_hmac_key "$_eab_hmac_key"
|
|
_email="$(_getAccountEmail)"
|
|
if [ "$_email" ]; then
|
|
_savecaconf "CA_EMAIL" "$_email"
|
|
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 "Error registering account: $response"
|
|
return 1
|
|
fi
|
|
|
|
_eabAlreadyBound=""
|
|
if [ "$code" = "" ] || [ "$code" = '201' ]; then
|
|
echo "$response" >"$ACCOUNT_JSON_PATH"
|
|
_info "Registered"
|
|
elif [ "$code" = '409' ] || [ "$code" = '200' ]; then
|
|
_info "Already registered"
|
|
elif [ "$code" = '400' ] && _contains "$response" 'The account is not awaiting external account binding'; then
|
|
_info "EAB already registered"
|
|
_eabAlreadyBound=1
|
|
else
|
|
_err "Account registration error: $response"
|
|
return 1
|
|
fi
|
|
|
|
if [ -z "$_eabAlreadyBound" ]; then
|
|
_debug2 responseHeaders "$responseHeaders"
|
|
_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 "Cannot find account id url."
|
|
_err "$responseHeaders"
|
|
return 1
|
|
fi
|
|
_savecaconf "ACCOUNT_URL" "$_accUri"
|
|
else
|
|
ACCOUNT_URL="$(_readcaconf ACCOUNT_URL)"
|
|
fi
|
|
export ACCOUNT_URL="$_accUri"
|
|
|
|
CA_KEY_HASH="$(__calcAccountKeyHash)"
|
|
_debug "Calc CA_KEY_HASH" "$CA_KEY_HASH"
|
|
_savecaconf CA_KEY_HASH "$CA_KEY_HASH"
|
|
|
|
if [ "$code" = '403' ]; then
|
|
_err "It seems that the account key has been deactivated, please use a new account key."
|
|
return 1
|
|
fi
|
|
|
|
ACCOUNT_THUMBPRINT="$(__calc_account_thumbprint)"
|
|
_info "ACCOUNT_THUMBPRINT" "$ACCOUNT_THUMBPRINT"
|
|
}
|
|
|
|
#implement updateaccount
|
|
updateaccount() {
|
|
_initpath
|
|
|
|
if [ ! -f "$ACCOUNT_KEY_PATH" ]; then
|
|
_err "Account key not found at: $ACCOUNT_KEY_PATH"
|
|
return 1
|
|
fi
|
|
|
|
_accUri=$(_readcaconf "ACCOUNT_URL")
|
|
_debug _accUri "$_accUri"
|
|
|
|
if [ -z "$_accUri" ]; then
|
|
_err "The account URL is empty, please run '--update-account' first to update the account info, then try again."
|
|
return 1
|
|
fi
|
|
|
|
if ! _calcjwk "$ACCOUNT_KEY_PATH"; then
|
|
return 1
|
|
fi
|
|
_initAPI
|
|
|
|
_email="$(_getAccountEmail)"
|
|
|
|
if [ "$_email" ]; then
|
|
updjson='{"contact": ["mailto:'$_email'"]}'
|
|
else
|
|
updjson='{"contact": []}'
|
|
fi
|
|
|
|
_send_signed_request "$_accUri" "$updjson"
|
|
|
|
if [ "$code" = '200' ]; then
|
|
echo "$response" >"$ACCOUNT_JSON_PATH"
|
|
_info "Account update success for $_accUri."
|
|
else
|
|
_info "An error occurred and the account was not updated."
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
#Implement deactivate account
|
|
deactivateaccount() {
|
|
_initpath
|
|
|
|
if [ ! -f "$ACCOUNT_KEY_PATH" ]; then
|
|
_err "Account key not found at: $ACCOUNT_KEY_PATH"
|
|
return 1
|
|
fi
|
|
|
|
_accUri=$(_readcaconf "ACCOUNT_URL")
|
|
_debug _accUri "$_accUri"
|
|
|
|
if [ -z "$_accUri" ]; then
|
|
_err "The account URL is empty, please run '--update-account' first to update the account info, then try again."
|
|
return 1
|
|
fi
|
|
|
|
if ! _calcjwk "$ACCOUNT_KEY_PATH"; then
|
|
return 1
|
|
fi
|
|
_initAPI
|
|
|
|
_djson="{\"status\":\"deactivated\"}"
|
|
|
|
if _send_signed_request "$_accUri" "$_djson" && _contains "$response" '"deactivated"'; then
|
|
_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 "Account deactivation failed for $_accUri."
|
|
return 1
|
|
fi
|
|
|
|
_debug "Account id: $_accid"
|
|
if [ "$_accid" ]; then
|
|
_deactivated_account_path="$CA_DIR/deactivated/$_accid"
|
|
_debug _deactivated_account_path "$_deactivated_account_path"
|
|
if mkdir -p "$_deactivated_account_path"; then
|
|
_info "Moving deactivated account info to $_deactivated_account_path/"
|
|
mv "$CA_CONF" "$_deactivated_account_path/"
|
|
mv "$ACCOUNT_JSON_PATH" "$_deactivated_account_path/"
|
|
mv "$ACCOUNT_KEY_PATH" "$_deactivated_account_path/"
|
|
else
|
|
_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"
|
|
fi
|
|
fi
|
|
}
|
|
|
|
# domain folder file
|
|
_findHook() {
|
|
_hookdomain="$1"
|
|
_hookcat="$2"
|
|
_hookname="$3"
|
|
|
|
if [ -f "$_SCRIPT_HOME/$_hookcat/$_hookname" ]; then
|
|
d_api="$_SCRIPT_HOME/$_hookcat/$_hookname"
|
|
elif [ -f "$_SCRIPT_HOME/$_hookcat/$_hookname.sh" ]; then
|
|
d_api="$_SCRIPT_HOME/$_hookcat/$_hookname.sh"
|
|
elif [ "$_hookdomain" ] && [ -f "$LE_WORKING_DIR/$_hookdomain/$_hookname" ]; then
|
|
d_api="$LE_WORKING_DIR/$_hookdomain/$_hookname"
|
|
elif [ "$_hookdomain" ] && [ -f "$LE_WORKING_DIR/$_hookdomain/$_hookname.sh" ]; then
|
|
d_api="$LE_WORKING_DIR/$_hookdomain/$_hookname.sh"
|
|
elif [ -f "$LE_WORKING_DIR/$_hookname" ]; then
|
|
d_api="$LE_WORKING_DIR/$_hookname"
|
|
elif [ -f "$LE_WORKING_DIR/$_hookname.sh" ]; then
|
|
d_api="$LE_WORKING_DIR/$_hookname.sh"
|
|
elif [ -f "$LE_WORKING_DIR/$_hookcat/$_hookname" ]; then
|
|
d_api="$LE_WORKING_DIR/$_hookcat/$_hookname"
|
|
elif [ -f "$LE_WORKING_DIR/$_hookcat/$_hookname.sh" ]; then
|
|
d_api="$LE_WORKING_DIR/$_hookcat/$_hookname.sh"
|
|
fi
|
|
|
|
printf "%s" "$d_api"
|
|
}
|
|
|
|
#domain
|
|
__get_domain_new_authz() {
|
|
_gdnd="$1"
|
|
_info "Getting new-authz for domain" "$_gdnd"
|
|
_initAPI
|
|
_Max_new_authz_retry_times=5
|
|
_authz_i=0
|
|
while [ "$_authz_i" -lt "$_Max_new_authz_retry_times" ]; do
|
|
_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 "Cannot get new authz for domain."
|
|
return 1
|
|
fi
|
|
if _contains "$response" "No registration exists matching provided key"; then
|
|
_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 "new-authz request successful."
|
|
break
|
|
fi
|
|
_authz_i="$(_math "$_authz_i" + 1)"
|
|
_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 has been retried $_Max_new_authz_retry_times times, stopping."
|
|
fi
|
|
|
|
if [ "$code" ] && [ "$code" != '201' ]; then
|
|
_err "new-authz error: $response"
|
|
return 1
|
|
fi
|
|
|
|
}
|
|
|
|
#uri keyAuthorization
|
|
__trigger_validation() {
|
|
_debug2 "Trigger domain validation."
|
|
_t_url="$1"
|
|
_debug2 _t_url "$_t_url"
|
|
_t_key_authz="$2"
|
|
_debug2 _t_key_authz "$_t_key_authz"
|
|
_t_vtype="$3"
|
|
_debug2 _t_vtype "$_t_vtype"
|
|
|
|
_send_signed_request "$_t_url" "{}"
|
|
|
|
}
|
|
|
|
#endpoint domain type
|
|
_ns_lookup_impl() {
|
|
_ns_ep="$1"
|
|
_ns_domain="$2"
|
|
_ns_type="$3"
|
|
_debug2 "_ns_ep" "$_ns_ep"
|
|
_debug2 "_ns_domain" "$_ns_domain"
|
|
_debug2 "_ns_type" "$_ns_type"
|
|
|
|
response="$(_H1="accept: application/dns-json" _get "$_ns_ep?name=$_ns_domain&type=$_ns_type")"
|
|
_ret=$?
|
|
_debug2 "response" "$response"
|
|
if [ "$_ret" != "0" ]; then
|
|
return $_ret
|
|
fi
|
|
_answers="$(echo "$response" | tr '{}' '<>' | _egrep_o '"Answer":\[[^]]*]' | tr '<>' '\n\n')"
|
|
_debug2 "_answers" "$_answers"
|
|
echo "$_answers"
|
|
}
|
|
|
|
#domain, type
|
|
_ns_lookup_cf() {
|
|
_cf_ld="$1"
|
|
_cf_ld_type="$2"
|
|
_cf_ep="https://cloudflare-dns.com/dns-query"
|
|
_ns_lookup_impl "$_cf_ep" "$_cf_ld" "$_cf_ld_type"
|
|
}
|
|
|
|
#domain, type
|
|
_ns_purge_cf() {
|
|
_cf_d="$1"
|
|
_cf_d_type="$2"
|
|
_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"
|
|
}
|
|
|
|
#checks if cf server is available
|
|
_ns_is_available_cf() {
|
|
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
|
|
fi
|
|
}
|
|
|
|
#domain, type
|
|
_ns_lookup_google() {
|
|
_cf_ld="$1"
|
|
_cf_ld_type="$2"
|
|
_cf_ep="https://dns.google/resolve"
|
|
_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() {
|
|
_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
|
|
_err "Unknown DOH provider: DOH_USE=$DOH_USE"
|
|
fi
|
|
|
|
}
|
|
|
|
#txtdomain, alias, txt
|
|
__check_txt() {
|
|
_c_txtdomain="$1"
|
|
_c_aliasdomain="$2"
|
|
_c_txt="$3"
|
|
_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"
|
|
|
|
}
|
|
|
|
#txtdomain
|
|
__purge_txt() {
|
|
_p_txtdomain="$1"
|
|
_debug _p_txtdomain "$_p_txtdomain"
|
|
if [ "$DOH_USE" = "$DOH_CLOUDFLARE" ] || [ -z "$DOH_USE" ]; then
|
|
_ns_purge_cf "$_p_txtdomain" "TXT"
|
|
else
|
|
_debug "No purge API for this DOH API, just sleeping for 5 seconds"
|
|
_sleep 5
|
|
fi
|
|
|
|
}
|
|
|
|
#wait and check each dns entries
|
|
_check_dns_entries() {
|
|
_success_txt=","
|
|
_end_time="$(_time)"
|
|
_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)
|
|
txtdomain=$(_getfield "$entry" 2)
|
|
txtdomain=$(_idn "$txtdomain")
|
|
aliasDomain=$(_getfield "$entry" 3)
|
|
aliasDomain=$(_idn "$aliasDomain")
|
|
txt=$(_getfield "$entry" 5)
|
|
d_api=$(_getfield "$entry" 6)
|
|
_debug "d" "$d"
|
|
_debug "txtdomain" "$txtdomain"
|
|
_debug "aliasDomain" "$aliasDomain"
|
|
_debug "txt" "$txt"
|
|
_debug "d_api" "$d_api"
|
|
_info "Checking $d for $aliasDomain"
|
|
if _contains "$_success_txt" ",$txt,"; then
|
|
_info "Already succeeded, continuing."
|
|
continue
|
|
fi
|
|
|
|
if __check_txt "$txtdomain" "$aliasDomain" "$txt"; then
|
|
_info "Success for domain $d '$aliasDomain'."
|
|
_success_txt="$_success_txt,$txt,"
|
|
continue
|
|
fi
|
|
_left=1
|
|
_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"
|
|
fi
|
|
_sleep 10
|
|
done
|
|
if [ "$_left" ]; then
|
|
_info "Let's wait for 10 seconds and check again".
|
|
_sleep 10
|
|
else
|
|
_info "All checks succeeded"
|
|
return 0
|
|
fi
|
|
done
|
|
_info "Timed out waiting for DNS."
|
|
return 1
|
|
|
|
}
|
|
|
|
#file
|
|
_get_chain_issuers() {
|
|
_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 'Issuer:' | _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 '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
|
|
}
|
|
|
|
#cert issuer
|
|
_match_issuer() {
|
|
_cfile="$1"
|
|
_missuer="$2"
|
|
_fissuers="$(_get_chain_issuers $_cfile)"
|
|
_debug2 _fissuers "$_fissuers"
|
|
_rootissuer="$(echo "$_fissuers" | _lower_case | _tail_n 1)"
|
|
_debug2 _rootissuer "$_rootissuer"
|
|
_missuer="$(echo "$_missuer" | _lower_case)"
|
|
_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
|
|
issue() {
|
|
if [ -z "$2" ]; then
|
|
_usage "Usage: $PROJECT_ENTRY --issue --domain <domain.tld> --webroot <directory>"
|
|
return 1
|
|
fi
|
|
if [ -z "$1" ]; then
|
|
_usage "Please specify at least one validation method: '--webroot', '--standalone', '--apache', '--nginx' or '--dns' etc."
|
|
return 1
|
|
fi
|
|
_web_roots="$1"
|
|
_main_domain="$2"
|
|
_alt_domains="$3"
|
|
|
|
if _contains "$_main_domain" ","; then
|
|
_main_domain=$(echo "$2,$3" | cut -d , -f 1)
|
|
_alt_domains=$(echo "$2,$3" | cut -d , -f 2- | sed "s/,${NO_VALUE}$//")
|
|
fi
|
|
_debug _main_domain "$_main_domain"
|
|
_debug _alt_domains "$_alt_domains"
|
|
|
|
_key_length="$4"
|
|
_real_cert="$5"
|
|
_real_key="$6"
|
|
_real_ca="$7"
|
|
_reload_cmd="$8"
|
|
_real_fullchain="$9"
|
|
_pre_hook="${10}"
|
|
_post_hook="${11}"
|
|
_renew_hook="${12}"
|
|
_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
|
|
_err "$_DNS_MANUAL_ERROR"
|
|
return 1
|
|
fi
|
|
|
|
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"
|
|
_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 "Skipping. Next renewal time is: $(__green "$(_readdomainconf Le_NextRenewTimeStr)")"
|
|
_info "Add '$(__red '--force')' to force renewal."
|
|
return $RENEW_SKIP
|
|
else
|
|
_info "Domains have changed."
|
|
fi
|
|
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"
|
|
|
|
_savedomainconf "Le_PreHook" "$_pre_hook" "base64"
|
|
_savedomainconf "Le_PostHook" "$_post_hook" "base64"
|
|
_savedomainconf "Le_RenewHook" "$_renew_hook" "base64"
|
|
|
|
if [ "$_local_addr" ]; then
|
|
_savedomainconf "Le_LocalAddress" "$_local_addr"
|
|
else
|
|
_cleardomainconf "Le_LocalAddress"
|
|
fi
|
|
if [ "$_challenge_alias" ]; then
|
|
_savedomainconf "Le_ChallengeAlias" "$_challenge_alias"
|
|
else
|
|
_cleardomainconf "Le_ChallengeAlias"
|
|
fi
|
|
if [ "$_preferred_chain" ]; then
|
|
_savedomainconf "Le_Preferred_Chain" "$_preferred_chain" "base64"
|
|
else
|
|
_cleardomainconf "Le_Preferred_Chain"
|
|
fi
|
|
|
|
Le_API="$ACME_DIRECTORY"
|
|
_savedomainconf "Le_API" "$Le_API"
|
|
|
|
_info "Using CA: $ACME_DIRECTORY"
|
|
if [ "$_alt_domains" = "$NO_VALUE" ]; then
|
|
_alt_domains=""
|
|
fi
|
|
|
|
if ! _on_before_issue "$_web_roots" "$_main_domain" "$_alt_domains" "$_pre_hook" "$_local_addr"; then
|
|
_err "_on_before_issue."
|
|
return 1
|
|
fi
|
|
|
|
_saved_account_key_hash="$(_readcaconf "CA_KEY_HASH")"
|
|
_debug2 _saved_account_key_hash "$_saved_account_key_hash"
|
|
|
|
if [ -z "$ACCOUNT_URL" ] || [ -z "$_saved_account_key_hash" ] || [ "$_saved_account_key_hash" != "$(__calcAccountKeyHash)" ]; then
|
|
if ! _regAccount "$_accountkeylength"; then
|
|
_on_issue_err "$_post_hook"
|
|
return 1
|
|
fi
|
|
else
|
|
_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)
|
|
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 [ "$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 "Error creating CSR."
|
|
_clearup
|
|
_on_issue_err "$_post_hook"
|
|
return 1
|
|
fi
|
|
fi
|
|
|
|
_savedomainconf "Le_Keylength" "$_key_length"
|
|
|
|
vlist="$Le_Vlist"
|
|
_cleardomainconf "Le_Vlist"
|
|
_debug "Getting domain auth token for each domain"
|
|
sep='#'
|
|
dvsep=','
|
|
if [ -z "$vlist" ]; then
|
|
#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
|
|
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
|
|
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
|
|
|
|
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"
|
|
|
|
_index=0
|
|
_currentRoot=""
|
|
_w_index=1
|
|
while true; do
|
|
d="$(echo "$_main_domain,$_alt_domains," | cut -d , -f "$_w_index")"
|
|
_w_index="$(_math "$_w_index" + 1)"
|
|
_debug d "$d"
|
|
if [ -z "$d" ]; then
|
|
break
|
|
fi
|
|
_info "Getting webroot for domain" "$d"
|
|
_index=$(_math $_index + 1)
|
|
_w="$(echo $_web_roots | cut -d , -f $_index)"
|
|
_debug _w "$_w"
|
|
if [ "$_w" ]; then
|
|
_currentRoot="$_w"
|
|
fi
|
|
_debug "_currentRoot" "$_currentRoot"
|
|
|
|
vtype="$VTYPE_HTTP"
|
|
#todo, v2 wildcard force to use dns
|
|
if _startswith "$_currentRoot" "$W_DNS"; then
|
|
vtype="$VTYPE_DNS"
|
|
fi
|
|
|
|
if [ "$_currentRoot" = "$W_ALPN" ]; then
|
|
vtype="$VTYPE_ALPN"
|
|
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
|
|
|
|
keyauthorization=""
|
|
|
|
if echo "$response" | grep '"status":"valid"' >/dev/null 2>&1; then
|
|
_debug "$d is already valid."
|
|
keyauthorization="$STATE_VERIFIED"
|
|
_debug keyauthorization "$keyauthorization"
|
|
fi
|
|
|
|
entry="$(echo "$response" | _egrep_o '[^\{]*"type":"'$vtype'"[^\}]*')"
|
|
_debug entry "$entry"
|
|
|
|
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
|
|
_clearup
|
|
_on_issue_err "$_post_hook"
|
|
return 1
|
|
fi
|
|
|
|
if [ -z "$keyauthorization" ]; then
|
|
token="$(echo "$entry" | _egrep_o '"token":"[^"]*' | cut -d : -f 2 | tr -d '"')"
|
|
_debug token "$token"
|
|
|
|
if [ -z "$token" ]; then
|
|
_err "Cannot get domain token $entry"
|
|
_clearup
|
|
_on_issue_err "$_post_hook"
|
|
return 1
|
|
fi
|
|
|
|
uri="$(echo "$entry" | _egrep_o '"url":"[^"]*' | cut -d '"' -f 4 | _head_n 1)"
|
|
|
|
_debug uri "$uri"
|
|
|
|
if [ -z "$uri" ]; then
|
|
_err "Cannot get domain URI $entry"
|
|
_clearup
|
|
_on_issue_err "$_post_hook"
|
|
return 1
|
|
fi
|
|
keyauthorization="$token.$thumbprint"
|
|
_debug keyauthorization "$keyauthorization"
|
|
fi
|
|
|
|
dvlist="$d$sep$keyauthorization$sep$uri$sep$vtype$sep$_currentRoot$sep$_authz_url"
|
|
_debug dvlist "$dvlist"
|
|
|
|
vlist="$vlist$dvlist$dvsep"
|
|
|
|
done
|
|
_debug vlist "$vlist"
|
|
#add entry
|
|
dns_entries=""
|
|
dnsadded=""
|
|
ventries=$(echo "$vlist" | tr "$dvsep" ' ')
|
|
_alias_index=1
|
|
for ventry in $ventries; do
|
|
d=$(echo "$ventry" | cut -d "$sep" -f 1)
|
|
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 has already been verified, skipping $vtype."
|
|
_alias_index="$(_math "$_alias_index" + 1)"
|
|
continue
|
|
fi
|
|
|
|
if [ "$vtype" = "$VTYPE_DNS" ]; then
|
|
dnsadded='0'
|
|
_dns_root_d="$d"
|
|
if _startswith "$_dns_root_d" "*."; then
|
|
_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
|
|
if _startswith "$_d_alias" "$DNS_ALIAS_PREFIX"; then
|
|
txtdomain="$(echo "$_d_alias" | sed "s/$DNS_ALIAS_PREFIX//")"
|
|
else
|
|
txtdomain="_acme-challenge.$_d_alias"
|
|
fi
|
|
dns_entry="${_dns_root_d}${dvsep}_acme-challenge.$_dns_root_d$dvsep$txtdomain$dvsep$_currentRoot"
|
|
else
|
|
txtdomain="_acme-challenge.$_dns_root_d"
|
|
dns_entry="${_dns_root_d}${dvsep}_acme-challenge.$_dns_root_d$dvsep$dvsep$_currentRoot"
|
|
fi
|
|
|
|
_debug txtdomain "$txtdomain"
|
|
txt="$(printf "%s" "$keyauthorization" | _digest "sha256" | _url_replace)"
|
|
_debug txt "$txt"
|
|
|
|
d_api="$(_findHook "$_dns_root_d" $_SUB_FOLDER_DNSAPI "$_currentRoot")"
|
|
_debug d_api "$d_api"
|
|
|
|
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"
|
|
else
|
|
if [ "$_currentRoot" != "$W_DNS" ]; then
|
|
_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 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 "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 incorrect. Make sure it has a function named: $addcommand"
|
|
return 1
|
|
fi
|
|
_info "Adding TXT value: $txt for domain: $txtdomain"
|
|
if ! $addcommand "$txtdomain" "$txt"; then
|
|
_err "Error adding TXT record to domain: $txtdomain"
|
|
return 1
|
|
fi
|
|
_info "The TXT record has been successfully added."
|
|
)
|
|
|
|
if [ "$?" != "0" ]; then
|
|
_on_issue_err "$_post_hook" "$vlist"
|
|
_clearup
|
|
return 1
|
|
fi
|
|
dns_entries="$dns_entries$dns_entry
|
|
"
|
|
_debug2 "$dns_entries"
|
|
dnsadded='1'
|
|
fi
|
|
done
|
|
|
|
if [ "$dnsadded" = '0' ]; then
|
|
_savedomainconf "Le_Vlist" "$vlist"
|
|
_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
|
|
# 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. Sleeping for 20 seconds first."
|
|
_sleep 20
|
|
if ! _check_dns_entries; then
|
|
_err "Error checking DNS."
|
|
_on_issue_err "$_post_hook"
|
|
_clearup
|
|
return 1
|
|
fi
|
|
else
|
|
_savedomainconf "Le_DNSSleep" "$Le_DNSSleep"
|
|
_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 verification"
|
|
|
|
_ncIndex=1
|
|
ventries=$(echo "$vlist" | tr "$dvsep" ' ')
|
|
for ventry in $ventries; do
|
|
d=$(echo "$ventry" | cut -d "$sep" -f 1)
|
|
keyauthorization=$(echo "$ventry" | cut -d "$sep" -f 2)
|
|
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, skipping $vtype."
|
|
continue
|
|
fi
|
|
|
|
_info "Verifying: $d"
|
|
_debug "d" "$d"
|
|
_debug "keyauthorization" "$keyauthorization"
|
|
_debug "uri" "$uri"
|
|
_debug "_authz_url" "$_authz_url"
|
|
removelevel=""
|
|
token="$(printf "%s" "$keyauthorization" | cut -d '.' -f 1)"
|
|
|
|
_debug "_currentRoot" "$_currentRoot"
|
|
|
|
if [ "$vtype" = "$VTYPE_HTTP" ]; then
|
|
if [ "$_currentRoot" = "$NO_VALUE" ]; then
|
|
_info "Standalone mode server"
|
|
_ncaddr="$(_getfield "$_local_addr" "$_ncIndex")"
|
|
_ncIndex="$(_math $_ncIndex + 1)"
|
|
_startserver "$keyauthorization" "$_ncaddr"
|
|
if [ "$?" != "0" ]; then
|
|
_clearup
|
|
_on_issue_err "$_post_hook" "$vlist"
|
|
return 1
|
|
fi
|
|
sleep 1
|
|
_debug serverproc "$serverproc"
|
|
elif [ "$_currentRoot" = "$MODE_STATELESS" ]; then
|
|
_info "Stateless mode for domain: $d"
|
|
_sleep 1
|
|
elif _startswith "$_currentRoot" "$NGINX"; then
|
|
_info "Nginx mode for domain: $d"
|
|
#set up nginx server
|
|
FOUND_REAL_NGINX_CONF=""
|
|
BACKUP_NGINX_CONF=""
|
|
if ! _setNginx "$d" "$_currentRoot" "$thumbprint"; then
|
|
_clearup
|
|
_on_issue_err "$_post_hook" "$vlist"
|
|
return 1
|
|
fi
|
|
|
|
if [ "$FOUND_REAL_NGINX_CONF" ]; then
|
|
_realConf="$FOUND_REAL_NGINX_CONF"
|
|
_backup="$BACKUP_NGINX_CONF"
|
|
_debug _realConf "$_realConf"
|
|
NGINX_RESTORE_VLIST="$d$sep$_realConf$sep$_backup$dvsep$NGINX_RESTORE_VLIST"
|
|
fi
|
|
_sleep 1
|
|
else
|
|
if [ "$_currentRoot" = "apache" ]; then
|
|
wellknown_path="$ACME_DIR"
|
|
else
|
|
wellknown_path="$_currentRoot/.well-known/acme-challenge"
|
|
if [ ! -d "$_currentRoot/.well-known" ]; then
|
|
removelevel='1'
|
|
elif [ ! -d "$_currentRoot/.well-known/acme-challenge" ]; then
|
|
removelevel='2'
|
|
else
|
|
removelevel='3'
|
|
fi
|
|
fi
|
|
|
|
_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: Cannot write token to file: $wellknown_path/$token"
|
|
_clearupwebbroot "$_currentRoot" "$removelevel" "$token"
|
|
_clearup
|
|
_on_issue_err "$_post_hook" "$vlist"
|
|
return 1
|
|
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 "Error starting TLS server."
|
|
_clearupwebbroot "$_currentRoot" "$removelevel" "$token"
|
|
_clearup
|
|
_on_issue_err "$_post_hook" "$vlist"
|
|
return 1
|
|
fi
|
|
fi
|
|
|
|
if ! __trigger_validation "$uri" "$keyauthorization" "$vtype"; then
|
|
_err "$d: Cannot get challenge: $response"
|
|
_clearupwebbroot "$_currentRoot" "$removelevel" "$token"
|
|
_clearup
|
|
_on_issue_err "$_post_hook" "$vlist"
|
|
return 1
|
|
fi
|
|
|
|
if [ "$code" ] && [ "$code" != '202' ]; then
|
|
if [ "$code" = '200' ]; then
|
|
_debug "Trigger validation code: $code"
|
|
else
|
|
_err "$d: Challenge error: $response"
|
|
_clearupwebbroot "$_currentRoot" "$removelevel" "$token"
|
|
_clearup
|
|
_on_issue_err "$_post_hook" "$vlist"
|
|
return 1
|
|
fi
|
|
fi
|
|
|
|
waittimes=0
|
|
if [ -z "$MAX_RETRY_TIMES" ]; then
|
|
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"
|
|
_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 '"')
|
|
_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: Invalid status. Verification error details: $errordetail"
|
|
else
|
|
_err "$d: Invalid status, Verification error: $error"
|
|
fi
|
|
if [ "$DEBUG" ]; then
|
|
if [ "$vtype" = "$VTYPE_HTTP" ]; then
|
|
_debug "Debug: GET token URL."
|
|
_get "http://$d/.well-known/acme-challenge/$token" "" 1
|
|
fi
|
|
fi
|
|
_clearupwebbroot "$_currentRoot" "$removelevel" "$token"
|
|
_clearup
|
|
_on_issue_err "$_post_hook" "$vlist"
|
|
return 1
|
|
fi
|
|
|
|
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: 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
|
|
done
|
|
|
|
done
|
|
|
|
_clearup
|
|
_info "Verification finished, beginning signing."
|
|
der="$(_getfile "${CSR_PATH}" "${BEGIN_CSR}" "${END_CSR}" | tr -d "\r\n" | _url_replace)"
|
|
|
|
_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
|
|
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
|
|
#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
|
|
_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"
|
|
_savedomainconf "Le_LinkCert" "$Le_LinkCert"
|
|
|
|
if [ -z "$Le_LinkCert" ] || ! _checkcert "$CERT_PATH"; then
|
|
response="$(echo "$response" | _dbase64 "multiline" | tr -d '\0' | _normalizeJson)"
|
|
_err "Signing failed: $(echo "$response" | _egrep_o '"detail":"[^"]*"')"
|
|
_on_issue_err "$_post_hook"
|
|
return 1
|
|
fi
|
|
|
|
if [ "$Le_LinkCert" ]; then
|
|
_info "$(__green "Cert success.")"
|
|
cat "$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")"
|
|
fi
|
|
|
|
if [ ! "$USER_PATH" ] || [ ! "$_ACME_IN_CRON" ]; then
|
|
USER_PATH="$PATH"
|
|
_saveaccountconf "USER_PATH" "$USER_PATH"
|
|
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 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=$(_time2str "$Le_CertCreateTime")
|
|
_savedomainconf "Le_CertCreateTimeStr" "$Le_CertCreateTimeStr"
|
|
|
|
if [ -z "$Le_RenewalDays" ] || [ "$Le_RenewalDays" -lt "0" ]; then
|
|
Le_RenewalDays="$DEFAULT_RENEW"
|
|
else
|
|
_savedomainconf "Le_RenewalDays" "$Le_RenewalDays"
|
|
fi
|
|
|
|
if [ "$CA_BUNDLE" ]; then
|
|
_saveaccountconf CA_BUNDLE "$CA_BUNDLE"
|
|
else
|
|
_clearaccountconf "CA_BUNDLE"
|
|
fi
|
|
|
|
if [ "$CA_PATH" ]; then
|
|
_saveaccountconf CA_PATH "$CA_PATH"
|
|
else
|
|
_clearaccountconf "CA_PATH"
|
|
fi
|
|
|
|
if [ "$HTTPS_INSECURE" ]; then
|
|
_saveaccountconf HTTPS_INSECURE "$HTTPS_INSECURE"
|
|
else
|
|
_clearaccountconf "HTTPS_INSECURE"
|
|
fi
|
|
|
|
if [ "$Le_Listen_V4" ]; then
|
|
_savedomainconf "Le_Listen_V4" "$Le_Listen_V4"
|
|
_cleardomainconf Le_Listen_V6
|
|
elif [ "$Le_Listen_V6" ]; then
|
|
_savedomainconf "Le_Listen_V6" "$Le_Listen_V6"
|
|
_cleardomainconf Le_Listen_V4
|
|
fi
|
|
|
|
if [ "$Le_ForceNewDomainKey" = "1" ]; then
|
|
_savedomainconf "Le_ForceNewDomainKey" "$Le_ForceNewDomainKey"
|
|
else
|
|
_cleardomainconf Le_ForceNewDomainKey
|
|
fi
|
|
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"
|
|
_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"
|
|
_savedomainconf "Le_RealKeyPath" "$_real_key"
|
|
_savedomainconf "Le_ReloadCmd" "$_reload_cmd" "base64"
|
|
_savedomainconf "Le_RealFullChainPath" "$_real_fullchain"
|
|
if ! _installcert "$_main_domain" "$_real_cert" "$_real_key" "$_real_ca" "$_real_fullchain" "$_reload_cmd"; then
|
|
return 1
|
|
fi
|
|
fi
|
|
|
|
if ! _on_issue_success "$_post_hook" "$_renew_hook"; then
|
|
_err "Error calling hook."
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
#in_out_cert out_fullchain out_ca
|
|
_split_cert_chain() {
|
|
_certf="$1"
|
|
_fullchainf="$2"
|
|
_caf="$3"
|
|
if [ "$(grep -- "$BEGIN_CERT" "$_certf" | wc -l)" -gt "1" ]; then
|
|
_debug "Found cert chain"
|
|
cat "$_certf" >"$_fullchainf"
|
|
_end_n="$(grep -n -- "$END_CERT" "$_fullchainf" | _head_n 1 | cut -d : -f 1)"
|
|
_debug _end_n "$_end_n"
|
|
sed -n "1,${_end_n}p" "$_fullchainf" >"$_certf"
|
|
_end_n="$(_math $_end_n + 1)"
|
|
sed -n "${_end_n},9999p" "$_fullchainf" >"$_caf"
|
|
fi
|
|
}
|
|
|
|
#domain [isEcc] [server]
|
|
renew() {
|
|
Le_Domain="$1"
|
|
if [ -z "$Le_Domain" ]; then
|
|
_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 "Renewing: '$Le_Domain'")"
|
|
if [ ! -f "$DOMAIN_CONF" ]; then
|
|
_info "'$Le_Domain' is not an issued domain, skipping."
|
|
return $RENEW_SKIP
|
|
fi
|
|
|
|
if [ "$Le_RenewalDays" ]; then
|
|
_savedomainconf Le_RenewalDays "$Le_RenewalDays"
|
|
fi
|
|
|
|
. "$DOMAIN_CONF"
|
|
_debug Le_API "$Le_API"
|
|
|
|
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
|
|
|
|
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 "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 "Skipping invalid cert for: $Le_Domain"
|
|
return $RENEW_SKIP
|
|
fi
|
|
|
|
_ACME_IS_RENEW="1"
|
|
Le_ReloadCmd="$(_readdomainconf Le_ReloadCmd)"
|
|
Le_PreHook="$(_readdomainconf Le_PreHook)"
|
|
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"
|
|
fi
|
|
|
|
if [ "$Le_DeployHook" ]; then
|
|
_deploy "$Le_Domain" "$Le_DeployHook"
|
|
res="$?"
|
|
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] [server]
|
|
renewAll() {
|
|
_initpath
|
|
_clearCA
|
|
_stopRenewOnError="$1"
|
|
_debug "_stopRenewOnError" "$_stopRenewOnError"
|
|
|
|
_server="$2"
|
|
_debug "_server" "$_server"
|
|
|
|
_ret="0"
|
|
_success_msg=""
|
|
_error_msg=""
|
|
_skipped_msg=""
|
|
_error_level=$NOTIFY_LEVEL_SKIP
|
|
_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, skipping: $di"
|
|
continue
|
|
fi
|
|
d=$(basename "$di")
|
|
_debug d "$d"
|
|
(
|
|
if _endswith "$d" "$ECC_SUFFIX"; then
|
|
_isEcc=$(echo "$d" | cut -d "$ECC_SEP" -f 2)
|
|
d=$(echo "$d" | cut -d "$ECC_SEP" -f 1)
|
|
fi
|
|
renew "$d" "$_isEcc" "$_server"
|
|
)
|
|
rc="$?"
|
|
_debug "Return code: $rc"
|
|
if [ "$rc" = "0" ]; then
|
|
if [ $_error_level -gt $NOTIFY_LEVEL_RENEW ]; then
|
|
_error_level="$NOTIFY_LEVEL_RENEW"
|
|
_notify_code=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
|
|
if [ $_error_level -gt $NOTIFY_LEVEL_SKIP ]; then
|
|
_error_level="$NOTIFY_LEVEL_SKIP"
|
|
_notify_code=$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
|
|
"
|
|
else
|
|
if [ $_error_level -gt $NOTIFY_LEVEL_ERROR ]; then
|
|
_error_level="$NOTIFY_LEVEL_ERROR"
|
|
_notify_code=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 renewing $d, stopping."
|
|
_ret="$rc"
|
|
break
|
|
else
|
|
_ret="$rc"
|
|
_err "Error renewing $d."
|
|
fi
|
|
fi
|
|
done
|
|
_debug _error_level "$_error_level"
|
|
_debug _set_level "$_set_level"
|
|
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="Errored certs:
|
|
${_error_msg}
|
|
"
|
|
fi
|
|
if [ "$_success_msg" ]; then
|
|
_msg_subject="${_msg_subject} Success"
|
|
_msg_data="${_msg_data}Successful certs:
|
|
${_success_msg}
|
|
"
|
|
fi
|
|
if [ "$_skipped_msg" ]; then
|
|
_msg_subject="${_msg_subject} Skipped"
|
|
_msg_data="${_msg_data}Skipped certs:
|
|
${_skipped_msg}
|
|
"
|
|
fi
|
|
|
|
_send_notify "$_msg_subject" "$_msg_data" "$NOTIFY_HOOK" "$_notify_code"
|
|
fi
|
|
fi
|
|
|
|
return "$_ret"
|
|
}
|
|
|
|
#csr webroot
|
|
signcsr() {
|
|
_csrfile="$1"
|
|
_csrW="$2"
|
|
if [ -z "$_csrfile" ] || [ -z "$_csrW" ]; then
|
|
_usage "Usage: $PROJECT_ENTRY --sign-csr --csr <csr-file> --webroot <directory>"
|
|
return 1
|
|
fi
|
|
|
|
_real_cert="$3"
|
|
_real_key="$4"
|
|
_real_ca="$5"
|
|
_reload_cmd="$6"
|
|
_real_fullchain="$7"
|
|
_pre_hook="${8}"
|
|
_post_hook="${9}"
|
|
_renew_hook="${10}"
|
|
_local_addr="${11}"
|
|
_challenge_alias="${12}"
|
|
_preferred_chain="${13}"
|
|
|
|
_csrsubj=$(_readSubjectFromCSR "$_csrfile")
|
|
if [ "$?" != "0" ]; then
|
|
_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. Dropping it."
|
|
_csrsubj=""
|
|
fi
|
|
|
|
_csrdomainlist=$(_readSubjectAltNamesFromCSR "$_csrfile")
|
|
if [ "$?" != "0" ]; then
|
|
_err "Cannot read domain list from CSR: $_csrfile"
|
|
return 1
|
|
fi
|
|
_debug "_csrdomainlist" "$_csrdomainlist"
|
|
|
|
if [ -z "$_csrsubj" ]; then
|
|
_csrsubj="$(_getfield "$_csrdomainlist" 1)"
|
|
_debug _csrsubj "$_csrsubj"
|
|
_csrdomainlist="$(echo "$_csrdomainlist" | cut -d , -f 2-)"
|
|
_debug "_csrdomainlist" "$_csrdomainlist"
|
|
fi
|
|
|
|
if [ -z "$_csrsubj" ]; then
|
|
_err "Cannot read subject from CSR: $_csrfile"
|
|
return 1
|
|
fi
|
|
|
|
_csrkeylength=$(_readKeyLengthFromCSR "$_csrfile")
|
|
if [ "$?" != "0" ] || [ -z "$_csrkeylength" ]; then
|
|
_err "Cannot read key length from CSR: $_csrfile"
|
|
return 1
|
|
fi
|
|
|
|
_initpath "$_csrsubj" "$_csrkeylength"
|
|
mkdir -p "$DOMAIN_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" "$_preferred_chain"
|
|
|
|
}
|
|
|
|
showcsr() {
|
|
_csrfile="$1"
|
|
_csrd="$2"
|
|
if [ -z "$_csrfile" ] && [ -z "$_csrd" ]; then
|
|
_usage "Usage: $PROJECT_ENTRY --show-csr --csr <csr-file>"
|
|
return 1
|
|
fi
|
|
|
|
_initpath
|
|
|
|
_csrsubj=$(_readSubjectFromCSR "$_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 "Cannot read domain list from CSR: $_csrfile"
|
|
return 1
|
|
fi
|
|
_debug "_csrdomainlist" "$_csrdomainlist"
|
|
|
|
_info "SubjectAltNames=$_csrdomainlist"
|
|
|
|
_csrkeylength=$(_readKeyLengthFromCSR "$_csrfile")
|
|
if [ "$?" != "0" ] || [ -z "$_csrkeylength" ]; then
|
|
_err "Cannot read key length from CSR: $_csrfile"
|
|
return 1
|
|
fi
|
|
_info "KeyLength=$_csrkeylength"
|
|
}
|
|
|
|
#listraw domain
|
|
list() {
|
|
_raw="$1"
|
|
_domain="$2"
|
|
_initpath
|
|
|
|
_sep="|"
|
|
if [ "$_raw" ]; then
|
|
if [ -z "$_domain" ]; then
|
|
printf "%s\n" "Main_Domain${_sep}KeyLength${_sep}SAN_Domains${_sep}CA${_sep}Created${_sep}Renew"
|
|
fi
|
|
for di in "${CERT_HOME}"/*.*/; do
|
|
d=$(basename "$di")
|
|
_debug d "$d"
|
|
(
|
|
if _endswith "$d" "$ECC_SUFFIX"; then
|
|
_isEcc="ecc"
|
|
d=$(echo "$d" | cut -d "$ECC_SEP" -f 1)
|
|
fi
|
|
DOMAIN_CONF="$di/$d.conf"
|
|
if [ -f "$DOMAIN_CONF" ]; then
|
|
. "$DOMAIN_CONF"
|
|
_ca="$(_getCAShortName "$Le_API")"
|
|
if [ -z "$_domain" ]; then
|
|
printf "%s\n" "$Le_Domain${_sep}\"$Le_Keylength\"${_sep}$Le_Alt${_sep}$_ca${_sep}$Le_CertCreateTimeStr${_sep}$Le_NextRenewTimeStr"
|
|
else
|
|
if [ "$_domain" = "$d" ]; then
|
|
cat "$DOMAIN_CONF"
|
|
fi
|
|
fi
|
|
fi
|
|
)
|
|
done
|
|
else
|
|
if _exists column; then
|
|
list "raw" "$_domain" | column -t -s "$_sep"
|
|
else
|
|
list "raw" "$_domain" | tr "$_sep" '\t'
|
|
fi
|
|
fi
|
|
|
|
}
|
|
|
|
_deploy() {
|
|
_d="$1"
|
|
_hooks="$2"
|
|
|
|
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 was not found."
|
|
return 1
|
|
fi
|
|
_debug _deployApi "$_deployApi"
|
|
|
|
if ! (
|
|
if ! . "$_deployApi"; then
|
|
_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. 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 deploying for domain: $_d"
|
|
return 1
|
|
fi
|
|
); then
|
|
_err "Error encountered while deploying."
|
|
return 1
|
|
else
|
|
_info "$(__green Success)"
|
|
fi
|
|
done
|
|
}
|
|
|
|
#domain hooks
|
|
deploy() {
|
|
_d="$1"
|
|
_hooks="$2"
|
|
_isEcc="$3"
|
|
if [ -z "$_hooks" ]; then
|
|
_usage "Usage: $PROJECT_ENTRY --deploy --domain <domain.tld> --deploy-hook <hookname> [--ecc] "
|
|
return 1
|
|
fi
|
|
|
|
_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 "Cannot find path: '$DOMAIN_PATH'"
|
|
return 1
|
|
fi
|
|
|
|
_debug2 DOMAIN_CONF "$DOMAIN_CONF"
|
|
. "$DOMAIN_CONF"
|
|
|
|
_savedomainconf Le_DeployHook "$_hooks"
|
|
|
|
_deploy "$_d" "$_hooks"
|
|
}
|
|
|
|
installcert() {
|
|
_main_domain="$1"
|
|
if [ -z "$_main_domain" ]; then
|
|
_usage "Usage: $PROJECT_ENTRY --install-cert --domain <domain.tld> [--ecc] [--cert-file <file>] [--key-file <file>] [--ca-file <file>] [ --reloadcmd <command>] [--fullchain-file <file>]"
|
|
return 1
|
|
fi
|
|
|
|
_real_cert="$2"
|
|
_real_key="$3"
|
|
_real_ca="$4"
|
|
_reload_cmd="$5"
|
|
_real_fullchain="$6"
|
|
_isEcc="$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 "Cannot find path: '$DOMAIN_PATH'"
|
|
return 1
|
|
fi
|
|
|
|
_savedomainconf "Le_RealCertPath" "$_real_cert"
|
|
_savedomainconf "Le_RealCACertPath" "$_real_ca"
|
|
_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"
|
|
}
|
|
|
|
#domain cert key ca fullchain reloadcmd backup-prefix
|
|
_installcert() {
|
|
_main_domain="$1"
|
|
_real_cert="$2"
|
|
_real_key="$3"
|
|
_real_ca="$4"
|
|
_real_fullchain="$5"
|
|
_reload_cmd="$6"
|
|
_backup_prefix="$7"
|
|
|
|
if [ "$_real_cert" = "$NO_VALUE" ]; then
|
|
_real_cert=""
|
|
fi
|
|
if [ "$_real_key" = "$NO_VALUE" ]; then
|
|
_real_key=""
|
|
fi
|
|
if [ "$_real_ca" = "$NO_VALUE" ]; then
|
|
_real_ca=""
|
|
fi
|
|
if [ "$_reload_cmd" = "$NO_VALUE" ]; then
|
|
_reload_cmd=""
|
|
fi
|
|
if [ "$_real_fullchain" = "$NO_VALUE" ]; then
|
|
_real_fullchain=""
|
|
fi
|
|
|
|
_backup_path="$DOMAIN_BACKUP_PATH/$_backup_prefix"
|
|
mkdir -p "$_backup_path"
|
|
|
|
if [ "$_real_cert" ]; then
|
|
_info "Installing cert to: $_real_cert"
|
|
if [ -f "$_real_cert" ] && [ ! "$_ACME_IS_RENEW" ]; then
|
|
cp "$_real_cert" "$_backup_path/cert.bak"
|
|
fi
|
|
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"
|
|
if [ "$_real_ca" = "$_real_cert" ]; then
|
|
echo "" >>"$_real_ca"
|
|
cat "$CA_CERT_PATH" >>"$_real_ca" || return 1
|
|
else
|
|
if [ -f "$_real_ca" ] && [ ! "$_ACME_IS_RENEW" ]; then
|
|
cp "$_real_ca" "$_backup_path/ca.bak"
|
|
fi
|
|
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"
|
|
if [ -f "$_real_key" ] && [ ! "$_ACME_IS_RENEW" ]; then
|
|
cp "$_real_key" "$_backup_path/key.bak"
|
|
fi
|
|
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"
|
|
if [ -f "$_real_fullchain" ] && [ ! "$_ACME_IS_RENEW" ]; then
|
|
cp "$_real_fullchain" "$_backup_path/fullchain.bak"
|
|
fi
|
|
if [ "$_real_fullchain" != "$CERT_FULLCHAIN_PATH" ]; then
|
|
cat "$CERT_FULLCHAIN_PATH" >"$_real_fullchain" || return 1
|
|
fi
|
|
fi
|
|
|
|
if [ "$_reload_cmd" ]; then
|
|
_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 successful")"
|
|
else
|
|
_err "Reload error for: $Le_Domain"
|
|
fi
|
|
fi
|
|
|
|
}
|
|
|
|
__read_password() {
|
|
unset _pp
|
|
prompt="Enter Password:"
|
|
while IFS= read -p "$prompt" -r -s -n 1 char; do
|
|
if [ "$char" = $'\0' ]; then
|
|
break
|
|
fi
|
|
prompt='*'
|
|
_pp="$_pp$char"
|
|
done
|
|
echo "$_pp"
|
|
}
|
|
|
|
_install_win_taskscheduler() {
|
|
_lesh="$1"
|
|
_centry="$2"
|
|
_randomminute="$3"
|
|
if ! _exists cygpath; then
|
|
_err "cygpath not found"
|
|
return 1
|
|
fi
|
|
if ! _exists schtasks; then
|
|
_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 "Cannot find bash path"
|
|
return 1
|
|
fi
|
|
_myname="$(whoami)"
|
|
_debug "_myname" "$_myname"
|
|
if [ -z "$_myname" ]; then
|
|
_err "Can not find own username"
|
|
return 1
|
|
fi
|
|
_debug "_lesh" "$_lesh"
|
|
|
|
_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
|
|
echo 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'\"" | cmd.exe >/dev/null
|
|
echo
|
|
|
|
}
|
|
|
|
_uninstall_win_taskscheduler() {
|
|
if ! _exists schtasks; then
|
|
_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 was not found."
|
|
else
|
|
_info "Removing $_WINDOWS_SCHEDULER_NAME"
|
|
echo SCHTASKS /delete /f /tn "$_WINDOWS_SCHEDULER_NAME" | cmd.exe >/dev/null
|
|
fi
|
|
}
|
|
|
|
#confighome
|
|
installcronjob() {
|
|
_c_home="$1"
|
|
_initpath
|
|
_CRONTAB="crontab"
|
|
if [ -f "$LE_WORKING_DIR/$PROJECT_ENTRY" ]; then
|
|
lesh="\"$LE_WORKING_DIR\"/$PROJECT_ENTRY"
|
|
else
|
|
_debug "_SCRIPT_" "$_SCRIPT_"
|
|
_script="$(_readlink "$_SCRIPT_")"
|
|
_debug _script "$_script"
|
|
if [ -f "$_script" ]; then
|
|
_info "Usinging 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"
|
|
fi
|
|
|
|
if ! _exists "$_CRONTAB"; then
|
|
if _exists cygpath && _exists schtasks.exe; then
|
|
_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 "Successfully installed Windows scheduler task."
|
|
return 0
|
|
else
|
|
_err "Failed to install Windows scheduler task."
|
|
return 1
|
|
fi
|
|
fi
|
|
_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_STDIN="$_CRONTAB --"
|
|
else
|
|
_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 "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
|
|
}
|
|
|
|
uninstallcronjob() {
|
|
_CRONTAB="crontab"
|
|
if ! _exists "$_CRONTAB" && _exists "fcrontab"; then
|
|
_CRONTAB="fcrontab"
|
|
fi
|
|
|
|
if ! _exists "$_CRONTAB"; then
|
|
if _exists cygpath && _exists schtasks.exe; then
|
|
_info "It seems you are on Windows, let's uninstall the Windows scheduler task."
|
|
if _uninstall_win_taskscheduler; then
|
|
_info "Successfully uninstalled Windows scheduler task."
|
|
return 0
|
|
else
|
|
_err "Failed to uninstall Windows scheduler task."
|
|
return 1
|
|
fi
|
|
fi
|
|
return
|
|
fi
|
|
_info "Removing cron job"
|
|
cr="$($_CRONTAB -l | grep "$PROJECT_ENTRY --cron")"
|
|
if [ "$cr" ]; 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 -
|
|
fi
|
|
LE_WORKING_DIR="$(echo "$cr" | cut -d ' ' -f 9 | tr -d '"')"
|
|
_info LE_WORKING_DIR "$LE_WORKING_DIR"
|
|
if _contains "$cr" "--config-home"; then
|
|
LE_CONFIG_HOME="$(echo "$cr" | cut -d ' ' -f 11 | tr -d '"')"
|
|
_debug LE_CONFIG_HOME "$LE_CONFIG_HOME"
|
|
fi
|
|
fi
|
|
_initpath
|
|
|
|
}
|
|
|
|
#domain isECC revokeReason
|
|
revoke() {
|
|
Le_Domain="$1"
|
|
if [ -z "$Le_Domain" ]; then
|
|
_usage "Usage: $PROJECT_ENTRY --revoke --domain <domain.tld> [--ecc]"
|
|
return 1
|
|
fi
|
|
|
|
_isEcc="$2"
|
|
_reason="$3"
|
|
if [ -z "$_reason" ]; then
|
|
_reason="0"
|
|
fi
|
|
_initpath "$Le_Domain" "$_isEcc"
|
|
if [ ! -f "$DOMAIN_CONF" ]; then
|
|
_err "$Le_Domain is not an issued domain, skipping."
|
|
return 1
|
|
fi
|
|
|
|
if [ ! -f "$CERT_PATH" ]; then
|
|
_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, skipping."
|
|
return 1
|
|
fi
|
|
|
|
_initAPI
|
|
|
|
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 "Trying domain key."
|
|
if _send_signed_request "$uri" "$data" "" "$CERT_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 using domain key."
|
|
_err "$response"
|
|
fi
|
|
fi
|
|
else
|
|
_info "Domain key file doesn't exist."
|
|
fi
|
|
return 1
|
|
}
|
|
|
|
#domain ecc
|
|
remove() {
|
|
Le_Domain="$1"
|
|
if [ -z "$Le_Domain" ]; then
|
|
_usage "Usage: $PROJECT_ENTRY --remove --domain <domain.tld> [--ecc]"
|
|
return 1
|
|
fi
|
|
|
|
_isEcc="$2"
|
|
|
|
_initpath "$Le_Domain" "$_isEcc"
|
|
_removed_conf="$DOMAIN_CONF.removed"
|
|
if [ ! -f "$DOMAIN_CONF" ]; then
|
|
if [ -f "$_removed_conf" ]; then
|
|
_err "$Le_Domain has already been removed. You can remove the folder by yourself: $DOMAIN_PATH"
|
|
else
|
|
_err "$Le_Domain is not an issued domain, skipping."
|
|
fi
|
|
return 1
|
|
fi
|
|
|
|
if mv "$DOMAIN_CONF" "$_removed_conf"; then
|
|
_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 "Failed to remove $Le_Domain."
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
#domain vtype
|
|
_deactivate() {
|
|
_d_domain="$1"
|
|
_d_type="$2"
|
|
_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="$(_getIdType "$_d_domain")"
|
|
entry="$(echo "$response" | _egrep_o '[^\{]*"type":"'$vtype'"[^\}]*')"
|
|
_debug entry "$entry"
|
|
if [ -z "$entry" ]; then
|
|
_err "$d: Cannot get domain token"
|
|
return 1
|
|
fi
|
|
token="$(echo "$entry" | _egrep_o '"token":"[^"]*' | cut -d : -f 2 | tr -d '"')"
|
|
_debug token "$token"
|
|
|
|
uri="$(echo "$entry" | _egrep_o "\"$_URL_NAME\":\"[^\"]*" | cut -d : -f 2,3 | tr -d '"')"
|
|
_debug uri "$uri"
|
|
|
|
keyauthorization="$token.$thumbprint"
|
|
_debug keyauthorization "$keyauthorization"
|
|
__trigger_validation "$uri" "$keyauthorization"
|
|
|
|
fi
|
|
|
|
_d_i=0
|
|
_d_max_retry=$(echo "$entries" | wc -l)
|
|
while [ "$_d_i" -lt "$_d_max_retry" ]; do
|
|
_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 entries found."
|
|
break
|
|
fi
|
|
|
|
_vtype="$(echo "$entry" | _egrep_o '"type": *"[^"]*"' | cut -d : -f 2 | tr -d '"')"
|
|
_debug _vtype "$_vtype"
|
|
_info "Found $_vtype"
|
|
|
|
uri="$(echo "$entry" | _egrep_o "\"$_URL_NAME\":\"[^\"]*\"" | tr -d '" ' | cut -d : -f 2-)"
|
|
_debug uri "$uri"
|
|
|
|
if [ "$_d_type" ] && [ "$_d_type" != "$_vtype" ]; then
|
|
_info "Skipping $_vtype"
|
|
continue
|
|
fi
|
|
|
|
_info "Deactivating $_vtype"
|
|
|
|
_djson="{\"status\":\"deactivated\"}"
|
|
|
|
if _send_signed_request "$authzUri" "$_djson" && _contains "$response" '"deactivated"'; then
|
|
_info "Successfully deactivated $_vtype."
|
|
else
|
|
_err "Could not deactivate $_vtype."
|
|
break
|
|
fi
|
|
|
|
done
|
|
_debug "$_d_i"
|
|
if [ "$_d_i" -eq "$_d_max_retry" ]; then
|
|
_info "Successfully deactivated!"
|
|
else
|
|
_err "Deactivation failed."
|
|
fi
|
|
|
|
}
|
|
|
|
deactivate() {
|
|
_d_domain_list="$1"
|
|
_d_type="$2"
|
|
_initpath
|
|
_initAPI
|
|
_debug _d_domain_list "$_d_domain_list"
|
|
if [ -z "$(echo $_d_domain_list | cut -d , -f 1)" ]; then
|
|
_usage "Usage: $PROJECT_ENTRY --deactivate --domain <domain.tld> [--domain <domain2.tld> ...]"
|
|
return 1
|
|
fi
|
|
for _d_dm in $(echo "$_d_domain_list" | tr ',' ' '); do
|
|
if [ -z "$_d_dm" ] || [ "$_d_dm" = "$NO_VALUE" ]; then
|
|
continue
|
|
fi
|
|
if ! _deactivate "$_d_dm" "$_d_type"; then
|
|
return 1
|
|
fi
|
|
done
|
|
}
|
|
|
|
# Detect profile file if not specified as environment variable
|
|
_detect_profile() {
|
|
if [ -n "$PROFILE" -a -f "$PROFILE" ]; then
|
|
echo "$PROFILE"
|
|
return
|
|
fi
|
|
|
|
DETECTED_PROFILE=''
|
|
SHELLTYPE="$(basename "/$SHELL")"
|
|
|
|
if [ "$SHELLTYPE" = "bash" ]; then
|
|
if [ -f "$HOME/.bashrc" ]; then
|
|
DETECTED_PROFILE="$HOME/.bashrc"
|
|
elif [ -f "$HOME/.bash_profile" ]; then
|
|
DETECTED_PROFILE="$HOME/.bash_profile"
|
|
fi
|
|
elif [ "$SHELLTYPE" = "zsh" ]; then
|
|
DETECTED_PROFILE="$HOME/.zshrc"
|
|
fi
|
|
|
|
if [ -z "$DETECTED_PROFILE" ]; then
|
|
if [ -f "$HOME/.profile" ]; then
|
|
DETECTED_PROFILE="$HOME/.profile"
|
|
elif [ -f "$HOME/.bashrc" ]; then
|
|
DETECTED_PROFILE="$HOME/.bashrc"
|
|
elif [ -f "$HOME/.bash_profile" ]; then
|
|
DETECTED_PROFILE="$HOME/.bash_profile"
|
|
elif [ -f "$HOME/.zshrc" ]; then
|
|
DETECTED_PROFILE="$HOME/.zshrc"
|
|
fi
|
|
fi
|
|
|
|
echo "$DETECTED_PROFILE"
|
|
}
|
|
|
|
_initconf() {
|
|
_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
|
|
}
|
|
|
|
# nocron
|
|
_precheck() {
|
|
_nocron="$1"
|
|
|
|
if ! _exists "curl" && ! _exists "wget"; then
|
|
_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 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 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."
|
|
_err "./$PROJECT_ENTRY --install --force"
|
|
return 1
|
|
fi
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
if ! _exists "${ACME_OPENSSL_BIN:-openssl}"; then
|
|
_err "Please install openssl first. ACME_OPENSSL_BIN=$ACME_OPENSSL_BIN"
|
|
_err "We need openssl to generate keys."
|
|
return 1
|
|
fi
|
|
|
|
if ! _exists "socat"; then
|
|
_err "It is recommended to install socat first."
|
|
_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
|
|
}
|
|
|
|
_setShebang() {
|
|
_file="$1"
|
|
_shebang="$2"
|
|
if [ -z "$_shebang" ]; then
|
|
_usage "Usage: file shebang"
|
|
return 1
|
|
fi
|
|
cp "$_file" "$_file.tmp"
|
|
echo "$_shebang" >"$_file"
|
|
sed -n 2,99999p "$_file.tmp" >>"$_file"
|
|
rm -f "$_file.tmp"
|
|
}
|
|
|
|
#confighome
|
|
_installalias() {
|
|
_c_home="$1"
|
|
_initpath
|
|
|
|
_envfile="$LE_WORKING_DIR/$PROJECT_ENTRY.env"
|
|
if [ "$_upgrading" ] && [ "$_upgrading" = "1" ]; then
|
|
echo "$(cat "$_envfile")" | sed "s|^LE_WORKING_DIR.*$||" >"$_envfile"
|
|
echo "$(cat "$_envfile")" | sed "s|^alias le.*$||" >"$_envfile"
|
|
echo "$(cat "$_envfile")" | sed "s|^alias le.sh.*$||" >"$_envfile"
|
|
fi
|
|
|
|
if [ "$_c_home" ]; then
|
|
_c_entry=" --config-home '$_c_home'"
|
|
fi
|
|
|
|
_setopt "$_envfile" "export LE_WORKING_DIR" "=" "\"$LE_WORKING_DIR\""
|
|
if [ "$_c_home" ]; then
|
|
_setopt "$_envfile" "export LE_CONFIG_HOME" "=" "\"$LE_CONFIG_HOME\""
|
|
else
|
|
_sed_i "/^export LE_CONFIG_HOME/d" "$_envfile"
|
|
fi
|
|
_setopt "$_envfile" "alias $PROJECT_ENTRY" "=" "\"$LE_WORKING_DIR/$PROJECT_ENTRY$_c_entry\""
|
|
|
|
_profile="$(_detect_profile)"
|
|
if [ "$_profile" ]; then
|
|
_debug "Found profile: $_profile"
|
|
_info "Installing alias to '$_profile'"
|
|
_setopt "$_profile" ". \"$_envfile\""
|
|
_info "Close and reopen your terminal to start using $PROJECT_NAME"
|
|
else
|
|
_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
|
|
_cshfile="$LE_WORKING_DIR/$PROJECT_ENTRY.csh"
|
|
_csh_profile="$HOME/.cshrc"
|
|
if [ -f "$_csh_profile" ]; then
|
|
_info "Installing alias to '$_csh_profile'"
|
|
_setopt "$_cshfile" "setenv LE_WORKING_DIR" " " "\"$LE_WORKING_DIR\""
|
|
if [ "$_c_home" ]; then
|
|
_setopt "$_cshfile" "setenv LE_CONFIG_HOME" " " "\"$LE_CONFIG_HOME\""
|
|
else
|
|
_sed_i "/^setenv LE_CONFIG_HOME/d" "$_cshfile"
|
|
fi
|
|
_setopt "$_cshfile" "alias $PROJECT_ENTRY" " " "\"$LE_WORKING_DIR/$PROJECT_ENTRY$_c_entry\""
|
|
_setopt "$_csh_profile" "source \"$_cshfile\""
|
|
fi
|
|
|
|
#for tcsh
|
|
_tcsh_profile="$HOME/.tcshrc"
|
|
if [ -f "$_tcsh_profile" ]; then
|
|
_info "Installing alias to '$_tcsh_profile'"
|
|
_setopt "$_cshfile" "setenv LE_WORKING_DIR" " " "\"$LE_WORKING_DIR\""
|
|
if [ "$_c_home" ]; then
|
|
_setopt "$_cshfile" "setenv LE_CONFIG_HOME" " " "\"$LE_CONFIG_HOME\""
|
|
fi
|
|
_setopt "$_cshfile" "alias $PROJECT_ENTRY" " " "\"$LE_WORKING_DIR/$PROJECT_ENTRY$_c_entry\""
|
|
_setopt "$_tcsh_profile" "source \"$_cshfile\""
|
|
fi
|
|
|
|
}
|
|
|
|
# nocron confighome noprofile accountemail
|
|
install() {
|
|
|
|
if [ -z "$LE_WORKING_DIR" ]; then
|
|
LE_WORKING_DIR="$DEFAULT_INSTALL_HOME"
|
|
fi
|
|
|
|
_nocron="$1"
|
|
_c_home="$2"
|
|
_noprofile="$3"
|
|
_accountemail="$4"
|
|
|
|
if ! _initpath; then
|
|
_err "Install failed."
|
|
return 1
|
|
fi
|
|
if [ "$_nocron" ]; then
|
|
_debug "Skipping cron job installation"
|
|
fi
|
|
|
|
if [ "$_ACME_IN_CRON" != "1" ]; then
|
|
if ! _precheck "$_nocron"; then
|
|
_err "Pre-check failed, cannot install."
|
|
return 1
|
|
fi
|
|
fi
|
|
|
|
if [ -z "$_c_home" ] && [ "$LE_CONFIG_HOME" != "$LE_WORKING_DIR" ]; then
|
|
_info "Using config home: $LE_CONFIG_HOME"
|
|
_c_home="$LE_CONFIG_HOME"
|
|
fi
|
|
|
|
#convert from le
|
|
if [ -d "$HOME/.le" ]; then
|
|
for envfile in "le.env" "le.sh.env"; do
|
|
if [ -f "$HOME/.le/$envfile" ]; then
|
|
if grep "le.sh" "$HOME/.le/$envfile" >/dev/null; then
|
|
_upgrading="1"
|
|
_info "You are upgrading from le.sh"
|
|
_info "Renaming \"$HOME/.le\" to $LE_WORKING_DIR"
|
|
mv "$HOME/.le" "$LE_WORKING_DIR"
|
|
mv "$LE_WORKING_DIR/$envfile" "$LE_WORKING_DIR/$PROJECT_ENTRY.env"
|
|
break
|
|
fi
|
|
fi
|
|
done
|
|
fi
|
|
|
|
_info "Installing to $LE_WORKING_DIR"
|
|
|
|
if [ ! -d "$LE_WORKING_DIR" ]; then
|
|
if ! mkdir -p "$LE_WORKING_DIR"; then
|
|
_err "Cannot create working dir: $LE_WORKING_DIR"
|
|
return 1
|
|
fi
|
|
|
|
chmod 700 "$LE_WORKING_DIR"
|
|
fi
|
|
|
|
if [ ! -d "$LE_CONFIG_HOME" ]; then
|
|
if ! mkdir -p "$LE_CONFIG_HOME"; then
|
|
_err "Cannot create config dir: $LE_CONFIG_HOME"
|
|
return 1
|
|
fi
|
|
|
|
chmod 700 "$LE_CONFIG_HOME"
|
|
fi
|
|
|
|
cp "$PROJECT_ENTRY" "$LE_WORKING_DIR/" && chmod +x "$LE_WORKING_DIR/$PROJECT_ENTRY"
|
|
|
|
if [ "$?" != "0" ]; then
|
|
_err "Installation failed, cannot copy $PROJECT_ENTRY"
|
|
return 1
|
|
fi
|
|
|
|
_info "Installed to $LE_WORKING_DIR/$PROJECT_ENTRY"
|
|
|
|
if [ "$_ACME_IN_CRON" != "1" ] && [ -z "$_noprofile" ]; then
|
|
_installalias "$_c_home"
|
|
fi
|
|
|
|
for subf in $_SUB_FOLDERS; do
|
|
if [ -d "$subf" ]; then
|
|
mkdir -p "$LE_WORKING_DIR/$subf"
|
|
cp "$subf"/* "$LE_WORKING_DIR"/"$subf"/
|
|
fi
|
|
done
|
|
|
|
if [ ! -f "$ACCOUNT_CONF_PATH" ]; then
|
|
_initconf
|
|
fi
|
|
|
|
if [ "$_DEFAULT_ACCOUNT_CONF_PATH" != "$ACCOUNT_CONF_PATH" ]; then
|
|
_setopt "$_DEFAULT_ACCOUNT_CONF_PATH" "ACCOUNT_CONF_PATH" "=" "\"$ACCOUNT_CONF_PATH\""
|
|
fi
|
|
|
|
if [ "$_DEFAULT_CERT_HOME" != "$CERT_HOME" ]; then
|
|
_saveaccountconf "CERT_HOME" "$CERT_HOME"
|
|
fi
|
|
|
|
if [ "$_DEFAULT_ACCOUNT_KEY_PATH" != "$ACCOUNT_KEY_PATH" ]; then
|
|
_saveaccountconf "ACCOUNT_KEY_PATH" "$ACCOUNT_KEY_PATH"
|
|
fi
|
|
|
|
if [ -z "$_nocron" ]; then
|
|
installcronjob "$_c_home"
|
|
fi
|
|
|
|
if [ -z "$NO_DETECT_SH" ]; then
|
|
#Modify shebang
|
|
if _exists bash; then
|
|
_bash_path="$(bash -c "command -v bash 2>/dev/null")"
|
|
if [ -z "$_bash_path" ]; then
|
|
_bash_path="$(bash -c 'echo $SHELL')"
|
|
fi
|
|
fi
|
|
if [ "$_bash_path" ]; then
|
|
_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
|
|
if [ -d "$LE_WORKING_DIR/$subf" ]; then
|
|
for _apifile in "$LE_WORKING_DIR/$subf/"*.sh; do
|
|
_setShebang "$_apifile" "$_shebang"
|
|
done
|
|
fi
|
|
done
|
|
fi
|
|
fi
|
|
|
|
if [ "$_accountemail" ]; then
|
|
_saveaccountconf "ACCOUNT_EMAIL" "$_accountemail"
|
|
fi
|
|
_saveaccountconf "UPGRADE_HASH" "$(_getUpgradeHash)"
|
|
_info OK
|
|
}
|
|
|
|
# nocron
|
|
uninstall() {
|
|
_nocron="$1"
|
|
if [ -z "$_nocron" ]; then
|
|
uninstallcronjob
|
|
fi
|
|
_initpath
|
|
|
|
_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."
|
|
|
|
}
|
|
|
|
_uninstallalias() {
|
|
_initpath
|
|
|
|
_profile="$(_detect_profile)"
|
|
if [ "$_profile" ]; then
|
|
_info "Uninstalling alias from: '$_profile'"
|
|
text="$(cat "$_profile")"
|
|
echo "$text" | sed "s|^.*\"$LE_WORKING_DIR/$PROJECT_NAME.env\"$||" >"$_profile"
|
|
fi
|
|
|
|
_csh_profile="$HOME/.cshrc"
|
|
if [ -f "$_csh_profile" ]; then
|
|
_info "Uninstalling alias from: '$_csh_profile'"
|
|
text="$(cat "$_csh_profile")"
|
|
echo "$text" | sed "s|^.*\"$LE_WORKING_DIR/$PROJECT_NAME.csh\"$||" >"$_csh_profile"
|
|
fi
|
|
|
|
_tcsh_profile="$HOME/.tcshrc"
|
|
if [ -f "$_tcsh_profile" ]; then
|
|
_info "Uninstalling alias from: '$_csh_profile'"
|
|
text="$(cat "$_tcsh_profile")"
|
|
echo "$text" | sed "s|^.*\"$LE_WORKING_DIR/$PROJECT_NAME.csh\"$||" >"$_tcsh_profile"
|
|
fi
|
|
|
|
}
|
|
|
|
cron() {
|
|
export _ACME_IN_CRON=1
|
|
_initpath
|
|
_info "$(__green "===Starting cron===")"
|
|
if [ "$AUTO_UPGRADE" = "1" ]; then
|
|
export LE_WORKING_DIR
|
|
(
|
|
if ! upgrade; then
|
|
_err "Cron: Upgrade failed!"
|
|
return 1
|
|
fi
|
|
)
|
|
. "$LE_WORKING_DIR/$PROJECT_ENTRY" >/dev/null
|
|
|
|
if [ -t 1 ]; then
|
|
__INTERACTIVE="1"
|
|
fi
|
|
|
|
_info "Automatically upgraded to: $VER"
|
|
fi
|
|
renewAll
|
|
_ret="$?"
|
|
_ACME_IN_CRON=""
|
|
_info "$(__green "===End cron===")"
|
|
exit $_ret
|
|
}
|
|
|
|
version() {
|
|
echo "$PROJECT"
|
|
echo "v$VER"
|
|
}
|
|
|
|
# subject content hooks code
|
|
_send_notify() {
|
|
_nsubject="$1"
|
|
_ncontent="$2"
|
|
_nhooks="$3"
|
|
_nerror="$4"
|
|
|
|
if [ "$NOTIFY_LEVEL" = "$NOTIFY_LEVEL_DISABLE" ]; then
|
|
_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, will just return."
|
|
return 0
|
|
fi
|
|
|
|
_nsource="$NOTIFY_SOURCE"
|
|
if [ -z "$_nsource" ]; then
|
|
_nsource="$(hostname)"
|
|
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 "Cannot find the hook file for $_n_hook"
|
|
continue
|
|
fi
|
|
if ! (
|
|
if ! . "$_n_hook_file"; then
|
|
_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. Make sure it has a function named: $d_command"
|
|
return 1
|
|
fi
|
|
|
|
if ! $d_command "$_nsubject" "$_ncontent" "$_nerror"; then
|
|
_err "Error sending message using $d_command"
|
|
return 1
|
|
fi
|
|
|
|
return 0
|
|
); then
|
|
_err "Error setting $_n_hook_file."
|
|
_send_err=1
|
|
else
|
|
_info "$_n_hook $(__green Success)"
|
|
fi
|
|
done
|
|
return $_send_err
|
|
|
|
}
|
|
|
|
# hook
|
|
_set_notify_hook() {
|
|
_nhooks="$1"
|
|
|
|
_test_subject="Hello, this is a notification from $PROJECT_NAME"
|
|
_test_content="If you receive this message, your notification works."
|
|
|
|
_send_notify "$_test_subject" "$_test_content" "$_nhooks" 0
|
|
|
|
}
|
|
|
|
#[hook] [level] [mode]
|
|
setnotify() {
|
|
_nhook="$1"
|
|
_nlevel="$2"
|
|
_nmode="$3"
|
|
_nsource="$4"
|
|
|
|
_initpath
|
|
|
|
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
|
|
|
|
if [ "$_nlevel" ]; then
|
|
_info "Set notify level to: $_nlevel"
|
|
export "NOTIFY_LEVEL=$_nlevel"
|
|
_saveaccountconf "NOTIFY_LEVEL" "$NOTIFY_LEVEL"
|
|
fi
|
|
|
|
if [ "$_nmode" ]; then
|
|
_info "Set notify mode to: $_nmode"
|
|
export "NOTIFY_MODE=$_nmode"
|
|
_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 "Clearing notify hook"
|
|
_clearaccountconf "NOTIFY_HOOK"
|
|
else
|
|
if _set_notify_hook "$_nhook"; then
|
|
export NOTIFY_HOOK="$_nhook"
|
|
_saveaccountconf "NOTIFY_HOOK" "$NOTIFY_HOOK"
|
|
return 0
|
|
else
|
|
_err "Cannot set notify hook to: $_nhook"
|
|
return 1
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
}
|
|
|
|
showhelp() {
|
|
_initpath
|
|
version
|
|
echo "Usage: $PROJECT_ENTRY <command> ... [parameters ...]
|
|
Commands:
|
|
-h, --help Show this help message.
|
|
-v, --version Show version info.
|
|
--install Install $PROJECT_NAME to your system.
|
|
--uninstall Uninstall $PROJECT_NAME, and uninstall the cron job.
|
|
--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.
|
|
-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.
|
|
--show-csr Show the content of a csr.
|
|
-ccr, --create-csr Create CSR, professional use.
|
|
--create-domain-key Create an domain private key, professional use.
|
|
--update-account Update account info.
|
|
--register-account Register account key.
|
|
--deactivate-account Deactivate the account.
|
|
--create-account-key Create an account private key, professional use.
|
|
--install-cronjob Install the cron job to renew certs, you don't need to call this. The 'install' command can automatically install the cron job.
|
|
--uninstall-cronjob Uninstall the cron job. The 'uninstall' command can do this automatically.
|
|
--cron Run cron job to renew all the certs.
|
|
--set-notify Set the cron notification hook, level or mode.
|
|
--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
|
|
|
|
--domain-alias <domain.tld> The domain alias for DNS alias mode.
|
|
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
|
|
|
|
--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 $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.
|
|
-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.
|
|
--dns [dns_hook] Use dns manual mode or dns api. Defaults to manual mode when argument is omitted.
|
|
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.
|
|
-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 $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.
|
|
--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
|
|
|
|
--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.
|
|
--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, --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.
|
|
--tlsport <port> Specifies the standalone tls listening port. Only valid if the server is behind a reverse proxy or load balancer.
|
|
--local-address <ip> Specifies the standalone/tls server listening address, in case you have multiple ip addresses.
|
|
--listraw Only used for '--list' command, list the certs in raw format.
|
|
-se, --stop-renew-on-error Only valid for '--renew-all' command. Stop if one cert has error in renewal.
|
|
--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.
|
|
--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 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.
|
|
--listen-v4 Force standalone/tls server to listen at ipv4.
|
|
--listen-v6 Force standalone/tls server to listen at ipv6.
|
|
--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
|
|
|
|
-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.
|
|
--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.
|
|
--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
|
|
|
|
--password <password> Add a password to exported pfx file. Use with --to-pkcs12.
|
|
|
|
|
|
"
|
|
}
|
|
|
|
installOnline() {
|
|
_info "Installing from online archive."
|
|
|
|
_branch="$BRANCH"
|
|
if [ -z "$_branch" ]; then
|
|
_branch="master"
|
|
fi
|
|
|
|
target="$PROJECT/archive/$_branch.tar.gz"
|
|
_info "Downloading $target"
|
|
localname="$_branch.tar.gz"
|
|
if ! _get "$target" >$localname; then
|
|
_err "Download error."
|
|
return 1
|
|
fi
|
|
(
|
|
_info "Extracting $localname"
|
|
if ! (tar xzf $localname || gtar xzf $localname); then
|
|
_err "Extraction error."
|
|
exit 1
|
|
fi
|
|
|
|
cd "$PROJECT_NAME-$_branch"
|
|
chmod +x $PROJECT_ENTRY
|
|
if ./$PROJECT_ENTRY --install "$@"; then
|
|
_info "Install success!"
|
|
fi
|
|
|
|
cd ..
|
|
|
|
rm -rf "$PROJECT_NAME-$_branch"
|
|
rm -f "$localname"
|
|
)
|
|
}
|
|
|
|
_getRepoHash() {
|
|
_hash_path=$1
|
|
shift
|
|
_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() {
|
|
_b="$BRANCH"
|
|
if [ -z "$_b" ]; then
|
|
_b="master"
|
|
fi
|
|
_hash=$(_getRepoHash "heads/$_b")
|
|
if [ -z "$_hash" ]; then _hash=$(_getRepoHash "tags/$_b"); fi
|
|
echo $_hash
|
|
}
|
|
|
|
upgrade() {
|
|
if (
|
|
_initpath
|
|
[ -z "$FORCE" ] && [ "$(_getUpgradeHash)" = "$(_readaccountconf "UPGRADE_HASH")" ] && _info "Already up to date!" && exit 0
|
|
export LE_WORKING_DIR
|
|
cd "$LE_WORKING_DIR"
|
|
installOnline "--nocron" "--noprofile"
|
|
); then
|
|
_info "Upgrade successful!"
|
|
exit 0
|
|
else
|
|
_err "Upgrade failed!"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
_processAccountConf() {
|
|
if [ "$_useragent" ]; then
|
|
_saveaccountconf "USER_AGENT" "$_useragent"
|
|
elif [ "$USER_AGENT" ] && [ "$USER_AGENT" != "$DEFAULT_USER_AGENT" ]; then
|
|
_saveaccountconf "USER_AGENT" "$USER_AGENT"
|
|
fi
|
|
|
|
if [ "$_openssl_bin" ]; then
|
|
_saveaccountconf "ACME_OPENSSL_BIN" "$_openssl_bin"
|
|
elif [ "$ACME_OPENSSL_BIN" ] && [ "$ACME_OPENSSL_BIN" != "$DEFAULT_OPENSSL_BIN" ]; then
|
|
_saveaccountconf "ACME_OPENSSL_BIN" "$ACME_OPENSSL_BIN"
|
|
fi
|
|
|
|
if [ "$_auto_upgrade" ]; then
|
|
_saveaccountconf "AUTO_UPGRADE" "$_auto_upgrade"
|
|
elif [ "$AUTO_UPGRADE" ]; then
|
|
_saveaccountconf "AUTO_UPGRADE" "$AUTO_UPGRADE"
|
|
fi
|
|
|
|
if [ "$_use_wget" ]; then
|
|
_saveaccountconf "ACME_USE_WGET" "$_use_wget"
|
|
elif [ "$ACME_USE_WGET" ]; then
|
|
_saveaccountconf "ACME_USE_WGET" "$ACME_USE_WGET"
|
|
fi
|
|
|
|
}
|
|
|
|
_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`, 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
|
|
return 1
|
|
fi
|
|
return 0
|
|
}
|
|
|
|
#server #keylength
|
|
_selectServer() {
|
|
_server="$1"
|
|
_skeylength="$2"
|
|
_server_lower="$(echo "$_server" | _lower_case)"
|
|
_sindex=0
|
|
for snames in $CA_NAMES; do
|
|
snames="$(echo "$snames" | _lower_case)"
|
|
_sindex="$(_math $_sindex + 1)"
|
|
_debug2 "_selectServer try snames" "$snames"
|
|
for sname in $(echo "$snames" | tr ',' ' '); do
|
|
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
|
|
return
|
|
fi
|
|
done
|
|
done
|
|
ACME_DIRECTORY="$_server"
|
|
export ACME_DIRECTORY
|
|
}
|
|
|
|
#url
|
|
_getCAShortName() {
|
|
caurl="$1"
|
|
if [ -z "$caurl" ]; then
|
|
#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
|
|
for surl in $(echo "$CA_SERVERS" | _lower_case | tr , ' '); do
|
|
_sindex="$(_math $_sindex + 1)"
|
|
if [ "$caurl_lower" = "$surl" ]; then
|
|
_nindex=0
|
|
for snames in $CA_NAMES; do
|
|
_nindex="$(_math $_nindex + 1)"
|
|
if [ $_nindex -ge $_sindex ]; then
|
|
_getfield "$snames" 1
|
|
return
|
|
fi
|
|
done
|
|
fi
|
|
done
|
|
echo "$caurl"
|
|
}
|
|
|
|
#set default ca to $ACME_DIRECTORY
|
|
setdefaultca() {
|
|
if [ -z "$ACME_DIRECTORY" ]; then
|
|
_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="$DEFAULT_DOMAIN_KEY_LENGTH"
|
|
_accountkeylength="$DEFAULT_ACCOUNT_KEY_LENGTH"
|
|
_cert_file=""
|
|
_key_file=""
|
|
_ca_file=""
|
|
_fullchain_file=""
|
|
_reloadcmd=""
|
|
_password=""
|
|
_accountconf=""
|
|
_useragent=""
|
|
_accountemail=""
|
|
_accountkey=""
|
|
_certhome=""
|
|
_confighome=""
|
|
_httpport=""
|
|
_tlsport=""
|
|
_dnssleep=""
|
|
_listraw=""
|
|
_stopRenewOnError=""
|
|
#_insecure=""
|
|
_ca_bundle=""
|
|
_ca_path=""
|
|
_nocron=""
|
|
_noprofile=""
|
|
_ecc=""
|
|
_csr=""
|
|
_pre_hook=""
|
|
_post_hook=""
|
|
_renew_hook=""
|
|
_deploy_hook=""
|
|
_logfile=""
|
|
_log=""
|
|
_local_address=""
|
|
_log_level=""
|
|
_auto_upgrade=""
|
|
_listen_v4=""
|
|
_listen_v6=""
|
|
_openssl_bin=""
|
|
_syslog=""
|
|
_use_wget=""
|
|
_server=""
|
|
_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
|
|
|
|
--help | -h)
|
|
showhelp
|
|
return
|
|
;;
|
|
--version | -v)
|
|
version
|
|
return
|
|
;;
|
|
--install)
|
|
_CMD="install"
|
|
;;
|
|
--install-online)
|
|
shift
|
|
installOnline "$@"
|
|
return
|
|
;;
|
|
--uninstall)
|
|
_CMD="uninstall"
|
|
;;
|
|
--upgrade)
|
|
_CMD="upgrade"
|
|
;;
|
|
--issue)
|
|
_CMD="issue"
|
|
;;
|
|
--deploy)
|
|
_CMD="deploy"
|
|
;;
|
|
--sign-csr | --signcsr)
|
|
_CMD="signcsr"
|
|
;;
|
|
--show-csr | --showcsr)
|
|
_CMD="showcsr"
|
|
;;
|
|
-i | --install-cert | --installcert)
|
|
_CMD="installcert"
|
|
;;
|
|
--renew | -r)
|
|
_CMD="renew"
|
|
;;
|
|
--renew-all | --renewAll | --renewall)
|
|
_CMD="renewAll"
|
|
;;
|
|
--revoke)
|
|
_CMD="revoke"
|
|
;;
|
|
--remove)
|
|
_CMD="remove"
|
|
;;
|
|
--list)
|
|
_CMD="list"
|
|
;;
|
|
--info)
|
|
_CMD="info"
|
|
;;
|
|
--install-cronjob | --installcronjob)
|
|
_CMD="installcronjob"
|
|
;;
|
|
--uninstall-cronjob | --uninstallcronjob)
|
|
_CMD="uninstallcronjob"
|
|
;;
|
|
--cron)
|
|
_CMD="cron"
|
|
;;
|
|
--to-pkcs12 | --to-pkcs | --toPkcs)
|
|
_CMD="toPkcs"
|
|
;;
|
|
--to-pkcs8 | --toPkcs8)
|
|
_CMD="toPkcs8"
|
|
;;
|
|
--create-account-key | --createAccountKey | --createaccountkey | -cak)
|
|
_CMD="createAccountKey"
|
|
;;
|
|
--create-domain-key | --createDomainKey | --createdomainkey | -cdk)
|
|
_CMD="createDomainKey"
|
|
;;
|
|
-ccr | --create-csr | --createCSR | --createcsr)
|
|
_CMD="createCSR"
|
|
;;
|
|
--deactivate)
|
|
_CMD="deactivate"
|
|
;;
|
|
--update-account | --updateaccount)
|
|
_CMD="updateaccount"
|
|
;;
|
|
--register-account | --registeraccount)
|
|
_CMD="registeraccount"
|
|
;;
|
|
--deactivate-account)
|
|
_CMD="deactivateaccount"
|
|
;;
|
|
--set-notify)
|
|
_CMD="setnotify"
|
|
;;
|
|
--set-default-ca)
|
|
_CMD="setdefaultca"
|
|
;;
|
|
--set-default-chain)
|
|
_CMD="setdefaultchain"
|
|
;;
|
|
-d | --domain)
|
|
_dvalue="$2"
|
|
|
|
if [ "$_dvalue" ]; then
|
|
if _startswith "$_dvalue" "-"; then
|
|
_err "'$_dvalue' is not a valid domain for parameter '$1'"
|
|
return 1
|
|
fi
|
|
if _is_idn "$_dvalue" && ! _exists idn; then
|
|
_err "It seems that $_dvalue is an IDN (Internationalized Domain Names), please install the 'idn' command first."
|
|
return 1
|
|
fi
|
|
|
|
if [ -z "$_domain" ]; then
|
|
_domain="$_dvalue"
|
|
else
|
|
if [ "$_altdomains" = "$NO_VALUE" ]; then
|
|
_altdomains="$_dvalue"
|
|
else
|
|
_altdomains="$_altdomains,$_dvalue"
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
shift
|
|
;;
|
|
|
|
-f | --force)
|
|
FORCE="1"
|
|
;;
|
|
--staging | --test)
|
|
STAGE="1"
|
|
;;
|
|
--server)
|
|
_server="$2"
|
|
shift
|
|
;;
|
|
--debug)
|
|
if [ -z "$2" ] || _startswith "$2" "-"; then
|
|
DEBUG="$DEBUG_LEVEL_DEFAULT"
|
|
else
|
|
DEBUG="$2"
|
|
shift
|
|
fi
|
|
;;
|
|
--output-insecure)
|
|
export OUTPUT_INSECURE=1
|
|
;;
|
|
-w | --webroot)
|
|
wvalue="$2"
|
|
if [ -z "$_webroot" ]; then
|
|
_webroot="$wvalue"
|
|
else
|
|
_webroot="$_webroot,$wvalue"
|
|
fi
|
|
shift
|
|
;;
|
|
--challenge-alias)
|
|
cvalue="$2"
|
|
_challenge_alias="$_challenge_alias$cvalue,"
|
|
shift
|
|
;;
|
|
--domain-alias)
|
|
cvalue="$DNS_ALIAS_PREFIX$2"
|
|
_challenge_alias="$_challenge_alias$cvalue,"
|
|
shift
|
|
;;
|
|
--standalone)
|
|
wvalue="$NO_VALUE"
|
|
if [ -z "$_webroot" ]; then
|
|
_webroot="$wvalue"
|
|
else
|
|
_webroot="$_webroot,$wvalue"
|
|
fi
|
|
;;
|
|
--alpn)
|
|
wvalue="$W_ALPN"
|
|
if [ -z "$_webroot" ]; then
|
|
_webroot="$wvalue"
|
|
else
|
|
_webroot="$_webroot,$wvalue"
|
|
fi
|
|
;;
|
|
--stateless)
|
|
wvalue="$MODE_STATELESS"
|
|
if [ -z "$_webroot" ]; then
|
|
_webroot="$wvalue"
|
|
else
|
|
_webroot="$_webroot,$wvalue"
|
|
fi
|
|
;;
|
|
--local-address)
|
|
lvalue="$2"
|
|
_local_address="$_local_address$lvalue,"
|
|
shift
|
|
;;
|
|
--apache)
|
|
wvalue="apache"
|
|
if [ -z "$_webroot" ]; then
|
|
_webroot="$wvalue"
|
|
else
|
|
_webroot="$_webroot,$wvalue"
|
|
fi
|
|
;;
|
|
--nginx)
|
|
wvalue="$NGINX"
|
|
if [ "$2" ] && ! _startswith "$2" "-"; then
|
|
wvalue="$NGINX$2"
|
|
shift
|
|
fi
|
|
if [ -z "$_webroot" ]; then
|
|
_webroot="$wvalue"
|
|
else
|
|
_webroot="$_webroot,$wvalue"
|
|
fi
|
|
;;
|
|
--dns)
|
|
wvalue="$W_DNS"
|
|
if [ "$2" ] && ! _startswith "$2" "-"; then
|
|
wvalue="$2"
|
|
shift
|
|
fi
|
|
if [ -z "$_webroot" ]; then
|
|
_webroot="$wvalue"
|
|
else
|
|
_webroot="$_webroot,$wvalue"
|
|
fi
|
|
;;
|
|
--dnssleep)
|
|
_dnssleep="$2"
|
|
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
|
|
;;
|
|
--key-file | --keypath)
|
|
_key_file="$2"
|
|
shift
|
|
;;
|
|
--ca-file | --capath)
|
|
_ca_file="$2"
|
|
shift
|
|
;;
|
|
--fullchain-file | --fullchainpath)
|
|
_fullchain_file="$2"
|
|
shift
|
|
;;
|
|
--reloadcmd | --reloadCmd)
|
|
_reloadcmd="$2"
|
|
shift
|
|
;;
|
|
--password)
|
|
_password="$2"
|
|
shift
|
|
;;
|
|
--accountconf)
|
|
_accountconf="$2"
|
|
ACCOUNT_CONF_PATH="$_accountconf"
|
|
shift
|
|
;;
|
|
--home)
|
|
export LE_WORKING_DIR="$(echo "$2" | sed 's|/$||')"
|
|
shift
|
|
;;
|
|
--cert-home | --certhome)
|
|
_certhome="$2"
|
|
export CERT_HOME="$_certhome"
|
|
shift
|
|
;;
|
|
--config-home)
|
|
_confighome="$2"
|
|
export LE_CONFIG_HOME="$_confighome"
|
|
shift
|
|
;;
|
|
--useragent)
|
|
_useragent="$2"
|
|
USER_AGENT="$_useragent"
|
|
shift
|
|
;;
|
|
-m | --email | --accountemail)
|
|
_accountemail="$2"
|
|
export ACCOUNT_EMAIL="$_accountemail"
|
|
shift
|
|
;;
|
|
--accountkey)
|
|
_accountkey="$2"
|
|
ACCOUNT_KEY_PATH="$_accountkey"
|
|
shift
|
|
;;
|
|
--days)
|
|
_days="$2"
|
|
Le_RenewalDays="$_days"
|
|
shift
|
|
;;
|
|
--valid-from)
|
|
_valid_from="$2"
|
|
shift
|
|
;;
|
|
--valid-to)
|
|
_valid_to="$2"
|
|
shift
|
|
;;
|
|
--httpport)
|
|
_httpport="$2"
|
|
Le_HTTPPort="$_httpport"
|
|
shift
|
|
;;
|
|
--tlsport)
|
|
_tlsport="$2"
|
|
Le_TLSPort="$_tlsport"
|
|
shift
|
|
;;
|
|
--listraw)
|
|
_listraw="raw"
|
|
;;
|
|
-se | --stop-renew-on-error | --stopRenewOnError | --stoprenewonerror)
|
|
_stopRenewOnError="1"
|
|
;;
|
|
--insecure)
|
|
#_insecure="1"
|
|
HTTPS_INSECURE="1"
|
|
;;
|
|
--ca-bundle)
|
|
_ca_bundle="$(_readlink "$2")"
|
|
CA_BUNDLE="$_ca_bundle"
|
|
shift
|
|
;;
|
|
--ca-path)
|
|
_ca_path="$2"
|
|
CA_PATH="$_ca_path"
|
|
shift
|
|
;;
|
|
--no-cron | --nocron)
|
|
_nocron="1"
|
|
;;
|
|
--no-profile | --noprofile)
|
|
_noprofile="1"
|
|
;;
|
|
--no-color)
|
|
export ACME_NO_COLOR=1
|
|
;;
|
|
--force-color)
|
|
export ACME_FORCE_COLOR=1
|
|
;;
|
|
--ecc)
|
|
_ecc="isEcc"
|
|
;;
|
|
--csr)
|
|
_csr="$2"
|
|
shift
|
|
;;
|
|
--pre-hook)
|
|
_pre_hook="$2"
|
|
shift
|
|
;;
|
|
--post-hook)
|
|
_post_hook="$2"
|
|
shift
|
|
;;
|
|
--renew-hook)
|
|
_renew_hook="$2"
|
|
shift
|
|
;;
|
|
--deploy-hook)
|
|
if [ -z "$2" ] || _startswith "$2" "-"; then
|
|
_usage "Please specify a value for '--deploy-hook'"
|
|
return 1
|
|
fi
|
|
_deploy_hook="$_deploy_hook$2,"
|
|
shift
|
|
;;
|
|
--extended-key-usage)
|
|
Le_ExtKeyUse="$2"
|
|
shift
|
|
;;
|
|
--ocsp-must-staple | --ocsp)
|
|
Le_OCSP_Staple="1"
|
|
;;
|
|
--always-force-new-domain-key)
|
|
if [ -z "$2" ] || _startswith "$2" "-"; then
|
|
Le_ForceNewDomainKey=1
|
|
else
|
|
Le_ForceNewDomainKey="$2"
|
|
shift
|
|
fi
|
|
;;
|
|
--yes-I-know-dns-manual-mode-enough-go-ahead-please)
|
|
export FORCE_DNS_MANUAL=1
|
|
;;
|
|
--log | --logfile)
|
|
_log="1"
|
|
_logfile="$2"
|
|
if _startswith "$_logfile" '-'; then
|
|
_logfile=""
|
|
else
|
|
shift
|
|
fi
|
|
LOG_FILE="$_logfile"
|
|
if [ -z "$LOG_LEVEL" ]; then
|
|
LOG_LEVEL="$DEFAULT_LOG_LEVEL"
|
|
fi
|
|
;;
|
|
--log-level)
|
|
_log_level="$2"
|
|
LOG_LEVEL="$_log_level"
|
|
shift
|
|
;;
|
|
--syslog)
|
|
if ! _startswith "$2" '-'; then
|
|
_syslog="$2"
|
|
shift
|
|
fi
|
|
if [ -z "$_syslog" ]; then
|
|
_syslog="$SYSLOG_LEVEL_DEFAULT"
|
|
fi
|
|
;;
|
|
--auto-upgrade)
|
|
_auto_upgrade="$2"
|
|
if [ -z "$_auto_upgrade" ] || _startswith "$_auto_upgrade" '-'; then
|
|
_auto_upgrade="1"
|
|
else
|
|
shift
|
|
fi
|
|
AUTO_UPGRADE="$_auto_upgrade"
|
|
;;
|
|
--listen-v4)
|
|
_listen_v4="1"
|
|
Le_Listen_V4="$_listen_v4"
|
|
;;
|
|
--listen-v6)
|
|
_listen_v6="1"
|
|
Le_Listen_V6="$_listen_v6"
|
|
;;
|
|
--openssl-bin)
|
|
_openssl_bin="$2"
|
|
ACME_OPENSSL_BIN="$_openssl_bin"
|
|
shift
|
|
;;
|
|
--use-wget)
|
|
_use_wget="1"
|
|
ACME_USE_WGET="1"
|
|
;;
|
|
--branch | -b)
|
|
export BRANCH="$2"
|
|
shift
|
|
;;
|
|
--notify-hook)
|
|
_nhook="$2"
|
|
if _startswith "$_nhook" "-"; then
|
|
_err "'$_nhook' is not a hook name for '$1'"
|
|
return 1
|
|
fi
|
|
if [ "$_notify_hook" ]; then
|
|
_notify_hook="$_notify_hook,$_nhook"
|
|
else
|
|
_notify_hook="$_nhook"
|
|
fi
|
|
shift
|
|
;;
|
|
--notify-level)
|
|
_nlevel="$2"
|
|
if _startswith "$_nlevel" "-"; then
|
|
_err "'$_nlevel' is not an integer for '$1'"
|
|
return 1
|
|
fi
|
|
_notify_level="$_nlevel"
|
|
shift
|
|
;;
|
|
--notify-mode)
|
|
_nmode="$2"
|
|
if _startswith "$_nmode" "-"; then
|
|
_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 an integer for '$1'"
|
|
return 1
|
|
fi
|
|
shift
|
|
;;
|
|
--eab-kid)
|
|
_eab_kid="$2"
|
|
shift
|
|
;;
|
|
--eab-hmac-key)
|
|
_eab_hmac_key="$2"
|
|
shift
|
|
;;
|
|
--preferred-chain)
|
|
_preferred_chain="$2"
|
|
shift
|
|
;;
|
|
*)
|
|
_err "Unknown parameter: $1"
|
|
return 1
|
|
;;
|
|
esac
|
|
|
|
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 page first:"
|
|
echo "$_SUDO_WIKI"
|
|
return 1
|
|
fi
|
|
fi
|
|
__initHome
|
|
if [ "$_log" ]; then
|
|
if [ -z "$_logfile" ]; then
|
|
_logfile="$DEFAULT_LOG_FILE"
|
|
fi
|
|
fi
|
|
if [ "$_logfile" ]; then
|
|
_saveaccountconf "LOG_FILE" "$_logfile"
|
|
LOG_FILE="$_logfile"
|
|
fi
|
|
|
|
if [ "$_log_level" ]; then
|
|
_saveaccountconf "LOG_LEVEL" "$_log_level"
|
|
LOG_LEVEL="$_log_level"
|
|
fi
|
|
|
|
if [ "$_syslog" ]; then
|
|
if _exists logger; then
|
|
if [ "$_syslog" = "0" ]; then
|
|
_clearaccountconf "SYS_LOG"
|
|
else
|
|
_saveaccountconf "SYS_LOG" "$_syslog"
|
|
fi
|
|
SYS_LOG="$_syslog"
|
|
else
|
|
_err "The 'logger' command was not found, cannot enable syslog."
|
|
_clearaccountconf "SYS_LOG"
|
|
SYS_LOG=""
|
|
fi
|
|
fi
|
|
|
|
_processAccountConf
|
|
fi
|
|
|
|
_debug2 LE_WORKING_DIR "$LE_WORKING_DIR"
|
|
|
|
if [ "$DEBUG" ]; then
|
|
version
|
|
if [ "$_server" ]; then
|
|
_debug "Using server: $_server"
|
|
fi
|
|
fi
|
|
_debug "Running cmd: ${_CMD}"
|
|
case "${_CMD}" in
|
|
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" "$_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" "$_preferred_chain"
|
|
;;
|
|
showcsr)
|
|
showcsr "$_csr" "$_domain"
|
|
;;
|
|
installcert)
|
|
installcert "$_domain" "$_cert_file" "$_key_file" "$_ca_file" "$_reloadcmd" "$_fullchain_file" "$_ecc"
|
|
;;
|
|
renew)
|
|
renew "$_domain" "$_ecc" "$_server"
|
|
;;
|
|
renewAll)
|
|
renewAll "$_stopRenewOnError" "$_server"
|
|
;;
|
|
revoke)
|
|
revoke "$_domain" "$_ecc" "$_revoke_reason"
|
|
;;
|
|
remove)
|
|
remove "$_domain" "$_ecc"
|
|
;;
|
|
deactivate)
|
|
deactivate "$_domain,$_altdomains"
|
|
;;
|
|
registeraccount)
|
|
registeraccount "$_accountkeylength" "$_eab_kid" "$_eab_hmac_key"
|
|
;;
|
|
updateaccount)
|
|
updateaccount
|
|
;;
|
|
deactivateaccount)
|
|
deactivateaccount
|
|
;;
|
|
list)
|
|
list "$_listraw" "$_domain"
|
|
;;
|
|
info)
|
|
info "$_domain" "$_ecc"
|
|
;;
|
|
installcronjob) installcronjob "$_confighome" ;;
|
|
uninstallcronjob) uninstallcronjob ;;
|
|
cron) cron ;;
|
|
toPkcs)
|
|
toPkcs "$_domain" "$_password" "$_ecc"
|
|
;;
|
|
toPkcs8)
|
|
toPkcs8 "$_domain" "$_ecc"
|
|
;;
|
|
createAccountKey)
|
|
createAccountKey "$_accountkeylength"
|
|
;;
|
|
createDomainKey)
|
|
createDomainKey "$_domain" "$_keylength"
|
|
;;
|
|
createCSR)
|
|
createCSR "$_domain" "$_altdomains" "$_ecc"
|
|
;;
|
|
setnotify)
|
|
setnotify "$_notify_hook" "$_notify_level" "$_notify_mode" "$_notify_source"
|
|
;;
|
|
setdefaultca)
|
|
setdefaultca
|
|
;;
|
|
setdefaultchain)
|
|
setdefaultchain "$_preferred_chain"
|
|
;;
|
|
*)
|
|
if [ "$_CMD" ]; then
|
|
_err "Invalid command: $_CMD"
|
|
fi
|
|
showhelp
|
|
return 1
|
|
;;
|
|
esac
|
|
_ret="$?"
|
|
if [ "$_ret" != "0" ]; then
|
|
return $_ret
|
|
fi
|
|
|
|
if [ "${_CMD}" = "install" ]; then
|
|
if [ "$_log" ]; then
|
|
if [ -z "$LOG_FILE" ]; then
|
|
LOG_FILE="$DEFAULT_LOG_FILE"
|
|
fi
|
|
_saveaccountconf "LOG_FILE" "$LOG_FILE"
|
|
fi
|
|
|
|
if [ "$_log_level" ]; then
|
|
_saveaccountconf "LOG_LEVEL" "$_log_level"
|
|
fi
|
|
|
|
if [ "$_syslog" ]; then
|
|
if _exists logger; then
|
|
if [ "$_syslog" = "0" ]; then
|
|
_clearaccountconf "SYS_LOG"
|
|
else
|
|
_saveaccountconf "SYS_LOG" "$_syslog"
|
|
fi
|
|
else
|
|
_err "The 'logger' command was not found, cannot enable syslog."
|
|
_clearaccountconf "SYS_LOG"
|
|
SYS_LOG=""
|
|
fi
|
|
fi
|
|
|
|
_processAccountConf
|
|
fi
|
|
|
|
}
|
|
|
|
main() {
|
|
[ -z "$1" ] && showhelp && return
|
|
if _startswith "$1" '-'; then _process "$@"; else "$@"; fi
|
|
}
|
|
|
|
main "$@"
|