#!/bin/bash
# shellcheck disable=SC2034,SC2086

#
# Name:         ssl-mgr
# Version:      1.2.2
# Description:  Seedbox SSL Manager
#
# (c) 2020-2023, Rapidseedbox, alexey@rapidseedbox.com
#

set -o pipefail

export LANG=en_US
export PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin

SUPPORTED_ON="Ubuntu20.04 Ubuntu22.04"

LSB_VENDOR="$(      lsb_release -si 2>&1 | awk '{print $1}' )"
LSB_RELEASE="$(     lsb_release -sr )"
LSB_CODENAME="$(    lsb_release -sc )"
LSB_DESCRIPTION="$( lsb_release -sd | sed -e 's/^\"\(.*\)\"$/\1/')"
[ "${LSB_VENDOR}" = "CentOS" ] || [ "${LSB_VENDOR}" = "Debian" ] \
    && LSB_RELEASE="$( echo "${LSB_RELEASE}" | awk -F '.' '{print $1}' )"
LSB_VER="${LSB_VENDOR}${LSB_RELEASE}"

#
# We don't support some vendors/versions :(
#
if ! grep -q " ${LSB_VER} " <<< " ${SUPPORTED_ON} " ; then
    >&2 echo -ne "\n$( \
            sed -re 's/^([a-zA-Z]*)(.*)$/\1 \2/' <<< "${LSB_VER}" \
                     ): Sorry, this distribution is not supported :(\n"
    >&2 echo -ne "\nSupported distributios:\n$( \
                   for _s in ${SUPPORTED_ON} ; do \
                       echo "  - $( sed -re 's/^([a-zA-Z]*)(.*)$/\1 \2/' <<< "${_s}" )" ; \
                   done )\n"
    exit 1
fi


# Fix "bad" terminals
case "${TERM}" in
  "xterm" )
      export TERM="linux"
      ;;
esac

APP_TITLE="RapidSeedbox SSL Manager"
SC_NAME="$( basename "${0}" )"
_VER="$( grep -m 1 -E '^#\s*Version:\s+' "${0}" | awk '{print $NF}' )"

RSBSSL_DIR="/var/local/rsbssl"
[ -d "${RSBSSL_DIR}" ] || mkdir -p "${RSBSSL_DIR}"

RSBSSL_LOG_DIR="/var/log/rsbssl"
[ -d "${RSBSSL_LOG_DIR}" ] || mkdir -p "${RSBSSL_LOG_DIR}"
LOGFILE="${RSBSSL_LOG_DIR}/${SC_NAME}.log"

