#!/usr/bin/env sh

VER=3.1.4

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_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"

CA_ACTALIS="https://acme-api.actalis.com/acme/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
SSL.com,sslcom
Google.com,google
Google.com_test,googletest,google_test
Actalis.com,actalis.com,actalis
"

CA_SERVERS="$CA_ZEROSSL,$CA_LETSENCRYPT_V2,$CA_LETSENCRYPT_V2_TEST,$CA_SSLCOM_RSA,$CA_GOOGLE,$CA_GOOGLE_TEST,$CA_ACTALIS"

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"
VTYPE_DNS_PERSIST="dns-persist-01"

ID_TYPE_DNS="dns"
ID_TYPE_IP="ip"

LOCAL_ANY_ADDRESS="0.0.0.0"

DEFAULT_RENEW="${DEFAULT_RENEW:-30}"

NO_VALUE="no"

W_DNS="dns"
W_ALPN="alpn"
W_DNS_PERSIST="dns_persist"
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"

_PROFILESELECTION_WIKI="https://github.com/acmesh-official/acme.sh/wiki/Profile-selection"

_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."
    if _exists "python3"; then
      python3 -V 2>&1
    elif _exists "python2"; then
      python2 -V 2>&1
    elif _exists "python"; then
      python -V 2>&1
    fi
  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
}

__USE_TR_TAG=""
if [ "$(echo "abc" | LANG=C tr a-z A-Z 2>/dev/null)" != "ABC" ]; then
  __USE_TR_TAG="1"
fi
export __USE_TR_TAG

_upper_case() {
  if [ "$__USE_TR_TAG" ]; then
    LANG=C tr '[:lower:]' '[:upper:]'
  else
    # shellcheck disable=SC2018,SC2019
    LANG=C tr '[a-z]' '[A-Z]'
  fi
}

_lower_case() {
  if [ "$__USE_TR_TAG" ]; then
    LANG=C tr '[:upper:]' '[:lower:]'
  else
    # shellcheck disable=SC2018,SC2019
    LANG=C tr '[A-Z]' '[a-z]'
  fi
}

_startswith() {
  _str="$1"
  _sub="$2"
  echo "$_str" | grep -- "^$_sub" >/dev/null 2>&1
}

_endswith() {
  _str="$1"
  _sub="$2"
  echo "$_str" | grep -- "$_sub\$" >/dev/null 2>&1
}

_contains() {
  _str="$1"
  _sub="$2"
  echo "$_str" | grep -- "$_sub" >/dev/null 2>&1
}

_hasfield() {
  _str="$1"
  _field="$2"
  _sep="$3"
  if [ -z "$_field" ]; then
    _usage "Usage: str field  [sep]"
    return 1
  fi

  if [ -z "$_sep" ]; then
    _sep=","
  fi

  for f in $(echo "$_str" | tr "$_sep" ' '); do
    if [ "$f" = "$_field" ]; then
      _debug2 "'$_str' contains '$_field'"
      return 0 #contains ok
    fi
  done
  _debug2 "'$_str' does not contain '$_field'"
  return 1 #not contains
}

# str index [sep]
_getfield() {
  _str="$1"
  _findex="$2"
  _sep="$3"

  if [ -z "$_findex" ]; then
    _usage "Usage: str field  [sep]"
    return 1
  fi

  if [ -z "$_sep" ]; then
    _sep=","
  fi

  _ffi="$_findex"
  while [ "$_ffi" -gt "0" ]; do
    _fv="$(echo "$_str" | cut -d "$_sep" -f "$_ffi")"
    if [ "$_fv" ]; then
      printf -- "%s" "$_fv"
      return 0
    fi
    _ffi="$(_math "$_ffi" - 1)"
  done

  printf -- "%s" "$_str"

}

_exists() {
  cmd="$1"
  if [ -z "$cmd" ]; then
    _usage "Usage: _exists cmd"
    return 1
  fi

  if eval type type >/dev/null 2>&1; then
    eval type "$cmd" >/dev/null 2>&1
  elif command >/dev/null 2>&1; then
    command -v "$cmd" >/dev/null 2>&1
  else
    which "$cmd" >/dev/null 2>&1
  fi
  ret="$?"
  _debug3 "$cmd exists=$ret"
  return $ret
}

#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

_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
    # shellcheck disable=SC2059
    printf "$(echo "$hex" | _upper_case | sed 's/\([0-9A-F]\{2\}\)/\\x\1/g')"
  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

#_url_encode [upper-hex]  the encoded hex will be upper-case if the argument upper-hex is followed
#stdin stdout
_url_encode() {
  _upper_hex=$1
  _hex_str=$(_hex_dump)
  _debug3 "_url_encode"
  _debug3 "_hex_str" "$_hex_str"
  for _hex_code in $_hex_str; do
    #upper case
    case "${_hex_code}" in
    "41")
      printf "%s" "A"
      ;;
    "42")
      printf "%s" "B"
      ;;
    "43")
      printf "%s" "C"
      ;;
    "44")
      printf "%s" "D"
      ;;
    "45")
      printf "%s" "E"
      ;;
    "46")
      printf "%s" "F"
      ;;
    "47")
      printf "%s" "G"
      ;;
    "48")
      printf "%s" "H"
      ;;
    "49")
      printf "%s" "I"
      ;;
    "4a")
      printf "%s" "J"
      ;;
    "4b")
      printf "%s" "K"
      ;;
    "4c")
      printf "%s" "L"
      ;;
    "4d")
      printf "%s" "M"
      ;;
    "4e")
      printf "%s" "N"
      ;;
    "4f")
      printf "%s" "O"
      ;;
    "50")
      printf "%s" "P"
      ;;
    "51")
      printf "%s" "Q"
      ;;
    "52")
      printf "%s" "R"
      ;;
    "53")
      printf "%s" "S"
      ;;
    "54")
      printf "%s" "T"
      ;;
    "55")
      printf "%s" "U"
      ;;
    "56")
      printf "%s" "V"
      ;;
    "57")
      printf "%s" "W"
      ;;
    "58")
      printf "%s" "X"
      ;;
    "59")
      printf "%s" "Y"
      ;;
    "5a")
      printf "%s" "Z"
      ;;

      #lower case
    "61")
      printf "%s" "a"
      ;;
    "62")
      printf "%s" "b"
      ;;
    "63")
      printf "%s" "c"
      ;;
    "64")
      printf "%s" "d"
      ;;
    "65")
      printf "%s" "e"
      ;;
    "66")
      printf "%s" "f"
      ;;
    "67")
      printf "%s" "g"
      ;;
    "68")
      printf "%s" "h"
      ;;
    "69")
      printf "%s" "i"
      ;;
    "6a")
      printf "%s" "j"
      ;;
    "6b")
      printf "%s" "k"
      ;;
    "6c")
      printf "%s" "l"
      ;;
    "6d")
      printf "%s" "m"
      ;;
    "6e")
      printf "%s" "n"
      ;;
    "6f")
      printf "%s" "o"
      ;;
    "70")
      printf "%s" "p"
      ;;
    "71")
      printf "%s" "q"
      ;;
    "72")
      printf "%s" "r"
      ;;
    "73")
      printf "%s" "s"
      ;;
    "74")
      printf "%s" "t"
      ;;
    "75")
      printf "%s" "u"
      ;;
    "76")
      printf "%s" "v"
      ;;
    "77")
      printf "%s" "w"
      ;;
    "78")
      printf "%s" "x"
      ;;
    "79")
      printf "%s" "y"
      ;;
    "7a")
      printf "%s" "z"
      ;;
      #numbers
    "30")
      printf "%s" "0"
      ;;
    "31")
      printf "%s" "1"
      ;;
    "32")
      printf "%s" "2"
      ;;
    "33")
      printf "%s" "3"
      ;;
    "34")
      printf "%s" "4"
      ;;
    "35")
      printf "%s" "5"
      ;;
    "36")
      printf "%s" "6"
      ;;
    "37")
      printf "%s" "7"
      ;;
    "38")
      printf "%s" "8"
      ;;
    "39")
      printf "%s" "9"
      ;;
    "2d")
      printf "%s" "-"
      ;;
    "5f")
      printf "%s" "_"
      ;;
    "2e")
      printf "%s" "."
      ;;
    "7e")
      printf "%s" "~"
      ;;
    #other hex
    *)
      if [ "$_upper_hex" = "upper-hex" ]; then
        _hex_code=$(printf "%s" "$_hex_code" | _upper_case)
      fi
      printf '%%%s' "$_hex_code"
      ;;
    esac
  done
}

_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"
}

#extract the authorization URLs from an order response on stdin, as a
#comma-separated list. The entries are quoted URL strings and a quote cannot
#occur inside a URL, so the first '"]' is always the end of the array. A
#char-class scan would stop early on the brackets of an IPv6 host
#(https://[2001:db8::1]/...). Outputs nothing if the field is missing.
_authorizations_from_order() {
  sed -n 's/.*"authorizations" *: *\[//p' | sed 's/" *\].*//' | tr -d '" '
}

#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"
  elif sed -h 2>&1 | grep "\-i extension" >/dev/null 2>&1; then
    _debug "Using FreeBSD sed -i"
    sed -i "" "$options" "$filename"
  else
    _debug "No -i support in sed"
    text="$(cat "$filename")"
    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
}

#file
_enddate() {
  _cf="$1"
  _res="$(${ACME_OPENSSL_BIN:-openssl} x509 -noout -enddate -in "$_cf")"
  if [ "$?" != "0" ] || [ -z "$_res" ]; then
    return 1
  fi

  case "$_res" in
  notAfter=*)
    echo "${_res#notAfter=}"
    ;;
  *)
    return 1
    ;;
  esac
}

