#!/bin/bash
# detect and propagate new certificates based on automatic renewals by certbot

SPATH="$(dirname "$(readlink -f ${BASH_SOURCE[0]})")"    # dir of the script
SNAME="$(basename ${BASH_SOURCE[0]})"                    # name of the script

ASCEDSCONF='local site hostconf certbot'
ASCEDSLIBS='utils'

if [ ! -r "${SPATH}/asceds-certbot-head" ] ; then
   echo "No header ${SPATH}/asceds-certbot-head. Exiting"
   exit 9
fi

. ${SPATH}/asceds-certbot-head

asceds-propagate-certbot-usage()
{
  cat << ENDUSAGE

asceds-propagate-certbot [-h] [-q] [-l] [-r] [-c <client>]
-h --> display usage and exit
-q --> no question asked; sends output to the logs in ${ASCEDSLOGDIR}/
-l --> keep the certificate local, don't trigger asceds-send-cert
-r --> ignore noreturn policy and try to send the certificate
       (mutually exclusive with -l; needs -c)
-c <client> --> propagate only for <client> 

Looks for renewed certs;
Copies renewd cert files to ~asceds/<client>/; chown asceds:asceds
If client is unmanaged:
   Copies the cert files in the web dir for download.
   Sends email to requestor for downloading the renewed cert files.
If client is managed:
   Creates transfer cert flag ~asceds/<client>/.newcerts.
   If fully managed client and no -l option, 
      or if privately managed client with -r option:
         Sends cert files to the client through asceds-send-cert.
   If privately managed client, sends email to ALARM announcing new cert.
Tries to re-send cert files to fully managed clients if .newcerts is present.
Removes clients which were offline for more than 30 days.

ENDUSAGE
exit 0
}

# output to terminal by default
QUIETRUN=""

# asceds-send-cert trigger: default = send
KEEPLOCAL=''

# override NO_RETURN
FORCERET=''

# client to use with the -c option
CLIENT_NAME=''

# general validation and parse of the command line
for ARG in ${ARGS} ; do
   #echo "ARG=${ARG}"
   if [ "${ARG}" != "${ARG//[^-_a-zA-Z0-9.]/}" ] ; then
      asceds-error "Malformed argument ${ARG}"
   elif [ "${ARG}" == "-q" ] ; then
      shift
      asceds-exec-noninteractive "$*"
      # bring other scripts in non-interactive mode
      QUIETRUN="y"
   elif [ "${ARG}" == "-l" ] ; then
      shift
      KEEPLOCAL='yes'
   elif [ "${ARG}" == "-r" ] ; then
      shift
      FORCERET='yes'
   elif [ "${ARG}" == "-h" ] ; then
      shift
      asceds-propagate-certbot-usage
   elif [ "${ARG}" == "-c" ] ; then
      shift
      if [[ "$1" != -* ]] && [ -n "$1" ] ; then
         CLIENT_NAME=$( cat <<< $1 | tr '[:upper:]' '[:lower:]' )
         asceds-expand-hostname "${CLIENT_NAME}" && \
            CLIENT_NAME="${ASCEDS_FQDN}"
         asceds-validate-fqdn "${CLIENT_NAME}" || \
            asceds-error "Invalid client FQDN ${CLIENT_NAME}"
      fi
   else
      shift
   fi
done

if [ -n "${KEEPLOCAL}" ] && [ -n "${FORCERET}" ] ; then
   asceds-error "Options -l and -r are mutually exclusive"
fi 

# run as root only
asceds-run-asroot

# determine client list to propagate
CLIENTLIST=''
if [ -z "${CLIENT_NAME}" ] ; then
   CLIENTLIST=$( ls -1 /etc/letsencrypt/live/ | grep -v README | xargs )
   FORCERET=''
elif [ -d "/etc/letsencrypt/live/${CLIENT_NAME}" ] ; then
   CLIENTLIST=${CLIENT_NAME}
else
   asceds-error "No client ${CLIENT_NAME} in /etc/letsencrypt/live/"
fi