if [ -d "/etc/logrotate.d" ] ; then
    cat <<-EOF > "/etc/logrotate.d/${SC_NAME}"
	${RSBSSL_LOG_DIR}/*.log
	{
	    rotate 8
	    weekly
	    missingok
	    notifempty
	    size 1M
	    compress
	    delaycompress
	}
	EOF
fi

#DEBUG="yes"
DEBUG="no"

RSB_URL="https://installers.rapidseedbox.com/scripts/whmcs_client_installers"

# Define the dialog exit status codes and options
DIALOG_OK=0
DIALOG_CANCEL=1
DIALOG_HELP=2
DIALOG_EXTRA=3
DIALOG_ITEM_HELP=4
DIALOG_ESC=255
DIALOG_OPTS="--ascii-lines --no-collapse --colors --cr-wrap"

DIALOG_MIXEDFORM_TEXT="You can use the UP/DOWN arrow keys to navigate between fields.\n\
Use TAB key to move to the buttons section\n\
Press ENTER when done or ESC to cancel."

CERTBOT_OPTS=""


# Collect some usage statistics :)
RSB_STATS_DIR="/var/local/stats"
[ ! -d "${RSB_STATS_DIR}" ] && mkdir -p "${RSB_STATS_DIR}"
if [ -e "${RSB_STATS_DIR}/${SC_NAME}.run.cnt" ] ; then
    run_cnt="$( cat "${RSB_STATS_DIR}/${SC_NAME}.run.cnt" )"
    run_cnt="$(( run_cnt + 1 ))"
else
    run_cnt=1
fi
echo "${run_cnt}" > "${RSB_STATS_DIR}/${SC_NAME}.run.cnt"



LIB_DIR="/usr/local/lib/rsb"
if [ ! -e "${LIB_DIR}/functions.sh" ] ; then
   if [ ! -d "${LIB_DIR}" ] ; then
       >&2 echo "Creating '${LIB_DIR}' directory..."
       if ! mkdir -p "${LIB_DIR}" >> "${LOGFILE}" 2>&1 ; then
           >&2 echo "Error creating '${LIB_DIR}' directory!" >> "${LOGFILE}"
           exit 1
       fi
   fi
   >&2 echo "Fetching shell common library..."
   wget -nc -q --no-check-certificate \
        -O "${LIB_DIR}/functions.sh" \
        --timeout=20 --tries=5 \
        "${RSB_URL}/support/functions.sh" >> "${LOGFILE}" 2>&1
   if [ ! -f "${LIB_DIR}/functions.sh" ] ; then
       >&2 echo "Error downloading '${RSB_URL}/support/functions.sh'" >> "${LOGFILE}"
       exit 1
   fi
fi
. "${LIB_DIR}/functions.sh"

usage () {
    if [ "${USE_UI}" = "yes" ] ; then
        [ -n "${ERR_MSG}" ] && echo "ERROR: ${ERR_MSG}"
        cat <<-EOF
	Usage:
	------------
	    ${0} [-h] [-d] [-t] [-i | -s [-n]] [-z]

	    -h       - Display this help message
	    -d       - Turn on DEBUG mode
	    -t       - Test mode
	    -i       - Request the certificate for "box-${IP}.nip.io" domain
	    -s       - Request the certificate for "www.${IP}.sslip.io" domain
	    -n       - When used with '-i' or '-s' options - do not present any UI
	    -z       - Use ZeroSSL CA instead of LetsEncrypt
	EOF
    fi
}

# Quick checks before we allow bad things to happen
_uid="$( id -u )"
if [ ${_uid} -ne 0 ] ; then
    echo -en "ERROR: Sorry\n'root' credentials required to run this script\n"
    exit 1
fi

if [ -z "${BASH_VERSINFO}" ]; then
    echo "ERROR: You must execute this script with 'bash'"
    exit 1
fi

# Default settings
USE_UI="yes"

# Parse command line
OPTIND=1
PARSE_ERR=0
ERR_MSG=""
NIP="no"
SSLIP="no"
TLD=""
BATCH="no"
#CERTBOT="/usr/bin/certbot"
CERTBOT="certbot"
CERTBOT_SERVER="https://acme-v02.api.letsencrypt.org/directory"
ZEROSSL="no"
while getopts ":hdtisnz" params; do
    case "${params}" in
      d)
          DEBUG="yes"
          continue
          ;;
      i)
          NIP="yes"
          TLD="nip.io"
          continue
          ;;
      s)
          SSLIP="yes"
          TLD="sslip.io"
          continue
          ;;
      n)
          BATCH="yes"
          continue
          ;;
      z)
          ZEROSSL="yes"
          CERTBOT_SERVER="https://acme.zerossl.com/v2/DV90"
          continue
          ;;
      h)
          usage ; exit 0
          ;;
      t)
          RSB_URL="https://installers.rapidseedbox.com/scripts/whmcs_client_installers_test"
          _title_pfx="[TEST] "
#          CERTBOT_OPTS="${CERTBOT_OPTS} --test-cert"
          continue
          ;;
      \?) 
          [ -n "${ERR_MSG}" ] && ERR_MSG="${ERR_MSG}; "
          ERR_MSG="${ERR_MSG}Invalid option: -${OPTARG}"
          PARSE_ERR=1
          ;;
      :)
          [ -n "${ERR_MSG}" ] && ERR_MSG="${ERR_MSG}; "
          ERR_MSG="${ERR_MSG}Option -${OPTARG} requires an argument"
          PARSE_ERR=1
          ;;
    esac
done
if [ ${PARSE_ERR} -ne 0 ] ; then
    usage ; exit 1
fi
shift "$((OPTIND-1))"


MKDIR_TMP || _exit 1
DEV="$( ip route get 8.8.8.8 | grep '8.8.8.8' | sed -e 's/.*dev//' | awk '{print $1}' )"
INPUT="${TMP_DIR}/in"
OUTPUT="${TMP_DIR}/out"
TEXT="${TMP_DIR}/text"
TMPFILE="${TMP_DIR}/tmpfile"


_clean_tmp () {
    [ -e "${TMP_DIR}" ] \
        && RM_DIRS "${TMP_DIR}"
}


_exit () {
    _clean_tmp
    if [ "${BATCH}" != "yes" ] ; then
        clear
        reset
    fi
    exit ${1}
}


# Fetch 'noip2' application if necessary
if ! which noip2 > /dev/null 2>&1 ; then
    RM_FILES "/usr/local/bin/noip2"
    WGET "${RSB_URL}/support/noip/2.1.9-1/noip2-x86_64" \
         "/usr/local/bin/noip2"
    if [ $? -ne 0 ] ; then
         >&2 echo -e "\nError connecting to ${RSB_URL}.\nPlease try again later."
         RM_FILES "/usr/local/bin/noip2"
         _exit 1
    fi
    CHMOD_FILE 755 "/usr/local/bin/noip2"
fi

# TERM dimensions
max_lines="$( tput lines )"
max_columns="$( tput cols )"

# ---------------------------------------------
# Purpose - echo the backtitle string 
#     in form 'Backtitle ... spaces ... [_VER]'
# ---------------------------------------------
_backtitle () {
    local title="${1}"
    local v_len
    local t_len

    title="${_title_pfx}${LSB_VENDOR} ${LSB_RELEASE}: ${1}"

    t_len=$( echo "${title}" | wc -c )
    v_len=$( echo "${_VER}" | wc -c )

    printf "%s%$(( max_columns - t_len - v_len ))s%s" "${title}" " " "${_VER}"
}


_dlg_url_text () {
    echo "\Zu\Z4${1}\Zn"
}

_dlg_btn_text () {
#    # underlined, pink
#    echo "\Zu\Z5${1}\Zn"
    # underlined, red
    echo "\Zu\Z1${1}\Zn"
}

_dlg_code_text () {
    echo "\Zu\Z0${1}\Zn"
}


_info () {
    if ! dialog ${DIALOG_OPTS} \
            --clear \
            --title "  Before we begin ...  " \
            --backtitle "$( _backtitle "${APP_TITLE}" )" \
            --aspect 14 \
            --yes-label "Continue >> " \
            --yesno "$( cat <<-EOFI
		 
		We recommend you to register a free DNS name for your box.
		If you do not have one yet, please visit one of these sites.

		  - $( _dlg_url_text "https://www.noip.com" )
		  - $( _dlg_url_text "https://www.duckdns.org" )
		  - $( _dlg_url_text "https://freedns.afraid.org" )
		  - $( _dlg_url_text "https://www.cloudns.net" )

		Do you want to proceed?
		 
		EOFI
                      )" \
        0 0
    then
       _exit 0
    fi
}


# ---------------------------------------------
# Just a development stub :)
# ---------------------------------------------
_dlg_under_construction () {
    _dialog_msgbox \
         "  *** Under construction ***  " \
         "${1}" \
         "\n    Not implemented yet!    \n\n"
    return 0
}


# ---------------------------------------------
# Prepare environment
# ---------------------------------------------
_prep_env () {
    local _rc

    case "${LSB_VENDOR}" in
      "CentOS")
          # Install required packages
          for _pkg in "dialog" "bind-utils" ; do
              if ! rpm -q "${_pkg}" > /dev/null 2>&1 ; then
                  yum -y install "${_pkg}" 2>&1 | tee -a "${LOGFILE}"
                  _rc=$?
                  [ ${_rc} -ne 0 ] && return ${_rc}
              fi
          done
          ;;
      "Debian"|"Devuan"|"Ubuntu")
          # Install required packages
          for _pkg in "dialog" "dnsutils" ; do
              if ! dpkg -s "${_pkg}" > /dev/null 2>&1 ; then
                  apt-get -y install "${_pkg}" 2>&1 | tee -a "${LOGFILE}"
                  _rc=$?
                  [ ${_rc} -ne 0 ] \
                      && return ${_rc}
              fi
          done
          ;;
      *)
          ERROR "Unsupported distribution: '${LSB_VENDOR} ${LSB_RELEASE}'"
          _exit 1
          ;;
    esac

    if [ -d "/etc/rsb.init.d" ] ; then
        cat <<-'EOF' > "/etc/rsb.init.d/001.rm-certjobs.sh"
	#!/bin/bash

	[ -d "/var/log/rsb" ] || mkdir -p "/var/log/rsb"
	LOGFILE="/var/log/rsbssl/001.rm-certjobs.log"
	. "/usr/local/lib/rsb/functions.sh"

	# Remove wrong SSL certificate cronjobs

	SCRIPTENTRY

	ip_part="$( echo "${IP}" | tr '.' '_' )"

	find /etc/cron.d -name "certbot-*ip_io" \
	  | while read _f ; do
	        if echo "${_f}" | grep -q -E -- "[_-]${ip_part}_" ; then
	            INFO "Keeping '${_f}'"
	        else
	            INFO "Deleting '${_f}'"
	            RM_FILES "${_f}"
	        fi
	    done

	SCRIPTEXIT
	EOF
    fi

    return ${_rc}
}


#
# https://gist.github.com/guessi/82a73ee7eb2b1216eb9db17bb8d65dd1
#
isEmailValid () {
    regex="^([A-Za-z]+[A-Za-z0-9]*((\.|\-|\_)?[A-Za-z]+[A-Za-z0-9]*){1,})@(([A-Za-z]+[A-Za-z0-9]*)+((\.|\-|\_)?([A-Za-z]+[A-Za-z0-9]*)+){1,})+\.([A-Za-z]{2,})+$"
    [[ "${1}" =~ ${regex} ]]
}


#
# https://stackoverflow.com/questions/3183444/check-for-valid-link-url
#
isUrlValid () {
    regex='^(https?|ftp|file)://[-A-Za-z0-9\+&@#/%?=~_|!:,.;]*[-A-Za-z0-9\+&@#/%=~_|]\.[-A-Za-z0-9\+&@#/%?=~_|!:,.;]*[-A-Za-z0-9\+&@#/%=~_|]$'
    [[ "${1}" =~ ${regex} ]]
}


_dialog_msgbox () {
    local t="${1}"                  # box title 
    local b="${2}"                  # backtitle
    local m="${3}"                  # box content
    local bt

    bt="${APP_TITLE}"
    [ -n "${b}" ] && bt="${bt}: ${b}"

    dialog \
        ${DIALOG_OPTS} \
        --aspect 14 \
        --title "${t}" \
        --backtitle "$( _backtitle "${bt}" )" \
        --msgbox "${m}" \
        0 0

    return 0
}


_dialog_infobox () {
    local t="${1}"                  # box title 
    local b="${2}"                  # backtitle
    local m="${3}"                  # box content
    local bt

    bt="${APP_TITLE}"
    [ -n "${b}" ] && bt="${bt}: ${b}"

    dialog \
        ${DIALOG_OPTS} \
        --aspect 14 \
        --title "${t}" \
        --backtitle "$( _backtitle "${bt}" )" \
        --infobox "${m}" \
        0 0

    return 0
}


_dialog_textbox () {
    local t="${1}"                  # box title 
    local b="${2}"                  # backtitle
    local m="${3}"                  # box content
    local bt

    bt="${APP_TITLE}"
    [ -n "${b}" ] && bt="${bt}: ${b}"

    echo -e "${m}" | expand > "${TEXT}"

    dialog \
        ${DIALOG_OPTS} \
        --aspect 14 \
        --title "${t}" \
        --backtitle "$( _backtitle "${bt}" )" \
        --exit-label "OK" \
        --textbox "${TEXT}" \
        0 0

    return 0
}


_check_existing_certificate () {
    local _cert="${1}"
    local _rc=1

    _dialog_infobox \
       "    Please wait...    " \
       "Check certificate for '${_cert}'" \
       "\n  Retrieving certificates info...  \n\n"

    if certbot certificates 2> /dev/null \
           | grep -q -e "^\s*Certificate Name:\s*${_cert}"
    then
        _dialog_msgbox \
            " Error! " \
            "Check if certificate exist for '${_cert}'" \
            "\nCertificate for $( _dlg_url_text "${_cert}" ) found !\n\
Please revoke and delete it first. \n\n"
        _rc=0
    fi

    return ${_rc}
}



# -------------------------------------------------------------
# Configure No-IP Dynamic DNS
# -------------------------------------------------------------

_noip_create_config () {
    local _msg
    local _rc

    password=""
    if [ -e "${RSBSSL_DIR}/noip.cfg" ] ; then
        username="$( head -1 < "${RSBSSL_DIR}/noip.cfg" )"
        password="$( tail -1 < "${RSBSSL_DIR}/noip.cfg" )"
    fi

    while true ; do
        dialog \
           ${DIALOG_OPTS} \
           --title " Enter your No-IP account information " \
           --backtitle "$( _backtitle "${APP_TITLE}: No-IP" )" \
           --help-button \
           --insecure \
           --mixedform "${DIALOG_MIXEDFORM_TEXT}" \
             0 0 0 \
             "Email address :" 1 1 "${username}" 1 20 100 0 0 \
             "Password      :" 2 1 "${password}" 2 20 100 0 1 \
           2> "${INPUT}"
        ret=$?

        [ ${ret} -eq ${DIALOG_CANCEL} -o ${ret} -eq ${DIALOG_ESC} ] \
            &&  return ${ret}

        username="$( head -1 < "${INPUT}" )"
        password="$( tail -1 < "${INPUT}" )"

        if [ -z "${username}" -o -z "${password}" ] ; then
            _dialog_msgbox \
                " Error! " \
                "No-IP" \
                "Required information missing!"
            continue
        fi

        if isEmailValid "${username}" ; then
            break
        else
            _dialog_msgbox \
                " Error! " \
                "No-IP" \
                "${username}: Email address is not valid!"
            INFO "${username}: Email address is not valid!"
            continue
        fi
    done

    _dialog_infobox \
         " Please wait... " \
         "No-IP" \
         "\nRequesting No-IP account confirmation ...\n\n"
    noip2 -C \
          -u "${username}" \
          -p "${password}" \
          -I "${DEV}" \
          -U 30 \
          -Y \
          -d \
       > "${OUTPUT}" 2>&1
    _rc=$?

    if [ ${_rc} -ne 0 ] ; then
        _msg="${username}:${password} ->\n$( cat "${OUTPUT}" )"
        _dialog_textbox \
             " Error: rc == ${_rc} " \
             "No-IP" \
             "${_msg}"
        ERROR "No-IP (create config): ${_msg}"
    else
        noip2
        cat <<-EOF > "/etc/rsb.init.d/noip2.sh"
		#!/bin/bash
		/usr/local/bin/noip2
		EOF
        _msg="${username}:${password} ->\n$( cat "${OUTPUT}" )"
        cat <<-EOF > "${RSBSSL_DIR}/noip.cfg"
	${username}
	${password}
	EOF
        CHMOD_FILE 0600 "${RSBSSL_DIR}/noip.cfg"
        _dialog_textbox \
             " Results: rc == ${_rc} " \
             "No-IP" \
             "${_msg}"
        INFO "No-IP (create config): ${_msg}"
    fi

    return ${_rc}
}

_noip_show_config () {
   local _msg

   if which noip2 > /dev/null 2>&1 ; then
       _msg="\n$( echo -e 'Current No-IP configuration:\n----------------------------' \
                ; noip2 -S 2>&1 | sed -e 's/^\(\s*\)host\s*\(.*\)$/\1host \\Zu\\Z0\2\\Zn/g' )"
   fi
   _dialog_msgbox \
       "  No-IP configuration:  " \
       "No-IP" \
       "${_msg}\n\n"

    return
}

_noip_erase_config () {
    local _i

    dialog \
        ${DIALOG_OPTS} \
        --aspect 14 \
        --defaultno \
        --backtitle "$( _backtitle "${APP_TITLE}: No-IP: Erase configuration" )" \
        --yesno "Are you sure you want to erase current No-IP Dynamic DNS configuration ?" \
        0 0 \
      || return

    _dialog_infobox \
         " Please wait... " \
         "No-IP" \
         "\nKilling existing 'noip2' processes ...\n\n"
    for _i in 1 2 3 4 5 ; do
        killall noip2 > /dev/null 2>&1 && break
    done

    [ -e "/etc/rsb.init.d/noip2.sh" ] \
        && RM_FILES "/etc/rsb.init.d/noip2.sh"
    [ -e "/usr/local/etc/no-ip2.conf" ] \
        && RM_FILES "/usr/local/etc/no-ip2.conf"

    INFO "No-IP (erase config): configuration has been removed."

    return
}

_noip_help_config () {
    local _msg

    _msg="$( cat <<-EOH

	  - Create your No-IP Account:  $( _dlg_url_text "https://www.noip.com/sign-up" )
	  - Confirm your Account (via link in you mail message)
	  - Log in to your Account
	  - Add a Host or Domain to your Account, f.e. $( _dlg_code_text "box.domain.com" )

	$( _dlg_code_text "IMPORTANT NOTE:" )
	Free No-IP Dynamic DNS hostnames must be confirmed every 30 days or they will be deleted.

	$( _dlg_code_text "Details:" )
	$( _dlg_url_text "https://www.noip.com/support/faq/frequently-asked-questions/why-did-my-free-hostname-expire-or-get-deleted/" )

	$( _dlg_code_text "Official 'Getting Started' guide:" )
	$( _dlg_url_text "https://www.noip.com/support/knowledgebase/getting-started-with-no-ip-com/" )
	  
	EOH
       )"

    if which noip2 > /dev/null 2>&1 ; then
        if [ -e "/usr/local/etc/no-ip2.conf" ] ; then
            _msg="${_msg}\n$( echo -e 'Current No-IP configuration:\n----------------------------' \
                     ; noip2 -S 2>&1 | sed -e 's/^\(\s*\)host\s*\(.*\)$/\1host \\Zu\\Z0\2\\Zn/g' )"
        fi
    fi

    _dialog_msgbox \
        "  Getting Started Guide:  " \
        "No-IP" \
        "${_msg} \n \n "

    return 0
}


_conf_noip () {
    local _msg
    local _op
    local _default_op
    local _menu_sh
    local _noip_cnt
    local _ret
    local _rc=0

    if ! which noip2 > /dev/null 2>&1 ; then
        ERROR "'noip2' application not found. Cannot continue."
        return 1
    fi

    _menu_sh="${TMP_DIR}/_conf_noip_menu.sh"
    while true ; do
        _noip_cnt="$( noip2 -S 2>&1 | grep -c -e '\s*host\s*' )"
        cat <<-EOM > "${_menu_sh}"
	  dialog \${DIALOG_OPTS}                                         \\
	       --backtitle "\$( _backtitle "\${APP_TITLE}: No-IP" )"   \\
	       --title " No-IP configuration "                         \\
	       --cancel-label " << Back"                                \\
	       --default-item "\${_default_op}"                        \\
	       --help-button                                           \\
	       --item-help                                             \\
	       --menu "You can use the UP/DOWN arrow keys to choose an option" \\
	       0 0 0 \\
	EOM
        if [ "${_noip_cnt}" = "0" ] ; then
            cat <<-EOM >> "${_menu_sh}"
	         "Create No-IP configuration" "" ""   \\
		EOM
        else
            cat <<-EOM >> "${_menu_sh}"
	         "Show No-IP configuration" "" ""   \\
	         "Erase No-IP configuration" "" ""   \\
		EOM
        fi
        cat <<-EOM >> "${_menu_sh}"
	    2> "\${INPUT}"
	    _ret=\$?
	EOM
        . "${_menu_sh}"

        [ ${_ret} -eq ${DIALOG_CANCEL} -o ${_ret} -eq ${DIALOG_ESC} ] \
            && return ${_ret}

        if [ ${_ret} -eq ${DIALOG_HELP} ] ; then
            _noip_help_config
            continue
        fi

        _op="$( cat "${INPUT}" )"
        _default_op="${_op}"

        case "${_op}" in
          "Create No-IP configuration")
              _noip_create_config
              continue
              ;;
          "Show No-IP configuration")
              _noip_show_config
              continue
              ;;
          "Erase No-IP configuration")
              _noip_erase_config
              continue
              ;;
        esac

    done

    return 0
}




# -------------------------------------------------------------
# Configure DuckDNS Dynamic DNS
# -------------------------------------------------------------

_duckdns_host_operations () {
    local _hostname="${1}"
    local _ret
    local _op
    local _default_op

    while true ; do
        dialog ${DIALOG_OPTS} \
                --backtitle "$( _backtitle "${APP_TITLE}: DuckDNS: '${_hostname}' configuration" )" \
                --title " '${_hostname}' configuration " \
                --cancel-label " << Back" \
                --default-item "${_default_op}" \
                --menu "You can use the UP/DOWN arrow keys to choose an option" \
                0 0 0 \
                "Modify host" "" \
                "Delete host" "" \
           2> "${INPUT}"
        _ret=$?
        [ ${_ret} -eq ${DIALOG_CANCEL} -o ${_ret} -eq ${DIALOG_ESC} ] \
            && return ${_ret}

        _op="$( cat "${INPUT}" )"
        _default_op="${_op}"

        case "${_op}" in
          "Modify host")
              _duckdns_modify_host "${_hostname}"
              continue
              ;;
          "Delete host")
              _duckdns_delete_host "${_hostname}" && break
              continue
              ;;
        esac
    done

    return 0
}

_duckdns_delete_host () {
    local _hostname="${1}"

    dialog \
        ${DIALOG_OPTS} \
        --aspect 14 \
        --defaultno \
        --backtitle "$( _backtitle "${APP_TITLE}: DuckDNS: Delete host" )" \
        --yesno "Are you sure you want to delete DuckDNS Dynamic DNS configuration for '${_hostname}' ?" \
        0 0 \
      || return 1

    _check_existing_certificate "${_hostname}" && return 0

    RM_FILES "/etc/cron.d/duckdns-$( echo "${_hostname}" | tr '.' '_' )"

    INFO "DuckDNS (delete host): '${_hostname}': Dynamic DNS configuration has been deleted."

    return 0
}

_duckdns_modify_host () {
    local _hostname="${1}"
    local _token=""
    local _rc=0

    if [ -e "${RSBSSL_DIR}/duckdns-${_hostname}.cfg" ] ; then
        _token="$( tail -1 < "${RSBSSL_DIR}/duckdns-${_hostname}.cfg" )"
    fi

    _duckdns_add_host "${_hostname}" "${_token}"
    _rc=$?

    INFO "DuckDNS (modify host): '${_hostname}': _rc == ${_rc}"

    return ${_rc}
}

_duckdns_add_host () {
    local _hostname="${1}"
    local _token="${2}"
    local _url
    local _msg
    local _i
    local _rc
    local _ret

    while true ; do
        dialog \
            ${DIALOG_OPTS} \
            --title " Enter you DuckDNS account information " \
            --backtitle "$( _backtitle "${APP_TITLE}: DuckDNS" )" \
            --insecure \
            --mixedform "${DIALOG_MIXEDFORM_TEXT}" \
              0 0 0 \
              "DuckDNS token :" 1 1 "${_token}"    1 20 300 0 0 \
              "Hostname      :" 2 1 "${_hostname}" 2 20 100 0 0 \
            2> "${INPUT}"
        _ret=$?

        [ ${_ret} -eq ${DIALOG_CANCEL} -o ${_ret} -eq ${DIALOG_ESC} ] \
            &&  return ${_ret}

        _token="$(    head -1 < "${INPUT}" )"
        _hostname="$( tail -1 < "${INPUT}" )"

        if [ -z "${_token}" -o -z "${_hostname}" ] ; then
            _dialog_msgbox \
                " Error! " \
                "DuckDNS" \
                "Required information missing!"
            continue
        fi

        break

    done

    _url="https://www.duckdns.org/update?domains=${_hostname}&token=${_token}"
    for _i in $( seq 1 10 ) ; do
        _dialog_infobox \
             " Updating hostname information " \
             "DuckDNS" \
             "\n    Attempt ${_i} / 10    \n    Please wait ...    \n\n"
        curl -s -k "${_url}" > "${OUTPUT}" 2>&1
        _rc=$?
        [ ${_rc} -eq 0 ] && break
        sleep 5
    done

    _msg="$( cat "${OUTPUT}" )"
    if [ "${_msg}" = "OK" ] ; then

        [ -e "/etc/cron.d/duckdns" ] \
            && RM_FILES "/etc/cron.d/duckdns"
        [ -e "/etc/cron.d/duckdns-${_hostname}" ] \
            && RM_FILES "/etc/cron.d/duckdns-${_hostname}"

        cat <<-EOF > "/etc/cron.d/duckdns-$( echo "${_hostname}" | tr '.' '_' )"
	# ${_hostname}
	MAILTO=""
	PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin

	*/30 * * * * root sleep \$( shuf -i 1-120 -n 1 ) ; ( curl -v -k "${_url}" 2>&1 ; echo ) | while IFS= read -r line; do echo "[\$(date -u)]: \${line}"; done | /usr/bin/tee -a ${RSBSSL_LOG_DIR}/duckdns-${_hostname}.log
	EOF

        cat <<-EOF > "${RSBSSL_DIR}/duckdns-${_hostname}.cfg"
	${_hostname}
	${_token}
	EOF

        _msg="${_hostname} registration results\nMessage: ${_msg}"
        _dialog_msgbox \
            " Success " \
            "DuckDNS" \
            "${_msg}"
        INFO "DuckDNS (add host): ${_msg}"

    else

        _msg="${_hostname}: registration error!\nMessage:\n${_msg}"
        _dialog_msgbox \
            " Error! " \
            "DuckDNS" \
            "${_msg}"
        ERROR "DuckDNS (add host): ${_msg}"

    fi

    return 0
}