#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" = "sha3-256" ] || [ "$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: certpath hashalg
#Output certificate fingerprint without colons
_fingerprint() {
  cert="$1"
  alg="$2"
  if [ -z "$alg" ]; then
    _usage "Usage: _fingerprint certpath hashalg"
    return 1
  fi

  if [ "$alg" = "sha256" ] || [ "$alg" = "sha1" ] || [ "$alg" = "md5" ]; then
    # openssl prints "SHA1 Fingerprint=AA:BB:CC:..."; strip prefix and colons.
    ${ACME_OPENSSL_BIN:-openssl} x509 -in "$cert" -noout -fingerprint -"$alg" | sed 's/.*=//; s/://g'
  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"
  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
  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"

  _new_key_file=""
  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"
    _new_key_file="1"
  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"
      #do not leave an empty file behind, or the next run would treat the key as existing
      if [ "$_new_key_file" ]; then
        rm -f "$f"
      fi
      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"
      #do not leave an empty file behind, or the next run would treat the key as existing
      if [ "$_new_key_file" ]; then
        rm -f "$f"
      fi
      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 extendedUsage
#cn
#The x509 Common Name is limited to 64 characters (RFC 5280 ub-common-name,
#enforced by openssl in ASN1_mbstring_ncopy), and an IP address or an empty
#name is not usable as CN either. When this rejects the name, _createcsr
#omits CN from the CSR subject and the CA takes the identifiers from the
#subjectAltName extension (issue 4867).
_is_valid_cn() {
  _cn_v="$1"
  if [ -z "$_cn_v" ] || [ "${#_cn_v}" -gt 64 ]; then
    return 1
  fi
  if _isIP "$_cn_v"; then
    return 1
  fi
  return 0
}

_createcsr() {
  _debug _createcsr
  domain="$1"
  domainlist="$2"
  csrkey="$3"
  csr="$4"
  csrconf="$5"
  acmeValidationv1="$6"
  extusage="$7"
  _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 [ "$extusage" ]; then
    printf "\nextendedKeyUsage=$extusage\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 _is_valid_cn "$_csr_cn"; then
      ${ACME_OPENSSL_BIN:-openssl} req -new -sha256 -key "$csrkey" -subj "//CN=$_csr_cn" -config "$csrconf" -out "$csr"
    else
      ${ACME_OPENSSL_BIN:-openssl} req -new -sha256 -key "$csrkey" -subj "//O=$PROJECT_NAME" -config "$csrconf" -out "$csr"
    fi
  else
    if _is_valid_cn "$_csr_cn"; then
      ${ACME_OPENSSL_BIN:-openssl} req -new -sha256 -key "$csrkey" -subj "/CN=$_csr_cn" -config "$csrconf" -out "$csr"
    else
      ${ACME_OPENSSL_BIN:-openssl} req -new -sha256 -key "$csrkey" -subj "/O=$PROJECT_NAME" -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
  # -config /dev/null: reading a CSR needs no config, but a missing default
  # openssl.cnf is fatal on some systems (e.g. NetBSD does not install one)
  ${ACME_OPENSSL_BIN:-openssl} req -noout -in "$_csrfile" -subject -config /dev/null | 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" -config /dev/null | grep "^ *DNS:.*" | tr -d ' \n')"
  _debug _dnsAltnames "$_dnsAltnames"

  # escape the wildcard '*' so it is not taken as a regex operator by grep/sed below
  _escapedAltnames="$(echo "$_dnsAltnames" | tr '*' '#')"
  _debug _escapedAltnames "$_escapedAltnames"
  _escapedSubject="$(echo "$_csrsubj" | tr '*' '#')"
  _debug _escapedSubject "$_escapedSubject"
  if _contains "$_escapedAltnames," "DNS:$_escapedSubject,"; then
    _debug "AltNames contains subject"
    _dnsAltnames="$(echo "$_escapedAltnames," | 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" -config /dev/null)"
  _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 [ "$(uname)" = "AIX" ]; then
    _debug "Using: AIX netstat"
    netstat -an | grep "^tcp" | grep "LISTEN" | grep "\.$_port "
    return 0
  fi

  if _exists "netstat"; then
    _debug "Using: netstat"
    if netstat -help 2>&1 | grep "\-p proto" >/dev/null; then
      #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" "base64"
  fi

}

_toPkcs8() {
  _cpkcs8="$1"
  _ckey="$2"
  pkcs8Password="$3"

  if [ "$pkcs8Password" ]; then
    ${ACME_OPENSSL_BIN:-openssl} pkcs8 -topk8 -inform PEM -outform PEM -v2 aes256 -passout "pass:$pkcs8Password" -in "$_ckey" -out "$_cpkcs8"
  else
    ${ACME_OPENSSL_BIN:-openssl} pkcs8 -topk8 -inform PEM -outform PEM -nocrypt -in "$_ckey" -out "$_cpkcs8"
  fi
  if [ "$?" = "0" ]; then
    _savedomainconf "Le_PKCS8Password" "$pkcs8Password" "base64"
  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 [password] [isEcc]
toPkcs8() {
  domain="$1"
  pkcs8Password="$2"

  if [ -z "$domain" ]; then
    _usage "Usage: $PROJECT_ENTRY --to-pkcs8 --domain <domain.tld> [--password <password>] [--ecc]"
    return 1
  fi

  _isEcc="$3"

  _initpath "$domain" "$_isEcc"

  _toPkcs8 "$CERT_PKCS8_PATH" "$CERT_KEY_PATH" "$pkcs8Password"

  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"
  _csreku="$4"

  _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" "" "$_csreku"

}

_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

  #Omnios
  if date -u -r "$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() {
  #Mac/BSD
  if date -u -j -f "%Y-%m-%d %H:%M:%S" "$(echo "$1" | tr -d "Z" | tr "T" ' ')" +"%s" 2>/dev/null; then
    return
  fi
  #Linux
  if date -u -d "$(echo "$1" | tr -d "Z" | tr "T" ' ')" +"%s" 2>/dev/null; then
    return
  fi

  #Solaris
  if gdate -u -d "$(echo "$1" | tr -d "Z" | tr "T" ' ')" +"%s" 2>/dev/null; then
    return
  fi
  #Omnios. Pass the date as argv (sys.argv[1]) instead of interpolating it into
  #the -c program text, so a quote in the input cannot inject Python code.
  if python3 -c "import datetime,sys; print(int(datetime.datetime.strptime(sys.argv[1], \"%Y-%m-%d %H:%M:%S\").replace(tzinfo=datetime.timezone.utc).timestamp()))" "$1" 2>/dev/null; then
    return
  fi
  #Omnios
  if python3 -c "import datetime,sys; print(int(datetime.datetime.strptime(sys.argv[1], \"%Y-%m-%dT%H:%M:%SZ\").replace(tzinfo=datetime.timezone.utc).timestamp()))" "$1" 2>/dev/null; then
    return
  fi
  _err "Cannot parse _date2time $1"
  return 1
}

#support the output format of openssl -enddate:
#     Apr 01 08:10:33 2022 GMT   to   1641283833
_ssldate2time() {
  #Linux
  if date -u -d "$1" +"%s" 2>/dev/null; then
    return
  fi
  #Solaris
  if gdate -u -d "$1" +"%s" 2>/dev/null; then
    return
  fi
  #Mac/BSD
  if date -j -f "%b %d %T %Y %Z" "$1" +"%s" 2>/dev/null; then
    return
  fi
  _err "Cannot parse _ssldate2time $1"
  return 1
}

_utc_date() {
  date -u "+%Y-%m-%d %H:%M:%S"
}

#Usage: _calc_next_renew_time createtime renewaldays [endtime]
#Prints createtime + renewaldays*86400 - 86400, capped so it never passes
#the certificate expiry: with short-lived certs (internal CAs, upcoming
#CA/B SC-081 47-day maximum) a fixed RenewalDays would otherwise schedule
#the renewal after notAfter and leave an expired cert in place.
#The cap is one day before endtime, or one hour before for certs whose
#lifetime is 24 hours or less, mirroring the --valid-to scheduling.
_calc_next_renew_time() {
  _cnrt_create="$1"
  _cnrt_days="$2"
  _cnrt_end="$3"
  _cnrt_next=$(_math "$_cnrt_create" + "$_cnrt_days" \* 24 \* 60 \* 60 - 86400)
  if [ -z "$_cnrt_end" ]; then
    printf "%s" "$_cnrt_next"
    return 0
  fi
  if [ "$(_math "$_cnrt_end" - "$_cnrt_create")" -gt 86400 ]; then
    _cnrt_cap=$(_math "$_cnrt_end" - 86400)
  else
    _cnrt_cap=$(_math "$_cnrt_end" - 3600)
  fi
  if [ "$_cnrt_next" -gt "$_cnrt_cap" ]; then
    _cnrt_next="$_cnrt_cap"
  fi
  printf "%s" "$_cnrt_next"
}

#Usage: _calc_validto_renew_time notaftertime renewaldays now
#Prints the next renew time for a cert issued with a relative --valid-to.
#A negative renewaldays is anchored to the expiry: notaftertime +
#renewaldays*86400. Otherwise the cert renews one day before the expiry,
#or one hour before for certs whose lifetime is 24 hours or less.
_calc_validto_renew_time() {
  _cvrt_end="$1"
  _cvrt_days="$2"
  _cvrt_now="$3"
  if [ "$_cvrt_days" ] && [ "$_cvrt_days" -lt 0 ]; then
    _math "$_cvrt_end" + "$_cvrt_days" \* 24 \* 60 \* 60
    return 0
  fi
  if [ "$(_math "$_cvrt_end" - "$_cvrt_now")" -gt 86400 ]; then
    _math "$_cvrt_end" - 86400
  else
    _math "$_cvrt_end" - 3600
  fi
}

_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 [ "$ACME_USE_IPV6_REQUESTS" ]; then
      _ACME_CURL="$_ACME_CURL --ipv6 "
    elif [ "$ACME_USE_IPV4_REQUESTS" ]; then
      _ACME_CURL="$_ACME_CURL --ipv4 "
    fi
    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_USE_IPV6_REQUESTS" ]; then
      _ACME_WGET="$_ACME_WGET --inet6-only "
    elif [ "$ACME_USE_IPV4_REQUESTS" ]; then
      _ACME_WGET="$_ACME_WGET --inet4-only "
    fi
    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
      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"
    chmod 600 "$__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 "/^$_sdkey *=.*$/d" >"$_c_c_f"
  else
    _err "Config file is empty, cannot clear"
  fi
}

#_read_conf file  key
_read_conf() {
  _r_c_f="$1"
  _sdkey="$2"
  if [ -f "$_r_c_f" ]; then
    _sdv="$(
      eval "$(grep "^$_sdkey *=" "$_r_c_f")"
      eval "printf \"%s\" \"\$$_sdkey\""
    )"
    if _startswith "$_sdv" "${B64CONF_START}" && _endswith "$_sdv" "${B64CONF_END}"; then
      _sdv="$(echo "$_sdv" | sed "s/${B64CONF_START}//" | sed "s/${B64CONF_END}//" | _dbase64)"
    fi
    printf "%s" "$_sdv"
  else
    _debug "Config file is empty, cannot read $_sdkey"
  fi
}

#_savedomainconf   key  value  base64encode
#save to domain.conf
_savedomainconf() {
  _save_conf "$DOMAIN_CONF" "$@"
}

#_cleardomainconf   key
_cleardomainconf() {
  _clear_conf "$DOMAIN_CONF" "$1"
}

#_readdomainconf   key
_readdomainconf() {
  _read_conf "$DOMAIN_CONF" "$1"
}

