diff --git a/agents/check_mk_agent.merged b/agents/check_mk_agent.merged new file mode 100755 index 00000000000..d1ddd00bf96 --- /dev/null +++ b/agents/check_mk_agent.merged @@ -0,0 +1,3086 @@ +#!/bin/sh +# vim: noai:ts=4:sw=4:expandtab + +# Copyright (C) 2019 tribe29 GmbH - License: GNU General Public License v2 +# This file is part of Checkmk (https://checkmk.com). It is subject to the terms and +# conditions defined in the file COPYING, which is part of this source code package. + +# Remove locale settings to eliminate localized outputs where possible +# The locale logic here is used to make the Python encoding detection work (see CMK-2778). +unset -v LANG +case "$(locale -a | paste -sd ' ' -)" in + (*'C.UTF-8'*) LC_ALL="C.UTF-8" ;; + (*'c.utf-8'*) LC_ALL="c.utf-8" ;; + (*'C'*|*) LC_ALL="C" ;; +esac +LC_ALL="${LC_ALL:-C}" +export LC_ALL + +# Set these helper functions early so that they can assist with init +var_is_set() { [ "${1+x}" = "x" ] && [ "${#1}" -gt "0" ]; } # set and not null +var_is_unset() { [ -z "${1+x}" ]; } # unset +var_is_empty() { [ "${1+x}" = "x" ] && [ "${#1}" -eq "0" ]; } # set and null +var_is_blank() { var_is_unset "${1}" || var_is_empty "${1}"; } # unset, or set and null + +# Close standard input (for security reasons) and stderr when not explicitly in debug mode. +# When the nodes agent is executed by a e.g. docker node in a container, +# then don't close stdin, because the agent is piped through it in this case. +if [ "${1}" = -d ]; then + set -xv +# TO-DO: Review the validity/use of MK_FROM_NODE, it seems to be superseded now +elif [ -z "${MK_FROM_NODE}" ]; then + exec /dev/null +fi + +#################################################################################################### +# Dynamically build PATH to ensure that locally installed binaries are found +# Blank a variable for the following dynamic PATH task +newPath= + +# Itirate through a list of potential PATH members and add any paths that are found +# to the 'newPath' variable. This way, we dynamically build PATH, and bias Solaris towards XPG4 +for dir in /usr/gnu/bin /usr/xpg6/bin /usr/xpg4/bin /usr/kerberos/bin /usr/kerberos/sbin /bin \ + /sbin /usr/bin /usr/sbin /usr/contrib/bin/usr/local/bin /usr/local/sbin /opt/csw/bin \ + /opt/csw/sbin /opt/sfw/bin /opt/sfw/sbin /usr/pkg/bin /usr/sfw/bin /usr/sfw/sbin /snap/bin \ + /opt/lsi /opt/MegaRAID/storcli; do + + [ -d "${dir}" ] && newPath="${newPath}:${dir}" +done + +# Now assign our freshly built newPath variable, removing any leading colon +PATH="${newPath#:}" + +# Finally, export the PATH and unset newPath +export PATH +unset -v newPath + +# 'write()' abstracts the portability of 'printf' and solves the major portability headaches +# caused by various implementations of 'echo'. This is called 'write()' rather than 'echo()' as +# an override function, because some shells protect their builtins and complain. +# Fun exercise: look at Oracle's man page for 'echo', specifically the USAGE section. +# This also adds the '-j' option to output in json keypair format +write() { + case "${1}" in + (-e) + case "${2}" in + (-n|--end) shift 2; printf -- '%b' "${*}" ;; + (*) shift; printf -- '%b\n' "${*}" ;; + esac + ;; + (-E) + case "${2}" in + (-n|--end) shift 2; printf -- '%s' "${*}" ;; + (*) shift; printf -- '%s\n' "${*}" ;; + esac + ;; + (-j) shift; printf -- '{"%s": "%s"}\n' "${1}" "${2}" ;; + (-n|--end) + case "${2}" in + (-e) shift 2; printf -- '%b' "${*}" ;; + (-E) shift 2; printf -- '%s' "${*}" ;; + (*) shift; printf -- '%s' "${*}" ;; + esac + ;; + (-en|-ne) shift; printf -- '%b' "${*}" ;; + (-En|-nE) shift; printf -- '%s' "${*}" ;; + (*) printf -- '%s\n' "${*}" ;; + esac +} + +# Functionalise and standardise 'quiet grep' based tests. +# This gives us 'grep -q' cleanliness where '>/dev/null 2>&1' +# would otherwise be required e.g. Solaris, older versions of grep etc +if write "word" | grep -q "word" >/dev/null 2>&1; then + grepq() { grep -q "$@" 2>/dev/null; } +else + grepq() { grep "$@" >/dev/null 2>&1; } +fi + +# Function to replace "if type [somecmd]" idiom +# 'command -v' tends to be more robust vs 'which' and 'type' based tests +# We set this function early so that it's available for the rest of the agent +inpath() { + command -v "${1:?No command to test}" >/dev/null 2>&1 +} + +#################################################################################################### +# If EUID isn't set, then set it +# Note that 'id -u' is now mostly portable here due to the alignment of xpg4 above +# '[ -w / ]' may be an alternative test for proving root privileges... +if var_is_unset "${EUID}"; then + EUID=$(id -u); readonly EUID; export EUID +fi + +# If HOSTNAME isn't set, then set it +if var_is_unset "${HOSTNAME}"; then + HOSTNAME=$(hostname); readonly HOSTNAME; export HOSTNAME +fi + +# If HOME isn't set, then set it +if var_is_unset "${HOME}"; then + HOME=$(getent passwd | awk -F':' -v EUID="${EUID}" '$3 == EUID{print $6}') + readonly HOME; export HOME +fi + +# If USER isn't set, then set it +if var_is_unset "${USER}"; then + USER=$(getent passwd | awk -F':' -v EUID="${EUID}" '$3 == EUID{print $1}') + readonly USER; export USER +fi + +# date +%s is not portable, so we provide this function +# To do this in pure shell is... fun... however 'perl' is assumed +# elsewhere in this agent, so we may as well depend on that for our failover option +if date +%s 2>/dev/null | grepq '%s'; then + get_epoch() { perl -e 'print time."\n";'; } +else + get_epoch() { date +%s; } +fi + +# Newer shells like zsh and bash5.x have EPOCHSECONDS. If it's not available, we set it +# Note that if this is set here, it will not update each time it's used +# Use get_epoch() if you need that kind of accuracy +if var_is_unset "${EPOCHSECONDS}"; then + EPOCHSECONDS=$(get_epoch); readonly EPOCHSECONDS; export EPOCHSECONDS +fi + +# Because $SHELL is an unreliable thing to test against (e.g. SHLVL > 1), we provide this function +# It tries multiple methods to discover and name the invoking shell +# This won't work for 'fish', which needs 'ps -p %self' or similar non-bourne-esque syntax. +get_shell() { + if [ -r "/proc/$$/cmdline" ]; then + # We use 'tr' because 'cmdline' files have NUL terminated lines + # TO-DO: Possibly handle multi-word output e.g. 'busybox ash' + printf -- '%s\n' "$(tr '\0' ' ' /dev/null 2>&1; then + # This double-awk caters for situations where CMD/COMMAND + # might be a full path e.g. /usr/bin/zsh + ps -p "$$" | awk -F'[\t /]' 'END {print $NF}' + # This one works well except for busybox + elif ps -o comm= -p "$$" >/dev/null 2>&1; then + ps -o comm= -p "$$" + elif ps -o pid,comm= >/dev/null 2>&1; then + ps -o pid,comm= | awk -v ppid="$$" '$1==ppid {print $2}' + # FreeBSD, may require more parsing + elif inpath procstat; then + procstat -bh $$ + else + case "${BASH_VERSION}" in (*.*) printf -- '%s\n' "bash"; return 0 ;; esac + case "${KSH_VERSION}" in (*.*) printf -- '%s\n' "ksh"; return 0 ;; esac + case "${ZSH_VERSION}" in (*.*) printf -- '%s\n' "zsh"; return 0 ;; esac + # If we get to this point, fail out: + return 1 + fi +} + +# If SHELL isn't set, then set it. +if var_is_unset "${SHELL}"; then + if get_shell >/dev/null 2>&1; then + SHELL=$(command -v "$(get_shell)"); readonly SHELL; export SHELL + fi +fi + +#################################################################################################### +# Define the version of Check_MK +MK_VERSION=TESTING + +# Manually define these variables here if you want to override the defaults +# This is where these would be templated in something like an Ansible role too e.g. +#MK_LIBDIR="{{ mk_libdir | d('/opt/check_mk/lib') }}" +#MK_CONFDIR="{{ mk_confdir | d('/opt/check_mk/etc') }}" +#MK_VARDIR="{{ mk_vardir | d('/opt/check_mk/var') }}" +#MK_TMPDIR="{{ mk_tmpdir | d('/opt/check_mk/tmp') }}" + +# Ensure that the MK_OSSTR environment variable is set. +# This imitates the 'OSTYPE' variable from bash +# We use this variable later on for OS specific behaviour +case $(uname -s) in + ("AIX") + MK_OSSTR=aix + MK_LIBDIR="${MK_LIBDIR:-/usr/check_mk/lib}" + MK_CONFDIR="${MK_CONFDIR:-/usr/check_mk/conf}" + MK_VARDIR="${MK_VARDIR:-/tmp/check_mk}" + ;; + ("Darwin") + MK_OSSTR=mac + MK_OSVER="$(sw_vers | sed 1d | paste -sd ' ' - | awk -F" " '{print $2" ("$4")"}')" + MK_LIBDIR="${MK_LIBDIR:-/usr/local/lib/check_mk_agent}" + MK_CONFDIR="${MK_CONFDIR:-/etc/check_mk}" + MK_VARDIR="${MK_VARDIR:-/var/lib/check_mk_agent}" + ;; + ("FreeBSD") + MK_OSSTR=freebsd + MK_LIBDIR="${MK_LIBDIR:-/usr/local/lib/check_mk_agent}" + MK_CONFDIR="${MK_CONFDIR:-/etc/check_mk}" + #MK_VARDIR="${MK_VARDIR:-/set/this}" + MK_TMPDIR="${MK_TMPDIR:-/var/run/check_mk}" + ;; + ("HPUX") + MK_OSSTR=hpux + MK_LIBDIR="${MK_LIBDIR:-/usr/lib/check_mk_agent}" + MK_CONFDIR="${MK_CONFDIR:-/etc/check_mk}" + #MK_VARDIR="${MK_VARDIR:-/set/this}"" + ;; + ("Linux"|"linux-gnu"|"GNU"*) + MK_OSSTR=linux + MK_LIBDIR="${MK_LIBDIR:-/usr/lib/check_mk_agent}" + MK_CONFDIR="${MK_CONFDIR:-/etc/check_mk}" + MK_VARDIR="${MK_VARDIR:-/var/lib/check_mk_agent}" + ;; + ("NetBSD") + MK_OSSTR=netbsd + #MK_LIBDIR="${MK_LIBDIR:-/change/me/}" + #MK_CONFDIR="${MK_CONFDIR:-/change/me}" + #MK_VARDIR="${MK_VARDIR:-/set/this}" + ;; + ("OpenBSD") + MK_OSSTR=openbsd + MK_LIBDIR="${MK_LIBDIR:-/usr/lib/check_mk_agent}" + MK_CONFDIR="${MK_CONFDIR:-/etc}" + #MK_VARDIR="${MK_VARDIR:-/set/this}" + ;; + ("SunOS"|"solaris") + MK_OSSTR=solaris + MK_LIBDIR="${MK_LIBDIR:-/usr/lib/check_mk_agent}" + MK_CONFDIR="${MK_CONFDIR:-/etc/check_mk}" + MK_VARDIR="${MK_VARDIR:-/var/lib/check_mk_agent}" + ;; + (*"BSD"|*"bsd"|"DragonFly"|"Bitrig") + MK_OSSTR=bsd + #MK_LIBDIR="${MK_LIBDIR:-/change/me/}" + #MK_CONFDIR="${MK_CONFDIR:-/change/me}" + #MK_VARDIR="${MK_VARDIR:-/set/this}" + ;; + (*) + MK_OSSTR=$(uname -s) + #MK_LIBDIR="${MK_LIBDIR:-/to/be/changed}" + #MK_CONFDIR="${MK_CONFDIR:-/to/be/changed}" + #MK_VARDIR="${MK_VARDIR:-/set/this}" + ;; +esac + +# All executables in MK_PLUGINSDIR will simply be executed and their +# ouput appended to the output of the agent. Plugins define their own +# sections and must output headers with '<<<' and '>>>' +MK_PLUGINSDIR="${MK_LIBDIR:?}"/plugins + +# All executables in MK_LOCALDIR will be executed and their +# output inserted into the section <<>>. Please +# refer to online documentation for details about local checks. +MK_LOCALDIR="${MK_LIBDIR}"/local + +# All files in MK_SPOOLDIR will simply appended to the agent +# output if they are not outdated (see below) +MK_SPOOLDIR="${MK_VARDIR:?}"/spool + +# Now we load our configuration files +# shellcheck source=/dev/null +{ + chmod 640 "${MK_CONFDIR}"/* 2>/dev/null + [ -r "${MK_CONFDIR}/agent.cfg" ] && . "${MK_CONFDIR}/agent.cfg" + [ -r "${MK_CONFDIR}/encryption.cfg" ] && . "${MK_CONFDIR}/encryption.cfg" + [ -r "${MK_CONFDIR}/exclude_sections.cfg" ] && . "${MK_CONFDIR}/exclude_sections.cfg" + RTC_PLUGINS="" # Is this still necessary? + # This provides RTC_TIMEOUT, RTC_PORT, RTC_SECRET and RTC_SECTIONS + [ -r "${MK_CONFDIR}/real_time_checks.cfg" ] && . "${MK_CONFDIR}/real_time_checks.cfg" +} + +# Protect all our MK variables +readonly MK_OSSTR MK_LIBDIR MK_CONFDIR MK_VARDIR MK_VERSION +readonly MK_PLUGINSDIR MK_LOCALDIR MK_SPOOLDIR MK_TMPDIR + +# Export all our MK variables +export MK_OSSTR MK_LIBDIR MK_CONFDIR MK_VARDIR MK_VERSION +export MK_PLUGINSDIR MK_LOCALDIR MK_SPOOLDIR MK_TMPDIR + +# Now that we have all of that figured out, we work through some OS specific setup tasks +case "${MK_OSSTR}" in + (aix) + # For AIX, force load of environment. + # shellcheck source=/dev/null + [ -e "${HOME}"/.profile ] && . "${HOME}"/.profile >/dev/null 2>&1 + # Avoid problems with wrong decimal separators in other language verions of aix + LC_NUMERIC="en_US" + export LC_NUMERIC + ;; + (freebsd) + osver="$(uname -r)" + is_jailed="$(sysctl -n security.jail.jailed)" + ;; + (solaris) + # Find out what zone we are running in + # Treat all pre-Solaris 10 systems as "global" + if inpath zonename; then + zonename=$(zonename) + pszone="-z ${zonename}" + else + zonename="global" + pszone="-A" + fi + ;; +esac + +#################################################################################################### +# The package name gets patched for baked agents to either +# "check-mk-agent" or the name set by the "name of agent packages" rule +XINETD_SERVICE_NAME=check_mk + +# Detect whether or not the agent is being executed in a container environment. +if var_is_blank "${MK_IS_DOCKERIZED}" || var_is_blank "${MK_IS_LXC_CONTAINER}"; then + if [ -f /.dockerenv ]; then + MK_IS_DOCKERIZED=1 + elif grepq container=lxc /proc/1/environ; then + # Works in lxc environment e.g. on Ubuntu bionic, but does not + # seem to work in proxmox (see CMK-1561) + MK_IS_LXC_CONTAINER=1 + elif grepq 'lxcfs /proc/cpuinfo fuse.lxcfs' /proc/mounts; then + # Seems to work in proxmox + MK_IS_LXC_CONTAINER=1 + else + unset -v MK_IS_DOCKERIZED + unset -v MK_IS_LXC_CONTAINER + fi +fi + +# Load our variables for encrypted data. See protect_output() +if [ -r "${MK_CONFDIR}/encryption.cfg" ]; then + # We ensure that this file is secure + chmod 640 "${MK_CONFDIR}/encryption.cfg" 2>/dev/null + + # shellcheck source=/dev/null + . "${MK_CONFDIR}/encryption.cfg" +fi + +# Load our variables for realtime data +RTC_PLUGINS="" +# Load our config file. This provides RTC_TIMEOUT, RTC_PORT, RTC_SECRET and RTC_SECTIONS +if [ -r "${MK_CONFDIR}/real_time_checks.cfg" ]; then + # We ensure that this file is secure + chmod 640 "${MK_CONFDIR}/real_time_checks.cfg" 2>/dev/null + + # shellcheck source=/dev/null + . "${MK_CONFDIR}/real_time_checks.cfg" +fi + +# Provide information about the remote host. That helps when data +# is being sent only once to each remote host. +if var_is_set "${REMOTE_HOST}"; then + MK_RTC_HOST="${REMOTE_HOST}" + export MK_RTC_HOST +elif var_is_set "${SSH_CLIENT}"; then + MK_RTC_HOST="${SSH_CLIENT%% *}" + export MK_RTC_HOST +fi + +# If we are called via xinetd, try to find only_from configuration +if var_is_set "${REMOTE_HOST}"; then + # shellcheck disable=SC2039 + write -n 'OnlyFrom: ' + sed -n '/^service[[:space:]]*'$XINETD_SERVICE_NAME'/,/}/s/^[[:space:]]*only_from[[:space:]]*=[[:space:]]*\(.*\)/\1/p' /etc/xinetd.d/* | head -n1 + write +fi + +# Convert a three number style semantic version number to an integer for version comparisons +# This zero pads, to double digits, the second and third numbers and removes any non-numerical chars +# e.g. 'openssl 1.0.2k-fips' -> '10002' +semver_to_int() { + _sem_ver="${1:?No version number supplied}" + + # Strip the variable of any non-numerics or dots + _sem_ver="$(write "${_sem_ver}" | sed 's/[^0-9.]//g')" + + # Swap the dots for spaces and assign the outcome to the positional param array + # We want word splitting here, so we disable shellcheck's complaints + # shellcheck disable=SC2046 + set -- $(write "${_sem_ver}" | tr '.' ' ') + + # Assemble and print our integer + printf -- '%d%02d%02d' "${1}" "${2:-0}" "${3:-0}" + + unset -v _sem_ver +} + +# GNU 'coreutils' 5.3.0 broke how 'stat' handled line endings in its output. +# This was corrected somewhere around 5.92 - 5.94 STABLE. +# If we detect a version of GNU stat 5.x, we investigate and set MK_STAT_BUG +# See: https://lists.gnu.org/archive/html/bug-coreutils/2005-12/msg00157.html +if var_is_blank "${MK_STAT_BUG}"; then + # We only care about versions in the range [ 5.3.0 .. 5.94 ] (yes different numbering schemes) + if stat --version 2>&1 | grepq "GNU.*5.[3-9]"; then + # Convert the version information from semantic versioning to an integer + stat_version=$(semver_to_int "$(stat --version 2>&1 | head -n 1)") + if [ "${stat_version}" -ge 50300 ] && [ "${stat_version}" -lt 59400 ]; then + MK_STAT_BUG="true" + else + MK_STAT_BUG="false" + fi + readonly MK_STAT_BUG + export MK_STAT_BUG + fi + +fi + +# If MK_STAT_BUG is true, we correct 'stat's behaviour globally +if [ "${MK_STAT_BUG}" = "true" ]; then + stat() { + printf -- '%s\n' "$(command stat "${@}")" + } +fi + +# Expected format example: "Nov 10 2034 21:19:01" +convert_time_to_epoch() { + # Read our incoming date/time information into our variables + _month="${1:?No date provided}"; _day="${2}"; _year="${3}"; _timestamp="${4}" + write "${_timestamp}" | while IFS=':' read -r _hours _min _sec; do + # Convert the month to 0..11 range + case "${_month}" in + ([jJ]an*) _month=0 ;; + ([fF]eb*) _month=1 ;; + ([mM]ar*) _month=2 ;; + ([aA]pr*) _month=3 ;; + ([mM]ay) _month=4 ;; + ([jJ]un*) _month=5 ;; + ([jJ]ul*) _month=6 ;; + ([aA]ug*) _month=7 ;; + ([sS]ep*) _month=8 ;; + ([oO]ct*) _month=9 ;; + ([nN]ov*) _month=10 ;; + ([dD]ec*) _month=11 ;; + esac + + # Pass our variables to the mighty 'perl' + perl -e 'use Time::Local; print timegm(@ARGV[0,1,2,3,4], $ARGV[5]-1900)."\n";' \ + "${_sec}" "${_min}" "${_hours}" "${_day}" "${_month}" "${_year}" + done + unset -v _sec _min _hours _day _month _year _timestamp +} + +# Calculate how many days until the cert expires +# Short circuit versions of 'date' that don't support '-d' (e.g. Solaris) +# In this instance, we want to call 'convert_time_to_epoch()' +if date -d yesterday 2>&1 | grep illegal >/dev/null 2>&1; then + calculate_cert_epoch() { + convert_time_to_epoch "$(read_cert_expiry "${1:?No Cert Defined}")" + } +else + calculate_cert_epoch() { + date -d "$(read_cert_expiry "${1:?No Cert Defined}")" +%s + } +fi + +# Function to list CIFS mounts for use by section_nfs +get_cifs_mounts() { + if [ -r /proc/mounts ]; then + sed -n '/ cifs\? /s/[^ ]* \([^ ]*\) .*/\1/p' < /proc/mounts | sed 's/\\040/ /g' + else + mount | awk '/ cifs/{print $3;}' + fi +} + +# Function to list NFS mounts for use by section_nfs +get_nfs_mounts() { + if [ -r /proc/mounts ]; then + sed -n '/ nfs4\? /s/[^ ]* \([^ ]*\) .*/\1/p' < /proc/mounts | sed 's/\\040/ /g' + else + mount | awk '/ nfs/{print $3;}' + fi +} + +# Helper function for 'section_ntp()' +get_ntpq() { + # If 'ntpq' isn't in PATH, there's no point going further + inpath ntpq || return 1 + [ "${1}" = "--header" ] && write '<<>>' + ntpq -np | sed -e 1,2d -e 's/^\\(.\\)/\\1 /' -e 's/^ /%/' || true +} + +# In theory, this is how these functions should work: +# If the GNU stat approach fails, its failure will be silenced +# and the BSD stat approach will be tried. Failing that, it's perl to the rescue. +get_file_atime() { + stat -c %X "${1}" 2>/dev/null || + stat -f %a "${1}" 2>/dev/null || + perl -e 'if (! -f $ARGV[0]){die "0000000"};$atime=(stat($ARGV[0]))[8];print $atime."\n";' "${1}" +} + +# Note: ctime is the least portable of these metrics! +get_file_ctime() { + stat -c %Z "${1}" 2>/dev/null || + stat -f %c "${1}" 2>/dev/null || + perl -e 'if (! -f $ARGV[0]){die "0000000"};$ctime=(stat($ARGV[0]))[10];print $ctime."\n";' "${1}" +} + +get_file_mtime() { + stat -c %Y "${1}" 2>/dev/null || + stat -f %m "${1}" 2>/dev/null || + perl -e 'if (! -f $ARGV[0]){die "0000000"};$mtime=(stat($ARGV[0]))[9];print $mtime."\n";' "${1}" +} + +# This function ensures that a file is executable and +# does not have certain patterns within its name +is_valid_plugin() { + case "${1:?No plugin defined}" in + (*dpkg-new) return 1 ;; + (*dpkg-old) return 1 ;; + (*dpkg-temp) return 1 ;; + (*) [ -x "${1}" ] && return "${?}" ;; + esac +} + +# Setup a function to encrypt the output using openssl +protect_output() { + if ! inpath openssl; then + write "ERROR: protect_output(): 'openssl' not found in PATH" >&2 + return + fi + + # If this function has an arg "-rt", then we're being used for realtime data + if [ "${1}" = "-rt" ]; then + # Let's see if RTC_SECRET is defined + if [ ! -r "${MK_CONFDIR}/real_time_checks.cfg" ]; then + write "ERROR: protect_output(): Unable to read real_time_checks.cfg" + return + fi + # If we have a defined RTC_SECRET, then we prioritise that. + # This is a reversal of the previous behaviour, because we may want a setup + # where realtime checks go to hostA secured with pwdA, and normal check + # output goes to hostB secured with pwdB. This approach makes that clear. + if grepq "^RTC_SECRET=.*" "${MK_CONFDIR}/real_time_checks.cfg"; then + PASSPHRASE=$(awk -F '=' '/^RTC_SECRET/{print $2' "${MK_CONFDIR}/real_time_checks.cfg") + fi + fi + + # Convert the openssl version to an integer e.g. 1.0.2k-fips -> 10002 + _opensslVer=$(semver_to_int "$(openssl version | awk '{print $2}')") + # shellcheck disable=SC2039 + if [ "${_opensslVer}" -ge 10000 ]; then + _encCode="02" + _encMode=sha256 + else + _encCode="00" + _encMode=md5 + fi + + # Print our protocol and/or epoch information + if [ "${1}" = "-rt" ]; then + write -n "${_encCode}$(get_epoch)" + else + write -n "${_encCode}" + fi + + # Call openssl with our required digest and auth + openssl enc -aes-256-cbc -md "${_encMode}" -k "${PASSPHRASE}" -nosalt + + unset _opensslVer _encMode _encCode +} + +read_ipmi_sensors() { + for _class in Temperature Power_Unit Fan; do + ipmi-sensors "${1:?No format given}" \ + --sdr-cache-directory /var/cache "${2:?No group opt given}" "${_class}" | + sed -e 's/ /_/g' -e 's/:_\\?/ /g' -e 's@ \\([^(]*\\)_(\\([^)]*\\))@ \\2_\\1@' + # In case of a timeout immediately leave loop. + [ $? = 255 ] && break + done + unset -v _class +} + +# Function to format the output of 'ipmitool' +read_ipmitool() { + inpath ipmitool || return 1 + case "${MK_OSSTR}" in + (linux) + ipmitool sensor list | + grep -v 'command failed' | + grep -E -v '^[^ ]+ na ' | + grep -v ' discrete ' + ;; + (*bsd) + # IPMI-Data (Fans, CPU, temperature, etc) + # needs the sysutils/ipmitool and kldload ipmi.ko + ipmitool sensor list | + grep -v 'command failed' | + sed -e 's/ *| */|/g' -e "s/ /_/g" -e 's/_*$//' -e 's/|/ /g' | + grep -Ev '^[^ ]+ na ' | + grep -v ' discrete ' + ;; + esac +} + +# We prefer the 'timeout' command shipped with 'coreutils' v7.0 and newer +# Our shipped 'waitmax' is statically linked and crashes on recent Ubuntu releases +if inpath timeout; then + waitmax() { + # The busybox version of 'timeout' requires '-t' to define the duration + # So if we're in busybox, we need to insert '-t' into '$*' + # This means that 'waitmax' invocations MUST follow a standard pattern + case "$(get_shell)" in + (*busybox*) + # This translates calls like 'waitmax -s 9 5 somecommand' + # to instead read like 'waitmax -s 9 -t 5 somecommand' + # Likewise, calls like 'waitmax 5 somecommand' + # are translated to look like 'waitmax -t 5 somecommand' + # Nb: busybox 'timeout' defaults to SIGTERM, GNU's to SIGALRM + case "${1}" in + (-s) + _signal="${2}" + shift 2 + set -- "-s ${_signal} -t ${*}" + ;; + (*) set -- "-t ${*}" ;; + esac + ;; + esac + timeout "${@}" + unset -v _signal + } + # TO-DO: Check if this export is still required + export -f waitmax +fi + +#################################################################################################### + +# Runs a command asynchronous by use of a cache file. Usage: +# run_cached [-m|-a] NAME MAXAGE COMMAND +# -m mrpe-mode: stores exit code with the cache +# -ma mrpe-mode with age: stores exit code with the cache and adds the cache age +# NAME is the name of the section (also used as cache file name) +# MAXAGE is the maximum cache livetime in seconds +# A section header is automatically generated based on NAME and MAXAGE +run_cached() { + # Use _var notation to denote local scope + _cached_section= + _cached_mrpe=0 + _cached_append_age=0 + + while getopts ":ma" _cached_arg; do + case "${_cached_arg}" in + (m) _cached_mrpe=1 ;; + (a) _cached_append_age=1 ;; + (*) : ;; + esac + done + shift "$((OPTIND-1))" + + _cached_name="${1}" + _cached_maxage="${2}" + _cached_section="write '<<<${_cached_name}:cached($(get_epoch),${_cached_maxage})>>>' ; " + shift 2 + _cached_cmd="${_cached_section}${*}" + + # If the cache directory does not exist, create it + [ ! -d "${MK_VARDIR}/cache" ]; mkdir -p "${MK_VARDIR}/cache" + + if [ "${_cached_mrpe}" = 1 ]; then + _cached_file="${MK_VARDIR}/cache/_mrpe_${_cached_name}.cache" + else + _cached_file="${MK_VARDIR}/cache/${_cached_name}.cache" + fi + + # Check if the creation of the cache takes suspiciously long and kill the + # process if the age (access time) of $_cached_file.new is twice the _cached_maxage. + # Output the evantually already cached section anyways and start the cache + # update again. + if [ -e "${_cached_file}".new ]; then + _cf_atime=$(get_file_atime "${_cached_file}".new) + if [ $(( $(get_epoch) - _cf_atime )) -ge $(( _cached_maxage * 2)) ]; then + # Kill the process still accessing that file in case + # it is still running. This avoids overlapping processes! + fuser -k -9 "${_cached_file}".new >/dev/null 2>&1 + rm -f "${_cached_file}".new + fi + fi + + # Check if cache file exists and is recent enough + if [ -s "${_cached_file}" ]; then + _cf_mtime=$(get_file_mtime "${_cached_file}") + _cf_age=$(( $(get_epoch) - _cf_mtime )) + [ "${_cf_age}" -le "${_cached_maxage}" ] && _use_cachefile=1 + # Output the file in any case, even if it is + # outdated. The new file will not yet be available + if [ "${_cached_append_age}" -eq 1 ]; then + # insert the cached-string before the pipe (first -e) + # or, if no pipe found (-e t) append it (third -e), + # but only once and on the second line (2!b) (first line is section header, + # all further lines are long output) + sed -e "2s/|/ (Cached: ${_cf_age}\\/${_cached_maxage}s)|/" \ + -e t \ + -e "2s/$/ (Cached: ${_cf_age}\\/${_cached_maxage}s)/" <"${_cached_file}" + else + _cached_info="cached(${_cf_mtime},${_cached_maxage})" + case "${_cached_name}" in + (local_*) + sed -e "s/^/$_cached_info /" "${_cached_file}" + ;; + (*) + # insert the cache info in the section header (^= after '!'), + # if none is present (^= before '!') + sed -e '/^<<<.*\(:cached(\).*>>>/!s/^<<<\([^>]*\)>>>$/<<<\1:'"${_cached_info}"'>>>/' "${_cached_file}" + ;; + esac + fi + fi + + # Cache file outdated and new job not yet running? Start it + if var_is_unset "${_use_cachefile}" && [ ! -e "${_cached_file}".new ]; then + # When the command fails, the output is throws away ignored + if [ "${_cached_mrpe}" -eq 1 ]; then + write "set -o noclobber ; exec > \"$_cached_file.new\" || exit 1 ; run_mrpe $_cached_name \"$_cached_cmd\" && mv \"$_cached_file.new\" \"$_cached_file\" || rm -f \"$_cached_file\" \"$_cached_file.new\"" | nohup /bin/bash >/dev/null 2>&1 & + else + write "set -o noclobber ; exec > \"$_cached_file.new\" || exit 1 ; $_cached_cmd && mv \"$_cached_file.new\" \"$_cached_file\" || rm -f \"$_cached_file\" \"$_cached_file.new\"" | nohup /bin/bash >/dev/null 2>&1 & + fi + fi + unset -v _cached_section _cached_mrpe _cached_append_age _cached_name _cached_maxage _cached_cmd + unset -v _cached_file _cached_info _cf_atime _cf_mtime _cf_age _use_cachefile +} + +# Make run_cached available for subshells (plugins, local checks, etc.) +# TO-DO: Check if this export is still required +export -f run_cached + +#################################################################################################### +# Define a helper function to send real time check data +# If we're using ksh93 or bash, then we should have /dev/tcp and /dev/udp capability built in +# This isn't always the case, however, as these shells can be compiled without it +# So we test directly for this capability and if it's not working, we use 'netcat' +case $(waitmax 1 bash -c ": &1") in + (""|*"Connection refused"*) + send_rtc() { + # RTC_PORT is defined in "${MK_CONFDIR}/real_time_checks.cfg + waitmax 4 "/dev/udp/${1:?No Host Defined}/${2:?No Port Defined}" < /dev/stdin + } + ;; + (*) + # Otherwise, we try netcat under either its 'nc' or its 'netcat' monikers + # TO-DO: Add alternatives like socat? + if inpath nc netcat; then + send_rtc() { + _bin_nc=$(command -v nc netcat 2>/dev/null | head -n 1) + "${_bin_nc}" "${1:?No Host Defined}" "${2:?No Port Defined}" + unset -v _bin_nc + } + else + write "send_rtc(): Unable to determine communication method" >&2 + fi + ;; +esac + +# Implements Real-Time Check feature of the Check_MK agent which can send +# some section data in 1 second resolution. Useful for fast notifications and +# detailed graphing (if you configure your RRDs to this resolution). +run_real_time_checks() { + _rt_pid=${MK_VARDIR:?}/real_time_checks.pid + write "$$" >"${_rt_pid}" + + while true; do + # terminate when pidfile is gone or other Real-Time Check process started or configured timeout + if [ ! -e "${_rt_pid}" ] || [ "$(cat "${_rt_pid}")" -ne $$ ] || [ "${RTC_TIMEOUT}" -eq 0 ]; then + exit 1 + fi + + for _rt_section in ${RTC_SECTIONS}; do + # Be aware of maximum packet size. Maybe we need to check the size of the section + # output and do some kind of nicer error handling. + # 2 bytes: protocol version, 10 bytes: timestamp, rest: encrypted data + # dd is used to concatenate the output of all commands to a single write/block => udp packet + # TO-DO: See if this use of dd can be made portable. 'iflag' is a gnuism. + { + if [ "${ENCRYPTED_RT}" != "no" ]; then + # protect_output() takes care of printing our protocol and epoch info + section_"${_rt_section}" | protect_output -rt + else + write -n "99$(get_epoch)" + section_"${_rt_section}" + fi + } | dd bs=9999 iflag=fullblock 2>/dev/null | send_rtc "${MK_RTC_HOST}" "${RTC_PORT}" + done + + # Plugins + if cd "${MK_PLUGINSDIR}"; then + for _rt_plugin in ${RTC_PLUGINS}; do + # If the plugin doesn't exist, skip to the next one in the list + [ ! -f "${_rt_plugin}" ] && continue + # Same comment as per section handling above applies here + { + if [ "${ENCRYPTED_RT}" != "no" ]; then + ./"${_rt_plugin}" | protect_output -rt + else + write -n "99$(get_epoch)" + ./"${_rt_plugin}" + fi + } | dd bs=9999 iflag=fullblock 2>/dev/null | send_rtc "${MK_RTC_HOST}" "${RTC_PORT}" + done + fi + + sleep 1 + RTC_TIMEOUT=$((RTC_TIMEOUT - 1)) + done + + unset -v _rt_pid _rt_section _rt_plugin +} + +#################################################################################################### +# CHECK SECTIONS + +# RAID status of 3WARE disk controller (by Radoslaw Bak) +section_3ware_raid() { + if inpath tw_cli; then + for _tw_ctl in $(tw_cli show | awk 'NR < 4 { next } { print $1 }'); do + write '<<<3ware_info>>>' + tw_cli "/${_tw_ctl}" show all | grep -E 'Model =|Firmware|Serial' + write '<<<3ware_disks>>>' + tw_cli "/${_tw_ctl}" show drivestatus | grep -E 'p[0-9]' | sed "s/^/${_tw_ctl}\\//" + write '<<<3ware_units>>>' + tw_cli "/${_tw_ctl}" show unitstatus | grep -E 'u[0-9]' | sed "s/^/${_tw_ctl}\\//" + done + unset -v _tw_ctl + fi +} + +# TO-DO: Determine if any of these can be merged into other functions +section_aix() { + if inpath lparstat; then + write '<<>>' + lparstat 1 1 + fi + + # powerHA + if inpath lslpp; then + _cluster_cmd_output=$(lslpp -l cluster.es.server.rte) + if ! write "${_cluster_cmd_output}" | grepq "not installed"; then + # now the following commands should be available + _nodes=$(cllsnode | grep "NODE" | sed -e s/NODE//g -e s/://g) + _list_active_nodes="" + for _node in ${_nodes}; do + _active_nodes=$(clgetactivenodes -n "${_node}") + if write "${_active_nodes}" | grepq "${_node}"; then + _list_active_nodes=${_list_active_nodes}"\\n${_node}" + fi + done + + if [ "${_list_active_nodes}" ]; then + write '<<>>' + # shellcheck disable=SC2039 + write -e "${_list_active_nodes}" + cllsnode + fi + + write '<<>>' + if inpath clshowsrv ; then + waitmax 5 clshowsrv -v + else # fallback, hardcoded base installation path + waitmax 5 /usr/es/sbin/cluster/utilities/clshowsrv -v + fi + + write '<<>>' + waitmax 5 clRGinfo -s + + unset -v _cluster_cmd_output _nodes _list_active_nodes _node _active_nodes + fi + fi + + write '<<>>' + mpstat -a | tail -n1 + + write '<<>>' + # -L disables LVM lock for the query. Avoids blocking while LVM is + # doing changes. For rootvg that is fine. + lsvg -L -l rootvg +} + +# RAID controllers from areca (Taiwan) +# cli64 can be found at ftp://ftp.areca.com.tw/RaidCards/AP_Drivers/Linux/CLI/ +section_areca_raid() { + if inpath cli64; then + run_cached arc_raid_status 300 "cli64 rsf info | tail -n +3 | head -n -2" + fi +} + +# RAID status of LSI controllers via cfggen +section_cfggen() { + if inpath cfggen; then + write '<<>>' + cfggen 0 DISPLAY | + grep -E '(Target ID|State|Volume ID|Status of volume)[[:space:]]*:' | + sed -e 's/ *//g' -e 's/:/ /' + fi +} + +# Function to pull timesync information from chrony +section_chrony() { + # The "| cat" has been added for some kind of regression in RedHat 7.5. The + # SELinux rules shipped with that release were denying the chronyc call without cat. + if inpath chronyc; then + # Identify if the daemon is active... + if [ "$(systemctl | awk '/chronyd.service/{print $3; exit}')" = "active" ]; then + run_cached chrony 30 "waitmax 5 chronyc -n tracking | cat || true" + fi + fi +} + +section_cpu() { + case "${MK_OSSTR}" in + (aix) + # CPU output of Linux agent simulated (thanks to Cameron Pierce) + write '<<>>' + _load=$(uptime|sed -e 's;.*average: \([[:digit:]]\{1,\}\.[[:digit:]]\{1,\}\), \([[:digit:]]\{1,\}\.[[:digit:]]\{1,\}\), \([[:digit:]]\{1,\}\.[[:digit:]]\{1,\}\);\1 \2 \3;') + _ps=$(ps -eo thcount | awk '{SUM+=$1} END {print SUM}') + _procs=$(vmstat|grep lcpu|sed -e 's;.*lcpu=\([[:digit:]]\{1,4\}\).*;\1;') + write "${_load} 1/${_ps} $$ ${_procs}" + unset -v _load _ps _procs + ;; + (freebsd) + write '<<>>' + sysctl -n vm.loadavg | tr -d '{}' + top -b -n 1 | grep -E '^[0-9]+ processes' | awk '{print $3"/"$1}' + sysctl -n kern.lastpid + sysctl -n hw.ncpu + ;; + (hpux) + write '<<>>' + uptime + # machinfo is unsupported addon thus not in $PATH + /usr/contrib/bin/machinfo | grep -E 'logical proc|core' | tail -1 + ;; + (linux) + case "$(uname -m)" in + (armv71|armv61) _cpu_regex='^processor' ;; + (''|*) _cpu_regex='^CPU|^processor' ;; + esac + _num_cpus=$(grep -c -E ${_cpu_regex} >>' + write "$(cat /proc/loadavg) $_num_cpus" + [ -r "/proc/sys/kernel/threads-max" ] && cat /proc/sys/kernel/threads-max + else + if var_is_set "${MK_IS_DOCKERIZED}"; then + write '<<>>' + else + write '<<>>' + fi + grep "^cpu " /proc/stat + write "num_cpus ${_num_cpus}" + cat /sys/fs/cgroup/cpuacct/cpuacct.stat + fi + unset -v _cpu_regex _num_cpus + ;; + (mac) + write '<<>>' + sysctl -n vm.loadavg | tr -d '{}' + top -l 1 -n 1 | grep -E '^Processes:' | awk '{$4"/"$2;}' + write 'write $$' | bash + sysctl -n hw.ncpu + ;; + (netbsd|openbsd) + write '<<>>' + sysctl -n vm.loadavg | tr -d '{}' + top -b -n 1 | grep -E '^[0-9]+ processes' | awk '{print $3"/"$1}' + sysctl -n hw.ncpu + ;; + (solaris) + # Simulated Output of Linux /proc/cpu + write '<<>>' + _load=$(uptime|sed -e 's;.*average: \([0-9]\{1,\}\.[0-9]\{1,\}\), \([0-9]\{1,\}\.[0-9]\{1,\}\), \([0-9]\{1,\}\.[0-9]\{1,\}\).*;\1 \2 \3;') + _nthreads=$(($(ps -AL | wc -l))) + _procs=$(($(psrinfo | wc -l))) + write "${_load} 1/${_nthreads} $$ ${_procs}" + unset -v _load _nthreads _procs + ;; + esac +} + +# Print out Partitions / Filesystems. (-P gives non-wrapped POSIXed output) +# Heads up: NFS-mounts are generally supressed to avoid agent hangs. +# If hard NFS mounts are configured or you have too large nfs retry/timeout +# settings, accessing those mounts from the agent would leave you with +# thousands of agent processes and, ultimately, a dead monitored system. +# These should generally be monitored on the NFS server, not on the clients. +section_df() { + case "${MK_OSSTR}" in + (aix) + if [ -x /usr/opt/freeware/bin/df ] ; then + _df_excludefs="-x smbfs -x cifs -x iso9660 -x udf -x nfsv4 -x nfs -x mvfs -x zfs -x cdrfs" + # All instances of _df_excludefs in this function + # are uncommented because we want word-splitting + # shellcheck disable=SC2086 + /usr/opt/freeware/bin/df -PTlk ${_df_excludefs} | sed 1d + + # df inodes information + write '<<>>' + write '[df_inodes_start]' + # shellcheck disable=SC2086 + /usr/opt/freeware/bin/df -PTli ${_df_excludefs} | sed 1d + write '[df_inodes_end]' + else + df -kP | sed 's/ / - /' | grep -v ^/proc | grep -v ^Filesystem | grep -v : + fi + unset -v _df_excludefs + ;; + (freebsd) + write '<<>>' + # no special zfs handling so far, the ZFS.pools plugin has been tested to + # work on FreeBSD + if df -T > /dev/null ; then + df -kTP -t ufs | + grep -Ev '(Filesystem|devfs|procfs|fdescfs|basejail)' + else + df -kP -t ufs | + grep -Ev '(Filesystem|devfs|procfs|fdescfs|basejail)' | + awk '{ print $1,"ufs",$2,$3,$4,$5,$6 }' + fi + ;; + (hpux) + # Filesystems. HP-UX does not provide a filesystem type. We assume + # modern systems with vxfs only here. The filesystem type is currently + # not used by the check anyway. + write '<<>>' + df -kP | + sed 's/ / - /' | + awk '/^(.*-.*)$/ { print $0 } /^([^-]+)$/ { printf $0 }' | + grep -Ev "^/proc|^Filesystem|^/aha|:" + ;; + (linux) + var_is_set "${MK_IS_DOCKERIZED}" && return + + # The exclusion list is getting a bit of a problem. + # -l should hide any remote FS but seems to be all but working. + _df_excludefs="-x smbfs -x cifs -x iso9660 -x udf -x nfsv4 -x nfs -x mvfs" + _df_excludefs="${_df_excludefs} -x prl_fs -x squashfs -x devtmpfs -x autofs" + if var_is_unset "${MK_IS_LXC_CONTAINER}"; then + _df_excludefs="${_df_excludefs} -x zfs" + fi + + write '<<>>' + # shellcheck disable=SC2086 + df -PTlk ${_df_excludefs} | sed 1d + + # df inodes information + write '<<>>' + write '[df_inodes_start]' + # shellcheck disable=SC2086 + df -PTli ${_df_excludefs} | sed 1d + write '[df_inodes_end]' + + unset -v _df_excludefs + ;; + (mac) + write '<<>>' + df -kPT hfs,apfs | + grep -Ev "Time Machine|com.apple.TimeMachine.localsnapshots|/Volumes/|/private/var/vm" | + sed 1d | + while read -r _df_dev _df_rest; do + _df_type=$(diskutil info "${_df_dev}" | grep '^\s*Type' | cut -d: -f2 | tr -d '[:space:]') + write "${_df_dev} ${_df_type} ${_df_rest}" + done + unset -v df_dev df_type df_rest + ;; + (netbsd|openbsd) + write '<<>>' + df -kPt ffs | sed -e 's/^\([^ ][^ ]*\) \(.*\)$/\1 ffs \2/' | sed 1d + ;; + (solaris) + # Filesystem usage for UFS and VXFS + write '<<>>' + for _fs in ufs vxfs samfs lofs tmpfs; do + df -l -k -F ${_fs} 2>/dev/null | sed 1d | grep -v "^[^ ]*/lib/[^ ]*\\.so\\.1 " | \ + while read -r _Filesystem _kbytes _used _avail _capacity _Mountedon; do + _kbytes=$((_used + _avail)) + write "${_Filesystem} ${_fs} ${_kbytes} ${_used} ${_avail} ${_capacity} ${_Mountedon}" + done + done + unset -v _fs _Filesystem _kbytes _used _avail _capacity _Mountedon + ;; + + esac +} + +section_diskstat() { + # Performancecounter Platten + if var_is_unset "${MK_IS_DOCKERIZED}" && [ -r "/proc/diskstats" ]; then + write '<<>>' + get_epoch + _grepFilter=" (x?[shv]d[a-z]*[0-9]*|cciss/c[0-9]+d[0-9]+|emcpower[a-z]+|dm-[0-9]+" + _grepFilter="${_grepFilter}|VxVM.*|mmcblk.*|dasd[a-z]*|bcache[0-9]+|nvme[0-9]+n[0-9]+) " + grep -E "${_grepFilter}" >>' + write "[time]" + get_epoch + for _ioFile in io_service_bytes io_serviced; do + write "[${_ioFile}]" + cat "/sys/fs/cgroup/blkio/blkio.throttle.${_ioFile}" + done + write "[names]" + for _ioFile in /sys/block/*; do + # shellcheck disable=SC2039 + write "${_ioFile##*/} $(cat "${_ioFile}/dev")" + done + unset -v _ioFile + fi +} + +section_dm_raid() { + # RAID status of Linux RAID via device mapper + if inpath dmraid && _dmstatus=$(waitmax 3 dmraid -r); then + write '<<>>' + + # Output name and status + waitmax 20 dmraid -s | grep -e ^name -e ^status + + # Output disk names of the RAID disks + _disks=$(write "${_dmstatus}" | cut -f1 -d":") + + for _disk in ${_disks}; do + _device=$(cat /sys/block/"$(basename "${_disk}")"/device/model) + _status=$(write "${_dmstatus}" | grep "^${_disk}") + write "${_status} Model: ${_device}" + done + fi + unset -v _dmstatus _disks _disk _device _status +} + +section_drbd() { + if var_is_unset "${MK_IS_DOCKERIZED}" && var_is_unset "${MK_IS_LXC_CONTAINER}" && [ -r /proc/drbd ]; then + write '<<>>' + cat /proc/drbd + fi +} + +section_fileinfo() { + # Fileinfo-Check: put patterns for files into /etc/check_mk/fileinfo.cfg + perl -e ' + use File::Glob "bsd_glob"; + my @patterns = (); + foreach (bsd_glob("$ARGV[0]/fileinfo.cfg"), bsd_glob("$ARGV[0]/fileinfo.d/*")) { + open my $handle, "<", $_ or next; + while (<$handle>) { + chomp; + next if /^\s*(#|$)/; + my $pattern = $_; + $pattern =~ s/\$DATE:(.*?)\$/substr(`date +"$1"`, 0, -1)/eg; + push @patterns, $pattern; + } + warn "error while reading $_: $!\n" if $!; + close $handle; + } + exit if ! @patterns; + + print "<<>>\n", time, "\n[[[header]]]\nname|status|size|time\n[[[content]]]\n"; + + foreach (@patterns) { + foreach (bsd_glob("$_")) { + if (! -f) { + print "$_|missing\n" if ! -d; + } elsif (my @infos = stat) { + print "$_|ok|$infos[7]|$infos[9]\n"; + } else { + print "$_|stat failed: $!\n"; + } + } + } + ' -- "${MK_CONFDIR}" +} + +section_haproxy() { + for HAPROXY_SOCK in /run/haproxy/admin.sock /var/lib/haproxy/stats; do + if [ -r "$HAPROXY_SOCK" ] && inpath socat; then + write "<<>>" + write "show stat" | socat - "UNIX-CONNECT:$HAPROXY_SOCK" + fi + done +} + +# Function to output the basic details of the check_mk agent's operating conditions +section_head() { +cat << EOF +"<<>>" +"Version: ${MK_VERSION}" +"AgentOS: ${MK_OSSTR} ${MK_OSVER}" +"Hostname: ${HOSTNAME}" +"AgentDirectory: ${MK_CONFDIR}" +"DataDirectory: ${MK_VARDIR}" +"SpoolDirectory: ${MK_SPOOLDIR}" +"PluginsDirectory: ${MK_PLUGINSDIR}" +"LocalDirectory: ${MK_LOCALDIR}" +EOF +} + +section_heartbeat() { + # Heartbeat monitoring + # Different handling for heartbeat clusters with and without CRM for the resource state + # Ref: Werk 11042, commit da543b1 + if { + [ -S /var/run/heartbeat/crm/cib_ro ] || + [ -S /var/run/crm/cib_ro ] || + pgrep crmd >/dev/null 2>&1 || + pgrep -f pacemaker-controld >/dev/null 2>&1 + }; then + write '<<>>' + TZ=UTC crm_mon -1 -r | grep -v ^$ | sed 's/^ //; /^\sResource Group:/,$ s/^\s//; s/^\s/_/g' + fi + if inpath cl_status; then + write '<<>>' + cl_status rscstatus + + write '<<>>' + for _node in $(cl_status listnodes); do + if [ "${_node}" != "$(write "${HOSTNAME}" | tr '[:upper:]' '[:lower:]')" ]; then + _status=$(cl_status nodestatus "${_node}") + # shellcheck disable=SC2039 + write -n "${_node} ${_status}" + for _link in $(cl_status listhblinks "${_node}" 2>/dev/null); do + # shellcheck disable=SC2039 + write -n " ${_link} $(cl_status hblinkstatus "${_node}" "${_link}")" + done + write + fi + done + unset -v _node _status _link + fi +} + +section_hpux() { + # Logical Volume Manager + write '<<>>' + /sbin/vgdisplay -v -F + + write '<<>>' + if inpath cmviewcl; then + cmviewcl -v -f line | grep summary + fi + + # Kernel tunnables + if inpath kcusage; then + write '<<>>' + kcusage -l + fi + + # State of FC HBAs + write '<<>>' + for _hba in /dev/fcd*; do + write "${_hba}" + /opt/fcms/bin/fcdutil "${_hba}" | + grep -e "Driver state" \ + -e "Topology" \ + -e "Dump Available" \ + -e "Code version" \ + -e "Hardware Path" \ + -e "Port World" + done + unset -v _hba +} + +section_iostat() { + # Skip if 'section_diskstat()' has this already covered + if [ -r /proc/diskstats ] || [ "${MK_IS_DOCKERIZED}" ]; then + return 0 + fi + # Skip if 'iostat' isn't present + if ! inpath iostat; then + return 0 + fi + + _grepFilter="^(x?[shv]d[a-z]*[0-9]*|cciss/c[0-9]+d[0-9]+|emcpower[a-z]+|dm-[0-9]+|VxVM.*" + _grepFilter="${_grepFilter}|mmcblk.*|dasd[a-z]*|bcache[0-9]+|nvme[0-9]+n[0-9]+|hdisk) " + + write "<<<${MK_OSSTR}_iostat>>>" + + # AIX was formerly under the header '<<>>' + # TO-DO: Update checkman/aix_diskiod, checks/aix_diskiod etc + # OpenBSD has a different output format, so we ringfence it for now + # FreeBSD - we might like to consider 'gstat' and/or 'dtrace' as well + case "${MK_OSSTR}" in + (openbsd) + iostat "${_iostatOpts:--d}" 2 1 + ;; + (*) + iostat "${_iostatOpts:--d}" 2 1 | tr -s ' ' | grep -E "${_grepFilter}" + ;; + esac + + unset -v _iostatOpts _grepFilter +} + +# This function pairs with read_ipmitool() +# TO-DO: merge them somehow? +section_ipmitool() { + # Hardware sensors via IPMI (need ipmitool) + if inpath ipmitool; then + # We denote our delimiter 'i.e. separator' as being at column 124 + run_cached "ipmi:sep(124)" 300 "waitmax 300 read_ipmitool" + # readable discrete sensor states + run_cached "ipmi_discrete:sep(124)" 300 "waitmax 300 ipmitool sdr elist compact" + fi +} + +section_ipmisensors() { + # IPMI data via ipmi-sensors (of freeipmi). Please make sure, that if you + # have installed freeipmi that IPMI is really support by your hardware. + if (inpath ipmi-sensors && ls /dev/ipmi*) >/dev/null 2>&1; then + write '<<>>' + # Newer ipmi-sensors version have new output format; Legacy format can be used + if ipmi-sensors --help | grepq legacy-output; then + _ipmi_format="--legacy-output" + else + _ipmi_format="" + fi + if ipmi-sensors --help | grepq " \\-\\-groups"; then + _ipmi_group_opt="-g" + else + _ipmi_group_opt="-t" + fi + + # At least with ipmi-sensors 0.7.16 this group is Power_Unit instead of "Power Unit" + run_cached ipmi_sensors 300 "read_ipmi_sensors ${_ipmi_format} ${_ipmi_group_opt}" + unset -v _ipmi_format _ipmi_group_opt + fi +} + +section_jobs() { + # Get statistics about monitored jobs. Below the job directory there + # is a sub directory per user that ran a job. That directory must be + # owned by the user so that a symlink or hardlink attack for reading + # arbitrary files can be avoided. + ( + cd "${MK_VARDIR}/job" || return + write '<<>>' + case "${MK_OSSTR}" in + (aix) + for _username in *; do + if [ -d "${_username}" ] && cd "${_username}"; then + for _filename in *; do + # Maybe a cursory "if [ -r "${_filename}" ]" could go here? + write "==> ${_filename} <==" + cat "${_filename}" + done + cd .. + fi + done + ;; + (linux) + for _username in *; do + if [ -d "${_username}" ] && cd "${_username}"; then + if [ "${EUID}" -eq 0 ]; then + su -s "${SHELL}" "${_username}" -c "head -n -0 -v *" + else + head -n -0 -v ./* + fi + cd .. + fi + done + ;; + (solaris) + for _username in *; do + if [ -d "${_username}" ] && cd "${_username}"; then + _count=$(su -s "${SHELL}" "${_username}" -c "ls -1 * | wc -l") + + if [ "${_count}" -eq "1" ]; then + _filename=$(su -s "${SHELL}" "${_username}" -c "ls -1 *") + write "==> ${_filename} <==" + fi + + su -s "${SHELL}" "${_username}" -c "head -n1000 *" + cd .. + fi + done + ;; + esac + ) + unset -v _username _filename _count +} + +section_kernel() { + case "${MK_OSSTR}" in + (freebsd) + # Performancecounter Kernel + write "<<>>" + get_epoch + _forks=$(sysctl -n vm.stats.vm.v_forks) + _vforks=$(sysctl -n vm.stats.vm.v_vforks) + _rforks=$(sysctl -n vm.stats.vm.v_rforks) + _kthreads=$(sysctl -n vm.stats.vm.v_kthreads) + write "cpu" "$(sysctl -n kern.cp_time | awk ' { print $1" "$2" "$3" "$5" "$4 } ')" + write "ctxt" "$(sysctl -n vm.stats.sys.v_swtch)" + # TO-DO: confirm that these are all ints and if so, convert to $(()) + # shellcheck disable=SC2003 + write "processes" "$(expr "${_forks}" + "${_vforks}" + "${_rforks}" + "${_kthreads}")" + unset -v _forks _vforks _rforks _kthreads + ;; + (linux) + # Performancecounter Kernel + if var_is_unset "${MK_IS_DOCKERIZED}" && var_is_unset "${MK_IS_LXC_CONTAINER}"; then + write '<<>>' + get_epoch + cat /proc/vmstat /proc/stat + fi + ;; + esac +} + +section_local() { + # Local checks + write '<<>>' + # Contain the 'cd' within a subshell so that we return + # where we came from once the subshell closes + ( + if cd "${MK_LOCALDIR}" 2>/dev/null; then + for _skript in ./*; do + if is_valid_plugin "${_skript}"; then + ./"${_skript}" + fi + done + # Call some plugins only every X'th second + for _skript in [1-9]*/*; do + if is_valid_plugin "${_skript}"; then + _skript_cache=$(write "${_skript}" | sed 's/\//\\/') + run_cached "local_${_skript_cache}" "${_skript%/*}" "${_skript}" + fi + done + unset -v _skript _skript_cache + fi + ) +} + +section_mac() { + # OSX Timemachine + if inpath tmutil; then + write '<<>>' + tmutil latestbackup 2>&1 + fi +} + +# TODO: Flesh this out +# See https://apple.stackexchange.com/questions/117864/how-can-i-tell-if-my-mac-is-keeping-the-clock-updated-properly +section_mac_time() { + # If we're not using network time, there's no point continuing + systemsetup -getusingnetworktime | grepq "Network Time: On" || return 1 + # Figure out our OS version so that we can determine our approach + var_is_blank "${MK_OSVER}" && return 1 + _macver=$(write "${MK_OSVER}" | tr -d '.') + if [ "${_macver}" -ge "1014" ]; then + # If this file is missing, create it + [ -f /var/db/ntp-kod ] || touch /var/db/ntp-kod + chown root:wheel /var/db/ntp-kod >/dev/null 2>&1 + + # Alt: systemsetup -getnetworktimeserver | awk -F ': ' '{print $2; exit}' + _ntp_server=$(awk '/^server/{print $2; exit}' /etc/ntp.conf) + var_is_blank "${_ntp_server}" && return + # Call sntp + sntp -sS "${_ntp_server}" + elif [ "${_macver}" -eq "1013" ]; then + defaults read /var/db/timed/com.apple.timed TMLastSystemTime + # TODO: parse /var/db/timed/com.apple.timed.plist and provide compatible output + # defaults read /var/db/timed/com.apple.timed TMSystemSource | grep TMTimeError | head -n1 | awk -F'"' '{print $2}' + elif [ "${_macver}" -le "1012" ]; then + get_ntpq + fi + unset -v _macver _ntp_server +} + +#TO-DO: De-duplicate this mess +section_mailqueue() { + # Postfix mailqueue monitoring + # Determine the number of mails and their size in several postfix mail queues + read_postfix_queue_dirs() { + _postfix_queue_dir="${1}" + if var_is_set "${_postfix_queue_dir}"; then + write '<<>>' + write "[[[${2}]]]" + + for _queue in deferred active; do + _count=$(find "${_postfix_queue_dir}/${_queue}" -type f | wc -l) + _size=$(du -s "${_postfix_queue_dir}/${_queue}" | awk '{print $1 }') + if var_is_empty "${_count}"; then + write "Mail queue is empty" + else + write "QUEUE_${_queue} ${_size:-0} ${_count}" + fi + done + fi + unset -v _postfix_queue_dir _queue _count _size + } + + # Postfix mailqueue monitoring + # Determine the number of mails and their size in several postfix mail queues + if inpath postconf; then + case "${MK_OSSTR}" in + (freebsd) + # Only handle mailq when postfix user is present. The mailq command is also + # available when postfix is not installed. But it produces different outputs + # which are not handled by the check at the moment. So try to filter out the + # systems not using postfix by searching for the postfix user. + # + # Cannot take the whole outout. This could produce several MB of agent output + # on blocking queues. + # Only handle the last 6 lines (includes the summary line at the bottom and + # the last message in the queue. The last message is not used at the moment + # but it could be used to get the timestamp of the last message. + write '<<>>' + postfix_queue_dir=$(postconf -h queue_directory) + postfix_count=$(find "$postfix_queue_dir"/deferred -type f | wc -l) + postfix_size=$(du -ks "$postfix_queue_dir"/deferred | awk '{print $1 }') + if [ "$postfix_count" -gt 0 ]; then + write "$postfix_size" Kbytes in "$postfix_count" Requests. + else + write Mail queue is empty + fi + ;; + (linux) + # Check if multi_instance_directories exists in main.cf and is not empty + # always takes the last entry, multiple entries possible + multi_instances_dirs=$(postconf -c /etc/postfix 2>/dev/null | grep ^multi_instance_directories | sed 's/.*=[[:space:]]*//g') + if var_is_set "$multi_instances_dirs"; then + for queue_dir in $multi_instances_dirs; do + if var_is_set "$queue_dir"; then + postfix_queue_dir=$(postconf -c "$queue_dir" 2>/dev/null | grep ^queue_directory | sed 's/.*=[[:space:]]*//g') + read_postfix_queue_dirs "$postfix_queue_dir" "$queue_dir" + fi + done + fi + # Always check for the default queue. It can exist even if multiple instances are configured + read_postfix_queue_dirs "$(postconf -h queue_directory 2>/dev/null)" + ;; + esac + + elif [ -x /usr/sbin/ssmtp ]; then + write '<<>>' + mailq 2>&1 | sed 's/^[^:]*: \(.*\)/\1/' | tail -n 6 + + # *bsd but should be a reasonable generic fallback + elif inpath mailq && getent passwd postfix >/dev/null 2>&1; then + write '<<>>' + mailq | tail -n 6 + fi + + # From AIX, seems generic enough + if [ -x /usr/sbin/sendmail ] ; then + write '<<>>'; + mailq 2>&1 | tail -n 6 + fi + + # Postfix status monitoring. Can handle multiple instances. + if inpath postfix; then + write "<<>>" + for i in /var/spool/postfix*/; do + if [ -e "$i/pid/master.pid" ]; then + if [ -r "$i/pid/master.pid" ]; then + postfix_pid=$(sed 's/ //g' <"$i/pid/master.pid") # handle possible spaces in output + if readlink -- "/proc/${postfix_pid}/exe" | grepq ".*postfix/\\(s\\?bin/\\)\\?master.*"; then + write "$i:the Postfix mail system is running:PID:$postfix_pid" | sed 's/\/var\/spool\///g' + else + write "$i:PID file exists but instance is not running!" | sed 's/\/var\/spool\///g' + fi + else + write "$i:PID file exists but is not readable" + fi + else + write "$i:the Postfix mail system is not running" | sed 's/\/var\/spool\///g' + fi + done + fi + + # Check status of qmail mailqueue + if inpath qmail-qstat; then + write "<<>>" + qmail-qstat + fi + + # Nullmailer queue monitoring + if inpath nullmailer-send && [ -d /var/spool/nullmailer/queue ]; then + write '<<>>' + COUNT=$(find /var/spool/nullmailer/queue -type f | wc -l) + SIZE=$(du -s /var/spool/nullmailer/queue | awk '{print $1 }') + write "$SIZE $COUNT" + fi +} + +section_megaraid() { + # RAID status of LSI MegaRAID controller via MegaCli. You can download that tool from: + # http://www.lsi.com/downloads/Public/MegaRAID%20Common%20Files/8.02.16_MegaCLI.zip + # From this list, the first that's found in PATH (if any) is mapped to lsicli() + for _cliAlt in MegaCli MegaCli64 megacli storcli storcli64; do + if inpath "${_cliAlt}"; then + lsicli() { "${_cliAlt}"; } + break + fi + unset -v _cliAlt + done + + if inpath lsicli; then + write '<<>>' + for part in $(lsicli -EncInfo -aALL -NoLog >>' + lsicli -LDInfo -Lall -aALL -NoLog >>' + lsicli -AdpBbuCmd -GetBbuStatus -aALL -NoLog >>" + # lsicli /call/eall/sall show + # write "<<>>" + # /opt/MegaRAID/storcli/storcli64 /call/vall show + # fi + fi +} + +# TO-DO: Standardise section headers +# We ignore the shellcheck alerts for 'write -n' as our 'write()' solves this +# shellcheck disable=SC2039 +section_mem() { + case "${MK_OSSTR}" in + (aix) + write '<<>>' + vmstat -v | tr -s ' ' + swap -s + # TO-DO: Possible candidates? + # lsattr -El sys0 -a realmem + # lsps -s + # lsps -a + ;; + (hpux) + write '<<>>' + machinfo | grep ^Memory + vmstat | sed -n 3p + ;; + (linux) + if var_is_unset "${MK_IS_DOCKERIZED}"; then + write '<<>>' + grep -E -v '^Swap:|^Mem:|total:' >>' + cat /sys/fs/cgroup/memory/memory.stat + write "usage_in_bytes $(cat /sys/fs/cgroup/memory/memory.usage_in_bytes)" + write "limit_in_bytes $(cat /sys/fs/cgroup/memory/memory.limit_in_bytes)" + grep -F 'MemTotal:' /proc/meminfo + fi + ;; + (mac) + # TODO: Validate if 'bc' is required here or if we can use builtin integer arithmetic + _pagesSpeculative=$(vm_stat | awk '/speculative:/{print $3}') + _pagesInactive=$(vm_stat | awk '/inactive:/{print $3}') + _pagesFree=$(vm_stat | awk '/free:/{print $3}') + _pageSize=$(vm_stat | awk '/^Mach/{print $8}') + _compressedPagesStored=$(vm_stat | awk '/stored in compressor:/{print $5}') + _compressedPagesOccupied=$(vm_stat | awk '/occupied by compressor:/{print $5}') + _memFree=$(write "( ${_pagesSpeculative} + ${_pagesInactive} + ${_pagesFree} ) * ${_pageSize} / 1024" | bc) + _swapTotal=$(write "${_compressedPagesStored} * ${_pageSize} / 1024" | bc) + _swapFree=$(write "( ${_compressedPagesStored} - ${_compressedPagesOccupied} ) * ${_pageSize} / 1024" | bc) + write '<<>>' + write "MemTotal: $(write "$(sysctl -n hw.memsize)/1024" | bc) kB" + write "MemFree: ${_memFree} kB" + write "SwapTotal: ${_swapTotal} kB" + write "SwapFree: ${_swapFree} kB" + + unset -v _pagesSpeculative _pagesInactive _pagesFree _pageSize _swapTotal _swapFree + unset -v _compressedPagesStored _compressedPagesOccupied _memFree + ;; + (openbsd) + write "<<>>" + MEM_FREE=$(vmstat | tail -n1 | awk '{ print $5 }') + MEM_TOTAL=$(sysctl hw.usermem | cut -d= -f2) + MEM_TOTAL=$(write "$MEM_TOTAL/1024" | bc) + + SWAPCTL_OUTPUT=$(swapctl -k -s) + SWAP_FREE=$(write "$SWAPCTL_OUTPUT" | awk '{ print $7 }') + SWAP_TOTAL=$(write "$SWAPCTL_OUTPUT" | awk '{ print $2 }') + + # if there is no swap space swap values are 0 + if var_is_unset "$SWAPCTL_OUTPUT"; then + SWAP_FREE=0 + SWAP_TOTAL=0 + fi + + write -e "MemTotal:\\t$MEM_TOTAL kB" + write -e "MemFree:\\t$MEM_FREE kB" + write -e "SwapTotal:\\t$SWAP_TOTAL kB" + write -e "SwapFree:\\t$SWAP_FREE kB" + ;; + (solaris) + # We prefer to use 'statgrab' if it's available. See section_statgrab() + # If 'statgrab' isn't available, we offer this workaround + if ! inpath statgrab; then + if [ -x /usr/bin/top ] || [ -x /usr/local/bin/top ]; then + write "<<>>" + if [ -x /usr/bin/top ]; then /usr/bin/top | grep '^Memory:'; fi + if [ -x /usr/local/bin/top ]; then /usr/local/bin/top | grep '^Memory:'; fi + fi + fi + ;; + esac +} + +section_mkbackup() { + # Collect states of configured Check_MK site backup jobs + if ls /omd/sites/*/var/check_mk/backup/*.state >/dev/null 2>&1; then + write "<<>>" + for _state_file in /omd/sites/*/var/check_mk/backup/*.state; do + _omd_site=${_state_file#/*/*/*} + _omd_site=${_omd_site%%/*} + + _job_ident=${F%.state} + _job_ident=${_job_ident##*/} + + if [ "${_job_ident}" != "restore" ]; then + write "[[[site:${_omd_site}:${_job_ident}]]]" + cat "${_state_file}" + write + fi + done + unset -v _state_file _omd_site _job_ident + fi + + # Collect states of configured CMA backup jobs + if inpath mkbackup && ls /var/lib/mkbackup/*.state >/dev/null 2>&1; then + write "<<>>" + for _state_file in /var/lib/mkbackup/*.state; do + _job_ident=${_state_file%.state} + _job_ident=${_job_ident##*/} + + if [ "${_job_ident}" != "restore" ]; then + write "[[[system:${_job_ident}]]]" + cat "$F" + write + fi + done + unset -v _state_file _job_ident + fi +} + +section_mounts() { + case "${MK_OSSTR}" in + (freebsd) + # Check mount options. + # FreeBSD doesn't do remount-ro on errors, but the users might consider + # security related mount options more important. + write '<<>>' + mount -p -t ufs + ;; + (linux) + # Check mount options. Filesystems may switch to 'ro' in case + # of a read error. + write '<<>>' + grep ^/dev >>' + + _mrpe_plugin=${_mrpe_cmdline%% *} + _mrpe_output=$(eval "${_mrpe_cmdline}") + + # We ignore the shellcheck alerts for 'write -n' as our 'write()' solves this + # shellcheck disable=SC2039 + write -n "(${_mrpe_plugin##*/}) ${_mrpe_descr} ${?} ${_mrpe_output}" | tr \\n \\1 + write + + # Unset the function variables + unset -v _mrpe_descr _mrpe_cmdline _mrpe_plugin _mrpe_output +} + +section_mrpe() { + + # MK's Remote Plugin Executor + # We handle mrpe.cfg format that looks like this for synchronous running: + # SERVICE_NAME /path/to/the/plugin/script -warn 10 -crit 20 + # Or this for asynchronous running: + # SERVICE_NAME (interval=360:appendage=1) /path/to/the/plugin/script -warn 10 -crit 20 + if [ -r "${MK_CONFDIR}/mrpe.cfg" ]; then + grep -Ev '^[[:space:]]*($|#)' "${MK_CONFDIR}/mrpe.cfg" | + while read -r _mrpe_descr _mrpe_cmdline; do + # Detect if _mrpe_cmdline starts with '(' i.e. async mode + case "${_mrpe_cmdline}" in + (\(*) + # If we do start with '(', then split 'params' out of '_mrpe_cmdline' + # We strip the brackets from 'params' and rewrite '_mrpe_cmdline' without the params + _mrpe_params=$(write "${_mrpe_cmdline% *}" | tr -d '()') + _mrpe_cmdline="${_mrpe_cmdline##* }" + + # split multiple parameter assignments + for _par in $(write "${_mrpe_params}" | tr ":" "\\n"); do + # split each assignment + _key="${_par%=*}" + _value="${_par#*=}" + # Setting 'args' here upsets shellcheck because we're within a while pipeline + # This should be fine: if we use it, it's immediately so + # shellcheck disable=SC2030 + case "${_key}" in + (interval) _interval="${_value}" ;; + (appendage) _args="-ma" ;; + esac + done + run_cached "${_args:--m}" "${_mrpe_descr}" "${_interval:-}" "${_mrpe_cmdline}" + ;; + (*) + run_mrpe "${_mrpe_descr}" "${_mrpe_cmdline}" + ;; + esac + done + unset -v _mrpe_descr _mrpe_cmdline _mrpe_params _par _key _value _interval _args + fi +} + +section_multipathing() { + case "${MK_OSSTR}" in + (aix) + # TO-DO: Consider merging in mpio_get_config, lsmpio, sanlun, + # dlnkmgr, powermt and pcmpath handling + write '<<>>' + lspath -F"name parent status" + ;; + (freebsd) + # Multipathing is supported in FreeBSD by now + # http://www.mywushublog.com/2010/06/freebsd-and-multipath/ + if kldstat -v | grep g_multipath > /dev/null ; then + write '<<>>' + gmultipath status | grep -v ^Name + fi + ;; + (hpux) + # Multipathing + write '<<>>' + scsimgr lun_map | grep -E '^[[:space:]]*(LUN PATH|State|World Wide Identifier)' + ;; + (linux) + if inpath multipath; then + if [ -f /etc/multipath.conf ]; then + write '<<>>' + multipath -l + fi + fi + ;; + (solaris) + if inpath mpathadm; then + if [ "$zonename" = "global" ]; then + write '<<>>' + mpathadm list LU | + nawk '{if(NR%3==1){dev=$1} + if(NR%3==2){tc=$NF} + if(NR%3==0){printf "%s %s %s\n",dev,tc,$NF}}' + fi + fi + ;; + esac +} + +section_net() { + case "${MK_OSSTR}" in + (aix) + write "<<>>" + for _ent in $(ifconfig -a | grep '^en' | cut -d ":" -f 1); do + write "[${_ent}]" + entstat "${_ent}" | grep -E "(^Hardware|^Bytes:|^Packets:|^Transmit|^Broadcast:|^Multicast:)" + entstat "${_ent}" | grep -p "Driver Flags:" + done + unset -v _ent + ;; + (hpux) + write '<<>>' + for _nic in $(nwmgr -g | sed -n '/^lan/s/\(^[^ ]* \).*/\1/p'); do + nwmgr -g --st mib -c "${_nic}" + done + unset -v _nic + ;; + (linux) + # New variant: Information about speed and state in one section + if inpath ip; then + write '<<>>' + write "[start_iplink]" + ip address + write "[end_iplink]" + fi + + write '<<>>' + sed 1,2d /proc/net/dev + sed -e 1,2d /proc/net/dev | cut -d':' -f1 | sort | while read -r _eth; do + write "[${_eth}]" + if inpath ethtool; then + ethtool "${_eth}" | grep -E '(Speed|Duplex|Link detected|Auto-negotiation):' + else + # If interface down we get "Invalid argument" + _speed=$(cat "/sys/class/net/${_eth}/speed" 2>/dev/null) + var_is_set "${_speed}" && write -e "\tSpeed: ${_speed}Mb/s\n" + fi + # shellcheck disable=SC2039 + write -e "\tAddress: $(cat "/sys/class/net/${_eth}/address")\n" + done + unset -v _eth _speed + ;; + (mac) + write '<<>>' + write "[start_iplink]" + _linkNum=1 + if inpath ip; then + _interfaces=$(ip a | awk -F ':' '/^[a-z]/{print $1}' | grep -Ev '^(awdl0|utun|llw)') + var_is_blank "${_interfaces}" && return + for _link in ${_interfaces}; do + write "${_linkNum}: $(ip a show "${_link}")" + _linkNum=$(( _linkNum + 1 )) + done + else + _interfaces=$(netstat -inb | awk '/(^en|^lo).*Link/ && $7>0{print $1}') + var_is_blank "${_interfaces}" && return + for _link in ${_interfaces}; do + write "${_linkNum}: $(ifconfig "${_link}")" + _linkNum=$(( _linkNum + 1 )) + done + fi + write "[end_iplink]" + + write '<<>>'; + # Format: + # Name, IBytes, IPckts, IErr, Drop, 0 (fifo), 0 (frame), 0 (compressed), 0 (multicast), + # Coll, Obyts, OPckts, OErrs, 0 (drop), 0 (fifo), 0 (coll), 0 (drop) + # Collisions & drops are not broken out into in/out, so I'm assuming receive for now + netstat -inb | + grep -E "${_interfaces}" | + awk '/Link/{print $1": "$7,$5,$6,$12,"0 0 0 0",$11,$10,$8,$9,"0 0 0 0";}' + + # Convert OSX ifconfig to lnx_if format: + # Note: Wifi interfaces may have variable speeds each call + for _iface in ${_interfaces}; do + _cur_ifconfig=$(ifconfig -v "${_iface}") + # Alt: grep -E "^\s*(down)?link rate:\s*" | cut -d " " -f3,4 | sed -E 's, (.)bps$,\1b/s,' + _ifspeed=$(write "${_cur_ifconfig}" | grep -Eo '[0-9]+baseT' | sed -e 's@baseT@Mb/s@') + write "${_cur_ifconfig}" | grepq "full-duplex" && _ifduplex="Full" + write "${_cur_ifconfig}" | grepq "media: autoselect" && _ifauto="on" + write "${_cur_ifconfig}" | grepq "status: active" && _iflink="yes" + [ "${_iface}" = "lo0" ] && _iflink="yes" + _ifaddr=$(write "${_cur_ifconfig}" | grep -E '^\s*ether' | cut -d " " -f2) + write "[${_iface}]" + write -e "\tSpeed: ${_ifspeed:-Unknown!}" + write -e "\tDuplex: ${_ifduplex:-Half}" + write -e "\tAuto-negotiation: ${_ifauto:-off}" + write -e "\tLink detected: ${_iflink:-no}" + write -e "\tAddress: ${_ifaddr:-00:00:00:00:00:00}" + done + + unset -v _linkNum _link _iface _cur_ifconfig _ifspeed _ifduplex _ifauto _iflink _ifaddr + ;; + (openbsd) + write '<<>>' + # Example line: + # em0 1500 08:00:27:e6:c4:70 16358 0 254 0 0 + netstat -in | grep '' | grep -E -v "\\*|lo|pfsync|enc" | + while read -r _ifName _ _ _ _pktsIn _errsIn _pktsOut _errsOut _collisions; do + # Make another 'netstat' call to get our extra metrics + # Example line: + # em0 1500 08:00:27:e6:c4:70 1815571 37701 + netstat -inb | grep "${_ifName}.*" | + while read -r _ _ _ _ _bytesIn _bytesOut; do + _ifData="${_ifName}:${_bytesIn} ${_pktsIn} ${_errsIn} 0 0 0 0 0 ${_bytesOut}" + _ifData="${_ifData} ${_pktsOut} ${_errsOut} 0 0 ${_collisions} 0 0" + write "${_ifData}" + done + done + + unset -v _ifName _pktsIn _errsIn _pktsOut _errsOut _collisions _ifData + + for _ifName in $(netstat -in | awk '//{print $1}' | grep -E -v "\\*|lo|pfsync|enc"); do + write "[${_ifName}]" + + _macAddr=$(ifconfig "${_ifName}" | awk '/lladdr/{print $NF}') + + # Example line: + # media: Ethernet autoselect (1000baseT full-duplex) + _ifData=$(ifconfig "${_ifName}" | grep "media:") + + # Speed + _ifSpeed=$(write "${_ifData}" | cut -d\( -f2 | cut -db -f1) + # shellcheck disable=SC2039 + [ "${_ifSpeed}" ] && write -e "\\tSpeed: ${_ifSpeed}Mb/s" + + # Detect duplexity - in reality only available for physical devices but + # virtual ones like CARP devices will get at least a half duplex + # shellcheck disable=SC2039 + case "${_ifData}" in + (*full-duplex*) write -e "\\tDuplex: Full" ;; + (*half-duplex*) write -e "\\tDuplex: Half" ;; + (*) write -e "\\tDuplex: Unknown" ;; + esac + + # Auto-negotiation + # shellcheck disable=SC2039 + case "${_ifData}" in + (*autoselect*) write -e "\\tAuto-negotiation: on" ;; + (*) write -e "\\tAuto-negotiation: off" ;; + esac + + # Detect detected link + if ifconfig "${_ifName}" | grep "status:" | grepq -E "active|backup|master"; then + # shellcheck disable=SC2039 + write -e "\\tLink detected: yes" + fi + # shellcheck disable=SC2039 + write -e "\\tAddress: ${_macAddr}" + done + + unset -v _ifName _ifData _ifSpeed _macAddr + ;; + esac +} + +section_net_bonding() { + # Current state of bonding interfaces + if [ -e /proc/net/bonding ]; then + write '<<>>' + ( + cd /proc/net/bonding || return + head -v -n 1000 ./* + ) + fi +} + +section_net_ctr() { + case "${MK_OSSTR}" in + (freebsd) + # Network device statistics (Packets, Collisions, etc) + # only the "Link/Num" interface has all counters. + write '<<>>' + get_epoch + if [ "$(write "${osver}" | cut -f1 -d\. )" -gt "8" ]; then + netstat -inb | + grep -Ev '(^Name|lo|plip)' | + grep Link | + awk '{print $1" "$8" "$5" "$6" "$7" 0 0 0 0 "$11" "$9" "$10" 0 0 0 0 0"}' + else + # pad output for freebsd 7 and before + netstat -inb | + grep -Ev '(^Name|lo|plip)' | + grep Link | + awk '{print $1" "$7" "$5" "$6" 0 0 0 0 0 "$10" "$8" "$9" 0 0 "$11" 0 0"}' + fi + ;; + (mac) + write '<<>>'; + get_epoch + netstat -inb | + grep -Ev '(^Name|lo|plip)' | + awk '/Link/{ print $1,$7,$5,$6,"0","0","0","0","0",$10,$8,$9,"0","0",$11,"0","0"; }' + ;; + (netbsd) + write '<<>>' + # BI= Bytes in + # PI= Packets in + # EI= Errors in + # EO= Errors out + # BO= Bytes out + # PO= Packets out + # CO= Colls + + Z1=1 + Z2=p + + get_epoch + while [ $Z1 -lt 15 ]; do + BI=$( netstat -inb | grep -Ev Name | awk '/Link/{print $1" "$5}' | sed -ne $Z1$Z2 ) + PI=$( netstat -in | grep -Ev Name | awk '/Link/{print $5}' | sed -ne $Z1$Z2 ) + EI=$( netstat -in | grep -Ev Name | awk '/Link/{print $6}' | sed -ne $Z1$Z2 ) + FF1="0 0 0 0 0" + BO=$( netstat -inb | grep -Ev Name | awk '/Link/{print $6}' | sed -ne $Z1$Z2 ) + PO=$( netstat -in | grep -Ev Name | awk '/Link/{print $7}' | sed -ne $Z1$Z2 ) + EO=$( netstat -in | grep -Ev Name | awk '/Link/{print $8}' | sed -ne $Z1$Z2 ) + CO=$( netstat -in | grep -Ev Name | awk '/Link/{print $9}' | sed -ne $Z1$Z2 ) + FF2="0 0" + if [ "$PI" -gt "0" ]; then + write "$BI $PI $EI $FF1 $BO $PO $EO $FF2 $CO $FF2" + fi + Z1=$((Z1+1)) + done + esac +} + +section_net_openvswitch_bonding() { + # Same as section_net_bonding(), but for Open vSwitch bonding + if inpath ovs-appctl; then + _bonds=$(ovs-appctl bond/list) + _col=$(write "${_bonds}" | awk '{for(i=1;i<=NF;i++) {if($i = "bond") printf("%d", i)} exit 0}') + write '<<>>' + for _bond in $(write "${_bonds}" | sed -e 1d | cut -f"${_col}"); do + write "[${_bond}]" + ovs-appctl bond/show "${_bond}" + done + fi + unset -v _bonds _col _bond +} + +section_nfs() { + # Check NFS mounts by accessing them with stat -f (System call statfs()). + # If this lasts more then 2 seconds we consider it as hanging. We need waitmax. + if inpath waitmax; then + case "${MK_OSSTR}" in + (aix) statFmt="ok - - - -" ;; + (linux) statFmt="ok %b %f %a %s" ;; + esac + write '<<>>' + get_nfs_mounts | while read -r mountPoint; do + waitmax -s 9 5 stat -f -c "${mountPoint} ${statFmt}" "${mountPoint}" \ + || write "${mountPoint} hanging 0 0 0 0" + done + + write '<<>>' + get_cifs_mounts | while read -r mountPoint; do + if [ ! -r "${mountPoint}" ]; then + write "${mountPoint} Permission denied" + else + waitmax -s 9 2 stat -f -c "${mountPoint} ${statFmt}" "${mountPoint}" \ + || write "${mountPoint} hanging 0 0 0 0" + fi + done + fi + + # Clean up our function variables + unset mountPoint statFmt +} + +# Requires 'get_ntpq()' +section_ntp() { + # If 'ntpq' isn't in PATH, there's no point going further + inpath ntpq || return 1 + + # First we try to identify if we're beholden to systemd + if inpath systemctl; then + # shellcheck disable=SC2016 + if [ "$(systemctl | awk '/ntp.service|ntpd.service/{print $3; exit}')" = "active" ]; then + # remove heading, make first column space separated + run_cached -s ntp 30 "waitmax 5 get_ntpq" + fi + # Return to leave the function with no further processing + return + fi + + # If we get to this point, we attempt to test classic ntp daemons + case "${MK_OSSTR}" in + (aix) + [ "$(lssrc -s xntpd|grep -c active)" -gt 0 ] && get_ntpq --header + ;; + (solaris) + # 'pgrep' does not work well in this case + ps -o comm "${pszone}" | grepq -w ".*ntpd" && get_ntpq --header + ;; + (linux|*) + # Try to determine status via /etc/init.d + # This might also be appropriate for AIX, Solaris and others + for _ntp_daemon in ntp ntpd openntpd; do + # Check for a service script + if [ -x /etc/init.d/"${_ntp_daemon}" ]; then + # If the status returns 0, we assume we have a running service + if /etc/init.d/"${_ntp_daemon}" status >/dev/null 2>&1; then + run_cached -s ntp 30 "waitmax 5 get_ntpq" + fi + fi + done + unset -v _ntp_daemon + ;; + esac +} + +section_nvidia() { + # See PR #129 / f8a96be + if inpath nvidia-smi; then + _nvidia_settings="GPUErrors" + write '<<>>' + waitmax 2 nvidia-smi --query-gpu=temperature.gpu --format=csv,nounits,noheader | sed "s/^/GPUCoreTemp: /" + fi + + if inpath nvidia-settings && [ -S /tmp/.X11-unix/X0 ]; then + write '<<>>' + for _var in ${_nvidia_settings:-GPUErrors GPUCoreTemp}; do + DISPLAY=:0 waitmax 2 nvidia-settings -t -q "${_var}" | sed "s/^/${_var}: /" + done + fi + unset -v _var _nvidia_settings +} + +section_omd() { + # Check status of OMD sites and Check_MK Notification spooler + if inpath omd; then + write '<<>>' + # write '{"cmk/check_mk_server": "yes"}' + write -j "cmk/check_mk_server" "yes" + + run_cached omd_status 60 "omd status --bare || true" + write '<<>>' + get_epoch + for _statefile in /omd/sites/*/var/log/mknotifyd.state; do + if [ -e "${_statefile}" ]; then + _site=${_statefile%/var/log*} + _site=${_site#/omd/sites/} + write "[${_site}]" + grep -v '^#' <"${_statefile}" + fi + unset -v _site + done + + write '<<>>' + for _statsfile in /omd/sites/*/var/log/apache/stats; do + if [ -e "${_statsfile}" ]; then + _site=${_statsfile%/var/log*} + _site=${_site#/omd/sites/} + write "[${_site}]" + cat "${_statsfile}" + : >"${_statsfile}" + # prevent next section to fail caused by a missing newline at the end of the statsfile + write + fi + unset -v _site + done + fi + + write '<<>>' + write '[versions]' + write 'version;number;edition;demo' + for _versiondir in /omd/versions/*; do + _omd_version=${_versiondir#/omd/versions/} + + # filter out special directory 'default' + [ "${_omd_version}" = "default" ] && continue + + _omd_number="${_omd_version}" + _omd_demo="0" + case "${_omd_version}" in + (*.demo) + _omd_number=${_omd_version%.demo} + _omd_demo="1" + ;; + esac + _omd_edition=${_omd_number##*.} + _omd_number=${_omd_number%.*} + write "${_omd_version};${_omd_number};${_omd_edition};${_omd_demo}" + done + write '[sites]' + write 'site;used_version;autostart' + for _sitedir in /omd/sites/*; do + _site=${_sitedir#/omd/sites/} + _used_version=$(readlink "${_sitedir}"/version) + _used_version=${_used_version##*/} + _autostart="0" + if grepq "CONFIG_AUTOSTART[[:blank:]]*=[[:blank:]]*'on'" "${_sitedir}"/etc/omd/site.conf; then + _autostart="1" + fi + write "${_site};${_used_version};${_autostart}" + done + + unset -v _autostart _site _sitedir _statefile _statsfile _used_version _versiondir + unset -v _omd_demo _omd_edition _omd_number _omd_site _omd_version +} + +section_omd_core() ( + # Get stats about OMD monitoring cores running on this machine. + # Since cd is a shell builtin the check does not affect the performance + # on non-OMD machines. + if cd /omd/sites; then + write '<<>>' + for _site in *; do + if [ -S "/omd/sites/${_site}/tmp/run/live" ]; then + write "[${_site}]" + # shellcheck disable=SC2039 + write -e "GET status" | + waitmax 3 "/omd/sites/${_site}/bin/unixcat" "/omd/sites/${_site}/tmp/run/live" + fi + done + + write '<<>>' + for _site in *; do + write "[${_site}]" + for _pem_path in "/omd/sites/${_site}/etc/ssl/ca.pem" "/omd/sites/${_site}/etc/ssl/sites/${_site}.pem"; do + if [ -f "${_pem_path}" ]; then + _cert_date=$( + openssl x509 -enddate -noout -in "${_pem_path}" | + sed -e "s/^notAfter=//" -e "s/GMT//" | + awk '{printf("%s %02d %d %s\n", $1,$2,$4,$3)}' + ) + # TO-DO: Confirm that this works + write "${_pem_path}|$(calculate_cert_epoch "${_cert_date}")" + fi + done + done + + write '<<>>' + for _site in *; do + if [ -S "/omd/sites/${_site}/tmp/run/mkeventd/status" ]; then + write "[\"${_site}\"]" + # shellcheck disable=SC2039 + write -e "GET status\\nOutputFormat: json" | + waitmax 3 "/omd/sites/${_site}/bin/unixcat" "/omd/sites/${_site}/tmp/run/mkeventd/status" + fi + done + fi + unset -v _site _pem_path _cert_date +) + +section_openvpn() { + # OpenVPN Clients. These are our two known log locations. + # The 'sed' invocation may need to be tested on non-linux + if [ -e /etc/openvpn/openvpn-status.log ]; then + _openvpn_logfile='/etc/openvpn/openvpn-status.log' + elif [ -e /var/log/openvpn/openvpn-status.log ]; then + _openvpn_logfile='/var/log/openvpn/openvpn-status.log' + else + return 0 + fi + + write '<<>>' + sed -n -e '/CLIENT LIST/,/ROUTING TABLE/p' < "${_openvpn_logfile}" | + sed -e 1,3d -e '$d' + + unset -v _openvpn_logfile +} + +# TO-DO: Merge with section_local and deduplicate i.e. DRY approach +section_plugins() { + # Plugins + ( + if cd "${MK_PLUGINSDIR}" 2>/dev/null; then + for _skript in ./*; do + if is_valid_plugin "${_skript}"; then + ./"${_skript}" + fi + done + # Call some plugins only every Xth second + for _skript in [1-9]*/*; do + if is_valid_plugin "${_skript}"; then + _skript_cache=$(write "${_skript}" | sed 's/\//\\/') + run_cached "plugins_${_skript_cache}" "${_skript%/*}" "${_skript}" + fi + done + unset -v _skript _skript_cache + fi + ) +} + +section_proxmox() { + # Proxmox Cluster + if inpath pvecm; then + write "<<>>" + pvecm status + write "<<>>" + pvecm nodes + fi +} + +section_ps() { + case "${MK_OSSTR}" in + (aix) + write '<<>>' + ps -ef -F user,vszsize,rssize,pcpu,etime,pid,args | + sed -e 1d -e 's/ *\([^ ]*\) *\([^ ]*\) *\([^ ]*\) *\([^ ]*\) *\([^ ]*\) *\([^ ]*\) */(\1,\2,\3,\4\/\5,\6) /' + ;; + (freebsd) + # processes including username, without kernel processes + write '<<>>' + COLUMNS=10000 + if [ "$is_jailed" = "0" ]; then + ps ax -o state,user,vsz,rss,pcpu,command | + sed -e 1d -e '/\([^ ]*J\) */d' -e 's/ *\([^ ]*\) *\([^ ]*\) *\([^ ]*\) *\([^ ]*\) *\([^ ]*\) */(\2,\3,\4,\5) /' + else + ps ax -o user,vsz,rss,pcpu,command | + sed -e 1d -e 's/ *\([^ ]*\) *\([^ ]*\) *\([^ ]*\) *\([^ ]*\) */(\1,\2,\3,\4) /' + fi + ;; + (hpux) + # Process table: HP-UX does not provide a resident size of processes. + # We send a 0 here for RSZ. + write '<<>>' + UNIX95=yes ps -ef -o user,vsz,pcpu,args | + sed -e 1d -e 's/ *\([^ ]*\) *\([^ ]*\) *\([^ ]*\) */(\1,\2,0,\3) /' + ;; + (linux) + if inpath ps; then + # processes including username, without kernel processes + write '<<>>' + CGROUP="" + if [ -e /sys/fs/cgroup ]; then + CGROUP="cgroup:512," + fi + # shellcheck disable=SC2039 + write "[header] $(ps ax -o "$CGROUP"user:32,vsz,rss,cputime,etime,pid,command --columns 10000)" + fi + ;; + (mac|netbsd|openbsd) + write '<<>>' + COLUMNS=10000 + ps ax -o user,vsz,rss,pcpu,command | + sed -e 1d -e 's/ *\([^ ]*\) *\([^ ]*\) *\([^ ]*\) *\([^ ]*\) */(\1,\2,\3,\4) /' + ;; + (solaris) + write '<<>>' + # The default solaris ps command strips the command lines of the processes. But for good process + # matching on the server we really need to whole command line. On linux there are arguments to + # make ps output the whole command line, but on solaris this seems to be missing. We use the ucb + # ps command to get the full command line instead. What a hack. + if [ -x /usr/ucb/ps ]; then + UCB_PS=$(/usr/ucb/ps -agwwwx) + ps -o user,vsz,rss,pcpu,etime,pid,args "${pszone}" | + sed -e 1d -e 's/ *\([^ ]*\) *\([^ ]*\) *\([^ ]*\) *\([^ ]*\) *\([^ ]*\) *\([^ ]*\) */(\1,\2,\3,\4\/\5,\6) /' | + while read -r LINE; do + STATS=${LINE%) *} + PID=${STATS##*,} + + # Directly use ps output when line is too slow to be stripped + if [ ${#LINE} -lt 100 ]; then + write "$LINE" + continue + fi + + CMD=$(write "$UCB_PS" | grep "^[ ]*$PID " | head -n1 | \ + awk '{ s = ""; for (i = 5; i <= NF; i++) s = s $i " "; print s }') + # Only use the ucb ps line when it's not empty (process might already been gone) + if var_is_unset "$CMD"; then + write "$LINE" + else + write "${STATS}) ${CMD}" + fi + done + else + ps -o user,vsz,rss,pcpu,etime,pidargs "${pszone}" | + sed -e 1d -e 's/ *\([^ ]*\) *\([^ ]*\) *\([^ ]*\) *\([^ ]*\) *\([^ ]*\) *\([^ ]*\) */(\1,\2,\3,\4\/\5,\6) /' + fi + ;; + esac +} + +section_soft_raid() { + case "${MK_OSSTR}" in + (freebsd) + # Soft-RAID + write '<<>>' + gmirror status | grep -v ^Name + ;; + (linux) + # RAID status of Linux software RAID + write '<<>>' + cat /proc/mdstat + ;; + esac +} + +# Start new liveupdate process in background on each agent execution. +# Starting a new live update process will terminate the old one +# automatically after a maximum of 1 second. +section_realtime_checks() { + # Validate that we're configured and equipped, otherwise return + if [ -r "${MK_CONFDIR}/real_time_checks.cfg" ]; then + if var_is_unset "${MK_RTC_HOST}"; then + write "ERROR: \${MK_RTC_HOST} not specified. Not starting Real-Time Checks." >&2 + return + elif ! inpath openssl; then + write "ERROR: openssl command is missing. Not starting Real-Time Checks." >&2 + return + fi + else + return + fi + + run_real_time_checks >/dev/null & +} + +section_runas() { + # MK's runas Executor + # runas.cfg syntax is: [Script type] [User context] [File / Directory] + # Script type can be 'mrpe', 'local' or 'plugin' + # User context defines the target user. Use '-' to skip + if [ -r "${MK_CONFDIR}/runas.cfg" ]; then + + # For now, we only define this function for this section. + runas_user() { + _suUser="${1:?No user supplied}" + shift + _suCmd="${*}" + case "${_suUser}" in + ('-'|'_'|null ) "${_suCmd}" ;; + (*) su "${_suUser}" -s "${SHELL}" -c "${_suCmd}" ;; + esac + unset _suUser _suCmd + } + + # Filter out lines startng with unwanted characters, then read what's left + grep -Ev '^[[:space:]]*($|#)' "${MK_CONFDIR}/runas.cfg" | while read -r type user include; do + case "${type}" in + (mrpe) + grep -Ev '^[[:space:]]*($|#)' "$include" | while read -r descr cmdline; do + # Detect if cmdline starts with '(' i.e. async mode + case "${cmdline}" in + (\(*) + # If we do start with '(', then split 'params' out of 'cmdline' + # We strip the brackets from 'params' and rewrite 'cmdline' without the params + params=$(write "${cmdline% *}" | tr -d '()') + cmdline="${cmdline##* }" + # split multiple parameter assignments + for par in $(write "${params}" | tr ":" "\\n"); do + # split each assignment + key="${par%=*}" + value="${par#*=}" + case "${key}" in + (interval) interval="${value}" ;; + (appendage) args="-ma" ;; + esac + done + case "${user}" in + ('-') + run_cached "${args:--m}" "${descr}" "${interval:-}" "${cmdline}" + ;; + (*) + run_cached "${args:--m}" "${descr}" "${interval:-}" runas_user "${user}" "${cmdline}" + ;; + esac + ;; + (*) + case "${user}" in + ('-') + run_mrpe "${descr}" "${cmdline}" + ;; + (*) + run_mrpe "${descr}" runas_user "${user}" "${cmdline}" + ;; + esac + ;; + esac + done + ;; + (local|plugin) + [ "$type" = "local" ] && write "<<>>" + find "$include" -executable -type f | + while read -r filename; do + runas_user "${user}" "${filename}" + done + ;; + esac + done + fi +} + +section_services() { + case "${MK_OSSTR}" in + (linux) + if inpath systemctl; then + write '<<>>' + write "[list-unit-files]" + systemctl list-unit-files --no-pager + write "[all]" + systemctl --all --no-pager | sed '/^$/q' + fi + + # Next we check for 'chkconfig' + if inpath chkconfig; then + write '<<>>' + chkconfig --list 2>&1 + fi + + # Some debian hosts have sysv-rc-conf + if inpath sysv-rc-conf; then + write '<<>>' + sysv-rc-conf + fi + ;; + (solaris) + # Getting Information About Services Running on Solaris + # We can get a list of all service instances, including disabled + # or incomplete ones by 'svcs -a' + if inpath svcs; then + write '<<>>' + svcs -a + fi + ;; + esac +} + +section_solaris() { + if inpath prtdiag; then + # prtdiag does not work in local zones + if [ "${zonename}" = "global" ]; then + run_cached solaris_prtdiag_status 300 '/usr/sbin/prtdiag 1>/dev/null 2>&1; write $?' + fi + fi + + # Displaying Information About Faults or Defects + # If there are no faults the output of this command will be empty. + if inpath fmadm; then + write '<<>>' + fmadm faulty + fi +} + +section_spooldir() { + # Agent output snippets created by cronjobs, etc. + # See: Werk 0016 + ( + # Output every file in this directory. If the file is prefixed with a number, + # then that number is the maximum age of the file in seconds. + # If the file is older than that, it is ignored. + if cd "${MK_SPOOLDIR}" 2>/dev/null; then + _now=$(get_epoch) + + for _file in *; do + [ "${_file}" = "*" ] && break + + # Split any leading digits from a filename and assign to 'maxage' + # This should ignore further digits e.g. '600file20' will output '600' + _maxage="$(write "${_file}" | sed 's/^[^0-9]*//;s/[^0-9].*$//')" + + # If 'maxage' is set, then we grab the file's mtime, subtract it from + # the epoch time, and compare that to 'maxage' + if [ "${_maxage}" ]; then + _mtime=$(get_file_mtime "${_file}") + if [ $((_now - _mtime)) -gt "${_maxage}" ]; then + continue + fi + fi + + # Output the file + cat "${_file}" + done + unset -v _now _file _maxage _mtime + fi + ) +} + +section_statgrab() { + if inpath statgrab; then + case "${MK_OSSTR}" in + (freebsd) + # To install: pkg install libstatgrab + statgrab_vars="const. disk. general. page. proc. user." + statgrab_vars_mem="mem. swap." + statgrab_sections="proc disk page" + + statgrab "$statgrab_vars" | grep -v md 1> /tmp/statgrab.$$ + statgrab "$statgrab_vars_mem" 1>>/tmp/statgrab.$$ + + for s in $statgrab_sections; do + write "<<>>" + grep "^${s}\\." /tmp/statgrab.$$ | cut -d. -f2-99 | sed 's/ *= */ /' + done + + write '<<>>' + statgrab net. 2>&1 | cut -d. -f2-99 | sed 's/ *= */ /' + + write '<<>>' + grep -E "^(swap|mem)\\." /tmp/statgrab.$$ | sed 's/ *= */ /' + + [ -f /tmp/statgrab.$$ ] && rm -f /tmp/statgrab.$$ + ;; + (solaris) + # source: http://www.i-scream.org/libstatgrab/ + # binary: http://www.opencsw.org/ + statgrab_vars="const. cpu. disk. general. mem. page. swap. user." + statgrab_sections="cpu disk page" + + # Collect net stats in the global zone and in local zones if dlstat is present. + if [ "$zonename" = "global" ] || inpath dlstat >/dev/null 2>&1; then + statgrab_vars="$statgrab_vars net." + statgrab_sections="$statgrab_sections net" + fi + + statgrab "$statgrab_vars" | grep -v md 1> /tmp/statgrab.$$ + for s in $statgrab_sections; do + write "<<>>" + grep "^$s\\." /tmp/statgrab.$$ | cut -d. -f2-99 | sed 's/ *= */ /' + done + + # <<>> info is preferred over <<>> + # since solaris_mem is under suspicion to be buggy. + write '<<>>' + grep -E "^(swap|mem)\\." /tmp/statgrab.$$ | sed 's/ *= */ /' + + [ -f /tmp/statgrab.$$ ] && rm -f /tmp/statgrab.$$ + + ;; + esac + fi +} + +section_tcp_stats() { + # Helper function, we'll keep it tucked away here for now + filter_netstats() { + awk ' /^tcp/ { c[$6]++; } END { for (x in c) { print x, c[x]; } }' + } + write '<<>>' + case "${MK_OSSTR}" in + (aix|mac) _netstat_opts="-ntfinet" ;; + (freebsd) _netstat_opts="-na" ;; + (hpux) _netstat_opts="-f inet -n" ;; + (linux) + if inpath waitmax; then + _tcp_stats=$(waitmax 5 cat /proc/net/tcp /proc/net/tcp6 2>/dev/null | awk ' /:/ { c[$4]++; } END { for (x in c) { print x, c[x]; } }') + # We simply ignore this warning until we can come up with a cleaner way to do this + # shellcheck disable=SC2181 + if [ $? = 0 ]; then + write "${_tcp_stats}" + elif inpath ss; then + ss -ant | + grep -v ^State | + awk ' /:/ { c[$1]++; } END { for (x in c) { print x, c[x]; } }' | + sed -e 's/^ESTAB/01/g;s/^SYN-SENT/02/g;s/^SYN-RECV/03/g;s/^FIN-WAIT-1/04/g;s/^FIN-WAIT-2/05/g;s/^TIME-WAIT/06/g;s/^CLOSED/07/g;s/^CLOSE-WAIT/08/g;s/^LAST-ACK/09/g;s/^LISTEN/0A/g;s/^CLOSING/0B/g;' + else + _netstat_opts="-na" + fi + unset -v _tcp_stats + else + _netstat_opts="-na" + fi + ;; + (solaris) + netstat -n -a -f inet -P tcp | + tail +5 | + nawk '{ c[$7]++; } END { for (x in c) { print x, c[x]; } }' + return + ;; + esac + netstat "${_netstat_opts}" | filter_netstats + unset -v _netstat_opts +} + +section_thermal() { + # Gather thermal information provided e.g. by acpi + # At the moment only supporting thermal sensors + if { + var_is_unset "${MK_IS_DOCKERIZED}" && + var_is_unset "${MK_IS_LXC_CONTAINER}" && + ls /sys/class/thermal/thermal_zone* >/dev/null 2>&1 + }; then + write '<<>>' + for _sysPath in /sys/class/thermal/thermal_zone*; do + _line="${_sysPath##*/}" + if [ ! -e "${_sysPath}/mode" ]; then + _line="${_line}|-" + else + _line="${_line}|$(cat "${_sysPath}"/mode)" + fi + _line="${_line}|$(cat "${_sysPath}"/type "${_sysPath}"/temp 2>/dev/null | tr \\n "|")" + for _temp in "${_sysPath}"/trip_point_*_temp; do + _line="${_line}$(tr <"${_temp}" \\n "|")" + done + for _type in "${_sysPath}"/trip_point_*_type; do + _line="${_line}$(tr <"${_type}" \\n "|")" + done + write "${_line%?}" + done + unset _sysPath _line _temp _type + fi +} + +# Function to pull timesync information via timedatectl (if possible) +section_timesyncd() { + inpath timedatectl || return 1 + timedatectl timesync-status >/dev/null 2>&1 || return 1 + write "<<>>" + timedatectl timesync-status + get_file_mtime /var/lib/systemd/timesync/clock | awk '{print "[[["$1"]]]"}' + return 0 +} + +# Libelle Business Shadow +section_libelle() { + if inpath trd; then + write "<<>>" + trd -s + fi +} + +section_uptime() { + write '<<>>' + case "${MK_OSSTR}" in + (aix) + # uptime formats + # 12:55pm up 105 days, 21 hrs, 2 users, load average: 0.26, 0.26, 0.26 --> 9147600 + # 1:41pm up 105 days, 21:46, 2 users, load average: 0.28, 0.28, 0.27 --> 9150360 + # 05:26PM up 1:16, 1 user, load average: 0.33, 0.21, 0.20 --> 4560 + # 06:13PM up 2:03, 1 user, load average: 1.16, 1.07, 0.91 --> 7380 + # 08:43AM up 29 mins, 1 user, load average: 0.09, 0.18, 0.21 --> 1740 + # 08:47AM up 66 days, 18:34, 1 user, load average: 2.25, 2.43, 2.61 --> 5769240 + # 08:45AM up 76 days, 34 mins, 1 user, load average: 2.25, 2.43, 2.61 --> 5769240 + + _uptime=$(uptime | sed -e 's/^.*up//g' -e 's/[0-9]* user.*//g') + case ${_uptime} in + ( *day* ) _up_days=$(write "${_uptime}" | sed -e 's/days\{0,1\},.*//g') ;; + ( * ) _up_days="0" ;; + esac + + case ${_uptime} in + ( *:* ) + _up_hours=$(write "${_uptime}" | sed -e 's/.*days\{0,1\},//g' -e 's/:.*//g') + _up_mins=$(write "${_uptime}" | sed -e 's/.*days\{0,1\},//g' -e 's/.*://g' -e 's/,.*//g') + ;; + ( *hr* ) + _up_hours=$(write "${_uptime}" | sed -e 's/hrs\{0,1\},.*//g' -e 's/.*,//g') + _up_mins=0 + ;; + ( *min* ) + _up_hours=0 + _up_mins=$(write "${_uptime}" | sed -e 's/mins\{0,1\},.*//g' -e 's/.*hrs\{0,1\},//g' -e 's/.*days\{0,1\},//g') + ;; + ( * ) + _up_hours="0" + _up_mins=0 + ;; + esac + + write $(((_up_days*86400)+(_up_hours*3600)+(_up_mins*60))) + unset -v _uptime _up_hours _up_mins + ;; + (freebsd) + # Calculate the uptime in seconds since epoch compatible to /proc/uptime in linux + _up_seconds=$(( $(get_epoch) - $(sysctl -n kern.boottime | cut -f1 -d\, | awk '{print $4}') )) + # pgrep is not appropriate (or even available?) here + # shellcheck disable=SC2009 + _idle_seconds=$(ps axw | grep "[i]dle" | awk '/idle/{print $4}' | cut -f1 -d':' ) + write "${_up_seconds} ${_idle_seconds}" + unset -v _up_seconds _idle_seconds + ;; + (linux) + if var_is_unset "${MK_IS_DOCKERIZED}"; then + cat /proc/uptime + else + write "$(($(get_epoch) - $(stat -c %Z /dev/pts)))" + fi + ;; + (mac|netbsd|openbsd) + write "$(get_epoch) - $(sysctl -n kern.boottime | cut -d' ' -f 4,7 | tr ',' '.' | tr -d ' ')" | bc + ;; + (solaris) + # Solaris doesn't always give a consistent output on uptime, thus include side information + # Tested in VM for solaris 10/11 + _ctime=$(nawk 'BEGIN{print srand()}') + _btime=$(kstat '-p' 'unix:::boot_time' 2>&1|grep 'boot_time'|awk '{print $2}') + write $((_ctime - _btime)); + write '[uptime_solaris_start]' + uname -a + zonename + uptime + kstat -p unix:0:system_misc:snaptime + write '[uptime_solaris_end]' + unset -v _ctime _btime + ;; + esac + + # 'who -b' is a mostly portable way to report the boot time + # TO-DO: is this information header the correct format? + if who -b > /dev/null 2>&1; then + write "[who_b_boot_time]" + who -b + fi +} + +# HTTP Accelerator Statistics +section_http_accelerator() { + if inpath varnishstat; then + write "<<>>" + varnishstat -1 + fi +} + +section_vbox_guest() { + # VirtualBox Guests. Section must always been output. Otherwise the + # check would not be executed in case no guest additions are installed. + # And that is something the check wants to detect + write '<<>>' + if inpath VBoxControl; then + if lsmod | grepq vboxguest; then + VBoxControl -nologo guestproperty enumerate | cut -d, -f1,2 + fi + else + write "ERROR" + fi +} + +section_veritas_cluster() { + # Veritas Cluster Server + # Software is always installed in /opt/VRTSvcs. + # Secure mode must be off to allow root to execute commands + if [ -x /opt/VRTSvcs/bin/haclus ]; then + write "<<>>" + vcshost=$(hostname | cut -d. -f1) + waitmax -s 9 2 /opt/VRTSvcs/bin/haclus -display -localclus | grep -e ClusterName -e ClusState + waitmax -s 9 2 /opt/VRTSvcs/bin/hasys -display -attribute SysState + waitmax -s 9 2 /opt/VRTSvcs/bin/hagrp -display -sys "$vcshost" -attribute State -localclus + waitmax -s 9 2 /opt/VRTSvcs/bin/hares -display -sys "$vcshost" -attribute State -localclus + waitmax -s 9 2 /opt/VRTSvcs/bin/hagrp -display -attribute TFrozen -attribute Frozen + fi +} + +# TO-DO: Standardise headers, identify what these commands are for, merge with section_kernel() ? +section_vmstat() { + case "${MK_OSSTR}" in + (aix) + write '<<>>' + vmstat | tail -n1 + ;; + (hpux) + # Several machine performance counters + write '<<>>' + vmstat -s + ;; + esac +} + +# TO-DO: Can this behaviour be simplified or made portable? +# Filesystem usage for ZFS +section_zfs() { + case "${MK_OSSTR}" in + (freebsd) + if inpath zfs; then + write '<<>>' + zfs get -t filesystem,volume -Hp name,quota,used,avail,mountpoint,type || + zfs get -Hp name,quota,used,avail,mountpoint,type + write '[df]' + df -kP -t zfs | sed 1d + # arc stats for zfs_arc_cache + write '<<>>' + sysctl -q kstat.zfs.misc.arcstats | + sed -e 's/kstat.zfs.misc.arcstats.//g' -e 's/: / = /g' + fi + ;; + (linux|solaris) + if inpath zfs; then + write '<<>>' + zfs get -t filesystem,volume -Hp name,quota,used,avail,mountpoint,type || + zfs get -Hp name,referenced,avail,mountpoint,type | sed 's/referenced/used/g' + write '<<>>' + write '[df]' + df -PTlk -t zfs | sed 1d + fi + ;; + esac + + if [ "${MK_OSSTR}" = "solaris" ]; then + # ZFS arc cache + # newer Solaris (>=11.3) do not provide hits and misses via mdb -k + write '<<>>' + if inpath kstat; then + kstat -p zfs:0:arcstats | + sed -e 's/.*arcstats://g' | + awk '{printf "%s = %s\n", $1, $2;}' + elif inpath; then + write '::arc' | mdb -k + fi + fi +} + +section_zpool() { + case "${MK_OSSTR}" in + (freebsd) + # check zpool status + if [ -x /sbin/zpool ]; then + write "<<>>" + /sbin/zpool status -x | grep -v "errors: No known data errors" + fi + ;; + (linux) + if inpath zpool; then + write "<<>>" + zpool status -x + write "<<>>" + zpool list + fi + ;; + (solaris) + if [ -x /sbin/zpool ]; then + run_cached zpool_status 120 "/sbin/zpool status -x" + write '<<>>' + zpool list + fi + ;; + esac +} + +######################################################################################################################## +# Output begins here +main() { + section_head + var_is_unset "${MK_SKIP_CPU}" && section_cpu + var_is_unset "${MK_SKIP_MEM}" && section_mem + var_is_unset "${MK_SKIP_DF}" && section_df + var_is_unset "${MK_SKIP_ZFS}" && section_zfs + var_is_unset "${MK_SKIP_NFS_MOUNTS}" && section_nfs + var_is_unset "${MK_SKIP_MOUNTS}" && section_mounts + var_is_unset "${MK_SKIP_MULTIPATHING}" && section_multipathing + var_is_unset "${MK_SKIP_SOFT_RAID}" && section_soft_raid + var_is_unset "${MK_SKIP_DM_RAID}" && section_dm_raid + var_is_unset "${MK_SKIP_CFGGEN}" && section_cfggen + var_is_unset "${MK_SKIP_MEGARAID}" && section_megaraid + var_is_unset "${MK_SKIP_THREE_WARE_RAID}" && section_3ware_raid + var_is_unset "${MK_SKIP_ARECA}" && section_areca_raid + var_is_unset "${MK_SKIP_ZPOOL}" && section_zpool + var_is_unset "${MK_SKIP_DISKSTAT}" && section_diskstat + var_is_unset "${MK_SKIP_NET}" && section_net + var_is_unset "${MK_SKIP_NET_CTR}" && section_net_ctr + var_is_unset "${MK_SKIP_NET_BONDING}" && section_net_bonding + var_is_unset "${MK_SKIP_NET_VSWITCH_BONDING}" && section_net_openvswitch_bonding + var_is_unset "${MK_SKIP_TCP_STATS}" && section_tcp_stats + if var_is_unset "${MK_SKIP_TIMESYNCHRONISATION}"; then + section_timesyncd || section_ntp + fi + var_is_unset "${MK_SKIP_CHRONY}" && section_chrony + var_is_unset "${MK_SKIP_MAILQUEUE}" && section_mailqueue + var_is_unset "${MK_SKIP_UPTIME}" && section_uptime + var_is_unset "${MK_SKIP_KERNEL}" && section_kernel + var_is_unset "${MK_SKIP_SERVICES}" && section_services + var_is_unset "${MK_SKIP_PS}" && section_ps + var_is_unset "${MK_SKIP_STATGRAB}" && section_statgrab + var_is_unset "${MK_SKIP_VMSTAT}" && section_vmstat + var_is_unset "${MK_SKIP_IPMITOOL}" && section_ipmitool + var_is_unset "${MK_SKIP_IPMISENSORS}" && section_ipmisensors + var_is_unset "${MK_SKIP_THERMAL}" && section_thermal + var_is_unset "${MK_SKIP_OPENVPN}" && section_openvpn + var_is_unset "${MK_SKIP_OMD}" && section_omd + var_is_unset "${MK_SKIP_OMD_CORES}" && section_omd_core + section_mkbackup + var_is_unset "${MK_SKIP_JOBS}" && section_jobs + var_is_unset "${MK_SKIP_VBOX_GUEST}" && section_vbox_guest + var_is_unset "${MK_SKIP_NVIDIA}" && section_nvidia + var_is_unset "${MK_SKIP_HEARTBEAT}" && section_heartbeat + var_is_unset "${MK_SKIP_VERITAS}" && section_veritas_cluster + var_is_unset "${MK_SKIP_PROXMOX}" && section_proxmox + var_is_unset "${MK_SKIP_DRBD}" && section_drbd + var_is_unset "${MK_SKIP_LIBELLE}" && section_libelle + var_is_unset "${MK_SKIP_HTTP_ACCELERATOR}" && section_http_accelerator + var_is_unset "${MK_SKIP_HAPROXY}" && section_haproxy + section_realtime_checks + var_is_unset "${MK_SKIP_FILEINFO}" && section_fileinfo + section_runas + section_mrpe + section_local + section_plugins + var_is_unset "${MK_SKIP_SPOOLDIR}" && section_spooldir + # Now any OS-specific catch-all functions + # I wonder if this could be called universally/portably with section_${MK_OSSTR} + case "${MK_OSSTR}" in + (aix) section_aix ;; + (hpux) section_hpux ;; + (mac) section_mac ;; + (solaris) section_solaris ;; + esac +} + +# Determine whether we need to send our output via encryption or not +case "${ENCRYPTION}" in + (yY*) main | protect_output ;; + (*) main ;; +esac diff --git a/agents/mk-job b/agents/mk-job index 94b0f461c89..af445ca2440 100755 --- a/agents/mk-job +++ b/agents/mk-job @@ -3,57 +3,112 @@ # This file is part of Checkmk (https://checkmk.com). It is subject to the terms and # conditions defined in the file COPYING, which is part of this source code package. -export MK_VARDIR=/var/lib/check_mk_agent - +# Exempt from indentation rules as it's a heredoc help() { - echo "Usage: mk-job IDENT PROGRAM [ARGS...]" - echo "" - echo "Execute PROGRAM as subprocess while measuring performance information" - echo "about the running process and writing it to an output file. This file" - echo "can be monitored using Check_MK. The Check_MK Agent will forward the" - echo "information of all job files to the monitoring server." - echo "" - echo "This file is being distributed with the Check_MK Agent." +cat << EOF >&2 +Usage: mk-job ident PROGRAM [ARGS...] + +Execute PROGRAM as subprocess while measuring performance information +about the running process and writing it to an output file. This file +can be monitored using checkmk. The checkmk Agent will forward the +information of all job files to the monitoring server. + +This file is being distributed with the checkmk Agent. +EOF + +exit "${1:-0}" } -if [ $# -lt 2 ]; then - help >&2 - exit 1 -fi +# If args is less than 2, or 0, call `help()` and exit 1 +[ "${#}" -lt 2 ] && help 1 +[ "${#}" -eq 0 ] && help 1 + +# If help is requested, deliver it +[ "${1}" = "-h" ] && help 0 +[ "${1}" = "--help" ] && help 0 -MYSELF=$(id -nu) -OUTPUT_PATH=$MK_VARDIR/job/$MYSELF -IDENT=$1 -RUNNING_FILE="$OUTPUT_PATH/$IDENT.$$running" +# Source the agent, as it has a bunch of functions and variables for us to use +# shellcheck disable=SC1090 +. "$(command -v check_mk_agent)" || exit 1 -shift +# Set up the variables that are specific to this script +output_path="${MK_VARDIR}/job/${USER}" +ident="${1:?}" +running_file="${output_path}/${ident}.$$running" -if [ ! -d "$OUTPUT_PATH" ]; then - if [ "$MYSELF" = root ] ; then - mkdir -p "$OUTPUT_PATH" +# Shift to the rest of the positional parameters +shift 1 + +if [ ! -d "${output_path}" ]; then + if [ "${USER}" = root ]; then + mkdir -p "${output_path}" else - echo "ERROR: Missing output directory $OUTPUT_PATH for non-root user '$MYSELF'." >&2 + echo "ERROR: Missing output directory ${output_path} for non-root user '${USER}'." >&2 exit 1 fi fi -if ! type $1 >/dev/null 2>&1; then - echo -e "ERROR: Cannot run $1. Command not found.\n" >&2 - help >&2 - exit 1 +if ! inpath "${1}"; then + echo "ERROR: Cannot run '${1}'. Command not found." >&2 + help 1 fi -date +"start_time %s" > "$RUNNING_FILE" 2>/dev/null +# Structure GNU's time format into an NDJSON format +gnu_time_fmt="{\"$(get_epoch)\": {\"exit_code\": %x, \"real_time\": \"%E\", \"user_time\": \"%U\"," +gnu_time_fmt="${gnu_time_fmt} \"system_time\": \"%S\", \"reads\": %I, \"writes\": %O," +gnu_time_fmt="${gnu_time_fmt} \"max_res_kbytes\": %M, \"avg_mem_kbytes\": %K," +gnu_time_fmt="${gnu_time_fmt} \"invol_context_switches\": %c, \"vol_context_switches\": %w}" + +# Structure our dummy data for older systems +dummy_data="\"reads\": 0, \"writes\": 0, \"max_res_kbytes\": 0, \"avg_mem_kbytes\": 0," +dummy_data="${dummy_data} \"invol_context_switches\": 0, \"vol_context_switches\": 0}}" -if [ ! -w "$RUNNING_FILE" ] ; then - # Looks like we are lacking the permissions to create this file.. +if [ ! -w "${running_file}" ] ; then + # Looks like we are lacking the permissions to create this file... # In this scenario no mk-job status file is created. We simply execute the command exec "$@" fi +case "${MK_OSSTR}" in + (aix) + # TO-DO: Test if the AIX approach a) works and b) also works for Solaris, if so, merge them + # We want word splitting here so that our positional params are assigned properly + # shellcheck disable=SC2046 + set -- $( (/usr/bin/time -p "${@}" 2>&1; echo "${?}") | tail -n 4 | paste -sd ' ' -) + + # Start building our output format + classic_fmt="{\"$(get_epoch)\": {\"exit_code\": ${7}, \"real_time\": \"${2}\"," + classic_fmt="${classic_fmt} \"user_time\": \"${4}\", \"system_time\": \"${6}\"," + classic_fmt="${classic_fmt} ${dummy_data}" + return_code="${7}" + + echo "${classic_fmt}" >> "${running_file}" + ;; + (linux) + /usr/bin/time -o "${running_file}" --append -f "${gnu_time_fmt}" "${@}" + return_code="${?}" + ;; + (mac) + # For OSX: requires GNU-time installed (`brew install gnu-time`) + inpath gtime || return 1 + /usr/local/bin/gtime -o "${running_file}" --append -f "${gnu_time_fmt}" "${@}" + return_code="${?}" + ;; + (solaris) + # TO-DO: Test if the AIX approach a) works and b) also works for Solaris, if so, merge them + # We want word splitting here so that our positional params are assigned properly + # shellcheck disable=SC2046 + set -- $( (/usr/bin/time -p sh -c "$* 2>/dev/null 1>&2" 2>&1; echo $?) | sed -e 's/,/\./g') + + # Start building our output format + classic_fmt="{\"$(get_epoch)\": {\"exit_code\": ${7}, \"real_time\": \"${2}\"," + classic_fmt="${classic_fmt} \"user_time\": \"${4}\", \"system_time\": \"${6}\"," + classic_fmt="${classic_fmt} ${dummy_data}" + return_code="${7}" + + echo "${classic_fmt}" >> "${running_file}" + ;; +esac -/usr/bin/time -o "$RUNNING_FILE" --append \ - -f "exit_code %x\nreal_time %E\nuser_time %U\nsystem_time %S\nreads %I\nwrites %O\nmax_res_kbytes %M\navg_mem_kbytes %K\ninvol_context_switches %c\nvol_context_switches %w" "$@" -RC=$? -mv "$RUNNING_FILE" "$OUTPUT_PATH/$IDENT" -exit $RC +mv "${running_file}" "${output_path}/${ident}" +exit "${return_code}" diff --git a/agents/mk-job.solaris b/agents/mk-job.solaris deleted file mode 100755 index 9e92113f94d..00000000000 --- a/agents/mk-job.solaris +++ /dev/null @@ -1,57 +0,0 @@ -#!/bin/bash -# Copyright (C) 2019 tribe29 GmbH - License: GNU General Public License v2 -# This file is part of Checkmk (https://checkmk.com). It is subject to the terms and -# conditions defined in the file COPYING, which is part of this source code package. - -export MK_VARDIR=/var/lib/check_mk - -help() { - echo "Usage: mk-job IDENT PROGRAM [ARGS...]" - echo "" - echo "Execute PROGRAM as subprocess while measuring performance information" - echo "about the running process and writing it to an output file. This file" - echo "can be monitored using Check_MK. The Check_MK Agent will forward the" - echo "information of all job files to the monitoring server." - echo "" - echo "This file is being distributed with the Check_MK Agent." -} - -if [ $# -lt 2 ]; then - help >&2 - exit 1 -fi - -MYSELF=`id | awk -F')' '{print $1}' | awk -F'(' '{print $2}'` -OUTPUT_PATH=$MK_VARDIR/job/$MYSELF -IDENT=$1 -RUNNING_FILE="$OUTPUT_PATH/$IDENT.$$running" - -shift - -if [ ! -d "$OUTPUT_PATH" ]; then - if [ "$MYSELF" = root ] ; then - mkdir -p "$OUTPUT_PATH" - else - echo "ERROR: Missing output directory $OUTPUT_PATH for non-root user '$MYSELF'." >&2 - exit 1 - fi -fi - -if ! type $1 >/dev/null 2>&1; then - echo -e "ERROR: Cannot run $1. Command not found.\n" >&2 - help >&2 - exit 1 -fi - - -echo "start_time `perl -e 'print time'`" > "$RUNNING_FILE" - -info=`(/usr/bin/time -p sh -c "$@ 2>/dev/null 1>&2" 2>&1; echo $?) | sed -e 's/,/\./g'` -RC=`echo $info | awk '{print $7}'` - -(echo $info | awk '{print "exit_code "$7"\nreal_time "$2"\nuser_time "$4"\nsystem_time "$6""}') >> "$RUNNING_FILE" -(echo -e "reads 0\nwrites 0\nmax_res_kbytes 0\navg_mem_kbytes 0\ninvol_context_switches 0\nvol_context_switches 0";) >> "$RUNNING_FILE" - -mv "$RUNNING_FILE" "$OUTPUT_PATH/$IDENT" -exit $RC -