#!/bin/sh

# the following variables can be overridden by the config file
CERT_DST="/usr/local/etc/ssl"
CERT_SERVER="https://certs.example.org/certs"

# a space separated list of domains for which certs will be downloads
MYCERTS="example.com"

# a space separated list of services to restart/reload
SERVICES=""
SERVICES_RELOAD=""
SERVICES_RESTART=""

DOWNLOAD_DIR="/var/db/anvil"

# be sure to specify the agument & have no spaces in between the single quotes
USER_AGENT="anvil-cert-puller"

#
# --mirror avoids replacement when identical
# --quiet avoids noise
#
FETCH="/usr/bin/fetch --mirror --quiet --user-agent='${USER_AGENT}'"

CURL="/usr/local/bin/curl --silent --user-agent '${USER_AGENT}' --remote-time"

WGET="/usr/local/bin/wget --quiet --user-agent='${USER_AGENT}'"

# items above can be overridden via the configuration file
# items below here are not usually altered

CONFIG="/usr/local/etc/anvil/cert-puller.conf"

if [ -f ${CONFIG} ]; then
  . ${CONFIG}
fi

# initialize variables used below

SUDO_EXAMPLES=0
NEW_CERTS_FOUND=0

# various commands used by this script
BASENAME="/usr/bin/basename"
CP="/bin/cp"
DIFF="/usr/bin/diff"
# These are the downlaoded certs which we will consider for installation
FIND_CERT_FILES="/usr/bin/find ${DOWNLOAD_DIR} -type f"

# if you want to disable logging, not recommend, put a hash before /usr
LOGGER="/usr/bin/logger -t cert-puller"
MV="/bin/mv"
SERVICE="/usr/sbin/service"
SUDO="/usr/local/bin/sudo"
TOUCH="/usr/bin/touch"

usage(){
  echo "Usage: $0 [-s] [-h]"
  exit 1
}

sudo_examples(){
  # this function prints out commands which you can use with visudo to copy/paste

  #
  # NOTE: the code here must closely match that within install_new_certs() & restart_services() & restart_services_user()
  #
  for cert in ${MYCERTS}
  do
    FILES_FETCHING="ca.cer ${cert}.cer ${cert}.fullchain.cer"
    for file in ${FILES_FETCHING}
    do
      echo "anvil   ALL=(ALL) NOPASSWD:${CP} -a ${DOWNLOAD_DIR}/${file} ${CERT_DST}/${file}.tmp"
      echo "anvil   ALL=(ALL) NOPASSWD:${MV} ${CERT_DST}/${file}.tmp ${CERT_DST}/${file}"
    done
  done

  for service in ${SERVICES}
  do
    case ${service} in
      "apache22" | "apache24")
        echo "anvil   ALL=(ALL) NOPASSWD:${SERVICE} ${service} graceful"
        ;;

      "dovecot" | "mosquitto" | "nginx" | "postfix")
        echo "anvil   ALL=(ALL) NOPASSWD:${SERVICE} ${service} restart"
        ;;

      "postgresql")
        echo "anvil   ALL=(ALL) NOPASSWD:${SERVICE} ${service} reload"
        ;;
    esac
  done

  for service in ${SERVICES_RELOAD}
  do
    echo "anvil   ALL=(ALL) NOPASSWD:${SERVICE} ${service} reload"
  done

  for service in ${SERVICES_RESTART}
  do
    echo "anvil   ALL=(ALL) NOPASSWD:${SERVICE} ${service} restart"
  done
}

sanity_checks(){
  if [ ! -r "${CERT_DST}" -o ! -d "${CERT_DST}" ]; then
    ${LOGGER} "${CERT_DST}" is NOT readable and a directory
    ${LOGGER} $0 exits
    exit 2
  fi
}

fetch_new_certs(){
  # first, we fetch the certs are looking for.
  ${LOGGER} fetching into ${DOWNLOAD_DIR}
  for cert in ${MYCERTS}
  do
    ${LOGGER} checking certs for ${cert}
    FILES_FETCHING="ca.cer ${cert}.cer ${cert}.fullchain.cer"
    for file in ${FILES_FETCHING}
    do
      ${LOGGER}         "${cert} :: ${file}"
      case ${FETCH_TOOL} in
        "wget")
          ${LOGGER} running: ${WGET} --output-document=${DOWNLOAD_DIR}/${file} ${CERT_SERVER}/${cert}/${file}
          ${WGET} ${WGET_OPTIONS} --output-document=${DOWNLOAD_DIR}/${file} ${CERT_SERVER}/${cert}/${file}
          ;;
        "curl")
          ${LOGGER} running: ${CURL} -o ${DOWNLOAD_DIR}/${file} ${CERT_SERVER}/${cert}/${file}
          ${CURL} ${CURL_OPTIONS} -o ${DOWNLOAD_DIR}/${file} ${CERT_SERVER}/${cert}/${file}
          ;;
        *)
          ${LOGGER} running: ${FETCH} -o ${DOWNLOAD_DIR} ${CERT_SERVER}/${cert}/${file}
          ${FETCH} ${FETCH_OPTIONS} -o ${DOWNLOAD_DIR} ${CERT_SERVER}/${cert}/${file}
          ;;
      esac
      RESULT=$?
      if [ "${RESULT}" != "0" ]; then
        ${LOGGER} "error '${RESULT}' on fetch - perhaps the remote file does not exist."
      fi
    done
  done
}