#_migratedomainconf   oldkey  newkey  base64encode
_migratedomainconf() {
  _old_key="$1"
  _new_key="$2"
  _b64encode="$3"
  _old_value=$(_readdomainconf "$_old_key")
  _cleardomainconf "$_old_key"
  if [ -z "$_old_value" ]; then
    return 1 # migrated failed: old value is empty
  fi
  _new_value=$(_readdomainconf "$_new_key")
  if [ -n "$_new_value" ]; then
    _debug "Domain config new key exists, old key $_old_key='$_old_value' has been removed."
    return 1 # migrated failed: old value replaced by new value
  fi
  _savedomainconf "$_new_key" "$_old_value" "$_b64encode"
  _debug "Domain config $_old_key has been migrated to $_new_key."
}

#_migratedeployconf   oldkey  newkey  base64encode
_migratedeployconf() {
  _migratedomainconf "$1" "SAVED_$2" "$3" ||
    _migratedomainconf "SAVED_$1" "SAVED_$2" "$3" # try only when oldkey itself is not found
}

#key  value  base64encode
_savedeployconf() {
  _savedomainconf "SAVED_$1" "$2" "$3"
  #remove later
  _cleardomainconf "$1"
}

#key
_cleardeployconf() {
  _cleardomainconf "SAVED_$1"
  #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"

  if _exists "socat"; then
    _NC="socat"
    if [ "$Le_Listen_V6" ]; then
      _NC="$_NC -6"
      SOCAT_OPTIONS=TCP6-LISTEN
    elif [ "$Le_Listen_V4" ]; then
      _NC="$_NC -4"
      SOCAT_OPTIONS=TCP4-LISTEN
    else
      SOCAT_OPTIONS=TCP-LISTEN
    fi

    if [ "$DEBUG" ] && [ "$DEBUG" -gt "1" ]; then
      _NC="$_NC -d -d -v"
    fi

    SOCAT_OPTIONS=$SOCAT_OPTIONS:$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="$!"
  else
    _PYTHON=""
    if _exists "python3"; then
      _PYTHON="python3"
    elif _exists "python2"; then
      _PYTHON="python2"
    elif _exists "python"; then
      _PYTHON="python"
    fi
    if [ "$_PYTHON" ]; then
      _debug "Using python: $_PYTHON"
      _AF="socket.AF_INET"
      _BIND_ADDR="0.0.0.0"
      if [ "$Le_Listen_V6" ]; then
        _AF="socket.AF_INET6"
        _BIND_ADDR="::"
      fi
      if [ "$ncaddr" ]; then
        _BIND_ADDR="$ncaddr"
      fi
      export _SOCAT_ERR="$(_mktemp)"
      $_PYTHON -c "import socket,sys;s=socket.socket($_AF,socket.SOCK_STREAM);s.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1);s.bind((sys.argv[2],int(sys.argv[1])));s.listen(5);res='HTTP/1.0 200 OK\r\nContent-Length: '+str(len(sys.argv[3]))+'\r\n\r\n'+sys.argv[3];
while True:
 c,a=s.accept()
 c.sendall(res.encode() if hasattr(res, 'encode') else res)
 c.close()" "$Le_HTTPPort" "$_BIND_ADDR" "$content" 2>"$_SOCAT_ERR" &
      serverproc="$!"
      _NC="$_PYTHON"
    else
      _err "Please install socat or python first for standalone mode."
      return 1
    fi
  fi

  if [ -f "$_SOCAT_ERR" ]; then
    if grep "Permission denied" "$_SOCAT_ERR" >/dev/null; then
      _err "$_NC: $(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
  # Convert a relative --home to an absolute path: later code cd's around
  # (e.g. installOnline extracts and enters the archive dir), where a
  # relative path would point into the wrong directory.
  # https://github.com/acmesh-official/acme.sh/issues/6477
  case "$LE_WORKING_DIR" in
  /*) ;;
  *)
    if [ -d "$LE_WORKING_DIR" ]; then
      LE_WORKING_DIR="$(cd "$LE_WORKING_DIR" && pwd)"
    fi
    ;;
  esac
  export LE_WORKING_DIR

  if [ -z "$LE_CONFIG_HOME" ]; then
    LE_CONFIG_HOME="$LE_WORKING_DIR"
  fi
  case "$LE_CONFIG_HOME" in
  /*) ;;
  *)
    if [ -d "$LE_CONFIG_HOME" ]; then
      LE_CONFIG_HOME="$(cd "$LE_CONFIG_HOME" && pwd)"
    fi
    ;;
  esac
  _debug "Using config home: $LE_CONFIG_HOME"
  export LE_CONFIG_HOME

  # Paths with whitespace break the unquoted $_CURL/$_WGET command expansion,
  # so fail early with a clear error instead of a cryptic curl/wget failure.
  # https://github.com/acmesh-official/acme.sh/issues/2163
  case "$LE_WORKING_DIR$LE_CONFIG_HOME" in
  *" "*)
    _err "The --home or --config-home path can not contain spaces: '$LE_WORKING_DIR'"
    _err "Please install $PROJECT_NAME to a path without spaces."
    exit 1
    ;;
  esac

  _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=""
  ACME_RENEWAL_INFO=""
}

#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" "" 10)
    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

    ACME_RENEWAL_INFO=$(echo "$response" | _egrep_o 'renewalInfo" *: *"[^"]*"' | cut -d '"' -f 3)
    export ACME_RENEWAL_INFO

    _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"
    _debug "ACME_RENEWAL_INFO" "$ACME_RENEWAL_INFO"
    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"
  if [ "$_api_server" = "$CA_ZEROSSL" ]; then
    _info "$(__green "If this host is IPv6-only: ZeroSSL currently has no IPv6 endpoint.")"
    _info "$(__green "Try another CA, e.g.: $PROJECT_ENTRY --set-default-ca --server letsencrypt")"
    _info "See: $(__green "https://github.com/acmesh-official/acme.sh/issues/6872")"
  fi
  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/ {
  # the ^~ prefix wins over regex-skipping blocks like \"location ^~ /\",
  # the nested regex location still captures the token as \$1
  location ~ \"^/\.well-known/acme-challenge/([-_a-zA-Z0-9]+)\$\" {
    default_type text/plain;
    return 200 \"\$1.$_thumbpt\";
  }
  return 404;
}
#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 2>&1; 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 "Running pre hook:'$_chk_pre_hook'"
    if ! (
      export Le_Domain="$_chk_main_domain"
      export Le_Alt="$_chk_alt_domains"
      cd "$DOMAIN_PATH" && eval "$_chk_pre_hook"
    ); then
      _err "Error occurred when running pre hook."
      return 1
    fi
  fi

  if _hasfield "$_chk_web_roots" "$NO_VALUE" && [ "$_chk_web_roots" = "$NO_VALUE" ]; then
    if ! _exists "socat" && ! _exists "python" && ! _exists "python2" && ! _exists "python3"; then
      _err "Please install socat or python 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_kid="$2"
  _eab_hmac_key="$3"
  _initpath
  _regAccount "$_account_key_length" "$_eab_kid" "$_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
}

#Reads a comma- or space-separated email list from stdin and prints
#the ACME contact list items: "mailto:a@example.com","mailto:b@example.com"
_mailto_contacts() {
  _mc_out=""
  for _mc_m in $(tr ',' ' '); do
    _mc_out="$_mc_out,\"mailto:$_mc_m\""
  done
  echo "$_mc_out" | cut -c 2-
}

_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_kid="$2"
  _eab_hmac_key="$3"
  _debug3 _regAccount "$_regAccount"
  _initAPI

  mkdir -p "$CA_DIR"

  if [ ! -s "$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_kid" ] && [ "$_eab_hmac_key" ]; then
    _savecaconf CA_EAB_KEY_ID "$_eab_kid"
    _savecaconf CA_EAB_HMAC_KEY "$_eab_hmac_key"
  fi
  _eab_kid=$(_readcaconf "CA_EAB_KEY_ID")
  _eab_hmac_key=$(_readcaconf "CA_EAB_HMAC_KEY")
  _secure_debug3 _eab_kid "$_eab_kid"
  _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_kid" ] || [ -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
      #the ZeroSSL EAB endpoint takes a single address, use the first one
      _eab_email="$(echo "$_email" | tr ',' ' ' | awk '{print $1}')"
      _eabresp=$(_post "email=$_eab_email" $_ZERO_EAB_ENDPOINT)
      if [ "$?" != "0" ]; then
        _debug2 "$_eabresp"
        _err "Cannot get EAB credentials from ZeroSSL."
        return 1
      fi
      _secure_debug2 _eabresp "$_eabresp"
      _eab_kid="$(echo "$_eabresp" | tr ',}' '\n\n' | grep '"eab_kid"' | cut -d : -f 2 | tr -d '"')"
      _secure_debug2 _eab_kid "$_eab_kid"
      if [ -z "$_eab_kid" ]; then
        _err "Cannot resolve _eab_kid"
        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_kid"
      _savecaconf CA_EAB_HMAC_KEY "$_eab_hmac_key"
    fi
  fi
  if [ "$_eab_kid" ] && [ "$_eab_hmac_key" ]; then
    eab_protected="{\"alg\":\"HS256\",\"kid\":\"$_eab_kid\",\"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"
    if [ -z "$key_hex" ]; then
      _err "Cannot base64-decode the eab-hmac-key. Please check the value, and your openssl version."
      return 1
    fi

    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\": [$(echo "$_email" | _mailto_contacts)], "
  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
    _accUri="$(_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"

  #RFC 8555 sec 7.3.6 requires 401 for requests from a deactivated account,
  #but Boulder (Let's Encrypt) historically returns 403. Accept both.
  if [ "$code" = '403' ] || [ "$code" = '401' ]; 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": ['$(echo "$_email" | _mailto_contacts)']}'
  else
    updjson='{"contact": []}'
  fi

  _send_signed_request "$_accUri" "$updjson"

  if [ "$code" = '200' ]; then
    echo "$response" >"$ACCOUNT_JSON_PATH"
    _info "Account update success for $_accUri."
    # persist the effective mailbox like _regAccount does; otherwise
    # "--update-account -m new@..." updates the CA but the local conf
    # keeps showing the old address (issue 4673)
    if [ "$_email" ]; then
      _savecaconf "CA_EMAIL" "$_email"
    fi

    ACCOUNT_THUMBPRINT="$(__calc_account_thumbprint)"
    _info "ACCOUNT_THUMBPRINT" "$ACCOUNT_THUMBPRINT"
  else
    _info "An error occurred and the account was not updated."
    return 1
  fi
}

#Implement account key rollover
updateaccountkey() {
  _length="$1"
  _initpath

  if [ ! -f "$ACCOUNT_KEY_PATH" ]; then
    _err "Account key not found at: $ACCOUNT_KEY_PATH"
    return 1
  fi
  ACCOUNT_KEY_PATH_NEW="$ACCOUNT_KEY_PATH.new"

  _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
  _inner_payload="{\"account\": \"$_accUri\", \"oldKey\": $jwk}"

  _initAPI
  if [ -z "$ACME_KEY_CHANGE" ]; then
    _err "Server does not expose keyChange url."
    return 1
  fi

  _url="$ACME_KEY_CHANGE"
  if _createkey "$_length" "$ACCOUNT_KEY_PATH_NEW"; then
    _info "New account key creation OK."
  else
    _err "New account key creation error."
    return 1
  fi

  if ! _calcjwk "$ACCOUNT_KEY_PATH_NEW"; then
    rm -f "$ACCOUNT_KEY_PATH_NEW"
    return 1
  fi
  _inner_protected="{\"url\": \"${_url}$JWK_HEADERPLACE_PART2, \"jwk\": $jwk"'}'
  _inner_protected64="$(printf "%s" "$_inner_protected" | _base64 | _url_replace)"
  _inner_payload64="$(printf "%s" "$_inner_payload" | _base64 | _url_replace)"
  if ! _inner_sig_t="$(printf "%s" "$_inner_protected64.$_inner_payload64" | _sign "$ACCOUNT_KEY_PATH_NEW" "sha256")"; then
    _err "Sign request failed."
    rm -f "$ACCOUNT_KEY_PATH_NEW"
    return 1
  fi
  _debug3 _inner_sig_t "$_inner_sig_t"

  _inner_sig="$(printf "%s" "$_inner_sig_t" | _url_replace)"
  _debug3 _inner_sig "$_inner_sig"

  _body="{\"protected\": \"$_inner_protected64\", \"payload\": \"$_inner_payload64\", \"signature\": \"$_inner_sig\"}"

  if ! _send_signed_request "$_url" "$_body" "" "$ACCOUNT_KEY_PATH"; then
    _err "Error rotating account key: $response."
    rm -f "$ACCOUNT_KEY_PATH_NEW"
    return 1
  fi

  if [ "$code" = '200' ]; then
    echo "$response" >"$ACCOUNT_JSON_PATH"
    mv -f "$ACCOUNT_KEY_PATH_NEW" "$ACCOUNT_KEY_PATH"
    _info "Account key rotation success for $_accUri."
  elif [ "$code" = "409" ]; then
    _err "An existing account is using the new key"
    rm -f "$ACCOUNT_KEY_PATH_NEW"
    return 1
  else
    _err "Account key rollover error: $response"
    rm -f "$ACCOUNT_KEY_PATH_NEW"
    return 1
  fi

  __CACHED_JWK_KEY_FILE=""
  _calcjwk "$ACCOUNT_KEY_PATH"

  ACCOUNT_THUMBPRINT="$(__calc_account_thumbprint)"
  _info "ACCOUNT_THUMBPRINT" "$ACCOUNT_THUMBPRINT"

  CA_KEY_HASH="$(__calcAccountKeyHash)"
  _debug "Calc CA_KEY_HASH" "$CA_KEY_HASH"
  _savecaconf CA_KEY_HASH "$CA_KEY_HASH"
}

#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" ] || [ "$code" = "401" ]; then
    #RFC 8555 sec 7.3.6: 401 from a deactivated account; Boulder returns 403
    _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  wildcard  ca_name  days
#Print the TXT record(s) the user must add to enable persistent DNS validation
#per draft-ietf-acme-dns-persist-01.
makednspersistvalue() {
  _mdpv_domain="$1"
  _mdpv_wildcard="$2"
  _mdpv_ca_name="$3"
  _mdpv_days="$4"

  if [ -z "$_mdpv_domain" ]; then
    _err "Please specify a domain with -d."
    return 1
  fi

  if [ -n "$_mdpv_days" ]; then
    case "$_mdpv_days" in
    '' | *[!0-9]*)
      _err "--dns-persist-days must be a positive integer, got: $_mdpv_days"
      return 1
      ;;
    esac
    if [ "$_mdpv_days" -lt 1 ]; then
      _err "--dns-persist-days must be at least 1."
      return 1
    fi
  fi

  _initpath

  _accUri="$(_readcaconf ACCOUNT_URL)"
  if [ -z "$_accUri" ]; then
    _info "No account is registered for $ACME_DIRECTORY yet, registering one now..."
    if ! _regAccount "$DEFAULT_ACCOUNT_KEY_LENGTH"; then
      _err "Cannot register account."
      return 1
    fi
    _accUri="$(_readcaconf ACCOUNT_URL)"
  fi

  if [ -z "$_accUri" ]; then
    _err "Cannot determine the ACME account URL."
    return 1
  fi
  _debug "Account URL" "$_accUri"

  _txt_name="_validation-persist.$_mdpv_domain"

  _txt_suffix="; accounturi=$_accUri"
  if [ "$_mdpv_wildcard" = "1" ]; then
    _txt_suffix="$_txt_suffix; policy=wildcard"
  fi
  if [ -n "$_mdpv_days" ]; then
    _persist_until=$(_math "$(_time)" + "$_mdpv_days" \* 86400)
    _txt_suffix="$_txt_suffix; persistUntil=$_persist_until"
    _info "persistUntil set to $(__green "$(_time2str "$_persist_until")") ($_mdpv_days days from now)"
  fi

  if [ -n "$_mdpv_ca_name" ]; then
    _info ""
    _info "Add the following DNS TXT record to enable persistent DNS validation:"
    _info ""
    _info "$(printf 'TXT persist domain:%s' "$(__green "$_txt_name")")"
    _info "$(printf 'TXT persist value :%s' "$(__green "\"$_mdpv_ca_name$_txt_suffix\"")")"
    _info ""
    return 0
  fi

  _info "Fetching ACME directory: $ACME_DIRECTORY"
  _dir_resp="$(_get "$ACME_DIRECTORY" "" 30)"
  if [ "$?" != "0" ] || [ -z "$_dir_resp" ]; then
    _err "Cannot fetch ACME directory: $ACME_DIRECTORY"
    return 1
  fi
  _dir_resp="$(echo "$_dir_resp" | _json_decode)"
  _debug2 _dir_resp "$_dir_resp"

  _caa_array="$(echo "$_dir_resp" | tr -d ' \r\n\t' | _egrep_o '"caaIdentities":\[[^]]*\]')"
  _debug2 _caa_array "$_caa_array"
  _caaids="$(echo "$_caa_array" | sed 's/.*\[//' | sed 's/\].*//' | tr ',' '\n' | tr -d '"')"
  _debug2 _caaids "$_caaids"

  if [ -z "$_caaids" ]; then
    _err "The directory does not include 'caaIdentities'. Please specify --dns-persist-ca-name explicitly."
    return 1
  fi

  _info ""
  _info "Add ANY ONE of the following DNS TXT records to enable persistent DNS validation."
  _info "(You only need to add one; pick whichever issuer identity you prefer.)"
  for _id in $_caaids; do
    [ -z "$_id" ] && continue
    _info ""
    _info "$(printf 'TXT persist domain:%s' "$(__green "$_txt_name")")"
    _info "$(printf 'TXT persist value :%s' "$(__green "\"$_id$_txt_suffix\"")")"
  done
  _info ""
}

