lynis/include/functions
hlein b595cc0fb5 Various cleanups (#363)
* Typo fix.

* Style change: always use $(), never ``.

The Lynis code already mostly used $(), but backticks were sprinkled
around.  Converted all of them.

* Lots of minor spelling/typo fixes.

FWIW these were found with:

  find . -type f -print0 | xargs -0 cat | aspell list | sort -u | egrep '^[a-z]+$' | less

And then reviewing the list to pick out things that looked like
misspelled words as opposed to variables, etc., and then manual
inspection of context to determine the intention.
2017-03-06 07:41:21 +00:00

3038 lines
126 KiB
Bash

#!/bin/sh
#################################################################################
#
# Lynis
# ------------------
#
# Copyright 2007-2013, Michael Boelen
# Copyright 2007-2017, CISOfy
#
# Website : https://cisofy.com
# Blog : http://linux-audit.com
# GitHub : https://github.com/CISOfy/lynis
#
# Lynis comes with ABSOLUTELY NO WARRANTY. This is free software, and you are
# welcome to redistribute it under the terms of the GNU General Public License.
# See LICENSE file for usage of this software.
#
#################################################################################
#
# Functions
#
#################################################################################
#
# Function Description
# ----------------------- -------------------------------------------------
# AddHP Add Hardening points to plot a graph later
# AddSetting Addition of setting
# AddSystemGroup Adds a system to a group
# CheckFilePermissions Check file permissions
# CheckItem Test for presence of a string in report file
# CheckUpdates Determine if a new version of Lynis is available
# CleanUp Clean up files before closing program
# CountTests Count number of performed tests
# ContainsString Find the needle (string) in the haystack (another string)
# CreateTempFile Create a temporary file
# Debug Display additional information on the screen (not suited for cronjob)
# DigitsOnly Return only the digits from a string
# DirectoryExists Check if a directory exists on the disk
# DiscoverProfiles Determine available profiles on system
# Display Output text to screen with colors and identation
# DisplayError Show an error on screen
# DisplayManual Output text to screen without any layout
# DisplayToolTip Show a tip for improving usage of the tool
# ExitClean Stop the program (cleanly), with exit code 0
# ExitCustom Stop the program (cleanly), with custom exit code
# ExitFatal Stop the program (cleanly), with exit code 1
# FileExists Check if a file exists on the disk
# FileIsEmpty Check if a file is empty
# FileIsReadable Check if a file is readable or directory accessible
# GetHostID Retrieve an unique ID for this host
# InsertSection Insert a section block
# InsertPluginSection Insert a section block for plugins
# IsContainer Determine if program runs in a container
# IsDebug Check if --debug is used
# IsDeveloperMode Check if --developer is used
# IsDeveloperVersion Check if program is a developer release
# IsNotebook System detection
# IsOwnedByRoot Determine if file or directory is owned by root
# IsRunning Check if a process is running
# IsVerbose Check if --verbose is used
# IsVirtualMachine Check if this system is a virtual machine
# IsWorldExecutable Check if a file is world executable
# IsWorldReadable Check if a file is world readable
# IsWorldWritable Check if a file is world writable
# LogText Log text strings to logfile, prefixed with date/time
# LogTextBreak Insert a separator in log file
# ParseNginx Parse nginx configuration lines
# ParseProfiles Parse all available profiles
# ParseTestValues Parse a set of values
# PortIsListening Check if machine is listening on specified protocol and port
# Progress Show progress on screen
# Register Register a test (for logging and execution)
# RandomString Show a random string
# RemoveColors Reset all colors
# RemovePIDFile Remove PID file
# RemoveTempFiles Remove temporary files
# Report Add string of data to report file
# ReportDetails Store details of tests which include smaller atomic tests in report
# ReportException Add an exception to the report file (for debugging purposes)
# ReportManual Log manual actions to report file
# ReportSuggestion Add a suggestion to report file
# ReportWarning Add a warning and priority to report file
# SafePerms Check if a directory has safe permissions
# SearchItem Search a string in a file
# ShowComplianceFinding Display a particular finding regarding compliance or a security standard
# ShowSymlinkPath Show a path behind a symlink
# SkipAtomicTest Test if a subtest needs to be skipped
# StoreNginxSettings Save parsed nginx settings to file
# TestValue Evaluate a value in a string or key
# ViewCategories Show available category of tests
# ViewGroups Display test groups
# WaitForKeyPress Wait for user to press a key to continue
#
#################################################################################
#
# Under Development:
# ----------------------
# TestCase_Equal Test case for checking if value is equal to something
# TestCase_NotEqual Test case for checking if value is not equal to something
# TestCase_GreaterThan Test case for checking if value is greater than something
# TestCase_GreaterOrEqual Test case for checking if value is greater or equal to something
# TestCase_LessThan Test case for checking if value is less than something
# TestCase_LessOrEqual Test case for checking if value is less or equal to something
#
#################################################################################
################################################################################
# Name : AddHP()
# Description : Add hardening points and count them
#
# Input : $1 = points to add, $2 = maximum points for this item
# Returns : <nothing>
# Usage : AddHP 1 3
################################################################################
AddHP() {
HPADD=$1; HPADDMAX=$2
HPPOINTS=$((HPPOINTS + HPADD))
HPTOTAL=$((HPTOTAL + HPADDMAX))
if [ ${HPADD} -eq ${HPADDMAX} ]; then
LogText "Hardening: assigned maximum number of hardening points for this item (${HPADDMAX}). Currently having ${HPPOINTS} points (out of ${HPTOTAL})"
else
LogText "Hardening: assigned partial number of hardening points (${HPADD} of ${HPADDMAX}). Currently having ${HPPOINTS} points (out of ${HPTOTAL})"
fi
}
################################################################################
# Name : AddSetting()
# Description : Addition of a setting for display with 'lynis show settings'
#
# Input : $1 = setting, $2 = value, $3 description
# Returns : <nothing>
# Usage : AddSetting debug 1 'Debug mode'
################################################################################
AddSetting() {
if [ $# -eq 3 ]; then
SETTING="$1"
VALUE="$2"
DESCRIPTION="$3"
if [ -z "${SETTINGS_FILE}" ]; then
CreateTempFile
SETTINGS_FILE="${TEMP_FILE}"
fi
FIND=$(egrep "^${SETTING};" ${SETTINGS_FILE})
if [ -z "${FIND}" ]; then
echo "${SETTING};${VALUE};${DESCRIPTION};" >> ${SETTINGS_FILE}
else
Debug "Setting '${SETTING}' was already configured, overwriting previous line '${FIND}' in ${SETTINGS_FILE} with value '${VALUE}'"
# Delete line first, then add new value (inline search and replace is messy)
sed -i -e '/^'"${SETTING}"';/d' ${SETTINGS_FILE}
echo "${SETTING};${VALUE};${DESCRIPTION};" >> ${SETTINGS_FILE}
fi
else
echo "Error: incorrect call to AddSetting. Needs 3 arguments."
fi
}
################################################################################
# Name : AddSystemGroup()
# Description : Adds a system to a group, which can be used for categorizing
#
# Input : Group name
# Returns : <nothing>
# Usage : AddSystemGroup "test"
################################################################################
AddSystemGroup() {
Report "system_group[]=$1"
}
################################################################################
# Name : CheckFilePermissions()
# Description : Check file permissions
#
# Input : full path to file or directory
# Returns : PERMS (FILE_NOT_FOUND | OK | BAD)
# Notes : This function might be replaced in future
################################################################################
CheckFilePermissions() {
CHECKFILE=$1
if [ ! -d ${CHECKFILE} -a ! -f ${CHECKFILE} ]; then
PERMS="FILE_NOT_FOUND"
else
# If 'file' is an directory, use -d
if [ -d ${CHECKFILE} ]; then
FILEVALUE=$(ls -d -l ${CHECKFILE} | cut -c 2-10)
PROFILEVALUE=$(grep '^permdir' ${PROFILE} | grep ":${CHECKFILE}:" | cut -d: -f3)
else
FILEVALUE=$(ls -l ${CHECKFILE} | cut -c 2-10)
PROFILEVALUE=$(grep '^permfile' ${PROFILE} | grep ":${CHECKFILE}:" | cut -d: -f3)
fi
if [ "${FILEVALUE}" = "${PROFILEVALUE}" ]; then PERMS="OK"; else PERMS="BAD"; fi
fi
}
################################################################################
# Name : CheckItem()
# Description : Check if a specific item exists in the report
#
# Input : $1 = key, $2 = value
# Returns : ITEM_FOUND
# Usage : CheckItem "key" "value"
################################################################################
CheckItem() {
ITEM_FOUND=0
RETVAL=255
if [ $# -eq 2 ]; then
# Don't search in /dev/null, it's too empty there
if [ ! "${REPORTFILE}" = "/dev/null" ]; then
# Check if we can find the main type (with or without brackets)
LogText "Test: search string $2 in earlier discovered results"
FIND=$(egrep "^$1(\[\])?=" ${REPORTFILE} | egrep "$2")
if [ ! "${FIND}" = "" ]; then
ITEM_FOUND=1
RETVAL=0
LogText "Result: found search string (result: $FIND)"
else
LogText "Result: search string NOT found"
RETVAL=1
fi
else
LogText "Skipping search, as /dev/null is being used"
fi
return ${RETVAL}
else
ReportException ${TEST_NO} "Error in function call to CheckItem"
fi
}
################################################################################
# Name : CheckUpdates()
# Description : Determine if there is an update available
# Returns : <nothing>
# Usage : CheckUpdates
# Use PROGRAM_LV (latest version) and compare it with actual version (PROGRAM_AC)
################################################################################
CheckUpdates() {
PROGRAM_LV="0000000000"; DB_MALWARE_LV="0000000000"; DB_FILEPERMS_LV="0000000000"
if [ ${RUN_UPDATE_CHECK} -eq 1 ]; then
LYNIS_LV_RECORD="lynis-latest-version.cisofy.com."
FIND=$(which dig 2> /dev/null)
if [ ! -z "${FIND}" ]; then
PROGRAM_LV=$(dig +short +time=3 -t txt lynis-latest-version.cisofy.com 2> /dev/null | grep -v "connection timed out" | sed 's/[".]//g' | grep "^[1-9][0-9][0-9]$")
else
FIND=$(which host 2> /dev/null)
if [ ! -z "${FIND}" ]; then
PROGRAM_LV=$(host -t txt -W 3 lynis-latest-version.cisofy.com 2> /dev/null | grep -v "connection timed out" | awk '{ if ($1=="lynis-latest-version.cisofy.com" && $3=="text") { print $4 }}' | sed 's/"//g' | grep "^[1-9][0-9][0-9]$")
if [ "${PROGRAM_LV}" = "" ]; then PROGRAM_LV=0; fi
else
FIND=$(which drill 2> /dev/null)
if [ ! -z "${FIND}" ]; then
PROGRAM_LV=$(drill txt ${LYNIS_LV_RECORD} | awk '{ if ($1=="lynis-latest-version.cisofy.com." && $4=="TXT") { print $5 }}' | tr -d '"' | grep "^[1-9][0-9][0-9]$")
if [ -z "${PROGRAM_LV}" ]; then PROGRAM_LV=0; fi
else
LogText "Result: dig, drill or host not installed, update check skipped"
UPDATE_CHECK_SKIPPED=1
fi
fi
fi
fi
}
################################################################################
# Name : CleanUp()
# Description : Cleanup service
# Returns : <nothing>
################################################################################
CleanUp() {
echo ""; echo "Interrupt detected."
RemovePIDFile
RemoveTempFiles
Display --text "Cleaning up..." --result DONE --color GREEN
ExitFatal
}
################################################################################
# Name : ContainsString()
# Description : Search a specific string (or regular expression) in another
# Returns : (0 - True, 1 - False)
# Usage : if ContainsString "needle" "there is a needle in the haystack"; echo "Found"; else "Not found"; fi
################################################################################
ContainsString() {
RETVAL=1
if [ $# -ne 2 ]; then ReportException "ContainsString" "Incorrect number of arguments for ContainsStrings function"; fi
FIND=$(echo "$2" | egrep "$1")
if [ ! "${FIND}" = "" ]; then RETVAL=0; fi
return ${RETVAL}
}
################################################################################
# Name : CountTests()
# Description : Count the number of tests performed
#
# Input : <nothing>
# Returns : <nothing>
# Usage : CountTests
################################################################################
CountTests() {
CTESTS_PERFORMED=$((CTESTS_PERFORMED + 1))
}
################################################################################
# Name : CreateTempFile()
# Description : Creates a temporary file
#
# Input : <nothing>
# Returns : TEMP_FILE (variable)
# Usage : CreateTempFile
# if [ ! "${TEMP_FILE}" = "" ]; then
# MYTMPFILE="${TEMP_FILE}"
# echo "My temporary file is ${MYTMPFILE}"
# fi
################################################################################
CreateTempFile() {
TEMP_FILE=""
if [ "${OS}" = "AIX" ]; then
RANDOMSTRING1=$(echo lynis-$(od -N4 -tu /dev/random | awk 'NR==1 {print $2} {}'))
TEMP_FILE="/tmp/${RANDOMSTRING1}"
touch ${TEMP_FILE}
else
TEMP_FILE=$(mktemp /tmp/lynis.XXXXXXXXXX) || exit 1
fi
if [ ! "${TEMP_FILE}" = "" ]; then
LogText "Action: created temporary file ${TEMP_FILE}"
else
Fatal "Could not create a temporary file"
fi
# Add temporary file to queue for cleanup later
TEMP_FILES="${TEMP_FILES} ${TEMP_FILE}"
}
################################################################################
# Name : DirectoryExists()
# Description : Check if a directory exists
#
# Returns : 0 (directory exists), 1 (directory does not exist)
# Usage : if DirectoryExists; then echo "it exists"; else echo "It does not exist"; fi
################################################################################
# Determine if a directory exists
DirectoryExists() {
DIRECTORY_FOUND=0
LogText "Test: checking if directory $1 exists"
if [ -d $1 ]; then
LogText "Result: directory $1 exists"
DIRECTORY_FOUND=1
return 0
else
LogText "Result: directory $1 NOT found"
return 1
fi
}
################################################################################
# Name : Debug()
# Description : Show additional information on screen
#
# Input : $1 = text
# Returns : Nothing
# Usage : Debug "More details"
################################################################################
Debug() {
if [ ${DEBUG} -eq 1 -a $# -gt 0 ]; then echo "${PURPLE}[DEBUG]${NORMAL} $1"; fi
}
################################################################################
# Name : DigitsOnly()
# Description : Only extract numbers from a string
#
# Returns : Digits only string (VALUE)
################################################################################
DigitsOnly() {
VALUE=$1
LogText "Value is now: ${VALUE}"
if [ ! "${AWKBINARY}" = "" ]; then
VALUE=$(echo ${VALUE} | grep -Eo '[0-9]{1,}')
fi
LogText "Returning value: ${VALUE}"
}
################################################################################
# Name : DiscoverProfiles()
# Description : Determine which profiles we have available
#
# Returns : Nothing
# Usage : DiscoverProfiles
################################################################################
DiscoverProfiles() {
# Try to find a default and custom profile, unless one was specified manually
if [ "${PROFILE}" = "" ]; then
CUSTOM_PROFILE=""
DEFAULT_PROFILE=""
PROFILEDIR=""
tPROFILE_NAMES="default.prf custom.prf"
tPROFILE_TARGETS="/usr/local/etc/lynis /etc/lynis /usr/local/lynis ."
for PNAME in ${tPROFILE_NAMES}; do
for PLOC in ${tPROFILE_TARGETS}; do
# Only use one default.prf
if [ "${PNAME}" = "default.prf" -a ! "${DEFAULT_PROFILE}" = "" ]; then
Debug "Already discovered default.prf - skipping this file (${PLOC}/${PNAME})"
elif [ "${PNAME}" = "custom.prf" -a ! "${CUSTOM_PROFILE}" = "" ]; then
Debug "Already discovered custom.prf - skipping this file (${PLOC}/${PNAME})"
else
if [ "${PLOC}" = "." ]; then FILE="${WORKDIR}/${PNAME}"; else FILE="${PLOC}/${PNAME}"; fi
if [ -r ${FILE} ]; then
PROFILES="${PROFILES} ${FILE}"
case ${PNAME} in
"custom.prf") CUSTOM_PROFILE="${FILE}" ;;
"default.prf") DEFAULT_PROFILE="${FILE}" ;;
esac
# Set profile directory to last match (Lynis could be both installed, and run as a separate download)
if [ "${PLOC}" = "." ]; then PROFILEDIR="${WORKDIR}"; else PROFILEDIR="${PLOC}"; fi
fi
fi
done
done
# Search any profiles defined with --profile
for FILE in ${SEARCH_PROFILES}; do
if [ -r ${FILE} ]; then
Debug "Found profile defined with --profile"
PROFILES="${PROFILES} ${FILE}"
fi
done
fi
if [ "${PROFILES}" = "" ]; then
echo "${RED}Fatal error: ${WHITE}No profile defined and could not find default profile${NORMAL}"
echo "Search paths used --> ${tPROFILE_TARGETS}"
ExitCustom 66
else
PROFILES=$(echo ${PROFILES} | sed 's/^ //')
fi
}
################################################################################
# Name : Display()
# Description : Show text on screen, with markup
#
# Input : <multiple parameters, see test>
# Returns : <nothing>
################################################################################
Display() {
INDENT=0; TEXT=""; RESULT=""; COLOR=""; SPACES=0; SHOWDEBUG=0
while [ $# -ge 1 ]; do
case $1 in
--color)
shift
case $1 in
GREEN) COLOR=$GREEN ;;
RED) COLOR=$RED ;;
WHITE) COLOR=$WHITE ;;
YELLOW) COLOR=$YELLOW ;;
esac
;;
--debug)
SHOWDEBUG=1
;;
--indent)
shift
INDENT=$1
;;
--result)
shift
RESULT=$1
;;
--text)
shift
TEXT=$1
;;
*)
echo "INVALID OPTION (Display): $1"
ExitFatal
;;
esac
# Go to next parameter
shift
done
if [ "${RESULT}" = "" ]; then
RESULTPART=""
else
if [ ${CRONJOB} -eq 0 ]; then
RESULTPART=" [ ${COLOR}${RESULT}${NORMAL} ]"
else
RESULTPART=" [ ${RESULT} ]"
fi
fi
if [ ! "${TEXT}" = "" ]; then
SHOW=0
if [ ${SHOW_WARNINGS_ONLY} -eq 1 ]; then
if [ "${RESULT}" = "WARNING" ]; then SHOW=1; fi
elif [ ${QUIET} -eq 0 ]; then SHOW=1
fi
if [ ${SHOW} -eq 1 ]; then
# Display:
# - counting with -m instead of -c, to support language locale
# - wc needs LANG to deal with multi-bytes characters but LANG has been unset in include/consts...
LINESIZE=$(export LC_ALL= ; export LANG="${DISPLAY_LANG}";echo "${TEXT}" | wc -m | tr -d ' ')
if [ ${SHOWDEBUG} -eq 1 ]; then DEBUGTEXT=" [${PURPLE}DEBUG${NORMAL}]"; else DEBUGTEXT=""; fi
if [ ${INDENT} -gt 0 ]; then SPACES=$((62 - INDENT - LINESIZE)); fi
if [ ${SPACES} -lt 0 ]; then SPACES=0; fi
if [ ${CRONJOB} -eq 0 ]; then
# Check if we already have already discovered a proper echo command tool. It not, set it default to 'echo'.
if [ "${ECHOCMD}" = "" ]; then ECHOCMD="echo"; fi
${ECHOCMD} "\033[${INDENT}C${TEXT}\033[${SPACES}C${RESULTPART}${DEBUGTEXT}"
else
echo "${TEXT}${RESULTPART}"
fi
fi
fi
}
################################################################################
# Name : DisplayError()
# Description : Show error on screen
#
# Input : $1 = text (string), $2 = optional exit code (integer)
# Returns : <nothing>
################################################################################
DisplayError() {
EXITCODE=""
if [ $# -gt 1 ]; then EXITCODE=$2; fi
${ECHOCMD} ""
${ECHOCMD} "${WARNING}Error${NORMAL}: ${BOLD}$1${NORMAL}"
${ECHOCMD} ""
if [ ! -z "${EXITCODE}" ]; then ExitCustom ${EXITCODE}; fi
}
################################################################################
# Name : DisplayManual()
# Description : Show text on screen, without any markup
#
# Input : $1 = text (string)
# Returns : <nothing>
################################################################################
DisplayManual() {
if [ ${QUIET} -eq 0 ]; then ${ECHOCMD} "$1"; fi
}
################################################################################
# Name : DisplayToolTip()
# Description : Show tooltip on screen
#
# Input : $1 = text
# Returns : <nothing>
################################################################################
DisplayToolTip() {
# Display tooltip when enabled and no tip has been displayed yet
if [ ${SHOW_TOOL_TIPS} -eq 1 -a ${TOOLTIP_SHOWED} -eq 0 ]; then
# Check if we already have already discovered a proper echo command tool. It not, set it default to 'echo'.
if [ "${ECHOCMD}" = "" ]; then ECHOCMD="echo"; fi
if [ ${CRONJOB} -eq 0 ]; then
printf "\n"
${ECHOCMD} " ${BG_BLUE}[TIP]${NORMAL}: ${LIGHTBLUE}$1${NORMAL}"
printf "\n"
else
${ECHOCMD} " [TIP]: $1"
fi
TOOLTIP_SHOWED=1
fi
}
################################################################################
# Name : ExitClean()
# Description : Perform a normal exit of the program, and clean up resources
#
# Input : <nothing>
# Returns : <nothing>
# Usage : ExitClean
################################################################################
ExitClean() {
RemovePIDFile
RemoveTempFiles
LogText "${PROGRAM_NAME} ended successfully."
exit 0
}
################################################################################
# Name : ExitCustom()
# Description : Perform a normal exit of the program, and clean up resources
#
# Input : $1 = exit code (optional)
# Returns : Nothing
# Usage : ExitCustom 35
################################################################################
ExitCustom() {
RemovePIDFile
RemoveTempFiles
# Exit with the exit code given, otherwise use 1
if [ $# -eq 1 ]; then
LogText "${PROGRAM_NAME} ended with exit code $1."
exit $1
else
LogText "${PROGRAM_NAME} ended with exit code 1."
exit 1
fi
}
################################################################################
# Name : ExitFatal()
# Description : Perform exit of the program (with code 1), clean up resources
#
# Input : $1 = text string (optional)
# Returns : <nothing>
# Usage : ExitFatal
################################################################################
ExitFatal() {
RemovePIDFile
RemoveTempFiles
LogText "${PROGRAM_NAME} ended with exit code 1."
if [ $# -eq 1 ]; then
${ECHOCMD} ""
${ECHOCMD} "${RED}Fatal error${NORMAL}: ${WHITE}$1${NORMAL}"
${ECHOCMD} ""
fi
exit 1
}
################################################################################
# Name : FileExists()
# Description : Determine if a file exists
# Returns : 0 (found), 1 (not found)
# FILE_FOUND (0:found, 1:not found) - deprecated usage
################################################################################
FileExists() {
FILE_FOUND=0
LogText "Test: checking if file $1 exists"
if [ -f $1 ]; then
LogText "Result: file $1 exists"
FILE_FOUND=1
return 0
else
LogText "Result: file $1 NOT found"
return 1
fi
}
################################################################################
# Name : FileIsEmpty()
# Description : Check if a file is empty
#
# Returns : 0 (empty), 1 (not empty)
# EMPTY (0 or 1) - deprecated usage
# Usage : xyz
################################################################################
FileIsEmpty() {
EMPTY=0
LogText "Test: checking if file $1 is empty"
if [ -z $1 ]; then
LogText "Result: file $1 is empty"
EMPTY=1
return 0
else
LogText "Result: file $1 is NOT empty"
return 1
fi
}
################################################################################
# Name : FileIsReadable()
# Description : Check if a file readable or directory is accessible
#
# Returns : Return code (0 = readable, 1 = not readable)
# Usage : if FileIsReadable /etc/shadow; then echo "File is readable"; fi
################################################################################
FileIsReadable() {
if [ $# -eq 0 ]; then ExitFatal "Function FileIsReadable() called without a file name"; fi
sFILE=$1
CANREAD=0
RETVAL=1
LogText "Test: check if we can access ${sFILE}"
# Check for symlink
if [ -L ${sFILE} ]; then
ShowSymlinkPath ${sFILE}
if [ ! "${SYMLINK}" = "" ]; then sFILE="${SYMLINK}"; fi
fi
# Only check the file if it isn't a symlink (after previous check)
if [ -L ${sFILE} ]; then
OTHERPERMS="-"
LogText "Result: unclear if we can read this file, as this is a symlink"
ReportException "FileIsReadable" "Can not determine symlink ${sFILE}"
elif [ -d ${sFILE} ]; then
OTHERPERMS=$(ls -d -l ${sFILE} | cut -c 8)
elif [ -f ${sFILE} ]; then
OTHERPERMS=$(ls -d -l ${sFILE} | cut -c 8)
else
OTHERPERMS="-"
fi
# Also check if we are the actual owner of the file (use -d to get directory itself, if its a directory)
FILEOWNER=$(ls -dln ${sFILE} | awk -F" " '{ print $3 }')
if [ "${FILEOWNER}" = "${MYID}" ]; then
LogText "Result: file is owned by our current user ID (${MYID}), checking if it is readable"
if [ -L ${sFILE} ]; then
LogText "Result: unclear if we can read this file, as this is a symlink"
ReportException "FileIsReadable" "Can not determine symlink ${sFILE}"
elif [ -d ${sFILE} ]; then
OTHERPERMS=$(ls -d -l ${sFILE} | cut -c 2)
elif [ -f ${sFILE} ]; then
OTHERPERMS=$(ls -d -l ${sFILE} | cut -c 2)
fi
else
LogText "Result: file is not owned by current user ID (${MYID}), but UID ${FILEOWNER}"
fi
# Check if we are root, or have the read bit
if [ "${MYID}" = "0" -o "${OTHERPERMS}" = "r" ]; then
CANREAD=1
LogText "Result: file ${sFILE} is readable (or directory accessible)."
return 0
else
return 1
LogText "Result: file ${sFILE} is NOT readable (or directory accessible), symlink, or does not exist. (OTHERPERMS: ${OTHERPERMS})"
fi
}
################################################################################
# Name : GetHostID()
# Description : Create an unique id for the system
#
# Returns : Nothing
# Usage : GetHostID
################################################################################
GetHostID() {
FIND=""
# Avoid some hashes (empty, only zeros)
BLACKLISTED_HASHES="6ef1338f520d075957424741d7ed35ab5966ae97 adc83b19e793491b1c6ea0fd8b46cd9f32e592fc"
# Check which utilities we can use (e.g. lynis show hostids). Normally these are detected during binaries collecting.
if [ "${SHA1SUMBINARY}" = "" ]; then SHA1SUMBINARY=$(which sha1sum 2> /dev/null); fi
if [ "${SHA1SUMBINARY}" = "" ]; then SHA1SUMBINARY=$(which sha1 2> /dev/null); fi
if [ "${SHA256SUMBINARY}" = "" ]; then SHA256SUMBINARY=$(which sha256sum 2> /dev/null); fi
if [ "${SHA256SUMBINARY}" = "" ]; then SHA256SUMBINARY=$(which sha256 2> /dev/null); fi
if [ "${CSUMBINARY}" = "" ]; then CSUMBINARY=$(which csum 2> /dev/null); fi
if [ "${OPENSSLBINARY}" = "" ]; then OPENSSLBINARY=$(which openssl 2> /dev/null); fi
if [ "${IFCONFIGBINARY}" = "" ]; then IFCONFIGBINARY=$(which ifconfig 2> /dev/null); fi
if [ "${IPBINARY}" = "" ]; then IPBINARY=$(which ip 2> /dev/null); fi
if [ ! "${SHA1SUMBINARY}" = "" -o ! "${OPENSSLBINARY}" = "" -o ! "${CSUMBINARY}" = "" ]; then
case "${OS}" in
"AIX")
# Common interfaces: en0 en1 en2, ent0 ent1 ent2
FIND=$(entstat en0 2>/dev/null | grep "Hardware Address" | awk -F ": " '{ print $2 }')
if [ "${FIND}" = "" ]; then
FIND=$(entstat ent0 2>/dev/null | grep "Hardware Address" | awk -F ": " '{ print $2 }')
fi
if [ ! "${FIND}" = "" ]; then
# We have a MAC address, now hashing it
if [ ! "${SHA1SUMBINARY}" = "" ]; then
HOSTID=$(echo ${FIND} | ${SHA1SUMBINARY} | awk '{ print $1 }')
elif [ ! "${CSUMBINARY}" = "" ]; then
HOSTID=$(echo ${FIND} | ${CSUMBINARY} -h SHA1 - | awk '{ print $1 }')
elif [ ! "${OPENSSLBINARY}" = "" ]; then
HOSTID=$(echo ${FIND} | ${OPENSSLBINARY} sha -sha1 | awk '{ print $2 }')
else
ReportException "GetHostID" "No sha1, sha1sum, csum or openssl binary available on AIX"
fi
else
ReportException "GetHostID" "No output from entstat on interfaces: en0, ent0"
fi
;;
"DragonFly" | "FreeBSD")
FIND=$(${IFCONFIGBINARY} | grep ether | head -1 | awk '{ print $2 }' | tr '[:upper:]' '[:lower:]')
if [ ! "${FIND}" = "" ]; then
HOSTID=$(echo ${FIND} | sha1)
else
ReportException "GetHostID" "No MAC address returned on DragonFly or FreeBSD"
fi
;;
"Linux")
# Define preferred interfaces
#PREFERRED_INTERFACES="eth0 eth1 eth2 enp0s25"
# Only use ifconfig if no ip binary has been found
if [ ! "${IFCONFIGBINARY}" = "" ]; then
# Determine if we have ETH0 at all (not all Linux distro have this, e.g. Arch)
HASETH0=$(${IFCONFIGBINARY} | grep "^eth0")
# Check if we can find it with HWaddr on the line
FIND=$(${IFCONFIGBINARY} 2> /dev/null | grep "^eth0" | grep -v "eth0:" | grep HWaddr | awk '{ print $5 }' | tr '[:upper:]' '[:lower:]')
# If nothing found, then try first for alternative interface. Else other versions of ifconfig (e.g. Slackware/Arch)
if [ "${FIND}" = "" ]; then
FIND=$(${IFCONFIGBINARY} 2> /dev/null | grep HWaddr)
if [ "${FIND}" = "" ]; then
# If possible directly address eth0 to avoid risking gathering the incorrect MAC address.
# If not, then falling back to getting first interface. Better than nothing.
if [ ! "${HASETH0}" = "" ]; then
FIND=$(${IFCONFIGBINARY} eth0 2> /dev/null | grep "ether " | awk '{ print $2 }' | tr '[:upper:]' '[:lower:]')
else
FIND=$(${IFCONFIGBINARY} 2> /dev/null | grep "ether " | awk '{ print $2 }' | head -1 | tr '[:upper:]' '[:lower:]')
if [ "${FIND}" = "" ]; then
ReportException "GetHostID" "No eth0 found (and no ether was found with ifconfig)"
else
LogText "Result: No eth0 found (ether found), using first network interface to determine hostid (with ifconfig)"
fi
fi
else
FIND=$(${IFCONFIGBINARY} 2> /dev/null | grep HWaddr | head -1 | awk '{ print $5 }' | tr '[:upper:]' '[:lower:]')
LogText "GetHostID: No eth0 found (but HWaddr was found), using first network interface to determine hostid, with ifconfig"
fi
fi
else
# See if we can use ip binary instead
if [ ! "${IPBINARY}" = "" ]; then
# Determine if we have the common available eth0 interface
FIND=$(${IPBINARY} addr show eth0 2> /dev/null | egrep "link/ether " | head -1 | awk '{ print $2 }' | tr '[:upper:]' '[:lower:]')
if [ "${FIND}" = "" ]; then
# Determine the MAC address of first interface with the ip command
FIND=$(${IPBINARY} addr show 2> /dev/null | egrep "link/ether " | head -1 | awk '{ print $2 }' | tr '[:upper:]' '[:lower:]')
if [ "${FIND}" = "" ]; then
ReportException "GetHostID" "Can't create hostid (no MAC addresses found)"
fi
fi
else
ReportException "GetHostID" "Can't create hostid, missing both ifconfig and ip binary"
fi
fi
# Check if we found a HostID
if [ ! "${FIND}" = "" ]; then
LogText "Info: using hardware address ${FIND} to create ID"
HOSTID=$(echo ${FIND} | ${SHA1SUMBINARY} | awk '{ print $1 }')
LogText "Result: Found HostID: ${HOSTID}"
else
ReportException "GetHostID" "Can't create HOSTID, command ip not found"
fi
;;
"macOS")
FIND=$(${IFCONFIGBINARY} en0 | grep ether | head -1 | awk '{ print $2 }' | tr '[:upper:]' '[:lower:]')
if [ ! "${FIND}" = "" ]; then
HOSTID=$(echo ${FIND} | shasum | awk '{ print $1 }')
else
ReportException "GetHostID" "No MAC address returned on macOS"
fi
LYNIS_HOSTID2_PART1=$(hostname -s)
if [ ! -z "${LYNIS_HOSTID2_PART1}" ]; then
LogText "Info: using hostname ${LYNIS_HOSTID2_PART1}"
LYNIS_HOSTID2_PART2=$(sysctl -n kern.uuid 2> /dev/null)
if [ ! -z "${LYNIS_HOSTID2_PART2}" ]; then
LogText "Info: using UUID ${LYNIS_HOSTID2_PART2}"
else
LogText "Info: could not create HOSTID2 as kern.uuid sysctl key is missing"
fi
HOSTID2=$(echo "${LYNIS_HOSTID2_PART1}${LYNIS_HOSTID2_PART2}" | shasum -a 256 | awk '{ print $1 }')
else
LogText "Info: could not create HOSTID2 as hostname is missing"
fi
;;
"NetBSD")
FIND=$(${IFCONFIGBINARY} -a | grep "address:" | head -1 | awk '{ print $2 }' | tr '[:upper:]' '[:lower:]')
if [ ! "${FIND}" = "" ]; then
HOSTID=$(echo ${FIND} | sha1)
else
ReportException "GetHostID" "No MAC address returned on NetBSD"
fi
;;
"OpenBSD")
FIND=$(${IFCONFIGBINARY} | grep "lladdr " | head -1 | awk '{ print $2 }' | tr '[:upper:]' '[:lower:]')
if [ ! "${FIND}" = "" ]; then
HOSTID=$(echo ${FIND} | sha1)
else
ReportException "GetHostID" "No MAC address returned on OpenBSD"
fi
;;
"Solaris")
INTERFACES_TO_TEST="e1000g1 net0"
FOUND=0
for I in ${INTERFACES_TO_TEST}; do
FIND=$(${IFCONFIGBINARY} -a | grep "^${I}")
if [ ! "${FIND}" = "" ]; then
FOUND=1; LogText "Found interface ${I} on Solaris"
fi
done
if [ ${FOUND} -eq 1 ]; then
FIND=$(${IFCONFIGBINARY} ${I} | grep ether | awk '{ if ($1=="ether") { print $2 }}')
if [ ! "${SHA1SUMBINARY}" = "" ]; then
HOSTID=$(echo ${FIND} | ${SHA1SUMBINARY} | awk '{ print $1 }')
elif [ ! "${OPENSSLBINARY}" = "" ]; then
HOSTID=$(echo ${FIND} | ${OPENSSLBINARY} sha -sha1 | awk '{ print $2 }')
else
ReportException "GetHostID" "Can not find sha1/sha1sum or openssl"
fi
else
ReportException "GetHostID" "No interface found op Solaris to create HostID"
fi
;;
*)
ReportException "GetHostID" "Can't create HOSTID as OS is not supported yet by this function"
;;
esac
# Remove HOSTID if it contains a default MAC address with a related hash value
if [ ! "${HOSTID}" = "" ]; then
for CHECKHASH in ${BLACKLISTED_HASHES}; do
if [ "${CHECKHASH}" = "${HOSTID}" ]; then
LogText "Result: hostid is a blacklisted value"
HOSTID=""
fi
done
fi
else
ReportException "GetHostID" "Can't create HOSTID as there is no SHA1 hash tool available (sha1, sha1sum, openssl)"
fi
# Search machine ID
# This applies to IDs generated for systemd
# Optional: DBUS creates ID as well with dbus-uuidgen and is stored in /var/lib/dbus-machine-id (might be symlinked to /etc/machine-id)
sMACHINEIDFILE="/etc/machine-id"
if [ -f ${sMACHINEIDFILE} ]; then
FIND=$(head -1 ${sMACHINEIDFILE} | grep "^[a-f0-9]")
if [ "${FIND}" = "" ]; then
MACHINEID="${FIND}"
fi
fi
if [ "${HOSTID}" = "" ]; then
LogText "Result: no HOSTID available, trying to use SSH key as unique source"
# Create host ID when a MAC address was not found
SSH_KEY_FILES="ssh_host_ed25519_key.pub ssh_host_ecdsa_key.pub ssh_host_dsa_key.pub ssh_host_rsa_key.pub"
if [ -d /etc/ssh ]; then
for I in ${SSH_KEY_FILES}; do
if [ "${HOSTID}" = "" ]; then
if [ -f /etc/ssh/${I} ]; then
LogText "Result: found ${I} in /etc/ssh"
if [ ! "${SHA1SUMBINARY}" = "" ]; then
HOSTID=$(cat /etc/ssh/${I} | ${SHA1SUMBINARY} | awk '{ print $1 }')
LogText "result: Created HostID with SSH key ($I): ${HOSTID}"
else
ReportException "GetHostID" "Can't create HOSTID with SSH key, as sha1sum binary is missing"
fi
fi
fi
done
else
LogText "Result: no /etc/ssh directory found, skipping"
fi
fi
# New style host ID
if [ "${HOSTID2}" = "" ]; then
LogText "Info: creating a HostID (version 2)"
FOUND=0
DATA_SSH=""
# Use public keys
SSH_KEY_FILES="ssh_host_ed25519_key.pub ssh_host_ecdsa_key.pub ssh_host_dsa_key.pub ssh_host_rsa_key.pub"
if [ -d /etc/ssh ]; then
for I in ${SSH_KEY_FILES}; do
if [ ${FOUND} -eq 0 ]; then
if [ -f /etc/ssh/${I} ]; then
LogText "Result: found file ${I} in /etc/ssh, using that to create host identifier"
DATA_SSH=$(cat /etc/ssh/${I})
FOUND=1
fi
fi
done
else
LogText "Result: no /etc/ssh directory found, skipping"
fi
STRING_TO_HASH=""
if [ ${FOUND} -eq 1 -a ! -z "${DATA_SSH}" ]; then
LogText "Using SSH public key to create the second host identifier"
STRING_TO_HASH="${DATA_SSH}"
else
if [ ! -z "${MACHINEID}" ]; then
LogText "Using the machine ID to create the second host identifier"
STRING_TO_HASH="${MACHINEID}"
fi
fi
# Check if we have a string to turn into a host identifier
if [ ! -z "${STRING_TO_HASH}" ]; then
# Create hashes
if [ ! "${SHA256SUMBINARY}" = "" ]; then
HASH2=$(echo ${STRING_TO_HASH} | ${SHA256SUMBINARY} | awk '{ print $1 }')
HASH_HOSTNAME=$(echo ${HOSTNAME} | ${SHA256SUMBINARY} | awk '{ print $1 }')
elif [ ! "${OPENSSLBINARY}" = "" ]; then
HASH2=$(echo ${STRING_TO_HASH} | ${OPENSSLBINARY} sha -sha256 | awk '{ print $2 }')
HASH_HOSTNAME=$(echo ${HOSTNAME} | ${OPENSSLBINARY} sha -sha256 | awk '{ print $2 }')
fi
LogText "Hash (hostname): ${HASH_HOSTNAME}"
LogText "Hash (ssh or machineid): ${HASH2}"
HOSTID2="${HASH2}"
Report "hostid2=${HOSTID2}"
fi
fi
# Show an exception if no HostID could be created, to ensure each system (and scan) has one
if [ "${HOSTID}" = "" ]; then
ReportException "GetHostID" "No unique host identifier could be created."
fi
}
################################################################################
# Name : InsertSection()
# Description : Show a section block on screen
#
# Returns : Nothing
# Usage : InsertSection
################################################################################
InsertSection() {
if [ ${QUIET} -eq 0 ]; then
echo ""
echo "[+] ${SECTION}$1${NORMAL}"
echo "------------------------------------"
fi
LogTextBreak
LogText "Action: Performing tests from category: $1"
}
################################################################################
# Name : InsertPlugionSection()
# Description : Insert section block for plugins (different color)
#
# Returns : Nothing
# Usage : InsertPluginSection
################################################################################
InsertPluginSection() {
if [ ${QUIET} -eq 0 ]; then
echo ""
echo "[+] ${MAGENTA}$1${NORMAL}"
echo "------------------------------------"
fi
LogText "Action: Performing plugin tests"
}
################################################################################
# Name : IsContainer()
# Description : Determine if we are running in a container
# Returns : Exit code (0 = true, 1 = false)
# CONTAINER_TYPE
################################################################################
IsContainer() {
FOUND=0
if [ -e /proc/1/cgroup ]; then
FIND=$(cat /proc/1/cgroup | grep -i docker)
if [ $? -eq 0 ]; then
LogText "Result: found Docker in control groups (/proc/1/cgroup), so we are running in Docker container"
CONTAINER_TYPE="Docker"; FOUND=1
EXITCODE=0
fi
fi
if [ ${FOUND} -eq 0 ]; then
CONTAINER_TYPE=""
EXITCODE=1
fi
return ${EXITCODE}
}
################################################################################
# Name : IsDebug()
# Description : Check if --debug option is used to show more details
# Returns : 0 (True) or 1 (False)
################################################################################
IsDebug() {
if [ ${DEBUG} -eq 1 ]; then return 0; else return 1; fi
}
################################################################################
# Name : IsDeveloperMode()
# Description : Check if we are in development mode (--developer)
#
# Returns : 0 (True) or 1 (False)
# Notes : This is set with command line option or as a profile setting
################################################################################
IsDeveloperMode() {
if [ ${DEVELOPER_MODE} -eq 1 ]; then return 0; else return 1; fi
}
################################################################################
# Name : IsDeveloperVersion()
# Description : Check if this version is development or stable release
#
# Returns : 0 (True) or 1 (False)
################################################################################
IsDeveloperVersion() {
if [ "${PROGRAM_RELEASE_TYPE}" = "dev" ]; then return 0; else return 1; fi
}
################################################################################
# Name : IsRunning()
# Description : Check if a process is running
# Returns : 0 (process is running), 1 (process not running)
# RUNNING (1 = running, 0 = not running) - will be deprecated
################################################################################
IsRunning() {
RUNNING=0
PSOPTIONS=""
if [ ${SHELL_IS_BUSYBOX} -eq 0 ]; then PSOPTIONS=" ax"; fi
FIND=$(${PSBINARY} ${PSOPTIONS} | egrep "( |/)$1" | grep -v "grep")
if [ ! "${FIND}" = "" ]; then
RUNNING=1
LogText "IsRunning: process '$1' found (${FIND})"
return 0
else
LogText "IsRunning: process '$1' not found"
return 1
fi
}
################################################################################
# Name : IsNotebook
# Description : Check if file or directory is owned by root
# Returns : 0 (true), 1 (false), or 255 (unknown)
################################################################################
IsNotebook() {
FIND=$(which laptop-detect 2> /dev/null)
if [ ! -z "${FIND}" ]; then
Debug "Testing if we are a notebook"
laptop-detect
if [ $? -eq 0 ]; then SYSTEM_IS_NOTEBOOK=1; Debug "System is a notebook according to laptop-detect"
elif [ $? -eq 1 ]; then SYSTEM_IS_NOTEBOOK=0; Debug "System is a NOT a notebook according to laptop-detect"; fi
Report "notebook=${SYSTEM_IS_NOTEBOOK}"
fi
}
################################################################################
# Name : IsOwnedByRoot
# Description : Check if file or directory is owned by root
# Returns : 0 (true), 1 (false), or 255 (unknown)
################################################################################
IsOwnedByRoot() {
PERMS=""
if [ $# -eq 1 ]; then
FILE="$1"
case $OS in
"AIX")
if [ ! "${ISTATBINARY}" = "" ]; then PERMS=$(${ISTATBINARY} ${FILE} | sed "s/Owner: //" | sed "s/[a-zA-Z() ]//g"); fi
;;
"Linux")
if [ ! "${STATBINARY}" = "" ]; then PERMS=$(${STATBINARY} -c "%u:%g" ${FILE}); fi
;;
"FreeBSD")
if [ ! "${STATBINARY}" = "" ]; then PERMS=$(${STATBINARY} -f "%u:%g" ${FILE}); fi
;;
esac
# Fallback with ls (for other platforms, or when a test did not reveal any output)
if [ "${PERMS}" = "" ]; then
PERMS=$(ls -n ${FILE} | ${AWKBINARY} '{ print $3":"$4 }')
fi
else
ReportException "IsOwnedByRoot" "Functions needs 1 argument"
return 255
fi
if [ "${PERMS}" = "0:0" ]; then
if IsDeveloperMode; then LogText "Debug: found incorrect file permissions on ${FILE}"; fi
return 0
else
return 1
fi
}
################################################################################
# Name : IsVerbose()
# Description : Check if --verbose option is used to show more details on screen
# Returns : 0 (true) or 1 (false)
################################################################################
IsVerbose() {
if [ ${VERBOSE} -eq 1 ]; then return 0; else return 1; fi
}
################################################################################
# Name : IsVirtualMachine()
# Description : Determine whether it is a virtual machine
# Returns : ISVIRTUALMACHINE (0-2)
# VMTYPE
# VMFULLTYPE
################################################################################
IsVirtualMachine() {
LogText "Test: Determine if this system is a virtual machine"
# 0 = no, 1 = yes, 2 = unknown
ISVIRTUALMACHINE=2; VMTYPE="unknown"; VMFULLTYPE="Unknown"
SHORT=""
# facter
if [ "${SHORT}" = "" ]; then
if [ -x /usr/bin/facter ] || [ -x /usr/local/bin/facter ]; then
case "$(facter is_virtual)" in
"true")
SHORT=$(facter virtual)
LogText "Result: found ${SHORT}"
;;
"false")
LogText "Result: facter says this machine is not a virtual"
;;
esac
else
LogText "Result: facter utility not found"
fi
else
LogText "Result: skipped facter test, as we already found machine type"
fi
# systemd
if [ "${SHORT}" = "" ]; then
if [ -x /usr/bin/systemd-detect-virt ]; then
LogText "Test: trying to guess virtualization technology with systemd-detect-virt"
FIND=$(/usr/bin/systemd-detect-virt)
if [ ! "${FIND}" = "" ]; then
LogText "Result: found ${FIND}"
SHORT="${FIND}"
fi
else
LogText "Result: systemd-detect-virt not found"
fi
else
LogText "Result: skipped systemd test, as we already found machine type"
fi
# lscpu
# Values: VMware
if [ "${SHORT}" = "" ]; then
if [ -x /usr/bin/lscpu ]; then
LogText "Test: trying to guess virtualization with lscpu"
FIND=$(lscpu | grep "^Hypervisor Vendor" | awk -F: '{ print $2 }' | sed 's/ //g')
if [ ! "${FIND}" = "" ]; then
LogText "Result: found ${FIND}"
SHORT="${FIND}"
else
LogText "Result: can't find hypervisor vendor with lscpu"
fi
else
LogText "Result: lscpu not found"
fi
else
LogText "Result: skipped lscpu test, as we already found machine type"
fi
# dmidecode
# Values: VMware Virtual Platform / VirtualBox
if [ "${SHORT}" = "" ]; then
if [ -x /usr/bin/dmidecode ]; then DMIDECODE_BINARY="/usr/bin/dmidecode"
elif [ -x /usr/sbin/dmidecode ]; then DMIDECODE_BINARY="/usr/sbin/dmidecode"
else DMIDECODE_BINARY=""
fi
if [ ! "${DMIDECODE_BINARY}" = "" -a ${PRIVILEGED} -eq 1 ]; then
LogText "Test: trying to guess virtualization with dmidecode"
FIND=$(/usr/sbin/dmidecode -s system-product-name | awk '{ print $1 }')
if [ ! "${FIND}" = "" ]; then
LogText "Result: found ${FIND}"
SHORT="${FIND}"
else
LogText "Result: can't find product name with dmidecode"
fi
else
LogText "Result: dmidecode not found (or no access)"
fi
else
LogText "Result: skipped dmidecode test, as we already found machine type"
fi
# Other options
# SaltStack: salt-call grains.get virtual
# < needs snippet >
# Try common guest processes
if [ "${SHORT}" = "" ]; then
LogText "Test: trying to guess virtual machine type by running processes"
# VMware
IsRunning vmware-guestd
if [ ${RUNNING} -eq 1 ]; then SHORT="vmware"; fi
IsRunning vmtoolsd
if [ ${RUNNING} -eq 1 ]; then SHORT="vmware"; fi
# VirtualBox based on guest services
IsRunning vboxguest-service
if [ ${RUNNING} -eq 1 ]; then SHORT="virtualbox"; fi
IsRunning VBoxClient
if [ ${RUNNING} -eq 1 ]; then SHORT="virtualbox"; fi
else
LogText "Result: skipped processes test, as we already found platform"
fi
# Amazon EC2
if [ "${SHORT}" = "" ]; then
LogText "Test: checking specific files for Amazon"
if [ -f /etc/ec2_version -a ! -z /etc/ec2_version ]; then
SHORT="amazon-ec2"
else
LogText "Result: system not hosted on Amazon"
fi
else
LogText "Result: skipped Amazon EC2 test, as we already found platform"
fi
# sysctl values
if [ "${SHORT}" = "" ]; then
LogText "Test: trying to guess virtual machine type by sysctl keys"
# FreeBSD: hw.hv_vendor (remains empty for VirtualBox)
# NetBSD: machdep.dmi.system-product
# OpenBSD: hw.product
FIND=$(sysctl -a 2> /dev/null | egrep "(hw.product|machdep.dmi.system-product)" | head -1 | sed 's/ = /=/' | awk -F= '{ print $2 }')
if [ ! "${FIND}" = "" ]; then
SHORT="${FIND}"
fi
else
LogText "Result: skipped sysctl test, as we already found platform"
fi
# lshw
if [ "${SHORT}" = "" ]; then
if [ ${PRIVILEGED} -eq 1 ]; then
if [ -x /usr/bin/lshw ]; then
LogText "Test: trying to guess virtualization with lshw"
FIND=$(lshw -quiet -class system 2> /dev/null | awk '{ if ($1=="product:") { print $2 }}')
if [ ! "${FIND}" = "" ]; then
LogText "Result: found ${FIND}"
SHORT="${FIND}"
fi
else
LogText "Result: lshw not found"
fi
else
LogText "Result: skipped lshw test, as we are non-privileged and need more permissions to run lshw"
fi
else
LogText "Result: skipped lshw test, as we already found machine type"
fi
# Check if we caught some string along all tests
if [ ! "${SHORT}" = "" ]; then
# Lowercase and see if we found a match
SHORT=$(echo ${SHORT} | awk '{ print $1 }' | tr [[:upper:]] [[:lower:]])
case ${SHORT} in
amazon-ec2) ISVIRTUALMACHINE=1; VMTYPE="amazon-ec2"; VMFULLTYPE="Amazon AWS EC2 Instance" ;;
bochs) ISVIRTUALMACHINE=1; VMTYPE="bochs"; VMFULLTYPE="Bochs CPU emulation" ;;
docker) ISVIRTUALMACHINE=1; VMTYPE="docker"; VMFULLTYPE="Docker container" ;;
kvm) ISVIRTUALMACHINE=1; VMTYPE="kvm"; VMFULLTYPE="KVM" ;;
lxc) ISVIRTUALMACHINE=1; VMTYPE="lxc"; VMFULLTYPE="Linux Containers" ;;
lxc-libvirt) ISVIRTUALMACHINE=1; VMTYPE="lxc-libvirt"; VMFULLTYPE="libvirt LXC driver (Linux Containers)" ;;
microsoft) ISVIRTUALMACHINE=1; VMTYPE="microsoft"; VMFULLTYPE="Microsoft Virtual PC" ;;
openvz) ISVIRTUALMACHINE=1; VMTYPE="openvz"; VMFULLTYPE="OpenVZ" ;;
oracle|virtualbox) ISVIRTUALMACHINE=1; VMTYPE="virtualbox"; VMFULLTYPE="Oracle VM VirtualBox" ;;
qemu) ISVIRTUALMACHINE=1; VMTYPE="qemu"; VMFULLTYPE="QEMU" ;;
systemd-nspawn) ISVIRTUALMACHINE=1; VMTYPE="systemd-nspawn"; VMFULLTYPE="Systemd Namespace container" ;;
uml) ISVIRTUALMACHINE=1; VMTYPE="uml"; VMFULLTYPE="User-Mode Linux (UML)" ;;
vmware) ISVIRTUALMACHINE=1; VMTYPE="vmware"; VMFULLTYPE="VMware product" ;;
xen) ISVIRTUALMACHINE=1; VMTYPE="xen"; VMFULLTYPE="XEN" ;;
zvm) ISVIRTUALMACHINE=1; VMTYPE="zvm"; VMFULLTYPE="IBM z/VM" ;;
openstack) ISVIRTUALMACHINE=1; VMTYPE="openstack"; VMFULLTYPE="Openstack Nova" ;;
*) LogText "Result: Unknown virtualization type, so most likely system is physical" ;;
esac
fi
# Check final status
if [ ${ISVIRTUALMACHINE} -eq 1 ]; then
LogText "Result: found virtual machine (type: ${VMTYPE}, ${VMFULLTYPE})"
Report "vm=1"
Report "vmtype=${VMTYPE}"
elif [ ${ISVIRTUALMACHINE} -eq 2 ]; then
LogText "Result: unknown if this system is a virtual machine"
Report "vm=2"
else
LogText "Result: system seems to be non-virtual"
fi
}
################################################################################
# Name : IsWorldReadable()
# Description : Determines if a file is readable for all users (world)
#
# Input : path (string)
# Returns : exit code (0 = readable, 1 = not readable, 255 = error)
# Usage : if IsWorldReadable /etc/motd; then echo "File is readable"; fi
################################################################################
IsWorldReadable() {
sFILE=$1
# Check for symlink
if [ -L ${sFILE} ]; then
ShowSymlinkPath ${sFILE}
if [ ! "${SYMLINK}" = "" ]; then sFILE="${SYMLINK}"; fi
fi
if [ -f ${sFILE} -o -d ${sFILE} ]; then
FINDVAL=$(ls -ld ${sFILE} | cut -c 8)
if [ "${FINDVAL}" = "r" ]; then return 0; else return 1; fi
else
return 255
fi
}
################################################################################
# Name : IsWorldExecutable()
# Description : Determines if a file is executable for all users (world)
#
# Input : path (string)
# Returns : exit code (0 = executable, 1 = not executable, 255 = error)
# Usage : if IsWorldExecutable /bin/ps; then echo "File is executable"; fi
################################################################################
# Function IsWorldExecutable
IsWorldExecutable() {
sFILE=$1
# Check for symlink
if [ -L ${sFILE} ]; then
ShowSymlinkPath ${sFILE}
if [ ! "${SYMLINK}" = "" ]; then sFILE="${SYMLINK}"; fi
fi
if [ -f ${sFILE} -o -d ${sFILE} ]; then
FINDVAL=$(ls -l ${sFILE} | cut -c 10)
if [ "${FINDVAL}" = "x" ]; then return 0; else return 1; fi
else
return 255
fi
}
################################################################################
# Name : IsWorldWritable()
# Description : Determines if a file is writable for all users
#
# Input : path
# Returns : exit code (0 = writable, 1 = not writable, 255 = error)
# Usage : if IsWorldWritable /etc/motd; then echo "File is writable"; fi
################################################################################
IsWorldWritable() {
sFILE=$1
FileIsWorldWritable=""
# Only check if target is a file or directory
if [ -f ${sFILE} -o -d ${sFILE} ]; then
FINDVAL=$(ls -ld ${sFILE} | cut -c 9)
if IsDeveloperMode; then Debug "File mode of ${sFILE} is ${FINDVAL}"; fi
if [ "${FINDVAL}" = "w" ]; then return 0; else return 1; fi
else
return 255
fi
}
################################################################################
# Name : LogText()
# Description : Function logtext (redirect data ($1) to log file)
#
# Input : $1 = text (string)
# Returns : Nothing
# Usage : LogText "This line goes into the log file"
################################################################################
LogText() {
if [ ! "${LOGFILE}" = "" -a ${LOGTEXT} -eq 1 ]; then CDATE=$(date "+%Y-%m-%d %H:%M:%S"); echo "${CDATE} $1" >> ${LOGFILE}; fi
}
################################################################################
# Name : LogTextBreak()
# Description : Add a separator to log file between sections, tests etc
# Returns : <nothing>
################################################################################
LogTextBreak() {
if [ ! "${LOGFILE}" = "" -a ${LOGTEXT} -eq 1 ]; then
CDATE=$(date "+%Y-%m-%d %H:%M:%S")
echo "${CDATE} ===---------------------------------------------------------------===" >> ${LOGFILE}
fi
}
################################################################################
# Name : ParseProfiles()
# Description : Check file permissions and parse data from profiles
# Returns : <nothing>
################################################################################
ParseProfiles() {
SafePerms ${INCLUDEDIR}/profiles
. ${INCLUDEDIR}/profiles
}
################################################################################
# Name : ParseTestValues()
# Description : Parse values from a specific test
# Inputs : service (e.g. ssh)
# Returns : CHECK_VALUES_ARRAY
################################################################################
ParseTestValues() {
RETVAL=1
FOUND=0
CHECK_VALUES_ARRAY=""
if [ $# -gt 0 -a ! "${CHECK_OPTION_ARRAY}" = "" ]; then
for I in ${CHECK_OPTION_ARRAY}; do
Debug "Array value: ${I}"
SPLIT=$(echo ${I} | sed 's/,/\n/g')
for ITEM in ${SPLIT}; do
FUNCTION=""
VALUE=""
SEARCH=""
SERVICE=""
ITEM_KEY=$(echo ${ITEM} | awk -F: '{print $1}')
ITEM_VALUE=$(echo ${ITEM} | awk -F: '{print $2}')
Debug "Array item: ${ITEM_KEY} with value ${ITEM_VALUE}"
case ${ITEM_KEY} in
"function")
case ${ITEM_VALUE} in
"equals")
FUNCTION="eq"
;;
esac
;;
"service")
if [ "${ITEM_VALUE}" = "$1" ]; then
FOUND=1
fi
;;
"value")
VALUE="${ITEM_VALUE}"
;;
*)
echo "Incorrect call to function ParseTestValues"; ExitFatal
;;
esac
if [ ${FOUND} -eq 1 ]; then
CHECK_VALUES_ARRAY="${CHECK_VALUES_ARRAY} ${SEARCH}:${VALUE}:${FUNCTION}:"
FOUND=0
fi
done
done
RETVAL=1
fi
return ${RETVAL}
}
################################################################################
# Name : ParseNginx()
# Description : Parse nginx configuration lines
# Input : $1 = file (should be readable and tested upfront)
# Returns : <nothing>
################################################################################
ParseNginx() {
COUNT=0
BREADCRUMB=""
if [ $# -eq 0 ]; then ExitFatal "No arguments provided to ParseNginx()"; fi
CONFIG_FILE=$1
# Create temporary files
CreateTempFile || ExitFatal "Could not create temporary file"
TMP_NGINX_FILE_RAW="${TEMP_FILE}"
CreateTempFile || ExitFatal "Could not create temporary file"
TMP_NGINX_FILE="${TEMP_FILE}"
# Strip out spaces, tabs and line breaks
awk '{$1=$1;print $0}' ${CONFIG_FILE} > ${TMP_NGINX_FILE_RAW}
# Now clean up the file further (combine lines, remove commented lines and empty lines)
cat ${TMP_NGINX_FILE_RAW} | sed 's#\\$##g' | grep -v "^#" | grep -v "^$" > ${TMP_NGINX_FILE}
LogText "Action: parsing configuration file ${CONFIG_FILE}"
COUNT=$(( COUNT + 1))
FIND=$(cat ${TMP_NGINX_FILE} | sed 's/ /:space:/g')
DEPTH=0
for I in ${FIND}; do
I=$(echo ${I} | sed 's/:space:/ /g' | sed 's/;$//' | sed 's/ #.*$//')
OPTION=$(echo ${I} | awk '{ print $1 }')
VALUE=$(echo ${I}| cut -d' ' -f2-)
LogText "Result: found option ${OPTION} in ${CONFIG_FILE} with value '${VALUE}'"
STORE_SETTING=1
case ${OPTION} in
"events")
BREADCRUMB="${BREADCRUMB}/events"
DEPTH=$(( DEPTH + 1))
STORE_SETTING=0
NGINX_EVENTS_COUNTER=$(( NGINX_EVENTS_COUNTER + 1 ))
;;
"http")
BREADCRUMB="${BREADCRUMB}/http"
DEPTH=$(( DEPTH + 1))
STORE_SETTING=0
NGINX_HTTP_COUNTER=$(( NGINX_HTTP_COUNTER + 1 ))
;;
"location")
BREADCRUMB="${BREADCRUMB}/location"
DEPTH=$(( DEPTH + 1))
STORE_SETTING=0
NGINX_LOCATION_COUNTER=$(( NGINX_LOCATION_COUNTER + 1 ))
;;
"server")
BREADCRUMB="${BREADCRUMB}/server"
DEPTH=$(( DEPTH + 1))
STORE_SETTING=0
NGINX_SERVER_COUNTER=$(( NGINX_SERVER_COUNTER + 1 ))
;;
"}")
BREADCRUMB=$(echo ${BREADCRUMB} | awk -F/ 'sub(FS $NF,x)')
DEPTH=$(( DEPTH - 1))
STORE_SETTING=0
;;
access_log)
if [ "${VALUE}" = "off" ]; then
LogText "Result: found logging disabled for one virtual host"
NGINX_ACCESS_LOG_DISABLED=1
else
if [ ! "${VALUE}" = "" ]; then
# If multiple values follow, select first one
VALUE=$(echo ${VALUE} | awk '{ print $1 }')
if [ ! -f ${VALUE} ]; then
LogText "Result: could not find referenced log file ${VALUE} in nginx configuration"
NGINX_ACCESS_LOG_MISSING=1
fi
fi
fi
;;
# Headers
add_header)
HEADER=$(echo ${VALUE} | awk '{ print $1 }')
HEADER_VALUE=$(echo ${VALUE} | cut -d' ' -f2-)
LogText "Result: found header ${HEADER} with value ${HEADER_VALUE}"
#Report "nginx_header[]=${HEADER}|${HEADER_VALUE}|"
;;
alias)
NGINX_ALIAS_FOUND=1
;;
allow)
NGINX_ALLOW_FOUND=1
;;
autoindex)
;;
deny)
NGINX_DENY_FOUND=1
;;
expires)
NGINX_EXPIRES_FOUND=1
;;
error_log)
# Check if debug is appended
FIND=$(echo ${VALUE} | awk '{ if ($2=="debug") { print 1 } else { print 0 }}')
if [ ${FIND} -eq 1 ]; then
NGINX_ERROR_LOG_DEBUG=1
fi
# Check if log file exists
FILE=$(echo ${VALUE} | awk '{ print $1 }')
if [ ! "${FILE}" = "" ]; then
if [ ! -f ${FILE} ]; then
NGINX_ERROR_LOG_MISSING=1
fi
else
LogText "Warning: did not find a filename after error_log in nginx configuration"
fi
;;
error_page)
;;
fastcgi_intercept_errors)
;;
fastcgi_param)
NGINX_FASTCGI_FOUND=1
NGINX_FASTCGI_PARAMS_FOUND=1
;;
fastcgi_pass)
NGINX_FASTCGI_FOUND=1
NGINX_FASTCGI_PASS_FOUND=1
;;
fastcgi_pass_header)
;;
include)
if [ -f "${VALUE}" ]; then
FOUND=0
for CONF in ${NGINX_CONF_FILES}; do
if [ "${CONF}" = "${VALUE}" ]; then FOUND=1; LogText "Found this file already in our configuration files array, not adding to queue"; fi
done
for CONF in ${NGINX_CONF_FILES_ADDITIONS}; do
if [ "${CONF}" = "${VALUE}" ]; then FOUND=1; LogText "Found this file already in our configuration files array (additions), not adding to queue"; fi
done
if [ ${FOUND} -eq 0 ]; then NGINX_CONF_FILES_ADDITIONS="${NGINX_CONF_FILES_ADDITIONS} ${VALUE}"; fi
else
LogText "Result: this include does not point to a file"
fi
;;
index)
;;
keepalive_timeout)
;;
listen)
NGINX_LISTEN_FOUND=1
# Test for ssl on listen statement
FIND_SSL=$(echo ${VALUE} | grep ssl)
if [ ! "${FIND_SSL}" = "" ]; then NGINX_SSL_ON=1; fi
;;
location)
NGINX_LOCATION_FOUND=1
;;
return)
NGINX_RETURN_FOUND=1
;;
root)
NGINX_ROOT_FOUND=1
;;
server_name)
;;
ssl)
if [ "${VALUE}" = "on" ]; then NGINX_SSL_ON=1; fi
;;
ssl_certificate)
LogText "Found SSL certificate in nginx configuration"
;;
ssl_certificate_key)
;;
ssl_ciphers)
NGINX_SSL_CIPHERS=1
;;
ssl_prefer_server_ciphers)
if [ "${VALUE}" = "on" ]; then NGINX_SSL_PREFER_SERVER_CIPHERS=1; fi
;;
ssl_protocols)
NGINX_SSL_PROTOCOLS=1
VALUE=$(echo ${VALUE} | sed 's/;$//' | tr '[:upper:]' '[:lower:]')
for ITEM in ${VALUE}; do
LogText "Result: found protocol ${ITEM}"
case ${ITEM} in
"sslv2" | "sslv3")
NGINX_WEAK_SSL_PROTOCOL_FOUND=1
;;
esac
Report "ssl_tls_protocol_enabled[]=${ITEM}"
ReportDetails --service nginx --field protocol --value "${ITEM}"
done
;;
ssl_session_cache)
;;
ssl_session_timeout)
;;
types)
;;
*)
LogText "Found unknown option ${OPTION} in nginx configuration"
;;
esac
if [ ${STORE_SETTING} -eq 1 ]; then
CONFIG_TREE="${BREADCRUMB}"
if [ -z "${CONFIG_TREE}" ]; then CONFIG_TREE="/"; fi
if [ -z "${OPTION}" ]; then OPTION="NA"; fi
if [ -z "${VALUE}" ]; then VALUE="NA"; fi
StoreNginxSettings --config ${CONFIG_FILE} --tree ${CONFIG_TREE} --depth ${DEPTH} --setting ${OPTION} --value "${VALUE}"
fi
done
}
################################################################################
# Name : PortIsListening()
# Description : Check if machine is listening on specified protocol and port
# Returns : exit code 0 (listening) or 1 (not listening)
# Usage : if PortIsListening "TCP" 22; then echo "Port is listening"; fi
################################################################################
PortIsListening() {
if [ "${LSOFBINARY}" = "" ]; then
return 255
else
if [ $# -eq 2 ] && [ $1 = "TCP" -o $1 = "UDP" ]; then
LogText "Test: find service listening on $1:$2"
if [ $1 = "TCP" ]; then FIND=$(${LSOFBINARY} -i${1} -s${1}:LISTEN -P -n | grep ":${2} "); else FIND=$(${LSOFBINARY} -i${1} -P -n | grep ":${2} "); fi
if [ ! "${FIND}" = "" ]; then
LogText "Result: found service listening on port $2 ($1)"
return 0
else
LogText "Result: did not find service listening on port $2 ($1)"
return 1
fi
else
return 255
ReportException ${TEST_NO} "Error in function call to PortIsListening"
fi
fi
}
################################################################################
# Name : Progress()
# Description : Displays progress on screen with dots
#
# Input : --finish or text (string)
# Returns : <nothing>
# Tip : Use this function from Register with the --progress parameter
################################################################################
Progress() {
if [ ${CRONJOB} -eq 0 ]; then
if [ ${QUIET} -eq 0 ]; then
if [ "$1" = "--finish" ]; then
${ECHOCMD} ""
else
# If the No-Break version of echo is known, use that (usually breaks in combination with -e)
if [ ! "${ECHONB}" = "" ]; then
${ECHONB} "$1"
else
${ECHOCMD} -en "$1"
fi
fi
fi
fi
}
################################################################################
# Name : RandomString()
# Description : Displays progress on screen with dots
# Input : Amount of characters (optional)
# Returns : RANDOMSTRING
# Usage : RandomString 32
################################################################################
RandomString() {
# Check a (pseudo) random character device
if [ -c /dev/urandom ]; then FILE="/dev/urandom"
elif [ -c /dev/random ]; then FILE="/dev/random"
else
Display "Can not use RandomString function, as there is no random device to be used"
fi
if [ $# -eq 0 ]; then SIZE=16; else SIZE=$1; fi
CSIZE=$((SIZE / 2))
RANDOMSTRING=$(head -c ${CSIZE} /dev/urandom | od -An -x | tr -d ' ' | cut -c 1-${SIZE})
}
################################################################################
# Name : Register()
# Description : Register a test and see if it has to be run
# Returns : SKIPTEST (0 or 1)
################################################################################
Register() {
# Do not insert a log break, if previous test was not logged
if [ ${SKIPLOGTEST} -eq 0 ]; then LogTextBreak; fi
ROOT_ONLY=0; SKIPTEST=0; SKIPLOGTEST=0; SKIPREASON=""; TEST_NEED_OS=""; PREQS_MET=""
TEST_CATEGORY=""; TEST_NEED_NETWORK=""; TEST_NEED_PLATFORM=""
TOTAL_TESTS=$((TOTAL_TESTS + 1))
while [ $# -ge 1 ]; do
case $1 in
--category)
shift
TEST_CATEGORY=$1
;;
--description)
shift
TEST_DESCRIPTION=$1
;;
--platform)
shift
TEST_NEED_PLATFORM=$1
;;
--network)
shift
TEST_NEED_NETWORK=$1
;;
--os)
shift
TEST_NEED_OS=$1
;;
--preqs-met)
shift
PREQS_MET=$1
;;
--progress)
Progress "."
;;
--root-only)
shift
if [ "$1" = "YES" -o "$1" = "yes" ]; then
ROOT_ONLY=1
elif [ "$1" = "NO" -o "$1" = "no" ]; then
ROOT_ONLY=0
else
Debug "Invalid option for --root-only parameter of Register function"
fi
;;
--skip-reason)
shift
SKIPREASON="$1"
;;
--test-no)
shift
TEST_NO=$1
;;
--weight)
shift
TEST_WEIGHT=$1
;;
*)
echo "INVALID OPTION (Register): $1"
exit 1
;;
esac
# Go to next parameter
shift
done
# Skip test if it's configured in profile (old style)
if [ ${SKIPTEST} -eq 0 ]; then
FIND=$(echo "${TEST_SKIP_ALWAYS}" | grep "${TEST_NO}" | tr '[:lower:]' '[:upper:]')
if [ ! "${FIND}" = "" ]; then SKIPTEST=1; SKIPREASON="Skipped by configuration"; fi
fi
# Check if this test is on the list to skip
if [ ${SKIPTEST} -eq 0 ]; then
VALUE=$(echo ${TEST_NO} | tr '[:lower:]' '[:upper:]')
for I in ${SKIP_TESTS}; do
if [ "${I}" = "${VALUE}" ]; then SKIPTEST=1; SKIPREASON="Skipped by profile setting (skip-test)"; fi
done
fi
# Skip if test is not in the list
if [ ${SKIPTEST} -eq 0 -a ! "${TESTS_TO_PERFORM}" = "" ]; then
FIND=$(echo "${TESTS_TO_PERFORM}" | grep "${TEST_NO}")
if [ "${FIND}" = "" ]; then SKIPTEST=1; SKIPREASON="Test not in list of tests to perform"; fi
fi
# Do not run scans which have a higher intensity than what we prefer
if [ ${SKIPTEST} -eq 0 -a "${TEST_WEIGHT}" = "H" -a "${SCAN_TEST_HEAVY}" = "NO" ]; then SKIPTEST=1; SKIPREASON="Test to system intensive for scan mode (H)"; fi
if [ ${SKIPTEST} -eq 0 -a "${TEST_WEIGHT}" = "M" -a "${SCAN_TEST_MEDIUM}" = "NO" ]; then SKIPTEST=1; SKIPREASON="Test to system intensive for scan mode (M)"; fi
# Skip test if OS is different than requested
if [ ${SKIPTEST} -eq 0 -a ! -z "${TEST_NEED_OS}" -a ! "${OS}" = "${TEST_NEED_OS}" ]; then
SKIPTEST=1; SKIPREASON="Incorrect guest OS (${TEST_NEED_OS} only)"
if [ ${LOG_INCORRECT_OS} -eq 0 ]; then
SKIPLOGTEST=1
fi
fi
# Skip test when it belongs to another category (default is 'all')
if [ ${SKIPTEST} -eq 0 -a ! -z "${TEST_CATEGORY_TO_CHECK}" -a ! "${TEST_CATEGORY_TO_CHECK}" = "all" -a ! "${TEST_CATEGORY}" = "${TEST_CATEGORY_TO_CHECK}" ]; then
SKIPTEST=1; SKIPREASON="Incorrect category (${TEST_CATEGORY_TO_CHECK} only)"
fi
# Check for correct hardware platform
if [ ${SKIPTEST} -eq 0 -a ! -z "${TEST_NEED_PLATFORM}" -a ! "${HARDWARE}" = "${TEST_NEED_PLATFORM}" ]; then SKIPTEST=1; SKIPREASON="Incorrect hardware platform"; fi
# Not all prerequisites met, like missing tool
if [ ${SKIPTEST} -eq 0 -a "${PREQS_MET}" = "NO" ]; then SKIPTEST=1; if [ -z "${SKIPREASON}" ]; then SKIPREASON="Prerequisities not met (ie missing tool, other type of Linux distribution)"; fi; fi
# Skip if a test is root only and we are running a non-privileged test
if [ ${SKIPTEST} -eq 0 -a ${ROOT_ONLY} -eq 1 -a ! ${MYID} = "0" ]; then
SKIPTEST=1; SKIPREASON="This test needs root permissions"
SKIPPED_TESTS_ROOTONLY="${SKIPPED_TESTS_ROOTONLY}====${TEST_NO}:space:-:space:${TEST_DESCRIPTION}"
#SkipTest "${TEST_NO}:Test:space:requires:space:root:space:permissions:-:-:"
fi
# Skip test?
if [ ${SKIPTEST} -eq 0 ]; then
# First wait X seconds (depending pause_between_tests)
if [ ${TEST_PAUSE_TIME} -gt 0 ]; then sleep ${TEST_PAUSE_TIME}; fi
# Increase counter for every registered test which is performed
CountTests
if [ ${SKIPLOGTEST} -eq 0 ]; then
LogText "Performing test ID ${TEST_NO} (${TEST_DESCRIPTION})"
if IsVerbose; then Debug "Performing test ID ${TEST_NO} (${TEST_DESCRIPTION})"; fi
fi
TESTS_EXECUTED="${TEST_NO}|${TESTS_EXECUTED}"
else
if [ ${SKIPLOGTEST} -eq 0 ]; then LogText "Skipped test ${TEST_NO} (${TEST_DESCRIPTION})"; fi
if [ ${SKIPLOGTEST} -eq 0 ]; then LogText "Reason to skip: ${SKIPREASON}"; fi
TESTS_SKIPPED="${TEST_NO}|${TESTS_SKIPPED}"
fi
unset SKIPREASON
}
################################################################################
# Name : RemoveColors()
# RemoveColors Clear color settings for --no-colors (see include/consts)
################################################################################
RemoveColors() {
COLORS=0 # disable most color selections
# Normal color names
CYAN=""
BLUE=""
BROWN=""
DARKGRAY=""
GRAY=""
GREEN=""
LIGHTBLUE=""
MAGENTA=""
PURPLE=""
RED=""
YELLOW=""
WHITE=""
# Colors with background
BG_BLUE=""
# Semantic names
BAD=""
BOLD=""
HEADER=""
NORMAL=""
NOTICE=""
OK=""
WARNING=""
SECTION=""
}
################################################################################
# Name : RemovePIDFile()
################################################################################
# Remove PID file
RemovePIDFile() {
# Test if PIDFILE is defined, before checking file presence
if [ ! "${PIDFILE}" = "" ]; then
if [ -f ${PIDFILE} ]; then
rm -f $PIDFILE;
LogText "PID file removed (${PIDFILE})"
else
LogText "PID file not found (${PIDFILE})"
fi
fi
}
################################################################################
# Name : RemoveTempFiles()
################################################################################
# Remove any temporary files
RemoveTempFiles() {
if [ ! "${TEMP_FILES}" = "" ]; then
LogText "Temporary files: ${TEMP_FILES}"
# Clean up temp files
for FILE in ${TEMP_FILES}; do
# Temporary files should be in /tmp
TMPFILE=$(echo ${FILE} | egrep "^/tmp/lynis" | grep -v "\.\.")
if [ ! "${TMPFILE}" = "" ]; then
if [ -f ${TMPFILE} ]; then
LogText "Action: removing temporary file ${TMPFILE}"
rm -f ${TMPFILE}
else
LogText "Info: temporary file ${TMPFILE} was already removed"
fi
else
LogText "Found invalid temporary file (${FILE}), not removed. Check your /tmp directory."
fi
done
else
LogText "No temporary files to be deleted"
fi
}
################################################################################
# Name : Report()
################################################################################
Report() {
if [ ${CREATE_REPORT_FILE} -eq 1 ]; then echo "$1" >> ${REPORTFILE}; fi
}
################################################################################
# Name : ReportDetails()
# Description : Adds specific details to the report, in particular when many
# smaller atomic tests are performed. For example sysctl keys,
# and SSH settings.
#
# Input : <multiple fields, see test>
# Returns : <nothing>
# Notes : Need to check for values (strip out semicolons from values)
# Only add fields which are filled in
################################################################################
ReportDetails() {
TEST_DESCRIPTION=""
TEST_FIELD=""
TEST_ID=""
TEST_OTHER=""
TEST_PREFERRED_VALUE=""
TEST_SERVICE=""
TEST_VALUE=""
while [ $# -ge 1 ]; do
case $1 in
--description)
shift
TEST_DESCRIPTION="desc:$1;"
;;
--field)
shift
TEST_FIELD="field:$1;"
;;
--preferredvalue|--preferred-value)
shift
TEST_PREFERRED_VALUE="prefval:$1;"
;;
# Other details
--other)
shift
TEST_OTHER="other:$1;"
;;
--service)
shift
TEST_SERVICE="$1"
;;
--test)
shift
TEST_ID=$1
;;
--value)
shift
TEST_VALUE="value:$1;"
;;
*)
echo "INVALID OPTION (ReportDetails): $1"
ExitFatal
;;
esac
shift # Go to next parameter
done
if [ "${TEST_ID}" = "" ]; then TEST_ID="-"; fi
if [ "${TEST_SERVICE}" = "" ]; then TEST_SERVICE="-"; fi
Report "details[]=${TEST_ID}|${TEST_SERVICE}|${TEST_DESCRIPTION}${TEST_FIELD}${TEST_PREFERRED_VALUE}${TEST_VALUE}${TEST_OTHER}|"
}
################################################################################
# Name : ReportException()
################################################################################
# Log exceptions
ReportException() {
# 1 parameters
# <ID>:<2 char numeric>|text|
Report "exception_event[]=$1|$2|"
LogText "Exception: test has an exceptional event ($1) with text $2"
}
################################################################################
# Name : ReportManual()
################################################################################
# Log manual actions to report file
ReportManual() {
# 1 parameter: Text
Report "manual_event[]=$1"
LogText "Manual: one or more manual actions are required for further testing of this control/plugin"
}
################################################################################
# Name : ReportSuggestion()
################################################################################
# Log suggestions to report file
ReportSuggestion() {
TOTAL_SUGGESTIONS=$((TOTAL_SUGGESTIONS + 1))
# 4 parameters
# <ID> <Suggestion> <Details> <Solution>
# <ID> Lynis ID (use CUST-.... for your own tests)
# <Suggestion> Suggestion text to be displayed
# <Details> Specific item or details
# <Solution> Optional link for additional information:
# * url:http://site/link
# * text:Additional explanation
# * - for none
if [ $# -eq 0 ]; then echo "Not enough arguments provided for function ReportSuggestion"; ExitFatal; fi
if [ $# -ge 1 ]; then TEST="$1"; else TEST="UNKNOWN"; fi
if [ $# -ge 2 ]; then MESSAGE="$2"; else MESSAGE="UNKNOWN"; fi
if [ $# -ge 3 ]; then DETAILS="$3"; else DETAILS="-"; fi
if [ $# -ge 4 ]; then SOLUTION="$4"; else SOLUTION="-"; fi
if [ $# -ge 5 ]; then echo "Too many arguments for function ReportSuggestion"; ExitFatal; fi
Report "suggestion[]=${TEST}|${MESSAGE}|${DETAILS}|${SOLUTION}|"
LogText "Suggestion: ${MESSAGE} [test:${TEST}] [details:${DETAILS}] [solution:${SOLUTION}]"
}
################################################################################
# Name : ReportWarning()
################################################################################
# Log warning to report file
ReportWarning() {
TOTAL_WARNINGS=$((TOTAL_WARNINGS + 1))
# Old style
# <ID> <priority/impact> <warning text>
if [ "$2" = "L" -o "$2" = "M" -o "$2" = "H" ]; then
if [ $# -ge 1 ]; then TEST="$1"; fi
if [ $# -ge 2 ]; then DETAILS="$2"; fi
if [ $# -ge 3 ]; then MESSAGE="$3"; fi
SOLUTION="-"
else
# New style warning format:
# <ID> <Warning> <Details> <Solution>
#
# <ID> Lynis ID (use CUST-.... for your own tests)
# <Warning> Warning text to be displayed
# <Details> Specific item or details
# <Solution> Optional link for additional information:
# * url:http://site/link
# * text:Additional explanation
# * - for none
if [ $# -eq 0 ]; then echo "Not enough arguments provided for function ReportWarning"; ExitFatal; fi
if [ $# -ge 1 ]; then TEST="$1"; else TEST="UNKNOWN"; fi
if [ $# -ge 2 ]; then MESSAGE="$2"; else MESSAGE="UNKNOWN"; fi
if [ $# -ge 3 ]; then DETAILS="$3"; else DETAILS="-"; fi
if [ $# -ge 4 ]; then SOLUTION="$4"; else SOLUTION="-"; fi
if [ $# -ge 5 ]; then echo "Too many arguments for function ReportWarning"; ExitFatal; fi
fi
Report "warning[]=${TEST}|${MESSAGE}|${DETAILS}|${SOLUTION}|"
LogText "Warning: ${MESSAGE} [test:${TEST}] [details:${DETAILS}] [solution:${SOLUTION}]"
}
################################################################################
# Name : SafePerms()
# Return : 0 (file OK) or break
################################################################################
SafePerms() {
if [ ${WARN_ON_FILE_ISSUES} -eq 1 ]; then
PERMS_OK=0
LogText "Checking permissions of $1"
if [ $# -eq 1 ]; then
IS_PARAMETERS_FILE=$(echo $1 | grep "/parameters")
# Check file permissions
if [ ! -f "$1" ]; then
LogText "Fatal error: file $1 does not exist. Quitting."
echo "Fatal error: file $1 does not exist"
ExitFatal
else
PERMS=$(ls -l $1)
# Owner permissions
OWNER=$(echo ${PERMS} | awk -F" " '{ print $3 }')
OWNERID=$(ls -n $1 | awk -F" " '{ print $3 }')
if [ ${PENTESTINGMODE} -eq 0 -a "${IS_PARAMETERS_FILE}" = "" ]; then
if [ ! "${OWNER}" = "root" -a ! "${OWNERID}" = "0" ]; then
echo "Fatal error: file $1 should be owned by user 'root' when running it as root (found: ${OWNER})."
ExitFatal
fi
fi
# Group permissions
GROUP=$(echo ${PERMS} | awk -F" " '{ print $4 }')
GROUPID=$(ls -n $1 | awk -F" " '{ print $4 }')
if [ ${PENTESTINGMODE} -eq 0 -a "${IS_PARAMETERS_FILE}" = "" ]; then
if [ ! "${GROUP}" = "root" -a ! "${GROUP}" = "wheel" -a ! "${GROUPID}" = "0" ]; then
echo "Fatal error: group owner of directory $1 should be owned by root user, wheel or similar (found: ${GROUP})."
ExitFatal
fi
fi
# Owner permissions
OWNER_PERMS=$(echo ${PERMS} | cut -c2-4)
if [ ! "${OWNER_PERMS}" = "rw-" -a ! "${OWNER_PERMS}" = "r--" ]; then
echo "Fatal error: permissions of file $1 are not strict enough. Access to 'owner' should be read-write, or read. Change with: chmod 600 $1"
ExitFatal
fi
# Owner permissions
GROUP_PERMS=$(echo ${PERMS} | cut -c5-7)
if [ ! "${GROUP_PERMS}" = "rw-" -a ! "${GROUP_PERMS}" = "r--" -a ! "${GROUP_PERMS}" = "---" ]; then
echo "Fatal error: permissions of file $1 are not strict enough. Access to 'group' should be read-write, read, or none. Change with: chmod 600 $1"
ExitFatal
fi
# Other permissions
OTHER_PERMS=$(echo ${PERMS} | cut -c8-10)
if [ ! "${OTHER_PERMS}" = "---" -a ! "${OTHER_PERMS}" = "r--" ]; then
echo "Fatal error: permissions of file $1 are not strict enough. Access to 'other' should be denied or read-only. Change with: chmod 600 $1"
ExitFatal
fi
# Set PERMS_OK to 1 if no fatal errors occurred
PERMS_OK=1
LogText "File permissions are OK"
return 0
fi
else
ReportException "SafePerms()" "Invalid number of arguments for function"
fi
else
PERMS_OK=1
return 0
fi
}
################################################################################
# Name : SearchItem()
# Description : Search if a specific string exists in in a file
#
# Input : $1 = search key (string), $2 = file (string), $3 and later
# are optional arguments
# Returns : True (0) or False (1)
################################################################################
SearchItem() {
PERFORM_SCAN=0
ITEM_FOUND=0
MASK_LOG=0
RETVAL=1
if [ $# -lt 2 ]; then
ExitFatal "Not enough arguments for function SearchItem()"
elif [ $# -ge 2 ]; then
FILE=$2
STRING=$1
PERFORM_SCAN=1
fi
# Parse any additional arguments
if [ $# -gt 2 ]; then
shift; shift # Skip the first two (string and file)
while [ $# -ge 1 ]; do
case $1 in
"--sensitive") MASK_LOG=1 ;;
esac
shift # Go to next parameter
done
fi
if [ ${PERFORM_SCAN} -eq 1 ]; then
# Don't search in /dev/null, it's too empty there
if [ -f ${FILE} ]; then
# Check if we can find the main type (with or without brackets)
LogText "Test: search string ${STRING} in file ${FILE}"
FIND=$(egrep "${STRING}" ${FILE})
if [ ! "${FIND}" = "" ]; then
ITEM_FOUND=1
LogText "Result: found search string '${STRING}'"
if [ ${MASK_LOG} -eq 0 ]; then LogText "Full string returned: ${FIND}"; fi
RETVAL=0
else
LogText "Result: search search string '${STRING}' NOT found"
RETVAL=1
fi
else
LogText "Skipping search, file (${FILE}) does not exist"
ReportException "${TEST_NO}" "Test is trying to search for a string in nonexistent file"
fi
else
ReportException "${TEST_NO}" "Search test is skipped, which is unexpected"
fi
return ${RETVAL}
}
# Show result code (to be removed)
ShowResult() {
case $1 in
OK)
echo "[ ${OK}OK${NORMAL} ]"
;;
WARNING)
echo "[ ${WARNING}WARNING${NORMAL} ]"
# log the warning to our log file
#LogText "Warning: $2"
# add the warning to our report file
#Report "warning=$2"
;;
esac
}
################################################################################
# Name : ShowComplianceFinding()
# Description : Display a section of a compliance standard which is not fulfilled
#
# Input : <several parameters, see test>
# Returns : <nothing>
################################################################################
ShowComplianceFinding() {
REASON=""
STANDARD_NAME=""
STANDARD_VERSION=""
STANDARD_SECTION=""
STANDARD_SECTION_TITLE=""
ACTUAL_VALUE=""
EXPECTED_VALUE=""
while [ $# -ge 1 ]; do
case $1 in
--standard)
shift
STANDARD_NAME=$1
;;
--version)
shift
STANDARD_VERSION=$1
;;
--section)
shift
STANDARD_SECTION=$1
;;
--section-title)
shift
STANDARD_SECTION_TITLE=$1
;;
--reason)
shift
REASON=$1
;;
--actual)
shift
ACTUAL_VALUE=$1
;;
--expected)
shift
EXPECTED_VALUE=$1
;;
*)
echo "INVALID OPTION (ShowComplianceFinding): $1"
exit 1
;;
esac
# Go to next parameter
shift
done
# Should we show this non-compliance on screen?
SHOW=0
case ${STANDARD_NAME} in
cis)
if [ ${COMPLIANCE_ENABLE_CIS} -eq 1 ]; then SHOW=1; fi
STANDARD_FRIENDLY_NAME="CIS"
;;
hipaa)
if [ ${COMPLIANCE_ENABLE_HIPAA} -eq 1 ]; then SHOW=1; fi
STANDARD_FRIENDLY_NAME="HIPAA"
;;
iso27001)
if [ ${COMPLIANCE_ENABLE_ISO27001} -eq 1 ]; then SHOW=1; fi
STANDARD_FRIENDLY_NAME="ISO27001"
;;
pci-dss)
if [ ${COMPLIANCE_ENABLE_PCI_DSS} -eq 1 ]; then SHOW=1; fi
STANDARD_FRIENDLY_NAME="PCI DSS"
;;
esac
# Only display if standard is enabled in the profile and mark system as non-compliant
if [ ${SHOW} -eq 1 ]; then
COMPLIANCE_FINDINGS_FOUND=1
DisplayManual " [${WHITE}${STANDARD_FRIENDLY_NAME} ${STANDARD_VERSION}${NORMAL}] - ${CYAN}Section ${STANDARD_SECTION}${NORMAL} - ${WHITE}${STANDARD_SECTION_TITLE}${NORMAL}"
DisplayManual " - Details: ${REASON}"
DisplayManual " - Configuration: ${RED}${ACTUAL_VALUE}${NORMAL} / ${EXPECTED_VALUE}"
DisplayManual ""
fi
}
################################################################################
# Name : ShowSymlinkPath()
# Description : Check if we can find the path behind a symlink
#
# Input : $1 = file (string)
# Returns : FOUNDPATH (0 not found, 1 found path), SYMLINK (new path)
################################################################################
ShowSymlinkPath() {
if [ $# -eq 0 ]; then ExitFatal "Function ShowSymlinkPath() called without a file name"; fi
sFILE=$1
FOUNDPATH=0
SYMLINK_USE_PYTHON=0
SYMLINK_USE_READLINK=0
LogText "Action: checking symlink for file ${sFILE}"
# Check for symlink
if [ -L ${sFILE} ]; then
# macOS does not know -f option, nor do some others
if [ "${OS}" = "macOS" ]; then
# If a Python binary is found, use the one in path
if [ ${BINARY_SCAN_FINISHED} -eq 0 -a "${PYTHONBINARY}" = "" ]; then
FIND=$(which python 2> /dev/null)
if [ ! "${FIND}" = "" ]; then LogText "Setting temporary pythonbinary variable"; PYTHONBINARY="${FIND}"; fi
fi
if [ ! "${PYTHONBINARY}" = "" ]; then
SYMLINK_USE_PYTHON=1
LogText "Note: using Python to determine symlinks"
tFILE=$(python -c "import os,sys; print(os.path.realpath(os.path.expanduser(sys.argv[1])))" $1)
fi
else
if [ ${BINARY_SCAN_FINISHED} -eq 0 -a "${READLINKBINARY}" = "" ]; then
FIND=$(which readlink 2> /dev/null)
if [ ! "${FIND}" = "" ]; then LogText "Setting temporary readlinkbinary variable"; READLINKBINARY="${FIND}"; fi
fi
if [ ! "${READLINKBINARY}" = "" ]; then
SYMLINK_USE_READLINK=1
LogText "Note: Using real readlink binary to determine symlink on ${sFILE}"
tFILE=$(${READLINKBINARY} -f ${sFILE})
LogText "Result: readlink shows ${tFILE} as output"
fi
fi
# Check if we can find the file now
if [ "${tFILE}" = "" ]; then
LogText "Result: command did not return any value"
elif [ -f ${tFILE} ]; then
sFILE="${tFILE}"
LogText "Result: symlink found, pointing to file ${sFILE}"
FOUNDPATH=1
elif [ -b ${tFILE} ]; then
sFILE="${tFILE}"
LogText "Result: symlink found, pointing to block device ${sFILE}"
FOUNDPATH=1
elif [ -c ${tFILE} ]; then
sFILE="${tFILE}"
LogText "Result: symlink found, pointing to character device ${sFILE}"
FOUNDPATH=1
elif [ -d ${tFILE} ]; then
sFILE="${tFILE}"
LogText "Result: symlink found, pointing to directory ${sFILE}"
FOUNDPATH=1
else
# Check the full path of the symlink, strip the filename, copy the path and linked filename together
tDIR=$(echo ${sFILE} | awk '{match($1, "^.*/"); print substr($1, 1, RLENGTH-1)}')
tFILE="${tDIR}/${tFILE}"
if [ -L ${tFILE} ]; then
LogText "Result: this symlink links to another symlink"
# Ensure that we use a second try with the right tool as well
if [ ${SYMLINK_USE_PYTHON} -eq 1 ]; then
tFILE=$(python -c "import os,sys; print(os.path.realpath(os.path.expanduser(sys.argv[1])))" ${tFILE})
elif [ ${SYMLINK_USE_READLINK} -eq 1 ]; then
tFILE=$(${READLINKBINARY} -f ${tFILE})
fi
# Check if we now have a normal file
if [ -f ${tFILE} ]; then
sFILE="${tFILE}"
LogText "Result: symlink finally found, seems to be file ${sFILE}"
FOUNDPATH=1
elif [ -d ${tFILE} ]; then
sFILE="${tFILE}"
LogText "Result: symlink finally found, seems to be directory ${sFILE}"
FOUNDPATH=1
else
LogText "Result: could not find file ${tFILE}, most likely too complicated symlink or too often linked"
fi
elif [ -f ${tFILE} ]; then
sFILE="${tFILE}"
LogText "Result: symlink found, seems to be file ${sFILE}"
FOUNDPATH=1
elif [ -d ${tFILE} ]; then
sFILE="${tFILE}"
LogText "Result: symlink found, seems to be directory ${sFILE}"
FOUNDPATH=1
else
LogText "Result: file ${tFILE} in ${tDIR} not found"
fi
fi
else
LogText "Result: file ${sFILE} is not a symlink"
fi
# Now check if our new location is actually a file or directory destination
if [ -L ${sFILE} ]; then
LogText "Result: unable to determine symlink, or location ${sFILE} is just another symlink"
FOUNDPATH=0
fi
if [ ${FOUNDPATH} -eq 1 ]; then
SYMLINK="${sFILE}"
else
SYMLINK=""
fi
}
################################################################################
# Name : SkipAtomicTest()
# Description : Test if an atomic test should be skipped
#
# Input : String (particular test)
# Returns : 0 (skip test) or 1 (do not skip test)
# Usage : if SkipAtomicTest "ssh-7408:port"; then echo "Skip this atomic test"; fi
################################################################################
SkipAtomicTest() {
RETVAL=255
if [ $# -eq 1 ]; then
STRING=""
RETVAL=1
# Check if this test is on the list to skip
for I in ${SKIP_TESTS}; do
STRING=$(echo $1 | tr '[:lower:]' '[:upper:]')
if [ "${I}" = "${STRING}" ]; then RETVAL=0; LogText "Atomic test ($1) skipped by configuration (skip-test)"; fi
done
else
ReportException "SkipAtomicTest()" "Function called without right number of arguments (1)"
fi
return $RETVAL
}
################################################################################
# Name : StoreNginxSettings()
# Description : Store parsed settings from nginx (by ParseNginx)
# Input : multiple options
# Returns : <nothing>
################################################################################
StoreNginxSettings() {
CONFIG_DEPTH=0; CONFIG_FILE=""; CONFIG_SETTING=""; CONFIG_TREE=""; CONFIG_VALUE=""
while [ $# -ge 1 ]; do
case $1 in
--config)
shift
CONFIG_FILE=$1
;;
--depth)
shift
CONFIG_DEPTH=$1
;;
# none | events | server | unknown
--tree)
shift
CONFIG_TREE=$1
case ${CONFIG_TREE} in
"/") CONFIG_COUNTER=0 ;;
"/events") CONFIG_COUNTER=${NGINX_EVENTS_COUNTER=0} ;;
"/http") CONFIG_COUNTER=${NGINX_HTTP_COUNTER=0} ;;
"/server") CONFIG_COUNTER=${NGINX_SERVER_COUNTER=0} ;;
"/server/location") CONFIG_COUNTER=${NGINX_LOCATION_COUNTER=0} ;;
*)
Debug "Unknown configuration tree of nginx ${CONFIG_TREE}"
;;
esac
;;
--setting)
shift
CONFIG_SETTING=$1
;;
--value)
shift
CONFIG_VALUE=$1
;;
*)
echo "INVALID OPTION (StoreNginxSettings): $1 $2"
#ExitFatal
;;
esac
# Go to next parameter
shift
done
if [ -z "${CONFIG_DEPTH}" ]; then CONFIG_DEPTH="0"; fi
if [ -z "${CONFIG_SETTING}" ]; then CONFIG_SETTING="NA"; fi
if [ -z "${CONFIG_TREE}" ]; then CONFIG_TREE="/"; fi
if [ -z "${CONFIG_VALUE}" ]; then CONFIG_VALUE="NA"; fi
Report "nginx_config[]=|file=${CONFIG_FILE}|depth=${CONFIG_DEPTH}|tree=${CONFIG_TREE}|number=${CONFIG_COUNTER}|setting=${CONFIG_SETTING}|value=${CONFIG_VALUE}|"
}
################################################################################
# Name : TestValue()
# Description : Test if a value is good/bad (e.g. according to best practices)
#
# Input : --function <value> --value <value> --search <value>
# Returns : 0 (True) or 1 (False)
# Usage : if TestValue --function contains --value "Full Text" --search "Text"; then echo "Found!"; fi
################################################################################
TestValue() {
RETVAL=255
FIND=""
VALUE=""
RESULT=""
SEARCH=""
CMP1=""; CMP2=""
while [ $# -ge 1 ]; do
case $1 in
--function)
shift
FUNCTION=$1
;;
--value)
shift
VALUE=$1
;;
--search)
shift
SEARCH=$1
;;
*)
echo "INVALID OPTION USED (TestValue): $1"
exit 1
;;
esac
# Go to next parameter
shift
done
if [ "${VALUE}" = "" ]; then echo "No value provided to function (TestValue)"; fi
# Apply the related function
case ${FUNCTION} in
"contains")
FIND=$(echo ${VALUE} | egrep "${SEARCH}")
if [ "${FIND}" = "" ]; then RETVAL=1; else RETVAL=0; fi
;;
#"gt" | "greater-than") COLOR=$GREEN ;;
"equals")
CMP1=$(echo ${SEARCH} | tr '[:upper:]' '[:lower:'])
CMP2=$(echo ${VALUE} | tr '[:upper:]' '[:lower:'])
if [ "${CMP1}" = "${CMP2}" ]; then RETVAL=0; else RETVAL=1; fi
;;
#"not-equal") COLOR=$WHITE ;;
#"lt" | "less-than") COLOR=$YELLOW ;;
*) echo "INVALID OPTION USED (TestValue, parameter of function: $1)"; exit 1 ;;
esac
if [ "${RETVAL}" -lt 2 ]; then
return ${RESULT}
else
Fatal "ERROR: No result returned from function (TestValue). Incorrect usage?"
#ExitFatal
fi
}
################################################################################
# Name : ViewCategories()
# Description : Show all available categories
#
# Input : <nothing>
# Returns : <nothing>
# Usage : ViewCategories
################################################################################
ViewCategories() {
for CATEGORY in ${TEST_AVAILABLE_CATEGORIES}; do echo "${CATEGORY}"; done
echo ""
ExitClean
}
################################################################################
# Name : ViewGroups()
# Description : Show what group of tests are available
#
# Input : <nothing>
# Returns : <nothing>
# Usage : ViewGroups
################################################################################
ViewGroups() {
if [ ! "${INCLUDEDIR}" = "" ]; then
InsertSection "Available test groups"
for I in $(ls ${INCLUDEDIR}/tests_* | xargs -n 1 basename | sed 's/tests_//' | grep -v "custom.template"); do
echo "${I}"
done
fi
echo ""
ExitClean
}
################################################################################
# Name : WaitForKeyPress()
# Description : Wait for the user to press [ENTER], unless quickmode is set
#
# Input : <nothing>
# Returns : <nothing>
# Usage : WaitForKeyPress
################################################################################
WaitForKeyPress() {
if [ ${QUICKMODE} -eq 0 ]; then
echo ""; echo "[ Press [ENTER] to continue, or [CTRL]+C to stop ]"
read void
fi
}
################################################################################
# Name : TestCase_Equal()
# Description : Test case for checking if value whether value is equal to value
# Returns : (0 - SUCCESS; 1 - MEDIUM-VALUED; 2 - FAIL)
################################################################################
TestCase_Equal() {
RETVAL=2
if [ "$#" -lt "2" ]; then
ReportException "${TEST_NO}" "Error in function call to ${FUNCNAME}"
else
LogText "${FUNCNAME}: checking value for application ${APP}"
LogText "${FUNCNAME}: ${OPTION} is set to ${1}"
LogText "${FUNCNAME}: check if ${1} is equal to ${2}"
if [ "$1" == "$2" ]; then
LogText "${FUNCNAME}: ${1} is equal to ${2}"
RETVAL=0
fi
if ! [ -z ${3+x} ]; then
LogText "${FUNCNAME}: ${1} is equal to ${3}"
if [ "$2" == "$3" ]; then
LogText "${FUNCNAME}: ${OPTION} is equal to ${3}"
RETVAL=1
fi
fi
fi
return ${RETVAL}
}
################################################################################
# Name : TestCase_ValueNotEqual()
# Description : Test case for checking if value is not equal to something
# Returns : (0 - SUCCESS; 1 - FAIL)
################################################################################
TestCase_NotEqual() {
RETVAL=1
if [ "$#" -ne "2" ]; then
ReportException "${TEST_NO}" "Error in function call to ${FUNCNAME}"
else
LogText "${FUNCNAME}: checking value for application ${APP}"
LogText "${FUNCNAME}: ${OPTION} is set to ${1}"
if [ "$1" != "$2" ]; then
LogText "${FUNCNAME}: ${1} is not equal to ${2}"
RETVAL=0
else
LogText "${FUNCNAME}: ${1} is equal to ${2}"
fi
fi
return ${RETVAL}
}
################################################################################
# Name : TestCase_GreaterThan()
# Description : Test case for checking if value is greater than something
# Returns : (0 - SUCCESS; 1 - FAIL)
################################################################################
TestCase_GreaterThan() {
RETVAL=1
if [ "$#" -ne "2" ]; then
ReportException "${TEST_NO}" "Error in function call to ${FUNCNAME}"
else
LogText "${FUNCNAME}: checking value for application ${APP}"
LogText "${FUNCNAME}: ${OPTION} is set to ${1}"
LogText "${FUNCNAME}: checking if ${1} is greater than ${2}"
if [ "$1" > "$2" ]; then
LogText "${FUNCNAME}: ${1} is greater than ${2}"
RETVAL=0
else
LogText "${FUNCNAME}: ${1} is not greater than ${2}"
fi
fi
return ${RETVAL}
}
################################################################################
# Name : TestCase_GreaterOrEqual()
# Description : Test case for checking if value is greater than something
# Returns : (0 - SUCCESS; 1 - FAIL)
################################################################################
TestCase_GreaterOrEqual() {
RETVAL=1
if [ "$#" -ne "2" ]; then
ReportException "${TEST_NO}" "Error in function call to ${FUNCNAME}"
else
LogText "${FUNCNAME}: checking value for application ${APP}"
LogText "${FUNCNAME}: ${OPTION} is set to ${1}"
LogText "${FUNCNAME}: checking if ${1} is greater or equal ${2}"
if [ TestCase_Equal "${1}" "${2}" ] || [ TestCase_GreaterThan "${1}" "${2}" ]; then
RETVAL=0
fi
fi
return ${RETVAL}
}
################################################################################
# Name : TestCase_LessThan()
# Description : Test case for checking if value is greater than something
# Returns : (0 - SUCCESS; 1 - FAIL)
################################################################################
TestCase_LessThan() {
RETVAL=1
if [ "$#" -ne "2" ]; then
ReportException "${TEST_NO}" "Error in function call to TestCase_GreaterOrEqual"
else
LogText "${FUNCNAME}: checking value for application ${APP}"
LogText "${FUNCNAME}: ${OPTION} is set to ${1}"
LogText "${FUNCNAME}: checking if ${1} is less than ${2}"
if ! [ TestCase_GreaterOrEqual "${1}" "${2}" ]; then
LogText "${FUNCNAME}: ${1} is less than ${2}"
RETVAL=0
fi
fi
return ${RETVAL}
}
################################################################################
# Name : TestCase_LessOrEqual()
# Description : Test case for checking if value is less or equal something
# Returns : (0 - SUCCESS; 1 - FAIL)
################################################################################
TestCase_LessOrEqual() {
RETVAL=1
if [ "$#" -ne "2" ]; then
ReportException "${TEST_NO}" "Error in function call to ${FUNCNAME}"
else
LogText "${FUNCNAME}: checking value for application ${APP}"
LogText "${FUNCNAME}: ${OPTION} is set to ${1}"
LogText "${FUNCNAME}: checking if ${1} is less or equal ${2}"
if [ TestCase_Equal "${1}" "${2}" ] || [ TestCase_LessThan "${1}" "${2}" ]; then
LogText "${FUNCNAME}: ${1} is less than ${2}"
RETVAL=0
fi
fi
return ${RETVAL}
}
################################################################################
#
# Legacy functions - Do not use!
#
################################################################################
# For compatibility reasons they are listed here, but will be phased out in
# steps. If they still get used, they will trigger an alert when using the
# developer mode. In a later phase they will trigger errors on screen.
################################################################################
counttests() {
if IsDeveloperMode; then Debug "Warning: old counttests function is used. Please replace any reference with CountTests."; fi
CountTests
}
logtext() {
if IsDeveloperMode; then Debug "Warning: old logtext function is used. Please replace any reference with LogText."; fi
LogText "$1"
}
logtextbreak() {
if IsDeveloperMode; then Debug "Warning: old logtextbreak function is used. Please replace any reference with LogTextBreak."; fi
LogTextBreak "$1"
}
report() {
if IsDeveloperMode; then Debug "Warning: old report function is used. Please replace any reference with Report."; fi
Report "$1"
}
wait_for_keypress() {
if IsDeveloperMode; then Debug "Warning: old wait_for_keypress function is used. Please replace any reference with WaitForKeyPress."; fi
WaitForKeyPress
}
#================================================================================
# Lynis is part of Lynis Enterprise and released under GPLv3 license
# Copyright 2007-2017 - Michael Boelen, CISOfy - https://cisofy.com