#!/bin/sh #VERSION=2.0.30 # This script is written by Martynas Bendorius and DirectAdmin # It is used to create/renew let's encrypt certificate for a domain # Official DirectAdmin webpage: http://www.directadmin.com # Usage: # ./letsencrypt.sh MYUID=`/usr/bin/id -u` if [ "${MYUID}" != 0 ]; then echo "You require Root Access to run this script"; exit 0 fi export EXEC_PROPAGATION_TIMEOUT=300 export EXEC_POLLING_INTERVAL=30 LEGO=/usr/local/bin/lego # Use Google DNS for external lookups DNS_SERVER="8.8.8.8" DNS6_SERVER="2001:4860:4860::8888" # Fallback DNS server NEW_IP="1.1.1.1" NEW6_IP="2606:4700:4700::1111" #NEW_IP=`cat /etc/resolv.conf |grep ^nameserver | grep -v 127.0.0.1 | head -n1 | cut -d\ -f2` DA_IPV6=false TASK_QUEUE=/usr/local/directadmin/data/task.queue.cb LEGO_DATA_PATH=/usr/local/directadmin/data/.lego if [ $# -lt 2 ]; then echo "Usage:"; echo "$0 request|request_single|request_full|renew|revoke ()"; echo "you gave #$#: $0 $1 $2 $3"; echo "Multiple comma separated domains, owned by the same user, can be used for a certificate request" exit 0; elif [ $# -lt 3 ]; then #No key size specified, assign default one KEY_SIZE=ec256 ECC_USED=true ECC="secp384r1" fi KEY_SIZE=$3 #Set key size (flag) if [ "${KEY_SIZE}" = "secp384r1" ]; then ECC="secp384r1" KEY_SIZE="ec384" ECC_USED=true elif [ "${KEY_SIZE}" = "prime256v1" ]; then ECC="prime256v1" KEY_SIZE="ec256" ECC_USED=true elif [ "${KEY_SIZE}" = "4096" ]; then KEY_SIZE="rsa4096" ECC_USED=false elif [ "${KEY_SIZE}" = "2048" ]; then KEY_SIZE="rsa2048" ECC_USED=false elif [ "${KEY_SIZE}" = "8192" ]; then KEY_SIZE="rsa8192" ECC_USED=false else #set default to ec256 ECC="prime256v1" KEY_SIZE="ec256" ECC_USED=true fi DA_BIN="/usr/local/directadmin/directadmin" if [ ! -s ${DA_BIN} ]; then echo "Unable to find DirectAdmin binary /usr/local/directadmin/directadmin. Exiting..." exit 1 fi run_dataskq() { DATASKQ_OPT=$1 /usr/local/directadmin/dataskq ${DATASKQ_OPT} --custombuild } #we received present/cleanup as $1 from lego callback # ./letsencrypt.sh "present" "_acme-challenge.foo.example.com." "MsijOYZxqyjGnFGwhjrhfg-Xgbl5r68WPda0J9EgqqI" if [ "$1" = "present" ]; then #remove _acme-challenge from the beginning DOMAIN_TO_USE=`echo $2 | perl -p0 -e 's|^_acme-challenge\.||g' | perl -p0 -e 's|\.$||g'` # cleanup of the old record echo "action=dns&do=delete&domain=${DOMAIN_TO_USE}&type=TXT&name=_acme-challenge" >> ${TASK_QUEUE} # it's run in reverse because the list is sorted for duplicates. Must run the dataskq immediately before calling the add. run_dataskq echo "action=dns&do=add&domain=${DOMAIN_TO_USE}&type=TXT&name=_acme-challenge&value=\"${3}\"&ttl=5&named_reload=yes" >> ${TASK_QUEUE} run_dataskq exit 0 elif [ "$1" = "cleanup" ]; then DOMAIN_TO_USE=`echo $2 | perl -p0 -e 's|^_acme-challenge\.||g' | perl -p0 -e 's|\.$||g'` echo "action=dns&do=delete&domain=${DOMAIN_TO_USE}&type=TXT&name=_acme-challenge" >> ${TASK_QUEUE} # it's run in reverse because the list is sorted for duplicates. Must run the dataskq immediately before calling the add. run_dataskq exit 0 fi if ${DA_BIN} c | grep -m1 -q "^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} NEW_IP=${NEW6_IP} fi fi fi CURL=/usr/local/bin/curl if [ ! -x ${CURL} ]; then CURL=/usr/bin/curl fi DIG=/usr/bin/dig if [ ! -x ${DIG} ]; then if [ -x /usr/local/bin/dig ]; then DIG=/usr/local/bin/dig else echo "Cannot find $DIG nor /usr/local/bin/dig" fi fi CHALLENGETYPE="http" GENERAL_TIMEOUT=40 CURL_OPTIONS="--connect-timeout ${GENERAL_TIMEOUT} -k --silent" OS=`uname` OPENSSL=/usr/bin/openssl TIMESTAMP=`date +%s` LETSENCRYPT_OPTION=`${DA_BIN} c | grep '^letsencrypt=' | cut -d= -f2` ACCESS_GROUP_OPTION=`${DA_BIN} c | grep '^secure_access_group=' | cut -d= -f2` DA_HOSTNAME=`${DA_BIN} c | grep '^servername=' | cut -d= -f2` FILE_CHOWN="diradmin:mail" FILE_CHMOD="640" if [ "${ACCESS_GROUP_OPTION}" != "" ]; then FILE_CHOWN="diradmin:${ACCESS_GROUP_OPTION}" fi if [ ! -x ${LEGO} ]; then echo "${LEGO} is required, exiting..." exit 1 fi ####### PRE_CHECK FUNCTIONS BEGIN ######### DOCUMENT_ROOT=$5 WELLKNOWN_PATH="/var/www/html/.well-known/acme-challenge" if [ ! -z "${DOCUMENT_ROOT}" ] && [ -d "${DOCUMENT_ROOT}" ]; then WELLKNOWN_PATH=${DOCUMENT_ROOT} fi caa_check() { CAA_OK=true #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=`${DIG} @${DNS_SERVER} AAAA ${1} +short | grep -v '\.$' | tail -n1` if [ "$?" -eq 9 ] && [ "${DNS_SERVER}" != "${NEW_IP}" ]; then if [ "${NEW_IP}" != "" ]; then DNS_SERVER=${NEW_IP} IP_TO_RESOLV=`${DIG} @${DNS_SERVER} AAAA ${1} +short | grep -v '\.$' | tail -n1` fi fi for i in `echo ${1} | awk -F'.' '{b=$NF;for(i=NF-1;i>0;i--){b=$i FS b;print b}}'`; do { if ${DIG} CAA ${i} @${DNS_SERVER} +short | grep -m1 -q -F -- "issue"; then CAA_OK=false if ${DIG} CAA ${i} @${DNS_SERVER} +short | grep -m1 -q -F -- "letsencrypt.org"; then CAA_OK=true else CAA_CURRENT=`${DIG} CAA ${i} @${DNS_SERVER} +short | grep -m1 issue | awk '{print $3}'` fi fi if ${DIG} CAA ${i} @${DNS_SERVER} | 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() { if [ ! -d ${WELLKNOWN_PATH} ]; then mkdir -p ${WELLKNOWN_PATH} chown ${USER}:${USER} ${HOSTNAME_DIR}/.well-known chown ${USER}:${USER} ${WELLKNOWN_PATH} fi RAND_BITS=`openssl rand -hex 8` TEMP_FILENAME=letsencrypt_${TIMESTAMP}_${RAND_BITS} touch ${WELLKNOWN_PATH}/${TEMP_FILENAME} chmod 644 ${WELLKNOWN_PATH}/${TEMP_FILENAME} chown ${USER}:${USER} ${WELLKNOWN_PATH}/${TEMP_FILENAME} CURL_RESOLV_OPTIONS="" #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=`${DIG} @${DNS_SERVER} AAAA ${1} +short | grep -v '\.$' | tail -n1` if [ "$?" -eq 9 ] && [ "${DNS_SERVER}" != "${NEW_IP}" ]; then if [ "${NEW_IP}" != "" ]; then DNS_SERVER=${NEW_IP} IP_TO_RESOLV=`${DIG} @${DNS_SERVER} AAAA ${1} +short | grep -v '\.$' | tail -n1` fi fi if ! echo "${IP_TO_RESOLV}" | grep -m1 -q ':'; then IP_TO_RESOLV="" fi if [ -z "${IP_TO_RESOLV}" ]; then IP_TO_RESOLV=`${DIG} @${DNS_SERVER} ${1} +short | tail -n1` CURRENT_RESOLV=`${DIG} ${1} +short | tail -n1` else CURRENT_RESOLV=`${DIG} AAAA ${1} +short | grep -v '\.$' | tail -n1` fi if [ -z "${IP_TO_RESOLV}" ]; then echo 1 rm -f ${WELLKNOWN_PATH}/${TEMP_FILENAME} return 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=`${DIG} @${DNS_SERVER} ${1} +short | tail -n1` CURRENT_RESOLV=`${DIG} ${1} +short | tail -n1` fi fi fi if [ ! -z "${IP_TO_RESOLV}" ]; then if ${CURL} --help connection | grep -m1 -q 'resolve'; then CURL_RESOLV_OPTIONS="--resolve ${1}:80:${IP_TO_RESOLV} --resolve ${1}:443:${IP_TO_RESOLV}" fi fi #Checking if http://www.domain.com/.well-known/acme-challenge/${TEMP_FILENAME} is available if ! ${CURL} ${CURL_OPTIONS} ${CURL_RESOLV_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 echo 1 rm -f ${WELLKNOWN_PATH}/${TEMP_FILENAME} return else echo "Challenge pre-checks for http://${1}/.well-known/acme-challenge/${TEMP_FILENAME} failed... Command:" echo "${CURL} ${CURL_OPTIONS} ${CURL_RESOLV_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 echo 0 rm -f ${WELLKNOWN_PATH}/${TEMP_FILENAME} return fi if [ -e ${WELLKNOWN_PATH}/${TEMP_FILENAME} ]; then rm -f ${WELLKNOWN_PATH}/${TEMP_FILENAME} fi } ####### PRE_CHECK FUNCITONS END ######### #Set staging server if testing SERVER_HOSTNAME="`hostname -f 2>/dev/null`" if [ -z "${SERVER_HOSTNAME}" ] && [ "${OS}" != "FreeBSD" ] && [ -x /usr/bin/hostnamectl ]; then SERVER_HOSTNAME=`/usr/bin/hostnamectl --static | head -n1` if ! echo "${SERVER_HOSTNAME}" | grep -m1 -q '\.'; then SERVER_HOSTNAME=`grep -m1 -o "${SERVER_HOSTNAME}\.[^ ]*" /etc/hosts` fi fi if [ ! -s /usr/local/directadmin/data/users/admin/user.conf ] ;then ADMIN_USERCONF="`grep -l -m1 '^creator=root$' /usr/local/directadmin/data/users/*/user.conf`" if [ -z "${ADMIN_USERCONF}" ]; then ADMIN_USERCONF="/usr/local/directadmin/data/users/`head -n1 /usr/local/directadmin/data/admin/admin.list`/user.conf" fi else ADMIN_USERCONF=/usr/local/directadmin/data/users/admin/user.conf fi if [ ! -z "${ADMIN_USERCONF}" ] && [ -s ${ADMIN_USERCONF} ]; then EMAIL="`grep -m1 '^email=' ${ADMIN_USERCONF} 2>/dev/null | cut -d= -f2 | cut -d, -f1`" fi if [ -z "${EMAIL}" ]; then EMAIL="admin@${SERVER_HOSTNAME}" fi DOMAIN=$2 CHILD_DOMAIN=false #Old-method if [ "${SINGLE_CERT_STORE}" != "yes" ]; then #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 { DA_SERVER_NAME=`echo "${DA_HOSTNAME}" | grep -m1 "^${DOMAIN_ESCAPED}\$"` if [ ! -z "${DA_SERVER_NAME}" ]; then #we're a hostname, skip this check break fi DOMAIN_NAME_FOUND=${TDOMAIN} DOMAIN_ESCAPED="`echo ${DOMAIN_NAME_FOUND} | perl -p0 -e 's#\.#\\\.#g'`" 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 { DA_SERVER_NAME=`echo "${DA_HOSTNAME}" | grep -m1 "^${DOMAIN_ESCAPED}\$"` if [ ! -z "${DA_SERVER_NAME}" ]; 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="`echo ${PARENT_DOMAIN_NAME_FOUND} | perl -p0 -e 's#\.#\\\.#g'`" 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 -m1 -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="`echo ${DOMAIN_NAME_FOUND} | perl -p0 -e 's#\.#\\\.#g'`" USER=${PARENT_DOMAIN_OWNER_USER} HOSTNAME=0 FOUNDDOMAIN=1 CHILD_DOMAIN=true break fi fi } done fi if [ "${FOUNDDOMAIN}" = "0" ]; then LETSENCRYPT_LIST=`${DA_BIN} c | grep -m1 "^letsencrypt_list=" | cut -d= -f2 | tr ':' ' '` #check parent domain for TDOMAIN in `echo "${DOMAIN}" | tr ',' ' '`; do { if [ `echo ${TDOMAIN} | grep -o '\.' | wc -l` -gt 1 ] && [ "${FOUNDDOMAIN}" = "0" ]; then DA_SERVER_NAME=`echo "${DA_HOSTNAME}" | grep -m1 "^${DOMAIN_ESCAPED}\$"` if [ ! -z "${DA_SERVER_NAME}" ]; then #we're a hostname, don't do any further lookups break fi CHILD_NAME=`echo ${TDOMAIN} | cut -d'.' -f1` PARENT_DOMAIN_NAME_FOUND=`echo ${TDOMAIN} | perl -p0 -e 's|^[^\.]*\.||g'` PARENT_DOMAIN_ESCAPED="`echo ${PARENT_DOMAIN_NAME_FOUND} | perl -p0 -e 's#\.#\\\.#g'`" 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}" ] && [ ! -z "${PARENT_DOMAIN_OWNER_USER}" ]; then DOMAIN_NAME_FOUND=${TDOMAIN} DOMAIN_ESCAPED="`echo ${DOMAIN_NAME_FOUND} | perl -p0 -e 's#\.#\\\.#g'`" 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="`echo ${DOMAIN_NAME_FOUND} | perl -p0 -e 's#\.#\\\.#g'`" USER="root" DA_SERVER_NAME=`echo "${DA_HOSTNAME}" | grep -m1 "^${DOMAIN_ESCAPED}\$"` if [ ! -z "${DA_SERVER_NAME}" ]; 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 CSR_CF_FILE=$4 DA_USERDIR="/usr/local/directadmin/data/users/${USER}" DA_CONFDIR="/usr/local/directadmin/conf" HOSTNAME_DIR="/var/www/html" 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" if [ "${DOCUMENT_ROOT}" != "" ]; then DOMAIN_DIR="${DOCUMENT_ROOT}" elif ${DA_BIN} c | grep -m1 -q '^letsencrypt=2$'; then USER_HOMEDIR="`grep -m1 \"^${USER}:\" /etc/passwd | cut -d: -f6`" DOMAIN_DIR="${USER_HOMEDIR}/domains/${DOMAIN_NAME_FOUND}/public_html" else DOMAIN_DIR="${HOSTNAME_DIR}" fi WELLKNOWN_PATH="${DOMAIN_DIR}/.well-known/acme-challenge" else DNSPROVIDER_FALLBACK="${DA_CONFDIR}/ca.dnsprovider" KEY=`${DA_BIN} c |grep ^cakey= | cut -d= -f2` CERT=`${DA_BIN} c |grep ^cacert= | cut -d= -f2` CACERT=`${DA_BIN} c |grep ^carootcert= | cut -d= -f2` SET_DA_CACERT=false if [ "${CACERT}" = "" ] || [ "${CERT}" = "${DA_CONFDIR}/carootcert.pem" ]; then CERT="${DA_CONFDIR}/cacert.pem" CACERT="${DA_CONFDIR}/carootcert.pem" SET_DA_CACERT=true fi DOMAIN_DIR="${HOSTNAME_DIR}" WELLKNOWN_PATH="${DOMAIN_DIR}/.well-known/acme-challenge" fi if [ -s "${CERT}" ] && [ "$1" = "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 [ "$1" = "request" ] && ! echo "${DOMAIN}" | grep -m1 -q ","; then if [ -s "${CSR_CF_FILE}" ] && grep -m1 -q 'DNS:' "${CSR_CF_FILE}"; then DOMAIN="`cat \"${CSR_CF_FILE}\" | grep '^subjectAltName=' | 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 elif [ "$1" = "request_full" ]; then #find all subdomains and pointers, and include them in the list. SUB_LIST_FILE="${DA_USERDIR}/domains/${DOMAIN}.subdomains" SUB_LIST="" if [ -s ${SUB_LIST_FILE} ]; then SUB_LIST=`cat ${SUB_LIST_FILE}` fi SUB_LIST="${SUB_LIST} www mail ftp smtp pop" for s in `echo ${SUB_LIST}`; do { H=${s}.${DOMAIN} if [ "${CHALLENGETYPE}" = "http" ]; then CHALLENGE_TEST=`challenge_check ${H} silent` else CHALLENGE_TEST=0 fi if [ ${CHALLENGE_TEST} -eq 1 ] && [ "${CHALLENGETYPE}" = "http" ]; then echo "${H} was skipped due to unreachable http://${H}/.well-known/acme-challenge/${TEMP_FILENAME} file." else DOMAIN="${DOMAIN},${H}" fi }; done; POINTER_LIST="${DA_USERDIR}/domains/${DOMAIN}.pointers" if [ -s ${POINTER_LIST} ]; then for p in `cat ${POINTER_LIST} | cut -d= -f1`; do { DOMAIN="${DOMAIN},${p},www.${p}" }; done; fi fi #It could be a symlink, so we use -e if [ ! -e "${DOMAIN_DIR}" ]; then echo "${DOMAIN_DIR} does not exist. Exiting..." exit 1 fi fi #Set validation method CHALLENGETYPE=http #empty env for dnsprovider - but dnsprovider file in use if [ -s "${DNSPROVIDER_FALLBACK}" ] && [ -z "${dnsprovider}" ]; then export `grep -o '^[a-zA-Z0-9_]*=[^;<>|\ ]*' "${DNSPROVIDER_FALLBACK}" | xargs` fi if [ ! -z "${dnsprovider}" ] && [ "${dnsprovider}" != "exec" ]; then DNSPROVIDER_NAME=${dnsprovider} echo "Found DNS provider configured: ${DNSPROVIDER_NAME}" CHALLENGETYPE="dns ${DNSPROVIDER_NAME}" elif echo "${DOMAIN}" | grep -m1 -q '*\.'; then echo "Found wildcard domain name and http challenge type, switching to dns-01 validation." CHALLENGETYPE="dns exec" 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 { CHALLENGE_TEST=`challenge_check ${domain_name} silent` if [ ${CHALLENGE_TEST} -eq 1 ]; 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" ]; then challenge_check ${domain_name} fi }; done if echo "${DOMAIN}" | grep -m1 -q ','; then DOMAINS=`echo "${DOMAIN}" | perl -p0 -e "s/,/ -d /g"` DOMAIN_FLAG="-d ${DOMAINS}" FIRST_DOMAIN=`echo "${DOMAIN}" | cut -d, -f1` else DOMAINS="${DOMAIN}" DOMAIN_FLAG="-d ${DOMAIN}" FIRST_DOMAIN=${DOMAIN} fi if [ "$1" = "request_full" ] && [ "${SINGLE_CERT_STORE}" = "yes" ]; then echo "request_full is not supported/recommended to use anymore" exit 1 fi # extract the acme_provider=provider from domain configuration and determine the ACME url from that domain_conf_file="${DA_USERDIR}/domains/${FIRST_DOMAIN}.conf" ACME=`grep -m1 ^acme_provider= $domain_conf_file 2>/dev/null | cut -d= -f2` if [ "${ACME}" = "zerossl" ]; then API_URI="https://acme.zerossl.com/v2/DV90" elif [ "${ACME}" = "letsencrypt" ] && [ "${staging}" = "yes" ]; then API_URI="https://acme-staging-v02.api.letsencrypt.org/directory" elif [ "${ACME}" = "letsencrypt" ]; then API_URI="https://acme-v02.api.letsencrypt.org/directory" elif [ -f /root/.zerossl ]; then API_URI="https://acme.zerossl.com/v2/DV90" elif [ "${staging}" = "yes" ]; then API_URI="https://acme-staging-v02.api.letsencrypt.org/directory" else API_URI="https://acme-v02.api.letsencrypt.org/directory" fi APPEND_SERVER="-s ${API_URI}" if [ "$1" = "request_single" ] || [ "$1" = "request" ] || [ "$1" = "renew" ] || [ "$1" = "request_full" ] ; then CERT_DOMAIN_FILE=`echo "${FIRST_DOMAIN}" | tr '*' '_'` LEGO_CERT_PATH="${LEGO_DATA_PATH}/certificates/${CERT_DOMAIN_FILE}.crt" LEGO_KEY_PATH="${LEGO_DATA_PATH}/certificates/${CERT_DOMAIN_FILE}.key" LEGO_ISSUER_CERT_PATH=`echo "${LEGO_CERT_PATH}" | perl -p0 -e 's|\.crt$|.issuer.crt|g'` if [ -s "${LEGO_KEY_PATH}" ] && [ -z "$3" ]; then if ! grep -m1 -q 'BEGIN EC PRIVATE' "${LEGO_KEY_PATH}"; then KEY_SIZE="rsa4096" ECC_USED=false fi fi ${LEGO} --path ${LEGO_DATA_PATH} --dns.resolvers ${DNS_SERVER} --accept-tos ${APPEND_SERVER} -m ${EMAIL} --${CHALLENGETYPE} --http.webroot ${DOMAIN_DIR} ${DOMAIN_FLAG} --key-type ${KEY_SIZE} run --no-bundle --preferred-chain="ISRG Root X1" if [ $? -eq 0 ]; then if [ "${SINGLE_CERT_STORE}" != "yes" ]; then if [ -s "${LEGO_CERT_PATH}" ] && [ -s "${LEGO_KEY_PATH}" ]; then if [ `grep -c "BEGIN CERTIFICATE" "${LEGO_CERT_PATH}"` -eq 1 ]; then cp -pf "${LEGO_CERT_PATH}" ${CERT} else ${OPENSSL} x509 -in "${LEGO_CERT_PATH}" -out ${CERT} fi cp -pf "${LEGO_KEY_PATH}" ${KEY} if [ -s "${LEGO_ISSUER_CERT_PATH}" ]; then cp -pf "${LEGO_ISSUER_CERT_PATH}" ${CACERT} cat ${CERT} ${CACERT} > ${CERT}.combined else cp -pf "${LEGO_CERT_PATH}" ${CERT}.combined fi date +%s > ${CERT}.creation_time chown ${FILE_CHOWN} ${KEY} ${CERT} ${CERT}.combined ${CACERT} ${CERT}.creation_time chmod ${FILE_CHMOD} ${KEY} ${CERT} ${CERT}.combined ${CACERT} ${CERT}.creation_time echo "Certificate for ${DOMAIN} has been created successfully!" else echo "New key/certificate is empty. Exiting..." exit 1 fi else echo "Certificate for ${DOMAIN} has been created successfully!" #here we need to do something light to scan ${LEGO_DATA_PATH}/.lego/certificates/ certs, wildcard name is _.domain.com.crt / _.domain.com.key. If we need cacert to be in a separate file - there is also a flag for this. It should auto-move the certs from this dir and update .creation_time file if we still rely on it? #it has the hook to just do it for the domain we generate (for example _.domain.com), but DA needs to place it to all domains that may use it... # echo "action=certscan&do=update&path=${LEGO_DATA_PATH}/.lego/certificates" >> ${TASK_QUEUE} fi else echo "Certificate generation failed." exit 1 fi #Change exim, apache/nginx certs if [ "${SINGLE_CERT_STORE}" != "yes" ]; then if [ "${HOSTNAME}" -eq 1 ]; then echo "DirectAdmin certificate has been setup." if grep -m1 -q "^cacert=${DA_CONFDIR}/carootcert.pem$" /usr/local/directadmin/conf/directadmin.conf; then ${DA_BIN} set cacert ${DA_CONFDIR}/cacert.pem fi if ${SET_DA_CACERT}; then ${DA_BIN} set carootcert ${CACERT} fi if ${DA_BIN} c | grep -m1 -q "^ssl=0$"; then ${DA_BIN} set ssl 1 fi #Exim echo "Setting up cert for Exim..." EXIMKEY="/etc/exim.key" EXIMCERT="/etc/exim.cert" cp -f ${KEY} ${EXIMKEY} cat ${CERT} ${CACERT} > ${EXIMCERT} chown mail:mail ${EXIMKEY} ${EXIMCERT} chmod 600 ${EXIMKEY} ${EXIMCERT} echo "action=exim&value=restart" >> ${TASK_QUEUE} echo "action=dovecot&value=restart" >> ${TASK_QUEUE} #Apache echo "Setting up cert for WWW server..." if [ -d /etc/httpd/conf/ssl.key ] && [ -d /etc/httpd/conf/ssl.crt ]; then APACHEKEY="/etc/httpd/conf/ssl.key/server.key" APACHECERT="/etc/httpd/conf/ssl.crt/server.crt" APACHECACERT="/etc/httpd/conf/ssl.crt/server.ca" APACHECERTCOMBINED="${APACHECERT}.combined" cp -f ${KEY} ${APACHEKEY} cp -f ${CERT} ${APACHECERT} cp -f ${CACERT} ${APACHECACERT} cat ${APACHECERT} ${APACHECACERT} > ${APACHECERTCOMBINED} chown root:root ${APACHEKEY} ${APACHECERT} ${APACHECACERT} ${APACHECERTCOMBINED} chmod 600 ${APACHEKEY} ${APACHECERT} ${APACHECACERT} ${APACHECERTCOMBINED} HTTPDACTION=restart GRACEFUL=`$DA_BIN c |grep ^graceful_restarts= | cut -d= -f2` if [ "$GRACEFUL" = "1" ]; then SYSTEMD=`$DA_BIN c |grep ^systemd= | cut -d= -f2` if [ "$SYSTEMD" = "1" ]; then HTTPDACTION=reload else HTTPDACTION=graceful fi fi echo "action=httpd&value=${HTTPDACTION}&affect_php_fpm=no" >> ${TASK_QUEUE} fi #Nginx if [ -d /etc/nginx/ssl.key ] && [ -d /etc/nginx/ssl.crt ]; then NGINXKEY="/etc/nginx/ssl.key/server.key" NGINXCERT="/etc/nginx/ssl.crt/server.crt" NGINXCACERT="/etc/nginx/ssl.crt/server.ca" NGINXCERTCOMBINED="${NGINXCERT}.combined" cp -f ${KEY} ${NGINXKEY} cp -f ${CERT} ${NGINXCERT} cp -f ${CACERT} ${NGINXCACERT} cat ${NGINXCERT} ${NGINXCACERT} > ${NGINXCERTCOMBINED} chown root:root ${NGINXKEY} ${NGINXCERT} ${NGINXCACERT} ${NGINXCERTCOMBINED} chmod 600 ${NGINXKEY} ${NGINXCERT} ${NGINXCACERT} ${NGINXCERTCOMBINED} echo "action=nginx&value=restart&affect_php_fpm=no" >> ${TASK_QUEUE} fi #OLS if [ -d /usr/local/lsws/ssl.key ] && [ -d /usr/local/lsws/ssl.crt ]; then OLSKEY="/usr/local/lsws/ssl.key/server.key" OLSCERT="/usr/local/lsws/ssl.crt/server.crt" OLSCACERT="/usr/local/lsws/ssl.crt/server.ca" OLSCERTCOMBINED="${OLSCERT}.combined" cp -f ${KEY} ${OLSKEY} cp -f ${CERT} ${OLSCERT} cp -f ${CACERT} ${OLSCACERT} cat ${OLSCERT} ${OLSCACERT} > ${OLSCERTCOMBINED} chown root:root ${OLSKEY} ${OLSCERT} ${OLSCACERT} ${OLSCERTCOMBINED} chmod 600 ${OLSKEY} ${OLSCERT} ${OLSCACERT} ${OLSCERTCOMBINED} echo "action=openlitespeed&value=restart&affect_php_fpm=no" >> ${TASK_QUEUE} fi #FTP echo "Setting up cert for FTP server..." cat ${KEY} ${CERT} ${CACERT} > /etc/pure-ftpd.pem chmod 600 /etc/pure-ftpd.pem chown root:root /etc/pure-ftpd.pem if /usr/local/directadmin/directadmin c | grep -m1 -q "^pureftp=1\$"; then echo "action=pure-ftpd&value=restart" >> ${TASK_QUEUE} else echo "action=proftpd&value=restart" >> ${TASK_QUEUE} fi echo "action=directadmin&value=restart" >> ${TASK_QUEUE} echo "The services will be restarted in about 1 minute via the dataskq." run_dataskq fi fi elif [ "$1" = "revoke" ]; then ${LEGO} --path ${LEGO_DATA_PATH} ${APPEND_SERVER} -m ${EMAIL} ${DOMAIN_FLAG} revoke fi