# 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() {
  #splitting must not glob: a "*" segment would match files in cwd
  set -f
  _ipv4_saved_ifs="$IFS"
  IFS='.'
  # shellcheck disable=SC2086
  set -- $1
  IFS="$_ipv4_saved_ifs"
  set +f
  if [ $# -ne 4 ]; then
    return 1
  fi
  for _ipv4_seg in "$@"; do
    _debug2 _ipv4_seg "$_ipv4_seg"
    case "$_ipv4_seg" in
    *[!0-9]* | "") return 1 ;;
    esac
    if [ "${#_ipv4_seg}" -gt 3 ] || [ "$_ipv4_seg" -gt 255 ]; then
      return 1
    fi
  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}"
  _certificate_profile="${18}"
  _extended_key_usage="${19}"

  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" ] && [ -z "$_ari_should_renew" ] && [ "$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
  # Save Le_DNSSleep unconditionally here: the save inside the dns_entries
  # branch is skipped when all authorizations are already valid (e.g. issuing
  # the ECC twin of a just-issued RSA cert), which left the setting out of
  # that cert's conf. https://github.com/acmesh-official/acme.sh/issues/6986
  if [ "$Le_DNSSleep" ]; then
    _savedomainconf "Le_DNSSleep" "$Le_DNSSleep"
  fi
  if [ "$_preferred_chain" ]; then
    _savedomainconf "Le_Preferred_Chain" "$_preferred_chain" "base64"
  else
    _cleardomainconf "Le_Preferred_Chain"
  fi
  if [ "$_certificate_profile" ]; then
    _savedomainconf "Le_Certificate_Profile" "$_certificate_profile"
  else
    _cleardomainconf "Le_Certificate_Profile"
  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."
    _on_issue_err "$_post_hook"
    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" "$_eab_kid" "$_eab_hmac_key"; 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
    _keyusage="$_extended_key_usage"
    if [ "$Le_API" = "$CA_GOOGLE" ] || [ "$Le_API" = "$CA_GOOGLE_TEST" ]; then
      if [ -z "$_keyusage" ]; then
        #https://github.com/acmesh-official/acme.sh/issues/6610
        #google accepts serverauth only
        _keyusage="serverAuth"
      fi
    fi
    if ! _createcsr "$_main_domain" "$_alt_domains" "$CERT_KEY_PATH" "$CSR_PATH" "$DOMAIN_SSL_CONF" "" "$_keyusage"; then
      _err "Error creating CSR."
      _clearup
      _on_issue_err "$_post_hook"
      return 1
    fi
    if [ "$_extended_key_usage" ]; then
      _savedomainconf "Le_ExtKeyUse" "$_extended_key_usage"
    else
      _cleardomainconf "Le_ExtKeyUse"
    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
    if [ "$_certificate_profile" ]; then
      _newOrderObj="$_newOrderObj,\"profile\": \"$_certificate_profile\""
    fi

    # RFC 9773 Section 5: include "replaces" only when this is an actual
    # renewal (--renew path), the CA advertises renewalInfo, and a prior
    # cert exists. --issue (even with --force) is not a renewal per RFC 9773
    # which speaks of "a clear predecessor certificate" issued by this CA.
    # NO_ARI=1 (env, account.conf, or ca.conf) disables ARI entirely, so the
    # "replaces" field is also omitted.
    _replaces_certID=""
    if [ "$NO_ARI" = "1" ]; then
      _debug "NO_ARI=1, omitting ARI 'replaces' field from newOrder"
    elif [ "$_ACME_IS_RENEW" = "1" ] && [ "$ACME_RENEWAL_INFO" ] && [ -f "$CERT_PATH" ]; then
      _replaces_certID="$(_getARICertID "$CERT_PATH")"
      _debug "Adding ARI replaces" "$_replaces_certID"
    fi

    _debug "STEP 1, Ordering a Certificate"
    _newOrderReplacesObj="$_newOrderObj"
    if [ "$_replaces_certID" ]; then
      _newOrderReplacesObj="$_newOrderObj,\"replaces\": \"$_replaces_certID\""
    fi
    if ! _send_signed_request "$ACME_NEW_ORDER" "$_newOrderReplacesObj}"; then
      _err "Error creating new order."
      _clearup
      _on_issue_err "$_post_hook"
      return 1
    fi
    # RFC 9773 Section 5 only defines the "alreadyReplaced" error, but real CAs
    # (Let's Encrypt) may also reject with a malformed error if the prior cert
    # was issued by a different issuer / different CA. Retry without "replaces"
    # whenever the failure mentions ARI or the replaces field.
    if [ "$_replaces_certID" ] && { _contains "$response" "alreadyReplaced" || _contains "$response" "urn:ietf:params:acme:error:malformed" || _contains "$response" "'replaces'" || _contains "$response" "ARI"; }; then
      _info "ARI 'replaces' rejected by CA, retrying newOrder without 'replaces'."
      if ! _send_signed_request "$ACME_NEW_ORDER" "$_newOrderObj}"; then
        _err "Error creating new order."
        _clearup
        _on_issue_err "$_post_hook"
        return 1
      fi
    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 | _authorizations_from_order)"
    _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 [ "$_currentRoot" = "$W_DNS_PERSIST" ]; then
        vtype="$VTYPE_DNS_PERSIST"
      elif _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

      # Fix for empty error objects in response which mess up the original code, adapted from fix suggested here: https://github.com/acmesh-official/acme.sh/issues/4933#issuecomment-1870499018
      entry="$(echo "$response" | sed s/'"error":{}'/'"error":null'/ | _egrep_o '[^{]*"type":"'$vtype'"[^}]*')"
      _debug entry "$entry"

      if [ -z "$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
        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

        if [ "$vtype" = "$VTYPE_DNS_PERSIST" ]; then
          # dns-persist-01 challenges have no token; the TXT record is
          # provisioned out-of-band. Use a non-empty placeholder so the
          # downstream code does not treat this entry as already verified.
          keyauthorization="$VTYPE_DNS_PERSIST"
          _debug keyauthorization "$keyauthorization"
        else
          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
          keyauthorization="$token.$thumbprint"
          _debug keyauthorization "$keyauthorization"
        fi
      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=""
        # strip the trailing dot of a fully-qualified alias domain
        _d_alias="${_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"

        # Ensure .well-known is visible to web server user/group
        # https://github.com/Neilpang/acme.sh/pull/32
        if ! (umask ugo+rx &&
          mkdir -p "$wellknown_path" &&
          printf "%s" "$keyauthorization" >"$wellknown_path/$token"); then
          _err "$d: Cannot write token to file: $wellknown_path/$token"
          _clearupwebbroot "$_currentRoot" "$removelevel" "$token"
          _clearup
          _on_issue_err "$_post_hook" "$vlist"
          return 1
        fi
        if ! chmod a+r "$wellknown_path/$token"; then
          _debug "chmod failed, will just continue."
        fi
      fi
    elif [ "$vtype" = "$VTYPE_ALPN" ]; then
      _ncaddr="$(_getfield "$_local_addr" "$_ncIndex")"
      _ncIndex="$(_math $_ncIndex + 1)"
      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."
            if _isIPv6 "$d"; then
              host="[$d]"
            else
              host="$d"
            fi
            _get "http://$host/.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
      _retryafter=$(echo "$responseHeaders" | grep -i "^Retry-After *: *[0-9]\+ *" | cut -d : -f 2 | tr -d ' ' | tr -d '\r')
      _sleep_overload_retry_sec=$_retryafter
      if [ "$_sleep_overload_retry_sec" ]; then
        if [ $_sleep_overload_retry_sec -le 600 ]; then
          _sleep $_sleep_overload_retry_sec
        else
          _info "The retryafter=$_retryafter value is too large (> 600), will not retry anymore."
          _clearupwebbroot "$_currentRoot" "$removelevel" "$token"
          _clearup
          _on_issue_err "$_post_hook" "$vlist"
          return 1
        fi
      fi
    done

  done

  _clearup
  _info "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" "\"ready\""; then
      _info "Order status is 'ready', 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" ] && [ $_retryafter -gt 0 ]; then
        _info "Sleeping for $_retryafter seconds then retrying"
        _sleep $_retryafter
      else
        _sleep 2
      fi
    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" ] && [ $_retryafter -gt 0 ]; 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

  # cover case where the final poll returned 'valid'
  if [ -z "$Le_LinkCert" ] && _contains "$response" "\"status\":\"valid\""; then
    Le_LinkCert="$(echo "$response" | _egrep_o '"certificate" *: *"[^"]*"' | cut -d '"' -f 4)"
  fi

  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

  if ! _contains "$response" "$BEGIN_CERT"; 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

  echo "$response" | _strip_blank_lines >"$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

        if ! _contains "$response" "$BEGIN_CERT"; then
          _debug2 "Skipping alternate cert link due to unexpected response format."
          continue
        fi
        _relcert="$CERT_PATH.alt"
        _relfullchain="$CERT_FULLCHAIN_PATH.alt"
        _relca="$CA_CERT_PATH.alt"
        echo "$response" | _strip_blank_lines >"$_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" ]; 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
      Le_NextRenewTime=$(_calc_validto_renew_time "$Le_NextRenewTime" "$Le_RenewalDays" "$(_time)")
      Le_NextRenewTimeStr=$(_time2str "$Le_NextRenewTime")
    fi
  elif [ "$Le_RenewalDays" -lt "0" ]; then
    _enddate_value=$(_enddate "$CERT_PATH")
    if [ "$?" != "0" ] || [ -z "$_enddate_value" ]; then
      _err "Failed to get certificate end date for $CERT_PATH"
      return 1
    fi

    _endtime=$(_ssldate2time "$_enddate_value")
    if [ "$?" != "0" ] || [ -z "$_endtime" ]; then
      _err "Cannot parse _enddate_value: $_enddate_value"
      return 1
    fi
    Le_NextRenewTime=$(_math "$_endtime" + "$Le_RenewalDays" \* 24 \* 60 \* 60)
    Le_NextRenewTimeStr=$(_time2str "$Le_NextRenewTime")
  else
    _endtime_for_cap=""
    _enddate_value=$(_enddate "$CERT_PATH")
    if [ "$?" = "0" ] && [ "$_enddate_value" ]; then
      _endtime_for_cap=$(_ssldate2time "$_enddate_value")
    fi
    Le_NextRenewTime=$(_calc_next_renew_time "$Le_CertCreateTime" "$Le_RenewalDays" "$_endtime_for_cap")
    Le_NextRenewTimeStr=$(_time2str "$Le_NextRenewTime")
  fi

  # RFC 9773 ARI: if the CA exposes renewalInfo, override Le_NextRenewTime
  # with a time picked at random within the suggestedWindow. This both gives
  # the CA full control over renewal scheduling and disperses renewals across
  # the network so all clients don't hit the CA at the same instant.
  # Set NO_ARI=1 (env, account.conf, or ca.conf) to opt out and fall back to
  # the legacy time-based renewal calculation.
  if [ "$NO_ARI" = "1" ]; then
    _debug "NO_ARI=1, skipping ARI suggestedWindow override"
  elif [ "$ACME_RENEWAL_INFO" ] && [ -f "$CERT_PATH" ] && [ -z "$_notAfter" ]; then
    _ari_resp_new="$(_get_ARI "$CERT_PATH")"
    _debug2 "_ari_resp_new" "$_ari_resp_new"
    _ari_start_new="$(echo "$_ari_resp_new" | _egrep_o '"start" *: *"[^"]*' | sed 's/.*"//')"
    _ari_end_new="$(echo "$_ari_resp_new" | _egrep_o '"end" *: *"[^"]*' | sed 's/.*"//')"
    if [ "$_ari_start_new" ] && [ "$_ari_end_new" ]; then
      _ari_start_t_new="$(_date2time "$(echo "$_ari_start_new" | sed 's/\.[0-9]*//')")"
      _ari_end_t_new="$(_date2time "$(echo "$_ari_end_new" | sed 's/\.[0-9]*//')")"
      if [ "$_ari_start_t_new" ] && [ "$_ari_end_t_new" ] && [ "$_ari_end_t_new" -gt "$_ari_start_t_new" ]; then
        _ari_window=$(_math "$_ari_end_t_new" - "$_ari_start_t_new")
        _ari_offset=$(_math "$(_time)" % "$_ari_window")
        Le_NextRenewTime=$(_math "$_ari_start_t_new" + "$_ari_offset")
        Le_NextRenewTimeStr=$(_time2str "$Le_NextRenewTime")
        _info "ARI suggestedWindow: $(__green "$_ari_start_new") to $(__green "$_ari_end_new")"
        _info "Next renewal time picked from ARI window: $(__green "$Le_NextRenewTimeStr")"
      fi
    fi
  fi

  # Warn when the scheduled renewal falls after the cert has already expired,
  # e.g. a 1-day cert from an internal CA combined with the default 30-day
  # schedule, which computes from the creation date and never looks at
  # notAfter. Skip the warning for a fixed-date --valid-to: there
  # Le_NextRenewTime equals the expiry by design and the non-renewable state
  # was already reported above. https://github.com/acmesh-official/acme.sh/issues/6917
  if [ -z "$_valid_to" ] || _startswith "$_valid_to" "+"; then
    _renew_chk_enddate="$(_enddate "$CERT_PATH")"
    _renew_chk_endtime="$(_ssldate2time "$_renew_chk_enddate")"
    if [ "$Le_NextRenewTime" ] && [ "$_renew_chk_endtime" ] && [ "$Le_NextRenewTime" -ge "$_renew_chk_endtime" ]; then
      _info "$(__red "WARNING: the cert expires at $_renew_chk_enddate, BEFORE the next scheduled renewal time $Le_NextRenewTimeStr.")"
      _info "$(__red "The cert will already be expired when the renewal runs. If your CA issues short-lived certs, use a negative --days value (e.g. --days -1) to renew relative to the expiry time.")"
    fi
  fi

  _savedomainconf "Le_NextRenewTimeStr" "$Le_NextRenewTimeStr"
  _savedomainconf "Le_NextRenewTime" "$Le_NextRenewTime"

  #convert to pkcs12
  Le_PFXPassword="$(_readdomainconf Le_PFXPassword)"
  if [ "$Le_PFXPassword" ]; then
    _toPkcs "$CERT_PFX_PATH" "$CERT_KEY_PATH" "$CERT_PATH" "$CA_CERT_PATH" "$Le_PFXPassword"
  fi

  #convert to pkcs8
  Le_PKCS8Password="$(_readdomainconf Le_PKCS8Password)"
  if [ "$Le_PKCS8Password" ]; then
    _toPkcs8 "$CERT_PKCS8_PATH" "$CERT_KEY_PATH" "$Le_PKCS8Password"
  fi

  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
#Reads a PEM chain from stdin, prints it without the blank lines.
#Some CAs (Let's Encrypt) separate the certificates of a chain with a blank
#line, others (ZeroSSL) don't. The blank lines are valid PEM (RFC 7468), but
#some devices and APIs reject them, so the certs are stored back to back.
#https://github.com/acmesh-official/acme.sh/issues/1940
_strip_blank_lines() {
  sed '/^[[:space:]]*$/d'
}

_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"
  _info "Renew: $Le_Domain"
  _set_level=${NOTIFY_LEVEL:-$NOTIFY_LEVEL_DEFAULT}
  _info "$(__green "Renewing: '$Le_Domain'")"
  if [ ! -f "$DOMAIN_CONF" ]; then
    _info "'$Le_Domain' is not an issued domain, skipping."
    return $RENEW_SKIP
  fi

  if [ "$Le_RenewalDays" ]; then
    _savedomainconf Le_RenewalDays "$Le_RenewalDays"
  fi

  . "$DOMAIN_CONF"
  _debug Le_API "$Le_API"

  #don't switch it back
  #  case "$Le_API" in
  #  "$CA_LETSENCRYPT_V2_TEST")
  #    _info "Switching back to $CA_LETSENCRYPT_V2"
  #    Le_API="$CA_LETSENCRYPT_V2"
  #    ;;
  #  "$CA_GOOGLE_TEST")
  #    _info "Switching back to $CA_GOOGLE"
  #    Le_API="$CA_GOOGLE"
  #    ;;
  #  esac

  if [ "$_server" ]; then
    Le_API="$_server"
  fi
  _info "Renewing using Le_API=$Le_API"

  # Honor --local-address given on the renew/renewAll command line: it overrides
  # the value saved at issue time (and gets re-saved by issue() below), so certs
  # issued before the machine gained multiple addresses can still be renewed.
  # https://github.com/acmesh-official/acme.sh/issues/7009
  if [ "$_local_address" ]; then
    Le_LocalAddress="$_local_address"
  fi

  _clearAPI
  _clearCA
  export ACME_DIRECTORY="$Le_API"

  #reload ca configs
  _debug2 "initpath again."
  _initpath "$Le_Domain" "$_isEcc"

  # ARI (RFC 9773): fetch the CA's suggestedWindow on every renewal check.
  # If the window has started, renew now even if Le_NextRenewTime is in the future.
  # Set NO_ARI=1 (env, account.conf, or ca.conf) to opt out and use only
  # Le_NextRenewTime for the renewal decision.
  if [ "$NO_ARI" = "1" ]; then
    _debug "NO_ARI=1, skipping ARI suggestedWindow check"
  elif [ -z "$FORCE" ] && [ -f "$CERT_PATH" ]; then
    if _initAPI && [ "$ACME_RENEWAL_INFO" ]; then
      _ari_resp="$(_get_ARI "$CERT_PATH")"
      _debug2 "_ari_resp" "$_ari_resp"
      _ari_start="$(echo "$_ari_resp" | _egrep_o '"start" *: *"[^"]*' | sed 's/.*"//')"
      _ari_end="$(echo "$_ari_resp" | _egrep_o '"end" *: *"[^"]*' | sed 's/.*"//')"
      _debug "ARI suggestedWindow.start" "$_ari_start"
      _debug "ARI suggestedWindow.end" "$_ari_end"
      if [ "$_ari_start" ] && [ "$_ari_end" ]; then
        _ari_start_t="$(_date2time "$(echo "$_ari_start" | sed 's/\.[0-9]*//')")"
        _ari_end_t="$(_date2time "$(echo "$_ari_end" | sed 's/\.[0-9]*//')")"
        _ari_explanation_url="$(echo "$_ari_resp" | _egrep_o '"explanationURL" *: *"[^"]*' | sed 's/.*"//')"
        _debug "_ari_start_t" "$_ari_start_t"
        _debug "_ari_end_t" "$_ari_end_t"
        _debug "_ari_explanation_url" "$_ari_explanation_url"
        _debug "Le_NextRenewTime" "$Le_NextRenewTime"
        # Update ARI if needed
        if [ "$_ari_start_t" ] && [ "$_ari_end_t" ] && [ "$Le_NextRenewTime" ] && [ "$_ari_end_t" -gt "$_ari_start_t" ] && ([ "$Le_NextRenewTime" -lt "$_ari_start_t" ] || [ "$Le_NextRenewTime" -gt "$_ari_end_t" ]); then
          _ari_old_time_str="$Le_NextRenewTimeStr"
          _info "Current renewal time: $(__green "$_ari_old_time_str")"
          _ari_window=$(_math "$_ari_end_t" - "$_ari_start_t")
          _ari_offset=$(_math "$(_time)" % "$_ari_window")
          Le_NextRenewTime=$(_math "$_ari_start_t" + "$_ari_offset")
          Le_NextRenewTimeStr=$(_time2str "$Le_NextRenewTime")
          _info "ARI suggestedWindow: $(__green "$_ari_start") to $(__green "$_ari_end")"
          _info "Updating renewal time picked from ARI window: $(__green "$Le_NextRenewTimeStr")"
          _savedomainconf Le_NextRenewTime "$Le_NextRenewTime"
          _savedomainconf Le_NextRenewTimeStr "$Le_NextRenewTimeStr"
        fi
        if [ "$Le_NextRenewTime" ] && [ "$(_time)" -ge "$Le_NextRenewTime" ]; then
          _info "ARI suggested renewal has passed ($(__green "$Le_NextRenewTimeStr")), proceeding with renewal."
          if [ "$_ari_explanation_url" ]; then
            _info "For more information on this renewal: $(__green "$_ari_explanation_url")"
          fi
        fi
      fi
    fi
  fi

  if [ -z "$FORCE" ] && [ "$Le_NextRenewTime" ] && [ "$(_time)" -lt "$Le_NextRenewTime" ]; then
    _renew_retry_fixed=""
    res="0"
    _ensure_install "$Le_Domain"
    res="$?"
    if [ "$Le_DeployHook" ] && [ "$res" = "0" ]; then
      _ensure_deploy "$Le_Domain"
      res="$?"
    fi
    if [ "$res" != "0" ]; then
      if [ -z "$_ACME_IN_RENEWALL" ]; then
        if [ $_set_level -ge $NOTIFY_LEVEL_ERROR ]; then
          _send_notify "Renew $Le_Domain error" "There is an error." "$NOTIFY_HOOK" 1
        fi
      fi
      return 1
    fi
    if [ "$_renew_retry_fixed" ]; then
      _info "Install/deploy retry succeeded, no renewal is needed."
      if [ -z "$_ACME_IN_RENEWALL" ]; then
        if [ $_set_level -ge $NOTIFY_LEVEL_RENEW ]; then
          _send_notify "Renew $Le_Domain success" "Good, the cert install/deploy retry succeeded." "$NOTIFY_HOOK" 0
        fi
      fi
      return 0
    fi
    _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)"
  Le_Certificate_Profile="$(_readdomainconf Le_Certificate_Profile)"
  Le_Valid_From="$(_readdomainconf Le_Valid_From)"
  Le_Valid_To="$(_readdomainconf Le_Valid_To)"
  Le_ExtKeyUse="$(_readdomainconf Le_ExtKeyUse)"

  # When renewing from an old version, the empty Le_Keylength means 2048.
  # Note, do not use DEFAULT_DOMAIN_KEY_LENGTH as that value may change over
  # time but an empty value implies 2048 specifically.
  Le_Keylength="$(_readdomainconf Le_Keylength)"
  if [ -z "$Le_Keylength" ]; then
    Le_Keylength=2048
  fi
  if [ "$CA_LETSENCRYPT_V2" = "$Le_API" ]; then
    #letsencrypt doesn't support ocsp anymore
    if [ "$Le_OCSP_Staple" ]; then
      export Le_OCSP_Staple=""
      _cleardomainconf Le_OCSP_Staple
    fi
  fi
  issue "$Le_Webroot" "$Le_Domain" "$Le_Alt" "$Le_Keylength" "$Le_RealCertPath" "$Le_RealKeyPath" "$Le_RealCACertPath" "$Le_ReloadCmd" "$Le_RealFullChainPath" "$Le_PreHook" "$Le_PostHook" "$Le_RenewHook" "$Le_LocalAddress" "$Le_ChallengeAlias" "$Le_Preferred_Chain" "$Le_Valid_From" "$Le_Valid_To" "$Le_Certificate_Profile" "$Le_ExtKeyUse"
  res="$?"

  if [ "$Le_DeployHook" ] && [ "$res" = "0" ]; 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
  if ! [ -d "$CERT_HOME" ]; then
    _err "$CERT_HOME is not a directory, please check your configuration."
    return 1
  fi
  for di in "${CERT_HOME}"/*.* "${CERT_HOME}"/*:*; do
    _debug di "$di"
    if ! [ -d "$di" ]; then
      _debug "Not a directory, skipping: $di"
      continue
    fi
    d=$(basename "$di")
    _debug d "$d"
    _d_ari="$di.ari"
    _debug _d_ari "$_d_ari"
    (
      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="$?"
      if [ "$rc" = "0" ] && [ "$_ari_explanation_url" ]; then
        echo "$_ari_explanation_url" >"$_d_ari"
      fi
      return $rc
    )
    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
      _renewal_explanation=""
      if [ -f "$_d_ari" ]; then
        _renewal_explanation=" ($(cat "$_d_ari"))"
        rm -f "$_d_ari"
      fi

      _success_msg="${_success_msg}    $d$_renewal_explanation
"
    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

  if [ "$_TREAT_SKIP_AS_SUCCESS" ] && [ "$_ret" = "$RENEW_SKIP" ]; then
    _ret=0
  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}"
  _valid_f="${14}"
  _valid_t="${15}"
  _cert_prof="${16}"
  _en_key_usage="${17}"

  _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" "$_valid_f" "$_valid_t" "$_cert_prof" "$_en_key_usage"

}

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}Profile${_sep}CA${_sep}Created${_sep}Renew"
    fi
    for di in "${CERT_HOME}"/*.* "${CERT_HOME}"/*:*; do
      [ -d "$di" ] || continue
      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}$Le_Certificate_Profile${_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

}

list_profiles() {
  _initpath
  _initAPI

  _l_server_url="$ACME_DIRECTORY"
  _l_server_name="$(_getCAShortName "$_l_server_url")"
  _info "Fetching profiles from $_l_server_name ($_l_server_url)..."

  response=$(_get "$_l_server_url" "" 10)
  if [ "$?" != "0" ]; then
    _err "Failed to connect to CA directory: $_l_server_url"
    return 1
  fi

  normalized_response=$(echo "$response" | _normalizeJson)
  profiles_json=$(echo "$normalized_response" | _egrep_o '"profiles" *: *[{][^}]*[}]')

  if [ -z "$profiles_json" ]; then
    _info "The CA '$_l_server_name' does not publish certificate profiles via its directory endpoint."
    return 0
  fi

  # Strip the outer layer to get the key-value pairs
  profiles_kv=$(echo "$profiles_json" | sed 's/"profiles" *: *{//' | sed 's/}$//' | tr ',' '\n')

  printf "\n%-15s %s\n" "name" "info"
  printf -- "--------------------------------------------------------------------\n"

  _old_IFS="$IFS"
  IFS='
'
  for pair in $profiles_kv; do
    # Trim quotes and whitespace
    _name=$(echo "$pair" | cut -d: -f1 | tr -d '" \t')
    _info_url=$(echo "$pair" | cut -d: -f2- | sed 's/^ *//' | tr -d '"')
    printf "%-15s %s\n" "$_name" "$_info_url"
  done
  IFS="$_old_IFS"

  return 0
}

_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" "$CERT_PFX_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

  _deploy_success_time="$(_time)"
  _savedomainconf "Le_DeploySuccessTime" "$_deploy_success_time"
  _savedomainconf "Le_DeploySuccessTimeStr" "$(_time2str "$_deploy_success_time")"
}

_ensure_deploy() {
  _d="$1"
  if [ -z "$Le_DeployHook" ]; then
    return 0
  fi
  if [ -z "$Le_CertCreateTime" ]; then
    return 0
  fi

  _deploy_success_time="$(_readdomainconf Le_DeploySuccessTime)"
  if [ -z "$_deploy_success_time" ]; then
    _debug "Le_DeploySuccessTime is empty, skip deploy retry check."
    return 0
  fi
  case "$_deploy_success_time$Le_CertCreateTime" in
  *[!0-9]*)
    _debug "Le_DeploySuccessTime or Le_CertCreateTime is not a number, skip deploy retry check."
    return 0
    ;;
  esac

  if [ "$_deploy_success_time" -lt "$Le_CertCreateTime" ]; then
    _info "The cert was created after the last successful deploy, retrying deploy hooks."
    if _deploy "$_d" "$Le_DeployHook"; then
      _info "Deploy retry succeeded."
      _renew_retry_fixed=1
      return 0
    fi
    _err "Deploy retry failed."
    return 1
  fi

  return 0
}

#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"
  # The cert dir may exist without a domain conf (e.g. the conf was deleted, or
  # the cert was placed here manually). Deploy can still proceed using env-provided
  # settings, and _savedomainconf below will recreate the conf, so only source it
  # when present instead of failing on a missing file.
  if [ -f "$DOMAIN_CONF" ]; then
    . "$DOMAIN_CONF"
  fi

  _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: $_main_domain"
      return 1
    fi
  fi

  _installcert_success_time="$(_time)"
  _savedomainconf "Le_InstallCertSuccessTime" "$_installcert_success_time"
  _savedomainconf "Le_InstallCertSuccessTimeStr" "$(_time2str "$_installcert_success_time")"
}

