lynis/include/functions
Simon Biewald 73f39baca8 Use first e1000 interface and break after match
Fixes CISOfy/lynis#1075.

Before this commit, the interfaces "e1000g1" and "net0" were allowed.
The name "e1000g0" is appended to the list.
After finding an interface, the loop is interrupted now. As previously
"net0" was always used, even if another interface was available, the list
is reordered to "net0 e1000g1 e1000g0" to not break previous generations.

A typo is also fixed ("No interface found op Solaris ..." -> "No
interface found on").

Signed-off-by: Simon Biewald <simon@fam-biewald.de>
2020-11-14 22:56:42 +00:00

3689 lines
154 KiB
Bash

#!/bin/sh
#################################################################################
#
# Lynis
# ------------------
#
# Copyright 2007-2013, Michael Boelen
# Copyright 2007-2020, 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 indentation
# DisplayError Show an error on screen
# DisplayException Show an exception on screen
# DisplayManual Output text to screen without any layout
# DisplayToolTip Show a tip for improving usage of the tool
# DisplayWarning Show a clear warning on screen
# Equals Compares two strings
# 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
# FileInstalledByPackage Check if a file is linked to a package
# 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
# GetReportData Request data from report
# HasCorrectFilePermissions Check file permissions and see if they match expected values
# HasData Checks for data in variable
# 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
# IsEmpty Check for empty result or variable
# 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
# PackageIsInstalled Test for installed package
# 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
# Readonly Mark a variable as read-only data
# 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
# SafeFile Security tests to perform on a file before using it
# SafePerms Check if a file has safe permissions
# SafeInput Test provided string to see if it contains unwanted characters
# 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
# Status Show execution status, such as active test being performed
# 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
#
#################################################################################
################################################################################
# Name : AddHP()
# Description : Add hardening points and count them
#
# Parameters : $1 = points to add (0 or higher)
# $2 = maximum points (at least value of $1 or higher)
# 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'
#
# Parameters : $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=$(grep -E "^${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)
CreateTempFile
TEMP_SETTINGS_FILE="${TEMP_FILE}"
cat ${SETTINGS_FILE} > ${TEMP_SETTINGS_FILE}
sed -e '/^'"${SETTING}"';/d' ${TEMP_SETTINGS_FILE} > ${SETTINGS_FILE}
rm "${TEMP_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
#
# Parameters : $1 = group name
# Returns : <nothing>
# Usage : AddSystemGroup "test"
################################################################################
AddSystemGroup() {
Report "system_group[]=$1"
}
################################################################################
# Name : CheckFilePermissions()
# Description : Check file permissions
#
# Parameters : 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"
FILEVALUE=""
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: -f2)
else
FILEVALUE=$(ls -l ${CHECKFILE} | cut -c 2-10)
PROFILEVALUE=$(grep '^permfile' ${PROFILE} | grep "=${CHECKFILE}:" | cut -d: -f2)
fi
if [ "${FILEVALUE}" = "${PROFILEVALUE}" ]; then PERMS="OK"; else PERMS="BAD"; fi
fi
}
################################################################################
# Name : CheckItem()
# Description : Check if a specific item exists in the report
#
# Parameters : $1 = key
# $2 = value
# Returns : exit code (0 = True, 1 = False)
# Usage : if CheckItem "key" "value"; then ....; fi
################################################################################
CheckItem() {
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=$(grep -E "^$1(\[\])?=" ${REPORTFILE} | grep -E "$2")
if HasData "${FIND}"; then
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 | grep -v "no [^ ]* in")
if [ -n "${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 | grep -v "no [^ ]* in ")
if [ -n "${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 | grep -v "no [^ ]* in ")
if [ -n "${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 : Delete PID and temporary files, stop execution (exit code 1)
#
# Parameters : <none>
# Returns : <nothing>
# Usage : this function is triggered by a manual break by user
################################################################################
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 : exit code (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" | grep -E "$1")
if [ ! "${FIND}" = "" ]; then RETVAL=0; fi
return ${RETVAL}
}
################################################################################
# Name : CountTests()
# Description : Counter for the number of tests performed
#
# Parameters : <none>
# Returns : <nothing>
# Usage : Call CountTests to increase number by 1
################################################################################
CountTests() {
CTESTS_PERFORMED=$((CTESTS_PERFORMED + 1))
}
################################################################################
# Name : CreateTempFile()
# Description : Creates a temporary file
#
# 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="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 : exit code (0 = True, 1 = False)
# Usage : if DirectoryExists; then echo "it exists"; else echo "It does not exist"; fi
################################################################################
# Determine if a directory exists
DirectoryExists() {
if [ $# -eq 0 ]; then ExitFatal "Missing parameter when calling DirectoryExists function"; fi
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"
if [ ${USE_CWD} -eq 1 ]; then
tPROFILE_TARGETS="."
else
tPROFILE_TARGETS="/usr/local/etc/lynis /etc/lynis /usr/local/lynis ."
fi
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}"
else
ExitFatal "Could not find or read profile (${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 [ -z "${RESULT}" ]; then
RESULTPART=""
else
if [ ${CRONJOB} -eq 0 ]; then
RESULTPART=" [ ${COLOR}${RESULT}${NORMAL} ]"
else
RESULTPART=" [ ${RESULT} ]"
fi
fi
if [ -n "${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:
# - for full shells, count with -m instead of -c, to support language locale (older busybox does not have -m)
# - 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 [ -n "${EXITCODE}" ]; then ExitCustom ${EXITCODE}; fi
}
################################################################################
# Name : DisplayException()
# Description : Show a discovered exception on screen
#
# Parameters : $1 = function or test
# $2 = text
# Returns : <nothing>
# Note : This function is usually triggered by ReportException
################################################################################
DisplayException() {
${ECHOCMD:-echo} ""
${ECHOCMD:-echo} "================================================================="
${ECHOCMD:-echo} ""
${ECHOCMD:-echo} " ${WARNING}Exception found!${NORMAL}"
${ECHOCMD:-echo} ""
${ECHOCMD:-echo} " Function/test: [$1]"
${ECHOCMD:-echo} " Message: ${BOLD}$2${NORMAL}"
${ECHOCMD:-echo} ""
${ECHOCMD:-echo} " Help improving the Lynis community with your feedback!"
${ECHOCMD:-echo} ""
${ECHOCMD:-echo} " Steps:"
${ECHOCMD:-echo} " - Ensure you are running the latest version ($0 update check)"
${ECHOCMD:-echo} " - If so, create a GitHub issue at ${PROGRAM_SOURCE}"
${ECHOCMD:-echo} " - Include relevant parts of the log file or configuration file"
${ECHOCMD:-echo} ""
${ECHOCMD:-echo} " Thanks!"
${ECHOCMD:-echo} ""
${ECHOCMD:-echo} "================================================================="
${ECHOCMD:-echo} ""
sleep 5
}
################################################################################
# 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 -a ${QUIET} -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 : DisplayWarning
# Description : Show a warning on the screen
#
# Parameters : $1 = text
# Returns : <nothing>
################################################################################
DisplayWarning() {
if [ ${CRONJOB} -eq 0 ]; then
printf "\n"
${ECHOCMD:-echo} " ${BG_WARNING}[WARNING]${NORMAL}: $1${NORMAL}"
printf "\n"
else
${ECHOCMD} " [WARNING]: $1"
fi
}
################################################################################
# Name : Equals()
# Description : Compare two strings after special characters were stripped
#
# Parameters : $1 = string1
# $2 = string2
# Returns : exit code (0 = True, 1 = False)
# Usage : if Equals "${MYDIR}" "/etc"; then echo "Found"; else "Not found"; fi
################################################################################
Equals() {
RETVAL=1
if [ $# -ne 2 ]; then ReportException "Equals" "Incorrect number of arguments for $0 function"; fi
# Strip any strange control characters
INPUT1=$(echo $1 | tr -d '[:cntrl:]<>' | ${SEDBINARY} 's/__space__/ /g' | ${SEDBINARY} 's/:space:/ /g')
INPUT2=$(echo $2 | tr -d '[:cntrl:]<>' | ${SEDBINARY} 's/__space__/ /g' | ${SEDBINARY} 's/:space:/ /g')
if [ "${INPUT1}" = "${INPUT2}" ]; then RETVAL=0; fi
return ${RETVAL}
}
################################################################################
# Name : ExitClean()
# Description : Perform a normal exit of the program, and clean up resources
#
# Parameters : <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
#
# Parameters : $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
#
# Parameters : $1 = text string (optional)
# Returns : <nothing>
# Usage : ExitFatal
################################################################################
ExitFatal() {
RemovePIDFile
RemoveTempFiles
LogText "${PROGRAM_NAME} ended with exit code 1."
if [ $# -eq 1 ]; then
${ECHOCMD:-echo} ""
${ECHOCMD:-echo} "${RED}Fatal error${NORMAL}: ${WHITE}$1${NORMAL}"
${ECHOCMD:-echo} ""
fi
exit 1
}
################################################################################
# Name : FileExists()
# Description : Determine if a file exists
#
# Parameters : $1 = path
# Returns : 0 (found), 1 (not found)
# FILE_FOUND (0:found, 1:not found) - deprecated usage
################################################################################
FileExists() {
if [ $# -eq 0 ]; then ExitFatal "Missing parameter when calling FileExists function"; fi
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 : FileInstalledByPackage()
# Description : Check if a file is part of a package
# Returns : 0 (true), 1 (default: unknown or false)
################################################################################
FileInstalledByPackage() {
exitcode=1
file=$1
find=""
if [ -n "${DPKGBINARY}" ]; then
find=$(${DPKGBINARY} -S "${file}" 2> /dev/null | ${AWKBINARY} -F: '{print $1}')
elif [ -n "${RPMBINARY}" ]; then
find=$(${RPMBINARY} -qf "${file}" 2> /dev/null | ${AWKBINARY} -F- '{print $1}')
fi
if [ -n "${find}" ]; then
LogText "Result: file '${file}' belongs to package (${find})"
exitcode=0
else
LogText "Result: file '${file}' does most likely not belong to a package"
fi
return ${exitcode}
}
################################################################################
# Name : FileIsEmpty()
# Description : Check if a file is empty
#
# Returns : 0 (empty), 1 (not empty)
# EMPTY (0 or 1) - deprecated usage
# Usage : if FileIsEmpty /etc/passwd; then
################################################################################
FileIsEmpty() {
if [ $# -eq 0 ]; then ExitFatal "Missing parameter when calling FileIsEmpty function"; fi
EMPTY=0
LogText "Test: checking if file $1 is empty"
if [ ! -s "$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
escaped_file=$(echo ${sFILE} | sed 's/\*/\\*/; s/?/\\?/')
LogText "Test: check if we can access ${sFILE} (escaped: ${escaped_file})"
# Check for symlink
if [ -L "${escaped_file}" ]; then
ShowSymlinkPath ${escaped_file}
if [ -n "${SYMLINK}" ]; then escaped_file="${SYMLINK}"; fi
fi
# Only check the file if it isn't a symlink (after previous check)
if [ -L "${escaped_file}" ]; 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 "${escaped_file}" ]; then
OTHERPERMS=$(${LSBINARY} -d -l "${escaped_file}" 2> /dev/null | ${CUTBINARY} -c 8)
elif [ -f "${escaped_file}" ]; then
OTHERPERMS=$(${LSBINARY} -d -l "${escaped_file}" 2> /dev/null | ${CUTBINARY} -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 "${escaped_file}" 2> /dev/null | ${AWKBINARY} -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 ${escaped_file}"
elif [ -d "${escaped_file}" ]; then
OTHERPERMS=$(${LSBINARY} -d -l "${escaped_file}" 2> /dev/null | ${CUTBINARY} -c 2)
elif [ -f "${escaped_file}" ]; then
OTHERPERMS=$(${LSBINARY} -l "${escaped_file}" 2> /dev/null | ${CUTBINARY} -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 ${escaped_file} is readable (or directory accessible)."
return 0
else
return 1
LogText "Result: file ${escaped_file} 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 : 0 = fetched or created IDs, 1 = failed, 2 = skipped
# Usage : GetHostID
################################################################################
GetHostID() {
if [ ${SKIP_GETHOSTID} -eq 1 ]; then
return 2
fi
if [ -n "${HOSTID}" -a -n "${HOSTID2}" ]; then
Debug "Skipping creation of host identifiers, as they are already configured (via profile)"
return 2
fi
if [ -f "${ROOTDIR}etc/lynis/hostids" ]; then
Debug "Used hostids file to fetch values"
HOSTID=$(grep "^hostid=" ${ROOTDIR}etc/lynis/hostids | awk -F= '{print $2}')
HOSTID2=$(grep "^hostid2=" ${ROOTDIR}etc/lynis/hostids | awk -F= '{print $2}')
return 0
fi
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 | grep -v "no [^ ]* in "); fi
if [ "${SHA1SUMBINARY}" = "" ]; then SHA1SUMBINARY=$(which sha1 2> /dev/null | grep -v "no [^ ]* in "); fi
if [ "${SHA256SUMBINARY}" = "" ]; then SHA256SUMBINARY=$(which sha256sum 2> /dev/null | grep -v "no [^ ]* in "); fi
if [ "${SHA256SUMBINARY}" = "" ]; then SHA256SUMBINARY=$(which sha256 2> /dev/null | grep -v "no [^ ]* in "); fi
if [ "${CSUMBINARY}" = "" ]; then CSUMBINARY=$(which csum 2> /dev/null | grep -v "no [^ ]* in "); fi
if [ "${OPENSSLBINARY}" = "" ]; then OPENSSLBINARY=$(which openssl 2> /dev/null | grep -v "no [^ ]* in "); fi
if [ "${IFCONFIGBINARY}" = "" ]; then IFCONFIGBINARY=$(which ifconfig 2> /dev/null | grep -v "no [^ ]* in "); fi
if [ "${IPBINARY}" = "" ]; then IPBINARY=$(which ip 2> /dev/null | grep -v "no [^ ]* in "); fi
# If using openssl, use the best hash type it supports
if [ ! "${OPENSSLBINARY}" = "" ]; then
OPENSSL_HASHLIST=$(openssl dgst -h 2>&1)
for OPENSSL_HASHTYPE in sha256 sha1 md5 ; do
if echo "${OPENSSL_HASHLIST}" | grep "^-${OPENSSL_HASHTYPE} " >/dev/null ; then
break
fi
done
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 [ -n "${SHA1SUMBINARY}" ]; then
HOSTID=$(echo ${FIND} | ${SHA1SUMBINARY} | awk '{ print $1 }')
elif [ -n "${CSUMBINARY}" ]; then
HOSTID=$(echo ${FIND} | ${CSUMBINARY} -h SHA1 - | awk '{ print $1 }')
elif [ -n "${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 HasData "${FIND}"; then
HOSTID=$(echo ${FIND} | sha1)
else
ReportException "GetHostID" "No MAC address returned on DragonFly or FreeBSD"
fi
;;
"HP-UX")
FIND=$(nwmgr -q info -c lan0 2> /dev/null | awk '{ if ($1=="MAC" && $2=="Address") { print $4 }}')
if HasData "${FIND}"; then
if [ -n "${OPENSSLBINARY}" ]; then
HOSTID=$(echo ${FIND} | ${OPENSSLBINARY} sha -sha1 | awk '{ print $2 }')
else
ReportException "GetHostID" "No openssl binary available on this HP-UX system"
fi
else
ReportException "GetHostID" "No MAC address found by using nwmgr"
fi
;;
"Linux")
# Future change
# Show brief output of ip of links that are UP. Filter out items like 'UNKNOWN' in col 2
# Using the {2} syntax does not work on all systems
# ip -br link show up | sort | awk '$2=="UP" && $3 ~ /^[a-f0-9][a-f0-9]:/ {print $3}'
# Use ifconfig
if [ -n "${IFCONFIGBINARY}" ]; then
# Determine if we have the eth0 interface (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 IsEmpty "${FIND}"; then
FIND=$(${IFCONFIGBINARY} 2> /dev/null | grep HWaddr)
if IsEmpty "${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 HasData "${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 IsEmpty "${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
elif [ -n "${IPBINARY}" ]; then
# Determine if we have the common available eth0 interface
FIND=$(${IPBINARY} addr show eth0 2> /dev/null | grep -E "link/ether " | head -1 | awk '{ print $2 }' | tr '[:upper:]' '[:lower:]')
if IsEmpty "${FIND}"; then
# Determine the MAC address of first interface with the ip command
FIND=$(${IPBINARY} addr show 2> /dev/null | grep -E "link/ether " | head -1 | awk '{ print $2 }' | tr '[:upper:]' '[:lower:]')
if IsEmpty "${FIND}"; then
ReportException "GetHostID" "Can't create hostid (no MAC addresses found)"
fi
fi
else
ReportException "GetHostID" "Both ip and ifconfig tools are missing"
fi
# Check if we found a HostID
if HasData "${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 [ -n "${LYNIS_HOSTID2_PART1}" ]; then
LogText "Info: using hostname ${LYNIS_HOSTID2_PART1}"
LYNIS_HOSTID2_PART2=$(sysctl -n kern.uuid 2> /dev/null)
if [ -n "${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 HasData "${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 HasData "${FIND}"; then
HOSTID=$(echo ${FIND} | sha1)
else
ReportException "GetHostID" "No MAC address returned on OpenBSD"
fi
;;
"Solaris")
INTERFACES_TO_TEST="net0 e1000g1 e1000g0"
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"
break
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 on 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 [ -z "${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 [ -z "${HOSTID}" ]; then
if [ -f /etc/ssh/${I} ]; then
LogText "Result: found ${I} in /etc/ssh"
if [ -n "${SHA1SUMBINARY}" ]; then
HOSTID=$(${SHA1SUMBINARY} /etc/ssh/${I} | 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 -n "${DATA_SSH}" ]; then
LogText "Using SSH public key to create the second host identifier"
STRING_TO_HASH="${DATA_SSH}"
else
if [ -n "${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 [ -n "${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} dgst -${OPENSSL_HASHTYPE} | awk '{ print $2 }')
HASH_HOSTNAME=$(echo ${HOSTNAME} | ${OPENSSLBINARY} dgst -${OPENSSL_HASHTYPE} | awk '{ print $2 }')
fi
LogText "Hash (hostname): ${HASH_HOSTNAME}"
LogText "Hash (ssh or machineid): ${HASH2}"
HOSTID2="${HASH2}"
fi
fi
# Show an exception if no HostID could be created, to ensure each system (and scan) has one
if [ -z "${HOSTID}" ]; then
ReportException "GetHostID" "No unique host identifier could be created."
return 1
elif [ -n "${HOSTID2}" ]; then
return 0
fi
}
################################################################################
# Name : GetReportData()
# Description : Request data from report
# Returns : Data (when matches were found)
# Returns : exit code (0 = True, 1 = False, meaning search was cancelled)
# stdout (output of search result)
################################################################################
GetReportData() {
KEY=""
VALID_CHARS="[:alnum:]/:;\-,\._\[\]\n "
if [ $# -eq 0 ]; then ExitFatal "No parameters provided to GetReportData() function"; fi
while [ $# -ge 1 ]; do
case $1 in
--key)
shift
KEY="$1"
;;
--valid-chars)
shift
VALID_CHARS="$1"
;;
*)
ExitFatal "Invalid option provided to GetReportData() function"
;;
esac
# Go to next parameter
shift
done
if [ "${REPORTFILE}" = "/dev/null" ]; then
return 1
else
${AWKBINARY} -v pattern="^${KEY}" -F= '$1 ~ pattern {print $2}' ${REPORTFILE} | ${TRBINARY} -cd "${VALID_CHARS}" | ${TRBINARY} '[:blank:]' '__space__'
fi
return 0
}
################################################################################
# Name : HasCorrectFilePermissions()
# Description : Check file permissions
#
# Parameters : $1 = Full path to file or directory
# $2 = Permissions
# Returns : exit code (0 = correct, 1 = not correct, 2 = file does not exist)
################################################################################
HasCorrectFilePermissions() {
if [ $# -ne 2 ]; then Fatal "Incorrect usage of HasCorrectFilePermissions"; fi
CHECKFILE="$1"
CHECKPERMISSION_FULL="$2"
if [ ! -d ${CHECKFILE} -a ! -f ${CHECKFILE} ]; then
return 2
else
for CHECK_PERMISSION in ${CHECKPERMISSION_FULL}; do
DATA=$(echo ${CHECK_PERMISSION} | ${EGREPBINARY} "[rwx]")
if [ $? -eq 0 ]; then
# add a dummy character as first character so it looks like output is a normal file
CHECK_PERMISSION=$(echo "-${CHECK_PERMISSION}" | ${AWKBINARY} '{k=0;for(i=0;i<=8;i++)k+=((substr($1,i+2,1)~/[rwx]/)*2^(8-i));if(k)printf("%0o",k)}')
fi
# Add leading zeros if necessary
CHECK_PERMISSION=$(echo "${CHECK_PERMISSION}" | ${AWKBINARY} '{printf "%03d",$1}')
# First try stat command
LogText "Test: checking if file ${CHECKFILE} has the permissions set to ${CHECK_PERMISSION} or more restrictive"
if [ -n "${STATBINARY}" ]; then
case ${OS} in
*BSD | "macOS")
# BSD and macOS have no --format, only short notation
DATA=$(${STATBINARY} -f "%OLp" ${CHECKFILE})
;;
*)
# busybox does not support format
if [ ${SHELL_IS_BUSYBOX} -eq 0 ]; then
DATA=$(${STATBINARY} --format=%a ${CHECKFILE})
fi
;;
esac
fi
# See if we can use the find binary
if [ -z "${DATA}" ]; then
case ${OS} in
"AIX" | *BSD)
Debug "Skipping find command, as this operating system does not support -printf parameter"
;;
*)
# Only use find when OS is NOT AIX and binaries are NOT busybox
if [ ${SHELL_IS_BUSYBOX} -eq 0 ]; then
if [ -d ${CHECKFILE} ]; then
DATA=$(${FINDBINARY} ${CHECKFILE} -maxdepth 0 -printf "%m")
else
DATA=$(${FINDBINARY} ${CHECKFILE} -printf "%m")
fi
fi
;;
esac
fi
# Finally use ls command
if [ -z "${DATA}" ]; then
# If 'file' is an directory, use -d
if [ -d ${CHECKFILE} ]; then
DATA=$(${LSBINARY} -d -l ${CHECKFILE} | cut -c 2-10)
else
DATA=$(${LSBINARY} -l ${CHECKFILE} | cut -c 2-10)
fi
fi
# Convert permissions to octal when needed
case ${DATA} in
[-r][-w][-x][-r][-w][-x][-r][-w][-x] )
LogText "Converting value ${DATA} to octal"
# add a dummy character as first character so it looks like output is a normal file
DATA=$(echo "-${DATA}" | ${AWKBINARY} '{k=0;for(i=0;i<=8;i++)k+=((substr($1,i+2,1)~/[rwx]/)*2^(8-i));if(k)printf("%0o",k)}')
;;
esac
# Add leading zeros if necessary
DATA=$(echo "${DATA}" | ${AWKBINARY} '{printf "%03d",$1}')
if [ -n "${DATA}" ]; then
if [ "${DATA}" -le "${CHECK_PERMISSION}" ]; then
LogText "Outcome: correct permissions (${DATA})"
return 0
fi
else
ReportException "HasCorrectFilePermissions:02" "No data value found, which is unexpected"
fi
done
LogText "Outcome: permissions of file ${CHECKFILE} are not matching expected value (${DATA} != ${CHECKPERMISSION_FULL})"
# No match, return exit code 1
return 1
fi
}
################################################################################
# Name : HasData()
# Description : Check for a filled variable
#
# Returns : exit code (0 = True, 1 = False)
# Usage : if HasData "${FIND}"; then
################################################################################
HasData() {
if [ $# -eq 1 ]; then
if [ -n "$1" ]; then return 0; else return 1; fi
else
ExitFatal "Function HasData called without parameters - look in log to determine where this happened, or use sh -x lynis to see all details."
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
#
# Parameters : <none>
# Returns : exit code (0 = true, 1 = false)
# variable: CONTAINER_TYPE
################################################################################
IsContainer() {
FOUND=0
# Early on we can't use FileIsReadable yet
if [ -e /proc/1/cgroup ]; then
FIND=$(grep -i docker ${ROOTDIR}proc/1/cgroup 2> /dev/null)
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 [ -e /proc/1/environ ]; then
FIND=$(grep -qa 'container=lxc' ${ROOTDIR}proc/1/environ 2> /dev/null)
if [ $? -eq 0 ]; then
LogText "Result: found LXC in environment (/proc/1/environ), so we are running in LXC container"
CONTAINER_TYPE="LXC"; 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
#
# Parameters : <none>
# Returns : exit code (0 = True, 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)
#
# Parameters : <none>
# Returns : exit code (0 = True, 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
#
# Parameters : <none>
# Returns : exit code (0 = True, 1 = False)
################################################################################
IsDeveloperVersion() {
if [ "${PROGRAM_RELEASE_TYPE}" = "pre-release" ]; then return 0; else return 1; fi
}
################################################################################
# Name : IsEmpty()
# Description : Check for variable that has no data in it
#
# Returns : exit code (0 = True, 1 = False)
# Usage : if IsEmpty "${FIND}"; then
################################################################################
IsEmpty() {
if [ $# -eq 0 ]; then
ExitFatal "Function IsEmpty called without parameters - look in log to determine where this happened, or use sh -x lynis to see all details."
else
if [ -z "$1" ]; then return 0; else return 1; fi
fi
}
################################################################################
# Name : IsRunning()
# Description : Check if a process is running
#
# Parameters : $1 = search argument
# $2 = optional arguments
# Returns : 0 (process is running), 1 (process not running)
# RUNNING (1 = running, 0 = not running) - will be deprecated
# Notes : PSOPTIONS are declared globally, to prevent testing each call
# Fallback is used on binaries as IsRunning is used for 'show' command
################################################################################
IsRunning() {
if [ $# -eq 0 ]; then ExitFatal "Missing parameter when calling IsRunning function"; fi
pgrep_options="-x"
search=""
FIND=""
PSOPTIONS=""
PARTIAL_SEARCH=1
while [ $# -ge 1 ]; do
case $1 in
--full)
pgrep_options="-f" # replace -x with -f
PARTIAL_SEARCH=0
;;
--user)
shift
users="$1"
;;
*)
search="$1"
;;
esac
shift # Go to next parameter
done
if [ -z "${search}" ]; then ExitFatal "Missing process to search for when using IsRunning function"; fi
RUNNING=0
if [ -x "${PGREPBINARY}" ] && [ "${OS}" != "AIX" ]; then
# When --user is used, perform a search using the -u option
# Initialize users for strict mode
if [ -n "${users:-}" ]; then
for u in ${users}; do
user_uid=$(getent passwd "${u}" 2> /dev/null | ${AWKBINARY:-awk} -F: '{print $3}')
# Only perform search if user exists and we had no match yet
if [ -n "${user_uid}" ]; then
if [ -z "${FIND}" ]; then
LogText "Performing pgrep scan using uid ${user_uid}"
FIND=$(${PGREPBINARY:-pgrep} ${pgrep_options} -u "${user_uid}" "${search}" | ${TRBINARY:-tr} '\n' ' ')
fi
fi
done
else
LogText "Performing pgrep scan without uid"
FIND=$(${PGREPBINARY:-pgrep} ${pgrep_options} "${search}" | ${TRBINARY:-tr} '\n' ' ')
fi
else
if [ "${SHELL_IS_BUSYBOX}" -eq 1 ]; then
# This search is not foolproof
LogText "Performing simple ps scan (busybox)"
PSOPTIONS=" -o args="
FIND=$(${PSBINARY:-ps} ${PSOPTIONS} | ${EGREPBINARY:-egrep} "( |/)${search}" | ${GREPBINARY:-grep} -v "grep")
else
if [ -n "${users}" ]; then
for u in ${users}; do
user_uid=$(getent passwd "${u}" 2> /dev/null | ${AWKBINARY:-awk} -F: '{print $3}')
# Only perform search if user exists and we had no match yet
if [ -n "${user_uid}" ]; then
if [ -z "${FIND}" ]; then
if [ ${PARTIAL_SEARCH} -eq 1 ]; then
LogText "Performing ps scan using partial match and for uid ${user_uid}"
FIND=$(${PSBINARY:-ps} -u "${user_uid}" -o comm= "${search}" | ${AWKBINARY:-awk} -v pattern="${search}" '$0 ~ pattern {print}')
else
LogText "Performing ps scan using exact match and for uid ${user_uid}"
FIND=$(${PSBINARY:-ps} -u "${user_uid}" -o comm= "${search}" | ${AWKBINARY:-awk} -v pattern="^${search}$" '$0 ~ pattern {print}')
fi
fi
fi
done
else
case "${OS}" in
"Linux")
PSOPTIONS=" -o args= -C ${search}"
;;
esac
if [ ${PARTIAL_SEARCH} -eq 1 ]; then
LogText "Performing ps scan using partial match and without uid"
FIND=$(${PSBINARY:-ps} ${PSOPTIONS} | ${AWKBINARY:-awk} -v pattern="${search}" '$0 ~ pattern {print}')
else
LogText "Performing ps scan using exact match and without uid"
FIND=$(${PSBINARY:-ps} ${PSOPTIONS} | ${AWKBINARY:-awk} -v pattern="^${search}$" '$0 ~ pattern {print}')
fi
fi
fi
fi
if [ -n "${FIND}" ]; then
RUNNING=1
LogText "IsRunning: process '${search}' found (${FIND})"
return 0
else
LogText "IsRunning: process '${search}' not found"
return 1
fi
}
################################################################################
# Name : IsNotebook
# Description : Check if file or directory is owned by root
# Returns : exit code (0 = True, 1 = False, 255 = Unknown)
################################################################################
IsNotebook() {
FIND=$(which laptop-detect 2> /dev/null | grep -v "no [^ ]* in ")
if [ -n "${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
#
# Parameters : <none>
# Returns : exit code (0 =true, 1 =false)
################################################################################
IsVerbose() {
if [ ${VERBOSE} -eq 1 ]; then return 0; else return 1; fi
}
################################################################################
# Name : IsVirtualMachine()
# Description : Determine whether it is a virtual machine
# Parameters : <none>
# Returns : exit code (0 = True, 1 = False, 2 = Unknown)
# variable: ISVIRTUALMACHINE (0-2)
# variable: VMTYPE
# variable: 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=""
if [ ${SKIP_VM_DETECTION} -eq 1 ]; then
return 2
fi
# lxc environ detection
if [ -z "${SHORT}" ]; then
if [ -f /proc/1/environ ]; then
FIND=$(grep -qa 'container=lxc' /proc/1/environ 2> /dev/null)
if [ $? -eq 0 ]; then
SHORT=lxc
LogText "Result: found ${SHORT}"
fi
fi
else
LogText "Result: skipped lxc environ detection test, as we already found machine type"
fi
# facter
if [ -z "${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 [ -z "${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 [ -n "${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 [ -z "${SHORT}" ]; then
if [ -x /usr/bin/lscpu ]; then
LogText "Test: trying to guess virtualization with lscpu"
FIND=$(lscpu | grep -i "^Hypervisor Vendor" | awk -F: '{ print $2 }' | sed 's/ //g')
if [ -n "${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 [ -z "${SHORT}" ]; then
# Try to find dmidecode in case we did not check binaries (e.g. lynis show environment)
if [ ${CHECK_BINARIES} -eq 0 ]; then DMIDECODEBINARY=$(command -v dmidecode 2> /dev/null); fi
if [ -n "${DMIDECODEBINARY}" -a -x "${DMIDECODEBINARY}" -a ${PRIVILEGED} -eq 1 ]; then
LogText "Test: trying to guess virtualization with dmidecode"
FIND=$(${DMIDECODEBINARY} -s system-product-name | awk '{ print $1 }')
if [ -n "${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 [ -z "${SHORT}" ]; then
LogText "Test: trying to guess virtual machine type by running processes"
# VMware
if IsRunning vmware-guestd; then SHORT="vmware"
elif IsRunning vmtoolsd; then SHORT="vmware"
fi
# VirtualBox based on guest services
if IsRunning vboxguest-service; then SHORT="virtualbox"
elif IsRunning VBoxClient; then SHORT="virtualbox"
elif IsRunning VBoxService; then SHORT="virtualbox"
fi
else
LogText "Result: skipped processes test, as we already found platform"
fi
# Amazon EC2
if [ -z "${SHORT}" ]; then
LogText "Test: checking specific files for Amazon"
if [ -f /etc/ec2_version -a -s /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 [ -z "${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 | grep -E "(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 [ -z "${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 HasData "${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 [ -n "${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 : $1 = path (string)
# Returns : exit code (0 = readable, 1 = not readable, 255 = error)
# Usage : if IsWorldReadable /etc/motd; then echo "File is readable"; fi
################################################################################
IsWorldReadable() {
if [ $# -eq 0 ]; then ExitFatal "Missing parameter when calling IsWorldReadable function"; fi
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 : $1 = 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() {
if [ $# -eq 0 ]; then ExitFatal "Missing parameter when calling IsWorldExecutable function"; fi
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
#
# Parameters : $1 = path
# Returns : exit code (0 = writable, 1 = not writable, 255 = error)
# Usage : if IsWorldWritable /etc/motd; then echo "File is writable"; fi
################################################################################
IsWorldWritable() {
if [ $# -eq 0 ]; then ExitFatal "Missing parameter when calling IsWorldWritable function"; fi
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)
#
# Parameters : $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 : PackageIsInstalled()
# Description : Determines if a package is installed
# Returns : exit code
# Notes : this function is not used yet, but created in advance to allow
# the addition of support for all operating systems
################################################################################
PackageIsInstalled() {
exit_code=255
# First parameter is package name (or __dummy__ for initial test to see if package manager is available and works as expected)
if [ $# -eq 1 ]; then
package="$1"
else
Fatal "Incorrect usage of PackageIsInstalled function"
fi
if [ -n "${DNFBINARY}" ]; then
output=$(${DNFBINARY} --quiet --cacheonly --noplugins --assumeno info --installed ${package} > /dev/null 2>&1)
exit_code=$?
elif [ -n "${DPKGBINARY}" ]; then
output=$(${DPKGBINARY} -l ${package} 2> /dev/null | ${GREPBINARY} "^ii")
exit_code=$?
elif [ -n "${EQUERYBINARY}" ]; then
output=$(${EQUERYBINARY} --quiet ${package} > /dev/null 2>&1)
exit_code=$? # 0=package installed, 3=package not installed
elif [ -n "${PACMANBINARY}" ]; then
output=$(${PACMANBINARY} -Qs ${PKG} | grep "local/${PKG} " >/dev/null 2>&1)
exit_code=$? # 0=package installed, 1=package not installed
elif [ -n "${PKG_BINARY}" ]; then
output=$(${PKG_BINARY} -N info ${package} >/dev/null 2>&1)
exit_code=$? # 0=package installed, 70=invalid package
elif [ -n "${PKGINFOBINARY}" ]; then
output=$(${PKGINFOBINARY} -q -e ${package} >/dev/null 2>&1)
exit_code=$? # 0=package installed, 1=package not installed
elif [ -n "${RPMBINARY}" ]; then
output=$(${RPMBINARY} --quiet -q ${package} > /dev/null 2>&1)
exit_code=$?
elif [ -n "${SWUPDBINARY}" ]; then
output=$(${SWUPDBINARY} bundle-list > /dev/null 2>&1 | ${GREPBINARY} "^${package}$")
exit_code=$?
elif [ -n "${ZYPPERBINARY}" ]; then
output=$(${ZYPPERBINARY} --quiet --non-interactive search --installed -i ${package} 2> /dev/null | grep "^i")
if [ -n "${output}" ]; then exit_code=0; else exit_code=1; fi
elif [ -n "${XBPSBINARY}" ]; then
output=$(${XBPSBINARY} ${package} 2> /dev/null | ${GREPBINARY} "^ii")
exit_code=$?
else
if [ "${package}" != "__dummy__" ]; then
ReportException "PackageIsInstalled:01 (test=${TEST_NO:-unknown})"
fi
fi
# Give thumbs up if dummy package is used during initial test for package manager availability
if [ "${package}" = "__dummy__" ]; then
# There should be no positive match on this dummy package
if [ ${exit_code} -eq 0 ]; then
exit_code=1
elif [ ${exit_code} -eq 255 ]; then
exit_code=1
else
exit_code=0
fi
fi
return ${exit_code}
}
################################################################################
# Name : ParseProfiles()
# Description : Check file permissions and parse data from profiles
# Parameters : <none>
# Returns : <nothing>
################################################################################
ParseProfiles() {
SafePerms ${INCLUDEDIR}/profiles
. ${INCLUDEDIR}/profiles
}
################################################################################
# Name : ParseTestValues()
# Description : Parse values from a specific test
#
# Parameters : $1 = service (e.g. ssh)
# Returns : CHECK_VALUES_ARRAY variable
################################################################################
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
#
# Parameters : $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)
sed 's#\\$##g' ${TMP_NGINX_FILE_RAW} | grep -v "^#" | grep -v "^$" > ${TMP_NGINX_FILE}
LogText "Action: parsing configuration file ${CONFIG_FILE}"
COUNT=$(( COUNT + 1))
FIND=$(sed 's/ /:space:/g' ${TMP_NGINX_FILE})
DEPTH=0
for I in ${FIND}; do
I=$(echo ${I} | sed 's/:space:/ /g' | sed 's/;$//' | sed 's/ #.*$//')
OPTION=$(echo ${I} | awk '{ print $1 }')
# Use quotes here to prevent wildcard expansion
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 }')
# Find both, log files provided with full or relative path
if [ ! -f ${VALUE} -a ! -f "${CONFIG_FILE%nginx.conf}${VALUE}" ]; then
LogText "Result: could not find log file ${VALUE} referenced 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
# Find both, log files provided with full or relative path
if [ ! -f ${FILE} -a ! -f "${CONFIG_FILE%nginx.conf}${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
# Check if include value is a relative path only
elif [ -f "${CONFIG_FILE%nginx.conf}${VALUE%;*}" ]; then
VALUE="${CONFIG_FILE%nginx.conf}${VALUE}"
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
# Check for additional config files included as follows
# "include sites-enabled/*.conf" (relative path)
# "include /etc/nginx/sites-enabled/*.conf" (absolute path)
elif [ $(echo "${VALUE}" | grep -F -c "*.conf") -gt 0 ]; then
# Check if path is absolute or relative
case $VALUE in
/*)
# Absolute path, so wildcard pattern is already correct
CONF_WILDCARD=${VALUE%;*}
;;
*)
# Relative path, so construct absolute path for wildcard pattern
CONF_WILDCARD=${CONFIG_FILE%nginx.conf}${VALUE%;*}
;;
esac
for FOUND_CONF in ${CONF_WILDCARD}; do
if [ "${FOUND_CONF}" = "${CONF_WILDCARD}" ]; then
LogText "Found no match for wildcard pattern: ${CONF_WILDCARD}"
break
fi
FOUND=0
for CONF in ${NGINX_CONF_FILES}; do
if [ "${CONF}" = "${FOUND_CONF}" ]; 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}" = "${FOUND_CONF}" ]; 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} ${FOUND_CONF}"; fi
done
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" | "tlsv1")
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
#
# Parameters : $1 = protocol
# $2 = port
# Returns : exit code (0 = listening, 1 = not listening, 255 = can't perform test)
# Usage : if PortIsListening "TCP" 22; then echo "Port is listening"; fi
################################################################################
PortIsListening() {
if [ -z "${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}${LSOF_EXTRA_OPTIONS} -i${1} -s${1}:LISTEN -P -n | grep ":${2} "); else FIND=$(${LSOFBINARY}${LSOF_EXTRA_OPTIONS} -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
#
# Parameters : $1 = --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
#
# Parameters : $1 = number (amount of characters, optional)
# Returns : RANDOMSTRING variable
# 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 : Readonly()
# Description : Mark a variable as read-only data
#
# Returns : <nothing>
# Notes : new function, not in use yet
################################################################################
Readonly() {
if [ $# -eq 1 ]; then
if type -t typeset; then
typeset -r $1
else
Debug "No typeset available to mark variable '$1' as read-only variable"
fi
else
ExitFatal "Expected 1 parameter, received none or multiple"
fi
}
################################################################################
# Name : Register()
# Description : Register a test and see if it has to be run
#
# Parameters : multiple, see test
# Returns : SKIPTEST (0 or 1)
################################################################################
GetTimestamp() {
ts=0
case "${OS}" in
"Linux")
ts=$(date "+%s%N")
;;
*)
ts=$(date "+%s")
;;
esac
echo $ts
}
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=""; PREQS_MET=""
TEST_CATEGORY=""; TEST_NEED_NETWORK=""; TEST_NEED_OS=""; TEST_NEED_PKG_MGR=0; 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
;;
--package-manager-required)
TEST_NEED_PKG_MGR=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
# Measure timing
CURRENT_TS=$(GetTimestamp)
if [ ${PREVIOUS_TS} -gt 0 ]; then
SLOW_TEST=0
TIME_THRESHOLD=$SLOW_TEST_THRESHOLD # seconds
# Calculate timing and determine if we use seconds or nanoseconds (more precise)
TIME_DIFF=$((CURRENT_TS - PREVIOUS_TS))
if [ ${CURRENT_TS} -gt 1000000000000000000 ]; then
TIME_DIFF_FORMAT="nanoseconds"
TIME_THRESHOLD=$((TIME_THRESHOLD * 1000000000))
if [ ${TIME_DIFF} -gt ${TIME_THRESHOLD} ]; then
SLOW_TEST=1
# Convert back to seconds for readability
TIME_DIFF_FORMAT="seconds"
TIME_DIFF=$(echo ${TIME_DIFF} | ${AWKBINARY} '{printf "%f",$1/1000000000}')
fi
else
TIME_DIFF_FORMAT="seconds"
if [ ${TIME_DIFF} -gt ${TIME_THRESHOLD} ]; then
SLOW_TEST=1
fi
fi
if [ ${SLOW_TEST} -eq 1 ]; then
DisplayWarning "Test ${PREVIOUS_TEST} had a long execution: ${TIME_DIFF} ${TIME_DIFF_FORMAT}"
Report "slow_test[]=${PREVIOUS_TEST},${TIME_DIFF}"
fi
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 '[: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
# Test if our OS is the same as the requested OS (can be multiple values)
if [ ${SKIPTEST} -eq 0 -a -n "${TEST_NEED_OS}" ]; then
HASMATCH=0
for I in ${TEST_NEED_OS}; do
if [ "${I}" = "${OS}" ]; then HASMATCH=1; fi
done
if [ ${HASMATCH} -eq 0 ]; then
SKIPTEST=1; SKIPREASON="Incorrect guest OS (${TEST_NEED_OS} only)"
if [ ${LOG_INCORRECT_OS} -eq 0 ]; then SKIPLOGTEST=1; fi
fi
fi
# Skip test when it belongs to another category (default is 'all')
if [ ${SKIPTEST} -eq 0 -a -n "${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 -n "${TEST_NEED_PLATFORM}" -a ! "${HARDWARE}" = "${TEST_NEED_PLATFORM}" ]; then SKIPTEST=1; SKIPREASON="Incorrect hardware platform"; fi
# Check for required (and discovered) package manager
if [ ${SKIPTEST} -eq 0 -a ${TEST_NEED_PKG_MGR} -eq 1 -a ${HAS_PACKAGE_MANAGER} -eq 0 ]; then SKIPTEST=1; SKIPREASON="Requires a known package manager to test presence of a particular package"; fi
# Not all prerequisites met, like missing tool
if [ ${SKIPTEST} -eq 0 -a "${PREQS_MET}" = "NO" ]; then SKIPTEST=1; if [ -z "${SKIPREASON}" ]; then SKIPREASON="Prerequisites 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
# Save timestamp for next time the Register function is called
PREVIOUS_TEST="${TEST_NO}"
PREVIOUS_TS="${CURRENT_TS}"
}
################################################################################
# Name : RemoveColors()
# Description : Disabled colors by overriding them (defined in include/consts)
#
# Notes : Can be activated using --no-colors
################################################################################
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=""
BG_WARNING=""
# Semantic names
BAD=""
BOLD=""
HEADER=""
NORMAL=""
NOTICE=""
OK=""
WARNING=""
SECTION=""
}
################################################################################
# Name : RemovePIDFile()
# Description : When defined, remove the file storing the process ID
#
# Parameters : <none>
# Returns : <nothing>
################################################################################
# Remove PID file
RemovePIDFile() {
# Test if PIDFILE is defined, before checking file presence
if [ -n "${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()
# Description : When created, delete any temporary file
################################################################################
# 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} | grep -E "^/tmp/lynis" | grep -v "\.\.")
if [ -n "${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()
# Description : Store data in the report file
#
# Parameters : $1 = data (format: key=value)
# Returns : <nothing>
################################################################################
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.
#
# Parameters : <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()
# Description : Store an exceptional event in the report
#
# Parameters : $1 = test ID + colon + 2 numeric characters (TEST-1234:01)
# $2 = string (text)
# Notes : Allow this function with only 1 parameter in strict mode with ${2-Text}
################################################################################
ReportException() {
Report "exception_event[]=$1|${2-NoInfo}|"
LogText "Exception: test has an exceptional event ($1) with text ${2-NoText}"
if [ ${QUIET} -eq 0 ]; then
DisplayException "$1" "$2"
fi
}
################################################################################
# Name : ReportManual()
# Description : Add an item to the report that requires manual intervention
#
# Parameters : $1 = string (text)
################################################################################
ReportManual() {
Report "manual_event[]=$1"
LogText "Manual: one or more manual actions are required for further testing of this control/plugin"
}
################################################################################
# Name : ReportSuggestion()
# Description : Log a suggestion to the report file
#
# Parameters : <ID> <Suggestion> <Details> <Solution>
# $1 = Test ID - Lynis ID (use CUST-.... for your own tests)
# $2 = Suggestion - Suggestion text to be displayed
# $3 = Details - Specific item or details
# $4 = Solution - Optional link for additional information:
# * url:https://example.org/how-to-solve-link
# * text:Additional explanation
# * - (dash) for none
# Returns : <nothing>
################################################################################
ReportSuggestion() {
TOTAL_SUGGESTIONS=$((TOTAL_SUGGESTIONS + 1))
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()
# Description : Log a warning to the report file
#
# Parameters : $1 = test ID
# $2 = message (optional)
# $3 = details (optional)
# $4 = possible solution (optional)
# Returns : <nothing>
################################################################################
ReportWarning() {
TOTAL_WARNINGS=$((TOTAL_WARNINGS + 1))
# Old style used L/M/H level as second parameter. This is deprecated as of 3.x
if [ "$2" = "L" -o "$2" = "M" -o "$2" = "H" ]; then
ExitFatal "Deprecated usage of ReportWarning() function"
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 : SafeInput()
# Description : Test provided string to see if it contains unwanted characters
#
# Input : string + optional class (parameter 2)
# Returns : exit code (0 = input considered to be safe, 1 = validation failed)
################################################################################
SafeInput() {
exitcode=1
# By default remove only control characters
if [ $# -eq 1 ]; then
input="$1"
cleaned=$(echo ${input} | tr -d '[:cntrl:]')
# If know what to test against, then see if input matches the specified class
elif [ $# -eq 2 ]; then
input="$1"
testchars="$2"
cleaned=$(echo $1 | tr -cd "${testchars}")
else
ExitFatal "No argument or too many arguments provided to SafeInput()"
fi
if [ "${cleaned}" = "${input}" ]; then
exitcode=0
fi
return ${exitcode}
}
################################################################################
# Name : SafeFile()
# Description : Check if a file is safe to use
#
# Parameters : $1 = file name
# Returns : exit code (0 = OK, 1 = issue)
################################################################################
SafeFile() {
unsafe=0
if [ $# -ne 1 ]; then
ExitFatal "No argument or too many arguments provided to SafeFile()"
else
FILE="$1"
# Generic checks
if [ -g "${FILE}" ]; then
LogText "Security alert: file has setgid attribute"
unsafe=1
# sticky bit
elif [ -k "${FILE}" ]; then
LogText "Security alert: file has sticky bit"
unsafe=1
# symbolic link
elif [ -L "${FILE}" ]; then
LogText "Security alert: file is a symbolic link"
unsafe=1
elif [ -f "${FILE}" ]; then
LogText "Security check: file is normal"
else
unsafe=1
fi
# Perform additional checks based on privilege level
if [ ${PRIVILEGED} -eq 0 ]; then
# File is not owned by active user, but still able to write
if [ ! -O "${FILE}" -a -w "${FILE}" ]; then
unsafe=1
LogText "Security alert: file is not owned by active user, but can write to it"
fi
fi
# Check file permissions
if ! SafePerms "${FILE}"; then
unsafe=1
fi
fi
return ${unsafe}
}
################################################################################
# Name : SafePerms()
# Description : Check if a file has safe permissions to be used
#
# Parameters : $1 = file name
# $2 = type of file (optional)
# Returns : 0 (file permissions OK) or break
################################################################################
SafePerms() {
exitcode=1
IS_PARAMETERS=0
IS_PROFILE=0
if [ ${IGNORE_FILE_PERMISSION_ISSUES} -eq 0 ]; then
PERMS_OK=0
LogText "Checking permissions of $1"
if [ $# -gt 0 ]; then
if [ $# -eq 2 ]; then
case "$2" in
"parameters")
IS_PARAMETERS=1
;;
"profile")
IS_PROFILE=1
;;
esac
else
FIND=$(echo $1 | grep "/parameters")
if [ $? -eq 0 ]; then IS_PARAMETERS=1; fi
fi
# Check file permissions
if [ ! -f "$1" ]; then
LogText "Fatal error: file $1 does not exist."
ExitFatal "Fatal error: file $1 does not exist"
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} -eq 0 ]; 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} -eq 0 ]; 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 u=rw $1"
ExitFatal
fi
# Group permissions
# TODO - harden this even more by setting default to read-only for group (like 'other')
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 g=r $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 o=r $1"
ExitFatal
fi
# Set PERMS_OK to 1 if no fatal errors occurred
PERMS_OK=1
LogText "File permissions are OK"
exitcode=0
fi
else
ReportException "SafePerms()" "Invalid number of arguments for function"
fi
else
PERMS_OK=1
exitcode=0
fi
return ${exitcode}
}
################################################################################
# Name : SearchItem()
# Description : Search if a specific string exists in in a file
#
# Parameters : $1 = search key (string)
# $2 = file (string)
# $3 = optional arguments:
# --sensitive - don't store results in log
# Returns : True (0) or False (1)
################################################################################
SearchItem() {
PERFORM_SCAN=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=$(grep -E "${STRING}" ${FILE})
if [ -n "${FIND}" ]; then
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}
}
################################################################################
# Name : ShowComplianceFinding()
# Description : Display a section of a compliance standard which is not fulfilled
#
# Parameters : <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
#
# Parameters : $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 | grep -v "no [^ ]* in ")
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 | grep -v "no [^ ]* in ")
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 item in ${SKIP_TESTS}; do
STRING=$(echo $1 | tr '[:lower:]' '[:upper:]')
if [ "${item}" = "${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 : Status()
# Description : Reports back the status of tool
#
# Returns : text to screen
# Notes : kill --signal USR1 <PID> or pkill --signal USR1 lynis
################################################################################
Status() {
echo ""
echo "Date / time : $(date "+%Y-%m-%d %H:%M:%S")"
echo "Active test : ${TEST_NO:-NONE}"
echo ""
}
################################################################################
# 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} | grep -E "${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
ExitClean
}
################################################################################
# Name : ViewGroups()
# Description : Show what group of tests are available
#
# Input : <nothing>
# Returns : <nothing>
# Usage : ViewGroups
################################################################################
ViewGroups() {
if [ -n "${INCLUDEDIR}" ]; then
for I in $(ls ${INCLUDEDIR}/tests_* | xargs -n 1 basename | sed 's/tests_//' | grep -v "custom.template"); do
echo "${I}"
done
fi
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 -r void
fi
}
################################################################################
#
# 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 errors on screen.
################################################################################
counttests() {
DisplayWarning "Deprecated function used (counttests)"
if IsDeveloperMode; then Debug "Warning: old counttests function is used. Please replace any reference with CountTests."; fi
CountTests
}
logtext() {
DisplayWarning "Deprecated function used (logtext)"
if IsDeveloperMode; then Debug "Warning: old logtext function is used. Please replace any reference with LogText."; fi
LogText "$1"
}
logtextbreak() {
DisplayWarning "Deprecated function used (logtextbreak)"
if IsDeveloperMode; then Debug "Warning: old logtextbreak function is used. Please replace any reference with LogTextBreak."; fi
LogTextBreak "$1"
}
report() {
DisplayWarning "Deprecated function used (report)"
if IsDeveloperMode; then Debug "Warning: old report function is used. Please replace any reference with Report."; fi
Report "$1"
}
wait_for_keypress() {
DisplayWarning "Deprecated function used (wait_for_keypress)"
if IsDeveloperMode; then Debug "Warning: old wait_for_keypress function is used. Please replace any reference with WaitForKeyPress."; fi
WaitForKeyPress
}
ShowResult() {
DisplayWarning "Deprecated function used (ShowResult)"
if IsDeveloperMode; then Debug "Warning: old ShowResult() function is used. Please replace any reference with WaitForKeyPress."; fi
}
#================================================================================
# Lynis is part of Lynis Enterprise and released under GPLv3 license
# Copyright 2007-2020 - Michael Boelen, CISOfy - https://cisofy.com