#!/bin/bash
#
# Apache 2 Tools.
#
# For suggestion and bug reports, please contact
# Pierre-Yves Landuré <pierre-yves dot landure at biapy dot fr>
version=3.5.1

# History
# -------
#
# 3.5.1:
#  - Add fix for 503 error in HTTP/1.1 to HTTP/2.0 reverse proxy.
# 3.5.0:
#  - Enable HTTP 2 in hosts when mod_http2.c is available.
# 3.4.1:
#  - Fix acme.sh path detection bug and missing certificate paths.
# 3.4.0:
#  - Enable custom reverse proxy settings with --proxy-settings= argument.
# 3.3.0:
#  - Fix most of shellcheck warnings.
#  - Use new convention for variable names.
#  - Add X-SendFile support.
#  - Use _default_ as default SSL ip if possible.
# 3.2.3:
#  - Fix null byte warning in basename.
# 3.2.2:
#  - add test for version < 2.4 on allow/deny directives.
# 3.2.1:
#  - bug fix for redirect template with acme.
# 3.2.0:
#  - add support for Let's Encrypt via acme.sh.
#  - use cecho to add some colors in message output.
#


# Get the basename of a path (multi-plateform version)
# Print the result on &1 if found.
#
# @param string $path A path.
#
# @return A return code..
function basename() {
  [[ ${#} -eq 0 ]] && exit 1

  case "$(uname)" in
    'Linux' )
      command basename -z -- "${@}" \
        | command tr -d '\0'
      ;;
    'Darwin' | * )
      command basename -- "${@}"
      ;;
  esac

  return ${?}
} # basename()
export -f 'basename'

scriptName="$(basename "${0}")"

# Print this script help.
function usage {
  command echo "Apache 2 Tools v${version}
This tool ease Apache 2 HTTP server administration from command line.

Usage :

  ${scriptName} --template={ vhost | virtual-host } [ options ]
                  domain.name /path/to/www
        Setup a named virtualhost using domain.name as ServerName
        and /path/to/www as DocumentRoot.
  ${scriptName} --template={ rproxy | reverse-proxy } [ options ]
        [ --proxy-settings='connectiontimeout=600 timeout=4000 Keepalive=On' ]
                  domain.name http://server.local/
        Setup a reverse proxy using domain.name as ServerName
        and http://server.local/ proxied server.
  ${scriptName} --template=redirect [ options ]
                  domain.name http://domain.com/
        Setup a permanent redirection from http://domain.name/
        to http://domain.com/ as target url.
  ${scriptName} --template=custom [ options ]
                 domain.name '# Custom Apache 2 configuration chunck.'
        Setup a customized virtual host using domain.name as ServerName.
  ${scriptName} --remove domain.name
        Remove all virtualhosts for the given domain.

Available options are:
  * --help | -h
        Display this message.
  * --template= | -t { virtual-host | reverse-proxy | redirect | custom }
        Set the template to use to create the Apache 2 configuration.
        Available templates are:
          * vhost | virtual-host : A standard VirtualHost with a DocumentRoot.
          * rproxy | reverse-proxy : A reverse proxy VirtualHost.
          * redirect : A VirtualHost that performs a permanent redirection.
          * custom : A customized VirtualHost for specific configurations.
        If template option is not used, the 'virtual-host' template is
        used by default.
  * --remove | -d
        Remove all virtualhosts for the given domain.
  * --alias= | -a other-domain.name
        Set ServerAlias directive. You can provide a list of domain names
        separated by spaces. This option can be used more than once.
  * --overrides= | -o All
        Set AllowOverride directive (only for 'virtual-host' template).
  * --without-multiviews
        Disable MultiViews for VirtualHost. Can fix some rewriting problems.
  * --ssl= | -s { key_name | auto }
        Create a HTTP virtual host with mod_ssl.
        Require a key name or --private-key and --public-key
        If 'auto' is used instead of a key name, a Let's Encrypt certificate
        is created (require acme.sh installed for root user,
        see https://github.com/Neilpang/acme.sh ).
        Using the key_name option set the following defaults:
         - private-key : /etc/ssl/private/key_name.key
         - public-key  : /etc/ssl/certificates/key_name.crt
         - root-ca     : /etc/ssl/roots/key_name-root.ca
         - chain-ca    : /etc/ssl/chains/key_name.ca
  * --private-key= | --key= | -k /etc/ssl/private/domain.com.key
        Set the SSL private key (mandatory with --ssl).
  * --public-key= | --cert= | -p /etc/ssl/certificates/domain.com.crt
        Set the SSL public key (mandatory with --ssl).
  * --root-ca= | --root= | -r /etc/ssl/roots/domain.com-root.ca
        Set the SSL root certificate (with --ssl only).
  * --chain-ca= | --chain= | -c /etc/ssl/chains/domain.com.ca
        Set the SSL intermediate certificate (with --ssl only).
  * --bind-ip= | --ip= | -i xx.xx.xx.xx
        Set the bind IP for a SSL virtual host (with --ssl only).
  * --xsendfile= | --xsf= /var/www/html/path
        Allow X-SendFile header for given path.
        This option can be used multiple times.
  * --proxy-settings= | --ps= 'connectiontimeout=600 timeout=4000 Keepalive=On'
        Replace default reverse proxy settings.
  * --custom-options= | --co= '# A additional Apache configuration snippet'
        Add custom Apache configuration snippets at the end of generated file.
        This option can be used multiple times.
"

  [[ -n "${1}" ]] && exit "${1}"
} # usage



# Common Apache 2 Vhost template
VHOST_TEMPLATE[0]="<VirtualHost *:80>
  # Uncomment this line and set it up with your actual webmaster email
  # or with your real email.
  #ServerAdmin webmaster@my-domain.com

  # Your actual domain name, on witch this virtual host is available.
  ServerName %_SITE_HOSTNAME_%

  # You may want your site to be available on other domain names, this is
  # what alias are for.
  # You can use the * wildcard caracter to match multiple sub-domains.
  #ServerAlias %_SERVER_ALIAS_%

  # Theses lines only apply of the rewrite module is enabled.
  # This is a security enhancement recommanded by the nessus tool.
  <IfModule mod_rewrite.c>
    RewriteEngine on
    RewriteCond %{REQUEST_METHOD} ^(TRACE|TRACK)
    RewriteRule .* - [F]
  </IfModule>

  # These section tel Apache 2 that it can follow symbolic links (cf. ln -s)
  # on your system. This can avoid a lot of problems... as well at
  # it can be a security issue if the links points to /etc...
  # be carefull at what you link :)
  <Directory />
    Options FollowSymLinks
    AllowOverride None
  </Directory>
"

VHOST_TEMPLATE[1]="
</VirtualHost>"



LOGS_TEMPLATE="
  # The error log and access log. This can be used by awstats
  # Note : since we keed theses logs in /var/log/apache2, they are
  # automaticaly rotated by logrotate :D.
  ErrorLog \${APACHE_LOG_DIR}/%_SITE_HOSTNAME_%-error.log
  LogLevel warn
  CustomLog \${APACHE_LOG_DIR}/%_SITE_HOSTNAME_%-access.log combined
"



SSL_TEMPLATE="
  <IfModule mod_ssl.c>
    #
    # SSL magic
    #

    # We enable the SSL engine. Without this line, we use HTTP, not HTTPS.
    SSLEngine On

    # Mitigating the BEAST attack on TLS
    # see https://community.qualys.com/blogs/securitylabs/2011/10/17/mitigating-the-beast-attack-on-tls
    SSLHonorCipherOrder On
    SSLCipherSuite EECDH+ECDSA+AESGCM:EECDH+aRSA+AESGCM:EECDH+ECDSA+SHA384:EECDH+ECDSA+SHA256:EECDH+aRSA+SHA384:EECDH+aRSA+SHA256:EECDH+aRSA+RC4:EECDH:EDH+aRSA:!aNULL:!eNULL:!LOW:!3DES:!MD5:!EXP:!PSK:!SRP:!DSS:!RC4
    # Allow RC4 for older browsers.
    # see https://www.digicert.com/ssl-support/ssl-enabling-perfect-forward-secrecy.htm#apache_forward_secrecy
    # SSLCipherSuite EECDH+ECDSA+AESGCM:EECDH+aRSA+AESGCM:EECDH+ECDSA+SHA384:EECDH+ECDSA+SHA256:EECDH+aRSA+SHA384:EECDH+aRSA+SHA256:EECDH+aRSA+RC4:EECDH:EDH+aRSA:!aNULL:!eNULL:!LOW:!3DES:!MD5:!EXP:!PSK:!SRP:!DSS:+RC4:RC4

    # We allow SSLv3 and TLSv1 only, we reject the old SSLv2.
    # TLSv1 is needed to support SNI (Server Name Indication).
    # SNI allow to have multiple SSL certificates on one IP address.
    SSLProtocol All +TLSv1 -SSLv2 -SSLv3

    # Server public and private certificate files:
    <IfVersion < 2.4>
      SSLCertificateFile \"%_CERTIFICATE_FILE_%\"
    </IfVersion>
    <IfVersion >= 2.4>
      SSLCertificateFile \"%_CERTIFICATE_COMPLETE_FILE_%\"
    </IfVersion>
    SSLCertificateKeyFile \"%_KEY_FILE_%\"

    #   Server Certificate Chain:
    #   Point SSLCertificateChainFile at a file containing the
    #   concatenation of PEM encoded CA certificates which form the
    #   certificate chain for the server certificate. Alternatively
    #   the referenced file can be the same as SSLCertificateFile
    #   when the CA certificates are directly appended to the server
    #   certificate for convinience.
    <IfVersion < 2.4>
      SSLCertificateChainFile \"%_CHAIN_FILE_%\"
    </IfVersion>

    #   Certificate Authority (CA):
    #   Set the CA certificate verification path where to find CA
    #   certificates for client authentication or alternatively one
    #   huge file containing all of them (file must be PEM encoded)
    #   Note: Inside SSLCACertificatePath you need hash symlinks
    #         to point to the certificate files. Use the provided
    #         Makefile to update the hash symlinks after changes.
    # SSLCACertificatePath \"/etc/ssl/certs\"
    # SSLCACertificateFile \"%_ROOT_CA_FILE_%\"

    #   Certificate Revocation Lists (CRL):
    #   Set the CA revocation path where to find CA CRLs for client
    #   authentication or alternatively one huge file containing all
    #   of them (file must be PEM encoded)
    #   Note: Inside SSLCARevocationPath you need hash symlinks
    #         to point to the certificate files. Use the provided
    #         Makefile to update the hash symlinks after changes.
    #SSLCARevocationPath /etc/apache2/ssl.crl/
    #SSLCARevocationFile /etc/apache2/ssl.crl/ca-bundle.crl

    #   Client Authentication (Type):
    #   Client certificate verification type and depth.  Types are
    #   none, optional, require and optional_no_ca.  Depth is a
    #   number which specifies how deeply to verify the certificate
    #   issuer chain before deciding the certificate is not valid.
    #SSLVerifyClient require
    #SSLVerifyDepth  10

    #   Access Control:
    #   With SSLRequire you can do per-directory access control based
    #   on arbitrary complex boolean expressions containing server
    #   variable checks and other lookup directives.  The syntax is a
    #   mixture between C and Perl.  See the mod_ssl documentation
    #   for more details.
    #<Location />
    #SSLRequire (    %{SSL_CIPHER} !~ m/^(EXP|NULL)/ \
    #            and %{SSL_CLIENT_S_DN_O} eq \"Snake Oil, Ltd.\" \
    #            and %{SSL_CLIENT_S_DN_OU} in {\"Staff\", \"CA\", \"Dev\"} \
    #            and %{TIME_WDAY} >= 1 and %{TIME_WDAY} <= 5 \
    #            and %{TIME_HOUR} >= 8 and %{TIME_HOUR} <= 20       ) \
    #           or %{REMOTE_ADDR} =~ m/^192\.76\.162\.[0-9]+$/
    #</Location>

    #   SSL Engine Options:
    #   Set various options for the SSL engine.
    #   o FakeBasicAuth:
    #     Translate the client X.509 into a Basic Authorisation.  This means that
    #     the standard Auth/DBMAuth methods can be used for access control.  The
    #     user name is the 'one line' version of the client's X.509 certificate.
    #     Note that no password is obtained from the user. Every entry in the user
    #     file needs this password: 'xxj31ZMTZzkVA'.
    #   o ExportCertData:
    #     This exports two additional environment variables: SSL_CLIENT_CERT and
    #     SSL_SERVER_CERT. These contain the PEM-encoded certificates of the
    #     server (always existing) and the client (only existing when client
    #     authentication is used). This can be used to import the certificates
    #     into CGI scripts.
    #   o StdEnvVars:
    #     This exports the standard SSL/TLS related 'SSL_*' environment variables.
    #     Per default this exportation is switched off for performance reasons,
    #     because the extraction step is an expensive operation and is usually
    #     useless for serving static content. So one usually enables the
    #     exportation for CGI and SSI requests only.
    #   o StrictRequire:
    #     This denies access when 'SSLRequireSSL' or 'SSLRequire' applied even
    #     under a 'Satisfy any' situation, i.e. when it applies access is denied
    #     and no other module can change it.
    #   o OptRenegotiate:
    #     This enables optimized SSL connection renegotiation handling when SSL
    #     directives are used in per-directory context.
    #SSLOptions +FakeBasicAuth +ExportCertData +StrictRequire
    <FilesMatch \"\.(cgi|shtml|phtml|php)$\">
      SSLOptions +StdEnvVars
    </FilesMatch>
    <Directory /usr/lib/cgi-bin>
      SSLOptions +StdEnvVars
    </Directory>

    #   SSL Protocol Adjustments:
    #   The safe and default but still SSL/TLS standard compliant shutdown
    #   approach is that mod_ssl sends the close notify alert but doesn't wait for
    #   the close notify alert from client. When you need a different shutdown
    #   approach you can use one of the following variables:
    #   o ssl-unclean-shutdown:
    #     This forces an unclean shutdown when the connection is closed, i.e. no
    #     SSL close notify alert is send or allowed to received.  This violates
    #     the SSL/TLS standard but is needed for some brain-dead browsers. Use
    #     this when you receive I/O errors because of the standard approach where
    #     mod_ssl sends the close notify alert.
    #   o ssl-accurate-shutdown:
    #     This forces an accurate shutdown when the connection is closed, i.e. a
    #     SSL close notify alert is send and mod_ssl waits for the close notify
    #     alert of the client. This is 100% SSL/TLS standard compliant, but in
    #     practice often causes hanging connections with brain-dead browsers. Use
    #     this only for browsers where you know that their SSL implementation
    #     works correctly.
    #   Notice: Most problems of broken clients are also related to the HTTP
    #   keep-alive facility, so you usually additionally want to disable
    #   keep-alive for those clients, too. Use variable \"nokeepalive\" for this.
    #   Similarly, one has to force some clients to use HTTP/1.0 to workaround
    #   their broken HTTP/1.1 implementation. Use variables \"downgrade-1.0\" and
    #   \"force-response-1.0\" for this.
    BrowserMatch \"MSIE [2-6]\" \\
      nokeepalive ssl-unclean-shutdown \\
      downgrade-1.0 force-response-1.0
    # MSIE 7 and newer should be able to use keepalive
    BrowserMatch \"MSIE [17-9]\" ssl-unclean-shutdown
  </IfModule>
"

CERTBOT_TEMPLATE="
  # Certbot acme challenge.
  Alias \"/.well-known/acme-challenge\" \"%_CERTBOT_PATH_%\"

  <Directory \"%_CERTBOT_PATH_%\">

    <IfVersion >= 2.4>
      Require all granted
    </IfVersion>

    <IfVersion < 2.4>
      Order allow,deny
      Allow from all
    </IfVersion>
  </Directory>
"

# Apache 2 standard Virtual Host template.
PATH_TEMPLATE="
  # The root folder of this virtual host.
  DocumentRoot %_TARGET_VALUE_%

  # Some options for the root folder.
  # Read Apache 2 documentation to know exactly what is done.
  <Directory %_TARGET_VALUE_%>
    Options +Indexes +FollowSymLinks %_MULTI_VIEWS_%

    # If you want to enable overrides, you should read:
    # http://httpd.apache.org/docs/2.0/mod/core.html#allowoverride
    AllowOverride %_ALLOW_OVERRIDE_%

    <IfVersion >= 2.4>
      Require all granted
    </IfVersion>

    <IfVersion < 2.4>
      Order allow,deny
      Allow from all
    </IfVersion>
  </Directory>
"



REVERSE_PROXY_TEMPLATE="
  <IfModule mod_rewrite.c>
    <IfModule mod_proxy.c>
      # Do not ever never comment this line !
      # This line prevent your web server to be used
      # as a proxy server by lurkers and other lamers.
      ProxyRequests   Off

      # This little option pass the hostname to the proxyfied server.
      # This allow you to setup virtual hosts on the proxyfied server.
      # Yay ! This can be a life saver, so trust me, you want this option On.
      ProxyPreserveHost On

      # Fix IE problem (http error 408/409)
      BrowserMatch \"MSIE [2-6]\" proxy-nokeepalive 1

      <IfModule mod_ssl.c>
        # Turn this on if the site behind the reverse proxy is on HTTPS.
        SSLProxyEngine %_SSL_PROXY_ENGINE_%
        # SSLProxyVerify none
        # SSLProxyCheckPeerCN off
        # SSLProxyCheckPeerName off
        SSLProxyCheckPeerExpire off
      </IfModule>

      # Declare the current request protocol.
      RequestHeader set X-Forwarded-Proto \"%_FORWARDED_PROTOCOL_%\"

      <Proxy %_TARGET_VALUE_%>
        # Rise timeout values for failsafe on some applications (Horde ActiveSync or Plone, for example).
        #ProxySet %_PROXY_SETTINGS_%
      </Proxy>

      # Here is the magic that proxyfy the LAN server.
      # The first line is .... i don't remember what...
      # but trust me, it is usefull ;p.
      # The second line is a rewrite rule that do the proxy
      # magic. I was used to use a ProxyPass rule to do this work, but it
      # turned out that sometimes ProxyPass give you a 503 error when under
      # heavy load. The RewriteRule does not have this flaw.
      ProxyPassReverse  /       %_TARGET_VALUE_%

      # For HTTP2 Only: # Limit Proxy to h2 or h2c protocol when current protocol is HTTP/2.0.
      # For HTTP2 Only: RewriteCond %{THE_REQUEST} ^.*HTTP/2\.0$
      # For HTTP2 Only: RewriteCond %{REQUEST_URI} !^/.well-known/acme-challenge/.*
      # For HTTP2 Only: RewriteRule       ^/(.*)  %_HTTP2_TARGET_VALUE_%\$1  [P,L]
      # For HTTP2 Only: 
      RewriteCond %{REQUEST_URI} !^/.well-known/acme-challenge/.*
      RewriteRule       ^/(.*)  %_TARGET_VALUE_%\$1  [P,L]

      # This Location directives allow users to access to the proxyfied contents.
      # Do not remove this if you want your site to work.
      <Location />
        <IfVersion >= 2.4>
          Require all granted
        </IfVersion>

        <IfVersion < 2.4>
          Order deny,allow
          Allow from all
        </IfVersion>
      </Location>

    </IfModule>
  </IfModule>
"



REDIRECT_TEMPLATE="
  # Redirect every body to the HTTPS site.
  # This make sure that all users use secure version of the site.
  # Note the \"permanent\" : It is good for search engine optimization :D.
  <LocationMatch ^(?!/.well-known/acme-challenge)/>
    Redirect permanent / %_TARGET_VALUE_%
  </LocationMatch>

  # This Location directives allow users to access the redirection.
  # Do not remove this if you want your site to work.
  <Location />
    <IfVersion >= 2.4>
      Require all granted
    </IfVersion>

    <IfVersion < 2.4>
      Order deny,allow
      Allow from all
    </IfVersion>
  </Location>"

XSENDFILE_TEMPLATE="
  # Enable X-SendFile header support for path.
  <IfModule mod_xsendfile.c>
    XSendFile on
%_XSENDFILE_OPTIONS_%
  </IfModule>"

XSENDFILE_PATH_TEMPLATE="    XSendFilePath \"%_XSENDFILE_PATH_%\""

# h2 protocol needs https certificates.
H2_SSL_TEMPLATE="
  # Enable HTTP/2.0
  <IfModule mod_http2.c>
    Protocols h2 http/1.1
  </IfModule>"

# h2c stands for clear text h2. To use with http only VirtualHosts.
H2_TEMPLATE="
  # Enable HTTP/2.0
  <IfModule mod_http2.c>
    Protocols h2c http/1.1
  </IfModule>"

# Echo text in color.
#
# Colors definitions.
# See http://mywiki.wooledge.org/BashFAQ/037
#
# @param string $color Color and weight for text. (boldgreen for example).
# @param string $text The text to echo (and echo options).
function cecho() {
  if [[ ${#} -lt 2 ]]; then
    echo "${@}"
    return 0
  fi

  local color="${1}"

  # remove color information from arguments.
  shift 1

  # Check that the output is to a terminal.
  if [[ ! -t 1 ]]; then
    # Not outputing to a terminal, discaring colors.
    echo "${@}"
    return 0
  fi

  # Bash 4 version with associative array.
  ## Color and weight definitions.
  #declare -A font
  #font['black']="$(tput 'setaf' 0)"
  #font['red']="$(tput 'setaf' 1)"
  #font['green']="$(tput 'setaf' 2)"
  #font['yellow']="$(tput 'setaf' 3)"
  #font['blue']="$(tput 'setaf' 4)"
  #font['magenta']="$(tput 'setaf' 5)"
  #font['cyan']="$(tput 'setaf' 6)"
  #font['white']="$(tput 'setaf' 7)"

  #font['bgBlack']="$(tput 'setab' 0)"
  #font['bgRed']="$(tput 'setab' 1)"
  #font['bgGreen']="$(tput 'setab' 2)"
  #font['bgYellow']="$(tput 'setab' 3)"
  #font['bgBlue']="$(tput 'setab' 4)"
  #font['bgMagenta']="$(tput 'setab' 5)"
  #font['bgCyan']="$(tput 'setab' 6)"
  #font['bgWhite']="$(tput 'setab' 7)"

  #font['bold']="$(tput 'bold')"
  #font['stout']="$(tput 'smso')" # Standout.
  #font['under']="$(tput 'smul')" # Underline.
  #font['blink']="$(tput 'blink')" # Blinking
  #font['italic']="$(tput 'sitm')"

  ## Parse the color string.
  #for key in "${!font[@]}"; do
  #  [[ "${color}" = *"${key}"* ]] && echo -n "${font[${key}]}"
  #done

  declare -a fontIndex
  declare -a fontValue

  local index=0
  fontIndex[$index]='black';     fontValue[$index]="$(tput 'setaf' 0)"; ((index++))
  fontIndex[$index]='red';       fontValue[$index]="$(tput 'setaf' 1)"; ((index++))
  fontIndex[$index]='green';     fontValue[$index]="$(tput 'setaf' 2)"; ((index++))
  fontIndex[$index]='yellow';    fontValue[$index]="$(tput 'setaf' 3)"; ((index++))
  fontIndex[$index]='blue';      fontValue[$index]="$(tput 'setaf' 4)"; ((index++))
  fontIndex[$index]='magenta';   fontValue[$index]="$(tput 'setaf' 5)"; ((index++))
  fontIndex[$index]='cyan';      fontValue[$index]="$(tput 'setaf' 6)"; ((index++))
  fontIndex[$index]='white';     fontValue[$index]="$(tput 'setaf' 7)"; ((index++))

  fontIndex[$index]='bgBlack';   fontValue[$index]="$(tput 'setab' 0)"; ((index++))
  fontIndex[$index]='bgRed';     fontValue[$index]="$(tput 'setab' 1)"; ((index++))
  fontIndex[$index]='bgGreen';   fontValue[$index]="$(tput 'setab' 2)"; ((index++))
  fontIndex[$index]='bgYellow';  fontValue[$index]="$(tput 'setab' 3)"; ((index++))
  fontIndex[$index]='bgBlue';    fontValue[$index]="$(tput 'setab' 4)"; ((index++))
  fontIndex[$index]='bgMagenta'; fontValue[$index]="$(tput 'setab' 5)"; ((index++))
  fontIndex[$index]='bgCyan';    fontValue[$index]="$(tput 'setab' 6)"; ((index++))
  fontIndex[$index]='bgWhite';   fontValue[$index]="$(tput 'setab' 7)"; ((index++))

  fontIndex[$index]='bold';      fontValue[$index]="$(tput 'bold')"; ((index++))
  fontIndex[$index]='stout';     fontValue[$index]="$(tput 'smso')"; ((index++)) # Standout.
  fontIndex[$index]='under';     fontValue[$index]="$(tput 'smul')"; ((index++)) # Underline.
  fontIndex[$index]='blink';     fontValue[$index]="$(tput 'blink')"; ((index++)) # Blinking.
  fontIndex[$index]='italic';    fontValue[$index]="$(tput 'sitm')"; ((index++))

  for key in "${!fontIndex[@]}"; do
    [[ "${color}" = *"${fontIndex[${key}]}"* ]] && echo -n "${fontValue[${key}]}"
  done

  # Output the text.
  echo "${@}"

  # Reset all attributes.
  tput 'sgr0'

  return 0
} # cecho()
export -f 'cecho'



# Get the absolute path for a file or directory.
# Does not resolve symbolic links.
# Print the result on &1 if found.
#
# @param string $path A relative path.
#
# @return A code.
function absolutepath() {
    if [ -d "${1}" ]; then
        # Path point to a directory.
        echo "$(cd "${1}" 2>'/dev/null' || exit 1; pwd)"
    elif [ -f "${1}" ]; then
        # Path point to a file.
        if [[ "${1}" = '/'* ]]; then
            echo "${1}"
        elif [[ "${1}" == *'/'* ]]; then
            echo "$(cd "${1%/*}" 2>'/dev/null' || exit 1; pwd)/${1##*/}"
        else
            echo "$(pwd)/${1}"
        fi
    fi
} # absolutepath



# Get the absolute real path for a file or directory.
# Resolve symbolic links.
# Print the result on &1 if found.
#
# @param string $path A relative path.
#
# @return A code.
function realpath {

  command test ${#} -ne 1 && exit 1

  command readlink -f "${1}"

  return ${?}
} # realpath



# Get the absolute real path for a file or directory and check the file existance.
# Resolve symbolic links.
# Print the result on &1 if found.
# If the file does not exists, display an error message and exit the script.
#
# @param string $path A relative path.
#
# @return A code.
function realpath_check {
  command test ${#} -ne 1 && exit 1

  realpath="$(realpath "${1}")"

  if [[ -n "${realpath}" && ! -e "${realpath}" ]]; then
    realpath=""
  fi

  if [[ -z "${realpath}" ]]; then
    cecho "red" "Error : File '${1}' does not exists." >&2
    exit 1
  fi

  echo "${realpath}"

  return 0
} # realpath_check



# Check if a binary is present. Print its path on &1 if found.
#
# @param string $binary The binaries to check, separated by ;.
# @param string $package The package the binary come from.
#
# @return Exit with error if the binary is missing.
function check_binary() {
  [[ ${#} -ne 2 ]] && exit 1

  local primary
  local binaries
  local binary

  primary="${1%%;*}"
  binaries=()

  read -d ';' -r -a binaries <<< "${1}"

  # Test the binary presence.
  for binary in "${binaries[@]}"; do
    if type "${binary}" &>'/dev/null'; then
      command -v "${binary}"
      return 0
    fi
  done

  cecho 'redbold' "Error: '${primary}' is missing. Please install package '${2}'." >&2
  exit 1
} # check_binary()



# Read the eth0 IP.
#
# @return A IP address.
function eth0_ip {
  command ifconfig eth0 \
            | command grep "inet " \
            | command sed -e 's/.*inet [^:]*:\([^ ]*\).*/\1/'
} # eth0_ip


#######################################################################################
#######################################################################################
#######################################################################################
# Include from /usr/share/doc/bash-doc/examples/functions/getoptx.bash of package bash-doc.
#######################################################################################
#######################################################################################
#######################################################################################
function getoptex()
{
  let $# || return 1
  local optionList
  optionList="${1#;}"
  let optionIndex || optionIndex=1
  [[ ${optionIndex} -lt $# ]] || return 1
  shift ${optionIndex}
  if [[ "${1}" != "-" && "${1}" != "${1#-}" ]]; then
    optionIndex=$((optionIndex + 1)); if [[ "${1}" != "--" ]]; then
    local o
    o="-${1#-${optionOfs}}"
    for opt in ${optionList#;}
    do
      optionName="${opt%[;.:]}"
      unset optionArgument
      local optionType="${opt##*[^;:.]}"
      [[ -z "${optionType}" ]] && optionType=";"
      if [[ ${#optionName} -gt 1 ]]; then
        # long-named option
        case "$o" in
          "--${optionName}")
            if [[ "${optionType}" != ":" ]]; then
              return 0
            fi
            optionArgument="$2"
            if [[ -z "${optionArgument}" ]]; then
              # error: must have an agrument
              let optionError && echo "$0: error: ${optionName} must have an argument" >&2
              optionArgument="${optionName}";
              optionName="?"
              return 1;
            fi
            optionIndex=$((optionIndex + 1)) # skip option's argument
            return 0
          ;;
          "--${optionName}="*)
            if [[ "${optionType}" = ";" ]];
            then  # error: must not have arguments
              let optionError && echo "$0: error: ${optionName} must not have arguments" >&2
              optionArgument="${optionName}"
              optionName="?"
              return 1
            fi
            optionArgument=${o#--${optionName}=}
            return 0
          ;;
        esac
      else # short-named option
        case "$o" in
          "-${optionName}")
            unset optionOfs
            [[ "${optionType}" != ":" ]] && return 0
            optionArgument="$2"
            if [[ -z "${optionArgument}" ]]; then
              echo "$0: error: -${optionName} must have an argument" >&2
              optionArgument="${optionName}"
              optionName="?"
              return 1
            fi
            optionIndex=$(( optionIndex + 1 )) # skip option's argument
            return 0
          ;;
          "-${optionName}"*)
            if [ ${optionType} = ";" ]
            then # an option with no argument is in a chain of options
              optionOfs="${optionOfs}?" # move to the next option in the chain
              optionIndex=$(( optionIndex - 1 )) # the chain still has other options
              return 0
            else
              unset optionOfs
              optionArgument="${o#-${optionName}}"
              return 0
            fi
          ;;
        esac
      fi
    done
    echo "Error : invalid option : '${o}'." >&2
    usage 1
  fi; fi
  optionName="?"
  unset optionArgument
  return 1
}
function optionListex
{
  local l="${1}"
  local m # mask
  local r # to store result
  while [[ ${#m} -lt $((${#l}-1)) ]]; do m="$m?"; done # create a "???..." mask
  while [[ -n "$l" ]]
  do
    r="${r:+"$r "}${l%$m}" # append the first character of $l to $r
    l="${l#?}" # cut the first charecter from $l
    m="${m#?}"  # cut one "?" sign from m
    if [[ -n "${l%%[^:.;]*}" ]]
    then # a special character (";", ".", or ":") was found
      r="$r${l%$m}" # append it to $r
      l="${l#?}" # cut the special character from l
      m="${m#?}"  # cut one more "?" sign
    fi
  done
  echo "${r}"
}
function getopt()
{
  local optionList

  optionList="$(optionListex "${1}")"
  shift
  getoptex "${optionList}" "${@}"
  return ${?}
}
#######################################################################################
#######################################################################################
#######################################################################################




# Reload Apache 2 configuration only if valid.
#
# @return void
function apache2_reload {
  if command apache2ctl -t > /dev/null 2>&1; then
    if [ -n "$(command which 'service')" ]; then
      command service 'apache2' 'reload'
    else
      /etc/init.d/apache2 'reload'
    fi
  else
    cecho "red" "Error in Apache 2 configuration : reload cancelled."
    exit 1
  fi
} # apache2_reload



# Force Apache 2 configuration reload only if valid.
#
# @return void
function apache2_force_reload {
  if command apache2ctl -t > /dev/null 2>&1; then
    if [ -n "$(command which 'service')" ]; then
      command service 'apache2' 'force-reload'
    else
      /etc/init.d/apache2 'force-reload'
    fi
  else
    cecho "red" "Error in Apache 2 configuration : forced reload cancelled."
    exit 1
  fi

} # apache2_force_reload



# Check for binaries presence
check_binary 'basename' 'coreutils' > '/dev/null'
check_binary 'dirname' 'coreutils' > '/dev/null'
check_binary 'tar' 'tar' > '/dev/null'
check_binary 'mktemp' 'mktemp' > '/dev/null'
check_binary 'sed' 'sed' > '/dev/null'
check_binary 'apache2ctl' 'apache2' > '/dev/null'
#check_binary 'wget' 'wget' > '/dev/null'
#check_binary 'unzip' 'unzip' > '/dev/null'
#check_binary 'apg' 'apg' > '/dev/null'
#check_binary 'mysql' 'mysql-client' > '/dev/null'



# Setup defaults
mainTemplate="${PATH_TEMPLATE}"
customOptions=''
targetValueIsPath='1'
serverAlias=''
sslEnabled='0'
sslCertbot='0'
sslCertbotBasePath='/var/lib/a2tools'
sslBindIp=''
allowOverride='None'
isRemoval='0'
multiViews='+MultiViews'
proxySettings='connectiontimeout=600 timeout=4000 Keepalive=On'

sslAcmeSh="${HOME}/.acme.sh/acme.sh"
if [[ ! -e "${sslAcmeSh}" ]]; then
  sslAcmeSh="$(eval 'echo  ~root')/.acme.sh/acme.sh"
fi

declare -a xsendfilePaths
xsendfilePaths=()

typePrefix=""
protocol="http"

apacheModules=( "rewrite" "http2" )

# Parse options using getoptex from /usr/share/doc/bash-doc/examples/functions/getoptx.bash
while getoptex "help h template: t: ssl. s. alias: a: private-key: key: k: public-key: crt: p: root-ca: root: r: chain-ca: chain: c: bind-ip: ip: i: overrides: o: without-multiviews remove d xsendfile: xsf: custom-options: co: proxy-settings: ps:" "${@}"; do
  # Options debuging.
  # echo "Option <${optionName}> ${optionArgument:+has an arg <${optionArgument}>}"

  case "${optionName}" in
    'template' | 't' )

      case "${optionArgument}" in
        vhost | virtual-host )
          mainTemplate="${PATH_TEMPLATE}"
          targetValueIsPath="1"
          ;;
        rproxy | reverse-proxy )
          mainTemplate="${REVERSE_PROXY_TEMPLATE}"
          targetValueIsPath="0"
          apacheModules+=( "proxy_http" "proxy_http2" "headers" )
          ;;
        redirect )
          mainTemplate="${REDIRECT_TEMPLATE}"
          targetValueIsPath="0"
          typePrefix="redirect-"
          ;;
        custom )
          mainTemplate="CUSTOM"
          targetValueIsPath="0"
          ;;
      esac

      ;;
    'ssl' | 's' )
      sslEnabled="1"
      protocol="https"
      apacheModules+=( "ssl" )

      if [[ -n "${optionArgument}" ]]; then
        if [[ "${optionArgument}" = "auto" && -e "${sslAcmeSh}" ]]; then
          sslCertbot="1"
        fi

        if [[ -z "${sslPrivateKey}" \
            && -e "/etc/ssl/private/${optionArgument}.key" ]]; then
          sslPrivateKey="/etc/ssl/private/${optionArgument}.key"
        fi

        if [[ -z "${sslCompletePublicKey}" \
            && -e "/etc/ssl/certificates/${optionArgument}.crt+chain+root" ]]; then
          sslCompletePublicKey="/etc/ssl/certificates/${optionArgument}.crt+chain+root"
        fi

        if [[ -z "${sslPublicKey}" \
            && -e "/etc/ssl/certificates/${optionArgument}.crt" ]]; then
          sslPublicKey="/etc/ssl/certificates/${optionArgument}.crt"

          if [[ -z "${sslCompletePublicKey}" ]]; then
            sslCompletePublicKey="${sslPublicKey}"
          fi
        fi

        if [[ -z "${sslRootCa}" \
            && -e "/etc/ssl/roots/${optionArgument}-root.ca" ]]; then
          sslRootCa="/etc/ssl/roots/${optionArgument}-root.ca"
        fi

        if [[ -z "${sslChainCa}" \
            && -e "/etc/ssl/chains/${optionArgument}.ca" ]]; then
          sslChainCa="/etc/ssl/chains/${optionArgument}.ca"
        fi
      fi
      ;;
    'alias' | 'a' )
      if [ -n "${optionArgument}" ]; then
        serverAlias="${serverAlias} ${optionArgument}"
      fi
      ;;
    'private-key' | 'key' | 'k' )
      sslPrivateKey="$(realpath_check "${optionArgument}")"
      ;;
    'public-key' | 'crt' | 'p' )
      sslPublicKey="$(realpath_check "${optionArgument}")"
      sslCompletePublicKey="${sslPublicKey}"
      ;;
    'root-ca' | 'root' | 'r' )
      sslRootCa="$(realpath_check "${optionArgument}")"
      ;;
    'chain-ca' | 'chain' | 'c' )
      sslChainCa="$(realpath_check "${optionArgument}")"
      ;;
    'bind-ip' | 'ip' | 'i' )
      if [ -n "${optionArgument}" ]; then
        sslBindIp="${optionArgument}"
      fi
      ;;
    'overrides' | 'o' )
      if [ -n "${optionArgument}" ]; then
        allowOverride="${optionArgument}"
      fi
      ;;
    'without-multiviews' )
      multiViews="-MultiViews"
      ;;
    'remove' | 'd' )
      isRemoval="1"
      ;;

    'xsendfile' | 'xsf' )
      xsendfilePath="$(absolutepath "${optionArgument}")"
      xsendfilePaths+=("${xsendfilePath}")
      ;;

    'proxy-settings' | 'ps' )
      proxySettings="${optionArgument}"
      ;;

    'custom-options' | 'co' )
      customOptions="${customOptions}