_duckdns_help_config () {
    local _f
    local _h
    local _msg=""

    for _f in $( ls -1 /etc/cron.d/duckdns-* 2> /dev/null ) ; do
        if [ -z "${_msg}" ] ; then
            _msg="$( \
                echo -ne "\nCurrent DuckDNS configuration:\n------------------------------------ \n " \
                   )"
        fi
        _h="$( head -1 "${_f}" | awk '{print $NF}' )"
        _msg="${_msg} \n\Zu\Z0${_h}\Zn"
    done
    [ -z "${_msg}" ] \
        && _msg=" "  \
        || _msg="${_msg}\n "
    _dialog_msgbox \
        "  Getting Started Guide:  " \
        "DuckDNS" \
        "$( cat <<-EOH

	  - Sign up at - $( _dlg_url_text "https://www.duckdns.org/" )
	  - Add a new domain such as $( _dlg_code_text "yourbox.duckdns.org" )
	  - Get your token (a very long sequence of random characters on the home page)

	$( _dlg_url_text "https://www.duckdns.org" )
	${_msg}
	EOH
        )"

    return 0
}


_conf_duckdns () {
    local _msg=""
    local _menu_arr
    local _f
    local _h
    local _rc=0
    local _ret
    local _default_op
    local _op

    while true ; do

        _menu_arr=()
        _menu_arr+=( "Add new host" "" "" )
        for _f in $( ls -1 /etc/cron.d/duckdns-* 2> /dev/null ) ; do
            _h="$( head -1 "${_f}" | awk '{print $NF}' )"
            _menu_arr+=( "${_h}" "Registered" "" )
        done

        dialog \
            ${DIALOG_OPTS} \
            --backtitle "$( _backtitle "${APP_TITLE}: DuckDNS" )" \
            --title " DuckDNS configuration " \
            --help-button \
            --item-help \
            --cancel-label " << Back" \
            --default-item "${_default_op}" \
            --menu "You can use the UP/DOWN arrow keys to choose an option" \
            0 0 0 \
            "${_menu_arr[@]}" 2> "${INPUT}"
        _ret=$?
        [ ${_ret} -eq ${DIALOG_CANCEL} -o ${_ret} -eq ${DIALOG_ESC} ] \
            && return ${_ret}

        if [ ${_ret} -eq ${DIALOG_HELP} ] ; then
            _duckdns_help_config
            continue
        fi

        _op="$( cat "${INPUT}" )"
        _default_op="${_op}"

        case "${_op}" in
          "Add new host")
              _duckdns_add_host
              continue
              ;;
          *)
              _duckdns_host_operations "${_op}"
              continue
              ;;
        esac

    done

    return ${_rc}
}




# -------------------------------------------------------------
# Configure FreeDNS Dynamic DNS
# -------------------------------------------------------------

