#!/bin/bash

#
# OUTPUT FUNCTIONS
#

function echov()
{
  if [ 0$VERBOSITY -ge 1 ]; then
    echo "$@"
  fi
}

function echod()
{
  if [ 0$VERBOSITY -ge 2 ]; then
    echo "$@"
  fi
}

#
# CONFIGURATION
#

# PATH environment variable for running tests
if [ -n "$BINARY_PATH" ]; then
   BP=''
   for bp in $(echo $BINARY_PATH | sed 's@:@ @'); do
     if [ -e "$bp" ]; then
       BP="$BP:$(readlink -f $bp)"
     else
       echo "WARNING: $PWD/$bp" does not exist
     fi
   done
   BINARY_PATH="$BP"
fi
echod "BINARY PATH: $BINARY_PATH"

PATH="$BINARY_PATH:/bin/:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin"

echod "OVERALL BINARY PATH: $PATH"

# used MPM
if [ -z "$HTTPD_MPM" ]; then
  HTTPD_MPM=prefork
fi

# module PATH
if [ -n "$MODULE_PATH" ]; then
   MP=''
   for mp in $(echo $MODULE_PATH | sed 's@:@ @'); do
     if [ -e "$mp" ]; then
       MP="$MP:$(readlink -f $mp)"
     else
       echo "WARNING: $PWD/$mp" does not exist
     fi
   done
   MODULE_PATH="$MP"
fi
MODULE_PATH="$MODULE_PATH:/usr/lib64/apache2:/usr/lib64/apache2-$HTTPD_MPM:/usr/lib/apache2:/usr/lib/apache2-$HTTPD_MPM"
echod "MODULES PATH: $MODULE_PATH"

# writeable dir for running the example
if [ -z "$RUN_DIR_BASE" ]; then
  RUN_DIR_BASE=/tmp/apache-rex/
fi
# we will pushd to example dir, thus we need
# absolute path
mkdir -p $RUN_DIR_BASE
RUN_DIR_BASE=$(readlink -f $RUN_DIR_BASE)

echod "BASE RUN DIR: $RUN_DIR_BASE"

# ports where httpd can listen from (start + 6)
if [ -z "$HTTP_PORT_START" ]; then
  HTTP_PORT_START=60080
fi

# port where ftp daemon can listen
if [ -z "$FTP_PORT" ]; then
  FTP_PORT=60021
fi

# port where fcgi starter can listen
if [ -z "$FCGI_PORT" ]; then
  FCGI_PORT=60009
fi

# port where scgi starter can listen
if [ -z "$SCGI_PORT" ]; then
  SCGI_PORT=60004
fi

# port where uwsgi daemon can listen
if [ -z "$UWSGI_PORT" ]; then
  UWSGI_PORT=60005
fi

# port where second uwsgi daemon can listen
if [ -z "$UWSGI_PORT2" ]; then
  UWSGI_PORT2=60006
fi

if [ -z "$OCSP_PORT"]; then
  OCSP_PORT=60888
fi

if [ -z "$PGSQL_PORT"]; then
  PGSQL_PORT=65432
fi

# verbosity of the output: normal=0, verbose=1, debug=2
if [ -z "$VERBOSITY" ]; then
  VERBOSITY=0
fi

if [ -z "$MYSQL_ADMIN" ]; then
  MYSQL_ADMIN=$USER
fi

echod

#
# HELPER FUNCTIONS
#

function usage()
{
  echo "Run example or examples of httpd configuration."
  echo
  echo "$0 \"<example-dir-list>\""
  echo "$0 <contents-file>"
  echo
  echo "  <example-dir-list> is a list of examples to be"
  echo "  run (e. g. $0 \"*proxy* 03*\")"
  echo "  <contents-file> is list examples dirs to be run"
  echo "  in that order"
}

function find_module()
{
  # name can be a regular expression, e. g. php[5,7]
  name=$1
  for mp in $(echo $MODULE_PATH $AREX_RUN_DIR | tr ':' ' '); do
    if [ -e $mp ]; then
      find $mp -regextype sed -regex ".*/mod_$name.so"
    fi
  done | tail -n 1
}

function find_module_symbol()
{
  # name can be a regular expression, e. g. php[5,7]
  path=$1
  name=$2
  # \?: exception: php8 have php_module
  objdump -T $path | grep "[[:space:]]${name}\?_module$" | sed "s:.*\(${name}\?_module\).*:\1:"
}

function command_exists()
{
  if [ -x $AREX_BINDIR/$1 ]; then
    return 0
  fi
  return 1
}

function register_command()
{
  command_path=$1
  command=$2
  [ -n "$command_path" ] && ln -sf $command_path $AREX_BINDIR/$command
}
#
# SYSTEM CHECK
#

function httpd_command()
{
  which httpd-$HTTPD_MPM httpd2-$HTTPD_MPM httpd httpd2 2>/dev/null | head -n 1
}

function apxs_command()
{
  which apxs-$HTTPD_MPM apxs2-$HTTPD_MPM apxs apxs2 2>/dev/null | head -n 1
}

function objdump_command()
{
  which objdump 2>/dev/null | head -n 1
}

function htpasswd_command()
{
  which htpasswd htpasswd2 2>/dev/null | head -n 1
}

