/
usr
/
share
/
lynis
/
include
/
/usr/share/lynis/include
mkdir
upload
Name
Size
Mode
Actions
binaries
39476
0644
edit
dl
rm
consts
10898
0644
edit
dl
rm
data_upload
13788
0644
edit
dl
rm
functions
163241
0644
edit
dl
rm
helper_audit_dockerfile
8000
0644
edit
dl
rm
helper_configure
3748
0644
edit
dl
rm
helper_generate
7557
0644
edit
dl
rm
helper_show
22820
0644
edit
dl
rm
helper_system_remote_scan
3620
0644
edit
dl
rm
helper_update
3671
0644
edit
dl
rm
osdetection
48570
0644
edit
dl
rm
parameters
16874
0644
edit
dl
rm
profiles
27540
0644
edit
dl
rm
report
18915
0644
edit
dl
rm
tests_accounting
25852
0644
edit
dl
rm
tests_authentication
86063
0644
edit
dl
rm
tests_banners
8562
0644
edit
dl
rm
tests_boot_services
56034
0644
edit
dl
rm
tests_containers
11439
0644
edit
dl
rm
tests_crypto
19456
0644
edit
dl
rm
tests_custom.template
6942
0644
edit
dl
rm
tests_databases
24863
0644
edit
dl
rm
tests_dns
3467
0644
edit
dl
rm
tests_filesystems
50995
0644
edit
dl
rm
tests_file_integrity
22714
0644
edit
dl
rm
tests_file_permissions
3185
0644
edit
dl
rm
tests_firewalls
32270
0644
edit
dl
rm
tests_hardening
7187
0644
edit
dl
rm
tests_homedirs
9392
0644
edit
dl
rm
tests_insecure_services
27528
0644
edit
dl
rm
tests_kerberos
8063
0644
edit
dl
rm
tests_kernel
61403
0644
edit
dl
rm
tests_kernel_hardening
5752
0644
edit
dl
rm
tests_ldap
4055
0644
edit
dl
rm
tests_logging
33726
0644
edit
dl
rm
tests_mac_frameworks
15067
0644
edit
dl
rm
tests_mail_messaging
21967
0644
edit
dl
rm
tests_malware
21101
0644
edit
dl
rm
tests_memory_processes
7334
0644
edit
dl
rm
tests_nameservices
35361
0644
edit
dl
rm
tests_networking
41705
0644
edit
dl
rm
tests_php
29772
0644
edit
dl
rm
tests_ports_packages
83692
0644
edit
dl
rm
tests_printers_spoolers
14188
0644
edit
dl
rm
tests_scheduling
16131
0644
edit
dl
rm
tests_shells
13643
0644
edit
dl
rm
tests_snmp
4273
0644
edit
dl
rm
tests_squid
17156
0644
edit
dl
rm
tests_ssh
18026
0644
edit
dl
rm
tests_storage
3680
0644
edit
dl
rm
tests_storage_nfs
8603
0644
edit
dl
rm
tests_system_integrity
2098
0644
edit
dl
rm
tests_time
33413
0644
edit
dl
rm
tests_tooling
22624
0644
edit
dl
rm
tests_usb
21554
0644
edit
dl
rm
tests_virtualization
1992
0644
edit
dl
rm
tests_webservers
33977
0644
edit
dl
rm
tool_tips
2205
0644
edit
dl
rm
Edit:
/usr/share/lynis/include/functions
(163241B)
#!/bin/sh ################################################################################# # # Lynis # ------------------ # # Copyright (c) Michael Boelen, CISOfy, and many contributors. # # Website : https://cisofy.com/ # Blog : https://linux-audit.com/ # GitHub : https://github.com/CISOfy/lynis # # Lynis comes with ABSOLUTELY NO WARRANTY. This is free software, and you are # welcome to redistribute it under the terms of the GNU General Public License. # See LICENSE file for usage of this software. # ################################################################################# # # Functions # ################################################################################# # # Function Description # ----------------------- ------------------------------------------------- # AddHP Add Hardening points to plot a graph later # AddSetting Addition of setting # AddSystemGroup Adds a system to a group # CheckFilePermissions Check file permissions # CheckItem Test for presence of a string in report file # CheckUpdates Determine if a new version of Lynis is available # CleanUp Clean up files before closing program # CountTests Count number of performed tests # ContainsString Find the needle (string) in the haystack (another string) # CreateTempFile Create a temporary file # Debug Display additional information on the screen (not suited for cronjob) # DigitsOnly Return only the digits from a string # DirectoryExists Check if a directory exists on the disk # DiscoverProfiles Determine available profiles on system # Display Output text to screen with colors and indentation # DisplayError Show an error on screen # DisplayException Show an exception on screen # DisplayManual Output text to screen without any layout # DisplayToolTip Show a tip for improving usage of the tool # DisplayWarning Show a clear warning on screen # Equals Compares two strings # ExitClean Stop the program (cleanly), with exit code 0 # ExitCustom Stop the program (cleanly), with custom exit code # ExitFatal Stop the program (cleanly), with exit code 1 # FileExists Check if a file exists on the disk # FileInstalledByPackage Check if a file is linked to a package # FileIsEmpty Check if a file is empty # FileIsReadable Check if a file is readable or directory accessible # GetHostID Retrieve an unique ID for this host # GetReportData Request data from report # HasCorrectFilePermissions Check file permissions and see if they match expected values # HasData Checks for data in variable # InsertSection Insert a section block # InsertPluginSection Insert a section block for plugins # IsContainer Determine if program runs in a container # IsDebug Check if --debug is used # IsDeveloperMode Check if --developer is used # IsDeveloperVersion Check if program is a developer release # IsEmpty Check for empty result or variable # IsNotebook System detection # IsOwnedByRoot Determine if file or directory is owned by root # IsRunning Check if a process is running # IsVerbose Check if --verbose is used # IsVirtualMachine Check if this system is a virtual machine # IsWorldExecutable Check if a file is world executable # IsWorldReadable Check if a file is world readable # IsWorldWritable Check if a file is world writable # LogText Log text strings to logfile, prefixed with date/time # LogTextBreak Insert a separator in log file # PackageIsInstalled Test for installed package # ParseNginx Parse nginx configuration lines # ParseProfiles Parse all available profiles # ParseTestValues Parse a set of values # PortIsListening Check if machine is listening on specified protocol and port # Progress Show progress on screen # Readonly Mark a variable as read-only data # Register Register a test (for logging and execution) # RandomString Show a random string # RemoveColors Reset all colors # RemovePIDFile Remove PID file # RemoveTempFiles Remove temporary files # Report Add string of data to report file # ReportDetails Store details of tests which include smaller atomic tests in report # ReportException Add an exception to the report file (for debugging purposes) # ReportManual Log manual actions to report file # ReportSuggestion Add a suggestion to report file # ReportWarning Add a warning and priority to report file # SafeFile Security tests to perform on a file before using it # SafePerms Check if a file has safe permissions # SafeInput Test provided string to see if it contains unwanted characters # SearchItem Search a string in a file # ShowComplianceFinding Display a particular finding regarding compliance or a security standard # ShowSymlinkPath Show a path behind a symlink # SkipAtomicTest Test if a subtest needs to be skipped # Status Show execution status, such as active test being performed # StoreNginxSettings Save parsed nginx settings to file # TestValue Evaluate a value in a string or key # ViewCategories Show available category of tests # ViewGroups Display test groups # WaitForKeyPress Wait for user to press a key to continue # ################################################################################# ################################################################################ # Name : AddHP() # Description : Add hardening points and count them # # Parameters : $1 = points to add (0 or higher) # $2 = maximum points (at least value of $1 or higher) # Returns : <nothing> # Usage : AddHP 1 3 ################################################################################ AddHP() { HPADD=$1; HPADDMAX=$2 HPPOINTS=$((HPPOINTS + HPADD)) HPTOTAL=$((HPTOTAL + HPADDMAX)) if [ ${HPADD} -eq ${HPADDMAX} ]; then LogText "Hardening: assigned maximum number of hardening points for this item (${HPADDMAX}). Currently having ${HPPOINTS} points (out of ${HPTOTAL})" else LogText "Hardening: assigned partial number of hardening points (${HPADD} of ${HPADDMAX}). Currently having ${HPPOINTS} points (out of ${HPTOTAL})" fi } ################################################################################ # Name : AddSetting() # Description : Addition of a setting for display with 'lynis show settings' # # Parameters : $1 = setting # $2 = value # $3 = description # Returns : <nothing> # Usage : AddSetting debug 1 'Debug mode' ################################################################################ AddSetting() { if [ $# -eq 3 ]; then SETTING="$1" VALUE="$2" DESCRIPTION="$3" if [ -z "${SETTINGS_FILE}" ]; then CreateTempFile SETTINGS_FILE="${TEMP_FILE}" fi FIND=$(grep -E "^${SETTING};" ${SETTINGS_FILE}) if [ -z "${FIND}" ]; then echo "${SETTING};${VALUE};${DESCRIPTION};" >> ${SETTINGS_FILE} else Debug "Setting '${SETTING}' was already configured, overwriting previous line '${FIND}' in ${SETTINGS_FILE} with value '${VALUE}'" # Delete line first, then add new value (inline search and replace is messy) CreateTempFile TEMP_SETTINGS_FILE="${TEMP_FILE}" cat ${SETTINGS_FILE} > ${TEMP_SETTINGS_FILE} sed -e '/^'"${SETTING}"';/d' ${TEMP_SETTINGS_FILE} > ${SETTINGS_FILE} rm "${TEMP_SETTINGS_FILE}" echo "${SETTING};${VALUE};${DESCRIPTION};" >> ${SETTINGS_FILE} fi else echo "Error: incorrect call to AddSetting. Needs 3 arguments." fi } ################################################################################ # Name : AddSystemGroup() # Description : Adds a system to a group, which can be used for categorizing # # Parameters : $1 = group name # Returns : <nothing> # Usage : AddSystemGroup "test" ################################################################################ AddSystemGroup() { Report "system_group[]=$1" } ################################################################################ # Name : CheckFilePermissions() # Description : Check file permissions # # Parameters : Full path to file or directory # Returns : PERMS (FILE_NOT_FOUND | OK | BAD) # Notes : This function might be replaced in future ################################################################################ CheckFilePermissions() { CHECKFILE="$1" if [ ! -d ${CHECKFILE} -a ! -f ${CHECKFILE} ]; then PERMS="FILE_NOT_FOUND" FILEVALUE="" else # If 'file' is an directory, use -d if [ -d ${CHECKFILE} ]; then FILEVALUE=$(ls -d -l ${CHECKFILE} | cut -c 2-10) PROFILEVALUE=$(grep '^permdir' ${PROFILE} | grep "=${CHECKFILE}:" | cut -d: -f2) else FILEVALUE=$(ls -l ${CHECKFILE} | cut -c 2-10) PROFILEVALUE=$(grep '^permfile' ${PROFILE} | grep "=${CHECKFILE}:" | cut -d: -f2) fi if [ "${FILEVALUE}" = "${PROFILEVALUE}" ]; then PERMS="OK"; else PERMS="BAD"; fi fi } ################################################################################ # Name : CheckItem() # Description : Check if a specific item exists in the report # # Parameters : $1 = key # $2 = value # Returns : exit code (0 = True, 1 = False) # Usage : if CheckItem "key" "value"; then ....; fi ################################################################################ CheckItem() { RETVAL=255 if [ $# -eq 2 ]; then # Don't search in /dev/null, it's too empty there if [ ! "${REPORTFILE}" = "/dev/null" ]; then # Check if we can find the main type (with or without brackets) LogText "Test: search string $2 in earlier discovered results" FIND=$(grep -E "^$1(\[\])?=" ${REPORTFILE} | grep -E "$2") if HasData "${FIND}"; then RETVAL=0 LogText "Result: found search string (result: $FIND)" else LogText "Result: search string NOT found" RETVAL=1 fi else LogText "Skipping search, as /dev/null is being used" fi return ${RETVAL} else ReportException ${TEST_NO} "Error in function call to CheckItem" fi } ################################################################################ # Name : CheckUpdates() # Description : Determine if there is an update available # # Returns : <nothing> # Usage : CheckUpdates # Use PROGRAM_LV (latest version) and compare it with actual version (PROGRAM_AC) ################################################################################ CheckUpdates() { PROGRAM_LV="0000000000"; DB_MALWARE_LV="0000000000"; DB_FILEPERMS_LV="0000000000" if [ ${RUN_UPDATE_CHECK} -eq 1 ]; then LYNIS_LV_RECORD="lynis-latest-version.cisofy.com." FIND=$(which dig 2> /dev/null | grep -v "no [^ ]* in") if [ -n "${FIND}" ]; then PROGRAM_LV=$(dig +short +time=3 -t txt lynis-latest-version.cisofy.com 2> /dev/null | grep -v "connection timed out" | sed 's/[".]//g' | grep "^[1-9][0-9][0-9]$") else FIND=$(which host 2> /dev/null | grep -v "no [^ ]* in ") if [ -n "${FIND}" ]; then PROGRAM_LV=$(host -t txt -W 3 lynis-latest-version.cisofy.com 2> /dev/null | grep -v "connection timed out" | awk '{ if ($1=="lynis-latest-version.cisofy.com" && $3=="text") { print $4 }}' | sed 's/"//g' | grep "^[1-9][0-9][0-9]$") if [ "${PROGRAM_LV}" = "" ]; then PROGRAM_LV=0; fi else FIND=$(which drill 2> /dev/null | grep -v "no [^ ]* in ") if [ -n "${FIND}" ]; then PROGRAM_LV=$(drill txt ${LYNIS_LV_RECORD} | awk '{ if ($1=="lynis-latest-version.cisofy.com." && $4=="TXT") { print $5 }}' | tr -d '"' | grep "^[1-9][0-9][0-9]$") if [ -z "${PROGRAM_LV}" ]; then PROGRAM_LV=0; fi else LogText "Result: dig, drill or host not installed, update check skipped" UPDATE_CHECK_SKIPPED=1 fi fi fi fi } ################################################################################ # Name : CleanUp() # Description : Delete PID and temporary files, stop execution (exit code 1) # # Parameters : <none> # Returns : <nothing> # Usage : this function is triggered by a manual break by user ################################################################################ CleanUp() { echo ""; echo "Interrupt detected." RemovePIDFile RemoveTempFiles Display --text "Cleaning up..." --result DONE --color GREEN ExitFatal } ################################################################################ # Name : ContainsString() # Description : Search a specific string (or regular expression) in another # # Returns : exit code (0 = True, 1 = False) # Usage : if ContainsString "needle" "there is a needle in the haystack"; echo "Found"; else "Not found"; fi ################################################################################ ContainsString() { RETVAL=1 if [ $# -ne 2 ]; then ReportException "ContainsString" "Incorrect number of arguments for ContainsStrings function"; fi FIND=$(echo "$2" | grep -E "$1") if [ ! "${FIND}" = "" ]; then RETVAL=0; fi return ${RETVAL} } ################################################################################ # Name : CountTests() # Description : Counter for the number of tests performed # # Parameters : <none> # Returns : <nothing> # Usage : Call CountTests to increase number by 1 ################################################################################ CountTests() { CTESTS_PERFORMED=$((CTESTS_PERFORMED + 1)) } ################################################################################ # Name : CreateTempFile() # Description : Creates a temporary file # # Returns : TEMP_FILE (variable) # Usage : CreateTempFile # if [ ! "${TEMP_FILE}" = "" ]; then # MYTMPFILE="${TEMP_FILE}" # echo "My temporary file is ${MYTMPFILE}" # fi ################################################################################ CreateTempFile() { TEMP_FILE="" if [ "${OS}" = "AIX" ]; then RANDOMSTRING1="lynis-$(od -N4 -tu /dev/random | awk 'NR==1 {print $2} {}')" TEMP_FILE="/tmp/${RANDOMSTRING1}" touch ${TEMP_FILE} else TEMP_FILE=$(mktemp /tmp/lynis.XXXXXXXXXX) || exit 1 fi if [ ! "${TEMP_FILE}" = "" ]; then LogText "Action: created temporary file ${TEMP_FILE}" else Fatal "Could not create a temporary file" fi # Add temporary file to queue for cleanup later TEMP_FILES="${TEMP_FILES} ${TEMP_FILE}" } ################################################################################ # Name : DirectoryExists() # Description : Check if a directory exists # # Returns : exit code (0 = True, 1 = False) # Usage : if DirectoryExists; then echo "it exists"; else echo "It does not exist"; fi ################################################################################ # Determine if a directory exists DirectoryExists() { if [ $# -eq 0 ]; then ExitFatal "Missing parameter when calling DirectoryExists function"; fi DIRECTORY_FOUND=0 LogText "Test: checking if directory $1 exists" if [ -d $1 ]; then LogText "Result: directory $1 exists" DIRECTORY_FOUND=1 return 0 else LogText "Result: directory $1 NOT found" return 1 fi } ################################################################################ # Name : Debug() # Description : Show additional information on screen # # Input : $1 = text # Returns : <nothing> # Usage : Debug "More details" ################################################################################ Debug() { if [ ${DEBUG} -eq 1 -a $# -gt 0 ]; then echo "${PURPLE}[DEBUG]${NORMAL} $1"; fi } ################################################################################ # Name : DigitsOnly() # Description : Only extract numbers from a string # # Returns : Digits only string (VALUE) ################################################################################ DigitsOnly() { VALUE=$1 LogText "Value is now: ${VALUE}" if [ ! "${AWKBINARY}" = "" ]; then VALUE=$(echo ${VALUE} | grep -Eo '[0-9]{1,}') fi LogText "Returning value: ${VALUE}" } ################################################################################ # Name : DiscoverProfiles() # Description : Determine which profiles we have available # # Returns : <nothing> # Usage : DiscoverProfiles ################################################################################ DiscoverProfiles() { # Try to find a default and custom profile, unless one was specified manually if [ "${PROFILE}" = "" ]; then CUSTOM_PROFILE="" DEFAULT_PROFILE="" PROFILEDIR="" tPROFILE_NAMES="default.prf custom.prf" if [ ${USE_CWD} -eq 1 ]; then tPROFILE_TARGETS="." else tPROFILE_TARGETS="/usr/local/etc/lynis /etc/lynis /usr/local/lynis ." fi for PNAME in ${tPROFILE_NAMES}; do for PLOC in ${tPROFILE_TARGETS}; do # Only use one default.prf if [ "${PNAME}" = "default.prf" -a ! "${DEFAULT_PROFILE}" = "" ]; then Debug "Already discovered default.prf - skipping this file (${PLOC}/${PNAME})" elif [ "${PNAME}" = "custom.prf" -a ! "${CUSTOM_PROFILE}" = "" ]; then Debug "Already discovered custom.prf - skipping this file (${PLOC}/${PNAME})" else if [ "${PLOC}" = "." ]; then FILE="${WORKDIR}/${PNAME}"; else FILE="${PLOC}/${PNAME}"; fi if [ -r ${FILE} ]; then PROFILES="${PROFILES} ${FILE}" case ${PNAME} in "custom.prf") CUSTOM_PROFILE="${FILE}" ;; "default.prf") DEFAULT_PROFILE="${FILE}" ;; esac # Set profile directory to last match (Lynis could be both installed, and run as a separate download) if [ "${PLOC}" = "." ]; then PROFILEDIR="${WORKDIR}"; else PROFILEDIR="${PLOC}"; fi fi fi done done # Search any profiles defined with --profile for FILE in ${SEARCH_PROFILES}; do if [ -r "${FILE}" ]; then Debug "Found profile defined with --profile" PROFILES="${PROFILES} ${FILE}" else ExitFatal "Could not find or read profile (${FILE})" fi done fi if [ "${PROFILES}" = "" ]; then echo "${RED}Fatal error: ${WHITE}No profile defined and could not find default profile${NORMAL}" echo "Search paths used --> ${tPROFILE_TARGETS}" ExitCustom 66 else PROFILES=$(echo ${PROFILES} | sed 's/^ //') fi } ################################################################################ # Name : Display() # Description : Show text on screen, with markup # # Input : <multiple parameters, see test> # Returns : <nothing> ################################################################################ Display() { INDENT=0; TEXT=""; RESULT=""; COLOR=""; SPACES=0; SHOWDEBUG=0 while [ $# -ge 1 ]; do case $1 in --color) shift case $1 in GREEN) COLOR=$GREEN ;; RED) COLOR=$RED ;; WHITE) COLOR=$WHITE ;; YELLOW) COLOR=$YELLOW ;; esac ;; --debug) SHOWDEBUG=1 ;; --indent) shift INDENT=$1 ;; --result) shift RESULT=$1 ;; --text) shift TEXT=$1 ;; *) echo "INVALID OPTION (Display): $1" ExitFatal ;; esac # Go to next parameter shift done if [ -z "${RESULT}" ]; then RESULTPART="" else if [ ${CRONJOB} -eq 0 ]; then RESULTPART=" [ ${COLOR}${RESULT}${NORMAL} ]" else RESULTPART=" [ ${RESULT} ]" fi fi if [ -n "${TEXT}" ]; then SHOW=0 if [ ${SHOW_WARNINGS_ONLY} -eq 1 ]; then if [ "${RESULT}" = "WARNING" ]; then SHOW=1; fi elif [ ${QUIET} -eq 0 ]; then SHOW=1 fi if [ ${SHOW} -eq 1 ]; then # Display: # - for full shells, count with -m instead of -c, to support language locale (older busybox does not have -m) # - wc needs LANG to deal with multi-bytes characters but LANG has been unset in include/consts LINESIZE=$(export LC_ALL= ; export LANG="${DISPLAY_LANG}";echo "${TEXT}" | wc -m | tr -d ' ') if [ ${SHOWDEBUG} -eq 1 ]; then DEBUGTEXT=" [${PURPLE}DEBUG${NORMAL}]"; else DEBUGTEXT=""; fi if [ ${INDENT} -gt 0 ]; then SPACES=$((62 - INDENT - LINESIZE)); fi if [ ${SPACES} -lt 0 ]; then SPACES=0; fi if [ ${CRONJOB} -eq 0 ]; then # Check if we already have already discovered a proper echo command tool. It not, set it default to 'echo'. if [ "${ECHOCMD}" = "" ]; then ECHOCMD="echo"; fi ${ECHOCMD} "\033[${INDENT}C${TEXT}\033[${SPACES}C${RESULTPART}${DEBUGTEXT}" else echo "${TEXT}${RESULTPART}" fi fi fi } ################################################################################ # Name : DisplayError() # Description : Show error on screen # # Input : $1 = text (string), $2 = optional exit code (integer) # Returns : <nothing> ################################################################################ DisplayError() { EXITCODE="" if [ $# -gt 1 ]; then EXITCODE=$2; fi ${ECHOCMD} "" ${ECHOCMD} "${WARNING}Error${NORMAL}: ${BOLD}$1${NORMAL}" ${ECHOCMD} "" if [ -n "${EXITCODE}" ]; then ExitCustom ${EXITCODE}; fi } ################################################################################ # Name : DisplayException() # Description : Show a discovered exception on screen # # Parameters : $1 = function or test # $2 = text # Returns : <nothing> # Note : This function is usually triggered by ReportException ################################################################################ DisplayException() { ${ECHOCMD:-echo} "" ${ECHOCMD:-echo} "=================================================================" ${ECHOCMD:-echo} "" ${ECHOCMD:-echo} " ${WARNING}Exception found!${NORMAL}" ${ECHOCMD:-echo} "" ${ECHOCMD:-echo} " Function/test: [$1]" ${ECHOCMD:-echo} " Message: ${BOLD}$2${NORMAL}" ${ECHOCMD:-echo} "" ${ECHOCMD:-echo} " Help improving the Lynis community with your feedback!" ${ECHOCMD:-echo} "" ${ECHOCMD:-echo} " Steps:" ${ECHOCMD:-echo} " - Ensure you are running the latest version ($0 update check)" ${ECHOCMD:-echo} " - If so, create a GitHub issue at ${PROGRAM_SOURCE}" ${ECHOCMD:-echo} " - Include relevant parts of the log file or configuration file" ${ECHOCMD:-echo} "" ${ECHOCMD:-echo} " Thanks!" ${ECHOCMD:-echo} "" ${ECHOCMD:-echo} "=================================================================" ${ECHOCMD:-echo} "" sleep 5 } ################################################################################ # Name : DisplayManual() # Description : Show text on screen, without any markup # # Input : $1 = text (string) # Returns : <nothing> ################################################################################ DisplayManual() { if [ ${QUIET} -eq 0 ]; then ${ECHOCMD} "$1"; fi } ################################################################################ # Name : DisplayToolTip() # Description : Show tooltip on screen # # Input : $1 = text # Returns : <nothing> ################################################################################ DisplayToolTip() { # Display tooltip when enabled and no tip has been displayed yet if [ ${SHOW_TOOL_TIPS} -eq 1 -a ${TOOLTIP_SHOWED} -eq 0 -a ${QUIET} -eq 0 ]; then # Check if we already have already discovered a proper echo command tool. It not, set it default to 'echo'. if [ "${ECHOCMD}" = "" ]; then ECHOCMD="echo"; fi if [ ${CRONJOB} -eq 0 ]; then printf "\n" ${ECHOCMD} " ${BG_BLUE}[TIP]${NORMAL}: ${LIGHTBLUE}$1${NORMAL}" printf "\n" else ${ECHOCMD} " [TIP]: $1" fi TOOLTIP_SHOWED=1 fi } ################################################################################ # Name : DisplayWarning # Description : Show a warning on the screen # # Parameters : $1 = text # Returns : <nothing> ################################################################################ DisplayWarning() { if [ ${CRONJOB} -eq 0 ]; then printf "\n" ${ECHOCMD:-echo} " ${BG_WARNING}[WARNING]${NORMAL}: $1${NORMAL}" printf "\n" else ${ECHOCMD} " [WARNING]: $1" fi } ################################################################################ # Name : Equals() # Description : Compare two strings after special characters were stripped # # Parameters : $1 = string1 # $2 = string2 # Returns : exit code (0 = True, 1 = False) # Usage : if Equals "${MYDIR}" "/etc"; then echo "Found"; else "Not found"; fi ################################################################################ Equals() { RETVAL=1 if [ $# -ne 2 ]; then ReportException "Equals" "Incorrect number of arguments for $0 function"; fi # Strip any strange control characters INPUT1=$(echo $1 | tr -d '[:cntrl:]<>' | ${SEDBINARY} 's/__space__/ /g' | ${SEDBINARY} 's/:space:/ /g') INPUT2=$(echo $2 | tr -d '[:cntrl:]<>' | ${SEDBINARY} 's/__space__/ /g' | ${SEDBINARY} 's/:space:/ /g') if [ "${INPUT1}" = "${INPUT2}" ]; then RETVAL=0; fi return ${RETVAL} } ################################################################################ # Name : ExitClean() # Description : Perform a normal exit of the program, and clean up resources # # Parameters : <nothing> # Returns : <nothing> # Usage : ExitClean ################################################################################ ExitClean() { RemovePIDFile RemoveTempFiles LogText "${PROGRAM_NAME} ended successfully." exit 0 } ################################################################################ # Name : ExitCustom() # Description : Perform a normal exit of the program, and clean up resources # # Parameters : $1 = exit code (optional) # Returns : <nothing> # Usage : ExitCustom 35 ################################################################################ ExitCustom() { RemovePIDFile RemoveTempFiles # Exit with the exit code given, otherwise use 1 if [ $# -eq 1 ]; then LogText "${PROGRAM_NAME} ended with exit code $1." exit $1 else LogText "${PROGRAM_NAME} ended with exit code 1." exit 1 fi } ################################################################################ # Name : ExitFatal() # Description : Perform exit of the program (with code 1), clean up resources # # Parameters : $1 = text string (optional) # Returns : <nothing> # Usage : ExitFatal ################################################################################ ExitFatal() { RemovePIDFile RemoveTempFiles LogText "${PROGRAM_NAME} ended with exit code 1." if [ $# -eq 1 ]; then ${ECHOCMD:-echo} "" ${ECHOCMD:-echo} "${RED}Fatal error${NORMAL}: ${WHITE}$1${NORMAL}" ${ECHOCMD:-echo} "" fi exit 1 } ################################################################################ # Name : FileExists() # Description : Determine if a file exists # # Parameters : $1 = path # Returns : 0 (found), 1 (not found) # FILE_FOUND (0:found, 1:not found) - deprecated usage ################################################################################ FileExists() { if [ $# -eq 0 ]; then ExitFatal "Missing parameter when calling FileExists function"; fi FILE_FOUND=0 LogText "Test: checking if file $1 exists" if [ -f $1 ]; then LogText "Result: file $1 exists" FILE_FOUND=1 return 0 else LogText "Result: file $1 NOT found" return 1 fi } ################################################################################ # Name : FileInstalledByPackage() # Description : Check if a file is part of a package # Returns : 0 (true), 1 (default: unknown or false) ################################################################################ FileInstalledByPackage() { exitcode=1 file=$1 find="" if [ -n "${DPKGBINARY}" ]; then find=$(${DPKGBINARY} -S "${file}" 2> /dev/null | ${AWKBINARY} -F: '{print $1}') elif [ -n "${RPMBINARY}" ]; then find=$(${RPMBINARY} -qf "${file}" 2> /dev/null | ${AWKBINARY} -F- '{print $1}') fi if [ -n "${find}" ]; then LogText "Result: file '${file}' belongs to package (${find})" exitcode=0 else LogText "Result: file '${file}' does most likely not belong to a package" fi return ${exitcode} } ################################################################################ # Name : FileIsEmpty() # Description : Check if a file is empty # # Returns : 0 (empty), 1 (not empty) # EMPTY (0 or 1) - deprecated usage # Usage : if FileIsEmpty /etc/passwd; then ################################################################################ FileIsEmpty() { if [ $# -eq 0 ]; then ExitFatal "Missing parameter when calling FileIsEmpty function"; fi EMPTY=0 LogText "Test: checking if file $1 is empty" if [ ! -s "$1" ]; then LogText "Result: file $1 is empty" EMPTY=1 return 0 else LogText "Result: file $1 is NOT empty" return 1 fi } ################################################################################ # Name : FileIsReadable() # Description : Check if a file readable or directory is accessible # # Returns : Return code (0 = readable, 1 = not readable) # Usage : if FileIsReadable /etc/shadow; then echo "File is readable"; fi ################################################################################ FileIsReadable() { if [ $# -eq 0 ]; then ExitFatal "Function FileIsReadable() called without a file name"; fi sFILE=$1 CANREAD=0 RETVAL=1 escaped_file=$(echo ${sFILE} | sed 's/\*/\\*/; s/?/\\?/') LogText "Test: check if we can access ${sFILE} (escaped: ${escaped_file})" # Check for symlink if [ -L "${escaped_file}" ]; then ShowSymlinkPath ${escaped_file} if [ -n "${SYMLINK}" ]; then escaped_file="${SYMLINK}"; fi fi # Only check the file if it isn't a symlink (after previous check) if [ -L "${escaped_file}" ]; then OTHERPERMS="-" LogText "Result: unclear if we can read this file, as this is a symlink" ReportException "FileIsReadable" "Can not determine symlink ${sFILE}" elif [ -d "${escaped_file}" ]; then OTHERPERMS=$(${LSBINARY} -d -l "${escaped_file}" 2> /dev/null | ${CUTBINARY} -c 8) elif [ -f "${escaped_file}" ]; then OTHERPERMS=$(${LSBINARY} -d -l "${escaped_file}" 2> /dev/null | ${CUTBINARY} -c 8) else OTHERPERMS="-" fi # Also check if we are the actual owner of the file (use -d to get directory itself, if its a directory) FILEOWNER=$(ls -dln "${escaped_file}" 2> /dev/null | ${AWKBINARY} -F" " '{ print $3 }') if [ "${FILEOWNER}" = "${MYID}" ]; then LogText "Result: file is owned by our current user ID (${MYID}), checking if it is readable" if [ -L "${sFILE}" ]; then LogText "Result: unclear if we can read this file, as this is a symlink" ReportException "FileIsReadable" "Can not determine symlink ${escaped_file}" elif [ -d "${escaped_file}" ]; then OTHERPERMS=$(${LSBINARY} -d -l "${escaped_file}" 2> /dev/null | ${CUTBINARY} -c 2) elif [ -f "${escaped_file}" ]; then OTHERPERMS=$(${LSBINARY} -l "${escaped_file}" 2> /dev/null | ${CUTBINARY} -c 2) fi else LogText "Result: file is not owned by current user ID (${MYID}), but UID ${FILEOWNER}" fi # Check if we are root, or have the read bit if [ "${MYID}" = "0" -o "${OTHERPERMS}" = "r" ]; then CANREAD=1 LogText "Result: file ${escaped_file} is readable (or directory accessible)." return 0 else return 1 LogText "Result: file ${escaped_file} is NOT readable (or directory accessible), symlink, or does not exist. (OTHERPERMS: ${OTHERPERMS})" fi } ################################################################################ # Name : GetHostID() # Description : Create an unique id for the system # # Returns : 0 = fetched or created IDs, 1 = failed, 2 = skipped # Usage : GetHostID ################################################################################ GetHostID() { if [ ${SKIP_GETHOSTID} -eq 1 ]; then 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 ! "${SHA256SUMBINARY}" = "" -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 -n 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 -n 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 -n 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 -n 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 -n 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 -n 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" if [ -n "${SHA1SUMBINARY}" ]; then HOSTID=$(echo ${FIND} | ${SHA1SUMBINARY} | awk '{ print $1 }') elif [ -n "${SHA256SUMBINARY}" ]; then # Truncate hash to match SHA1 length HOSTID=$(echo ${FIND} | ${SHA256SUMBINARY} | awk '{ print $1 }' | head -c 40) fi LogText "Result: Found HostID: ${HOSTID}" else ReportException "GetHostID" "HostID could not be generated" fi ;; "macOS") FIND=$(${IFCONFIGBINARY} en0 | grep ether | head -n 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 -n 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 -n 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 hash tool available (sha1, sha1sum, openssl, truncated sha256sum)" 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 -n 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="" if [ -d /etc/ssh ]; then SSH_PUBKEY_FILES="ssh_host_ed25519_key.pub ssh_host_ecdsa_key.pub ssh_host_dsa_key.pub ssh_host_rsa_key.pub" for I in ${SSH_PUBKEY_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 elif [ -d /etc/dropbear ]; then SSH_KEY_FILES="dropbear_ed25519_host_key dropbear_rsa_host_key" for I in ${SSH_KEY_FILES}; do if [ ${FOUND} -eq 0 ]; then if [ -f "/etc/dropbear/${I}" ]; then LogText "Result: found file ${I} in /etc/dropbear, using that as candidate to create hostid2" # Dropbear stores both keys in one binary file DATA_SSH=$(dropbearkey -y -f "/etc/dropbear/${I}" | grep '^ssh') FOUND=1 fi fi done else LogText "Result: no /etc/ssh nor /etc/dropbear 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} | ${GREPBINARY} -E "[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}) else DATA=$(${STATBINARY} -c %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 [ -d "${CHECKFILE}" ]; then MAXDEPTH="-maxdepth 0" else MAXDEPTH="" fi if [ ${SHELL_IS_BUSYBOX} -eq 0 ]; then DATA=$(${FINDBINARY} "${CHECKFILE}" ${MAXDEPTH} -printf "%m") else DATA=$(${FINDBINARY} "${CHECKFILE}" ${MAXDEPTH} -exec stat -c %a {} \;) 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 : <nothing> # Usage : InsertSection ################################################################################ InsertSection() { if [ ${QUIET} -eq 0 ]; then echo "" echo "[+] ${SECTION}$1${NORMAL}" echo "------------------------------------" fi LogTextBreak LogText "Action: Performing tests from category: $1" } ################################################################################ # Name : InsertPlugionSection() # Description : Insert section block for plugins (different color) # # Returns : <nothing> # Usage : InsertPluginSection ################################################################################ InsertPluginSection() { if [ ${QUIET} -eq 0 ]; then echo "" echo "[+] ${MAGENTA}$1${NORMAL}" echo "------------------------------------" fi LogText "Action: Performing plugin tests" } ################################################################################ # Name : IsContainer() # Description : Determine if we are running in a container # # Parameters : <none> # Returns : exit code (0 = true, 1 = false) # variable: CONTAINER_TYPE ################################################################################ IsContainer() { FOUND=0 # Early on we can't use FileIsReadable yet if [ -e /proc/1/cgroup ]; then FIND=$(grep -i docker ${ROOTDIR}proc/1/cgroup 2> /dev/null) if [ $? -eq 0 ]; then LogText "Result: found Docker in control groups (/proc/1/cgroup), so we are running in Docker container" CONTAINER_TYPE="Docker"; FOUND=1 EXITCODE=0 fi fi if [ -e /proc/1/environ ]; then FIND=$(grep -qa 'container=lxc' ${ROOTDIR}proc/1/environ 2> /dev/null) if [ $? -eq 0 ]; then LogText "Result: found LXC in environment (/proc/1/environ), so we are running in LXC container" CONTAINER_TYPE="LXC"; FOUND=1 EXITCODE=0 fi fi if [ ${FOUND} -eq 0 ]; then CONTAINER_TYPE="" EXITCODE=1 fi return ${EXITCODE} } ################################################################################ # Name : IsDebug() # Description : Check if --debug option is used to show more details # # Parameters : <none> # Returns : exit code (0 = True, 1 = False) ################################################################################ IsDebug() { if [ ${DEBUG} -eq 1 ]; then return 0; else return 1; fi } ################################################################################ # Name : IsDeveloperMode() # Description : Check if we are in development mode (--developer) # # Parameters : <none> # Returns : exit code (0 = True, 1 = False) # Notes : This is set with command line option or as a profile setting ################################################################################ IsDeveloperMode() { if [ ${DEVELOPER_MODE} -eq 1 ]; then return 0; else return 1; fi } ################################################################################ # Name : IsDeveloperVersion() # Description : Check if this version is development or stable release # # Parameters : <none> # Returns : exit code (0 = True, 1 = False) ################################################################################ IsDeveloperVersion() { if [ "${PROGRAM_RELEASE_TYPE}" = "pre-release" ]; then return 0; else return 1; fi } ################################################################################ # Name : IsEmpty() # Description : Check for variable that has no data in it # # Returns : exit code (0 = True, 1 = False) # Usage : if IsEmpty "${FIND}"; then ################################################################################ IsEmpty() { if [ $# -eq 0 ]; then ExitFatal "Function IsEmpty called without parameters - look in log to determine where this happened, or use sh -x lynis to see all details." else if [ -z "$1" ]; then return 0; else return 1; fi fi } ################################################################################ # Name : IsRunning() # Description : Check if a process is running # # Parameters : $1 = search argument # $2 = optional arguments # Returns : 0 (process is running), 1 (process not running) # RUNNING (1 = running, 0 = not running) - will be deprecated # Notes : PSOPTIONS are declared globally, to prevent testing each call # Fallback is used on binaries as IsRunning is used for 'show' command ################################################################################ IsRunning() { if [ $# -eq 0 ]; then ExitFatal "Missing parameter when calling IsRunning function"; fi pgrep_options="-x" search="" FIND="" PSOPTIONS="" PARTIAL_SEARCH=1 while [ $# -ge 1 ]; do case $1 in --full) pgrep_options="-f" # replace -x with -f PARTIAL_SEARCH=0 ;; --user) shift users="$1" ;; *) search="$1" ;; esac shift # Go to next parameter done if [ -z "${search}" ]; then ExitFatal "Missing process to search for when using IsRunning function"; fi RUNNING=0 if [ -x "${PGREPBINARY}" ] && [ "${OS}" != "AIX" ]; then # When --user is used, perform a search using the -u option # Initialize users for strict mode if [ -n "${users:-}" ]; then for u in ${users}; do user_uid=$(getent passwd "${u}" 2> /dev/null | ${AWKBINARY:-awk} -F: '{print $3}') # Only perform search if user exists and we had no match yet if [ -n "${user_uid}" ]; then if [ -z "${FIND}" ]; then LogText "Performing pgrep scan using uid ${user_uid}" FIND=$(${PGREPBINARY:-pgrep} ${pgrep_options} -u "${user_uid}" "${search}" | ${TRBINARY:-tr} '\n' ' ') fi fi done else LogText "Performing pgrep scan without uid" FIND=$(${PGREPBINARY:-pgrep} ${pgrep_options} "${search}" | ${TRBINARY:-tr} '\n' ' ') fi else if [ "${SHELL_IS_BUSYBOX}" -eq 1 ]; then # This search is not foolproof LogText "Performing simple ps scan (busybox)" PSOPTIONS=" -o args=" FIND=$(${PSBINARY:-ps} ${PSOPTIONS} | ${GREPBINARY:-grep} -E "( |/)${search}" | ${GREPBINARY:-grep} -v "grep") else if [ -n "${users}" ]; then for u in ${users}; do user_uid=$(getent passwd "${u}" 2> /dev/null | ${AWKBINARY:-awk} -F: '{print $3}') # Only perform search if user exists and we had no match yet if [ -n "${user_uid}" ]; then if [ -z "${FIND}" ]; then if [ ${PARTIAL_SEARCH} -eq 1 ]; then LogText "Performing ps scan using partial match and for uid ${user_uid}" FIND=$(${PSBINARY:-ps} -u "${user_uid}" -o comm= "${search}" | ${AWKBINARY:-awk} -v pattern="${search}" '$0 ~ pattern {print}') else LogText "Performing ps scan using exact match and for uid ${user_uid}" FIND=$(${PSBINARY:-ps} -u "${user_uid}" -o comm= "${search}" | ${AWKBINARY:-awk} -v pattern="^${search}$" '$0 ~ pattern {print}') fi fi fi done else case "${OS}" in "Linux") PSOPTIONS=" -o args= -C ${search}" ;; esac if [ ${PARTIAL_SEARCH} -eq 1 ]; then LogText "Performing ps scan using partial match and without uid" FIND=$(${PSBINARY:-ps} ${PSOPTIONS} | ${AWKBINARY:-awk} -v pattern="${search}" '$0 ~ pattern {print}') else LogText "Performing ps scan using exact match and without uid" FIND=$(${PSBINARY:-ps} ${PSOPTIONS} | ${AWKBINARY:-awk} -v pattern="^${search}$" '$0 ~ pattern {print}') fi fi fi fi if [ -n "${FIND}" ]; then RUNNING=1 LogText "IsRunning: process '${search}' found (${FIND})" return 0 else LogText "IsRunning: process '${search}' not found" return 1 fi } ################################################################################ # Name : IsNotebook # Description : Check if file or directory is owned by root # Returns : exit code (0 = True, 1 = False, 255 = Unknown) ################################################################################ IsNotebook() { FIND=$(which laptop-detect 2> /dev/null | grep -v "no [^ ]* in ") if [ -n "${FIND}" ]; then Debug "Testing if we are a notebook" laptop-detect if [ $? -eq 0 ]; then SYSTEM_IS_NOTEBOOK=1; Debug "System is a notebook according to laptop-detect" elif [ $? -eq 1 ]; then SYSTEM_IS_NOTEBOOK=0; Debug "System is a NOT a notebook according to laptop-detect"; fi Report "notebook=${SYSTEM_IS_NOTEBOOK}" fi } ################################################################################ # Name : IsOwnedByRoot # Description : Check if file or directory is owned by root # Returns : 0 (true), 1 (false), or 255 (unknown) ################################################################################ IsOwnedByRoot() { PERMS="" if [ $# -eq 1 ]; then FILE="$1" case $OS in "AIX") if [ ! "${ISTATBINARY}" = "" ]; then PERMS=$(${ISTATBINARY} ${FILE} | sed "s/Owner: //" | sed "s/[a-zA-Z() ]//g"); fi ;; "Linux") if [ ! "${STATBINARY}" = "" ]; then PERMS=$(${STATBINARY} -c "%u:%g" ${FILE}); fi ;; "FreeBSD") if [ ! "${STATBINARY}" = "" ]; then PERMS=$(${STATBINARY} -f "%u:%g" ${FILE}); fi ;; esac # Fallback with ls (for other platforms, or when a test did not reveal any output) if [ "${PERMS}" = "" ]; then PERMS=$(ls -n ${FILE} | ${AWKBINARY} '{ print $3":"$4 }') fi else ReportException "IsOwnedByRoot" "Functions needs 1 argument" return 255 fi if [ "${PERMS}" = "0:0" ]; then if IsDeveloperMode; then LogText "Debug: found incorrect file permissions on ${FILE}"; fi return 0 else return 1 fi } ################################################################################ # Name : IsVerbose() # Description : Check if --verbose option is used to show more details on screen # # Parameters : <none> # Returns : exit code (0 =true, 1 =false) ################################################################################ IsVerbose() { if [ ${VERBOSE} -eq 1 ]; then return 0; else return 1; fi } ################################################################################ # Name : IsVirtualMachine() # Description : Determine whether it is a virtual machine # Parameters : <none> # Returns : exit code (0 = True, 1 = False, 2 = Unknown) # variable: ISVIRTUALMACHINE (0-2) # variable: VMTYPE # variable: VMFULLTYPE ################################################################################ IsVirtualMachine() { LogText "Test: Determine if this system is a virtual machine" # 0 = no, 1 = yes, 2 = unknown ISVIRTUALMACHINE=2; VMTYPE="unknown"; VMFULLTYPE="Unknown" SHORT="" if [ ${SKIP_VM_DETECTION} -eq 1 ]; then return 2 fi # lxc environ detection if [ -z "${SHORT}" ]; then if [ -f /proc/1/environ ]; then FIND=$(grep -qa 'container=lxc' /proc/1/environ 2> /dev/null) if [ $? -eq 0 ]; then SHORT=lxc LogText "Result: found ${SHORT}" fi fi else LogText "Result: skipped lxc environ detection test, as we already found machine type" fi # facter if [ -z "${SHORT}" ]; then if [ -x /usr/bin/facter ] || [ -x /usr/local/bin/facter ]; then case "$(facter is_virtual)" in "true") SHORT=$(facter virtual) LogText "Result: found ${SHORT}" ;; "false") LogText "Result: facter says this machine is not a virtual" ;; esac else LogText "Result: facter utility not found" fi else LogText "Result: skipped facter test, as we already found machine type" fi # systemd if [ -z "${SHORT}" ]; then if [ -x /usr/bin/systemd-detect-virt ]; then LogText "Test: trying to guess virtualization technology with systemd-detect-virt" FIND=$(/usr/bin/systemd-detect-virt) if [ -n "${FIND}" ]; then LogText "Result: found ${FIND}" SHORT="${FIND}" fi else LogText "Result: systemd-detect-virt not found" fi else LogText "Result: skipped systemd test, as we already found machine type" fi # lscpu # Values: VMware if [ -z "${SHORT}" ]; then if [ -x /usr/bin/lscpu ]; then LogText "Test: trying to guess virtualization with lscpu" FIND=$(lscpu | grep -i "^Hypervisor Vendor" | awk -F: '{ print $2 }' | sed 's/ //g') if [ -n "${FIND}" ]; then LogText "Result: found ${FIND}" SHORT="${FIND}" else LogText "Result: can't find hypervisor vendor with lscpu" fi else LogText "Result: lscpu not found" fi else LogText "Result: skipped lscpu test, as we already found machine type" fi # dmidecode # Values: VMware Virtual Platform / VirtualBox if [ -z "${SHORT}" ]; then # Try to find dmidecode in case we did not check binaries (e.g. lynis show environment) if [ ${CHECK_BINARIES} -eq 0 ]; then DMIDECODEBINARY=$(command -v dmidecode 2> /dev/null); fi if [ -n "${DMIDECODEBINARY}" -a -x "${DMIDECODEBINARY}" -a ${PRIVILEGED} -eq 1 ]; then LogText "Test: trying to guess virtualization with dmidecode" FIND=$(${DMIDECODEBINARY} -s system-product-name | awk '{ print $1 }') if [ -n "${FIND}" ]; then LogText "Result: found ${FIND}" SHORT="${FIND}" else LogText "Result: can't find product name with dmidecode" fi else LogText "Result: dmidecode not found (or no access)" fi else LogText "Result: skipped dmidecode test, as we already found machine type" fi # Other options # SaltStack: salt-call grains.get virtual # < needs snippet > # Try common guest processes if [ -z "${SHORT}" ]; then LogText "Test: trying to guess virtual machine type by running processes" # VMware if IsRunning vmware-guestd; then SHORT="vmware" elif IsRunning vmtoolsd; then SHORT="vmware" fi # VirtualBox based on guest services if IsRunning vboxguest-service; then SHORT="virtualbox" elif IsRunning VBoxClient; then SHORT="virtualbox" elif IsRunning VBoxService; then SHORT="virtualbox" fi else LogText "Result: skipped processes test, as we already found platform" fi # Amazon EC2 if [ -z "${SHORT}" ]; then LogText "Test: checking specific files for Amazon" if [ -f /etc/ec2_version -a -s /etc/ec2_version ]; then SHORT="amazon-ec2" else LogText "Result: system not hosted on Amazon" fi else LogText "Result: skipped Amazon EC2 test, as we already found platform" fi # sysctl values if [ -z "${SHORT}" ]; then LogText "Test: trying to guess virtual machine type by sysctl keys" # FreeBSD: hw.hv_vendor (remains empty for VirtualBox) # NetBSD: machdep.dmi.system-product # OpenBSD: hw.product FIND=$(sysctl -a 2> /dev/null | grep -E "(hw.product|machdep.dmi.system-product)" | head -n 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 : <nothing> # Usage : LogText "This line goes into the log file" ################################################################################ LogText() { if [ ! "${LOGFILE}" = "" -a ${LOGTEXT} -eq 1 ]; then CDATE=$(date "+%Y-%m-%d %H:%M:%S"); echo "${CDATE} $1" >> ${LOGFILE}; fi } ################################################################################ # Name : LogTextBreak() # Description : Add a separator to log file between sections, tests etc # Returns : <nothing> ################################################################################ LogTextBreak() { if [ ! "${LOGFILE}" = "" -a ${LOGTEXT} -eq 1 ]; then CDATE=$(date "+%Y-%m-%d %H:%M:%S") echo "${CDATE} ====" >> ${LOGFILE} fi } ################################################################################ # Name : PackageIsInstalled() # Description : Determines if a package is installed # Returns : exit code # Notes : this function is not used yet, but created in advance to allow # the addition of support for all operating systems ################################################################################ PackageIsInstalled() { exit_code=255 # First parameter is package name (or __dummy__ for initial test to see if package manager is available and works as expected) if [ $# -eq 1 ]; then package="$1" else Fatal "Incorrect usage of PackageIsInstalled function" fi if [ -n "${DNFBINARY}" ]; then output=$(${DNFBINARY} --quiet --cacheonly --noplugins --assumeno info --installed ${package} > /dev/null 2>&1) exit_code=$? elif [ -n "${DPKGBINARY}" ]; then output=$(${DPKGBINARY} -l ${package} 2> /dev/null | ${GREPBINARY} "^ii") exit_code=$? elif [ -n "${EQUERYBINARY}" ]; then output=$(${EQUERYBINARY} --quiet ${package} > /dev/null 2>&1) exit_code=$? # 0=package installed, 3=package not installed elif [ -n "${PACMANBINARY}" ]; then output=$(${PACMANBINARY} -Qs ${PKG} | grep "local/${PKG} " >/dev/null 2>&1) exit_code=$? # 0=package installed, 1=package not installed elif [ -n "${PKG_BINARY}" ]; then output=$(${PKG_BINARY} -N info ${package} >/dev/null 2>&1) exit_code=$? # 0=package installed, 70=invalid package elif [ -n "${PKGINFOBINARY}" ]; then output=$(${PKGINFOBINARY} -q -e ${package} >/dev/null 2>&1) exit_code=$? # 0=package installed, 1=package not installed # Slackware also has RPM for some reason and that's why this test precedes the RPMBINARY test elif [ "${OS_NAME}" = "Slackware Linux" -a -d "${ROOTDIR}/var/lib/pkgtools/packages" ]; then output=$( ls ${ROOTDIR}/var/lib/pkgtools/packages/ 2> /dev/null | ${GREPBINARY} "^${package}-[^-]\+-[^-]\+-[^-]\+$" ) exit_code=$? 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} list --installed ${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 : <none> # Returns : <nothing> ################################################################################ ParseProfiles() { SafePerms ${INCLUDEDIR}/profiles . ${INCLUDEDIR}/profiles } ################################################################################ # Name : ParseTestValues() # Description : Parse values from a specific test # # Parameters : $1 = service (e.g. ssh) # Returns : CHECK_VALUES_ARRAY variable ################################################################################ ParseTestValues() { RETVAL=1 FOUND=0 CHECK_VALUES_ARRAY="" if [ $# -gt 0 -a ! "${CHECK_OPTION_ARRAY}" = "" ]; then for I in ${CHECK_OPTION_ARRAY}; do Debug "Array value: ${I}" SPLIT=$(echo ${I} | sed 's/,/\n/g') for ITEM in ${SPLIT}; do FUNCTION="" VALUE="" SEARCH="" SERVICE="" ITEM_KEY=$(echo ${ITEM} | awk -F: '{print $1}') ITEM_VALUE=$(echo ${ITEM} | awk -F: '{print $2}') Debug "Array item: ${ITEM_KEY} with value ${ITEM_VALUE}" case ${ITEM_KEY} in "function") case ${ITEM_VALUE} in "equals") FUNCTION="eq" ;; esac ;; "service") if [ "${ITEM_VALUE}" = "$1" ]; then FOUND=1 fi ;; "value") VALUE="${ITEM_VALUE}" ;; *) echo "Incorrect call to function ParseTestValues"; ExitFatal ;; esac if [ ${FOUND} -eq 1 ]; then CHECK_VALUES_ARRAY="${CHECK_VALUES_ARRAY} ${SEARCH}:${VALUE}:${FUNCTION}:" FOUND=0 fi done done RETVAL=1 fi return ${RETVAL} } ################################################################################ # Name : ParseNginx() # Description : Parse nginx configuration lines # # Parameters : $1 = file (should be readable and tested upfront) # Returns : <nothing> ################################################################################ ParseNginx() { COUNT=0 BREADCRUMB="" if [ $# -eq 0 ]; then ExitFatal "No arguments provided to ParseNginx()"; fi CONFIG_FILE=$1 # Create temporary files CreateTempFile || ExitFatal "Could not create temporary file" TMP_NGINX_FILE_RAW="${TEMP_FILE}" CreateTempFile || ExitFatal "Could not create temporary file" TMP_NGINX_FILE="${TEMP_FILE}" # Strip out spaces, tabs and line breaks awk '{$1=$1;print $0}' ${CONFIG_FILE} > ${TMP_NGINX_FILE_RAW} # Now clean up the file further (combine lines, remove commented lines and empty lines) sed 's#\\$##g' ${TMP_NGINX_FILE_RAW} | grep -v "^#" | grep -v "^$" > ${TMP_NGINX_FILE} LogText "Action: parsing configuration file ${CONFIG_FILE}" COUNT=$(( COUNT + 1)) FIND=$(sed 's/ /:space:/g' ${TMP_NGINX_FILE}) DEPTH=0 for I in ${FIND}; do I=$(echo ${I} | sed 's/:space:/ /g' | sed 's/;$//' | sed 's/ #.*$//') OPTION=$(echo ${I} | awk '{ print $1 }') # Use quotes here to prevent wildcard expansion VALUE=$(echo "${I}"| cut -d' ' -f2-) LogText "Result: found option ${OPTION} in ${CONFIG_FILE} with value '${VALUE}'" STORE_SETTING=1 case ${OPTION} in "events") BREADCRUMB="${BREADCRUMB}/events" DEPTH=$(( DEPTH + 1)) STORE_SETTING=0 NGINX_EVENTS_COUNTER=$(( NGINX_EVENTS_COUNTER + 1 )) ;; "http") BREADCRUMB="${BREADCRUMB}/http" DEPTH=$(( DEPTH + 1)) STORE_SETTING=0 NGINX_HTTP_COUNTER=$(( NGINX_HTTP_COUNTER + 1 )) ;; "location") BREADCRUMB="${BREADCRUMB}/location" DEPTH=$(( DEPTH + 1)) STORE_SETTING=0 NGINX_LOCATION_COUNTER=$(( NGINX_LOCATION_COUNTER + 1 )) ;; "server") BREADCRUMB="${BREADCRUMB}/server" DEPTH=$(( DEPTH + 1)) STORE_SETTING=0 NGINX_SERVER_COUNTER=$(( NGINX_SERVER_COUNTER + 1 )) ;; "}") BREADCRUMB=$(echo ${BREADCRUMB} | awk -F/ 'sub(FS $NF,x)') DEPTH=$(( DEPTH - 1)) STORE_SETTING=0 ;; access_log) if [ "${VALUE}" = "off" ]; then LogText "Result: found logging disabled for one virtual host" NGINX_ACCESS_LOG_DISABLED=1 else if [ ! "${VALUE}" = "" ]; then # If multiple values follow, select first one VALUE=$(echo ${VALUE} | awk '{ print $1 }') # Find both, log files provided with full or relative path if [ ! -f ${VALUE} -a ! -f "${CONFIG_FILE%nginx.conf}${VALUE}" ]; then LogText "Result: could not find log file ${VALUE} referenced in nginx configuration" NGINX_ACCESS_LOG_MISSING=1 fi fi fi ;; # Headers add_header) HEADER=$(echo ${VALUE} | awk '{ print $1 }') HEADER_VALUE=$(echo ${VALUE} | cut -d' ' -f2-) LogText "Result: found header ${HEADER} with value ${HEADER_VALUE}" #Report "nginx_header[]=${HEADER}|${HEADER_VALUE}|" ;; alias) NGINX_ALIAS_FOUND=1 ;; allow) NGINX_ALLOW_FOUND=1 ;; autoindex) ;; deny) NGINX_DENY_FOUND=1 ;; expires) NGINX_EXPIRES_FOUND=1 ;; error_log) # Check if debug is appended FIND=$(echo ${VALUE} | awk '{ if ($2=="debug") { print 1 } else { print 0 }}') if [ ${FIND} -eq 1 ]; then NGINX_ERROR_LOG_DEBUG=1 fi # Check if log file exists FILE=$(echo ${VALUE} | awk '{ print $1 }') if [ ! "${FILE}" = "" ]; then # Find both, log files provided with full or relative path if [ ! -f ${FILE} -a ! -f "${CONFIG_FILE%nginx.conf}${FILE}" ]; then NGINX_ERROR_LOG_MISSING=1 fi else LogText "Warning: did not find a filename after error_log in nginx configuration" fi ;; error_page) ;; fastcgi_intercept_errors) ;; fastcgi_param) NGINX_FASTCGI_FOUND=1 NGINX_FASTCGI_PARAMS_FOUND=1 ;; fastcgi_pass) NGINX_FASTCGI_FOUND=1 NGINX_FASTCGI_PASS_FOUND=1 ;; fastcgi_pass_header) ;; include) if [ -f "${VALUE}" ]; then FOUND=0 for CONF in ${NGINX_CONF_FILES}; do if [ "${CONF}" = "${VALUE}" ]; then FOUND=1; LogText "Found this file already in our configuration files array, not adding to queue"; fi done for CONF in ${NGINX_CONF_FILES_ADDITIONS}; do if [ "${CONF}" = "${VALUE}" ]; then FOUND=1; LogText "Found this file already in our configuration files array (additions), not adding to queue"; fi done if [ ${FOUND} -eq 0 ]; then NGINX_CONF_FILES_ADDITIONS="${NGINX_CONF_FILES_ADDITIONS} ${VALUE}"; fi # Check if include value is a relative path only elif [ -f "${CONFIG_FILE%nginx.conf}${VALUE%;*}" ]; then VALUE="${CONFIG_FILE%nginx.conf}${VALUE}" FOUND=0 for CONF in ${NGINX_CONF_FILES}; do if [ "${CONF}" = "${VALUE}" ]; then FOUND=1; LogText "Found this file already in our configuration files array, not adding to queue"; fi done for CONF in ${NGINX_CONF_FILES_ADDITIONS}; do if [ "${CONF}" = "${VALUE}" ]; then FOUND=1; LogText "Found this file already in our configuration files array (additions), not adding to queue"; fi done if [ ${FOUND} -eq 0 ]; then NGINX_CONF_FILES_ADDITIONS="${NGINX_CONF_FILES_ADDITIONS} ${VALUE}"; fi # Check for additional config files included as follows # "include sites-enabled/*.conf" (relative path) # "include /etc/nginx/sites-enabled/*.conf" (absolute path) elif [ $(echo "${VALUE}" | grep -F -c "*.conf") -gt 0 ]; then # Check if path is absolute or relative case $VALUE in /*) # Absolute path, so wildcard pattern is already correct CONF_WILDCARD=${VALUE%;*} ;; *) # Relative path, so construct absolute path for wildcard pattern CONF_WILDCARD=${CONFIG_FILE%nginx.conf}${VALUE%;*} ;; esac for FOUND_CONF in ${CONF_WILDCARD}; do if [ "${FOUND_CONF}" = "${CONF_WILDCARD}" ]; then LogText "Found no match for wildcard pattern: ${CONF_WILDCARD}" break fi FOUND=0 for CONF in ${NGINX_CONF_FILES}; do if [ "${CONF}" = "${FOUND_CONF}" ]; then FOUND=1; LogText "Found this file already in our configuration files array, not adding to queue"; fi done for CONF in ${NGINX_CONF_FILES_ADDITIONS}; do if [ "${CONF}" = "${FOUND_CONF}" ]; then FOUND=1; LogText "Found this file already in our configuration files array (additions), not adding to queue"; fi done if [ ${FOUND} -eq 0 ]; then NGINX_CONF_FILES_ADDITIONS="${NGINX_CONF_FILES_ADDITIONS} ${FOUND_CONF}"; fi done else LogText "Result: this include does not point to a file" fi ;; index) ;; keepalive_timeout) ;; listen) NGINX_LISTEN_FOUND=1 # Test for ssl on listen statement FIND_SSL=$(echo ${VALUE} | grep ssl) if [ ! "${FIND_SSL}" = "" ]; then NGINX_SSL_ON=1; fi ;; location) NGINX_LOCATION_FOUND=1 ;; return) NGINX_RETURN_FOUND=1 ;; root) NGINX_ROOT_FOUND=1 ;; server_name) ;; ssl) if [ "${VALUE}" = "on" ]; then NGINX_SSL_ON=1; fi ;; ssl_certificate) LogText "Found SSL certificate in nginx configuration" ;; ssl_certificate_key) ;; ssl_ciphers) NGINX_SSL_CIPHERS=1 ;; ssl_prefer_server_ciphers) if [ "${VALUE}" = "on" ]; then NGINX_SSL_PREFER_SERVER_CIPHERS=1; fi ;; ssl_protocols) NGINX_SSL_PROTOCOLS=1 VALUE=$(echo ${VALUE} | sed 's/;$//' | tr '[:upper:]' '[:lower:]') for ITEM in ${VALUE}; do LogText "Result: found protocol ${ITEM}" case ${ITEM} in "sslv2" | "sslv3" | "tlsv1") NGINX_WEAK_SSL_PROTOCOL_FOUND=1 ;; esac Report "ssl_tls_protocol_enabled[]=${ITEM}" ReportDetails --service nginx --field protocol --value "${ITEM}" done ;; ssl_session_cache) ;; ssl_session_timeout) ;; types) ;; *) LogText "Found unknown option ${OPTION} in nginx configuration" ;; esac if [ ${STORE_SETTING} -eq 1 ]; then CONFIG_TREE="${BREADCRUMB}" if [ -z "${CONFIG_TREE}" ]; then CONFIG_TREE="/"; fi if [ -z "${OPTION}" ]; then OPTION="NA"; fi if [ -z "${VALUE}" ]; then VALUE="NA"; fi StoreNginxSettings --config ${CONFIG_FILE} --tree ${CONFIG_TREE} --depth ${DEPTH} --setting ${OPTION} --value "${VALUE}" fi done } ################################################################################ # Name : PortIsListening() # Description : Check if machine is listening on specified protocol and port # # Parameters : $1 = protocol # $2 = port # Returns : exit code (0 = listening, 1 = not listening, 255 = can't perform test) # Usage : if PortIsListening "TCP" 22; then echo "Port is listening"; fi ################################################################################ PortIsListening() { if [ -z "${LSOFBINARY}" ]; then return 255 else if [ $# -eq 2 ] && [ $1 = "TCP" -o $1 = "UDP" ]; then LogText "Test: find service listening on $1:$2" if [ $1 = "TCP" ]; then FIND=$(${LSOFBINARY}${LSOF_EXTRA_OPTIONS} -i${1} -s${1}:LISTEN -P -n | grep ":${2} "); else FIND=$(${LSOFBINARY}${LSOF_EXTRA_OPTIONS} -i${1} -P -n | grep ":${2} "); fi if [ ! "${FIND}" = "" ]; then LogText "Result: found service listening on port $2 ($1)" return 0 else LogText "Result: did not find service listening on port $2 ($1)" return 1 fi else return 255 ReportException ${TEST_NO} "Error in function call to PortIsListening" fi fi } ################################################################################ # Name : Progress() # Description : Displays progress on screen with dots # # Parameters : $1 = --finish or text (string) # Returns : <nothing> # Tip : Use this function from Register with the --progress parameter ################################################################################ Progress() { if [ ${CRONJOB} -eq 0 ]; then if [ ${QUIET} -eq 0 ]; then if [ "$1" = "--finish" ]; then ${ECHOCMD} "" else # If the No-Break version of echo is known, use that (usually breaks in combination with -e) if [ ! "${ECHONB}" = "" ]; then ${ECHONB} "$1" else ${ECHOCMD} -en "$1" fi fi fi fi } ################################################################################ # Name : RandomString() # Description : Displays progress on screen with dots # # Parameters : $1 = number (amount of characters, optional) # Returns : RANDOMSTRING variable # Usage : RandomString 32 ################################################################################ RandomString() { # Check a (pseudo) random character device if [ -c /dev/urandom ]; then FILE="/dev/urandom" elif [ -c /dev/random ]; then FILE="/dev/random" else Display "Can not use RandomString function, as there is no random device to be used" fi if [ $# -eq 0 ]; then SIZE=16; else SIZE=$1; fi CSIZE=$((SIZE / 2)) RANDOMSTRING=$(head -c ${CSIZE} /dev/urandom | od -An -x | tr -d ' ' | cut -c 1-${SIZE}) } ################################################################################ # Name : Readonly() # Description : Mark a variable as read-only data # # Returns : <nothing> # Notes : new function, not in use yet ################################################################################ Readonly() { if [ $# -eq 1 ]; then if type -t typeset; then typeset -r $1 else Debug "No typeset available to mark variable '$1' as read-only variable" fi else ExitFatal "Expected 1 parameter, received none or multiple" fi } ################################################################################ # Name : Register() # Description : Register a test and see if it has to be run # # Parameters : multiple, see test # Returns : SKIPTEST (0 or 1) ################################################################################ GetTimestamp() { ts=0 # 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 # Save timestamp for next time the Register function is called PREVIOUS_TEST="${TEST_NO}" PREVIOUS_TS="${CURRENT_TS}" } ################################################################################ # Name : RemoveColors() # Description : Disabled colors by overriding them (defined in include/consts) # # Notes : Can be activated using --no-colors ################################################################################ RemoveColors() { COLORS=0 # disable most color selections # Normal color names CYAN="" BLUE="" BROWN="" DARKGRAY="" GRAY="" GREEN="" LIGHTBLUE="" MAGENTA="" PURPLE="" RED="" YELLOW="" WHITE="" # Colors with background BG_BLUE="" BG_WARNING="" # Semantic names BAD="" BOLD="" HEADER="" NORMAL="" NOTICE="" OK="" WARNING="" SECTION="" } ################################################################################ # Name : RemovePIDFile() # Description : When defined, remove the file storing the process ID # # Parameters : <none> # Returns : <nothing> ################################################################################ # Remove PID file RemovePIDFile() { # Test if PIDFILE is defined, before checking file presence if [ -n "${PIDFILE}" ]; then if [ -f "${PIDFILE}" ]; then rm -f "${PIDFILE}" LogText "PID file removed (${PIDFILE})" else LogText "PID file not found (${PIDFILE})" fi fi } ################################################################################ # Name : RemoveTempFiles() # Description : When created, delete any temporary file ################################################################################ # Remove any temporary files RemoveTempFiles() { if [ ! "${TEMP_FILES}" = "" ]; then LogText "Temporary files: ${TEMP_FILES}" # Clean up temp files for FILE in ${TEMP_FILES}; do # Temporary files should be in /tmp TMPFILE=$(echo ${FILE} | grep -E "^/tmp/lynis" | grep -v "\.\.") if [ -n "${TMPFILE}" ]; then if [ -f "${TMPFILE}" ]; then LogText "Action: removing temporary file ${TMPFILE}" rm -f "${TMPFILE}" else LogText "Info: temporary file ${TMPFILE} was already removed" fi else LogText "Found invalid temporary file (${FILE}), not removed. Check your /tmp directory." fi done else LogText "No temporary files to be deleted" fi } ################################################################################ # Name : Report() # Description : Store data in the report file # # Parameters : $1 = data (format: key=value) # Returns : <nothing> ################################################################################ Report() { if [ ${CREATE_REPORT_FILE} -eq 1 ]; then echo "$1" >> ${REPORTFILE}; fi } ################################################################################ # Name : ReportDetails() # Description : Adds specific details to the report, in particular when many # smaller atomic tests are performed. For example sysctl keys, # and SSH settings. # # Parameters : <multiple fields, see test> # Returns : <nothing> # Notes : Need to check for values (strip out semicolons from values) # Only add fields which are filled in ################################################################################ ReportDetails() { TEST_DESCRIPTION="" TEST_FIELD="" TEST_ID="" TEST_OTHER="" TEST_PREFERRED_VALUE="" TEST_SERVICE="" TEST_VALUE="" while [ $# -ge 1 ]; do case $1 in --description) shift TEST_DESCRIPTION="desc:$1;" ;; --field) shift TEST_FIELD="field:$1;" ;; --preferredvalue|--preferred-value) shift TEST_PREFERRED_VALUE="prefval:$1;" ;; # Other details --other) shift TEST_OTHER="other:$1;" ;; --service) shift TEST_SERVICE="$1" ;; --test) shift TEST_ID=$1 ;; --value) shift TEST_VALUE="value:$1;" ;; *) echo "INVALID OPTION (ReportDetails): $1" ExitFatal ;; esac shift # Go to next parameter done if [ "${TEST_ID}" = "" ]; then TEST_ID="-"; fi if [ "${TEST_SERVICE}" = "" ]; then TEST_SERVICE="-"; fi Report "details[]=${TEST_ID}|${TEST_SERVICE}|${TEST_DESCRIPTION}${TEST_FIELD}${TEST_PREFERRED_VALUE}${TEST_VALUE}${TEST_OTHER}|" } ################################################################################ # Name : ReportException() # Description : Store an exceptional event in the report # # Parameters : $1 = test ID + colon + 2 numeric characters (TEST-1234:01) # $2 = string (text) # Notes : Allow this function with only 1 parameter in strict mode with ${2-Text} ################################################################################ ReportException() { Report "exception_event[]=$1|${2-NoInfo}|" LogText "Exception: test has an exceptional event ($1) with text ${2-NoText}" if [ ${QUIET} -eq 0 ]; then DisplayException "$1" "$2" fi } ################################################################################ # Name : ReportManual() # Description : Add an item to the report that requires manual intervention # # Parameters : $1 = string (text) ################################################################################ ReportManual() { Report "manual_event[]=$1" LogText "Manual: one or more manual actions are required for further testing of this control/plugin" } ################################################################################ # Name : ReportSuggestion() # Description : Log a suggestion to the report file # # Parameters : <ID> <Suggestion> <Details> <Solution> # $1 = Test ID - Lynis ID (use CUST-.... for your own tests) # $2 = Suggestion - Suggestion text to be displayed # $3 = Details - Specific item or details # $4 = Solution - Optional link for additional information: # * url:https://example.org/how-to-solve-link # * text:Additional explanation # * - (dash) for none # Returns : <nothing> ################################################################################ ReportSuggestion() { TOTAL_SUGGESTIONS=$((TOTAL_SUGGESTIONS + 1)) if [ $# -eq 0 ]; then echo "Not enough arguments provided for function ReportSuggestion"; ExitFatal; fi if [ $# -ge 1 ]; then TEST="$1"; else TEST="UNKNOWN"; fi if [ $# -ge 2 ]; then MESSAGE="$2"; else MESSAGE="UNKNOWN"; fi if [ $# -ge 3 ]; then DETAILS="$3"; else DETAILS="-"; fi if [ $# -ge 4 ]; then SOLUTION="$4"; else SOLUTION="-"; fi if [ $# -ge 5 ]; then echo "Too many arguments for function ReportSuggestion"; ExitFatal; fi Report "suggestion[]=${TEST}|${MESSAGE}|${DETAILS}|${SOLUTION}|" LogText "Suggestion: ${MESSAGE} [test:${TEST}] [details:${DETAILS}] [solution:${SOLUTION}]" } ################################################################################ # Name : ReportWarning() # Description : Log a warning to the report file # # Parameters : $1 = test ID # $2 = message (optional) # $3 = details (optional) # $4 = possible solution (optional) # Returns : <nothing> ################################################################################ ReportWarning() { TOTAL_WARNINGS=$((TOTAL_WARNINGS + 1)) # Old style used L/M/H level as second parameter. This is deprecated as of 3.x if [ "$2" = "L" -o "$2" = "M" -o "$2" = "H" ]; then ExitFatal "Deprecated usage of ReportWarning() function" else # New style warning format: # <ID> <Warning> <Details> <Solution> # # <ID> Lynis ID (use CUST-.... for your own tests) # <Warning> Warning text to be displayed # <Details> Specific item or details # <Solution> Optional link for additional information: # * url:http://site/link # * text:Additional explanation # * - for none if [ $# -eq 0 ]; then echo "Not enough arguments provided for function ReportWarning"; ExitFatal; fi if [ $# -ge 1 ]; then TEST="$1"; else TEST="UNKNOWN"; fi if [ $# -ge 2 ]; then MESSAGE="$2"; else MESSAGE="UNKNOWN"; fi if [ $# -ge 3 ]; then DETAILS="$3"; else DETAILS="-"; fi if [ $# -ge 4 ]; then SOLUTION="$4"; else SOLUTION="-"; fi if [ $# -ge 5 ]; then echo "Too many arguments for function ReportWarning"; ExitFatal; fi fi Report "warning[]=${TEST}|${MESSAGE}|${DETAILS}|${SOLUTION}|" LogText "Warning: ${MESSAGE} [test:${TEST}] [details:${DETAILS}] [solution:${SOLUTION}]" } ################################################################################ # Name : SafeInput() # Description : Test provided string to see if it contains unwanted characters # # Input : string + optional class (parameter 2) # Returns : exit code (0 = input considered to be safe, 1 = validation failed) ################################################################################ SafeInput() { exitcode=1 # Test against the string with a generic test set if [ $# -eq 1 ]; then input="$1" # Use sed to strip all characters -except- those that are allowed # - Common set of characters: a-z, A-Z, 0-9 # - Special characters: , /._-:= # - Space for names (like auditor name) cleaned=$(echo "$input" | sed 's/[^[:space:]a-zA-Z0-9\/\._:=-]//g') # If two parameters are specified, then test input against 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 # Test if the cleaned string is the same as the original input 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 group, wheel or similar (found: ${GROUP})." ExitFatal fi fi # Owner permissions OWNER_PERMS=$(echo ${PERMS} | cut -c2-4) if [ ! "${OWNER_PERMS}" = "rw-" -a ! "${OWNER_PERMS}" = "r--" ]; then echo "Fatal error: permissions of file $1 are not strict enough. Access to 'owner' should be read-write, or read. Change with: chmod u=rw $1" ExitFatal fi # Group permissions # TODO - harden this even more by setting default to read-only for group (like 'other') GROUP_PERMS=$(echo ${PERMS} | cut -c5-7) if [ ! "${GROUP_PERMS}" = "rw-" -a ! "${GROUP_PERMS}" = "r--" -a ! "${GROUP_PERMS}" = "---" ]; then echo "Fatal error: permissions of file $1 are not strict enough. Access to 'group' should be read-write, read, or none. Change with: chmod g=r $1" ExitFatal fi # Other permissions OTHER_PERMS=$(echo ${PERMS} | cut -c8-10) if [ ! "${OTHER_PERMS}" = "---" -a ! "${OTHER_PERMS}" = "r--" ]; then echo "Fatal error: permissions of file $1 are not strict enough. Access to 'other' should be denied or read-only. Change with: chmod o=r $1" ExitFatal fi # Set PERMS_OK to 1 if no fatal errors occurred PERMS_OK=1 LogText "File permissions are OK" exitcode=0 fi else ReportException "SafePerms()" "Invalid number of arguments for function" fi else PERMS_OK=1 exitcode=0 fi return ${exitcode} } ################################################################################ # Name : SearchItem() # Description : Search if a specific string exists in in a file # # Parameters : $1 = search key (string) # $2 = file (string) # $3 = optional arguments: # --sensitive - don't store results in log # Returns : True (0) or False (1) ################################################################################ SearchItem() { PERFORM_SCAN=0 MASK_LOG=0 RETVAL=1 if [ $# -lt 2 ]; then ExitFatal "Not enough arguments for function SearchItem()" elif [ $# -ge 2 ]; then FILE=$2 STRING=$1 PERFORM_SCAN=1 fi # Parse any additional arguments if [ $# -gt 2 ]; then shift; shift # Skip the first two (string and file) while [ $# -ge 1 ]; do case $1 in "--sensitive") MASK_LOG=1 ;; esac shift # Go to next parameter done fi if [ ${PERFORM_SCAN} -eq 1 ]; then # Don't search in /dev/null, it's too empty there if [ -f ${FILE} ]; then # Check if we can find the main type (with or without brackets) LogText "Test: search string ${STRING} in file ${FILE}" FIND=$(grep -E "${STRING}" ${FILE}) if [ -n "${FIND}" ]; then LogText "Result: found search string '${STRING}'" if [ ${MASK_LOG} -eq 0 ]; then LogText "Full string returned: ${FIND}"; fi RETVAL=0 else LogText "Result: search search string '${STRING}' NOT found" RETVAL=1 fi else LogText "Skipping search, file (${FILE}) does not exist" ReportException "${TEST_NO}" "Test is trying to search for a string in nonexistent file" fi else ReportException "${TEST_NO}" "Search test is skipped, which is unexpected" fi return ${RETVAL} } ################################################################################ # Name : ShowComplianceFinding() # Description : Display a section of a compliance standard which is not fulfilled # # Parameters : <several parameters, see test> # Returns : <nothing> ################################################################################ ShowComplianceFinding() { REASON="" STANDARD_NAME="" STANDARD_VERSION="" STANDARD_SECTION="" STANDARD_SECTION_TITLE="" ACTUAL_VALUE="" EXPECTED_VALUE="" while [ $# -ge 1 ]; do case $1 in --standard) shift STANDARD_NAME=$1 ;; --version) shift STANDARD_VERSION=$1 ;; --section) shift STANDARD_SECTION=$1 ;; --section-title) shift STANDARD_SECTION_TITLE=$1 ;; --reason) shift REASON=$1 ;; --actual) shift ACTUAL_VALUE=$1 ;; --expected) shift EXPECTED_VALUE=$1 ;; *) echo "INVALID OPTION (ShowComplianceFinding): $1" exit 1 ;; esac # Go to next parameter shift done # Should we show this non-compliance on screen? SHOW=0 case ${STANDARD_NAME} in cis) if [ ${COMPLIANCE_ENABLE_CIS} -eq 1 ]; then SHOW=1; fi STANDARD_FRIENDLY_NAME="CIS" ;; hipaa) if [ ${COMPLIANCE_ENABLE_HIPAA} -eq 1 ]; then SHOW=1; fi STANDARD_FRIENDLY_NAME="HIPAA" ;; iso27001) if [ ${COMPLIANCE_ENABLE_ISO27001} -eq 1 ]; then SHOW=1; fi STANDARD_FRIENDLY_NAME="ISO27001" ;; pci-dss) if [ ${COMPLIANCE_ENABLE_PCI_DSS} -eq 1 ]; then SHOW=1; fi STANDARD_FRIENDLY_NAME="PCI DSS" ;; esac # Only display if standard is enabled in the profile and mark system as non-compliant if [ ${SHOW} -eq 1 ]; then COMPLIANCE_FINDINGS_FOUND=1 DisplayManual " [${WHITE}${STANDARD_FRIENDLY_NAME} ${STANDARD_VERSION}${NORMAL}] - ${CYAN}Section ${STANDARD_SECTION}${NORMAL} - ${WHITE}${STANDARD_SECTION_TITLE}${NORMAL}" DisplayManual " - Details: ${REASON}" DisplayManual " - Configuration: ${RED}${ACTUAL_VALUE}${NORMAL} / ${EXPECTED_VALUE}" DisplayManual "" fi } ################################################################################ # Name : ShowSymlinkPath() # Description : Check if we can find the path behind a symlink # # Parameters : $1 = file (string) # Returns : FOUNDPATH (0 not found, 1 found path), SYMLINK (new path) ################################################################################ ShowSymlinkPath() { if [ $# -eq 0 ]; then ExitFatal "Function ShowSymlinkPath() called without a file name"; fi sFILE=$1 FOUNDPATH=0 SYMLINK_USE_PYTHON=0 SYMLINK_USE_READLINK=0 LogText "Action: checking symlink for file ${sFILE}" # Check for symlink if [ -L ${sFILE} ]; then # macOS does not know -f option, nor do some others if [ "${OS}" = "macOS" ]; then # If a Python binary is found, use the one in path if [ ${BINARY_SCAN_FINISHED} -eq 0 -a "${PYTHONBINARY}" = "" ]; then FIND=$(which python 2> /dev/null | grep -v "no [^ ]* in ") if [ ! "${FIND}" = "" ]; then LogText "Setting temporary pythonbinary variable"; PYTHONBINARY="${FIND}"; fi fi if [ ! "${PYTHONBINARY}" = "" ]; then SYMLINK_USE_PYTHON=1 LogText "Note: using Python to determine symlinks" tFILE=$(python -c "import os,sys; print(os.path.realpath(os.path.expanduser(sys.argv[1])))" $1) fi else if [ ${BINARY_SCAN_FINISHED} -eq 0 -a "${READLINKBINARY}" = "" ]; then FIND=$(which readlink 2> /dev/null | grep -v "no [^ ]* in ") if [ ! "${FIND}" = "" ]; then LogText "Setting temporary readlinkbinary variable"; READLINKBINARY="${FIND}"; fi fi if [ ! "${READLINKBINARY}" = "" ]; then SYMLINK_USE_READLINK=1 LogText "Note: Using real readlink binary to determine symlink on ${sFILE}" tFILE=$(${READLINKBINARY} -f ${sFILE}) LogText "Result: readlink shows ${tFILE} as output" fi fi # Check if we can find the file now if [ "${tFILE}" = "" ]; then LogText "Result: command did not return any value" elif [ -f ${tFILE} ]; then sFILE="${tFILE}" LogText "Result: symlink found, pointing to file ${sFILE}" FOUNDPATH=1 elif [ -b ${tFILE} ]; then sFILE="${tFILE}" LogText "Result: symlink found, pointing to block device ${sFILE}" FOUNDPATH=1 elif [ -c ${tFILE} ]; then sFILE="${tFILE}" LogText "Result: symlink found, pointing to character device ${sFILE}" FOUNDPATH=1 elif [ -d ${tFILE} ]; then sFILE="${tFILE}" LogText "Result: symlink found, pointing to directory ${sFILE}" FOUNDPATH=1 else # Check the full path of the symlink, strip the filename, copy the path and linked filename together tDIR=$(echo ${sFILE} | awk '{match($1, "^.*/"); print substr($1, 1, RLENGTH-1)}') tFILE="${tDIR}/${tFILE}" if [ -L ${tFILE} ]; then LogText "Result: this symlink links to another symlink" # Ensure that we use a second try with the right tool as well if [ ${SYMLINK_USE_PYTHON} -eq 1 ]; then tFILE=$(python -c "import os,sys; print(os.path.realpath(os.path.expanduser(sys.argv[1])))" ${tFILE}) elif [ ${SYMLINK_USE_READLINK} -eq 1 ]; then tFILE=$(${READLINKBINARY} -f ${tFILE}) fi # Check if we now have a normal file if [ -f ${tFILE} ]; then sFILE="${tFILE}" LogText "Result: symlink finally found, seems to be file ${sFILE}" FOUNDPATH=1 elif [ -d ${tFILE} ]; then sFILE="${tFILE}" LogText "Result: symlink finally found, seems to be directory ${sFILE}" FOUNDPATH=1 else LogText "Result: could not find file ${tFILE}, most likely too complicated symlink or too often linked" fi elif [ -f ${tFILE} ]; then sFILE="${tFILE}" LogText "Result: symlink found, seems to be file ${sFILE}" FOUNDPATH=1 elif [ -d ${tFILE} ]; then sFILE="${tFILE}" LogText "Result: symlink found, seems to be directory ${sFILE}" FOUNDPATH=1 else LogText "Result: file ${tFILE} in ${tDIR} not found" fi fi else LogText "Result: file ${sFILE} is not a symlink" fi # Now check if our new location is actually a file or directory destination if [ -L ${sFILE} ]; then LogText "Result: unable to determine symlink, or location ${sFILE} is just another symlink" FOUNDPATH=0 fi if [ ${FOUNDPATH} -eq 1 ]; then SYMLINK="${sFILE}" else SYMLINK="" fi } ################################################################################ # Name : SkipAtomicTest() # Description : Test if an atomic test should be skipped # # Input : String (particular test) # Returns : 0 (skip test) or 1 (do not skip test) # Usage : if SkipAtomicTest "ssh-7408:port"; then echo "Skip this atomic test"; fi ################################################################################ SkipAtomicTest() { RETVAL=255 if [ $# -eq 1 ]; then STRING="" RETVAL=1 # Check if this test is on the list to skip for item in ${SKIP_TESTS}; do STRING=$(echo $1 | tr '[:lower:]' '[:upper:]') if [ "${item}" = "${STRING}" ]; then RETVAL=0; LogText "Atomic test ($1) skipped by configuration (skip-test)"; fi done else ReportException "SkipAtomicTest()" "Function called without right number of arguments (1)" fi return $RETVAL } ################################################################################ # Name : Status() # Description : Reports back the status of tool # # Returns : text to screen # Notes : kill --signal USR1 <PID> or pkill --signal USR1 lynis ################################################################################ Status() { echo "" echo "Date / time : $(date "+%Y-%m-%d %H:%M:%S")" echo "Active test : ${TEST_NO:-NONE}" echo "" } ################################################################################ # Name : StoreNginxSettings() # Description : Store parsed settings from nginx (by ParseNginx) # Input : multiple options # Returns : <nothing> ################################################################################ StoreNginxSettings() { CONFIG_DEPTH=0; CONFIG_FILE=""; CONFIG_SETTING=""; CONFIG_TREE=""; CONFIG_VALUE="" while [ $# -ge 1 ]; do case $1 in --config) shift CONFIG_FILE=$1 ;; --depth) shift CONFIG_DEPTH=$1 ;; # none | events | server | unknown --tree) shift CONFIG_TREE=$1 case ${CONFIG_TREE} in "/") CONFIG_COUNTER=0 ;; "/events") CONFIG_COUNTER=${NGINX_EVENTS_COUNTER=0} ;; "/http") CONFIG_COUNTER=${NGINX_HTTP_COUNTER=0} ;; "/server") CONFIG_COUNTER=${NGINX_SERVER_COUNTER=0} ;; "/server/location") CONFIG_COUNTER=${NGINX_LOCATION_COUNTER=0} ;; *) Debug "Unknown configuration tree of nginx ${CONFIG_TREE}" ;; esac ;; --setting) shift CONFIG_SETTING=$1 ;; --value) shift CONFIG_VALUE=$1 ;; *) echo "INVALID OPTION (StoreNginxSettings): $1 $2" #ExitFatal ;; esac # Go to next parameter shift done if [ -z "${CONFIG_DEPTH}" ]; then CONFIG_DEPTH="0"; fi if [ -z "${CONFIG_SETTING}" ]; then CONFIG_SETTING="NA"; fi if [ -z "${CONFIG_TREE}" ]; then CONFIG_TREE="/"; fi if [ -z "${CONFIG_VALUE}" ]; then CONFIG_VALUE="NA"; fi Report "nginx_config[]=|file=${CONFIG_FILE}|depth=${CONFIG_DEPTH}|tree=${CONFIG_TREE}|number=${CONFIG_COUNTER}|setting=${CONFIG_SETTING}|value=${CONFIG_VALUE}|" } ################################################################################ # Name : TestValue() # Description : Test if a value is good/bad (e.g. according to best practices) # # Input : --function <value> --value <value> --search <value> # Returns : 0 (True) or 1 (False) # Usage : if TestValue --function contains --value "Full Text" --search "Text"; then echo "Found!"; fi ################################################################################ TestValue() { RETVAL=255 FIND="" VALUE="" RESULT="" SEARCH="" CMP1=""; CMP2="" while [ $# -ge 1 ]; do case $1 in --function) shift FUNCTION=$1 ;; --value) shift VALUE=$1 ;; --search) shift SEARCH=$1 ;; *) echo "INVALID OPTION USED (TestValue): $1" exit 1 ;; esac # Go to next parameter shift done if [ "${VALUE}" = "" ]; then echo "No value provided to function (TestValue)"; fi # Apply the related function case ${FUNCTION} in "contains") FIND=$(echo ${VALUE} | grep -E "${SEARCH}") if [ "${FIND}" = "" ]; then RETVAL=1; else RETVAL=0; fi ;; #"gt" | "greater-than") COLOR=$GREEN ;; "equals") CMP1=$(echo ${SEARCH} | tr '[:upper:]' '[:lower:']) CMP2=$(echo ${VALUE} | tr '[:upper:]' '[:lower:']) if [ "${CMP1}" = "${CMP2}" ]; then RETVAL=0; else RETVAL=1; fi ;; #"not-equal") COLOR=$WHITE ;; #"lt" | "less-than") COLOR=$YELLOW ;; *) echo "INVALID OPTION USED (TestValue, parameter of function: $1)"; exit 1 ;; esac if [ "${RETVAL}" -lt 2 ]; then return ${RESULT} else Fatal "ERROR: No result returned from function (TestValue). Incorrect usage?" #ExitFatal fi } ################################################################################ # Name : ViewCategories() # Description : Show all available categories # # Input : <nothing> # Returns : <nothing> # Usage : ViewCategories ################################################################################ ViewCategories() { for CATEGORY in ${TEST_AVAILABLE_CATEGORIES}; do echo "${CATEGORY}"; done ExitClean } ################################################################################ # Name : ViewGroups() # Description : Show what group of tests are available # # Input : <nothing> # Returns : <nothing> # Usage : ViewGroups ################################################################################ ViewGroups() { if [ -n "${INCLUDEDIR}" ]; then for I in $(ls ${INCLUDEDIR}/tests_* | xargs -n 1 basename | sed 's/tests_//' | grep -v "custom.template"); do echo "${I}" done fi ExitClean } ################################################################################ # Name : WaitForKeyPress() # Description : Wait for the user to press [ENTER], unless quickmode is set # # Input : <nothing> # Returns : <nothing> # Usage : WaitForKeyPress ################################################################################ WaitForKeyPress() { if [ ${QUICKMODE} -eq 0 ]; then echo ""; echo "[ Press [ENTER] to continue, or [CTRL]+C to stop ]" read -r void fi } ################################################################################ # # Legacy functions - Do not use! # ################################################################################ # For compatibility reasons they are listed here, but will be phased out in # steps. If they still get used, they will trigger errors on screen. ################################################################################ counttests() { DisplayWarning "Deprecated function used (counttests)" if IsDeveloperMode; then Debug "Warning: old counttests function is used. Please replace any reference with CountTests."; fi CountTests } logtext() { DisplayWarning "Deprecated function used (logtext)" if IsDeveloperMode; then Debug "Warning: old logtext function is used. Please replace any reference with LogText."; fi LogText "$1" } logtextbreak() { DisplayWarning "Deprecated function used (logtextbreak)" if IsDeveloperMode; then Debug "Warning: old logtextbreak function is used. Please replace any reference with LogTextBreak."; fi LogTextBreak "$1" } report() { DisplayWarning "Deprecated function used (report)" if IsDeveloperMode; then Debug "Warning: old report function is used. Please replace any reference with Report."; fi Report "$1" } wait_for_keypress() { DisplayWarning "Deprecated function used (wait_for_keypress)" if IsDeveloperMode; then Debug "Warning: old wait_for_keypress function is used. Please replace any reference with WaitForKeyPress."; fi WaitForKeyPress } ShowResult() { DisplayWarning "Deprecated function used (ShowResult)" if IsDeveloperMode; then Debug "Warning: old ShowResult() function is used. Please replace any reference with WaitForKeyPress."; fi } # EOF
Save
cmd:
run