_freedns_host_operations () {
    local _hostname="${1}"
    local _ret
    local _op
    local _default_op

    while true ; do
        dialog ${DIALOG_OPTS} \
                --backtitle "$( _backtitle "${APP_TITLE}: FreeDNS: '${_hostname}' configuration" )" \
                --title " '${_hostname}' configuration " \
                --cancel-label " << Back" \
                --default-item "${_default_op}" \
                --menu "You can use the UP/DOWN arrow keys to choose an option" \
                0 0 0 \
                "Modify host" "" \
                "Delete host" "" \
           2> "${INPUT}"
        _ret=$?
        [ ${_ret} -eq ${DIALOG_CANCEL} -o ${_ret} -eq ${DIALOG_ESC} ] \
            && return ${_ret}

        _op="$( cat "${INPUT}" )"
        _default_op="${_op}"

        case "${_op}" in
          "Modify host")
              _freedns_modify_host "${_hostname}"
              continue
              ;;
          "Delete host")
              _freedns_delete_host "${_hostname}" && break
              continue
              ;;
        esac
    done

    return 0
}

_freedns_delete_host () {
    local _hostname="${1}"

    dialog \
        ${DIALOG_OPTS} \
        --aspect 14 \
        --defaultno \
        --backtitle "$( _backtitle "${APP_TITLE}: FreeDNS: Delete host" )" \
        --yesno "Are you sure you want to delete FreeDNS Dynamic DNS configuration for '${_hostname}' ?" \
        0 0 \
      || return 1

    _check_existing_certificate "${_hostname}" && return 0

    RM_FILES "/etc/cron.d/freedns-$( echo "${_hostname}" | tr '.' '_' )"

    INFO "FreeDNS (delete host): '${_hostname}': Dynamic DNS configuration has been deleted."

    return 0
}

_freedns_modify_host () {
    local _hostname="${1}"
    local _direct_url=""
    local _rc=0

    if [ -e "${RSBSSL_DIR}/freedns-${_hostname}.cfg" ] ; then
        _direct_url="$( tail -1 < "${RSBSSL_DIR}/freedns-${_hostname}.cfg" )"
    fi

    _freedns_add_host "${_hostname}" "${_direct_url}"
    _rc=$?

    INFO "FreeDNS (modify host): '${_hostname}': _rc == ${_rc}"

    return ${_rc}
}

_freedns_add_host () {
    local _hostname="${1}"
    local _direct_url="${2}"
    local _url
    local _msg
    local _i
    local _rc
    local _ret

    while true ; do
        dialog \
            ${DIALOG_OPTS} \
            --title " Enter you FreeDNS account information " \
            --backtitle "$( _backtitle "${APP_TITLE}: FreeDNS" )" \
            --insecure \
            --mixedform "${DIALOG_MIXEDFORM_TEXT}" \
              0 0 0 \
              "DirectURL :" 1 1 "${_direct_url}"    1 20 300 0 0 \
              "Hostname  :" 2 1 "${_hostname}"      2 20 100 0 0 \
            2> "${INPUT}"
        _ret=$?

        [ ${_ret} -eq ${DIALOG_CANCEL} -o ${_ret} -eq ${DIALOG_ESC} ] \
            &&  return ${_ret}

        _direct_url="$(  head -1 < "${INPUT}" )"
        _hostname="$(    tail -1 < "${INPUT}" )"

        if [ -z "${_direct_url}" -o -z "${_hostname}" ] ; then
            _dialog_msgbox \
                " Error! " \
                "FreeDNS" \
                "Required information missing!"
            continue
        fi

        break

    done

    for _i in $( seq 1 10 ) ; do
        _dialog_infobox \
             " Updating hostname information " \
             "FreeDNS" \
             "\n    Attempt ${_i} / 10    \n    Please wait ...    \n\n"
        curl -s -k "${_direct_url}" > "${OUTPUT}" 2>&1
        _rc=$?
        [ ${_rc} -eq 0 ] && break
        sleep 5
    done

    if [ ${_rc} -eq 0 ] ; then
        [ -e "/etc/cron.d/freedns" ] \
            && RM_FILES "/etc/cron.d/freedns"
        [ -e "/etc/cron.d/freedns-${_hostname}" ] \
            && RM_FILES "/etc/cron.d/freedns-${_hostname}"

        cat <<-EOF > "/etc/cron.d/freedns-$( echo "${_hostname}" | tr '.' '_' )"
	# ${_hostname}
	MAILTO=""
	PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin

	*/30 * * * * root sleep \$( shuf -i 1-120 -n 1 ) ; ( curl -s -k "${_direct_url}" ; echo ) | while IFS= read -r line; do echo "[\$(date -u)]: \${line}"; done | /usr/bin/tee -a ${RSBSSL_LOG_DIR}/freedns-${_hostname}.log
	EOF

        cat <<-EOF > "${RSBSSL_DIR}/freedns-${_hostname}.cfg"
	${_hostname}
	${_direct_url}
	EOF

        _msg="${_hostname}\nMessage:\n$( cat "${OUTPUT}" )"
        _dialog_msgbox \
            " Results " \
            "FreeDNS" \
            "${_msg}"
        INFO "FreeDNS (add host): ${_msg}"

    else

        _msg="${_hostname}\nMessage:\n$( cat "${OUTPUT}" )"
        _dialog_msgbox \
            " Results " \
            "FreeDNS" \
            "${_msg}"
        ERROR "FreeDNS (add host): ${_msg}"

    fi

    return 0
}

_freedns_help_config () {
    local _f
    local _h
    local _msg=""

    for _f in $( ls -1 /etc/cron.d/freedns-* 2> /dev/null ) ; do
        if [ -z "${_msg}" ] ; then
            _msg="$( \
                echo -ne "\nCurrent FreeDNS configuration:\n------------------------------------ \n " \
                   )"
        fi
        _h="$( head -1 "${_f}" | awk '{print $NF}' )"
        _msg="${_msg} \n\Zu\Z0${_h}\Zn"
    done
    [ -z "${_msg}" ] \
        && _msg=" "  \
        || _msg="${_msg}\n "
    _dialog_msgbox \
        "  Getting Started Guide:  " \
        "FreeDNS" \
        "$( cat <<-EOH

	  - Sign up at - $( _dlg_url_text "https://freedns.afraid.org/pricing/" )
	  - Select $( _dlg_btn_text "Starter" ) plan
	  - Check mailbox and activate your account via provided link
	  - Click $( _dlg_btn_text "Add a subdomain" )
	  - Choose a subdomain according to your taste :)
	  - Go to $( _dlg_btn_text "Dynamic DNS" ) menu
	  - We will need hostname and Direct URL to enter the correct data in $( _dlg_code_text "ssl-mgr" )

	$( _dlg_url_text "https://freedns.afraid.org" )
	${_msg}
	EOH
        )"

    return 0
}


_conf_freedns () {
    local _msg=""
    local _menu_arr
    local _f
    local _h
    local _rc=0
    local _ret
    local _default_op
    local _op

    while true ; do

        _menu_arr=()
        _menu_arr+=( "Add new host" "" "" )
        for _f in $( ls -1 /etc/cron.d/freedns-* 2> /dev/null ) ; do
            _h="$( head -1 "${_f}" | awk '{print $NF}' )"
            _menu_arr+=( "${_h}" "Registered" "" )
        done

        dialog \
            ${DIALOG_OPTS} \
            --backtitle "$( _backtitle "${APP_TITLE}: FreeDNS" )" \
            --title " FreeDNS configuration " \
            --help-button \
            --item-help \
            --cancel-label " << Back" \
            --default-item "${_default_op}" \
            --menu "You can use the UP/DOWN arrow keys to choose an option" \
            0 0 0 \
            "${_menu_arr[@]}" 2> "${INPUT}"
        _ret=$?
        [ ${_ret} -eq ${DIALOG_CANCEL} -o ${_ret} -eq ${DIALOG_ESC} ] \
            && return ${_ret}

        if [ ${_ret} -eq ${DIALOG_HELP} ] ; then
            _freedns_help_config
            continue
        fi

        _op="$( cat "${INPUT}" )"
        _default_op="${_op}"

        case "${_op}" in
          "Add new host")
              _freedns_add_host
              continue
              ;;
          *)
              _freedns_host_operations "${_op}"
              continue
              ;;
        esac

    done

    return ${_rc}
}




# -------------------------------------------------------------
# Configure ClouDNS Dynamic DNS
#   - https://www.cloudns.net/wiki/article/36/
# -------------------------------------------------------------

_cloudns_host_operations () {
    local _hostname="${1}"
    local _ret
    local _op
    local _default_op

    while true ; do
        dialog ${DIALOG_OPTS} \
                --backtitle "$( _backtitle "${APP_TITLE}: ClouDNS: '${_hostname}' configuration" )" \
                --title " '${_hostname}' configuration " \
                --cancel-label " << Back" \
                --default-item "${_default_op}" \
                --menu "You can use the UP/DOWN arrow keys to choose an option" \
                0 0 0 \
                "Modify host" "" \
                "Delete host" "" \
           2> "${INPUT}"
        _ret=$?
        [ ${_ret} -eq ${DIALOG_CANCEL} -o ${_ret} -eq ${DIALOG_ESC} ] \
            && return ${_ret}

        _op="$( cat "${INPUT}" )"
        _default_op="${_op}"

        case "${_op}" in
          "Modify host")
              _cloudns_modify_host "${_hostname}"
              continue
              ;;
          "Delete host")
              _cloudns_delete_host "${_hostname}" && break
              continue
              ;;
        esac
    done

    return 0
}

_cloudns_delete_host () {
    local _hostname="${1}"

    dialog \
        ${DIALOG_OPTS} \
        --aspect 14 \
        --defaultno \
        --backtitle "$( _backtitle "${APP_TITLE}: ClouDNS: Delete host" )" \
        --yesno "Are you sure you want to delete ClouDNS Dynamic DNS configuration for '${_hostname}' ?" \
        0 0 \
      || return 1

    _check_existing_certificate "${_hostname}" && return 0

    RM_FILES "/etc/cron.d/cloudns-$( echo "${_hostname}" | tr '.' '_' )"

    INFO "ClouDNS (delete host): '${_hostname}': Dynamic DNS configuration has been deleted."

    return 0
}

_cloudns_modify_host () {
    local _hostname="${1}"
    local _dynamic_url=""
    local _rc=0

    if [ -e "${RSBSSL_DIR}/cloudns-${_hostname}.cfg" ] ; then
        _dynamic_url="$( tail -1 < "${RSBSSL_DIR}/cloudns-${_hostname}.cfg" )"
    fi

    _cloudns_add_host "${_hostname}" "${_dynamic_url}"
    _rc=$?

    INFO "ClouDNS (modify host): '${_hostname}': _rc == ${_rc}"

    return ${_rc}
}