${optionArgument}"
      ;;

    'help' | 'h' | '*' )
      usage 0
      ;;
  esac
done

# Discard processed options.
shift $((optionIndex - 1))

# Fetch script path.
scriptPath="$(realpath "${0}")"

# Process retro-compatibility.
case "${1}" in
  'add-virtual-host' | 'add-vhost' )
    ${scriptPath} --template="virtual-host" \
                    --overrides="${4}" \
                    "${2}" "${3}"
    exit 0
    ;;
  'add-reverse-proxy' | 'add-reverse' )
    ${scriptPath} --template="reverse-proxy" \
                    "${2}" "${3}"
    exit 0
    ;;
  'add-ssl-virtual-host' | 'add-ssl-vhost' )
    ${scriptPath} --template="virtual-host" --ssl \
                    --private-key="${4}" --public-key="${5}" \
                    --bind-ip="${6}" --overrides="${7}" \
                    "${2}" "${3}"
    exit 0
    ;;
  'add-ssl-reverse-proxy' )
    ${scriptPath} --template="reverse-proxy" --ssl \
                    --private-key="${4}" --public-key="${5}" \
                    --bind-ip="${6}" \
                    "${2}" "${3}"
    exit 0
    ;;
  'add-redirect' )
    ${scriptPath} --template="redirect" \
                    "${2}" "${3}"
    exit 0
    ;;
  'add-ssl-redirect' )
    ${scriptPath} --template="redirect" --ssl \
                    --private-key="${4}" --public-key="${5}" \
                    --bind-ip="${6}" \
                    "${2}" "${3}"
    exit 0
    ;;
  'add-custom' )
    ${scriptPath} --template="custom" \
                    "${2}" "${3}"
    exit 0
    ;;
  'add-ssl-custom' )
    ${scriptPath} --template="custom" --ssl \
                    --private-key="${4}" --public-key="${5}" \
                    --bind-ip="${6}" \
                    "${2}" "${3}"
    exit 0
    ;;
  'remove' )
    ${scriptPath} --remove "${2}"
    exit 0
    ;;