_ensure_install() {
  _d="$1"
  if [ -z "$Le_CertCreateTime" ]; then
    return 0
  fi

  _real_cert="$(_readdomainconf Le_RealCertPath)"
  _real_key="$(_readdomainconf Le_RealKeyPath)"
  _real_ca="$(_readdomainconf Le_RealCACertPath)"
  _reload_cmd="$(_readdomainconf Le_ReloadCmd)"
  _real_fullchain="$(_readdomainconf Le_RealFullChainPath)"
  if [ -z "$_real_cert$_real_key$_real_ca$_reload_cmd$_real_fullchain" ]; then
    return 0
  fi

  _installcert_success_time="$(_readdomainconf Le_InstallCertSuccessTime)"
  if [ -z "$_installcert_success_time" ]; then
    _debug "Le_InstallCertSuccessTime is empty, skip install retry check."
    return 0
  fi
  case "$_installcert_success_time$Le_CertCreateTime" in
  *[!0-9]*)
    _debug "Le_InstallCertSuccessTime or Le_CertCreateTime is not a number, skip install retry check."
    return 0
    ;;
  esac

  if [ "$_installcert_success_time" -lt "$Le_CertCreateTime" ]; then
    _info "The cert was created after the last successful install, retrying install cert."
    if _installcert "$_d" "$_real_cert" "$_real_key" "$_real_ca" "$_real_fullchain" "$_reload_cmd"; then
      _info "Install cert retry succeeded."
      _renew_retry_fixed=1
      return 0
    fi
    _err "Install cert retry failed."
    return 1
  fi

  return 0
}