_cloudns_add_host () {
    local _hostname="${1}"
    local _dynamic_url="${2}"
    local _url
    local _msg
    local _i
    local _rc
    local _ret

    while true ; do
        dialog \
            ${DIALOG_OPTS} \
            --title " Enter you ClouDNS account information " \
            --backtitle "$( _backtitle "${APP_TITLE}: ClouDNS" )" \
            --insecure \
            --mixedform "${DIALOG_MIXEDFORM_TEXT}" \
              0 0 0 \
              "Dynamic URL :" 1 1 "${_dynamic_url}"   1 20 300 0 0 \
              "Hostname    :" 2 1 "${_hostname}"      2 20 100 0 0 \
            2> "${INPUT}"
        _ret=$?

        [ ${_ret} -eq ${DIALOG_CANCEL} -o ${_ret} -eq ${DIALOG_ESC} ] \
            &&  return ${_ret}

        _dynamic_url="$(  head -1 < "${INPUT}" )"
        _hostname="$(    tail -1 < "${INPUT}" )"

        if [ -z "${_dynamic_url}" -o -z "${_hostname}" ] ; then
            _dialog_msgbox \
                " Error! " \
                "ClouDNS" \
                "Required information missing!"
            continue
        fi

        break

    done

    for _i in $( seq 1 10 ) ; do
        _dialog_infobox \
             " Updating hostname information " \
             "ClouDNS" \
             "\n    Attempt ${i} / 10    \n    Please wait ...    \n\n"
        curl -s -k "${_dynamic_url}" > "${OUTPUT}" 2>&1
        _rc=$?
        [ ${_rc} -eq 0 ] && break
        sleep 5
    done

    _msg="$( cat "${OUTPUT}" )"
    if [ "${_msg}" = "OK" ] ; then

        [ -e "/etc/cron.d/cloudns" ] \
            && RM_FILES "/etc/cron.d/cloudns"
        [ -e "/etc/cron.d/cloudns-${_hostname}" ] \
            && RM_FILES "/etc/cron.d/cloudns-${_hostname}"

        cat <<-EOF > "/etc/cron.d/cloudns-$( echo "${_hostname}" | tr '.' '_' )"
	# ${_hostname}
	MAILTO=""
	PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin

	*/30 * * * * root sleep \$( shuf -i 1-120 -n 1 ) ; ( curl -s -k "${_dynamic_url}" ; echo ) | while IFS= read -r line; do echo "[\$(date -u)]: \${line}"; done | /usr/bin/tee -a ${RSBSSL_LOG_DIR}/cloudns-${_hostname}.log
	EOF

        cat <<-EOF > "${RSBSSL_DIR}/cloudns-${_hostname}.cfg"
	${_hostname}
	${_dynamic_url}
	EOF

        _msg="${_hostname}\nMessage:\n$( cat "${OUTPUT}" )"
        _dialog_msgbox \
            " Results " \
            "ClouDNS" \
            "${_msg}"
        INFO "ClouDNS (add host): ${_msg}"

    else

        _msg="${_hostname}\nMessage:\n$( cat "${OUTPUT}" )"
        _dialog_msgbox \
            " Results " \
            "ClouDNS" \
            "${_msg}"
        ERROR "ClouDNS (add host): ${_msg}"

    fi

    return 0
}

_cloudns_help_config () {
    local _f
    local _h
    local _msg=""

    for _f in $( ls -1 /etc/cron.d/cloudns-* 2> /dev/null ) ; do
        if [ -z "${_msg}" ] ; then
            _msg="$( \
                echo -ne "\nCurrent ClouDNS configuration:\n------------------------------------ \n " \
                   )"
        fi
        _h="$( head -1 "${_f}" | awk '{print $NF}' )"
        _msg="${_msg} \n\Zu\Z0${_h}\Zn"
    done
    [ -z "${_msg}" ] \
        && _msg=" "  \
        || _msg="${_msg}\n "
    _dialog_msgbox \
        "  Getting Started Guide:  " \
        "ClouDNS" \
        "$( cat <<-EOH

	  - Sign up at - $( _dlg_url_text "https://www.cloudns.net/" )
	  - Select $( _dlg_btn_text "SIGN UP FOR FREE" )
	  - Enter account information and click $( _dlg_btn_text "SIGN UP" )
	  - Check your mailbox and navigate to the activation URL
	  - Click $( _dlg_btn_text "ACTIVATE" )
	  - In the $( _dlg_btn_text "DNS Zones" ) section click $( _dlg_btn_text "Add new" )
	  - Click $( _dlg_btn_text "Free zones" )
	  - Enter $( _dlg_btn_text "Domain name" ) and click $( _dlg_btn_text "REGISTER" )
	  - Go to the $( _dlg_btn_text "A" ) record tab
	  - Click $( _dlg_btn_text "Add new record" )
	  - Leave $( _dlg_btn_text "Host" ) empty
	    enter some random IP address ($( _dlg_code_text "1.1.1.1" ) should be fine)
	    in $( _dlg_btn_text "Points to" ) field
	    Click $( _dlg_btn_text "SAVE" )

	  - Read $( _dlg_url_text "https://www.cloudns.net/wiki/article/36/" )
	    to learn how to activate $( _dlg_btn_text "Dynamic DNS" ) feature for your hostname


	$( _dlg_url_text "https://www.cloudns.net/wiki/article/36/" )
	${_msg}
	EOH
     )"

    return 0
}


_conf_cloudns () {
    local _msg=""
    local _menu_arr
    local _f
    local _h
    local _rc=0
    local _ret
    local _default_op
    local _op

    while true ; do

        _menu_arr=()
        _menu_arr+=( "Add new host" "" "" )
        for _f in $( ls -1 /etc/cron.d/cloudns-* 2> /dev/null ) ; do
            _h="$( head -1 "${_f}" | awk '{print $NF}' )"
            _menu_arr+=( "${_h}" "Registered" "" )
        done

        dialog \
            ${DIALOG_OPTS} \
            --backtitle "$( _backtitle "${APP_TITLE}: ClouDNS" )" \
            --title " ClouDNS configuration " \
            --help-button \
            --item-help \
            --cancel-label " << Back" \
            --default-item "${_default_op}" \
            --menu "You can use the UP/DOWN arrow keys to choose an option" \
            0 0 0 \
            "${_menu_arr[@]}" 2> "${INPUT}"
        _ret=$?
        [ ${_ret} -eq ${DIALOG_CANCEL} -o ${_ret} -eq ${DIALOG_ESC} ] \
            && return ${_ret}

        if [ ${_ret} -eq ${DIALOG_HELP} ] ; then
            _cloudns_help_config
            continue
        fi

        _op="$( cat "${INPUT}" )"
        _default_op="${_op}"

        case "${_op}" in
          "Add new host")
              _cloudns_add_host
              continue
              ;;
          *)
              _cloudns_host_operations "${_op}"
              continue
              ;;
        esac

    done

    return ${_rc}
}





# -------------------------------------------------------------
# Install Certbot
# -------------------------------------------------------------
_check_certbot () {
    local _rc=0
    local _msg

    # Remove `certbot-auto`
    # Rumor has they dropped support for it
    [ -e "/usr/local/bin/certbot-auto" ] \
        && RM_FILES "/usr/local/bin/certbot-auto"
    [ -d "/opt/eff.org/certbot" ] \
        && RM_DIRS "/opt/eff.org/certbot"

    # -- Remove standard `certbot` package on some distributions -- #
    case "${LSB_VENDOR}" in
      "Debian"|"Ubuntu")
          DPKG_CHECK "certbot" \
              && APT_REMOVE "certbot"
          ;;
    esac

    if ! which certbot > /dev/null 2>&1 ; then
        if [ "${BATCH}" != "yes" ] ; then
            _dialog_infobox \
                 "    Installing CertBot    " \
                 "CertBot" \
                 "\n      Please wait...     \n\n"
        fi
        while true ; do
            _rc=1
            case "${LSB_VENDOR}" in
              "Ubuntu")
                  case "${LSB_VER}" in
                    "Ubuntu20.04"|"Ubuntu22.04")
                        ;;
                    *)
                        if [ -e "/etc/apt/sources.list.d/certbot-ubuntu-certbot-${LSB_CODENAME}.list" ]
                        then
                            DEL_APT_REPO "ppa:certbot/certbot"
                            RUN_LOG \
                              "rm -vf /etc/apt/sources.list.d/certbot-ubuntu-certbot-${LSB_CODENAME}.list* 2&>1"
                            APT_UPDATE || break
                            APT_AUTOREMOVE || break
                        fi
                        ;;
                  esac
                  ;;
            esac
            RUN_LOG "python3 -m pip install --upgrade pip 2>&1" || break
            RUN_LOG "pip3 install --upgrade setuptools 2>&1" || break
            RUN_LOG "pip3 install --force-reinstall --upgrade certbot 2>&1" || break
            _rc=0
            break
        done
        if [ ${_rc} -ne 0 ] ; then
            _msg="ERROR ->\n$( cat "${OUTPUT}" )"
            if [ "${BATCH}" != "yes" ] ; then
                _dialog_textbox \
                     " Installation Error: rc == ${_rc} " \
                     "CertBot" \
                     "${_msg}"
            fi
            ERROR "CertBot install: ${_msg}"
        else
            _msg="Success ->\n$( cat "${OUTPUT}" )"
            INFO "CertBot install: ${_msg}"
        fi
    fi

    return ${_rc}
}


# -------------------------------------------------------------
# Test hostname in DNS
# -------------------------------------------------------------
_test_dns () {
    local _hostname="${hostname}"
    local _nameserver="8.8.8.8"
    local ret

    while true ; do

        dialog \
           ${DIALOG_OPTS} \
           --title "Enter you DuckDNS account information" \
           --backtitle "$( _backtitle "${APP_TITLE}: Test DNS" )" \
           --ok-label "Test" \
           --cancel-label " << Back" \
           --insecure \
           --mixedform "${DIALOG_MIXEDFORM_TEXT}" \
             0 0 0 \
             "Hostname    :" 1 1 "${_hostname}"   1 20 100 0 0 \
             "Name server :" 2 1 "${_nameserver}" 2 20 100 0 0 \
               2> "${INPUT}"
        ret=$?
        [ ${ret} -eq ${DIALOG_CANCEL} -o ${ret} -eq ${DIALOG_ESC} ] && break

        _hostname="$(   head -1 < "${INPUT}" )"
        _nameserver="$( tail -1 < "${INPUT}" )"

        [ -z "${_nameserver}" ] && _nameserver="8.8.8.8"

        if [ -z "${_hostname}" ] ; then
            _dialog_msgbox \
                " Error! " \
                "Test DNS" \
                "Required information missing!"
            continue
        fi

        dig @${_nameserver} "${_hostname}" > "${TMPFILE}" 2>&1
        _dialog_textbox \
            " Test results " \
            "Test DNS" \
            "$( cat "${TMPFILE}" )"

    done

    return 0
}


# -------------------------------------------------------------
# Check if apache is listening on tcp/80
# -------------------------------------------------------------
_apache_check_tcp80 () {
    local _need_apache_reload="no"

    if [ ! -e "/etc/apache2/conf-available/port-80.conf" ] ; then
        sed -i -e 's|^\s*\(Listen 80\)\s*$|#\1|' "/etc/apache2/ports.conf"
        cat <<-EOF > "/etc/apache2/conf-available/port-80.conf"
	Listen 80
	EOF
        _need_apache_reload="yes"
    elif [ ! -e "/etc/apache2/conf-enabled/port-80.conf" ] ; then
        _need_apache_reload="yes"
    fi
    if [ "${_need_apache_reload}" != "no" ] ; then
        a2enconf "port-80.conf" > /dev/null 2>&1
        service apache2 reload
    fi
}

