#!/bin/sh

# TODO:
# * check if php.ini is present
# * specify to check against sec vulnerabilities
# * TODO: Check if PHP exists (already does, but restructure)
# * Use: curl and fetch as backup for (-u)


################################################################################
#
#   V A R I A B L E S
#
################################################################################
INFO_AUTHOR="Patrick Plocke <patrick@plocke.de>"
INFO_GPGKEY="0x28BF179F"
INFO_LICENSE="MIT"
INFO_NAME="check_php"
INFO_DATE="2015-12-04"
INFO_VERSION="0.3"


# Check for performance data:
# http://nagios.sourceforge.net/docs/3_0/pluginapi.html
# Nagios error codes
EXIT_OK=0
EXIT_WARN=1
EXIT_ERR=2
EXIT_UNKNOWN=3


if ! command -v php > /dev/null 2>&1 ; then
	printf "[UNKNOWN] PHP not found.\n"
	exit $EXIT_UNKNOWN
fi

PHP="$(which php)"



################################################################################
#
#   F U N C T I O N S
#
################################################################################

############################################################
# Misc Functions
############################################################

print_usage() {
	printf "Usage: %s [-s <w|e>] [-u <w|e>] [-m <module> <w|e>] [-c <conf> <val> <w|e>] [-v]\n" "${INFO_NAME}"
	printf "       %s -h\n" "${INFO_NAME}"
	printf "       %s -V\n\n" "${INFO_NAME}"
	printf "Nagios plugin that will check if PHP exists, for PHP startup errors,\n"
	printf "missing modules, misconfigured directives and available updates.\n\n"
	printf "  -s <w|e>               [single] Check for PHP startup errors and display\n"
	printf "                         nagios warning or error if any exists.\n"
	printf "                         Warning:  -s w\n"
	printf "                         Error:    -s e\n\n"
	printf "  -u <w|e>               [single] Check for updated PHP version online. (requires wget, curl or fetch)\n"
	printf "                         Will only check for patch updates and will not notify if your current version\n"
	printf "                         PHP 5.5 and there is already PHP 5.6 out there.\n\n"
	printf "  -m <module> <w|e>      [multiple] Require compiled PHP module and display\n"
	printf "                         nagios warning/error if the module was not compiled against PHP.\n"
	printf "                         Use multiple times to check against multiple modules.\n"
	printf "                         Example: -m \"mysql\" w -m \"mysqli\" e\n\n"
	printf "  -c <conf> <val> <w|e>  [multiple] Check for misconfigured directives in php.ini and display\n"
	printf "                         nagios warning/error if the configuration does not match.\n"
	printf "                         Use multiple times to check against multiple configurations.\n"
	printf "                         Example: -c \"date.timezone\" w -c \"Europe/Berlin\" e\n\n"
	printf "  -v                     Be verbose (Show PHP Version and Zend Engine Version)\n\n"
	printf "  -h                     Display help\n\n"
	printf "  -V                     Display version\n"
}

print_version() {
	printf "Version: %s %s (%s)\n" "${INFO_NAME}" "${INFO_VERSION}" "${INFO_DATE}"
	printf "Author:  %s (%s)\n" "${INFO_AUTHOR}" "${INFO_GPGKEY}"
	printf "License: %s\n" "${INFO_LICENSE}"
}


merge_text() {
	CURR_TEXT="$1"
	NEXT_TEXT="$2"
	SEPARATOR="$3"

	if [ "$SEPARATOR" = "" ]; then
		SEPARATOR="\n"
	fi

	if [ "$CURR_TEXT" = "" ]; then
		CURR_TEXT="$NEXT_TEXT"
	else
		CURR_TEXT="${CURR_TEXT}${SEPARATOR}${NEXT_TEXT}"
	fi
	echo "${CURR_TEXT}"
}

merge_exit_codes() {
	CURR_EXIT="$1"
	NEXT_EXIT="$2"

	if [ "$CURR_EXIT" = "0" ]; then
		CURR_EXIT="$NEXT_EXIT"
	elif [ "$CURR_EXIT" = "1" ]; then
		if [ "$NEXT_EXIT" = "0" ]; then
			CURR_EXIT="1"
		elif [ "$NEXT_EXIT" = "1" ]; then
			CURR_EXIT="1"
		elif [ "$NEXT_EXIT" = "2" ]; then
			CURR_EXIT="2"
		elif [ "$NEXT_EXIT" = "3" ]; then # UNKNOWN -> WARNING
			CURR_EXIT="1"
		fi
	elif [ "$CURR_EXIT" = "2" ]; then
		CURR_EXIT="2"
	elif [ "$CURR_EXIT" = "3" ]; then
		if [ "$NEXT_EXIT" = "0" ]; then
			CURR_EXIT="3"
		elif [ "$NEXT_EXIT" = "1" ]; then
			CURR_EXIT="1"
		elif [ "$NEXT_EXIT" = "2" ]; then
			CURR_EXIT="2"
		elif [ "$NEXT_EXIT" = "3" ]; then # UNKNOWN -> WARNING
			CURR_EXIT="3"
		fi
	fi
	echo "$CURR_EXIT"
	return $CURR_EXIT
}