esac

siteHostname=${1}

if [ -z "${siteHostname}" ]; then
  cecho "red" "Error : no domain name has been provided." >&2
  usage 1
fi

if [ "${isRemoval}" = "1" ]; then
  # The --remove option has been given.
  # Process the virtualhosts removal.

  declare -a configFiles
  configFiles[0]="http-${siteHostname}.conf"
  configFiles[1]="https-${siteHostname}.conf"
  configFiles[2]="redirect-http-${siteHostname}.conf"
  configFiles[3]="redirect-https-${siteHostname}.conf"
  configFiles[4]="http-${siteHostname}"
  configFiles[5]="https-${siteHostname}"
  configFiles[6]="redirect-http-${siteHostname}"
  configFiles[7]="redirect-https-${siteHostname}"

  for configFile in "${configFiles[@]}"; do
    if [ -e "/etc/apache2/sites-available/${configFile}" ]; then
      command a2dissite "${configFile}"
      command rm "/etc/apache2/sites-available/${configFile}"
    fi
  done

  if command apache2ctl -t; then
    apache2_reload
    exit 0
  fi

else
  targetValue="${2}"

  # Compute the VirtualHost filename.
  vhostFile="${typePrefix}${protocol}-${siteHostname}.conf"

  # Setup defaults.
  sslProxyEngine="Off"

  # Setup certbot defaults.
  sslCertbotPath="${sslCertbotBasePath}/${siteHostname}"
  command mkdir -p "${sslCertbotPath}"
  if [ "${sslCertbot}" = "1" ]; then
    sslPrivateKey="/etc/ssl/private/${siteHostname}.key"
    sslCompletePublicKey="/etc/ssl/certificates/${siteHostname}.crt+chain+root"
    sslPublicKey="/etc/ssl/certificates/${siteHostname}.crt"
    sslChainCa="/etc/ssl/chains/${siteHostname}.ca"
  fi

  # Process X-SendFile options:
  if [[ ${#xsendfilePaths[@]} -gt 0 ]]; then
    xsendfileOptions=''

    for xsendfilePath in "${xsendfilePaths[@]}"; do
      xsendfileOptions="${xsendfileOptions}
${XSENDFILE_PATH_TEMPLATE//%_XSENDFILE_PATH_%/${xsendfilePath}}"
    done

    customOptions="${customOptions}

${XSENDFILE_TEMPLATE//%_XSENDFILE_OPTIONS_%/${xsendfileOptions}}"

    apacheModules+=( "xsendfile" )
  fi

  # For custom template, the target value is used as template.
  if [ "${mainTemplate}" = "CUSTOM" ]; then
    mainTemplate="${targetValue}"
    targetValue=""
  else
    # Check for target value existence if it is supposed to be a path.
    if [[ "${targetValueIsPath}" = "1" ]]; then
      targetValue="$(realpath_check "${targetValue}")"
    else
      targetValue=$(command echo "${targetValue}/" \
               | command sed -e 's|//$|/|')

      if [[ "${targetValue}" == "https:"* || "${targetValue}" == "h2:"* ]]; then
        sslProxyEngine="On"
      fi
    fi
  fi

  http2Template="${H2_SSL_TEMPLATE}"

  # Disable SSL template if not needed
  if [[ "${sslEnabled}" = "0" ]]; then
    SSL_TEMPLATE=""
    http2Template="${H2_TEMPLATE}"
  else
    if [[ "${sslCertbot}" = "0" && ( -z "${sslPrivateKey}" || -z "${sslPublicKey}" ) ]]; then
      cecho "red" "Error : --private-key and --public-key options are mandatory when --ssl is used." >&2
      usage 1
    fi

    # Detect IP for SSL vHost, if not provided.
    if [[ -z "${sslBindIp}" ]]; then
      # Try to detect existing SSL bind ip.
      sslBindIp="$(command grep '443' "/etc/apache2/sites-enabled/"* \
        | command head -n 1 \
        | command sed -e 's/^.*VirtualHost[\t ]*\([^:]*\):443.*$/\1/')"

      # If no SSL host is found, use "_default_" as bind IP.
      if [[ -z "${sslBindIp}" ]]; then
        sslBindIp="_default_"
      fi
    fi
  fi

  cecho "green" "Creating /etc/apache2/sites-available/${vhostFile}..."

  vhostContent="${VHOST_TEMPLATE[0]}
${LOGS_TEMPLATE}
${http2Template}
${SSL_TEMPLATE}
${CERTBOT_TEMPLATE}
${mainTemplate}
${customOptions}
${VHOST_TEMPLATE[1]}"



  # HTTP/2.0 preprocessing.
  if [[ "${targetValue}" == "h2c:"* || "${targetValue}" == "h2:"* ]]; then
  	# Preverse HTTP2 proxy Url.
  	http2TargetValue="${targetValue}"

  	# Create a safe HTTP(S) Url for HTTP/1.1 connexions.
  	targetValue="${targetValue//h2:/https:}"
  	targetValue="${targetValue//h2c:/http:}"

  	# Activate HTTP2 proxy rules lines, since target protocol is HTTP 2.0.
  	vhostContent="$(echo "${vhostContent}" \
    	| command sed -e 's|# For HTTP2 Only: ||g')"

  else
  	# Delete HTTP2 proxy rules lines, since target protocol is not HTTP 2.0.
  	vhostContent="$(echo "${vhostContent}" \
    	| command sed -e '/# For HTTP2 Only: /d')"
  fi



  vhostContent="$(echo "${vhostContent}" \
    | command sed -e "s|%_SITE_HOSTNAME_%|${siteHostname}|g" \
                  -e "s|%_SERVER_ALIAS_%|${serverAlias}|g" \
                  -e "s|%_TARGET_VALUE_%|${targetValue}|g" \
                  -e "s|%_HTTP2_TARGET_VALUE_%|${http2TargetValue}|g" \
                  -e "s|%_MULTI_VIEWS_%|${multiViews}|g" \
                  -e "s|%_ALLOW_OVERRIDE_%|${allowOverride}|g" \
                  -e "s|%_PROXY_SETTINGS_%|${proxySettings}|g" \
                  -e "s|%_SSL_PROXY_ENGINE_%|${sslProxyEngine}|" \
		          -e "s|%_FORWARDED_PROTOCOL_%|${protocol}|" \
                  -e "s|%_CERTBOT_PATH_%|${sslCertbotPath}/.well-known/acme-challenge/|g")"

  # Setup sed rules to enable ProxySet if needed.
  if [[ -n "${proxySettings}" ]]; then
    vhostContent="$(echo "${vhostContent}" \
      | command sed -e 's|#ProxySet|ProxySet|')"
  fi

  # Setup sed rules to enable ServerAlias if needed.
  if [[ -n "${serverAlias}" ]]; then
    vhostContent="$(echo "${vhostContent}" \
      | command sed -e 's|#ServerAlias|ServerAlias|')"
  fi

  # Apply SSL specific transforms.
  if [[ "${sslEnabled}" = "1" ]]; then

    # If no existing http configuration exists;
    if [[ ! ( -e "/etc/apache2/sites-enabled/http-${siteHostname}.conf" \
      || -e "/etc/apache2/sites-enabled/redirect-http-${siteHostname}.conf" ) ]]; then
      # Create HTTP redirect VirtualHost with Certbot Webroot path included (for certificate validation).
      command "${scriptName}" -t redirect "${siteHostname}" "https://${siteHostname}/"
    fi

    if [[ "${sslCertbot}" = "1" ]]; then

      # Create directories for keys.
      command mkdir -p "$(dirname "${sslPrivateKey}")"
      command mkdir -p "$(dirname "${sslPublicKey}")"
      command mkdir -p "$(dirname "${sslChainCa}")"
      command mkdir -p "$(dirname "${sslCompletePublicKey}")"

      # Use acme.sh to create a Let's Encrypt certificate for the domain.
      command bash "${sslAcmeSh}" --issue  --domain "${siteHostname}"  --webroot "${sslCertbotPath}"

      # Install the certificate on the system.
      command bash "${sslAcmeSh}" --installcert -d "${siteHostname}" \
            --keypath "${sslPrivateKey}" \
            --certpath "${sslPublicKey}" \
            --capath "${sslChainCa}" \
            --fullchainpath "${sslCompletePublicKey}" \
            --reloadcmd "service apache2 reload"

    fi

    vhostContent="$(echo "${vhostContent}" \
      | command sed -e "s|*:80|${sslBindIp}:443|g" \
        -e "s|%_CERTIFICATE_FILE_%|${sslPublicKey}|g" \
        -e "s|%_CERTIFICATE_COMPLETE_FILE_%|${sslCompletePublicKey}|g" \
        -e "s|%_KEY_FILE_%|${sslPrivateKey}|g")"

    if [ -n "${sslRootCa}" ]; then
    vhostContent="$(echo "${vhostContent}" \
      | command sed -e "s|%_ROOT_CA_FILE_%|${sslRootCa}|g" \
          -e "s|# SSLCACertificateFile|SSLCACertificateFile|g")"
    fi

    if [ -n "${sslChainCa}" ]; then
    vhostContent="$(echo "${vhostContent}" \
      | command sed -e "s|%_CHAIN_FILE_%|${sslChainCa}|g" \
          -e "s|# SSLCertificateChainFile|SSLCertificateChainFile|g")"
    fi
  fi

  echo "${vhostContent}" > "/etc/apache2/sites-available/${vhostFile}"

  command a2enmod "${apacheModules[@]}" > /dev/null

  command a2ensite "${vhostFile}"

  # Disable the site if there is a configuration error.
  if command apache2ctl -t; then
    apache2_reload
    exit 0
  else
    command a2dissite "${vhostFile}"
  fi
fi

exit 0