mirror of https://github.com/CISOfy/lynis.git
2562 lines
105 KiB
Bash
2562 lines
105 KiB
Bash
#!/bin/sh
|
|
|
|
#################################################################################
|
|
#
|
|
# Lynis
|
|
# ------------------
|
|
#
|
|
# Copyright 2007-2013, Michael Boelen
|
|
# Copyright 2013-2016, 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
|
|
# AddSystemGroup Adds a system to a group
|
|
# CheckFilePermissions Check file permissions
|
|
# CheckUpdates Determine if a new version of Lynis is available
|
|
# CreateTempFile Create a temporary file
|
|
# CountTests Count number of performed tests
|
|
# ContainsString Find the needle (string) in the haystack (another string)
|
|
# 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
|
|
# Display Output text to screen with colors and identation
|
|
# DisplayManual Output text to screen without any layout
|
|
# 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
|
|
# IsDebug Check if --debug is used
|
|
# IsDeveloperMode Check if --developer is used
|
|
# IsRunning Check if a process is running
|
|
# InsertSection Insert a section block
|
|
# InsertPluginSection Insert a section block for plugins
|
|
# IsOwnedByRoot Determine if file or directory is owned by root
|
|
# 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
|
|
# Maid
|
|
# ParseNginx Parse nginx configuration lines
|
|
# ParseTestValues Parse a set of values
|
|
# PortIsListening Check if machine is listening on specified protocol and port
|
|
# Progress Show progress on screen
|
|
# RandomString Show a random string
|
|
# 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)
|
|
# ReportSuggestion Add a suggestion to report file
|
|
# ReportWarning Add a warning and priority to report file
|
|
# Register Register a test (for logging and execution)
|
|
# 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
|
|
# TestValue Evaluate a value in a string or key
|
|
# ViewCategories Display tests categories
|
|
# 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 : 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 : Adds a system to a group, which can be used for categorizing
|
|
#
|
|
# 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"
|
|
LYNIS_LV_RECORD="lynis-latest-version.cisofy.com."
|
|
FIND=`which dig 2> /dev/null`
|
|
if [ ! "${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 [ ! "${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 [ ! "${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 [ "${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
|
|
}
|
|
|
|
|
|
################################################################################
|
|
# 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 : 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)
|
|
LINESIZE=`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 [ ${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 : 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 : 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 : <nothing>
|
|
# Returns : <nothing>
|
|
# Usage : ExitFatal
|
|
################################################################################
|
|
|
|
ExitFatal() {
|
|
RemovePIDFile
|
|
RemoveTempFiles
|
|
LogText "${PROGRAM_NAME} ended with exit code 1."
|
|
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() {
|
|
sFILE=$1
|
|
CANREAD=0
|
|
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 have the read bit
|
|
if [ "${OTHERPERMS}" = "r" ]; then
|
|
CANREAD=1
|
|
return 0
|
|
LogText "Result: file ${sFILE} is readable (or directory accessible)."
|
|
else
|
|
LogText "Result: file ${sFILE} is NOT readable (or directory accessible), symlink, or does not exist. (OTHERPERMS: ${OTHERPERMS})"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
|
|
################################################################################
|
|
# Name : GetHostID()
|
|
# Description : Create an unique id for the system
|
|
#
|
|
# Returns : Nothing
|
|
# Usage : GetHostID
|
|
################################################################################
|
|
|
|
GetHostID() {
|
|
HOSTID=""
|
|
HOSTID2=""
|
|
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 Mac OS"
|
|
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"
|
|
DATA_SSH=$(cat /etc/ssh/${I})
|
|
FOUND=1
|
|
fi
|
|
fi
|
|
done
|
|
else
|
|
LogText "Result: no /etc/ssh directory found, skipping"
|
|
fi
|
|
|
|
if [ ! "${DATA_SSH}" = "" ]; then
|
|
# Create hashes
|
|
if [ ! "${SHA256SUMBINARY}" = "" ]; then
|
|
HASH_SSH=$(echo ${DATA_SSH} | ${SHA256SUMBINARY} | awk '{ print $1 }')
|
|
HASH_HOSTNAME=$(echo ${HOSTNAME} | ${SHA256SUMBINARY} | awk '{ print $1 }')
|
|
elif [ ! "${OPENSSLBINARY}" = "" ]; then
|
|
HASH_SSH=$(echo ${DATA_SSH} | ${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): ${HASH_SSH}"
|
|
HOSTID2="${HASH_SSH}"
|
|
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 : 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 : 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 : 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 : Check if a specific item exists in the report
|
|
# 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 ]; 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}" = "" -a ${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 already found machine type"
|
|
fi
|
|
|
|
# Check if we catched 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" ;;
|
|
*) 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}" = "" ]; 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}" = "" ]; then
|
|
CDATE=`date "+[%H:%M:%S]"`
|
|
echo "${CDATE} ===---------------------------------------------------------------===" >> ${LOGFILE}
|
|
fi
|
|
}
|
|
|
|
|
|
################################################################################
|
|
# Name : Maid()
|
|
# Description : Cleanup service
|
|
# Returns : <nothing>
|
|
################################################################################
|
|
|
|
Maid() {
|
|
echo ""; echo "Interrupt detected."
|
|
RemovePIDFile
|
|
RemoveTempFiles
|
|
Display --text "Cleaning up..." --result DONE --color GREEN
|
|
ExitFatal
|
|
}
|
|
|
|
|
|
################################################################################
|
|
# Name : ParseTestValues()
|
|
# Description : Parse nginx configuration lines
|
|
# 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
|
|
# Returns : <nothing>
|
|
################################################################################
|
|
|
|
ParseNginx() {
|
|
FIND=`awk -F= '/^nginx_config_option=/ { print $2 }' ${REPORTFILE} | sed 's/ /:space:/g'`
|
|
for I in ${FIND}; do
|
|
I=`echo ${I} | sed 's/:space:/ /g' | sed 's/;$//'`
|
|
OPTION=`echo ${I} | awk '{ print $1 }'`
|
|
VALUE=`echo ${I}| cut -d' ' -f2-`
|
|
LogText "Result: found option ${OPTION} with parameters ${VALUE}"
|
|
case ${OPTION} in
|
|
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)
|
|
;;
|
|
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
|
|
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; TEST_NEED_OS=""; PREQS_MET=""
|
|
TEST_NEED_NETWORK=""; TEST_NEED_PLATFORM=""
|
|
TOTAL_TESTS=$((TOTAL_TESTS + 1))
|
|
while [ $# -ge 1 ]; do
|
|
case $1 in
|
|
--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
|
|
;;
|
|
--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 if a test is root only and we are running a non-privileged test
|
|
if [ ${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 it's configured in profile (old style)
|
|
if [ ${SKIPTEST} -eq 0 ]; then
|
|
FIND=`echo "${TEST_SKIP_ALWAYS}" | grep "${TEST_NO}" | tr '[:upper:]' '[:lower:]'`
|
|
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 '[:upper:]' '[:lower:]'`
|
|
for I in ${SKIP_TESTS}; do
|
|
if [ "${I}" = "${VALUE}" ]; then SKIPTEST=1; SKIPREASON="Skipped by configuration (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
|
|
|
|
# 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; SKIPREASON="Prerequisities not met (ie missing tool, other type of Linux distribution)"; 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
|
|
}
|
|
|
|
|
|
################################################################################
|
|
# 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() {
|
|
echo "$1" >> ${REPORTFILE}
|
|
}
|
|
|
|
|
|
################################################################################
|
|
# 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() {
|
|
while [ $# -ge 1 ]; do
|
|
TEST_DESCRIPTION=""
|
|
TEST_FIELD=""
|
|
TEST_ID=""
|
|
TEST_OTHER=""
|
|
TEST_PREFERRED_VALUE=""
|
|
TEST_SERVICE=""
|
|
TEST_VALUE=""
|
|
|
|
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="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 [ "$1" = "" ]; then TEST="UNKNOWN"; else TEST="$1"; fi
|
|
if [ "$2" = "" ]; then MESSAGE="UNKNOWN"; else MESSAGE="$2"; fi
|
|
if [ "$3" = "" ]; then DETAILS="-"; else DETAILS="$3"; fi
|
|
if [ "$4" = "" ]; then SOLUTION="-"; else SOLUTION="$4"; fi
|
|
Report "suggestion[]=${TEST}|${MESSAGE}|${DETAILS}|${SOLUTION}|"
|
|
LogText "Suggestion: ${MESSAGE} [test:$1] [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
|
|
DETAILS="$2"
|
|
MESSAGE="$3"
|
|
TEST="$1"
|
|
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 [ "$1" = "" ]; then TEST="UNKNOWN"; else TEST="$1"; fi
|
|
if [ "$2" = "" ]; then MESSAGE="UNKNOWN"; else MESSAGE="$2"; fi
|
|
if [ "$3" = "" ]; then DETAILS="-"; else DETAILS="$3"; fi
|
|
if [ "$4" = "" ]; then SOLUTION="-"; else SOLUTION="$4"; 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)
|
|
# Returns : <nothing>
|
|
################################################################################
|
|
|
|
SearchItem() {
|
|
ITEM_FOUND=0
|
|
if [ $# -eq 2 ]; then
|
|
# Don't search in /dev/null, it's too empty there
|
|
if [ -f $2 ]; then
|
|
# Check if we can find the main type (with or without brackets)
|
|
LogText "Test: search string $1 in file $2"
|
|
FIND=`egrep "$1" $2`
|
|
if [ ! "${FIND}" = "" ]; then
|
|
ITEM_FOUND=1
|
|
LogText "Result: found string"
|
|
LogText "Full string: ${FIND}"
|
|
else
|
|
LogText "Result: search string NOT found"
|
|
fi
|
|
else
|
|
LogText "Skipping search, file does not exist"
|
|
ReportException ${TEST_NO} "Test is trying to search for a string in nonexistent file"
|
|
fi
|
|
else
|
|
ReportException ${TEST_NO} "Error in function call to CheckItem"
|
|
fi
|
|
}
|
|
|
|
|
|
# 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() {
|
|
sFILE=$1
|
|
FOUNDPATH=0
|
|
SYMLINK_USE_PYTHON=0
|
|
SYMLINK_USE_READLINK=0
|
|
# Check for symlink
|
|
if [ -L ${sFILE} ]; then
|
|
|
|
# Mac OS 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 symlinks"
|
|
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 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 '[:upper:]' '[:lower:]'`
|
|
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 : 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() {
|
|
if [ ! "${INCLUDEDIR}" = "" ]; then
|
|
InsertSection "Available test categories"
|
|
for I in `ls ${INCLUDEDIR}/tests_* | xargs -n 1 basename | sed 's/tests_//' | grep -v "custom.template"`; do
|
|
echo " - ${I}"
|
|
done
|
|
fi
|
|
echo ""
|
|
exit 0
|
|
}
|
|
|
|
|
|
################################################################################
|
|
# 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-2016 - Michael Boelen, CISOfy - https://cisofy.com
|