for CLIENT in ${CLIENTLIST} ; do
   if [ -r "/etc/letsencrypt/live/${CLIENT}/cert.pem" ] && \
      [ -r "/etc/letsencrypt/live/${CLIENT}/fullchain.pem" ] && \
      [ -r "/etc/letsencrypt/live/${CLIENT}/privkey.pem" ] && \
      [ -r "${ASCEDSHOMEDIR}/${CLIENT}_cert.conf" ] ; then

      # add/guess missing fields from cert.conf
      asceds-fix-client-certconf "${CLIENT}"

      # get the value of NO_RETURN, NO_ASCEDS, ASCEDS_CERT_ID
      NO_RETURN=''
      NO_ASCEDS=''
      ASCEDS_CERT_ID=''
      asceds-parse-certconf "${ASCEDSHOMEDIR}/${CLIENT}_cert.conf"

      if [ "${CLIENT}" != "${CLIENT_DOMAIN_NAME}" ] ; then
         asceds-warning "Misconfigured ${CLIENT}_cert.conf"
      else
         # initialize client dir if missing
         if [ ! -d "${ASCEDSHOMEDIR}/${CLIENT}" ] ; then
            asceds-warning "No ${ASCEDSHOMEDIR}/${CLIENT} ... fixing"
            mkdir -p ${ASCEDSHOMEDIR}/${CLIENT}
         fi

         if [ ! -r "${ASCEDSHOMEDIR}/${CLIENT}/asceds-site.conf" ] ; then
            {CP} ${ASCEDSETCDIR}/asceds-site.conf ${ASCEDSHOMEDIR}/${CLIENT}/
            if [ "${CLIENT}" != "${ASCEDSCRTMGR}" ] && \
               [ "${CLIENT}" != "$( hostname -f | tr '[:upper:]' '[:lower:]' )" ] && \
               [ -z "${NO_ASCEDS}" ] ; then
               touch ${ASCEDSHOMEDIR}/${CLIENT}/.newsiteconf
            fi
            chown -R asceds:asceds ${ASCEDSHOMEDIR}/${CLIENT}
         fi
         # at this point all legitimate clients have proper dirs, asceds-site.conf, 
         # and site config file update trigger 

         # check if certificate is not expired
         asceds-expiration-test "/etc/letsencrypt/live/${CLIENT}/cert.pem"

         # SENDCERTS--> new certificate available if non-empty
         SENDCERTS=''
         if [ -r "${ASCEDSHOMEDIR}/${CLIENT}/fullchain.pem" ] ; then
      
            TESTCERT=$( diff ${ASCEDSHOMEDIR}/${CLIENT}/fullchain.pem \
                        /etc/letsencrypt/live/${CLIENT}/fullchain.pem )
            if [ -n "${TESTCERT}" ] ; then
               asceds-echo "Certificate for ${CLIENT} was renewed"
               SENDCERTS='yes'
            else
               # test if all files are the same
               if [ -r "${ASCEDSHOMEDIR}/${CLIENT}/cert.pem" ] && \
                  [ -r "${ASCEDSHOMEDIR}/${CLIENT}/privkey.pem" ] ; then
                  TESTCERT1=$( diff ${ASCEDSHOMEDIR}/${CLIENT}/cert.pem \
                           /etc/letsencrypt/live/${CLIENT}/cert.pem ) 
                  TESTCERT2=$( diff ${ASCEDSHOMEDIR}/${CLIENT}/privkey.pem \
                           /etc/letsencrypt/live/${CLIENT}/privkey.pem )
                  if [ -n "${TESTCERT1}" ] || [ -n "${TESTCERT1}" ] ; then
                     asceds-error "Only some cert files for ${CLIENT} were changed"
                  fi
               else
                  asceds-error "Some cert files for ${CLIENT} are missing"
               fi
            fi
         else
            asceds-echo "Certificate for ${CLIENT} was newly generated"
            SENDCERTS='yes'
         fi
         # block above set SENDCERTS value: 
         # not empty if new certificates need to be sent
      
         # copy and send cert files if new
         if [ -n "${SENDCERTS}" ] ; then
            for CERTFILE in cert.pem fullchain.pem privkey.pem ; do
               ${CP} /etc/letsencrypt/live/${CLIENT}/${CERTFILE} \
                     ${ASCEDSHOMEDIR}/${CLIENT}/
            done
            chown -R asceds:asceds ${ASCEDSHOMEDIR}/${CLIENT} 
            asceds-echo "New certificate filess copied to ${ASCEDSHOMEDIR}/${CLIENT}"

            if [ -n "${NO_RETURN}" ] && [ -z "${FORCERET}" ] && \
               [ -z "${NO_ASCEDS}" ] ; then
               # privately managed client
               asceds-warning "New certificate files for privately managed client ${CLIENT} 
               are ready to be propagated"
            
               # new cert files propagation trigger set
               touch ${ASCEDSHOMEDIR}/${CLIENT}/.newcerts
               chown asceds:asceds ${ASCEDSHOMEDIR}/${CLIENT}/.newcerts

               # email availability to ALARM
               asceds-mailto-alarm "New certificate files for ${CLIENT} are prepared" \
                                      "${ASCEDSLOGF}"
            elif [ -n "${NO_ASCEDS}" ] ; then
               # unmanaged client, copy cert files to website
               if [ -z "${ASCEDS_CERT_ID}" ] ; then
                  ASCEDS_CERT_ID=$( cat /dev/urandom | tr -dc 'a-zA-Z0-9' | \
                                    fold -w 32 | head -n 1 )
                  asceds-refresh-var "${ASCEDSHOMEDIR}/${CLIENT}_cert.conf" \
                                     "ASCEDS_CERT_ID" "${ASCEDS_CERT_ID}"
               fi
               if [ ! -d "${ASCEDSWEBDIR}/html/cert/keys/${ASCEDS_CERT_ID}" ] ; then
                  mkdir -p ${ASCEDSWEBDIR}/html/cert/keys/${ASCEDS_CERT_ID}
               fi
               for CERTFILE in cert.pem fullchain.pem privkey.pem ; do
                  ${CP} -p /etc/letsencrypt/live/${CLIENT}/${CERTFILE} \
                       ${ASCEDSWEBDIR}/html/cert/keys/${ASCEDS_CERT_ID}/
               done

               cd ${ASCEDSWEBDIR}/html/cert/keys/${ASCEDS_CERT_ID}/
               openssl pkcs12 -inkey privkey.pem -in fullchain.pem -export \
                           -passout pass:${NO_ASCEDS} -out combined.pfx

               # adjust ownership
               chown -R ${ASCEDSWEBUSER} ${ASCEDSWEBDIR}/html/cert/keys/${ASCEDS_CERT_ID}

               # notify user
               if [ -z "${CLIENT_NAME}" ] ; then
                  # send email to user
                  SUBJ="Renewed certificate available for ${CLIENT_DOMAIN_NAME}"
                  BODY="A renewed certificate was issued for ${CLIENT_DOMAIN_NAME}
                     Please visit  https://${ASCEDSCRTMGR}/ to download the files."
                  if [ -z "${ALARMSEND}" ] ; then
                     asceds-error "No mailer installed: no email will be sent"
                  else
                     cat <<< ${BODY} | ${ALARMSEND} -s "${SUBJ}" ${NO_ASCEDS}
                  fi
               fi
            else         
               # fully managed client or forced delivery
               # flag that new cert files are available
               touch ${ASCEDSHOMEDIR}/${CLIENT}/.newcerts
               chown asceds:asceds ${ASCEDSHOMEDIR}/${CLIENT}/.newcerts
            
               if [ -z "${KEEPLOCAL}" ] ; then
                  # send certificate files to the client
                  sudo -u asceds -i ${ASCEDSBINDIR}/asceds-send-cert -q -c ${CLIENT}
               else
                  asceds-echo "Certificate files were not copied on ${CLIENT} "
               fi
            fi
         fi
      
         # try resending cert files and site config file 
         # if triggers are still present
         if [ -r "${ASCEDSHOMEDIR}/${CLIENT}/.newcerts" ] || \
            [ -r "${ASCEDSHOMEDIR}/${CLIENT}/.newsiteconf" ] ; then

            if [ -z "${NO_RETURN}" ] && [ -z "${KEEPLOCAL}" ] ; then
              asceds-echo "Re-trying to send new cert files and site config to ${CLIENT}"
              sudo -u asceds -i ${ASCEDSBINDIR}/asceds-send-cert -q -c ${CLIENT}
            fi
         fi
      fi
   else
      asceds-warning "Client ${CLIENT} doesn't have letsencrypt certificates \
         or a ${CLIENT}_cert.conf config file"
   fi
done

# remove clients which were offiline for more than 30 days
if [ "$( echo ${ASCEDSHOMEDIR}/down/* )" != \
            "${ASCEDSHOMEDIR}/down/*" ] ; then
   for DOWN_CLIENT in ${ASCEDSHOMEDIR}/down/* ; do
      if [[ $( find "${DOWN_CLIENT}" \
                    -mtime +30 -print ) ]] ; then
         # if client down for more than 30 days, remove it from the server
         asceds-client-remove -c $( basename ${DOWN_CLIENT} )
      fi
   done
fi