# -------------------------------------------------------------
# Add Apache2 Virtual host
# -------------------------------------------------------------
_apache_add_vhost () {
    local _vhost="${1}"
    local _vhost_conf
    local _vhost444_conf
    local _msg
    local _rc
    local _rc1
    local _rc2
    local _rc3

    [ -z "${_vhost}" ] && return 255

    [ -e "${TMPFILE}" ] && RM_FILES "${TMPFILE}"

    _vhost_conf="rsbssl_${_vhost}.conf"
    _vhost444_conf="rsbssl_${_vhost}-444.conf"

    if apachectl -M 2> /dev/null | grep -q -e '^\s*ssl_module\s*' ; then
        INFO "Apache Add vHost: 'ssl_module' is already loaded"
    else
        INFO "Apache Add vHost: enabling 'ssl_module'"
        a2enmod ssl >> "${LOGFILE}" 2>&1
        _rc=$?
        INFO "_rc: [${_rc}]"
        a2enmod proxy_wstunnel >> "${LOGFILE}" 2>&1
        _rc=$?
        INFO "_rc: [${_rc}]"
        INFO "Apache Add vHost: restarting 'apache2' service"
        service apache2 restart >> "${LOGFILE}" 2>&1
        _rc=$?
        INFO "_rc: [${_rc}]"
    fi

    INFO "Apache Add vHost: adding '/etc/apache2/sites-available/${_vhost_conf}'"
    cat <<-EOF > "/etc/apache2/sites-available/${_vhost_conf}"
	# ------------------------------------------------------------
	# Version: ${_VER}
	#
	# !!! PLEASE DON'T CHANGE THIS FILE !!!
	#
	# -- Your RSB team :)
	#
	# ------------------------------------------------------------
	<VirtualHost *:80>

	    ServerName ${_vhost}

	    # This VirtualHost redirects everything to HTTPS on port 443.
	    RewriteEngine On
	    RewriteCond %{HTTPS} off
	    RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI}

	</VirtualHost>


	<VirtualHost *:443>

	    ServerName ${_vhost}
	    ServerAdmin webmaster@localhost
	    DocumentRoot /var/www/html

	    ErrorLog \${APACHE_LOG_DIR}/error.log
	    #CustomLog \${APACHE_LOG_DIR}/access.log combined
	    CustomLog /dev/null combined

	    SSLEngine on
	    SSLCertificateFile /etc/letsencrypt/live/${_vhost}/fullchain.pem
	    SSLCertificateKeyFile /etc/letsencrypt/live/${_vhost}/privkey.pem

	    <FilesMatch "\.(cgi|shtml|phtml|php)$">
	        SSLOptions +StdEnvVars
	    </FilesMatch>

	    <Directory /usr/lib/cgi-bin>
	        SSLOptions +StdEnvVars
	    </Directory>

	    <IfModule mod_userdir.c>
	        UserDir /home/*
	        UserDir disabled root

	        HeaderName HEADER.html
	        ReadmeName README.html

	        <Directory /home/*>
	            AllowOverride FileInfo AuthConfig Limit Indexes
	            Options MultiViews Indexes SymLinksIfOwnerMatch IncludesNoExec
	#            php_flag engine off
	            <Limit GET POST OPTIONS>
	                Require all granted
	            </Limit>
	            <LimitExcept GET POST OPTIONS>
	                Require all denied
	            </LimitExcept>
	        </Directory>
	    </IfModule>

	    <IfModule mod_proxy.c>
	        ProxyRequests off
	        ProxyPreserveHost On

	        RewriteEngine on
	        RewriteRule ^/rutorrent$ /rutorrent/ [R]

	        ProxyPass /rutorrent http://127.0.0.1:8080/
	        ProxyPassReverse /rutorrent http://127.0.0.1:8080/
	    </IfModule>

	    IncludeOptional userconf-enabled/*.conf

	</VirtualHost>
	EOF

    # --- Configure plex URL if needed --- #
    if id plex > /dev/null 2>&1 ; then
        cat <<-EOF > "/etc/apache2/sites-available/${_vhost444_conf}"
	# ------------------------------------------------------------
	# Version: ${_VER}
	#
	# !!! PLEASE DON'T CHANGE THIS FILE !!!
	#
	# -- Your RSB team :)
	#
	# ------------------------------------------------------------
	<VirtualHost *:444>

	    ServerName ${_vhost}
	    ServerAdmin webmaster@localhost
	    DocumentRoot /var/www/html

	    ErrorLog \${APACHE_LOG_DIR}/error.log
	    #CustomLog \${APACHE_LOG_DIR}/access.log combined
	    CustomLog /dev/null combined

	    SSLEngine on
	    SSLCertificateFile /etc/letsencrypt/live/${_vhost}/fullchain.pem
	    SSLCertificateKeyFile /etc/letsencrypt/live/${_vhost}/privkey.pem

	    <FilesMatch "\.(cgi|shtml|phtml|php)$">
	        SSLOptions +StdEnvVars
	    </FilesMatch>

	    <IfModule mod_proxy.c>

	        <Proxy *>
	            Order deny,allow
	            Allow from all
	        </Proxy>

	        ProxyRequests off
	        ProxyPreserveHost On

	        ProxyPass / http://127.0.0.1:32400/
	        ProxyPassReverse / http://127.0.0.1:32400/

	        ProxyPass /:/ ws://127.0.0.1:32400/:/
	        ProxyPassReverse /:/ ws://127.0.0.1:32400/:/

	        ProxyPass /:/ wss://127.0.0.1:32400/:/
	        ProxyPassReverse /:/ wss://127.0.0.1:32400/:/

	#        <Location /:/websockets/notifications>
	#            ProxyPass wss://127.0.0.1:32400/:/
	#            ProxyPassReverse wss://127.0.0.1:32400/
	#        </Location>

	    </IfModule>

	</VirtualHost>
	EOF
        cat <<-EOF > "/etc/apache2/conf-available/port-444.conf"
	Listen 444
	EOF
        a2enconf "port-444.conf" > /dev/null 2>&1
    fi

    [ -e "/etc/apache2/userconf-enabled/plex.conf" ] \
        && RM_FILES "/etc/apache2/userconf-enabled/plex.conf"

    apachectl -t > /dev/null 2>&1
    _rc=$?
    if [ ${_rc} -ne 0 ] ; then
        _msg="${_vhost}\n\nMessage:\n$( cat "${OUTPUT}" )"
        _dialog_textbox \
            " Error " \
            "Apache2 Virtual Host Creation" \
            "${_msg}"
        ERROR "Apache config test: ${_msg}"
    else
        echo "apachectl -t: Syntax OK" >> "${OUTPUT}"
        a2ensite "${_vhost_conf}" > "${TMPFILE}" 2>&1
        _rc1=$?
        _rc2=0
        if [ -e "/etc/apache2/sites-available/${_vhost444_conf}" ] ; then
            a2ensite "${_vhost444_conf}"  >> "${TMPFILE}" 2>&1
            _rc2=$?
        fi
        service apache2 reload >> "${TMPFILE}" 2>&1
        _rc3=$? 
        if [ ${_rc1} -eq 0 -a ${_rc2} -eq 0 -a ${_rc3} -eq 0 ] ; then
            INFO  "Apache reload: Success\n$( cat "${TMPFILE}" )"
        else
            ERROR "Apache reload: ERROR\n$( cat "${TMPFILE}" )"
            _rc="$(( _rc1 + _rc2 + _rc3 ))"
        fi
    fi

    return ${_rc}
}



# -------------------------------------------------------------
# Certificates post-configuration steps for certificate
#    request / renew operations
#
#  cronjobs
#  Apache virtual host
#  etc
#  ...
# -------------------------------------------------------------
_certificate_add_postconf () {
    local _cert="${1}"
    local _username="${2}"
    local _f
    local h1
    local h2
    local h3
    local h4

    [ -e "/etc/cron.d/certbot" ] \
        && RM_FILES "/etc/cron.d/certbot"
    [ -e "/etc/cron.d/certbot-${_cert}" ] \
        && RM_FILES "/etc/cron.d/certbot-${_cert}"

    h1="$(( RANDOM % 12 ))"
    h2="$(( ( h1 + 6 )  % 24 ))"
    h3="$(( ( h1 + 12 ) % 24 ))"
    h4="$(( ( h1 + 18 ) % 24 ))"
    _minute="$(( RANDOM % 60 ))"

    cat <<-EOF > "/etc/cron.d/certbot-$( echo "${_cert}" | tr '.' '_' )"
	# ${_cert} ${_username}
	MAILTO=""
	PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin

	${_minute} ${h1},${h2},${h3},${h4} * * * root sleep \$( shuf -i 1-300 -n 1 ) ; ${CERTBOT} renew --webroot --webroot-path /var/www/html --cert-name "${_cert}" --server "${CERTBOT_SERVER}" --post-hook "systemctl is-active apache2 > /dev/null 2>&1 && systemctl reload apache2" 2>&1 | while IFS= read -r line; do echo "[\$(date -u)]: \${line}"; done | /usr/bin/tee -a  ${RSBSSL_LOG_DIR}/certbot-${_cert}.log
	EOF

    cat <<-EOF > "${RSBSSL_DIR}/certbot-${_cert}.cfg"
	${_cert}
	${_username}
	EOF

    # (Re)create Apache2 virtual host:
    _apache_add_vhost "${_cert}"

    # We'll not touch "/etc/ssl/private/rsb.pem"
    # -- so, let's remove some unnedded stuff if any
    for _f in \
            "/etc/cron.d/rsbssl-rsb-cert-regen" \
            "/usr/local/sbin/rsbssl-rsb-cert-regen.sh" \
    ; do
        [ -e "${_f}" ] \
            && RM_FILES "${_f}"
    done

    return 0
}


# -------------------------------------------------------------
# Rebuild '${TMP_DIR}/*.info' files
# -------------------------------------------------------------
_certificates_refresh_info () {
    local _cert
    local _cert_idx=0
    local _cert_cnt

    rm -f ${TMP_DIR}/*.info > /dev/null 2>&1

    _dialog_infobox \
       "    Please wait...    " \
       "Manage certificates" \
       "\n  Retrieving certificates...  \n\n"

    _cert_cnt="$( ${CERTBOT} certificates 2>> "${LOGFILE}" | grep -c "Certificate Name:" )"

    for _cert in \
         $( ${CERTBOT} certificates 2>> "${LOGFILE}" \
               | grep "Certificate Name:" \
               | awk '{print $NF}' )
    do

        _cert_idx="$(( _cert_idx + 1 ))"

        cat <<-EOF
	XXX
	$(( 100 * _cert_idx / _cert_cnt ))
	Retrieving certificates information...
	XXX
	EOF

        ${CERTBOT} certificates -d "${_cert}" 2>> "${LOGFILE}" \
                | grep -v -e '^$' \
            > "${TMP_DIR}/${_cert}.info"

    done \
           | dialog ${DIALOG_OPTS} \
               --backtitle "$( _backtitle "${APP_TITLE}: Refresh certificates information" )" \
               --gauge " Please wait " \
               7 50

    return 0
}


# -------------------------------------------------------------
# Request LetsEncrypt certificate
# Usage:
#      _certificate_request ["fqdn.tld" ["e-mail"]]
# -------------------------------------------------------------
_certificate_request () {
    local data
    local _rc=0
    local _ip
    local _hostname="${1}"
    local _username="${2}"
    local _minute
    local _msg
    local _certbot_extra_opts=""

    while [ -z "${_hostname}" -o -z "${_username}" -a "${BATCH}" != "yes" ] ; do

        exec 3>&1
        data="$( dialog \
                   ${DIALOG_OPTS} \
                   --title "Enter you account information" \
                   --backtitle "$( _backtitle "${APP_TITLE}: Request SSL certificate" )" \
                   --cancel-label " << Back" \
                   --insecure \
                   --mixedform "You can use the UP/DOWN arrow keys to navigate between fields.\n\
Use TAB key to move to the buttons section\n\
Press ENTER when done or ESC to cancel." \
                           0 0 0 \
                           "Email address :" 1 1 "${_username}" 1 20 100 0 0 \
                           "Hostname      :" 2 1 "${_hostname}" 2 20 100 0 0 \
        2>&1 1>&3 )"
        exec 3>&-

        [ -z "${data}" ] && return 1

        _username="$( echo "${data}" | head -1 )"
        _hostname="$( echo "${data}" | tail -1 )"

        if [ -n "${_hostname}" -a -n "${_username}" ] ; then
            if isEmailValid "${_username}" ; then
                break
            else
                _dialog_msgbox \
                    "  Error!  " \
                    "Request SSL certificate" \
                    "${_username}: Email address is not valid!"
                _username=""
                continue
            fi
        else
            _dialog_msgbox \
                "  Error!  " \
                "Request SSL certificate" \
                "Both 'Email address' and 'Hostname' should be entered!"
            continue
        fi

    done
    hostname="${_hostname}"
    username="${_username}"

    _ip="$( dig +short a "${hostname}" )"
    if [ -z "${_ip}" ] ; then
        if [ "${BATCH}" != "yes" ] ; then
            _dialog_msgbox \
                "  Error!  " \
                "Request SSL certificate" \
                "\n${hostname}: Not registered in DNS.\n\
If you just registered this hostname please wait for the DNS \
propagation and retry the operation.\n\n"
        fi
        return 3
    elif [ "${_ip}" != "${IP}" ] ; then
        if [ "${BATCH}" != "yes" ] ; then
            _dialog_msgbox \
                "  Error!  " \
                "Request SSL certificate" \
                "\n${hostname}: Registered in DNS with IP address ${_ip}\n\
This box IP address is ${IP}\n\
Please fix this issue and retry the operation.\n\n"
        fi
        return 3
    fi

    _check_certbot || return 1

    if [ "${BATCH}" != "yes" ] ; then
        _dialog_infobox \
             "  Request SSL certificate  " \
             "Registering '${hostname}'" \
             "\n  Please wait ...  \n\n"
    fi

    # Request new certificate
    [ "${ZEROSSL}" = "yes" ] \
        && _certbot_extra_opts="$( curl -s https://api.zerossl.com/acme/eab-credentials-email --data "email=${username}" | jq -r '"--eab-kid " + .eab_kid + " --eab-hmac-key " + .eab_hmac_key' )"
    ${CERTBOT} \
          -vvv \
          -n \
          certonly \
          ${CERTBOT_OPTS} \
          ${_certbot_extra_opts} \
          --webroot \
          --webroot-path "/var/www/html" \
          -m "${username}" \
          --agree-tos \
          -d "${hostname}" \
          --server "${CERTBOT_SERVER}" \
          --post-hook \
              "systemctl is-active apache2 > /dev/null 2>&1 && systemctl reload apache2 > /dev/null 2>&1" \
       > "${OUTPUT}" 2>&1
    _rc=$?

    if [ ${_rc} -ne 0 ] ; then
        _msg="${username}:${hostname} ->\n$( cat "${OUTPUT}" )"
        if [ "${BATCH}" != "yes" ] ; then
            _dialog_textbox \
                 "  !!! ERROR :(( !!!  " \
                 "CertBot execution Error: rc == ${_rc}" \
                 "${_msg}"
        fi
        ERROR "CertBot execution: ${_msg}"
    else
        _certificate_add_postconf "${hostname}" "${username}"

        _msg="${username}:${hostname} ->\n$( cat "${OUTPUT}" )"
        if [ "${BATCH}" != "yes" ] ; then
            _dialog_textbox \
                 "  *** Success :) ***  " \
                 "Request SSL certificate" \
                 "${_msg}"
        fi
        INFO "CertBot execution: ${_msg}"
    fi

    return ${_rc}
}


# -------------------------------------------------------------
# Revoke LetsEncrypt certificate
# -------------------------------------------------------------
_certificate_revoke () {
    local _cert="${1}"
    local _vhost_conf
    local _vhost444_conf
    local _msg
    local _arg="${2}"
    local _rc

    dialog \
        ${DIALOG_OPTS} \
        --aspect 14 \
        --defaultno \
        --backtitle "$( _backtitle "${APP_TITLE}: Revoke '${_cert}' certificate" )" \
        --yesno "Are you sure you want to revoke ${_arg}the certificate for '${_cert}' ?" \
        0 0 \
      || return 0

    _dialog_infobox \
        "  Revoke SSL certificate  " \
        "Revoking '${_cert}' certificate" \
        "\n  Please wait ...  \n\n"

    # Revoke the certificate:
    ${CERTBOT} \
          -vvv \
          -n \
          revoke \
          ${CERTBOT_OPTS}  \
          --no-delete-after-revoke \
          --server "${CERTBOT_SERVER}" \
          --agree-tos \
          --cert-name "${_cert}" \
       > "${OUTPUT}" 2>&1
    _rc=$?

    # Remove Apache virtual host configuration:
    _vhost_conf="rsbssl_${_cert}.conf"
    if [ -e "/etc/apache2/sites-enabled/${_vhost_conf}" ] ; then
        a2dissite "${_vhost_conf}"  > "${TMPFILE}" 2>&1
        service apache2 reload    >> "${TMPFILE}" 2>&1
        cat "${TMPFILE}" >> "${OUTPUT}"
    fi
    _vhost444_conf="rsbssl_${_cert}-444.conf"
    if [ -e "/etc/apache2/sites-enabled/${_vhost444_conf}" ] ; then
        a2dissite "${_vhost444_conf}"  > "${TMPFILE}" 2>&1
        service apache2 reload    >> "${TMPFILE}" 2>&1
        cat "${TMPFILE}" >> "${OUTPUT}"
    fi

    # Remove certificate renew cronjob:
    [ -e "/etc/cron.d/certbot-$( echo "${_cert}" | tr '.' '_' )" ] \
        && RM_FILES "/etc/cron.d/certbot-$( echo "${_cert}" | tr '.' '_' )"

    _msg="${_cert} ->\n$( cat "${OUTPUT}" )"
    if [ "${_arg}" != "and delete "  ] ; then
        _dialog_textbox \
            "  Results  " \
            "Revoke SSL certificate" \
            "${_msg}"
    fi
    INFO "CertBot execution (revoke): ${_msg}"

    return ${_rc}
}


# -------------------------------------------------------------
# Delete LetsEncrypt certificate
# -------------------------------------------------------------
_certificate_delete () {
    local _cert="${1}"
    local _vhost_conf
    local _vhost444_conf
    local _msg
    local _arg="${2}"
    local _rc

    if [ "${_arg}" != "force"  ] ; then
        dialog \
            ${DIALOG_OPTS} \
            --aspect 14 \
            --defaultno \
            --backtitle "$( _backtitle "${APP_TITLE}: Delete '${_cert}' certificate" )" \
            --yesno "Are you sure you want to delete the certificate for '${_cert}' ?" \
            0 0 \
          || return 0
    fi

    _dialog_infobox \
        "  Delete SSL certificate  " \
        "Deleting '${_cert}' certificate" \
        "\n  Please wait ...  \n\n"

    # Delete the certificate:
    ${CERTBOT} \
          -vvv \
          -n \
          delete \
          ${CERTBOT_OPTS}  \
          --server "${CERTBOT_SERVER}" \
          --agree-tos \
          --cert-name "${_cert}" \
       > "${OUTPUT}" 2>&1
    _rc=$?

    # Remove Apache virtual host configuration:
    _vhost_conf="rsbssl_${_cert}.conf"
    if [ -e "/etc/apache2/sites-enabled/${_vhost_conf}" ] ; then
        a2dissite "${_vhost_conf}"  > "${TMPFILE}" 2>&1
        service apache2 reload    >> "${TMPFILE}" 2>&1
        cat "${TMPFILE}" >> "${OUTPUT}"
    fi
    _vhost444_conf="rsbssl_${_cert}-444.conf"
    if [ -e "/etc/apache2/sites-enabled/${_vhost444_conf}" ] ; then
        a2dissite "${_vhost444_conf}"  > "${TMPFILE}" 2>&1
        service apache2 reload    >> "${TMPFILE}" 2>&1
        cat "${TMPFILE}" >> "${OUTPUT}"
    fi

    # Remove certificate renew cronjob:
    [ -e "/etc/cron.d/certbot-$( echo "${_cert}" | tr '.' '_' )" ] \
        && RM_FILES "/etc/cron.d/certbot-$( echo "${_cert}" | tr '.' '_' )"

    # Remove certificate info file:
    [ -e "${TMP_DIR}/${_cert}.info" ] \
        && RM_FILES "${TMP_DIR}/${_cert}.info"

    _msg="${_cert} ->\n$( cat "${OUTPUT}" )"
    _dialog_textbox \
        "  Results  " \
        "Delete SSL certificate" \
        "${_msg}"
    INFO "CertBot execution (delete): ${_msg}"

    return ${_rc}
}


# -------------------------------------------------------------
# Renew LetsEncrypt certificate
# -------------------------------------------------------------
_certificate_renew () {
    local _cert="${1}"
    local _username=""
    local _msg
    local _rc

    _dialog_infobox \
        "  Renew SSL certificate  " \
        "Renewing '${_cert}' certificate" \
        "\n     Please wait ...     \n\n"

    # Renew the certificate
    ${CERTBOT} \
        -vvv \
        -n \
        renew \
        ${CERTBOT_OPTS} \
        --server "${CERTBOT_SERVER}" \
        --webroot \
        --webroot-path "/var/www/html" \
        --agree-tos \
        --cert-name "${_cert}" \
        --post-hook "systemctl is-active apache2 > /dev/null 2>&1 && systemctl reload apache2 > /dev/null 2>&1 ; systemctl is-active pure-ftpd > /dev/null 2>&1 && systemctl restart pure-ftpd > /dev/null 2>&1" \
      > "${OUTPUT}" 2>&1
    _rc=$?

    if [ ${_rc} -ne 0 ] ; then
        _msg="${_cert} ->\n$( cat "${OUTPUT}" )"
        _dialog_textbox \
             "  !!! ERROR :(( !!!  " \
             "CertBot execution error: rc == ${_rc}" \
             "${_msg}"
        ERROR "CertBot execution (renew): ${_msg}"
    else
        [ -e "${RSBSSL_DIR}/certbot-${_cert}.cfg" ] \
            && _username="$( tail -1 "${RSBSSL_DIR}/certbot-${_cert}.cfg" )"
        _certificate_add_postconf "${_cert}" "${_username}"

        _msg="${_cert} ->\n$( cat "${OUTPUT}" )"
        _dialog_textbox \
             "  *** Success :) ***  " \
             "Renew SSL certificate" \
             "${_msg}"
        INFO "CertBot execution (renew): ${_msg}"
    fi

    return ${_rc}
}


# -------------------------------------------------------------
# Individual certificate operations
# Such as: Revoke, Delete, Renew
# -------------------------------------------------------------
_certificate_operations () {
    local _cert="${1}"
    local _cert_expiry_date
    local _cert_help_text=""
    local _menu_arr
    local _ret
    local _rc=1
    local _op
    local _default_op

    while true ; do
        _menu_arr=()
        _cert_expiry_date="$( grep 'Expiry Date:' "${TMP_DIR}/${_cert}.info" \
                                          | sed -e 's/.*(\(.*\))$/\1/' )"
        if echo "${_cert_expiry_date}" | grep -q -e "^INVALID:" ; then
            _cert_help_text="\Zu\Z1${_cert_expiry_date}\Zn"
            _menu_arr+=("About certificate" "" "${_cert_help_text}")
            _menu_arr+=("Delete certificate" "" "${_cert_help_text}")
        else
            _cert_help_text="\Zu\Z2${_cert_expiry_date}\Zn"
            _menu_arr+=("About certificate" "" "${_cert_help_text}")
            _menu_arr+=("Revoke certificate" "" "${_cert_help_text}")
            _menu_arr+=("Revoke & Delete certificate" "" "${_cert_help_text}")
        fi
        _menu_arr+=("Renew certificate" "" "${_cert_help_text}")
        dialog ${DIALOG_OPTS} \
                --backtitle "$( _backtitle "${APP_TITLE}: '${_cert}' configuration" )" \
                --title " '${_cert}' configuration " \
                --cancel-label " << Back" \
                --default-item "${_default_op}" \
                --item-help \
                --menu "You can use the UP/DOWN arrow keys to choose an option" \
                0 0 0 \
                "${_menu_arr[@]}" \
           2> "${INPUT}"
        _ret=$?
        [ ${_ret} -eq ${DIALOG_CANCEL} -o ${_ret} -eq ${DIALOG_ESC} ] \
            && return ${_ret}

        _op="$( cat "${INPUT}" )"
        _default_op="${_op}"

        case "${_op}" in
          "About certificate")
              _dialog_textbox \
                   "  '${_cert}' certificate info  " \
                   "Certificate for '${_cert}'" \
                   "\n$( cat "${TMP_DIR}/${_cert}.info" )\n"
              continue
              ;;
          "Delete certificate")
              _certificate_delete "${_cert}"
              break
              ;;
          "Revoke certificate")
              _certificate_revoke "${_cert}"
              break
              ;;
          "Revoke & Delete certificate")
              _certificate_revoke "${_cert}" "and delete "
              _certificate_delete "${_cert}" "force"
              break
              ;;
          "Renew certificate")
              _certificate_renew "${_cert}"
              _certificates_refresh_info
              continue
              ;;
        esac
    done

    return ${_rc}
}


# -------------------------------------------------------------
# Certificates management: main menu
# -------------------------------------------------------------
_manage_certificates () {
    local _cert
    local _cert_menu
    local _cert_menu2
    local _cert_help_text
    local _menu_arr
    local _item
    local _default_item
    local _rc
    local _ret
    local _f
    local _default_button="ok"
    local _refresh_certs="yes"

    while true ; do

        if [ "${_refresh_certs}" = "yes" ] ; then
            _menu_arr=()
            _menu_arr+=("Request new certificate" "" "")
            _menu_arr+=("-----------------------" "" "")

            _certificates_refresh_info

            for _f in $( ls -1 ${TMP_DIR}/*.info ) ; do
                _cert="$( echo "${_f}" | sed -e 's|^.*/\(.*\)\.info$|\1|' )"
                _cert_expiry_date="$( grep 'Expiry Date:' "${_f}" \
                                          | sed -e 's/.*(\(.*\))$/\1/' )"
                _cert_menu2=""
                if [ -e "${RSBSSL_DIR}/certbot-${_cert}.cfg" ] ; then
                    _cert_menu2="$( tail -1 < "${RSBSSL_DIR}/certbot-${_cert}.cfg" )"
                fi
                if echo "${_cert_expiry_date}" | grep -q -e "^INVALID:" ; then
                    _cert_help_text="\Zu\Z1${_cert_expiry_date}\Zn"
                    [ -n "${_cert_menu2}" ] && _cert_menu2="\Zu\Z1${_cert_menu2}\Zu\Z1"
                else
                    _cert_help_text="\Zu\Z2${_cert_expiry_date}\Zn"
                fi
                _menu_arr+=( "${_cert}" "${_cert_menu2}" "${_cert}: ${_cert_help_text}" )
            done
        fi

        dialog ${DIALOG_OPTS}                            \
                --backtitle "$( _backtitle "${APP_TITLE}: Manage certificates" )" \
                --title ""                              \
                --cancel-label " << Back"               \
                --item-help                             \
                --help-button                           \
                --help-tags                             \
	        --default-item "${_default_item}"       \
	        --default-button "${_default_button}"   \
                --menu ""                               \
             0 0 0                                      \
             "${_menu_arr[@]}"                          \
          2> "${INPUT}"
        _ret=$?
        [ ${_ret} -eq ${DIALOG_CANCEL} -o ${_ret} -eq ${DIALOG_ESC} ] && break

        _item="$( cat "${INPUT}" )"
        _default_item="${_item}"

        if [ ${_ret} -eq ${DIALOG_HELP} ] ; then
            _item="$( echo "${_item}" | sed -e 's/HELP \(.*\)/\1/' )"
            _default_item="${_item}"
            _default_button="help"
            _cert="${_item}"
            if [ -e "${TMP_DIR}/${_cert}.info" ] ; then
                _dialog_textbox \
                     "  '${_cert}' certificate info  " \
                     "Certificate for '${_cert}'" \
                     "\n$( cat "${TMP_DIR}/${_cert}.info" )\n"
            else
                if echo "${_item}" | grep -q -v -e "^----" ; then
                    # Help for non-certs menu items
                    case "${_item}" in
                      "Request new certificate")
                          _dialog_msgbox \
                               "" \
                               "${_item}" \
                               "\n\n    Request new free LetsEncrypt certificate.    \n\n"
                          ;;
                    esac
                fi
            fi
            _refresh_certs="no"
            continue
        fi

        _default_button="ok"

        case "${_item}" in
          "Request new certificate")
              _certificate_request
              _refresh_certs="yes"
              continue
              ;;
          "-----------------------")
              _refresh_certs="no"
              continue
              ;;
          *)
              _certificate_operations "${_item}"
              _refresh_certs="yes"
              continue
              ;;
        esac

    done

    return
}


