File: //usr/local/directadmin/scripts/letsencrypt.sh
#!/bin/bash
export EXEC_PROPAGATION_TIMEOUT=300
export EXEC_POLLING_INTERVAL=30
# Use Google DNS for external lookups
DNS_SERVER="8.8.8.8"
DNS6_SERVER="2001:4860:4860::8888"
# Fallback DNS server
DA_IPV6=false
LEGO_DATA_PATH=/usr/local/directadmin/data/.lego
WELLKNOWN_PATH=/var/www/html/.well-known/acme-challenge
SERVER_CERT_DNSPROVIDER_ENV=/usr/local/directadmin/conf/ca.dnsprovider
DNS_SERVERS=( "8.8.8.8" "1.1.1.1" "2001:4860:4860::8888" "2606:4700:4700::1111")
if [ "$(id -u)" != "0" ]; then
echo "this script can only run as root";
exit 1
fi
if [ ! -x /usr/local/bin/lego ]; then
echo "missing 'lego' command, it can be installed using CustomBuild with command:"
echo " da build install lego"
exit 1
fi
fallbackedDig(){
lastret=1
for i in "${DNS_SERVERS[@]}";do
resp=$(dig "@${i}" "$@")
lastret=$?
if [ "${lastret}" -eq "0" ];then
echo "${resp}"
return 0
fi
if [ "${lastret}" -ne "9" ];then
return "${lastret}"
fi
DNS_SERVERS=("${DNS_SERVERS[@]:1}" "$i")
done
return ${lastret}
}
caa_check() {
CAA_OK=true
for i in $(echo "$1" | awk -F'.' '{b=$NF;for(i=NF-1;i>0;i--){b=$i FS b;print b}}'); do
if fallbackedDig CAA "${i}" +short | grep -m1 -q -F -- "issue"; then
CAA_OK=false
if fallbackedDig CAA "${i}" +short | grep -m1 -q -F -- "letsencrypt.org"; then
CAA_OK=true
else
CAA_CURRENT=$(fallbackedDig CAA "${i}" +short | grep -m1 issue | awk '{print $3}')
fi
fi
if fallbackedDig CAA "${i}" | grep -m1 -q -F -- "SERVFAIL"; then
CAA_OK=false
CAA_CURRENT="SERVFAIL"
fi
done
if ! ${CAA_OK}; then
echo "CAA record prevents issuing the certificate: ${CAA_CURRENT}"
exit 1
fi
}
challenge_check() {
TEMP_FILENAME="letsencrypt_$(openssl rand -hex 16)"
touch "${WELLKNOWN_PATH}/${TEMP_FILENAME}"
chmod 644 "${WELLKNOWN_PATH}/${TEMP_FILENAME}"
#if 8.8.8.8 is not accessible, dig returns code 9. The dig|grep method returns code 1, so have to redo.
IP_TO_RESOLV=$(fallbackedDig AAAA "$1" +short | grep -v '\.$' | tail -n1)
if ! echo "${IP_TO_RESOLV}" | grep -m1 -q ':'; then
IP_TO_RESOLV=""
fi
if [ -z "${IP_TO_RESOLV}" ]; then
IP_TO_RESOLV=$(fallbackedDig "$1" +short | tail -n1)
fi
if [ -z "${IP_TO_RESOLV}" ]; then
rm -f "${WELLKNOWN_PATH}/${TEMP_FILENAME}"
return 1
fi
if command -v ping6 > /dev/null; then
if ! ${DA_IPV6}; then
if ! ping6 -q -c 1 -W 1 "$1" >/dev/null 2>&1; then
IP_TO_RESOLV=$(fallbackedDig "$1" +short | tail -n1)
fi
fi
fi
local CURL_OPTIONS=("--connect-timeout" "40" "-k" "--silent")
if [ -n "${IP_TO_RESOLV}" ]; then
CURL_OPTIONS+=("--resolve" "${1}:80:${IP_TO_RESOLV}" "--resolve" "${1}:443:${IP_TO_RESOLV}")
fi
if ! curl "${CURL_OPTIONS[@]}" -I -L -X GET "http://${1}/.well-known/acme-challenge/${TEMP_FILENAME}" 2>/dev/null | grep -m1 -q 'HTTP.*200'; then
if [ "$2" = "silent" ]; then
rm -f "${WELLKNOWN_PATH}/${TEMP_FILENAME}"
return 1
else
echo "Challenge pre-checks for http://${1}/.well-known/acme-challenge/${TEMP_FILENAME} failed... Command:"
echo "curl ${CURL_OPTIONS[*]} -I -L -X GET http://${1}/.well-known/acme-challenge/${TEMP_FILENAME}"
echo "Exiting."
rm -f "${WELLKNOWN_PATH}/${TEMP_FILENAME}"
exit 1
fi
elif [ "$2" = "silent" ]; then
rm -f "${WELLKNOWN_PATH}/${TEMP_FILENAME}"
return 0
fi
if [ -e "${WELLKNOWN_PATH}/${TEMP_FILENAME}" ]; then
rm -f "${WELLKNOWN_PATH}/${TEMP_FILENAME}"
fi
}
acme_provider_url() {
local provider=$1
case "${provider}" in
zerossl) echo "https://acme.zerossl.com/v2/DV90" ;;
letsencrypt-staging) echo "https://acme-staging-v02.api.letsencrypt.org/directory" ;;
letsencrypt) echo "https://acme-v02.api.letsencrypt.org/directory" ;;
*) echo "https://acme-v02.api.letsencrypt.org/directory" ;;
esac
}
lego_key_type() {
local key_type=$1
case "${key_type}" in
# Identity mappings
ec256|ec384|rsa2048|rsa4096|rsa8192) echo "${key_type}";;
# Old DA key-types
prime256v1) echo "ec256";;
secp384r1) echo "ec384";;
2048) echo "rsa2048";;
4096) echo "rsa4096";;
8192) echo "rsa8192";;
# Default
*) echo "ec256" ;;
esac
}
issue_lego_cert() {
local provider=$1 # letsencrypt
local key_type=$2 # ec256
local dnsprovider=$3 # empty means HTTP challenge
local domains=("${@:4}") # example.com *.example.com
local email
email=$(sed -n 's/^email=\([^,]*\).*$/\1/p' "/usr/local/directadmin/data/users/$(da admin)/user.conf" 2>/dev/null)
if [ -z "${email}" ]; then
email="admin@$(da config-get servername)"
fi
local args=(
--path "${LEGO_DATA_PATH}"
--dns.resolvers "${DNS_SERVER}"
--accept-tos
--server "$(acme_provider_url "${provider}")"
--email "${email}"
--key-type "$(lego_key_type "${key_type}")"
)
if [ -z "${dnsprovider}" ]; then
args+=(--http)
if has_webserver; then
args+=("--http.webroot" "/var/www/html")
fi
else
args+=(--dns "${dnsprovider}")
fi
for d in "${domains[@]}"; do
args+=(--domains "$d")
done
/usr/local/bin/lego "${args[@]}" run --no-bundle --preferred-chain="ISRG Root X1"
}
install_file() {
local file_mode=$1 # 640
local file_owner=$2 # diradmin:access
local src=$3 # /usr/local/directadmin/conf/cacert.pem.combined
local dst=$4 # /etc/exim.cert
local tmp_file
if ! tmp_file=$(mktemp --tmpdir="$(dirname "${dst}")" --suffix=".$(basename "${dst}")"); then
echo "${FUNCNAME[0]}: failed to create temp file for '${dst}'" 1>&2
return 1
fi
if ! cp -f "${src}" "${tmp_file}"; then
rm -f "${tmp_file}"
echo "${FUNCNAME[0]}: failed to copy '${src}' to '${tmp_file}'" 1>&2
return 1
fi
if ! chmod "${file_mode}" "${tmp_file}"; then
rm -f "${tmp_file}"
echo "${FUNCNAME[0]}: chmod '${tmp_file}' to '${file_mode}'" 1>&2
return 1
fi
if ! chown "${file_owner}" "${tmp_file}"; then
rm -f "${tmp_file}"
echo "${FUNCNAME[0]}: failed to chown '${tmp_file}' to '${file_owner}'" 1>&2
return 1
fi
if ! mv -f "${tmp_file}" "${dst}"; then
rm -f "${tmp_file}"
echo "${FUNCNAME[0]}: failed to rename '${tmp_file}' to '${dst}'" 1>&2
return 1
fi
}
install_lego_cert() {
local name=$1 # example.net
local dst_key=$2 # /usr/local/directadmin/data/users/{user}/domains/{name}.key
local dst_crt=$3 # /usr/local/directadmin/data/users/{user}/domains/{name}.cert
local dst_ca_crt=$4 # /usr/local/directadmin/data/users/{user}/domains/{name}.cacert
local src_key="${LEGO_DATA_PATH}/certificates/${name}.key"
local src_crt="${LEGO_DATA_PATH}/certificates/${name}.crt"
local src_ca_crt="${LEGO_DATA_PATH}/certificates/${name}.issuer.crt"
if [ ! -s "${src_key}" ]; then
echo "key file '${src_key}' is missing or empty" 1>&2
return 1
fi
if [ ! -s "${src_crt}" ]; then
echo "certificate file '${src_crt}' is missing or empty" 1>&2
return 1
fi
# lego older than 4.9 used to combine multiple certs in main cert file:
# https://github.com/go-acme/lego/issues/963
if [ "$(grep -c "BEGIN CERTIFICATE" "${src_crt}")" -gt 1 ]; then
local first_cert
first_cert=$(openssl x509 -in "${src_crt}") || return 1
cat > "${src_crt}" <<< "${first_cert}" || return 1
fi
local access_group
access_group=$(da config-get secure_access_group)
# FIXME there is race condition between moving key and cert usually this
# is not a problem for services that require reload to re-read certs.
local owner="diradmin:${access_group:-mail}"
install_file 640 "${owner}" "${src_key}" "${dst_key}" || return 1
install_file 640 "${owner}" "${src_crt}" "${dst_crt}" || return 1
install_file 640 "${owner}" "${src_ca_crt}" "${dst_ca_crt}" || return 1
install_file 640 "${owner}" <(cat "${src_crt}" "${src_ca_crt}") "${dst_crt}.combined" || return 1
install_file 640 "${owner}" <(date +%s) "${dst_crt}.creation_time" || return 1
}
command_revoke() {
local domain=$2
local user
local provider
local email
email=$(sed -n 's/^email=\([^,]*\).*$/\1/p' "/usr/local/directadmin/data/users/$(da admin)/user.conf" 2>/dev/null)
if [ -z "${email}" ]; then
email="admin@$(da config-get servername)"
fi
user=$(grep -m 1 "^${domain//./\\.}: " /etc/virtual/domainowners | cut -d ' ' -f 2)
local domain_conf_file="/usr/local/directadmin/data/users/${user}/domains/${domain}.conf"
local domain_ssl_file="/usr/local/directadmin/data/users/${user}/domains/${domain}.ssl"
if [ -n "${user}" ] && [ -s "${domain_conf_file}" ]; then
provider=$(grep -m1 ^acme_provider= "${domain_conf_file}" | cut -d= -f2)
elif [ -n "${user}" ] && [ -s "${domain_ssl_file}" ]; then
provider=$(grep -m1 ^acme_provider= "${domain_ssl_file}" | cut -d= -f2)
else
provider=$(da config-get default_acme_provider)
fi
/usr/local/bin/lego \
--path "${LEGO_DATA_PATH}" \
--server "$(acme_provider_url "${provider}")" \
--email "${email}" \
--domains "${domain}" \
revoke
}
has_wildcard_domain() {
local d
for d in "${@}"; do
if [ "${d}" != "${d/\*.}" ]; then
return 0
fi
done
return 1
}
has_webserver() {
# Result of first check is cached in global variable
if [ -z "${has_webserver_rc:-}" ]; then
if ss --no-header --listening --numeric --tcp 'sport = 80' | grep --quiet LISTEN; then
has_webserver_rc=0
else
has_webserver_rc=1
fi
fi
return "${has_webserver_rc}"
}
command_server_cert() {
local domain_csv=$1
local key_type=$2
da config-set acme_server_cert_enabled "1"
if [ -n "${domain_csv}" ]; then
ADDITIONAL_DOMAINS="$(tr , '\n' <<< "${domain_csv}" | grep -Fvx "$(da config-get servername)" | paste -sd,)"
da config-set acme_server_cert_additional_domains "${ADDITIONAL_DOMAINS}"
fi
if [ -n "${key_type}" ]; then
da config-set acme_server_cert_key_type "$(lego_key_type "${key_type}")"
fi
if [ -s "${SERVER_CERT_DNSPROVIDER_ENV}" ]; then
da config-set acme_server_cert_dns_provider_env_file "${SERVER_CERT_DNSPROVIDER_ENV}"
fi
if ! da taskq --run 'action=ssl&value=server_acme&force=true'; then
echo "Failed to issue new certificate"
exit 1
fi
echo "Server certificate with domains ${domain_csv} has been created successfully"
da config-set --restart ssl 1
}
command_do_everything() {
local action=$1 # request|renew|revoke
DOMAIN=$2
KEY_SIZE=$3
CSR_CF_FILE=$4
if [ "$(da config-get ipv6)" = "1" ]; then
if command -v ping6 > /dev/null; then
if ping6 -q -c 1 -W 1 ${DNS6_SERVER} >/dev/null 2>&1; then
DA_IPV6=true
DNS_SERVER=${DNS6_SERVER}
fi
fi
fi
CHALLENGETYPE="http"
DA_HOSTNAME=$(da config-get servername)
CHILD_DOMAIN=false
#We need the domain to match in /etc/virtual/domainowners, if we use grep -F, we cannot use any regex'es including ^
FOUNDDOMAIN=0
for TDOMAIN in $(echo "${DOMAIN}" | tr ',' ' '); do
if [ "${DA_HOSTNAME}" = "${TDOMAIN}" ]; then
#we're a hostname, skip this check
break
fi
DOMAIN_NAME_FOUND=${TDOMAIN}
DOMAIN_ESCAPED=${TDOMAIN//./\\.}
if grep -m1 -q "^${DOMAIN_ESCAPED}:" /etc/virtual/domainowners; then
USER=$(grep -m1 "^${DOMAIN_ESCAPED}:" /etc/virtual/domainowners | cut -d' ' -f2)
HOSTNAME=0
FOUNDDOMAIN=1
break
fi
done
if [ "${FOUNDDOMAIN}" = "0" ]; then
#check parent domain
for TDOMAIN in $(echo "${DOMAIN}" | tr ',' ' '); do
if [ "${DA_HOSTNAME}" = "${TDOMAIN}" ]; then
#we're a hostname, skip this check
break
fi
if [ "$(echo "${TDOMAIN}" | grep -o '\.' | wc -l)" -gt 1 ]; then
CHILD_NAME=$(echo "${TDOMAIN}" | cut -d'.' -f1)
PARENT_DOMAIN_NAME_FOUND=$(echo "${TDOMAIN}" | perl -p0 -e 's|^[^\.]*\.||g')
PARENT_DOMAIN_ESCAPED=${PARENT_DOMAIN_NAME_FOUND//./\\.}
PARENT_DOMAIN_OWNER_USER=$(grep -m1 "^${PARENT_DOMAIN_ESCAPED}:" /etc/virtual/domainowners | cut -d' ' -f2)
if [ -s "/usr/local/directadmin/data/users/${PARENT_DOMAIN_OWNER_USER}/domains/${PARENT_DOMAIN_NAME_FOUND}.subdomains" ] && grep -q "^${CHILD_NAME}$" "/usr/local/directadmin/data/users/${PARENT_DOMAIN_OWNER_USER}/domains/${PARENT_DOMAIN_NAME_FOUND}.subdomains"; then
DOMAIN_NAME_FOUND=${TDOMAIN}
DOMAIN_ESCAPED=${DOMAIN_NAME_FOUND//./\\.}
USER=${PARENT_DOMAIN_OWNER_USER}
HOSTNAME=0
FOUNDDOMAIN=1
CHILD_DOMAIN=true
break
fi
fi
done
fi
if [ "${FOUNDDOMAIN}" = "0" ]; then
LETSENCRYPT_LIST=$(da config-get letsencrypt_list | tr ':' ' ')
#check parent domain
for TDOMAIN in $(echo "${DOMAIN}" | tr ',' ' '); do
if [ "${DA_HOSTNAME}" = "${TDOMAIN}" ]; then
#we're a hostname, skip this check
break
fi
if [ "${FOUNDDOMAIN}" != "0" ]; then
break
fi
if [ "$(echo "${TDOMAIN}" | grep -o '\.' | wc -l)" -gt 1 ]; then
CHILD_NAME=$(echo "${TDOMAIN}" | cut -d'.' -f1)
PARENT_DOMAIN_NAME_FOUND=$(echo "${TDOMAIN}" | perl -p0 -e 's|^[^\.]*\.||g')
PARENT_DOMAIN_ESCAPED=${PARENT_DOMAIN_NAME_FOUND//./\\.}
PARENT_DOMAIN_OWNER_USER=$(grep -m1 "^${PARENT_DOMAIN_ESCAPED}:" /etc/virtual/domainowners | cut -d' ' -f2)
for letsencrypt_prefix in ${LETSENCRYPT_LIST}; do
if [ "${CHILD_NAME}" = "${letsencrypt_prefix}" ] && [ -n "${PARENT_DOMAIN_OWNER_USER}" ]; then
DOMAIN_NAME_FOUND=${TDOMAIN}
DOMAIN_ESCAPED=${DOMAIN_NAME_FOUND//./\\.}
USER=${PARENT_DOMAIN_OWNER_USER}
HOSTNAME=0
FOUNDDOMAIN=1
CHILD_DOMAIN=true
break
fi
done
fi
done
fi
if [ "${FOUNDDOMAIN}" = "0" ]; then
for TDOMAIN in $(echo "${DOMAIN}" | tr ',' ' '); do
DOMAIN_NAME_FOUND=${TDOMAIN}
DOMAIN_ESCAPED=${DOMAIN_NAME_FOUND//./\\.}
USER="root"
if [ "${DA_HOSTNAME}" = "${TDOMAIN}" ]; then
echo "Setting up certificate for a hostname: ${DOMAIN_NAME_FOUND}"
HOSTNAME=1
FOUNDDOMAIN=1
if ! grep -m1 -q "^${DOMAIN_ESCAPED}$" /etc/virtual/domains; then
echo "${DOMAIN_NAME_FOUND}" >> /etc/virtual/domains
fi
break
else
echo "Domain does not exist on the system. Unable to find ${DOMAIN_NAME_FOUND} in /etc/virtual/domainowners, and domain is not set as hostname (servername) in DirectAdmin configuration. Exiting..."
fi
done
fi
if [ ${FOUNDDOMAIN} -eq 0 ]; then
echo "no valid domain found - exiting"
exit 1
fi
DA_USERDIR="/usr/local/directadmin/data/users/${USER}"
DA_CONFDIR="/usr/local/directadmin/conf"
if [ ! -d "${DA_USERDIR}" ] && [ "${HOSTNAME}" -eq 0 ]; then
echo "${DA_USERDIR} not found, exiting..."
exit 1
elif [ ! -d "${DA_CONFDIR}" ] && [ "${HOSTNAME}" -eq 1 ]; then
echo "${DA_CONFDIR} not found, exiting..."
exit 1
fi
if [ "${HOSTNAME}" -eq 0 ]; then
DNSPROVIDER_FALLBACK="${DA_USERDIR}/domains/${DOMAIN_NAME_FOUND}.dnsprovider"
if [ -s "${DNSPROVIDER_FALLBACK}" ]; then
if grep -m1 -q "^dnsprovider=inherit-creator$" "${DNSPROVIDER_FALLBACK}"; then
CREATOR=$(grep -m1 '^creator=' "${DA_USERDIR}/user.conf" | cut -d= -f2)
CREATOR_DNSPROVIDER="/usr/local/directadmin/data/users/${CREATOR}/dnsprovider.conf"
if [ -s "${CREATOR_DNSPROVIDER}" ]; then
DNSPROVIDER_FALLBACK="${CREATOR_DNSPROVIDER}"
fi
elif grep -m1 -q "^dnsprovider=inherit-global$" "${DNSPROVIDER_FALLBACK}"; then
if [ -s "/usr/local/directadmin/data/admin/dnsprovider.conf" ]; then
DNSPROVIDER_FALLBACK="/usr/local/directadmin/data/admin/dnsprovider.conf"
fi
fi
fi
KEY="${DA_USERDIR}/domains/${DOMAIN_NAME_FOUND}.key"
CERT="${DA_USERDIR}/domains/${DOMAIN_NAME_FOUND}.cert"
CACERT="${DA_USERDIR}/domains/${DOMAIN_NAME_FOUND}.cacert"
else
DNSPROVIDER_FALLBACK="${DA_CONFDIR}/ca.dnsprovider"
KEY=$(da config-get cakey)
CERT=$(da config-get cacert)
CACERT=$(da config-get carootcert)
fi
if [ -s "${CERT}" ] && [ "${action}" = "renew" ]; then
if [ -s "${CERT}" ]; then
DOMAIN=$(openssl x509 -text -noout -in "${CERT}" | grep -m1 'Subject Alternative Name:' -A1 | grep 'DNS:' | perl -p0 -e 's|DNS:||g' | tr -d ' ')
fi
elif [ "${action}" = "request" ] && ! echo "${DOMAIN}" | grep -m1 -q ","; then
if [ -s "${CSR_CF_FILE}" ] && grep -m1 -q 'DNS:' "${CSR_CF_FILE}"; then
DOMAIN=$(grep '^subjectAltName=' "${CSR_CF_FILE}" | cut -d= -f2 | grep 'DNS:' | perl -p0 -e 's|DNS:||g' | tr -d ' ')
elif [ -s "${CERT}" ] && openssl x509 -text -noout -in "${CERT}" | grep -m1 -q 'Subject Alternative Name:' >/dev/null 2>&1; then
DOMAIN=$(openssl x509 -text -noout -in "${CERT}" | grep -m1 'Subject Alternative Name:' -A1 | grep 'DNS:' | perl -p0 -e 's|DNS:||g' | tr -d ' ')
elif [ "${HOSTNAME}" -eq 0 ] && ! ${CHILD_DOMAIN}; then
if ! echo "${DOMAIN}" | grep -q "^www\."; then
#We have a domain without www., add www domain to to SAN too
DOMAIN="${DOMAIN},www.${DOMAIN}"
else
#We have a domain with www., drop www and add it to SAN too
DOMAIN2=$(echo "${DOMAIN}" | perl -p0 -e 's#^www.##')
DOMAIN="${DOMAIN2},www.${DOMAIN2}"
fi
fi
fi
#Set validation method
CHALLENGETYPE=http
#empty env for dnsprovider - but dnsprovider file in use
if [ -s "${DNSPROVIDER_FALLBACK}" ] && [ -z "${dnsprovider}" ]; then
readarray -t args < <(grep -o '^[a-zA-Z0-9_]*=[^;<>|\ ]*' "${DNSPROVIDER_FALLBACK}")
export "${args[@]}"
fi
if [ "${HOSTNAME}" -ne 0 ]; then
dnsprovider="$(da config-get acme_server_cert_dns_provider)"
fi
if [ -n "${dnsprovider}" ] && [ "${dnsprovider}" != "exec" ]; then
echo "Found DNS provider configured: ${dnsprovider}"
DNSPROVIDER_NAME=${dnsprovider}
CHALLENGETYPE="dns"
elif echo "${DOMAIN}" | grep -m1 -q '\*\.'; then
echo "Found wildcard domain name and http challenge type, switching to dns-01 validation."
DNSPROVIDER_NAME="exec"
CHALLENGETYPE="dns"
export EXEC_PATH=/usr/local/directadmin/scripts/letsencrypt.sh
fi
if [ "${CHALLENGETYPE}" = "http" ]; then
RESOLVING_DOMAINS=""
for domain_name in $(echo "${DOMAIN}" | perl -p0 -e "s/,/ /g" | perl -p0 -e "s/^\*.//g"); do
if has_webserver && ! challenge_check "${domain_name}" silent; then
echo "${domain_name} was skipped due to unreachable http://${domain_name}/.well-known/acme-challenge/${TEMP_FILENAME} file."
else
if [ -z "${RESOLVING_DOMAINS}" ]; then
RESOLVING_DOMAINS="${domain_name}"
else
RESOLVING_DOMAINS="${RESOLVING_DOMAINS},${domain_name}"
fi
fi
done
if [ -z "${RESOLVING_DOMAINS}" ]; then
echo "No domains pointing to this server to generate the certificate for."
exit 1
fi
DOMAIN="${RESOLVING_DOMAINS}"
fi
#Run all domains through CAA and http pre-checks to save LE rate-limits
for domain_name in $(echo "${DOMAIN}" | perl -p0 -e "s/,/ /g" | perl -p0 -e "s/^\*.//g"); do
caa_check "${domain_name}"
if [ "${CHALLENGETYPE}" = "http" ] && has_webserver; then
challenge_check "${domain_name}"
fi
done
FIRST_DOMAIN=$(echo "${DOMAIN}" | cut -d, -f1)
IFS=',' read -ra DOMAIN_ARRAY <<< "$DOMAIN"
# extract the acme_provider=provider from domain configuration and determine the ACME url from that
ACME=''
domain_conf_file="${DA_USERDIR}/domains/${FIRST_DOMAIN}.conf"
domain_ssl_file="${DA_USERDIR}/domains/${FIRST_DOMAIN}.ssl"
if [ -s "${domain_conf_file}" ]; then
ACME=$(grep -m1 ^acme_provider= "${domain_conf_file}" | cut -d= -f2)
elif [ -s "${domain_ssl_file}" ]; then
ACME=$(grep -m1 ^acme_provider= "${domain_ssl_file}" | cut -d= -f2)
else
ACME=$(da config-get default_acme_provider)
fi
local challenge=""
if [ "${CHALLENGETYPE}" = "dns" ]; then
challenge=${DNSPROVIDER_NAME}
fi
if ! issue_lego_cert "${ACME}" "${KEY_SIZE}" "${challenge}" "${DOMAIN_ARRAY[@]}"; then
echo "Failed to issue new certificate"
exit 1
fi
if ! install_lego_cert "${FIRST_DOMAIN//\*/_}" "${KEY}" "${CERT}" "${CACERT}"; then
echo "Failed to install new certificate"
exit 1
fi
echo "Certificate for ${DOMAIN} has been created successfully!"
#Change exim, apache/nginx certs
if [ "${HOSTNAME}" -eq 1 ]; then
echo "DirectAdmin certificate has been setup."
if [ "$(da config-get ssl)" = "0" ]; then
da config-set ssl 1
fi
systemctl restart directadmin
da build sync_server_cert
fi
}
command_add_dns_challenge_record() {
local key=${1%%.} # Strip '.' suffix
local value=$2
local domain=${key##_acme-challenge.} # Strip '_acme-challenge.' prefix
if [ -z "${domain}" ] || [ "${key}" = "${domain}" ]; then
echo "refusing to create DNS challenge record '${key}', missing _acme-challenge prefix" 1>&2
exit 1
fi
# cleanup of the old record
# it's run in reverse because the list is sorted for duplicates. Must run the dataskq immediately before calling the add.
da taskq --run "action=dns&do=delete&domain=${domain}&type=TXT&name=_acme-challenge"
da taskq --run "action=dns&do=add&domain=${domain}&type=TXT&name=_acme-challenge&value=\"${value}\"&ttl=5&named_reload=yes"
exit 0
}
command_remove_dns_challenge_record() {
local key=${1%%.} # Strip '.' suffix
local domain=${key##_acme-challenge.} # Strip '_acme-challenge.' prefix
if [ -z "${domain}" ] || [ "${key}" = "${domain}" ]; then
echo "refusing to remove DNS challenge record '${key}', missing _acme-challenge prefix" 1>&2
exit 1
fi
da taskq --run "action=dns&do=delete&domain=${domain}&type=TXT&name=_acme-challenge"
exit 0
}
command_help() {
echo "Usage:"
echo " $0 request|renew <domain> <key-type> [<csr-config-file>]"
echo " $0 server_cert [<domain>] [<key-type>]"
echo " $0 revoke"
echo ""
echo "Got $# args:"
echo " $0 $1 $2 $3 $4"
echo ""
echo "Multiple comma separated domains, owned by the same user, can be used for a certificate request"
exit 0
}
case "$1" in
server_cert)
command_server_cert "${2}" "${3}"
;;
request|request_single|request_full)
command_do_everything "request" "${2}" "${3}" "${4}"
;;
renew)
command_do_everything "renew" "${2}" "${3}" "${4}"
;;
revoke)
command_revoke "revoke" "${2}"
;;
present)
# lego callback for DNS challenge
# ./letsencrypt.sh "present" "_acme-challenge.foo.example.com." "MsijOYZxqyjGnFGwhjrhfg-Xgbl5r68WPda0J9EgqqI"
command_add_dns_challenge_record "${2}" "${3}"
;;
cleanup)
# lego callback for DNS challenge
command_remove_dns_challenge_record "${2}"
;;
*)
command_help "$@" ;;
esac