#!/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}
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/"

# we will fill APP_VALUES with output from ruby commands corresponding to the map
declare -A APP_VALUES
declare -A APP_VALUE_MAP=(
		["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')"
	)

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

function safe_ruby() {
    ruby "$@"
}

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 \"$@\""
    }
    function safe_ruby() {
        scl enable ruby193 "ruby \"$@\""
    }

    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() {
    ENABLE_USER_ACTION_LOG=`fetch_config ${BROKER_CONF} ENABLE_USER_ACTION_LOG`
    if [ $ENABLE_USER_ACTION_LOG = "true" ]
    then 
      USER_ACTION_LOG_FILE=`fetch_config ${BROKER_CONF} 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_missing_and_insecure_secrets() {
    AUTH_SALT=`fetch_config ${BROKER_CONF} AUTH_SALT`
    if [ "$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

    BROKER_SECRET=`fetch_config ${BROKER_CONF} SESSION_SECRET`
    if [ "$BROKER_SECRET" = "nil" -o "$BROKER_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=`fetch_config ${CONSOLE_CONF} 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
}

# This is another way to parse the config settings.  You could argue that it's
# less accurate than loading the Rails application but it's better than plain
# grep and it's way more readable/flexible than the bash array stuff.  I plan
# to replace that soon.
#
# TODO: It would be trivial to add all the configuration assertions in the ruby
# code and check them all at once.  This would be much faster than loading the
# rails apps multiple times.
function fetch_config() {
    pushd /var/www/openshift/broker > /dev/null
    safe_bundle "ruby -rubygems --" <<EOF
      require 'openshift-origin-common'
      conf = OpenShift::Config.new('$1')
      puts conf.get('$2')
EOF
    popd > /dev/null
}

#
# 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 APP_VALUE_MAP and
	# read the output values into the 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=""
	declare -a 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
	OLD_IFS=$IFS; IFS=$'\n' # read into array one line per element
    # Tail the output on the next line, since deprecation warnings and
    # other unrelated output will make this test fail, even when it
    # works
	declare -a new_app_values=( `run_ruby_rails_command "$ruby_program" | tail --lines="${#keys[@]}"` )
	IFS=$OLD_IFS # back to normal
	if [ $? -ne 0 ]
	then
		fail "Error while running ruby code.
#####
$ruby_program
#####
${new_app_values[@]}
#####"
	elif (( ${#new_app_values[@]} != ${#keys[@]} ))
	then
		fail "wrong number of values returned from ruby code
keys: ${#keys[@]}; values: ${#new_app_values[@]}
#####
$ruby_program
#####
${new_app_values[@]}
#####"
	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
}

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 2>&1
        begin
          require 'config/environment'
          require 'config/environments/$OSO_ENVIRONMENT'
        rescue Bundler::GemNotFound => gem_error
          puts "Error loading libs or gems: #{gem_error}"
          exit 1
        end
        $1
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 echo $PKGSTATUS | grep "not installed" >/dev/null
	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 not enforcing, just say so
  notice "SELinux is" `getenforce`
  if [ `getenforce 2>/dev/null` != "Enforcing" ]
  then
    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() {
  # if not enforcing, just say so
  notice "SELinux is " `getenforce`
  if [ `getenforce 2>/dev/null` != "Enforcing" ]
  then
    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
    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>/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

        # 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
    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

	APP_VALUE_MAP=(
		["RU_TRUSTED_HEADER"]="Rails.application.config.auth[:trusted_header]"
		)
	load_application_values
    verbose "Auth trusted header: ${APP_VALUES[RU_TRUSTED_HEADER]}"

    if grep -q 'BrowserMatchNoCase\s\+\^OpenShift\s\+passthrough' ${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
    APP_VALUE_MAP=(
            ["oo_auth_provider"]="OpenShift::AuthService.instance_variable_get('@oo_auth_provider')"
            )
    load_application_values
 
    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 = Kerberos keytab
    # $6 = Kerberos principal
    # $7 = function (add|delete)
    # $8 = type (A, TXT, CNAME)
    # $9 = name
    # $10 = value

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

    verbose "${7}ing $8 record named $9 to server $2: $10"

    if [ "$1" = "key" ]
    then
        nsupdate <<EOF
server $2
key $3 $4
update $7 $9 1 $8 $10
send
EOF
    elif [ "$1" = "krb" ]
    then
        kinit -kt "$5" -p "$6"
        nsupdate -g <<EOF
server $2
update $7 $9 1 $8 $10
send
EOF
    else
        nsupdate <<EOF
server $2
update $7 $9 1 $8 $10
send
EOF
    fi

    if [ $? != 0 ]
    then
	fail "error ${7}ing $8 record name $9 to server $2: $10
	-- 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

    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]"
		)
	load_application_values
        verbose "DNS Key Name: ${APP_VALUES[DNS_KEYNAME]}"
        verbose "DNS Key Value: *****"
    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
        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_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_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_dynamic_dns() {
    verbose checking dynamic dns plugin
    APP_VALUE_MAP=(
            ["oo_dns_provider"]="OpenShift::DnsService.instance_variable_get('@oo_dns_provider')"
            )
    load_application_values

    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
	    ;;

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

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

function check_messaging() {
    verbose checking messaging configuration
    APP_VALUE_MAP=(
            ["proxy_provider"]="OpenShift::ApplicationContainerProxy.instance_variable_get('@proxy_provider')"
            )
    load_application_values

    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_missing_and_insecure_secrets
check_logfile_perms
check_packages $PACKAGES
check_ruby_requirements $OSO_RUBY_LIBS
check_selinux_enforcing
check_selinux_booleans
check_firewall
check_datastore_mongo
check_services

load_application_values
check_authentication
check_dynamic_dns
check_messaging

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