mirror of
https://github.com/CISOfy/lynis.git
synced 2025-09-26 11:19:27 +02:00
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>
3689 lines
154 KiB
Bash
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
|