get_php_version() {

	# Current installed version
	CMD="$PHP -v 2>/dev/null | grep -oE '^PHP\s[0-9]+\.[0-9]+\.[0-9]+' | awk '{ print \$2}'"
	PHP_VERSION="$(eval "$CMD")"

	echo "$PHP_VERSION"
}

check_version() {
	# Current installed version
	PHP_VERSION="$(get_php_version)"

	PHP_MAJOR="$(echo "$PHP_VERSION" | awk -F'[.]' '{print $1}')"
	PHP_MINOR="$(echo "$PHP_VERSION" | awk -F'[.]' '{print $2}')"
	PHP_PATCH="$(echo "$PHP_VERSION" | awk -F'[.]' '{print $3}')"

	# Latest available versions
	if command -v wget >/dev/null 2>&1; then
		PHP_LATEST="$(wget -qO- "https://secure.php.net/releases/index.php?json&version=5&max=2" | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | sort -u)"
	elif command -v curl >/dev/null 2>&1; then
		PHP_LATEST="$(curl --silent "https://secure.php.net/releases/index.php?json&version=5&max=2" | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | sort -u)"
	elif command -v fetch >/dev/null 2>&1; then
		PHP_LATEST="$(fetch --no-verify-hostname --no-verify-peer --quiet --output=- "https://secure.php.net/releases/index.php?json&version=5&max=2" | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | sort -u)"
	else
		echo "[UNKNOW] - check version requires wget, curl or fetch"
		exit "$EXIT_UNKNOWN"
	fi


	for v in $PHP_LATEST; do

		PHP_LATEST_MAJOR="$(echo "$v" | awk -F'[.]' '{print $1}')"
		PHP_LATEST_MINOR="$(echo "$v" | awk -F'[.]' '{print $2}')"
		PHP_LATEST_PATCH="$(echo "$v" | awk -F'[.]' '{print $3}')"

		# Get Major version
		if [ "$PHP_LATEST_MAJOR" = "$PHP_MAJOR" ]; then
			# Get Minor version
			if [ "$PHP_LATEST_MINOR" = "$PHP_MINOR" ]; then
				# Get Patch version
				if [ "$PHP_LATEST_PATCH" -gt "$PHP_PATCH" ]; then
					echo "PHP Version $PHP_VERSION too old. Latest: ${PHP_LATEST_MAJOR}.${PHP_LATEST_MINOR}.${PHP_LATEST_PATCH}."
					return 2
				elif [ "$PHP_LATEST_PATCH" -eq "$PHP_PATCH" ]; then
					echo "PHP Version $PHP_VERSION up to date."
					return 0
				else
					echo "PHP Version $PHP_VERSION too new. Latest: ${PHP_LATEST_MAJOR}.${PHP_LATEST_MINOR}.${PHP_LATEST_PATCH}."
					return 3
				fi
			fi
		fi
	done

	echo "No PHP Version found online. Your version: $PHP_VERSION"
	return 3

}


# Make sure not to display any errors to stdout
# and store them in a variable to output it in a formated way.
check_startup_errors() {
	CMD="$PHP -v 3>&1 1>&2 2>&3 1>/dev/null"
	ERR="$(eval "$CMD")"

	if [ "$ERR" != "" ]; then
		echo "$ERR"
		return 1
	else
		echo "No PHP startup errors"
		return 0
	fi
}

check_configurration() {
	CONFIG="$1"
	VALUE="$2"

	CMD="$PHP -i 2>/dev/null | grep '^${CONFIG}\s' | awk 'BEGIN { FS = \"=>\" } ; {print \$NF}' | xargs"
	CURRENT="$(eval "$CMD")"

	if [ "$VALUE" = "$CURRENT" ]; then
		echo "Config \"$CONFIG\" = \"$VALUE\""
		return $EXIT_OK
	else
		echo "Config \"$CONFIG\" = \"$CURRENT\", excpected: \"$VALUE\""
		return $EXIT_ERR
	fi
}

check_module() {
	MODULE="$1"
	CMD="$PHP -m 2>/dev/null | grep -cE '^${MODULE}$'"
	CNT="$(eval "$CMD")"

	if [ "$CNT" = "1" ]; then
		echo "Module: \"$MODULE\" available"
		return $EXIT_OK
	elif [ "$CNT" -gt "1" ]; then
		echo "Multiple \"$MODULE\" found: $(eval "$CMD")"
		return $EXIT_WARN
	else
		echo "Module: \"$MODULE\" not available"
		return $EXIT_ERR
	fi
}