install_new_certs(){
  ${LOGGER} looking for cert files: ${FIND_CERT_FILES}
  # for each new thing we find, move it over to the right place
  NEW_FILES=`${FIND_CERT_FILES}`
  for new_file in ${NEW_FILES}
  do
    filename=`${BASENAME} ${new_file}`
    ${LOGGER} validating ${filename}

    # that cert may not be installed
    if [ -f ${CERT_DST}/${filename} ]; then
      diff=`${DIFF} ${new_file} ${CERT_DST}/${filename}`
    else
      diff="x";
      ${LOGGER} ${filename} does not exist and will be installed
    fi

    # only install if the certs are different.
    if [ "${diff}X" != "X" ]; then
      ${LOGGER} installing ${filename}
      #
      # NOTE: the code here must closely match that within sudo_examples() & restart_services()
      #
      ${SUDO} ${CP} -a ${new_file} ${CERT_DST}/${filename}.tmp
      if [ $? != 0 ]; then
         ${LOGGER} FATAL: could not install ${new_file} to ${CERT_DST}/${filename}.tmp - are the sudo permissions up to date? see cert-puller -s
      fi
      ${SUDO} ${MV} ${CERT_DST}/${filename}.tmp ${CERT_DST}/${filename}
      if [ $? != 0 ]; then
         ${LOGGER} FATAL: could not install ${CERT_DST}/${filename}.tmp to ${CERT_DST}/${filename} - are the sudo permissions up to date? see cert-puller -s
      fi
      NEW_CERTS_FOUND=1
    fi
  done
}


restart_services(){
  #
  # NOTE: the code here must closely match that within sudo_examples()
  #
  
  for service in ${SERVICES}
  do
    case ${service} in
      "apache22" | "apache24")
        ${LOGGER} doing a graceful on ${service}
        ERRORS=`${SUDO} ${SERVICE} ${service} graceful 2>&1`
        if [ $? != 0 ]; then
          ${LOGGER} ERROR on graceful
          ${LOGGER} "${ERRORS}"
          echo "${ERRORS}"
        else
          ${LOGGER} no errors on graceful
        fi
        ;;

      # it might be better if we do a reload.
      # will that be sufficient?
      "dovecot" | "mosquitto" | "nginx" | "postfix" )
        ${LOGGER} restarting ${service}
        ERRORS=`${SUDO} ${SERVICE} ${service} restart 2>&1`
        if [ $? != 0 ]; then
          ${LOGGER} ERROR on restart
          ${LOGGER} "${ERRORS}"
          echo "${ERRORS}"
        else
          ${LOGGER} no errors on restart
        fi
        ;;

      # For Postgresql, we only do a reload
      "postgresql" )
        ${LOGGER} reloading ${service}
        ERRORS=`${SUDO} ${SERVICE} ${service} reload 2>&1`
        if [ $? != 0 ]; then
          ${LOGGER} ERROR on reload
          ${LOGGER} "${ERRORS}"
          echo "${ERRORS}"
        else
          ${LOGGER} no errors on reload
        fi
        ;;

      *)
        ${LOGGER} "Unknown service requested in $0: ${service}"
        ;;
    esac
  done
}

restart_services_user(){
  #
  # NOTE: the code here must closely match that within sudo_examples()
  #
  for service in ${SERVICES_RELOAD}
  do
    ${LOGGER} doing a reload on ${service}
    ERRORS=`${SUDO} ${SERVICE} ${service} reload 2>&1`
    if [ $? != 0 ]; then
      ${LOGGER} ERROR on reload
      ${LOGGER} "${ERRORS}"
      echo "${ERRORS}"
    else
      ${LOGGER} no errors on reload
    fi
  done

  for service in ${SERVICES_RESTART}
  do
    ${LOGGER} doing a restart on ${service}
    ERRORS=`${SUDO} ${SERVICE} ${service} restart 2>&1`
    if [ $? != 0 ]; then
      ${LOGGER} ERROR on restart
      ${LOGGER} "${ERRORS}"
      echo "${ERRORS}"
    else
      ${LOGGER} no errors on restart
    fi
  done
}

#
# main code starts here
#

while getopts "hs" opt; do
  case $opt in
    s)
      SUDO_EXAMPLES=1
      shift
      ;;
    h)
      usage
      shift
      ;;
    * )
      usage
      ;;
  esac
done

#
# give them samples for visudo
#
if [ ${SUDO_EXAMPLES} == "1" ]; then
  sudo_examples
  exit 0
fi

${LOGGER} starting $0

sanity_checks

fetch_new_certs

install_new_certs

if [ ${NEW_CERTS_FOUND} == "1" ]; then
  restart_services
  restart_services_user
else
  ${LOGGER} no new certs found
fi

${LOGGER} stopping $0