__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"
  _randomhour="$4"
  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 /ST requires the HH:mm format, so the minute must be zero-padded (issue 4950)
  _st_minute="$(printf "%02d" "$_randomminute")"
  #SCHTASKS.exe '/create' '/SC' 'DAILY' '/TN' "$_WINDOWS_SCHEDULER_NAME" '/F' '/ST' "00:$_st_minute" '/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:$_st_minute" '/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 "Using the current script from: $_script"
      lesh="$_script"
    else
      _err "Cannot install cronjob, $PROJECT_ENTRY not found."
      return 1
    fi
  fi
  if [ "$_c_home" ]; then
    _c_entry="--config-home \"$_c_home\" "
  fi
  _t=$(_time)
  random_minute=$(_math $_t % 60)
  random_hour=$(_math $_t / 60 % 6)

  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" "$random_hour"; 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"
  _cron_entry="$random_minute $random_hour,$(_math "$random_hour" + 6),$(_math "$random_hour" + 12),$(_math "$random_hour" + 18) * * * $lesh --cron --home \"$LE_WORKING_DIR\" $_c_entry> /dev/null"
  _cron_entries="$($_CRONTAB -l 2>/dev/null)"
  if [ "$?" != "0" ]; then
    #when the user has no crontab yet, crontab -l also exits non-zero;
    #only that case may proceed with an empty list. Any other listing
    #failure must abort: piping an incomplete list back into 'crontab -'
    #would wipe the user's existing cron jobs (issue 3079)
    _cron_list_err="$($_CRONTAB -l 2>&1 >/dev/null)"
    #separate greps: BRE alternation \| is a GNU extension and Solaris
    #grep takes only a single -e pattern
    if echo "$_cron_list_err" | grep -i "no crontab" >/dev/null ||
      echo "$_cron_list_err" | grep -i "no fcrontab" >/dev/null ||
      echo "$_cron_list_err" | grep -i "can't open" >/dev/null; then
      _cron_entries=""
    else
      _err "Can not list the current cron jobs: $_cron_list_err"
      _err "Refusing to install the cron job, that could wipe your existing cron jobs."
      _err "Please add this cron job manually:"
      _err "$_cron_entry"
      return 1
    fi
  fi
  if ! echo "$_cron_entries" | grep "$PROJECT_ENTRY --cron"; then
    if _exists uname && uname -a | grep SunOS >/dev/null; then
      _CRONTAB_STDIN="$_CRONTAB --"
    else
      _CRONTAB_STDIN="$_CRONTAB -"
    fi
    {
      if [ "$_cron_entries" ]; then
        echo "$_cron_entries"
      fi
      echo "$_cron_entry"
    } | $_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" | _json_decode | _authorizations_from_order)"
  _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")"
    # Fix for empty error objects in response which mess up the original code, adapted from fix suggested here: https://github.com/acmesh-official/acme.sh/issues/4933#issuecomment-1870499018
    entry="$(echo "$response" | sed s/'"error":{}'/'"error":null'/ | _egrep_o '[^{]*"type":"'$vtype'"[^}]*')"
    _debug entry "$entry"
    if [ -z "$entry" ]; then
      _err "$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
}

