Sorry to resurrect an old question. I had the same issue trying to get maldet to email a report after every scan. I dug into the source code as suggested by @Tilman. The function responsible for sending report emails, view_report(), can be found in /usr/local/maldetect/internals/functions lines 645-706 in v1.6.4. Looking specifically at the code responsible (lines 681-696) we see mail is only sent if a SCANID, stored as $rid, is the suffix, ie 190429-0343.31494, corresponds to the filename of a report that is present in /usr/local/maldetect/sess/ such as session.190429-0343.31494
if [ -f "$sessdir/session.$rid" ] && [ ! -z "$(echo $2 | grep '\@')" ]; then
if [ -f "$mail" ]; then
cat $sessdir/session.$rid | $mail -s "$email_subj" "$2"
elif [ -f "$sendmail" ]; then
if ! grep -q "SUBJECT: " "$sessdir/session.$rid"; then
echo -e "SUBJECT: $email_subj\n$(cat $sessdir/session.$rid)" > $sessdir/session.$rid
fi
cat $sessdir/session.$rid | $sendmail -t "$2"
else
eout "{scan} no \$mail or \$sendmail binaries found, e-mail alerts disabled."
exit
fi
eout "{report} report ID $rid sent to $2" 1
exit
fi
The code that handles an empty SCANID immediatly follows this (lines 697-705):
if [ "$rid" == "" ] && [ -f "$sessdir/session.last" ]; then
rid=`cat $sessdir/session.last`
$EDITOR $sessdir/session.$rid
elif [ -f "$sessdir/session.$rid" ]; then
$EDITOR $sessdir/session.$rid
else
echo "{report} no report found, aborting."
exit
fi
I would have thought the code handling an empty SCANID would simply grab the most recent one and email it. What it actually does is look at /usr/local/maldetect/sess/session.last where maldet stores the most recent SCANID. And for some reason, it then opens the corresponding report in the terminal editor rather than just printing it out. Note that there actually isn't any working code to email the most recent report.
-- Updated Fix - May 5th, 2019--
Since preventing LMD from performing integrity checks, as required in my original fix, is a potential security risk, I created an alternative solution using LMD's custom.cron. The benefit is integrity checks remain in place and the email script should persist through updates. You don't need to touch LMD internal files or maldet daily cron.
Ensure $email_alert="1" and $email_addr= is set to at least one proper email address in /usr/local/maldetect/conf.maldet. Then add the following to /usr/local/maldetect/cron/custom.cron and it will run automatically at the end of the maldet daily cron:
##
# Please use this file for preservation of custom LMD execution code for the daily cronjob.
# NOTE: scripts in this file are called at the end of maldet daily cron as $custom_cron_exec
##
# log_cron="1" enable logging, log_cron="0" disable logging
# applies only to the code in this file
log_cron="1"
# logging function borrowed from /maldetect/internals/functions
eout() {
if [ "$log_cron" == "1" ]; then
msg="$1"
stdout="$2"
appn=maldet
if [ ! -d "$logdir" ]; then
mkdir -p $logdir ; chmod 700 $logdir
fi
if [ ! -f "$maldet_log" ]; then
touch $maldet_log
fi
log_size=`$wc -l $maldet_log | awk '{print$1}'`
if [ "$log_size" -ge "20000" ]; then
trim=1000
printf "%s\n" "$trim,${log_size}d" w | ed -s $maldet_log 2> /dev/null
fi
if [ ! "$msg" == "" ]; then
echo "$(date +"%b %d %H:%M:%S") $(hostname -s) $appn($$): $msg" >> $maldet_log
if [ ! -z "$stdout" ]; then
echo "$appn($$): $msg"
fi
fi
fi
}
eout "{cron} running $cron_custom_exec"
##
# LMD Daily Email v1.0.0
# Author: kdub Email: kdubdev@gmail.com Date: May 5th, 2019
# https://github.com/kdubdev/linux-malware-detect/blob/master/files/cron/custom.cron
# Script to send email of newest report after daily scan. More info:
# https://serverfault.com/questions/805158/how-to-get-an-email-report-of-whatever-the-most-recent-maldet-scan-is
# #
de_version='v1.0.0'
eout "{cron} starting LMD Cron Email $de_version"
eout "{cron} $intcnf shows email_alert=$email_alert email_addr=$email_addr"
# Default email subject defined in /usr/local/maldetect/internals/internals.conf
# is email_subj="maldet alert from $(hostname)"
# comment this line to use the default email_subj or change to what you want
printf -v email_subj '[%s] %s: Scan Report' "$(hostname)" "$appn($$)"
# uncomment email_addr below to override recipients. Separate multiple emails with ,
# use $email_addr to include recipient defined in /usr/local/maldetect/conf.maldet
# email_addr="$email_addr,second@domain.tld,third@domain.tld"
# this is the email text inserted before the report
body_intro="Here are the results of the latest LMD scan:"
# this is the email text inserted after the report
printf -v body_footer "Email provided by LMD Cron Email %s\nCron file: %s\nLog file: %s" "$de_version" "$cron_custom_exec" "$maldet_log"
# this is a very weak email validation, just looking for @
if [ "$email_alert" == "1" ] && [ ! -z "$(echo $email_addr | grep '\@')" ]; then
# email_alert is true and email provided, send newest report
if [ -f "$sessdir/session.last" ]; then
# Get most recent scan id
rid=$(cat "$sessdir/session.last")
if [ ! -z "$rid" ]; then
# session.list contains something
if [ -f "$sessdir/session.$rid" ]; then
# report exists, get contents
body=$(cat "$sessdir/session.$rid")
eout "{cron} reading report $sessdir/session.$rid"
else
# report doesn't exist
body="{cron} unable to find report $sessdir/session.$rid."
fi
if [ -z "$body" ]; then
# report file exists but is empty
body="{cron} report $sessdir/session.$rid is empty."
fi
else
# session.last is empty
body="{cron} $sessdir/session.last is empty."
fi
else
# session.last doesn't exist
body="{cron} unable to find $sessdir/session.last."
fi
# log if body starts with {cron} ie there's a problem reading report
if [[ $body == '{cron}'* ]]; then
eout "$body"
fi
# add intro and footer to body
body=$(printf "%s\n\n%s\n\n%s\n\n" "$body_intro" "$body" "$body_footer")
if [ -f "$mail" ]; then
printf "%s" "$body" | $mail -s "$email_subj" "$email_addr"
eout "{cron} mail sent using $mail to $email_addr, subject: $email_subj."
elif [ -f "$sendmail" ]; then
printf "%s\n%s" "$email_subj" "$body" | $sendmail -t "$email_addr"
eout "{cron} mail sent using $sendmail to $email_addr, subject: $email_subj."
fi
fi
eout "{cron} mail latest report finished."
eout "{cron} done running $cron_custom_exec"
You can check for updates here as well https://github.com/kdubdev/linux-malware-detect/blob/master/files/cron/custom.cron
In the script you can disable logging, override the email subject and/or recipients, and customize the email body intro and footer. The script is heavily commented so you can follow along or make changes.
I welcome any feedback or suggestions for improvement.
-- Original Fix Below --
To fix this and add other improvements, I modified view_report() with following changes:
- added option 'newest' as alias to
--report and --report "" to allow $ maldet --report newest user@domain.com
- properly email most recent report when using
$ maldet --report newest user@domain.com or $ maldet --report "" user@domain.com
- change from unnecessarily using editor to view reports, instead simply print to terminal
- improved logging
FIRST: You need to set autoupdate_version_hashed="0" in /usr/local/maldetect/conf.maldet to prevent LMD from automatically overwriting any changes you make when it runs the update check. Be aware that this is a potential security issue:
# This controls validating the LMD executable MD5 hash with known
# good upstream hash value. This allows LMD to replace the the
# executable / force a reinstallation in the event the LMD executable
# is tampered with or corrupted. If you intend to make customizations
# to the LMD executable, you should disable this feature.
# [0 = disabled, 1 = enabled]
autoupdate_version_hashed="0"
SECOND: Replace your current view_report() /usr/local/maldetect/internals/functions (lines 645-706) with this:
view_report() {
# $1 is first arg passed from command line ex. $ maldet --report $1 $2
rid="$1"
# $ maldet --report list
if [ "$rid" == "list" ]; then
tmpf="$tmpdir/.areps$$"
for file in `ls $sessdir/session.[0-9]* 2> /dev/null`; do
SCANID=`cat $file | grep "SCAN ID" | sed 's/SCAN ID/SCANID/'`
FILES=`cat $file | grep "TOTAL FILES" | sed 's/TOTAL //'`
HITS=`cat $file | grep "TOTAL HITS" | sed 's/TOTAL //'`
CLEAN=`cat $file | grep "TOTAL CLEANED" | sed 's/TOTAL //'`
TIME=`cat $file | grep -E "^TIME|^STARTED" | sed -e 's/TIME: //' -e 's/STARTED: //' | awk '{print$1,$2,$3,$4}'`
TIME_U=`date -d "$TIME" "+%s" 2> /dev/null`
ETIME=`cat $file | grep "ELAPSED" | awk '{print$1,$2}' | sed 's/ELAPSED/RUNTIME/'`
if [ -z "$ETIME" ]; then
ETIME="RUNTIME: unknown"
fi
if [ ! -z "$SCANID" ] && [ ! -z "$TIME" ]; then
clean_zero=`echo $CLEAN | awk '{print$2}'`
if [ -z "$clean_zero" ]; then
CLEAN="CLEANED: 0"
fi
echo "$TIME_U | $TIME | $SCANID | $ETIME | $FILES | $HITS | $CLEAN" >> $tmpf
fi
done
if [ -f "$tmpf" ]; then
if [ "$OSTYPE" == "FreeBSD" ]; then
cat $tmpf | sort -k1 -n | cut -d'|' -f2-7 | column -t | more
else
cat $tmpf | sort -k1 -n | tac | cut -d'|' -f2-7 | column -t | more
fi
rm -f $tmpf 2> /dev/null
exit 0
else
eout "{list} unable to find report data for list, check \$sessdir"
exit 1
fi
fi
# If no SCANID is provided or "recent" then set $rid to most recent.
# $ maldet --report "" or $maldet --report newest
if { [ "$rid" == "" ] || [ "$rid" == "newest" ]; } && [ -f "$sessdir/session.last" ]; then
rid=`cat $sessdir/session.last`
fi
# make sure report exists
if [ -f "$sessdir/session.$rid" ]; then
# if email is provided, then send the report and exit
if [ ! -z "$(echo $2 | grep '\@')" ]; then
if [ -f "$mail" ]; then
cat $sessdir/session.$rid | $mail -s "$email_subj" "$2"
elif [ -f "$sendmail" ]; then
if ! grep -q "SUBJECT: " "$sessdir/session.$rid"; then
echo -e "SUBJECT: $email_subj\n$(cat $sessdir/session.$rid)" > $sessdir/session.$rid
fi
cat $sessdir/session.$rid | $sendmail -t "$2"
else
# eout is an internal function to log to maldet_log and echo
eout "{scan} no \$mail or \$sendmail binaries found, e-mail alerts disabled."
exit
fi
eout "{report} report ID $rid sent to $2" 1
exit
# no email is provided so show report and exit
else
printf '%b\n' "$(cat $sessdir/session.$rid)"
exit
fi
# can't find requested report so log & echo error
else
eout "{report} unable to find report session.\$rid, aborting."
exit
fi
}
You can find the entire updated /usr/local/maldetect/internals/functions file in a pull request here as well: https://github.com/kdubdev/linux-malware-detect/blob/patch-1/files/internals/functions
LAST:
Add the following line to the end of /etc/cron.daily/maldet if you want to receive an email after each daily scan:
$inspath/maldet --report newest user@domain.com
Note: if it wasn't clear you can use -e or --report interchangeably.