#!/bin/bash
#
# Author: Mark Lamourine <markllama@gmail.com>
# Copyright 2012
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#   http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# This script checks the status of a host that is running an Openshift Origins
# Broker service.
#
# Check that the broker components are installed and communicating properly
#
OSO_CONF_DIR=${OSO_CONF_DIR:=/etc/openshift}

OSO_BROKER_ROOT=${OSO_BROKER_ROOT:=/var/www/openshift/broker}

OSO_CFG_DIR=${OSO_BROKER_ROOT}/config
OSO_HTTPD_DIR=${OSO_BROKER_ROOT}/httpd
OSO_HTTPD_CONF_DIR=${OSO_HTTPD_DIR}/conf.d
OSO_ENV_DIR=${OSO_CFG_DIR}/environments
OSO_INI_DIR=${OSO_CFG_DIR}/initializers

BROKER_CONF="/etc/openshift/broker.conf"
CONSOLE_CONF="/etc/openshift/console.conf"

if [ -e '/etc/openshift/development' ] ; then
  OSO_ENVIRONMENT=${OSO_ENVIRONMENT:=development}
  BROKER_CONF="/etc/openshift/broker-dev.conf"
  CONSOLE_CONF="/etc/openshift/console-dev.conf"
else
  OSO_ENVIRONMENT=${OSO_ENVIRONMENT:=production}
fi

AUTH_PLUGINS="mongo"
NAME_PLUGINS="bind"
MESG_PLUGINS="mcollective"

DEFAULT_PACKAGES="
  rubygem-openshift-origin-common
  rubygem-openshift-origin-controller
  openshift-origin-broker
"

DEFAULT_SERVICES="openshift-broker"

# Depends on OSO_BROKER_ROOT in ruby lib search list
OSO_RUBY_LIBS="openshift-origin-controller config/application"

if [ -z "$PACKAGES" ]
then
    PACKAGES="$DEFAULT_PACKAGES"
else
    echo WARNING: ENV overrides PACKAGES >&2
fi

if [ -z "$SERVICES" ]
then
    SERVICES="$DEFAULT_SERVICES"
else
    echo WARNING: ENV overrides SERVICES >&2
fi

BROKER_REST_API_BASE="https://localhost/broker/rest/"

function safe_bundle() {
    bundle exec "$@"
}

USE_SCL=""

if which scl 1> /dev/null 2> /dev/null && scl -l | grep -q '^ruby193$' ; then
    USE_SCL="TRUE"
    function safe_bundle() {
        scl enable ruby193 "bundle exec \"$@\""
    }

    PACKAGES+="  ruby193-rubygem-rails
  ruby193-rubygem-passenger
  ruby193-rubygems
"
else
    PACKAGES+="  rubygem-rails
  rubygem-passenger
  rubygems
"
fi


# ============================================================================
#
# Utility Functions
#
# ============================================================================

function verbose() {
    # MESSAGE=$*

    if [ -n "$VERBOSE" ]
    then
	echo "INFO: $*"
    fi
}

function notice() {
    # MESSAGE=$*
    echo "NOTICE: $*"
}

function fail() {
    # MESSAGE=$*
    echo "FAIL: $*" >&2
    STATUS=$(($STATUS + 1))
}


# ==========================================================================
#
# ==========================================================================
function probe_version() {
    # Find out who owns /var/www/openshift/broker

    BROKER_RPM=$(rpm -qf $OSO_BROKER_ROOT --qf '%{NAME}\n' 2>/dev/null)
    if [ -z "$BROKER_RPM" ]
    then
      fail "broker Rails app root ${OSO_BROKER_ROOT} does not exist: no broker package"
    else
      verbose "Broker package is: $BROKER_RPM"
    fi
}

function check_ruby_requirements() {
    verbose checking ruby requirements
    if [ -z "${USE_SCL}" -a ! -x /usr/bin/ruby ]
    then
      fail "ruby is not installed or not executable"
      return
    fi
    if ! /usr/bin/which oo-ruby 1> /dev/null 2> /dev/null
    then
      fail "oo-ruby wrapper is not in path"
      return
    fi
    for OSO_RUBY_LIB in $*
    do
      verbose checking ruby requirements for $OSO_RUBY_LIB
        GEM_ERROR=`oo-ruby -I ${OSO_BROKER_ROOT} -r rubygems -- <<EOF
            require 'bundler'
            begin
              require '$OSO_RUBY_LIB'
            rescue Bundler::GemNotFound => gem_error
              puts "Gem Not Found for $OSO_RUBY_LIB: #{gem_error}"
              exit 1
            end
EOF`
        if [ $? -ne 0 ]
        then
           fail module $OSO_RUBY_LIB -- "$GEM_ERROR"
        fi
    done
}