################################################################################
#
#   E N T R Y   P O I N T
#
################################################################################


############################################################
# Parameter
############################################################



if [ "$1" = "-h" ]; then
	print_usage
	exit $EXIT_OK
elif [ "$1" = "-V" ]; then
	print_version
	exit $EXIT_OK
fi


# Extended Status Message
MSG_STARTUP=""
MSG_UPDATE=""
MSG_MODULE=""
MSG_CONFIG=""
MSG_VERBOSE=""

# Info for normal Status message if there is an error/warning
HAS_STARTUP_ERROR=""
HAS_UPDATE_ERROR=""
HAS_MODULE_ERROR=""
HAS_CONFIG_ERROR=""

# Final exit code
NAGIOS_EXIT="$EXIT_OK"


while test -n "$1"; do
	case "$1" in

		# ---- 1. Evaluate startup errors
		-s)
			# Get next arg in list (Exit code)
			shift
			SEVERITY="$1"

			# Check valid exit code
			if [ "$SEVERITY" != "w" ] && [ "$SEVERITY" != "e" ]; then
				echo "Invalid value \"$SEVERITY\" for option \"-s\"."
				exit $EXIT_UNKNOWN
			fi

			# Execute Command
			MSG_STARTUP="$(check_startup_errors)"
			TMP_EXIT="$?"

			# Merge exit codes
			if [ "$TMP_EXIT" != "0" ]; then
				if [ "$SEVERITY" = "e" ]; then
					MSG_STARTUP="[CRITICAL] ${MSG_STARTUP}"
					NAGIOS_EXIT="$(merge_exit_codes "$NAGIOS_EXIT" "2")"
				else
					MSG_STARTUP="[WARNING]  ${MSG_STARTUP}"
					NAGIOS_EXIT="$(merge_exit_codes "$NAGIOS_EXIT" "1")"
				fi
				HAS_STARTUP_ERROR="true"
			else
				MSG_STARTUP="[OK]       ${MSG_STARTUP}"
				NAGIOS_EXIT="$(merge_exit_codes "$NAGIOS_EXIT" "0")"
			fi
			;;


		#  ---- 2. Evaluate php update version
		-u)
			# Get next arg in list (Exit code)
			shift
			SEVERITY="$1"

			# Check valid exit code
			if [ "$SEVERITY" != "w" ] && [ "$SEVERITY" != "e" ]; then
				echo "Invalid value \"$SEVERITY\" for option \"-u\"."
				exit $EXIT_UNKNOWN
			fi

			# execute command
			MSG_UPDATE="$(check_version)"
			TMP_EXIT="$?"

			# Merge exit codes
			if [ "$TMP_EXIT" != "0" ]; then
				if [ "$SEVERITY" = "e" ]; then
					MSG_UPDATE="[CRITICAL] ${MSG_UPDATE}"
					NAGIOS_EXIT="$(merge_exit_codes "$NAGIOS_EXIT" "2")"
				else
					MSG_UPDATE="[WARNING]  ${MSG_UPDATE}"
					NAGIOS_EXIT="$(merge_exit_codes "$NAGIOS_EXIT" "1")"
				fi
				HAS_UPDATE_ERROR="true"
			else
				MSG_UPDATE="[OK]       ${MSG_UPDATE}"
				NAGIOS_EXIT="$(merge_exit_codes "$NAGIOS_EXIT" "0")"
			fi
			;;


		# ---- 3 Evaluate missing modules
		-m)
			# Get next arg in list (Module name)
			shift
			MODULE="$1"

			# Get next arg in list (Exit code)
			shift
			SEVERITY="$1"

			# Check valid exit code
			if [ "$SEVERITY" != "w" ] && [ "$SEVERITY" != "e" ]; then
				echo "Invalid value \"$SEVERITY\" for option \"-m\"."
				exit $EXIT_UNKNOWN
			fi

			# Execute Command
			TMP_TEXT="$(check_module "$MODULE")"
			TMP_EXIT="$?"

			# Merge exit codes
			if [ "$TMP_EXIT" != "0" ]; then
				if [ "$SEVERITY" = "e" ]; then
					MSG_MODULE="$(merge_text "${MSG_MODULE}" "[CRITICAL] $TMP_TEXT")"
					NAGIOS_EXIT="$(merge_exit_codes "$NAGIOS_EXIT" "2")"
				else
					MSG_MODULE="$(merge_text "${MSG_MODULE}" "[WARNING]  $TMP_TEXT")"
					NAGIOS_EXIT="$(merge_exit_codes "$NAGIOS_EXIT" "1")"
				fi
				HAS_MODULE_ERROR="true"
			else
				MSG_MODULE="$(merge_text "${MSG_MODULE}" "[OK]       $TMP_TEXT")"
				NAGIOS_EXIT="$(merge_exit_codes "$NAGIOS_EXIT" "0")"
			fi
			;;


		# ---- 4. Evaluate wrong configuration
		-c)
			# Get next arg in list (Config name)
			shift
			CONFIG="$1"

			# Get next arg in list (Config value)
			shift
			VALUE="$1"

			# Get next arg in list (Exit code)
			shift
			SEVERITY="$1"

			# Check valid exit code
			if [ "$SEVERITY" != "w" ] && [ "$SEVERITY" != "e" ]; then
				echo "Invalid value \"$SEVERITY\" for option \"-c\"."
				exit $EXIT_UNKNOWN
			fi

			# execute command
			TMP_TEXT="$(check_configurration "$CONFIG" "$VALUE")"
			TMP_EXIT="$?"

			# Merge exit codes
			if [ "$TMP_EXIT" != "0" ]; then
				if [ "$SEVERITY" = "e" ]; then
					MSG_CONFIG="$(merge_text "${MSG_CONFIG}" "[CRITICAL] $TMP_TEXT")"
					NAGIOS_EXIT="$(merge_exit_codes "$NAGIOS_EXIT" "2")"
				else
					MSG_CONFIG="$(merge_text "${MSG_CONFIG}" "[WARNING]  $TMP_TEXT")"
					NAGIOS_EXIT="$(merge_exit_codes "$NAGIOS_EXIT" "1")"
				fi
				HAS_CONFIG_ERROR="true"
			else
				MSG_CONFIG="$(merge_text "${MSG_CONFIG}" "[OK]       $TMP_TEXT")"
				NAGIOS_EXIT="$(merge_exit_codes "$NAGIOS_EXIT" "0")"
			fi
			;;

		# ---- Be versbose
		-v)
			CMD="$PHP -v 2>/dev/null | grep -E '(^PHP)|(^Zend)' | awk -F'[,]' '{print \$1}' | awk -F'[(]' '{print \$1}' | awk -F'[-]' '{print \$1}'"
			MSG_VERBOSE="$(eval "$CMD")"
			;;

		-V)
			print_version
			exit $EXIT_OK
			;;
		*)
			printf "Unknown argument: %s\n" "$1"
			print_usage
			exit $EXIT_UNKNOWN
			;;
	esac
	shift
