#!/usr/bin/sh # # rkhunter -- Scan the system for rootkits and other known security issues. # # Copyright (c) 2003-2017, Michael Boelen ( michael AT rootkit DOT nl ) # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA. # # # Unfortunately we must do some O/S checks at the very beginning, # otherwise SunOS will complain about some of the ksh/bash syntax. # By default the SunOS root account uses a simple Bourne shell, # which does not work with RKH. So we exec to use the Bash shell # if it is present, or the Korn shell which is usually installed # by default on Solaris systems. # BSDOS=0 SUNOS=0 BUSYBOX=0 OPERATING_SYSTEM=`uname 2>/dev/null` case "${OPERATING_SYSTEM}" in *BSD|DragonFly) BSDOS=1 unset CLICOLOR CLICOLOR_FORCE ;; SunOS) SUNOS=1 unset CLICOLOR CLICOLOR_FORCE ;; esac if [ $SUNOS -eq 1 ]; then # Simple SunOS test of RANDOM to see if we are now running bash or ksh. if [ -z "$RANDOM" ]; then # If the 'which' output contains a space, then it is probably an error. if [ -n "`which bash 2>/dev/null | grep -v ' '`" ]; then exec bash $0 $* elif [ -n "`which ksh 2>/dev/null | grep -v ' '`" ]; then exec ksh $0 $* else echo "Unable to find the bash or ksh shell to run rkhunter." exit 1 fi exit 0 fi fi # # Check to see if we are using the '--debug' option. If so, then # we exec to log everything to the debug file. # if [ -n "`echo \"$*\" | grep '\-\-debug'`" ]; then RKHDEBUGFILE="" RKHDEBUGBASE="/tmp/rkhunter-debug" # # Ensure we create a random file name. # if [ -n "`which mktemp 2>/dev/null | grep -v ' '`" ]; then RKHDEBUGFILE=`mktemp ${RKHDEBUGBASE}.XXXXXXXXXX` elif [ -n "$RANDOM" ]; then RKHDEBUGFILE="${RKHDEBUGBASE}.$RANDOM" elif [ -n "`date +%N%s 2>/dev/null | grep '^[0-9][0-9]*$'`" ]; then RKHDEBUGFILE="${RKHDEBUGBASE}.`date +%N%s%N`" elif [ -n "`date +%Y%m%d%H%M%S 2>/dev/null | grep '^[0-9][0-9]*$'`" ]; then RKHDEBUGFILE="${RKHDEBUGBASE}.`date +%Y%m%d%H%M%S`" else RKHDEBUGFILE="${RKHDEBUGBASE}.$$" fi if [ -e "${RKHDEBUGFILE}" ]; then if [ -f "${RKHDEBUGFILE}" -a ! -h "${RKHDEBUGFILE}" ]; then rm -f "${RKHDEBUGFILE}" >/dev/null 2>&1 else echo "Cannot use '--debug' option. Debug file \"${RKHDEBUGFILE}\" already exists, but it is not a file." exit 1 fi fi DEBUG_OPT=1 exec 1>"${RKHDEBUGFILE}" 2>&1 chmod 600 "${RKHDEBUGFILE}" >/dev/null 2>&1 set -x else DEBUG_OPT=0 fi # # Now we must determine if we are using the Korn shell or not. If so, # then we alias the 'echo' command and set ECHOOPT. For other shells, # we try and determine the real shell being used, and test to see if # the 'echo -e' command is valid or not. We set ECHOOPT accordingly. # # # Unfortunately *BSD doesn't seem to allow capturing of unknown commands. # So we must alias 'print' to something valid, but which will fail. # test $BSDOS -eq 1 && alias print=false if [ "`print "rkh-ksh-string-test" 2>/dev/null`" = "rkh-ksh-string-test" ]; then alias echo='print' ECHOOPT="--" MYSHELL=ksh elif [ $SUNOS -eq 1 ]; then # For Solaris, if we are not running ksh, then it must be bash. MYSHELL=bash ECHOOPT="-e" else # # We want to get the actual shell used by this program, and # so we need to test /bin/sh. # MYSHELL=/bin/sh test -h ${MYSHELL} && MYSHELL=`readlink ${MYSHELL} 2>/dev/null` MYSHELL=`basename ${MYSHELL} 2>/dev/null` # Assume 'bash' if we have problems finding the real shell. test -z "${MYSHELL}" && MYSHELL=bash # Check if we are using BusyBox. test "${MYSHELL}" = "busybox" && BUSYBOX=1 # # Now test the 'echo -e' command. # if [ "`echo -e \"rkh-ksh\tstring-test\" 2>/dev/null`" = "rkh-ksh string-test" ]; then ECHOOPT="-e" else ECHOOPT="" fi fi # # We now perform a similar test to see if 'echo -n', or "\c", is valid # or not. Unfortunately on some systems both '-e' and '-n' are valid, # but not together. The "\c" option works in these cases. So we set # ECHON accordingly. # if [ "`echo -n -e \"rkh-ksh-string-test\" 2>/dev/null`" = "rkh-ksh-string-test" ]; then ECHON="-n" elif [ "`echo -e \"rkh-ksh-string-test\c\" 2>/dev/null`" = "rkh-ksh-string-test" ]; then ECHON="c" elif [ "`echo \"rkh-ksh-string-test\c\" 2>/dev/null`" = "rkh-ksh-string-test" ]; then ECHON="c" else ECHON="" fi # # We also need to run a test to see if POSIX grep is being # used. If it is, then some typical grep tests will fail. # if [ "`echo \"rkh-grep-test\" | grep '^\+'`" = "rkh-grep-test" ]; then alias grep='grep -E' fi # # It seems that the BusyBox 'readlink' command does have # a '-f' option, but it does not show the true pathname. # So we only use the option for everyone else. # test $BUSYBOX -eq 1 && READLINK_OPT="" || READLINK_OPT="-f" # # Finally, we need to test the 'head' and 'tail' commands # to see if they understand the '-n' option or not. # if head -n 1 /dev/null 2>&1; then HEAD_OPT="-n " else HEAD_OPT="-" fi if tail -n 1 /dev/null 2>&1; then TAIL_OPT="-n " else TAIL_OPT="-" fi ###################################################################### # # Global function definitions # ###################################################################### display() { # # This function is used to display text messages on to the # users screen, as well as in to the log file. The same # message is written to both. However, the screen may have # a coloured result (green for good, red for bad, etc), and # the log file will have the time prefixed to the message and, # optionally, additional information messages after the main # message. All the messages are indexed in the language file. # # Syntax: display --to --type # [--screen-indent ] [--log-indent ] # [--nl []] [--nl-after] [--log-nl] [--screen-nl] [--nonl] # [--result --color ] # [optional message arguments] # # where the destination can be one of SCREEN, LOG or SCREEN+LOG. # The type can be one of PLAIN, INFO or WARNING. # The language file will have all the current values. # # The --screen-indent and --log-indent options are used to # forcibly indent a message. # The --nl option causes a blank-line to be output before the # message both on the screen and in the log file. A following # number can be used to indicate how many blank lines should # be displayed on the screen. # The --log-nl option outputs a blank line only in the log file. # The --screen-nl option outputs a blank line on the screen # regardless of whether SCREEN was specified or not. # The --nl-after option outputs a blank line on the screen after # the message. # The --nonl option is only to be used in special cases where we # want the output of more than one message to appear on the same # line. This is currently only used when trying to obtain the # lock file. It only applies to PLAIN messages, and may not be # supported on all systems (depending on whether 'echo -n' works # or not). # # # We first initialize some variables and then # process the switches used. # WARN_MSG=0; NL=0; NLAFTER=0; LOGINDENT=0; SCREENINDENT=0 LOGNL=0; SCREENNL=0 WRITETO=''; TYPE=''; RESULT=''; COLOR=''; MSG='' LINE1=''; LOGLINE1=''; SPACES=''; NONL='' # # The IFS environment variable could be set to a non-default value # when this function is called. However, we need it to be the default # value in order to dislay things correctly. So, we record the initial # value, and then set it to the default. We set it back to the initial # value whenever we return from this function. # ORIG_IFS=$IFS IFS=$RKHIFS DISPLAY_LINE="display $*" if [ $# -le 0 ]; then echo "Error: Invalid display call - no arguments given" IFS=$ORIG_IFS return fi while [ $# -ge 1 ]; do case "$1" in --to) case "$2" in SCREEN|LOG|SCREEN+LOG) WRITETO=$2 ;; *) echo "Error: Invalid display destination: $2 Display line: ${DISPLAY_LINE}" IFS=$ORIG_IFS return ;; esac shift ;; --type) TYPE=`eval echo "\\$MSG_TYPE_$2"` if [ -z "${TYPE}" -a "$2" != "PLAIN" ]; then if [ $RKHLANGUPDT -eq 0 ]; then echo "Error: Invalid display type: $2 Display line: ${DISPLAY_LINE}" IFS=$ORIG_IFS return fi fi test "$2" = "WARNING" && WARN_MSG=1 shift ;; --result) RESULT=`eval echo "\\$MSG_RESULT_$2"` if [ -z "${RESULT}" ]; then if [ $RKHLANGUPDT -eq 0 ]; then echo "Error: Invalid display result: $2 Display line: ${DISPLAY_LINE}" IFS=$ORIG_IFS return fi fi shift ;; --color) if [ $COLORS -eq 1 ]; then test -n "$2" && COLOR=`eval "echo \\${$2}"` if [ -z "${COLOR}" ]; then echo "Error: Invalid display color: $2 Display line: ${DISPLAY_LINE}" IFS=$ORIG_IFS return fi fi shift ;; --log-indent) LOGINDENT=$2 if [ -z "${LOGINDENT}" ]; then echo "Error: No --log-indent value given. Display line: ${DISPLAY_LINE}" IFS=$ORIG_IFS return elif [ -z "`echo ${LOGINDENT} | grep '^[0-9]*$'`" ]; then echo "Error: Invalid '--log-indent' value given: $2 Display line: ${DISPLAY_LINE}" IFS=$ORIG_IFS return fi shift ;; --screen-indent) SCREENINDENT=$2 if [ -z "${SCREENINDENT}" ]; then echo "Error: No --screen-indent value given. Display line: ${DISPLAY_LINE}" IFS=$ORIG_IFS return elif [ -z "`echo ${SCREENINDENT} | grep '^[0-9]*$'`" ]; then echo "Error: Invalid '--screen-indent' value given: $2 Display line: ${DISPLAY_LINE}" IFS=$ORIG_IFS return fi shift ;; --nl) NL=1 case "$2" in [0-9]) NL=$2 shift ;; esac ;; --log-nl) LOGNL=1 ;; --screen-nl) SCREENNL=1 ;; --nl-after) NLAFTER=1 ;; --nonl) NONL=$ECHON ;; -*) echo "Error: Invalid display option given: $1 Display line: ${DISPLAY_LINE}" IFS=$ORIG_IFS return ;; *) MSG=$1 shift break ;; esac shift done # # Before anything we must record if this is a warning message. # test $WARN_MSG -eq 1 && WARNING_COUNT=`expr ${WARNING_COUNT} + 1` # # For simplicity we now set variables as to whether the output # goes to the screen and/or the log file. In some cases we do # not need to output anything, and so can just return. # if [ $NOLOG -eq 1 ]; then if [ "${WRITETO}" = "LOG" ]; then IFS=$ORIG_IFS return fi test "${WRITETO}" = "SCREEN+LOG" && WRITETO="SCREEN" fi if [ $NOTTY -eq 1 ]; then if [ "${WRITETO}" = "SCREEN" ]; then IFS=$ORIG_IFS return fi test "${WRITETO}" = "SCREEN+LOG" && WRITETO="LOG" fi test "${WRITETO}" = "SCREEN" -o "${WRITETO}" = "SCREEN+LOG" && WRITETOTTY=1 || WRITETOTTY=0 test "${WRITETO}" = "LOG" -o "${WRITETO}" = "SCREEN+LOG" && WRITETOLOG=1 || WRITETOLOG=0 # # Now check that the options we have been given make sense. # if [ $WRITETOTTY -eq 0 -a $WRITETOLOG -eq 0 ]; then echo "Error: Invalid display destination: Display line: ${DISPLAY_LINE}" IFS=$ORIG_IFS return elif [ $WRITETOTTY -eq 1 -a $COLORS -eq 1 -a -n "${RESULT}" -a -z "${COLOR}" ]; then echo "Error: Invalid display - no color given: Display line: ${DISPLAY_LINE}" IFS=$ORIG_IFS return fi # # We only allow no newline for PLAIN messages. # test -n "${TYPE}" && NONL="" # # If we want whitelisted results to be shown as white, or # black for colour set two users, then change the colour now. # if [ $WLIST_IS_WHITE -eq 1 -a $WRITETOTTY -eq 1 -a $COLORS -eq 1 -a "${RESULT}" = "${MSG_RESULT_WHITELISTED}" ]; then COLOR=$WHITE fi # # We set the variable LINE1 to contain the first line of the message. # For the log file we use the variable LOGLINE1. We also set # where the language file is located. If a message cannot be found # in the file, then we look in the English file. This will allow RKH # to still work even when the language files change. # LANG_FILE="${DB_PATH}/i18n/${LANGUAGE}" if [ -n "${MSG}" ]; then LINE1=`grep ${GREP_OPT} "^${MSG}:" "${LANG_FILE}" 2>/dev/null | head ${HEAD_OPT}1 | cut -d: -f2-` if [ $RKHCHKLOCALE -eq 1 ]; then LINE1=`echo "${LINE1}" | ${ICONV_CMD} -f UTF-8 -t ${RKHCHRMAP} 2>/dev/null` test $? -ne 0 && LINE1="" fi if [ -z "${LINE1}" ]; then LANG_FILE="${DB_PATH}/i18n/en" LINE1=`grep ${GREP_OPT} "^${MSG}:" "${LANG_FILE}" 2>/dev/null | head ${HEAD_OPT}1 | cut -d: -f2-` if [ -z "${LINE1}" ]; then echo "Error: Invalid display - keyword cannot be found: Display line: ${DISPLAY_LINE}" IFS=$ORIG_IFS return fi else LINE1=`echo "${LINE1}" | sed -e 's/\`/\\\\\`/g'` fi test -n "${LINE1}" && LINE1=`eval "echo \"${LINE1}\" | sed -e 's/;/\\;/g'"` fi # # At this point LINE1 is the text of the message. We have to # see if the message is to be indented, and must prefix the # time to log file messages. We must do the log file first # because it uses LINE1. # if [ $WRITETOLOG -eq 1 ]; then LOGLINE1=`date '+[%H:%M:%S]'` test $NL -gt 0 -o $LOGNL -eq 1 && echo "${LOGLINE1}" >>"${RKHLOGFILE}" if [ -n "${TYPE}" ]; then LOGLINE1="${LOGLINE1} ${TYPE}: ${LINE1}" else test $LOGINDENT -gt 0 && SPACES=`echo "${BLANK_LINE}" | cut -c1-$LOGINDENT` LOGLINE1="${LOGLINE1} ${SPACES}${LINE1}" fi fi if [ $WRITETOTTY -eq 1 -a $SCREENINDENT -gt 0 ]; then SPACES=`echo "${BLANK_LINE}" | cut -c1-$SCREENINDENT` LINE1="${SPACES}${LINE1}" fi # # We now check to see if a result is to be output. If it is, # then we need to space-out the line and color the result. # if [ -n "${RESULT}" ]; then if [ $WRITETOTTY -eq 1 ]; then LINE1_NUM=`echo "${LINE1}" | wc -c | tr -d ' '` NUM_SPACES=`expr 62 - ${LINE1_NUM}` test $NUM_SPACES -lt 1 && NUM_SPACES=1 if [ $COLORS -eq 0 ]; then SPACES=`echo "${BLANK_LINE}" | cut -c1-$NUM_SPACES` LINE1="${LINE1}${SPACES}[ ${RESULT} ]" else LINE1="${LINE1}\033[${NUM_SPACES}C[ ${COLOR}${RESULT}${NORMAL} ]" fi fi if [ $WRITETOLOG -eq 1 ]; then LOGLINE1_NUM=`echo "${LOGLINE1}" | wc -c | tr -d ' '` NUM_SPACES=`expr 62 - ${LOGLINE1_NUM}` test $NUM_SPACES -lt 1 && NUM_SPACES=1 SPACES=`echo "${BLANK_LINE}" | cut -c1-$NUM_SPACES` LOGLINE1="${LOGLINE1}${SPACES}[ ${RESULT} ]" fi elif [ $WRITETOTTY -eq 1 -a -n "${COLOR}" ]; then LINE1="${COLOR}${LINE1}${NORMAL}" fi # # We can now output the message. We start with any required blank # lines, and then the first line. If this is a warning message we # write to the log file any additional lines. # if [ $SCREENNL -eq 1 ]; then test $QUIET -eq 0 -a $SHOWWARNINGSONLY -eq 0 -a $NOTTY -eq 0 && echo "" fi if [ $WRITETOTTY -eq 1 ]; then NLLOOP=$NL while test $NLLOOP -gt 0; do echo "" NLLOOP=`expr ${NLLOOP} - 1` done if [ "${NONL}" = "c" ]; then echo $ECHOOPT "${LINE1}\c" else echo $NONL $ECHOOPT "${LINE1}" fi fi if [ $WRITETOLOG -eq 1 ]; then echo $ECHOOPT "${LOGLINE1}" >>"${RKHLOGFILE}" if [ $WARN_MSG -eq 1 ]; then test $SHOWWARNINGSONLY -eq 1 && echo $ECHOOPT "${LOGLINE1}" | cut -d' ' -f2- LINE1=1 OLDIFS=$IFS IFS=$IFSNL for LOGLINE1 in `grep ${GREP_OPT} "^${MSG}:" "${LANG_FILE}" 2>/dev/null | cut -d: -f2-`; do if [ $LINE1 -eq 1 ]; then LINE1=0 continue else test $SHOWWARNINGSONLY -eq 1 && echo $ECHOOPT " ${LOGLINE1}" echo $ECHOOPT " ${LOGLINE1}" >>"${RKHLOGFILE}" fi done IFS=$OLDIFS elif [ $SHOWWARNINGSONLY -eq 1 -a -n "`echo \"${LOGLINE1}\" | grep '^\[[0-9][0-9]:[0-9][0-9]:[0-9][0-9]\] '`" ]; then echo $ECHOOPT "${LOGLINE1}" | cut -d' ' -f2- fi fi # # Output a final blank line if requested to do so. # test $WRITETOTTY -eq 1 -a $NLAFTER -eq 1 && echo "" IFS=$ORIG_IFS return } name2text() { # # This function changes any spaces in a character string to '', # tabs to '' and any control characters to '?'. This allows # pathnames to be seen more easily - especially if spaces or tabs # are used. # # Whilst it would be nice to perform this function in 'display', we do # not want the changes to occur for all messages. So we keep this a # separate function, and only use it where necessary. # # Note that we must ensure that the 'echo' command does not interpret # any part of the string. # echo $ECHOOPT "$*" | sed -e 's/ //g; s/ //g' | tr -d '\n' | tr '[:cntrl:]' '?' return } keypresspause() { # # This function will display a prompt message to the user. # if [ $SKIP_KEY_PRESS -eq 0 -a $QUIET -eq 0 ]; then display --to SCREEN --type PLAIN --nl PRESSENTER read RKHTMPVAR test "${RKHTMPVAR}" = "s" -o "${RKHTMPVAR}" = "S" && SKIP_KEY_PRESS=1 fi return } get_option() { # # This function is used to process configuration file options. # # Syntax: get_option (single | space-list | newline-list)