#cert
_getAKI() {
  _cert="$1"
  ${ACME_OPENSSL_BIN:-openssl} x509 -in "$_cert" -text -noout | grep -A 1 "X509v3 Authority Key Identifier" | _tail_n 1 | tr -d ': ' | sed "s/keyid//"
}

#cert
_getSerial() {
  _cert="$1"
  ${ACME_OPENSSL_BIN:-openssl} x509 -in "$_cert" -serial -noout | cut -d = -f 2
}

#cert
#Compute the ARI/replaces certID for a cert: base64url(AKI).base64url(Serial)
#per RFC 9773 Section 4.1.
_getARICertID() {
  _cert="$1"
  _aki=$(_getAKI "$_cert")
  _ser=$(_getSerial "$_cert")
  _debug2 "_aki" "$_aki"
  _debug2 "_ser" "$_ser"

  # RFC 9773 Section 4.1 requires the DER-encoded INTEGER value bytes of
  # serialNumber. When the high bit of the first byte is set (>= 0x80) DER
  # prepends a 0x00 sign byte to keep the integer positive; openssl's hex
  # output strips that, so add it back. Boulder (LE) accepts either form,
  # but Sectigo (ZeroSSL) is strict and rejects newOrder with HTTP 401
  # "replaces field does not identify a certificate" if the byte is missing.
  case "$_ser" in
  [89aAbBcCdDeEfF]*) _ser="00$_ser" ;;
  esac

  _akiurl="$(echo "$_aki" | _h2b | _base64 | _url_replace)"
  _debug2 "_akiurl" "$_akiurl"
  _serurl="$(echo "$_ser" | _h2b | _base64 | _url_replace)"
  _debug2 "_serurl" "$_serurl"

  printf "%s.%s" "$_akiurl" "$_serurl"
}

#cert
_get_ARI() {
  _cert="$1"
  _ari_certID="$(_getARICertID "$_cert")"
  _ARI_URL="$ACME_RENEWAL_INFO/$_ari_certID"
  _get "$_ARI_URL"
}