function check_logfile_perms() {
    if [ "${APP_VALUES[ENABLE_USER_ACTION_LOG]}" = "true" ]
    then
      USER_ACTION_LOG_FILE=${APP_VALUES[USER_ACTION_LOG_FILE]}
      if [ ! -f "${USER_ACTION_LOG_FILE}" ]
      then
        fail "Rest API Logging enabled but the log file does not exist -- run touch ${USER_ACTION_LOG_FILE} && chown apache:apache ${USER_ACTION_LOG_FILE} && chmod 640 ${USER_ACTION_LOG_FILE}"
      else
        if [ `ls -l ${USER_ACTION_LOG_FILE} | awk '{print $3}'` != 'apache' ]
        then
          fail "Rest API Logging enabled but the log file has wrong owner -- run chown apache ${USER_ACTION_LOG_FILE}"
        fi
      fi
    fi
}

function check_mcollective_client_perms() {
    MCOLLECTIVE_CFG="/opt/rh/ruby193/root/etc/mcollective/client.cfg"
    if ! stat -c %U:%G $MCOLLECTIVE_CFG | grep apache:apache 2>&1 > /dev/null
    then
      fail "ERROR: $MCOLLECTIVE_CFG should be owned by apache:apache"
    fi

    if ! stat -c %a $MCOLLECTIVE_CFG | grep "0$" 2>&1 > /dev/null
    then
      fail "ERROR: $MCOLLECTIVE_CFG should not be world accessible"
    fi
}


function check_missing_and_insecure_secrets() {
    if [ "${APP_VALUES[AUTH_SALT]}" = "ClWqe5zKtEW4CJEMyjzQ" ]
    then
      fail "Default AUTH_SALT detected in ${BROKER_CONF}! Change the the value and use oo-admin-broker-auth to regenerate authentication tokens. (Hint: use 'openssl rand -base64 64' to generate a unique salt)"
    fi

    if [ "${APP_VALUES[SESSION_SECRET]}" = "nil" -o "${APP_VALUES[SESSION_SECRET]}" = "" ]
    then
      fail "SESSION_SECRET must be set in $BROKER_CONF (Hint: use 'openssl rand -hex 64' to generate a unique secret."
    fi

    if [ -f ${CONSOLE_CONF} ]
    then
      CONSOLE_SECRET="${APP_VALUES[CONSOLE_SESSION_SECRET]}"
      if [ "$CONSOLE_SECRET" = "nil" -o "$CONSOLE_SECRET" = "" ]
      then
        fail "SESSION_SECRET must be set in ${CONSOLE_CONF} (Hint: use 'openssl rand -hex 64' to generate a unique secret."
      fi
    fi
}

#
# Get a set of values from the Rails application.
# Yes, this is inefficient, but the point is to not depend on the system
# you're examining (Ruby/Rails) to confirm itself.
#