# -------------------------------------------------------------
# Process NIP.IO / SSLIP.IO certificate
# -------------------------------------------------------------
_process_Xip () {
    local _rc=0
    local _pfx
    case "${TLD}" in
      "nip.io")
          _pfx="box-"
          ;;
      "sslip.io")
          _pfx="www."
          ;;
      *)
          return 1
          ;;
    esac
    _certificate_request "${_pfx}${IP}.${TLD}" "rsb-$( shuf -i 1-300000 -n 1 )@rsb.great.com"
    return ${_rc}
}



# =============================================================
# Main section
# =============================================================

_prep_env ; _rc=$?
[ ${_rc} -ne 0 ] && _exit ${_rc}

_apache_check_tcp80

_check_certbot || exit 1

[ -z "${TLD}" ] && _info

username=""
password=""
hostname=""
default_op=""

while true ; do

    if [ -n "${TLD}" ] ; then
        _process_Xip
        if [ $? -ne 0 ] ; then
            echo "Error :("
        fi
        break
    fi

    main_menu_item_help="IP: \Zu\Z5${IP}\Zn"

    noip_menu2=""    ; noip_cnt=0
    duckdns_menu2="" ; duckdns_cnt=0
    freedns_menu2="" ; freedns_cnt=0
    cloudns_menu2="" ; cloudns_cnt=0

    if which noip2 > /dev/null 2>&1 ; then
        noip_cnt="$( noip2 -S 2>&1 | grep -c -e '\s*host\s*' )"
    fi
    duckdns_cnt="$( ls -1 /etc/cron.d/duckdns-* 2> /dev/null | wc -l )"
    freedns_cnt="$( ls -1 /etc/cron.d/freedns-* 2> /dev/null | wc -l )"
    cloudns_cnt="$( ls -1 /etc/cron.d/cloudns-* 2> /dev/null | wc -l )"

    [ "${noip_cnt}" != "0" ]    && noip_menu2="( ${noip_cnt} )"
    [ "${duckdns_cnt}" != "0" ] && duckdns_menu2="( ${duckdns_cnt} )"
    [ "${freedns_cnt}" != "0" ] && freedns_menu2="( ${freedns_cnt} )"
    [ "${cloudns_cnt}" != "0" ] && cloudns_menu2="( ${cloudns_cnt} )"

    dialog ${DIALOG_OPTS}                            \
            --backtitle "$( _backtitle "${APP_TITLE}" )" \
            --title " [ M A I N - M E N U ] "            \
            --cancel-label "Exit"                        \
            --default-item "${default_op}"               \
	    --item-help                                  \
            --menu "You can use the UP/DOWN arrow keys, the first \n\
letter of the choice as a hot key, or the \n\
number keys 1-9 to choose an option.\n\
Choose the TASK"  \
            0 0 0 \
            "Configure No-IP DNS" "${noip_menu2}" "${main_menu_item_help}"   \
            "Configure DuckDNS" "${duckdns_menu2}" "${main_menu_item_help}"  \
            "Configure FreeDNS" "${freedns_menu2}" "${main_menu_item_help}"  \
            "Configure ClouDNS" "${cloudns_menu2}" "${main_menu_item_help}"  \
            "Test DNS"          "" "${main_menu_item_help}"                  \
            "Manage certificates" "" "${main_menu_item_help}"                \
            2> "${INPUT}"
    ret=$?

    [ ${ret} -eq ${DIALOG_CANCEL} -o ${ret} -eq ${DIALOG_ESC} ] && break

    operation="$( cat "${INPUT}" )"
    default_op="${operation}"

    case "${operation}" in
      "Configure No-IP DNS")
          _conf_noip
          continue
          ;;
      "Configure DuckDNS")
          _conf_duckdns
          continue
          ;;
      "Configure FreeDNS")
          _conf_freedns
          continue
          ;;
      "Configure ClouDNS")
          _conf_cloudns
          continue
          ;;
      "Test DNS")
          _test_dns
          continue
          ;;
      "Manage certificates")
          _manage_certificates
          continue
          ;;
    esac

done

RM_DIRS "${TMP_DIR}"
_exit 0
