#!/bin/sh ################################################################################# # # Lynis # ------------------ # # Copyright 2007-2013, Michael Boelen # Copyright 2007-2021, 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 : # 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 : # 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 : # 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 : # 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 : # Returns : # 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 : # Returns : # 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 : # 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 : # 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 : # Returns : ################################################################################ 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 : ################################################################################ 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 : # 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 : ################################################################################ DisplayManual() { if [ ${QUIET} -eq 0 ]; then ${ECHOCMD} "$1"; fi } ################################################################################ # Name : DisplayToolTip() # Description : Show tooltip on screen # # Input : $1 = text # Returns : ################################################################################ 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 : ################################################################################ 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 : # Returns : # 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 : # 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 : # 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 Debug "Skipping HostID generation due to SKIP_GETHOSTID" return 2 fi if [ -n "${HOSTID}" -a -n "${HOSTID2}" ]; then Debug "Skipping creation of host identifiers, as they are already configured (via profile)" HOSTID_GEN="profile" return 2 fi if [ -f "${ROOTDIR}etc/lynis/hostids" ]; then HOSTID=$(grep "^hostid=" ${ROOTDIR}etc/lynis/hostids | awk -F= '{print $2}') HOSTID2=$(grep "^hostid2=" ${ROOTDIR}etc/lynis/hostids | awk -F= '{print $2}') Debug "Used hostids file to fetch values" HOSTID_GEN="hostids-file" 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 LogText "Info: found hashing tool, start generation of HostID" 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") # Try fetching information from /sys in case 'ip' is not available or does not give expected results if IsEmpty "${FIND}" && [ -d /sys/class/net ]; then NET_INTERFACES=$(${FINDBINARY} /sys/class/net ! -type d -exec realpath {} \; 2> /dev/null | sort | awk -F'/' '!/virtual/ && /devices/ {for (x=1;x<=NF;x++) if ($x~"net") print $(x+1)}') for INTERFACE in ${NET_INTERFACES}; do if grep -q -s 'up' "/sys/class/net/${INTERFACE}/operstate"; then LogText "Interface '${INTERFACE}' is up, fetching MAC address" FIND=$(head -1 "/sys/class/net/${INTERFACE}/address" | tr '[:upper:]' '[:lower:]') if HasData "${FIND}"; then HOSTID_GEN="linux-sys-interface-up" break fi fi done fi # Next is to try ip, as it is available to most modern Linux distributions if IsEmpty "${FIND}" && [ -n "${IPBINARY}" ]; then LogText "Info: trying output from 'ip' to generate HostID" # Determine if we have the common available eth0 interface. If so, give that priority. # Note: apply sorting in case there would be multiple MAC addresses linked to increase predictable end result FIND=$(${IPBINARY} addr show eth0 2> /dev/null | grep -E "link/ether " | awk '{ print $2 }' | tr '[:upper:]' '[:lower:]' | sort | head -1) if HasData "${FIND}"; then HOSTID_GEN="linux-ip-interface-eth0" else # If eth0 does not exist, which is also common, then trying the next option: # 1) First fetch all links that are UP # 2) Filter entries that have a MAC address and filter out Docker related MAC addresses starting with '02:42:' # 3) Convert everything to lowercase # 4) Sort the entries, so that the output is more predictable between runs when the same interfaces are available # 5) Select first entry FIND=$(${IPBINARY} -family link addr show up 2> /dev/null | awk '{if($1=="link/ether" && $2 !~ "^02:42:"){print $2}}' | tr '[:upper:]' '[:lower:]' | sort | head -1) if HasData "${FIND}"; then HOSTID_GEN="linux-ip-interface-up-other" else ReportException "GetHostID" "Can't create hostid (no MAC addresses found)" fi fi fi # Finally try ifconfig if IsEmpty "${FIND}" && [ -n "${IFCONFIGBINARY}" ]; then LogText "Info: no information found from 'ip' or in /sys, trying output from 'ifconfig'" # Determine if we have the eth0 interface (not all Linux distributions 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:]') if HasData "${FIND}"; then HOSTID_GEN="linux-ifconfig-interface-eth0-ether" fi 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 HOSTID_GEN="linux-ifconfig-interface-first-ether" LogText "Result: No eth0 found (but 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:]') HOSTID_GEN="linux-ifconfig-interface-first-hwaddr" fi else HOSTID_GEN="linux-ifconfig-interface-eth0-hwaddr" fi fi # Check if we found a MAC address to generate the HostID if HasData "${FIND}"; then LogText "Info: using hardware address '${FIND}' to create HostID" HOSTID=$(echo ${FIND} | ${SHA1SUMBINARY} | awk '{ print $1 }') LogText "Result: Found HostID: ${HOSTID}" else ReportException "GetHostID" "HostID could not be generated" 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 [ -n "${SHA1SUMBINARY}" ]; then HOSTID=$(echo ${FIND} | ${SHA1SUMBINARY} | awk '{ print $1 }') elif [ -n "${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 [ -n "${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}" HOSTID_GEN="fallback-ssh-public-key" 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 # Generation of HostID version 2 if [ -z "${HOSTID2}" ]; then LogText "Info: start generation of 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 as candidate to create hostid2" 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 hostid2" STRING_TO_HASH="${DATA_SSH}" HOSTID2_GEN="ssh-public-key" else if [ -n "${MACHINEID}" ]; then LogText "Using the machine ID to create hostid2" STRING_TO_HASH="${MACHINEID}" HOSTID2_GEN="machine-id" fi fi # Check if we have a string to turn into a host identifier if [ -n "${STRING_TO_HASH}" ]; then # Create hashes if [ -n "${SHA256SUMBINARY}" ]; then HASH2=$(echo ${STRING_TO_HASH} | ${SHA256SUMBINARY} | awk '{ print $1 }') HASH_HOSTNAME=$(echo ${HOSTNAME} | ${SHA256SUMBINARY} | awk '{ print $1 }') elif [ -n "${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" # Check for symlink if [ -L ${CHECKFILE} ]; then ShowSymlinkPath ${CHECKFILE} if [ ! "${SYMLINK}" = "" ]; then CHECKFILE="${SYMLINK}"; fi fi 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} (${CHECKPERMISSION_FULL}) 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} != ${CHECK_PERMISSION})" # 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 : # 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 : # 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 : # 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 : # 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 : # 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 : # 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 : # 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 : # 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="" # Check for symlink if [ -L ${sFILE} ]; then ShowSymlinkPath ${sFILE} if [ ! "${SYMLINK}" = "" ]; then sFILE="${SYMLINK}"; fi fi # 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 : # 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 : ################################################################################ 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=$? elif [ -n "${APKBINARY}" ]; then output=$(${APKBINARY} search ${package} 2> /dev/null | ${GREPBINARY} ${package}) 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 : # Returns : ################################################################################ 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 : ################################################################################ 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 : # 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 : # 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 # Detect if the implementation of date supports nanoseconds, if [ "${OS}" = "Linux" ]; then current_nanoseconds=$(date "+%N") # Verify if the result of the command is a number if [ -n "$current_nanoseconds" ] && [ "$current_nanoseconds" -eq "$current_nanoseconds" ] 2>/dev/null; then ts=$(date "+%s%N") else ts=$(date "+%s") fi else ts=$(date "+%s") fi 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}" ]; then HASMATCH=0 for I in ${TEST_NEED_PLATFORM}; do if [ "${I}" = "${HARDWARE}" ]; then HASMATCH=1; fi done if [ ${HASMATCH} -eq 0 ]; then SKIPTEST=1; SKIPREASON="Incorrect hardware platform (${TEST_NEED_PLATFORM} only)" fi 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 : # Returns : ################################################################################ # 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 : ################################################################################ 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 : # Returns : # 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 :
# $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 : ################################################################################ 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 : ################################################################################ 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: #
# # Lynis ID (use CUST-.... for your own tests) # Warning text to be displayed #
Specific item or details # 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 : # Returns : ################################################################################ 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 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 : ################################################################################ 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 --search # 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 : # Returns : # 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 : # Returns : # 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 : # Returns : # 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-2021 - Michael Boelen, CISOfy - https://cisofy.com