function load_application_values() {
  # Build ruby program statements from global APP_VALUE_MAP and
  # read the output values into the global APP_VALUES hash.
  # This is all predicated on each value landing on one line;
  # if there are any multiline values, we're in trouble.
  # Also if there are any errors this won't work. Which is why we
  # can't get all the values in one go - some exist conditionally.

  ruby_program=""
  local keys=( ${!APP_VALUE_MAP[@]} )
  for key in ${keys[@]}
  do
    ruby_program+="
      puts ${APP_VALUE_MAP[$key]}"
  done

  # run those statements after initializing the Rails env
  local OLD_IFS=$IFS; IFS=$'\n' # read into array one line per element
        local tempfile=`mktemp`  # for recording errors
  local new_app_values # separate so we keep rc from command
  readarray -t new_app_values < <(run_ruby_rails_command "$ruby_program" 2> $tempfile; echo $?)
  RETVAL=${new_app_values[${#new_app_values[@]}-1]}
  unset new_app_values[${#new_app_values[@]}-1]
        [ -s $tempfile ] && notice "stderr output loading broker settings:" `cat $tempfile | head -n 5`
        rm -f $tempfile
  IFS=$OLD_IFS # back to normal
  if [ $RETVAL -ne 0 ]
  then
    fail "Error while loading broker settings.
#####
$ruby_program
#####
${new_app_values[@]}
#####"
                return 1
  elif (( ${#new_app_values[@]} != ${#keys[@]} ))
  then
    fail "wrong number of values loaded from broker settings.
This is a bug in oo-accept-broker. Debug output follows:
keys: ${#keys[@]}; values: ${#new_app_values[@]}
#####
$ruby_program
#####
${new_app_values[@]}
#####"
                return 1
  fi

  # now read the array of output ruby values into the values hash
  for (( i=0 ; $i < ${#keys[@]} ; i++ ))
  do
    APP_VALUES["${keys[$i]}"]=${new_app_values[$i]}
  done
  return 0
}

function run_ruby_rails_command() {
  # $1 = ruby statements to run after initialization
  if [ -z "$OSO_BROKER_ROOT" ]
  then
      fail OSO_BROKER_ROOT is unset.  Cannot load app configuration
      exit $STATUS
  fi
  if [ -z "$OSO_ENVIRONMENT" ]
  then
      fail OSO_ENVIRONMENT is unset.  Cannot load app configuration
      exit $STATUS
  fi
  if which oo-ruby 2>/dev/null >/dev/null
  then
      pushd /var/www/openshift/broker > /dev/null
      safe_bundle "ruby -I ${OSO_BROKER_ROOT} -r rubygems --" <<EOF
        begin
          require 'config/environment'
          require 'config/environments/$OSO_ENVIRONMENT'
          broker_conf = OpenShift::Config.new('$BROKER_CONF')
          console_conf = OpenShift::Config.new('$CONSOLE_CONF') if File.exists? '$CONSOLE_CONF'
        rescue Bundler::GemNotFound => gem_error
          \$stderr.puts "Error loading libs or gems: #{gem_error}"
          exit 1
        end
        $1
        exit 0
EOF
      RETVAL=$?
      popd > /dev/null
      return $RETVAL
  else
      fail "oo-ruby is not installed or not executable"
      exit $STATUS
  fi
}


# ============================================================================
#
# Base Packages and Components
#
# ============================================================================

# Network Manager (disabled or not present)

# network "service" enabled

# SSH max connections

# Kernel Settings
#  - Ephemeral Port Range
#  - Kernel Semaphores (httpd communications)
#  - netfilter conntrack buffer size

# IPTables

# ============================================================================
#
# Configuration and Variables
# 
# ============================================================================

#
# Check packages
#
function check_packages() {
    # $* = $PACKAGES
    verbose checking packages
    for PKGNAME in $*
    do
      verbose checking package $PKGNAME
      PKGSTATUS=`rpm -q $PKGNAME`
      if [[ $? == "1" ]]
      then
        fail "package $PKGNAME is not installed"
      fi
    done
}


#
# There are two different service monitors:  RHEL6 still uses service and chkconfig
# Fedora 16+ use systemctl (as part of systemd)
#
function service_enabled() {
  # $1 = SERVICE_NAME

  if [ -x /bin/systemctl -a ! -x /etc/init.d/$1 ]
  then
      systemctl is-enabled $1.service 2>&1 >/dev/null
  else
      chkconfig $1
  fi

  if [ $? != 0 ]
  then
      fail "service $1 not enabled;"
  fi
}

function service_running() {
  # $1 = SERVICE_NAME

  if [ -x /bin/systemctl ]
  then
      systemctl status $1.service 2>&1 | grep -e "^\s*Active: active" >/dev/null
  else
      service $1 status 2>&1 > /dev/null
  fi

  if [ $? != 0 ]
  then
      fail "service $1 not running"
  fi
}


#
# Check services
#
function check_services() {
    verbose checking services
    for SVCNAME in $SERVICES
    do
        service_enabled $SVCNAME
        service_running $SVCNAME
    done
}

function check_selinux_enforcing() {
  verbose "checking that selinux modules are loaded"
  if [ `getenforce 2>/dev/null` != "Enforcing" ]
  then
    # if not enforcing, just say so
    notice "SELinux is" `getenforce`
    return
  fi

  SEMODULE=$(which semodule 2>/dev/null)
  if [ $? != 0 ]
  then
    fail "semodule binary not present (from policycoreutils RPM)"
    return
  fi
}


function check_firewall() {
  verbose "checking firewall settings"


  # check for 'enabled'
  service_enabled iptables
  service_running iptables
  # Need to check for ports TCP/22, TCP/53, TCP/80, TCP/443 and UDP/53 allowed
}

#
# Check SELinux
#
function check_selinux_booleans() {
  verbose "checking that selinux booleans are correct"
  if [ `getenforce 2>/dev/null` != "Enforcing" ]
  then
    # if not enforcing, just say so
    notice "SELinux is" `getenforce`
    return
  fi

  SELINUX_BOOLEANS='httpd_execmem httpd_unified httpd_can_network_connect httpd_can_network_relay httpd_run_stickshift'
  for bool in $SELINUX_BOOLEANS
  do
    if getsebool "$bool" | grep -e '--> on' >/dev/null
    then
      verbose SELinux boolean "$bool" is enabled
    else
      fail SELinux boolean "$bool" is disabled -- run setsebool -P ${bool}=on
    fi
  done


  # Only needed with BIND DDNS plugin
  if getsebool allow_ypbind | grep -e '--> on' >/dev/null
  then
    verbose SELinux boolean allow_ypbind is enabled
  else
    notice "SELinux boolean allow_ypbind is disabled -- run setsebool -P allow_ypbind=on"
  fi

}

# ============================================================================
#
# DataStore
#
# ============================================================================

function check_mongo_login() {
    # $HOST_PORT=$1
    # $DB=$2
    # $USER=$3
    # $PASS=$4
    # $SSL=$5
    verbose checking mongo db login access
    ssl_opt=""
    if [ "$5" == "true" ]
    then
        ssl_opt="--ssl"
    fi
    mongo $1/$2 --username $3 --password $ssl_opt<<EOF 2>&1 >/dev/null 
$4
exit
EOF

    if [ $? -eq 0 ]
    then
        verbose "mongo db login successful: $1/$2 --username $3"
    else
        fail "error logging into mongo db: $1/$2 --username $3, exit code: $?"
    fi
}

function check_datastore_mongo() {
    verbose checking mongo datastore configuration

    # get the service hostname, port, username, password
    APP_VALUE_MAP=(
      ["DS_HOST"]="Rails.application.config.datastore[:host_port].split(',')[0].split(':')[0]"
      ["DS_PORT"]="Rails.application.config.datastore[:host_port].split(',')[0].split(':')[1]"
      ["DS_USER"]="Rails.application.config.datastore[:user]"
      ["DS_PASS"]="Rails.application.config.datastore[:password]"
      ["DS_NAME"]="Rails.application.config.datastore[:db]"
      ["DS_SSL"]="Rails.application.config.datastore[:ssl]"
    )
    load_application_values || return 1
    verbose "Datastore Host: ${APP_VALUES[DS_HOST]}"
    verbose "Datastore Port: ${APP_VALUES[DS_PORT]}"
    verbose "Datastore User: ${APP_VALUES[DS_USER]}"
    verbose "Datastore SSL: ${APP_VALUES[DS_SSL]}"
    if [ "${APP_VALUES[DS_PASS]}" == "mooo" ]
    then
      fail "Datastore Password has been left configured as the default 'mooo'
    -- please reconfigure and ensure the DB user's password matches."
    else
        verbose "Datastore Password has been set to non-default"
    fi
    verbose "Datastore DB Name: ${APP_VALUES[DS_NAME]}"

    # Only check local values if DS_HOST is localhost
    if [ "${APP_VALUES[DS_HOST]}" == "localhost" ]
    then
      verbose "Datastore configuration is on localhost"

      # check presence of mongodb package
      check_packages mongodb

        # check auth enabled
        if grep -e '^auth = true' /etc/mongodb.conf >/dev/null
        then
            verbose "LOCAL: mongod auth is enabled"
        else
            fail "LOCAL: mongod auth is not enabled in /etc/mongodb.conf;
    -- please add a line with 'auth = true' and restart mongod."
        fi

        # check service enabled
        if (service mongod status 2>&1 >/dev/null \
        || ( chkconfig --level 2 mongod && chkconfig --level 3 mongod \
        && chkconfig --level 4 mongod && chkconfig --level 5 mongod  ) )

        then
          verbose "LOCAL: mongod service enabled"
        else
          fail "LOCAL: mongod service not enabled"
        fi

        # check service started
        if (service mongod status 2>/dev/null | grep 'running' 2>&1 >/dev/null)
        then
          verbose "LOCAL: mongod service running"
        else
          fail "LOCAL: mongod service not running"
        fi

        # check OpenShift mongo ssl configuration
        if [ "${APP_VALUES[DS_SSL]}" == "true" ]
        then
            if grep -e '^sslOnNormalPorts = true' /etc/mongodb.conf >/dev/null
            then
                verbose "LOCAL: mongodb ssl connections are enabled"
            else
                fail "LOCAL: OpenShift broker SSL connections to mongo is enabled;
                -- please update /etc/mongodb.conf with sslOnNormalPorts = true"
            fi
        else
            if grep -e '^sslOnNormalPorts = true' /etc/mongodb.conf >/dev/null
            then
                fail "LOCAL: Although the broker.conf has mongo SSL connections turned off,
                mongodb is configured to only accept SSL connection;
                -- please update /etc/mongodb.conf with sslOnNormalPorts = false"
            fi
        fi
    else
        verbose "Datastore: mongo db service is remote"
    fi

    # check OpenShift Origin user (username/password)
    check_mongo_login "${APP_VALUES[DS_HOST]}:${APP_VALUES[DS_PORT]}" ${APP_VALUES[DS_NAME]} ${APP_VALUES[DS_USER]} ${APP_VALUES[DS_PASS]} ${APP_VALUES[DS_SSL]}
}

function check_auth_mongo() {
    verbose checking mongo auth configuration

    # get the service hostname, port, username, password
  APP_VALUE_MAP=(
    ["DS_HOST"]="Rails.application.config.datastore[:host_port].split(',')[0].split(':')[0]"
    ["DS_PORT"]="Rails.application.config.datastore[:host_port].split(',')[0].split(':')[1]"
    ["DS_USER"]="Rails.application.config.auth[:mongo_user]"
    ["DS_PASS"]="Rails.application.config.auth[:mongo_password]"
    ["DS_NAME"]="Rails.application.config.auth[:mongo_db]"
    ["DS_SSL"]="Rails.application.config.auth[:mongo_ssl]"
    )
    load_application_values || return 1
    verbose "Auth Host: ${APP_VALUES[DS_HOST]}"
    verbose "Auth Port: ${APP_VALUES[DS_PORT]}"
    verbose "Auth User: ${APP_VALUES[DS_USER]}"
    verbose "Auth SSL: ${APP_VALUES[DS_SSL]}"
    if [ "${APP_VALUES[DS_PASS]}" == "mooo" ]
    then
      fail "Datastore Password has been left configured as the default 'mooo'
    -- please reconfigure and ensure the DB user's password matches."
    else
        verbose "Datastore Password has been set to non-default"
    fi
    verbose "Auth DB Name: ${APP_VALUES[DS_NAME]}"

    # Only check local values if DS_HOST is localhost
    if [ "${APP_VALUES[DS_HOST]}" = "localhost" ]
    then
      verbose "Auth configuration is on localhost"

      # check presence of mongodb package
      check_packages mongodb

        # check auth enabled
        if grep -e '^auth = true' /etc/mongodb.conf >/dev/null
        then
            verbose "LOCAL: mongod auth is enabled"
        else
          fail "LOCAL: mongod auth is not enabled in /etc/mongodb.conf"
        fi

        # check service enabled
        if (service mongod status 2>/dev/null | grep enabled 2>&1 >/dev/null \
            || chkconfig --list 2>/dev/null | grep '^mongod.*2:on.*3:on.*4:on.*5:on' 2>&1 >/dev/null)
        then
          verbose "LOCAL: mongod service enabled"
        else
          fail "LOCAL: mongod service not enabled"
        fi

        # check service started
        if (service mongod status 2>/dev/null | grep 'running' 2>&1 >/dev/null)
        then
          verbose "LOCAL: mongod service running"
        else
          fail "LOCAL: mongod service not running"
        fi
    else
        verbose "Auth: mongo db service is remote"
    fi

    # check OpenShift Origin user (username/password)
    check_mongo_login "${APP_VALUES[DS_HOST]}:${APP_VALUES[DS_PORT]}" ${APP_VALUES[DS_NAME]} ${APP_VALUES[DS_USER]} ${APP_VALUES[DS_PASS]} ${APP_VALUES[DS_SSL]}
}

function get_http_response_code() {
    curl -k -s -o /dev/null -w '%{http_code}'  "$1"
}

assert_rest_api_returns() {
    if [ "$1" = "$(get_http_response_code "$2")" ]
    then
        verbose "Got HTTP $1 response from $2"
    else
        fail "Did not get expected HTTP $1 response from $2"
    fi
}

function check_auth_remote_user() {
    verbose checking remote-user auth configuration
    verbose "Auth trusted header: ${APP_VALUES[RU_TRUSTED_HEADER]}"

    if grep -q 'BrowserMatchNoCase\s\+\^OpenShift\s\+passthrough' ${OSO_HTTPD_CONF_DIR}/*.conf \
       && grep -q 'SetEnvIf\s\+broker_auth_key' ${OSO_HTTPD_CONF_DIR}/*.conf \
       && grep -q 'Allow\s\+from\s\+env=passthrough' ${OSO_HTTPD_CONF_DIR}/*.conf \
       && grep -q 'SetEnvIfNoCase\s\+Authorization\s\+Bearer\s\+passthrough' ${OSO_HTTPD_CONF_DIR}/*.conf
    then
        verbose 'Auth passthrough is enabled for OpenShift services'
    else
        fail 'Auth passthrough appears not to be enabled, which will break JBossTools and node-to-broker authentication and authentication tokens'
    fi

    UNAUTHENTICATED_APIS="api cartridges"
    AUTHENTICATED_APIS="user domains"

    for api in $UNAUTHENTICATED_APIS
    do
        assert_rest_api_returns 200 "${BROKER_REST_API_BASE}${api}"
    done

    for api in $AUTHENTICATED_APIS
    do
        assert_rest_api_returns 401 "${BROKER_REST_API_BASE}${api}"
    done
}
# ============================================================================
#
# Cloud User Authentication
#
# ============================================================================

function check_authentication() {
    verbose checking cloud user authentication

    AUTH_PLUGIN=${APP_VALUES[oo_auth_provider]}
    verbose auth plugin = $AUTH_PLUGIN
    case $AUTH_PLUGIN in
        'OpenShift::AuthService')
          fail "configured auth class is the abstract one: $AUTH_PLUGIN"
      ;;
        'OpenShift::MongoAuthService')
          verbose "auth plugin: $AUTH_PLUGIN"
          check_auth_mongo
      ;;

        'OpenShift::RemoteUserAuthService')
          verbose "auth plugin: $AUTH_PLUGIN"
          check_auth_remote_user
      ;;

        *)
          notice unknown auth class: $AUTH_PLUGIN
      ;;
    esac
    unset AUTH_PLUGIN
}

# ============================================================================
#
# Dynamic DNS Updates
#
# ============================================================================

function dns_bind_update_record() {
    # $1 = auth type: key, krb, or unauthenticated
    # $2 = server
    # $3 = key name
    # $4 = key value
    # $5 = key algorithm
    # $6 = Kerberos keytab
    # $7 = Kerberos principal
    # $8 = function (add|delete)
    # $9 = type (A, TXT, CNAME)
    # $10 = name
    # $11 = value

    # check $2: should be an IP address
    # check $4: should be a key string
    # check $8: should be add|delete
    # check $9 should be A, TXT, CNAME

    verbose "${8}ing $9 record named ${10} to server $2: ${11}"
    if [ "$1" = "key" ]
    then
        nsupdate <<EOF
server $2
key $5:$3 $4
update $8 ${10} 1 $9 ${11}
send
EOF
    elif [ "$1" = "krb" ]
    then
        kinit -kt "$6" -p "$7"
        nsupdate -g <<EOF
server $2
update $8 ${10} 1 $9 ${11}
send
EOF
    else
        nsupdate <<EOF
server $2
update $8 ${10} 1 $9 ${11}
send
EOF
    fi

    if [ $? != 0 ]
    then
      fail "error ${8}ing $9 record name ${10} to server $2: ${11}
    -- is the nameserver running, reachable, and $1 auth working?"
    fi

}

function check_dns_bind() {
    verbose checking bind dns plugin configuration

    APP_VALUE_MAP=(
      ["DNS_SERVER"]="Rails.application.config.dns[:server]"
      ["DNS_PORT"]="Rails.application.config.dns[:port].to_s"
      ["DNS_ZONE"]="Rails.application.config.dns[:zone]"
      ["DNS_SUFFIX"]="Rails.application.config.openshift[:domain_suffix]"
      ["DNS_AUTH"]='(not Rails.application.config.dns[:keyname].blank?)?"key":(not Rails.application.config.dns[:krb_principal].blank?)?"krb":"unauthenticated"'
    )
    load_application_values || return 1

    verbose "DNS Server: ${APP_VALUES[DNS_SERVER]}"
    verbose "DNS Port: ${APP_VALUES[DNS_PORT]}"
    verbose "DNS Zone: ${APP_VALUES[DNS_ZONE]}"
    verbose "DNS Domain Suffix: ${APP_VALUES[DNS_SUFFIX]}"
    verbose "DNS Update Auth: ${APP_VALUES[DNS_AUTH]}"

    if [ "${APP_VALUES[DNS_AUTH]}" = "key" ]
    then
        APP_VALUE_MAP=(
            ["DNS_KEYNAME"]="Rails.application.config.dns[:keyname]"
            ["DNS_KEYVAL"]="Rails.application.config.dns[:keyvalue]"
            ["DNS_KEYALGORITHM"]="Rails.application.config.dns[:keyalgorithm]"
        )
        load_application_values || return 1
        verbose "DNS Key Name: ${APP_VALUES[DNS_KEYNAME]}"
        verbose "DNS Key Value: *****"
        verbose "DNS Key Algorithm: ${APP_VALUES[DNS_KEYALGORITHM]}"
    elif [ "${APP_VALUES[DNS_AUTH]}" = "krb" ]
    then
        APP_VALUE_MAP=(
            ["DNS_KRB_KEYTAB"]="Rails.application.config.dns[:krb_keytab]"
            ["DNS_KRB_PRINCIPAL"]="Rails.application.config.dns[:krb_principal]"
        )
        load_application_values || return 1
        verbose "DNS Kerberos Keytab: ${APP_VALUES[DNS_KRB_KEYTAB]}"
        verbose "DNS Kerberos Principal: ${APP_VALUES[DNS_KRB_PRINCIPAL]}"
    fi

    # check that zone suffix ends exactly with dns_zone (zone contains suffix)

    # try to add a dummy TXT record to the zone
    dns_bind_update_record ${APP_VALUES[DNS_AUTH]} ${APP_VALUES[DNS_SERVER]} "${APP_VALUES[DNS_KEYNAME]}" "${APP_VALUES[DNS_KEYVAL]}" "${APP_VALUES[DNS_KEYALGORITHM]}" "${APP_VALUES[DNS_KRB_KEYTAB]}" "${APP_VALUES[DNS_KRB_PRINCIPAL]}" add txt testrecord.${APP_VALUES[DNS_SUFFIX]} this_is_a_test

    # verify that the record is there
    if host -t txt testrecord.${APP_VALUES[DNS_SUFFIX]} ${APP_VALUES[DNS_SERVER]} >/dev/null
    then
        verbose "txt record successfully added"
    else
        fail "txt record testrecord.${APP_VALUES[DNS_SUFFIX]} does not resolve on server ${APP_VALUES[DNS_SERVER]}"
    fi

    # remove it.
    dns_bind_update_record ${APP_VALUES[DNS_AUTH]} ${APP_VALUES[DNS_SERVER]} "${APP_VALUES[DNS_KEYNAME]}" "${APP_VALUES[DNS_KEYVAL]}" "${APP_VALUES[DNS_KEYALGORITHM]}" "${APP_VALUES[DNS_KRB_KEYTAB]}" "${APP_VALUES[DNS_KRB_PRINCIPAL]}" delete txt testrecord.${APP_VALUES[DNS_SUFFIX]} 
    # verify that the record is removed
    if host -t txt testrecord.${APP_VALUES[DNS_SUFFIX]} ${APP_VALUES[DNS_SERVER]} >/dev/null
    then
        fail "txt record testrecord.${APP_VALUES[DNS_SUFFIX]} still resolves on server ${APP_VALUES[DNS_SERVER]}"
    else
        verbose "txt record successfully deleted"
    fi
}

function check_dns_avahi() {
    verbose checking avahi dns plugin configuration

    APP_VALUE_MAP=(
        ["MDNS_SERVER"]="Rails.application.config.dns[:server]"
        ["MDNS_PORT"]="Rails.application.config.dns[:port].to_s"
        ["MDNS_KEYNAME"]="Rails.application.config.dns[:keyname]"
        ["MDNS_KEYVALUE"]="Rails.application.config.dns[:keyvalue]"
        ["MDNS_ZONE"]="Rails.application.config.dns[:zone]"
        ["DNS_SUFFIX"]="Rails.application.config.openshift[:domain_suffix]"
    )
    load_application_values || return 1

    verbose "DNS Server: ${APP_VALUES[MDNS_SERVER]}"
    verbose "DNS Port: ${APP_VALUES[MDNS_PORT]}"
    verbose "DNS Zone: ${APP_VALUES[MDNS_ZONE]}"
    verbose "DNS Suffix: ${APP_VALUES[DNS_SUFFIX]}"

    # check the zone we update matches the broker's cloud domain
    [[ x"${APP_VALUES[MDNS_ZONE]}" == x"${APP_VALUES[DNS_SUFFIX]}" ]] \
    || fail "Zone in DNS plugin '${APP_VALUES[MDNS_ZONE]}' does not match CLOUD_DOMAIN ${APP_VALUES[DNS_SUFFIX]}"
    # there should probably be some tests here like for BIND
}

function check_dynamic_dns() {
    verbose checking dynamic dns plugin

    NAME_PLUGIN=${APP_VALUES[oo_dns_provider]}
    case $NAME_PLUGIN in
        'OpenShift::DnsService')
          fail "configured dns class is the abstract one: $NAME_PLUGIN"
        ;;

        'OpenShift::BindPlugin')
          verbose dynamic dns plugin = $NAME_PLUGIN
          check_dns_bind
        ;;

        'OpenShift::NsupdatePlugin')
          verbose dynamic dns plugin = $NAME_PLUGIN
          check_dns_bind
        ;;

        'OpenShift::AvahiPlugin')
          verbose dynamic dns plugin = $NAME_PLUGIN
          check_dns_avahi
        ;;

        *)
          notice unknown dns class: $NAME_PLUGIN
        ;;
    esac
    unset NAME_PLUGIN
}

# ============================================================================
#
# Broker -> Node messaging
#
# ============================================================================

function check_messaging() {
    verbose checking messaging configuration

    MSG_PLUGIN=${APP_VALUES[proxy_provider]}
    case $MSG_PLUGIN in
        'OpenShift::ApplicationContainerProxy')
          fail "configured messaging class is the abstract one: $MSG_PLUGIN"
        ;;

        'OpenShift::MCollectiveApplicationContainerProxy')
          verbose messaging plugin = $MSG_PLUGIN
        ;;

        *)
          notice unknown messaging class: $MSG_PLUGIN
        ;;
    esac
    unset MSG_PLUGIN
}

# ==========================================================================
# Process CLI arguments
# ==========================================================================

function print_help() {
    echo "usage: $0 [-h] [-v] [-d]

  -h) Display this simple usage message and exit.
  -v) Enable verbose (INFO) output during the run of the script
  -d) Enable debugging mode (display every command executed)
"
    exit 0
}


OPT_FORMAT="dhv"

while getopts $OPT_FORMAT OPTION
do
    case $OPTION in
        d)
          set -x
        ;;

        h)
          print_help
        ;;

        v)
          VERBOSE="true"
        ;;

        ?) print_help
        ;;
    esac
done

# =============================================================================
#
# MAIN
#
# =============================================================================

# Initial status is PASS (0)
# each fail adds one
STATUS=0

probe_version
check_packages $PACKAGES
check_mcollective_client_perms
check_ruby_requirements $OSO_RUBY_LIBS
if [ -z "$container" ]; then 
  #Can't check selinux/iptables if we are running inside a container
  check_selinux_enforcing
  check_selinux_booleans
  check_firewall
fi
check_services

# we will fill APP_VALUES with output from ruby commands corresponding to the map
declare -A APP_VALUES
declare -A APP_VALUE_MAP=(
    ["RU_TRUSTED_HEADER"]="Rails.application.config.auth[:trusted_header]"
        ["oo_auth_provider"]="OpenShift::AuthService.instance_variable_get('@oo_auth_provider')"
        ["oo_dns_provider"]="OpenShift::DnsService.instance_variable_get('@oo_dns_provider')"
        ["proxy_provider"]="OpenShift::ApplicationContainerProxy.instance_variable_get('@proxy_provider')"
   )
[ -f ${CONSOLE_CONF} ] && APP_VALUE_MAP["CONSOLE_SESSION_SECRET"]="console_conf.get('SESSION_SECRET')"
for var in ENABLE_USER_ACTION_LOG USER_ACTION_LOG_FILE AUTH_SALT SESSION_SECRET
  do APP_VALUE_MAP[$var]="broker_conf.get('$var')"
done
if load_application_values; then
    check_logfile_perms
    check_missing_and_insecure_secrets
    check_datastore_mongo
    check_authentication
    check_dynamic_dns
    check_messaging
fi

if [ "$STATUS" -eq 0 ]
then
    echo PASS
else
    echo $STATUS ERRORS
fi
exit $STATUS