function htdbm_command()
{
  which htdbm htdbm2 2>/dev/null | head -n 1
}

function rotatelogs_command()
{
  which rotatelogs rotatelogs2 2>/dev/null | head -n 1
}

function httxt2dbm_command()
{
  which httxt2dbm 2>/dev/null
}

function check_forensic_command()
{
  which check_forensic 2>/dev/null
}

function curl_command()
{
  which curl 2>/dev/null
}

function curl_have_resolve()
{
  { curl --manual || curl --help; } | grep -q '\--resolve' && echo -n 1 || echo -n 0
}

function curl_have_cert_status()
{
  { curl --manual || curl --help; } | grep -q '\--cert-status' && echo -n 1 || echo -n 0
}

function curl_have_http2()
{
  ldd $(curl_command) | grep -q 'libnghttp2' && echo -n 1 || echo -n 0
}

function sed_command()
{
  which sed 2>/dev/null
}

function sqlite_command()
{
  which sqlite sqlite3 2>/dev/null | head -n 1
}

function ps_command()
{
  which ps 2>/dev/null | head -n 1
}

function kill_command()
{
  which kill 2>/dev/null | head -n 1
}

function gdb_command()
{
  which gdb 2>/dev/null | head -n 1
}

function telnet_command()
{
  which telnet 2>/dev/null | head -n 1
}

function wget_command()
{
  which wget 2>/dev/null | head -n 1
}

function nc_command()
{
  which nc 2>/dev/null
}

function mysql_install_db_command
{
  which mysql_install_db 2>/dev/null
}

function mysqld_command
{
  which mysqld 2>/dev/null
}

function mysqladmin_command
{
  which mysqladmin 2>/dev/null
}

function mysql_command
{
  which mysql 2>/dev/null
}

function python_command()
{
  which python3 python 2>/dev/null | head -n 1
}

function sysctl_command()
{
  which sysctl 2>/dev/null
}

function coredumpctl_command()
{
  which coredumpctl 2>/dev/null | head -n 1
}

function openssl_command()
{
  which openssl 2>/dev/null
}

function softhsm2_command()
{
  which softhsm2-util 2>/dev/null
}

function pkcs11_tool_command()
{
  which pkcs11-tool 2>/dev/null
}

function nss_pcache_command()
{
  which nss_pcache 2>/dev/null
}

function lsof_command()
{
  which lsof 2>/dev/null
}

function vsftpd_command()
{
  which vsftpd 2>/dev/null
}

function vsftpd_version()
{
  vsftpd -v 0>&1 | sed 's:.*\([0-9]\+\.[0-9]\+\.[0-9]\+\).*:\1:' | tr -d '.'
}

function uwsgi_command()
{
  which uwsgi 2>/dev/null
}

UWSGI_PLUGINS_DIRS='/usr/lib64/uwsgi /usr/lib/uwsgi'

function uwsgi_plugin()
{
  plugin=$1
  ls $UWSGI_PLUGINS_DIRS 2>/dev/null | grep $1 | head -n 1 | sed 's/_plugin.so//'
}

function openssl_an_engine()
{
  openssl engine | grep -v dynamic | head -n 1 | sed 's:^(\([^()]*\)).*:\1:'
}

function softhsm2_so()
{
  ls /usr/lib{,64}/pkcs11/libsofthsm2.so /usr/lib{,64}/shofthsm2/libsofthsm2.so 2>/dev/null | head -n 1
}

function openssl_have_alpn()
{
  objdump -T $(ldconfig -p | grep 'libssl\.so\.' | sed 's:.* => ::') | grep alpn > /dev/null && echo -n '1' || echo -n '0'
}

function have_python_tornado()
{
  python -c "help('modules');" | grep tornado > /dev/null && echo -n '1' || echo -n '0'
}

function have_perl_scgi()
{
  perldoc -l "SCGI" > /dev/null 2>&1 && echo -n '1' || echo -n '0'
}

function have_python_websocket_client()
{
  python -c "help('modules');" | grep websocket > /dev/null && echo -n '1' || echo -n '0'
}

function core_pattern()
{
  cat /proc/sys/kernel/core_pattern 2>/dev/null
}

function have_systemd_coredump()
{
  # check whether: 1. system is configured to dump coredumps into systemd-coredump
  #                2. coredumpctl command exists and user have enough privileges to gather dumps 
  if sysctl kernel.core_pattern | grep -q 'systemd-coredump' && \
     coredumpctl list 2>&1 | sed "s,\x1B\[[0-9;]*[a-zA-Z],,g" | grep -q 'TIME\|No coredumps found.'; then
    echo '1'
  else 
    echo '0'
  fi
}