# 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"
    chmod 600 "$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" && ! _exists "python" && ! _exists "python2" && ! _exists "python3"; then
    _info "It is recommended to install socat or python first."
    _info "We use socat or python for the standalone server, which is used for standalone mode."
    _info "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\""
  if [ -f "$LE_WORKING_DIR/$PROJECT_ENTRY.completion" ]; then
    #the completion file does nothing when sourced by a non-bash shell
    _setopt "$_envfile" ". \"$LE_WORKING_DIR/$PROJECT_ENTRY.completion\""
  fi

  _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 [ -f "$PROJECT_ENTRY.completion" ]; then
    cp "$PROJECT_ENTRY.completion" "$LE_WORKING_DIR/"
    _debug "Installed bash completion to $LE_WORKING_DIR/$PROJECT_ENTRY.completion"
  fi

  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"
    # Create the custom cert home now instead of on first issuance, so the
    # user can see --install honored it.
    # https://github.com/acmesh-official/acme.sh/issues/4756
    if [ ! -d "$CERT_HOME" ]; then
      mkdir -p "$CERT_HOME"
    fi
  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"
  rm -f "$LE_WORKING_DIR/$PROJECT_ENTRY.completion"
  _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
  _TREAT_SKIP_AS_SUCCESS="1"
  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="$(uname -n)"
  fi

  _nsubject="$_nsubject by $_nsource"

  _send_err=0
  for _n_hook in $(echo "$_nhooks" | tr ',' " "); do
    _n_hook_file="$(_findHook "" $_SUB_FOLDER_NOTIFY "$_n_hook")"
    _info "Sending via: $_n_hook"
    _debug "Found $_n_hook_file for $_n_hook"
    if [ -z "$_n_hook_file" ]; then
      _err "Cannot find the hook file for $_n_hook"
      continue
    fi
    if ! (
      # The dns/deploy hooks export _H1.._H5 in the main process, so the
      # values are inherited here. Clear them: a stale Authorization header
      # from another service must not leak into the notify request.
      export _H1=""
      export _H2=""
      export _H3=""
      export _H4=""
      export _H5=""
      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.
  --update-account-key     Rotate account key.
  --register-account       Register account key.
  --deactivate-account     Deactivate the account.
  --make-dns-persist-value Print the DNS TXT record(s) to enable persistent DNS validation
                           (draft-ietf-acme-dns-persist-01). Use with -d <domain>.
  --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

  --cert-profile, --certificate-profile <profile>  If the CA offers profiles, select the desired profile
                                      See: $_PROFILESELECTION_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

  --dns-persist                     Use dns-persist-01 validation (draft-ietf-acme-dns-persist-01).
                                      Requires the persistent _validation-persist TXT record to already
                                      exist. Use '--make-dns-persist-value' to print the value to add.

  --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 (default), ec-384, ec-521.
  -ak, --accountkeylength <bits>    Specifies the account key length: 2048, 3072, 4096, 8192 or ec-256 (default), ec-384, ec-521.
  --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.

  --dns-persist-wildcard            Used with '--make-dns-persist-value'. Adds 'policy=wildcard' to the
                                      generated TXT record so the issuer is also authorized for wildcards
                                      and subdomains (draft-ietf-acme-dns-persist-01).
  --dns-persist-ca-name <name>      Used with '--make-dns-persist-value'. Use the given CA identity domain
                                      (e.g. 'ssl.com') as the issuer-domain-name in the TXT record. If
                                      omitted, the identities are read from the ACME directory's
                                      'caaIdentities' field and one record is printed per identity.
  --dns-persist-days <N>            Used with '--make-dns-persist-value'. Add a 'persistUntil' field to
                                      the TXT record so the record self-expires N days from now (the CA
                                      will refuse new validations against the record after that time).
                                      If omitted, the record has no expiry.


  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.
  --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.
                                      Multiple emails can be given as a comma-separated list: 'a@example.com,b@example.com'
  --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.
                                      A negative value renews that many days before the cert expiry.
                                      Negative values could be used to specify a number of days relative to the expiration date of the certificate.
  --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.
  --treat-skip-as-success           Only valid for '--renew-all' command. Treat skipped certs as success, return 0 instead of $RENEW_SKIP.
  --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.
  --request-v4                      Force client requests to use ipv4 to connect to the CA server.
  --request-v6                      Force client requests to use ipv6 to connect to the CA server.
  --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 the exported pfx or pkcs8 file. Use with '--to-pkcs12' or '--to-pkcs8'.


"
}

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
    ./$PROJECT_ENTRY --install "$@"
    _install_rc="$?"
    if [ "$_install_rc" = "0" ]; then
      _info "Install success!"
    fi

    cd ..

    rm -rf "$PROJECT_NAME-$_branch"
    rm -f "$localname"
    # Propagate the install result so a failed upgrade is not reported as
    # success. https://github.com/acmesh-official/acme.sh/issues/6477
    exit "$_install_rc"
  )
}

_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

  if [ "$_request_v6" ]; then
    _saveaccountconf "ACME_USE_IPV6_REQUESTS" "$_request_v6"
    _clearaccountconf "ACME_USE_IPV4_REQUESTS"
    ACME_USE_IPV4_REQUESTS=
  elif [ "$_request_v4" ]; then
    _saveaccountconf "ACME_USE_IPV4_REQUESTS" "$_request_v4"
    _clearaccountconf "ACME_USE_IPV6_REQUESTS"
    ACME_USE_IPV6_REQUESTS=
  elif [ "$ACME_USE_IPV6_REQUESTS" ]; then
    _saveaccountconf "ACME_USE_IPV6_REQUESTS" "$ACME_USE_IPV6_REQUESTS"
    _clearaccountconf "ACME_USE_IPV4_REQUESTS"
    ACME_USE_IPV4_REQUESTS=
  elif [ "$ACME_USE_IPV4_REQUESTS" ]; then
    _saveaccountconf "ACME_USE_IPV4_REQUESTS" "$ACME_USE_IPV4_REQUESTS"
    _clearaccountconf "ACME_USE_IPV6_REQUESTS"
    ACME_USE_IPV6_REQUESTS=
  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
      #The SUDO_* env vars are often inherited into shells that were not
      #started as `sudo acme.sh` at all (e.g. `sudo su - user`, or
      #`sudo pct enter <VID>` on Proxmox, which copies them into the
      #container). Only warn when sudo was used to run acme.sh itself;
      #anything else means the sudo happened further up and is fine.
      #https://github.com/acmesh-official/acme.sh/issues/6400
      if _contains "$SUDO_COMMAND" "$PROJECT_ENTRY"; then
        return 1
      fi
      return 0
    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=""
  _request_v4=""
  _request_v6=""
  _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=""
  _certificate_profile=""
  _extended_key_usage=""
  _dns_persist_wildcard=""
  _dns_persist_ca_name=""
  _dns_persist_days=""
  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"
      ;;
    --update-account-key | --updateaccountkey)
      _CMD="updateaccountkey"
      ;;
    --register-account | --registeraccount)
      _CMD="registeraccount"
      ;;
    --deactivate-account)
      _CMD="deactivateaccount"
      ;;
    --make-dns-persist-value | --makednspersistvalue)
      _CMD="makednspersistvalue"
      ;;
    --dns-persist-wildcard | --dnspersistwildcard)
      _dns_persist_wildcard="1"
      ;;
    --dns-persist-ca-name | --dnspersistcaname)
      _dns_persist_ca_name="$2"
      shift
      ;;
    --dns-persist-days | --dnspersistdays)
      _dns_persist_days="$2"
      shift
      ;;
    --set-notify)
      _CMD="setnotify"
      ;;
    --set-default-ca)
      _CMD="setdefaultca"
      ;;
    --set-default-chain)
      _CMD="setdefaultchain"
      ;;
    --list-profiles)
      _CMD="list_profiles"
      ;;
    -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"
      ;;
    --treat-skip-as-success | --treatskipassuccess)
      _TREAT_SKIP_AS_SUCCESS="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
      ;;
    --dns-persist)
      wvalue="$W_DNS_PERSIST"
      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
      ;;
    --certificate-profile | --cert-profile)
      _certificate_profile="$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)
      _extended_key_usage="$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"
      ;;
    --request-v4)
      _request_v4="1"
      ACME_USE_IPV4_REQUESTS="1"
      _request_v6=""
      ACME_USE_IPV6_REQUESTS=""
      ;;
    --request-v6)
      _request_v6="1"
      ACME_USE_IPV6_REQUESTS="1"
      _request_v4=""
      ACME_USE_IPV4_REQUESTS=""
      ;;
    --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"

  # --valid-to pins the cert lifetime, so a creation-anchored (positive)
  # --days schedule can not apply and is rejected. A negative --days is
  # anchored to the expiry and composes with a relative --valid-to: the
  # cert renews that many days before the expiry.
  if [ "$_days" ] && [ "$_valid_to" ]; then
    if ! _startswith "$_valid_to" "+"; then
      _err "--days can not be used together with a fixed-date --valid-to: such a cert can not be renewed automatically."
      return 1
    fi
    if ! _startswith "$_days" "-"; then
      _err "A positive --days can not be used together with --valid-to, the renewal time is derived from the expiry time."
      _err "Use a negative --days to renew that many days before the expiry, or omit --days to renew 1 day before the expiry."
      return 1
    fi
  fi

  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" "$_certificate_profile" "$_extended_key_usage"
    ;;
  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" "$_valid_from" "$_valid_to" "$_certificate_profile" "$_extended_key_usage"
    ;;
  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
    ;;
  updateaccountkey)
    updateaccountkey "$_accountkeylength"
    ;;
  deactivateaccount)
    deactivateaccount
    ;;
  makednspersistvalue)
    makednspersistvalue "$_domain" "$_dns_persist_wildcard" "$_dns_persist_ca_name" "$_dns_persist_days"
    ;;
  list)
    list "$_listraw" "$_domain"
    ;;
  info)
    info "$_domain" "$_ecc"
    ;;
  installcronjob) installcronjob "$_confighome" ;;
  uninstallcronjob) uninstallcronjob ;;
  cron) cron ;;
  toPkcs)
    toPkcs "$_domain" "$_password" "$_ecc"
    ;;
  toPkcs8)
    toPkcs8 "$_domain" "$_password" "$_ecc"
    ;;
  createAccountKey)
    createAccountKey "$_accountkeylength"
    ;;
  createDomainKey)
    createDomainKey "$_domain" "$_keylength"
    ;;
  createCSR)
    createCSR "$_domain" "$_altdomains" "$_ecc" "$_extended_key_usage"
    ;;
  setnotify)
    setnotify "$_notify_hook" "$_notify_level" "$_notify_mode" "$_notify_source"
    ;;
  setdefaultca)
    setdefaultca
    ;;
  setdefaultchain)
    setdefaultchain "$_preferred_chain"
    ;;
  list_profiles)
    list_profiles
    ;;
  *)
    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 "$@"