done




############################################################
# Get information and Check PHP
############################################################

PHPV="$(get_php_version)"
INFO=""
if [ "$HAS_STARTUP_ERROR" != "" ]; then
	INFO="$(merge_text "${INFO}" "Startup error(s)" ", ")"
fi
if [ "$HAS_UPDATE_ERROR" != "" ]; then
	INFO="$(merge_text "${INFO}" "Updates available" ", ")"
fi
if [ "$HAS_MODULE_ERROR" != "" ]; then
	INFO="$(merge_text "${INFO}" "Missing module(s)" ", ")"
fi
if [ "$HAS_CONFIG_ERROR" != "" ]; then
	INFO="$(merge_text "${INFO}" "Wrong config" ", ")"
fi


#### Performance Data

if [ "$NAGIOS_EXIT" = "$EXIT_OK" ]; then
	printf "%s | %s\n" "[OK] PHP ${PHPV} is healthy" "'OK'=1;;;; 'Errors'=0;;;; 'Warnings'=0;;;; 'Unknown'=0;;;;"
elif [ "$NAGIOS_EXIT" = "$EXIT_WARN" ]; then
	printf "%s: %s | %s\n" "[WARN] PHP ${PHPV} has warnings" "${INFO}" "'OK'=0;;;; 'Errors'=0;;;; 'Warnings'=1;;;; 'Unknown'=0;;;;"
elif [ "$NAGIOS_EXIT" = "$EXIT_ERR" ]; then
	printf "%s: %s | %s\n" "[ERROR] PHP ${PHPV} has errors" "${INFO}" "'OK'=0;;;; 'Errors'=1;;;; 'Warnings'=0;;;; 'Unknown'=0;;;;"
else
	printf "%s: %s | %s\n" "[UNKNOWN] PHP ${PHPV} is at unknown state - check extended info." "${INFO}" "OK'=0;;;; 'Errors'=0;;;; 'Warnings'=0;;;; 'Unknown'=1;;;;"
fi

if [ "$MSG_STARTUP" != "" ]; then echo "$MSG_STARTUP"; fi
if [ "$MSG_UPDATE"  != "" ]; then echo "$MSG_UPDATE";  fi
if [ "$MSG_MODULE"  != "" ]; then echo "$MSG_MODULE";  fi
if [ "$MSG_CONFIG"  != "" ]; then echo "$MSG_CONFIG";  fi
if [ "$MSG_VERBOSE" != "" ]; then echo "$MSG_VERBOSE"; fi

exit "$NAGIOS_EXIT"