function check_system()
{
  export AREX_BINDIR="$RUN_DIR_BASE/bin"
  if [ -d $AREX_BINDIR ]; then
    rm $AREX_BINDIR/*
  else
    mkdir $AREX_BINDIR
  fi
  # take symlinks created into account
  export PATH="$AREX_BINDIR:$PATH"

  echod -n "Checking the system "
  echod -n '.'
  HTTPD_COMMAND=$(httpd_command)
  register_command "$HTTPD_COMMAND"       httpd
  echod -n '.'
  APXS_COMMAND=$(apxs_command)
  register_command "$APXS_COMMAND"        apxs
  echod -n '.'
  OBJDUMP_COMMAND=$(objdump_command)
  register_command "$OBJDUMP_COMMAND"     objdump
  echod -n '.'
  HTPASSWD_COMMAND=$(htpasswd_command)
  register_command "$HTPASSWD_COMMAND"    htpasswd
  echod -n '.'
  HTDBM_COMMAND=$(htdbm_command)
  register_command "$HTDBM_COMMAND"       htdbm
  ROTATELOGS_COMMAND=$(rotatelogs_command)
  register_command "$ROTATELOGS_COMMAND"  rotatelogs
  echod -n '.'
  HTTXT2DBM_COMMAND=$(httxt2dbm_command)
  register_command "$HTTXT2DBM_COMMAND"   httxt2dbm
  echod -n '.'
  CHECK_FORENSIC_COMMAND=$(check_forensic_command)
  register_command "$CHECK_FORENSIC_COMMAND" check_forensic
  echod -n '.'
  CURL_COMMAND=$(curl_command)
  register_command "$CURL_COMMAND"        curl
  echod -n '.'
  SQLITE_COMMAND=$(sqlite_command)
  register_command "$SQLITE_COMMAND"      sqlite
  echod -n '.'
  PS_COMMAND=$(ps_command)
  register_command "$PS_COMMAND"          ps
  echod -n '.'
  KILL_COMMAND=$(kill_command)
  register_command "$KILL_COMMAND"        kill
  echod -n '.'
  GDB_COMMAND=$(gdb_command)
  register_command "$GDB_COMMAND"         gdb
  echod -n '.'
  TELNET_COMMAND=$(telnet_command)
  register_command  "$TELNET_COMMAND"     telnet
  echod -n '.'
  WGET_COMMAND=$(wget_command)
  register_command "$WGET_COMMAND"        wget
  echod -n '.'
  NC_COMMAND=$(nc_command)
  register_command "$NC_COMMAND"          nc
  echod -n '.'
  MYSQL_INSTALL_DB_COMMAND=$(mysql_install_db_command)
  register_command "$MYSQL_INSTALL_DB_COMMAND"    mysql_install_db
  echod -n '.'
  MYSQLD_COMMAND=$(mysqld_command)
  register_command "$MYSQLD_COMMAND"      mysqld
  echod -n '.'
  MYSQLADMIN_COMMAND=$(mysqladmin_command)
  register_command "$MYSQLADMIN_COMMAND"  mysqladmin
  echod -n '.'
  MYSQL_COMMAND=$(mysql_command)
  register_command "$MYSQL_COMMAND"       mysql
  echod -n '.'
  PYTHON_COMMAND=$(python_command)
  register_command "$PYTHON_COMMAND"      python
  echod -n '.'
  SYSCTL_COMMAND=$(sysctl_command)
  register_command "$SYSCTL_COMMAND"      sysctl
  echod -n '.'
  COREDUMPCTL_COMMAND=$(coredumpctl_command)
  register_command "$COREDUMPCTL_COMMAND" coredumpctl
  echod -n '.'
  OPENSSL_COMMAND=$(openssl_command)
  register_command "$OPENSSL_COMMAND"     openssl
  echod -n '.'
  SOFTHSM2_COMMAND=$(softhsm2_command)
  register_command "$SOFTHSM2_COMMAND"    softhsm2
  echod -n '.'
  PKCS11_TOOL_COMMAND=$(pkcs11_tool_command)
  register_command "$PKCS11_TOOL_COMMAND" pkcs11-tool
  echod -n '.'
  NSS_PCACHE_COMMAND=$(nss_pcache_command)
  register_command "$NSS_PCACHE_COMMAND"  nss_pcache
  echod -n '.'
  LSOF_COMMAND=$(lsof_command)
  register_command "$LSOF_COMMAND"        lsof
  echod -n '.'
  VSFTPD_COMMAND=$(vsftpd_command)
  register_command "$VSFTPD_COMMAND"      vsftpd
  UWSGI_COMMAND=$(uwsgi_command)
  register_command "$UWSGI_COMMAND"       uwsgi
  echod -n '.'
  # filter definitions need full path, otherwise
  # it is expected that sed is in the PATH
  export AREX_SED_COMMAND=$(sed_command)
  echod -n '.'
  # piped logs require full path to rotatelogs
  export AREX_ROTATELOGS_COMMAND=$ROTATELOGS_COMMAND
  echod -n '.'
  export AREX_CURL_HAVE_RESOLVE=$(curl_have_resolve)
  echod -n '.'
  export AREX_CURL_HAVE_CERT_STATUS=$(curl_have_cert_status)
  echod -n '.'
  export AREX_CURL_HAVE_HTTP2=$(curl_have_http2)
  echod -n '.'
  if command_exists openssl; then
    export AREX_AN_OPENSSL_ENGINE=$(openssl_an_engine)
    echod -n '.'
  fi
  if command_exists openssl; then
    export AREX_OPENSSL_HAVE_ALPN=$(openssl_have_alpn)
    echod -n '.'
  fi
  if command_exists softhsm2; then
    export AREX_SOFTHSM2_SO=$(softhsm2_so)
  fi
  if command_exists python; then
    export AREX_HAVE_PYTHON_TORNADO=$(have_python_tornado)
    echod -n '.'
  fi
  export AREX_HAVE_PERL_SCGI=$(have_perl_scgi)
  echod -n '.'
  if command_exists python; then
    export AREX_HAVE_PYTHON_WEBSOCKET_CLIENT=$(have_python_websocket_client)
    echod -n '.'
  fi
  export AREX_CORE_PATTERN=$(core_pattern)
  echod -n '.'
  if command_exists coredumpctl; then
    export AREX_HAVE_SYSTEMD_COREDUMP=$(have_systemd_coredump)
  fi
  if command_exists vsftpd; then
    echod -n '.'
    export AREX_VSFTPD_VERSION=$(vsftpd_version)
  fi
  export AREX_UWSGI_PLUGIN_HTTP=$(uwsgi_plugin http)
  echod -n '.'
  export AREX_UWSGI_PLUGIN_PYTHON=$(uwsgi_plugin python)
  echod -n '.'
  echod ' done.'
  
  echod "httpd command ............................ $HTTPD_COMMAND"
  echod "apxs command ............................. $APXS_COMMAND"
  echod "htpasswd command ......................... $HTPASSWD_COMMAND"
  echod "htdbm command ............................ $HTDBM_COMMAND"
  echod "rotatelogs command ....................... $AREX_ROTATELOGS_COMMAND"
  echod "httxt2dbm command ........................ $HTTXT2DBM_COMMAND"
  echod "check_forensic command ................... $CHECK_FORENSIC_COMMAND"
  echod "curl command ............................. $CURL_COMMAND"
  echod "curl have --resolve switch ............... $AREX_CURL_HAVE_RESOLVE"
  echod "curl have --cert-status switch ........... $AREX_CURL_HAVE_CERT_STATUS"
  echod "curl have HTTP2 protocol ................. $AREX_CURL_HAVE_HTTP2"
  echod "sed command .............................. $AREX_SED_COMMAND"
  echod "sqlite command ........................... $SQLITE_COMMAND"
  echod "ps command ............................... $PS_COMMAND"
  echod "kill command ............................. $KILL_COMMAND"
  echod "gdb command .............................. $GDB_COMMAND"
  echod "telnet command ........................... $TELNET_COMMAND"
  echod "wget command ............................. $WGET_COMMAND"
  echod "nc command ............................... $NC_COMMAND"
  echod "python command ........................... $PYTHON_COMMAND"
  echod "sysctl command ........................... $SYSCTL_COMMAND"
  echod "coredumpctl command ...................... $COREDUMPCTL_COMMAND"
  echod "openssl command .......................... $OPENSSL_COMMAND"
  echod "softhsm2 command ......................... $SOFTHSM2_COMMAND"
  echod "pkcs11-tool command ...................... $PKCS11_TOOL_COMMAND"
  echod "openssl engine ........................... $AREX_AN_OPENSSL_ENGINE"
  echod "openssl have alpn support ................ $AREX_OPENSSL_HAVE_ALPN"
  echod "softhsm2 shared object ................... $AREX_SOFTHSM2_SO"
  echod "nss_pcache command ....................... $NSS_PCACHE_COMMAND"
  echod "lsof command ............................. $LSOF_COMMAND"
  echod "vsftpd command ........................... $VSFTPD_COMMAND"
  echod "vsftpd version ........................... $AREX_VSFTPD_VERSION"
  echod "uwsgi command ............................ $UWSGI_COMMAND"
  echod "uwsgi http plugin ........................ $AREX_UWSGI_PLUGIN_HTTP"
  echod "uwsgi python plugin ...................... $AREX_UWSGI_PLUGIN_PYTHON"
  echod "system have python-tornado ............... $AREX_HAVE_PYTHON_TORNADO"
  echod "system have perl-scgi .................... $AREX_HAVE_PERL_SCGI"
  echod "system have python-websocket-client ...... $AREX_HAVE_PYTHON_WEBSOCKET_CLIENT"
  echod "system have systemd coredumps ............ $AREX_HAVE_SYSTEMD_COREDUMP"
  echod "system core pattern ...................... $AREX_CORE_PATTERN"
  echod

  #
  # following two are neccessary for running examples
  #

  if [ -z "$HTTPD_COMMAND" ]; then
    echo 'fatal: httpd binary not found' >&2
    exit 10
  fi

  if [ -z "$OBJDUMP_COMMAND" ]; then
    echo 'fatal: objdump binary not found' >&2
    exit 10
  fi

  if [ -z "$CURL_COMMAND" ]; then
    echo 'fatal: curl binary not found' >&2
    exit 10
  fi
}

#
# RUN ENVIRONMENT
#

function prepare_env()
{
  echod 'Setting environment for running examples.'

  export AREX_APACHE_VERSION=$(httpd -v | grep 'Server version' | sed 's:.*Apache/\([0-9\.]\+\).*:\1:' | tr '.' ' ' | { read maj min patch; printf "%d%02d%02d" $maj $min $patch;})
  echod "httpd version ............................ $AREX_APACHE_VERSION"

  export AREX_MPM=$HTTPD_MPM
  echod "httpd MPM ................................ $AREX_MPM"

  export AREX_PORT=$HTTP_PORT_START
  echod "httpd will listen on ..................... $AREX_PORT"
  export AREX_PORT1=$((HTTP_PORT_START+1))
  export AREX_PORT2=$((HTTP_PORT_START+2))
  export AREX_PORT3=$((HTTP_PORT_START+3))
  export AREX_PORT4=$((HTTP_PORT_START+4))
  export AREX_PORT5=$((HTTP_PORT_START+5))
  export AREX_PORT6=$((HTTP_PORT_START+6))
  echod "other available ports ...................."\
    "$AREX_PORT1 $AREX_PORT2 $AREX_PORT3 $AREX_PORT4 $AREX_PORT5 $AREX_PORT6"

  export AREX_FTP_PORT=$FTP_PORT
  echod "ftp daemon will listen on ................ $AREX_FTP_PORT"

  export AREX_FCGI_PORT=$FCGI_PORT
  echod "fcgi daemon will listen on ............... $AREX_FCGI_PORT"

  export AREX_SCGI_PORT=$SCGI_PORT
  echod "scgi daemon will listen on ............... $AREX_SCGI_PORT"

  export AREX_UWSGI_PORT=$UWSGI_PORT
  echod "uwsgi daemon will listen on .............. $AREX_UWSGI_PORT"

  export AREX_UWSGI_PORT2=$UWSGI_PORT2
  echod "second uwsgi daemon will listen on ....... $AREX_UWSGI_PORT2"

  export AREX_OCSP_PORT=$OCSP_PORT
  echod "ocsp responedr daemon will listen on ..... $AREX_OCSP_PORT"

  export AREX_PGSQL_PORT=$PGSQL_PORT
  echod "postgress daemon will listen on .......... $AREX_PGSQL_PORT"
  
  echod -n "authz_core module present ................ "
  if [ -n "$(find_module authz_core)" ]; then
    echod '1'
    export AREX_ALLOW_FROM_LOCALHOST="Require local"
    export AREX_DENY_FROM_ALL="Require all denied"
    export AREX_ALLOW_FROM_ALL="Require all granted"
  else
    echod '0'
    export AREX_ALLOW_FROM_LOCALHOST="Order deny,allow\nDeny from all\nAllow from localhost 127.0.0.1"
    export AREX_DENY_FROM_ALL="Order deny,allow\nDeny from all"
    export AREX_ALLOW_FROM_ALL="Order allow,deny\nAllow from all"
  fi

  export AREX_USER=$USER
  echod "httpd user ............................... $AREX_USER"
  export AREX_GROUP=$(id -gn)
  echod "httpd user group ......................... $AREX_GROUP"

  export AREX_MYSQL_ADMIN="$MYSQL_ADMIN"
  echod "mysql admin .............................. $AREX_MYSQL_ADMIN"
  echod
}

function check_open_ports()
{
  echod -n "checking that requested ports are not already opened .. " 
  if ! command_exists lsof; then
    echod "lsof does not exist, could not check"
  else
    lsof -i > $RUN_DIR_BASE/.opened_ports

    grep -q ":$AREX_PORT (LISTEN)"  $RUN_DIR_BASE/.opened_ports && { echo "fatal: $AREX_PORT is already opened";  exit 9; }
    grep -q ":$AREX_PORT1 (LISTEN)" $RUN_DIR_BASE/.opened_ports && { echo "fatal: $AREX_PORT1 is already opened"; exit 9; }
    grep -q ":$AREX_PORT2 (LISTEN)" $RUN_DIR_BASE/.opened_ports && { echo "fatal: $AREX_PORT2 is already opened"; exit 9; }
    grep -q ":$AREX_PORT3 (LISTEN)" $RUN_DIR_BASE/.opened_ports && { echo "fatal: $AREX_PORT3 is already opened"; exit 9; }
    grep -q ":$AREX_PORT4 (LISTEN)" $RUN_DIR_BASE/.opened_ports && { echo "fatal: $AREX_PORT4 is already opened"; exit 9; }
    grep -q ":$AREX_PORT5 (LISTEN)" $RUN_DIR_BASE/.opened_ports && { echo "fatal: $AREX_PORT5 is already opened"; exit 9; }
    grep -q ":$AREX_PORT6 (LISTEN)" $RUN_DIR_BASE/.opened_ports && { echo "fatal: $AREX_PORT6 is already opened"; exit 9; }
 
    echod "done"
  fi
  echod
}

#
# RUN LOOP (it is possible to run more examples)
#

function compile_modules()
{
  modules="$(cat MODULES | tr ':' ' ')"
  module_dir=$AREX_RUN_DIR/my_modules

  # there is a rule: when for $m mod_$m.c
  # exist, then we will compile it
  # and load to httpd
  for m in $modules; do
    if [ -e mod_$m.c ]; then
      mkdir -p $module_dir
      cp mod_$m.c $module_dir

      echo -n "Building '$m' module .. "
      apxs -c $module_dir/mod_$m.c >$module_dir/mod_$m.log 2>&1
      if [ ! -e $module_dir/.libs/mod_$m.so ]; then
        echo "FAILED:" 
        cat $module_dir/mod_$m.log
        echo
      fi
      echo "$module_dir/.libs/mod_$m.so."; echo 

    fi
  done
}

function fix_module_name()
{
  name=$1;
  if   [ $HTTPD_MPM == 'prefork' -a $name == 'cgid' ]; then 
    echo -n 'cgi'
  elif [ $HTTPD_MPM != 'prefork' -a $name == 'cgi' ]; then
    echo -n 'cgid'
  else
    echo -n $name
  fi
}

function load_modules()
{
  conf=$1

  modules="auth_basic dir authz_host $(cat MODULES | tr ':' ' ')"
  modules_opt="authz_core"
  if [ -e MODULES_OPT ]; then
    modules_opt="$modules_opt $(cat MODULES_OPT | tr ':' ' ')"
  fi

  for m in $modules; do
    m=$(fix_module_name $m)
    # ensure we have an absolute path
    module_path=$(readlink -f $(find_module $m))
    echov "Will load module $m ($module_path)"
    module_symbol=$(find_module_symbol $module_path $m)
    if [ -z "$module_symbol" ]; then
      echo "fatal: ${m}_module symbol not found in $module_path"
      exit 8
    fi
    echo "LoadModule ${module_symbol} $module_path"    >> $conf
  done

  for m in $modules_opt; do
    m=$(fix_module_name $m)
    module_path=$(find_module $m)
    if [ -n "$module_path" ]; then
      echov "Will load module $m ($module_path)"
      echo "LoadModule ${m}_module $module_path"  >> $conf
    fi
  done
}

function prepare_run()
{
  dir=$1

  # prepare config
  conf=$AREX_RUN_DIR/httpd.conf
  echo "ServerName test"                                > $conf
  echo "User $AREX_USER"                               >> $conf
  echo "Group $AREX_GROUP"                             >> $conf
  echo "Listen $AREX_PORT"                             >> $conf
  echo "PidFile $AREX_RUN_DIR/pid"                     >> $conf
  echo "ErrorLog $AREX_RUN_DIR/error_log"              >> $conf
  load_modules $conf
  if [ $AREX_MPM != 'prefork' ]; then
    echo "<IfModule mod_cgid.c>"                         >> $conf
    echo "  ScriptSock $AREX_RUN_DIR/cgid.sock"          >> $conf
    echo "</IfModule>"                                   >> $conf
  fi
  export AREX_DOCUMENT_ROOT=$AREX_RUN_DIR/htdocs
  echo "DocumentRoot $AREX_DOCUMENT_ROOT"              >> $conf
  echo "DirectoryIndex index.html"                     >> $conf
  echo                                                 >> $conf
  echo "### example configuration"                     >> $conf
  echo                                                 >> $conf

  if [ -e example.conf.in ]; then
    cat example.conf.in >> $AREX_RUN_DIR/httpd.conf

    sed -i "s:@AREX_BINDIR@:$AREX_BINDIR:"                             $AREX_RUN_DIR/httpd.conf
    sed -i "s:@AREX_RUN_DIR@:$AREX_RUN_DIR:"                           $AREX_RUN_DIR/httpd.conf
    sed -i "s:@AREX_USER@:$AREX_USER:"                                 $AREX_RUN_DIR/httpd.conf
    sed -i "s:@AREX_GROUP@:$AREX_GROUP:"                               $AREX_RUN_DIR/httpd.conf
    sed -i "s:@AREX_PORT@:$AREX_PORT:"                                 $AREX_RUN_DIR/httpd.conf
    sed -i "s:@AREX_PORT1@:$AREX_PORT1:"                               $AREX_RUN_DIR/httpd.conf
    sed -i "s:@AREX_PORT2@:$AREX_PORT2:"                               $AREX_RUN_DIR/httpd.conf
    sed -i "s:@AREX_PORT3@:$AREX_PORT3:"                               $AREX_RUN_DIR/httpd.conf
    sed -i "s:@AREX_PORT4@:$AREX_PORT4:"                               $AREX_RUN_DIR/httpd.conf
    sed -i "s:@AREX_PORT5@:$AREX_PORT5:"                               $AREX_RUN_DIR/httpd.conf
    sed -i "s:@AREX_PORT6@:$AREX_PORT6:"                               $AREX_RUN_DIR/httpd.conf
    sed -i "s:@AREX_DOCUMENT_ROOT@:$AREX_DOCUMENT_ROOT:"               $AREX_RUN_DIR/httpd.conf
    sed -i "s:@AREX_ALLOW_FROM_LOCALHOST@:$AREX_ALLOW_FROM_LOCALHOST:" $AREX_RUN_DIR/httpd.conf
    sed -i "s:@AREX_DENY_FROM_ALL@:$AREX_DENY_FROM_ALL:"               $AREX_RUN_DIR/httpd.conf
    sed -i "s:@AREX_ALLOW_FROM_ALL@:$AREX_ALLOW_FROM_ALL:"             $AREX_RUN_DIR/httpd.conf
    sed -i "s:@AREX_AN_OPENSSL_ENGINE@:$AREX_AN_OPENSSL_ENGINE:"       $AREX_RUN_DIR/httpd.conf
    sed -i "s:@AREX_FTP_PORT@:$AREX_FTP_PORT:"                         $AREX_RUN_DIR/httpd.conf
    sed -i "s:@AREX_FCGI_PORT@:$AREX_FCGI_PORT:"                       $AREX_RUN_DIR/httpd.conf
    sed -i "s:@AREX_SCGI_PORT@:$AREX_SCGI_PORT:"                       $AREX_RUN_DIR/httpd.conf
    sed -i "s:@AREX_UWSGI_PORT@:$AREX_UWSGI_PORT:"                     $AREX_RUN_DIR/httpd.conf
    sed -i "s:@AREX_UWSGI_PORT2@:$AREX_UWSGI_PORT2:"                   $AREX_RUN_DIR/httpd.conf
    sed -i "s:@AREX_OCSP_PORT@:$AREX_OCSP_PORT:"                       $AREX_RUN_DIR/httpd.conf
    sed -i "s:@AREX_PGSQL_PORT@:$AREX_PGSQL_PORT:"                     $AREX_RUN_DIR/httpd.conf
    sed -i "s:@AREX_SED_COMMAND@:$AREX_SED_COMMAND:"                   $AREX_RUN_DIR/httpd.conf
    sed -i "s:@AREX_ROTATELOGS_COMMAND@:$AREX_ROTATELOGS_COMMAND:"     $AREX_RUN_DIR/httpd.conf
  else
    cat example.conf >> $AREX_RUN_DIR/httpd.conf
  fi

  # run preparation script of the example
  if [ -e pre-run.sh ]; then
    cp pre-run.sh $AREX_RUN_DIR
    sh $AREX_RUN_DIR/pre-run.sh
  fi

  # source $AREX_RUN_DIR/server_environment, which pre-run.sh
  # can write
  if [ -e $AREX_RUN_DIR/server_environment ]; then
    . $AREX_RUN_DIR/server_environment
  fi

  # create default DocumentRoot
  mkdir -p $AREX_RUN_DIR/htdocs

  # server flags
  SERVER_FLAGS=""
  if [ -e SERVERFLAGS ]; then
    SERVER_FLAGS="$(cat SERVERFLAGS)"
  fi
}

function finish_run()
{
  # run cleanup script of the example
  if [ -e post-run.sh ]; then
    cp post-run.sh $AREX_RUN_DIR
    sh $AREX_RUN_DIR/post-run.sh
  fi
}

function syntax_check()
{
  syntax_ok=1
  httpd -f $AREX_RUN_DIR/httpd.conf $SERVER_FLAGS -t 2>$AREX_RUN_DIR/syntax_check || syntax_ok=0
  echo $syntax_ok
}

function start_apache()
{
  start_ok=1

  httpd -f $AREX_RUN_DIR/httpd.conf $SERVER_FLAGS -k start 2>$AREX_RUN_DIR/start_log || start_ok=0
  if [ $start_ok -ne 1 ]; then
    echo '0'
    return
  fi
  # wait for the apache to settle down
  sleep 2
  if [ ! -e $AREX_RUN_DIR/pid ]; then
    echo '0'
    return
  fi
  
  echo '1'
  return
}

function run()
{
  cp run.sh $AREX_RUN_DIR
  run_exit_code=0
  sh $AREX_RUN_DIR/run.sh 2>&1 || run_exit_code=$?
}

function stop_apache()
{
  pid=$(cat $AREX_RUN_DIR/pid)
  kill -TERM $pid
  sleep 1
  if [ -e $AREX_RUN_DIR/pid ]; then
    kill -KILL -$pid || true
    sleep 1
    if [ -e $AREX_RUN_DIR/pid ]; then
      echo '0'
      return
    fi
  fi
  echo '1'
}

function missing_binaries()
{
  req_binaries="$(cat BINARIES 2>/dev/null)"

  # check if apxs is needed, too
  modules="$(cat MODULES | tr ':' ' ')"
  for m in $modules; do
    # there is a rule: when for $m mod_$m.c
    # exist, then we will compile it
    # (see compile_modules() function)
    # and load to httpd
    if [ -e mod_$m.c ]; then
      req_binaries="$req_binaries apxs"
      break
    fi
  done

  result=""
  for b in $req_binaries; do
    if [ -z "$(which $b 2>/dev/null)" ]; then
      result="$result$b "
    fi
  done

  echo $result
}

function missing_modules()
{
  req_modules=$(cat MODULES | tr ':' ' ')
  result=""

  for m in $req_modules; do
    m=$(fix_module_name $m)
    if [ -z "$(find_module $m)" ]; then
      result="$result$m "
    fi
  done

  echo $result
}

run_result=255
function run_example()
{
  dir=$1
  n=$2
  count=$3
  run=$(echo $dir | sed 's:^[0-9]*-::')

  # run started
  pushd $dir > /dev/null

  echo "==========================================================="
  echo "EXAMPLE $(basename $dir) ($n/$count)"
  echo
  cat DESCRIPTION
  echo "-----------------------------------------------------------"
  echo

  AREX_RUN_DIR="$RUN_DIR_BASE/$(basename $dir)"

  # merge double slashes (// -> /), 
  # essential e. g. for core-DirectoryMatch-* examples
  AREX_RUN_DIR=$(echo $AREX_RUN_DIR | sed 's://:/:g')
  export AREX_RUN_DIR

  # prepare dir to write
  [ -e $AREX_RUN_DIR ] && rm -r $AREX_RUN_DIR
  mkdir -p $AREX_RUN_DIR

  mb=$(missing_binaries)
  if ! [[ $mb =~ 'apxs' ]]; then
    compile_modules
  fi
  mm=$(missing_modules)

  if [ -e skip.sh ] && sh skip.sh; then
    echo 'SKIPPED'
    head -n 1 skip.sh | sed 's:#::'
    run_result=1
  elif [ -n "$mb" ]; then
    echo 'SKIPPED'
    echo "Following utilities are missing: $mb"
    run_result=1
  elif [ -n "$mm" ]; then
    echo 'SKIPPED'
    echo "Following modules are missing: $mm"
    run_result=1
  else
    # prepare the config, dir, run pre-run.sh, etc.
    prepare_run $dir

    # check the syntax of the example configuration
    echo -n 'Checking syntax ... '
    syntax_ok="$(syntax_check)"
    if [ "$syntax_ok" == '1' ]; then
      echo 'OK.'
    else
      echo 'FAILURE.'
      echo
      echo 'SKIPPED'
      cat $AREX_RUN_DIR/syntax_check
      run_result=1
    fi

    start_ok='0'
    if [ "$syntax_ok" == '1' ]; then
      echo -n 'Starting httpd ... '
      start_ok=$(start_apache)
      if [ "$start_ok" == '1' ]; then
        cat $AREX_RUN_DIR/pid
      else
        echo 'SKIPPED'
        echo 'start_log:'
        cat $AREX_RUN_DIR/start_log
        echo 'error_log:'
        cat $AREX_RUN_DIR/error_log
        run_result=1
      fi
    fi

    if [ "$start_ok" == '1' ]; then
      echov
      echov 'Running the example'
      echov
      echov "+++++++++ $AREX_RUN_DIR/httpd.conf ++++++++++"
      if [ $VERBOSITY -ge 1 ]; then
        cat  $AREX_RUN_DIR/httpd.conf
      fi
      echov "+++++++++ $AREX_RUN_DIR/httpd.conf ++++++++++"
      echov
      echov "++++++++++ $AREX_RUN_DIR/run.sh ++++++++++"
      if [ $VERBOSITY -ge 1 ]; then
        cat run.sh
      fi
      echov "++++++++++ $AREX_RUN_DIR/run.sh ++++++++++"
      echov 
      echo
      run
      echo
      if [ $run_exit_code == 0 ]; then
        echo "Example result: PASSED"
        run_result=0
      else
        echo "Example FAILED (subexample #$run_exit_code):"
        for log in $AREX_RUN_DIR/error_log*; do
          echo "++++++++++ $log ++++++++++"
          cat  $log
          echo "++++++++++ $log ++++++++++"
          echo
        done
        run_result=2
      fi
      echo
  
      echo -n 'Stopping httpd ... '
      stop_ok=$(stop_apache)
      if [ $stop_ok == '1' ]; then
        echo OK.
      else
        # should not happen
        echo FAILURE.
      fi
    fi

    finish_run
 
    echo
    echo "See for details: $AREX_RUN_DIR"
  fi

  echo "==========================================================="

  popd > /dev/null
}

function run_examples()
{
  dirs=$1

  passed=""
  skipped=""
  failed=""

  count=$(echo $dirs | wc -w)
  n=0

  overall_exit_code=0

  # iterate over examples
  for dir in $dirs; do
    n=$((n+1))
    if [ ! -e $dir ]; then
      echo "error: $dir not found"
      continue
    fi
    if [ ! -d $dir ] || 
       [ ! -e $dir/run.sh ] || 
       [ ! -e $dir/DESCRIPTION ] || 
       [ ! -e $dir/MODULES ] || 
       [ ! -e $dir/example.conf* ]; then
      echo "warning: $dir does not look like example dir, skipping"
      continue
    fi
    run_example $dir $n $count
    case $run_result in
      0) passed="$passed$dir " ;;
      1) skipped="$skipped$dir "
         [ $overall_exit_code -lt 1 ] && overall_exit_code=1
         ;;
      2) failed="$failed$dir "
         [ $overall_exit_code -lt 2 ] && overall_exit_code=2
         ;;
      # should not happen
      *) echo 'wrong return code from run_example()'; exit 10;;
    esac
  done

  echo
  echo "SUMMARY"
  echo "======="
  echo "PASSED:  $(echo $passed | wc -w)"
  if [ -n "$passed" ]; then
    echov "PASSED RUNS:  $passed"
  fi
  echo "FAILED:  $(echo $failed  | wc -w)"
  if [ -n "$failed" ]; then
    echov "FAILED RUNS:  $failed"
  fi
  echo "SKIPPED: $(echo $skipped | wc -w)"
  if [ -n "$skipped" ]; then
    echov "SKIPPED RUNS: $skipped"
  fi
  echo -n "OVERALL RESULT: "
  case $overall_exit_code in 
    0) echo "PASS ($(echo $passed | wc -w) OK)" ;;
    1) echo "PASS ($(echo $passed | wc -w) OK, $(echo $skipped | wc -w) SKIPPED)" ;;
    2) echo "FAIL ($(echo $failed | wc -w) KO, $(echo $skipped | wc -w) SKIPPED)" ;;
  esac
}

if [ "$1" == "-h" -o "$1" == "--help" -o "$1" == "" ]; then
  usage
  exit 0
elif [ -f "$1" ]; then
  dirs=$(cat $1 | tr '\n' ' ')
else
  dirs="$@"
fi

check_system
prepare_env
check_open_ports
run_examples "$dirs"

exit $overall_exit_code

