#!/bin/sh
# copyright Philipp Wuensche
# License: beer ware (http://en.wikipedia.org/wiki/Beerware)
pkgng_bin='/usr/local/sbin/pkg'

if [ ! -x "${pkgng_bin}" ]; then
    echo "Error: `basename $0 ` depends on ports-mgmt/pkg installed"
    exit
fi

if [ -f /usr/local/etc/jailaudit.conf ]; then
    . /usr/local/etc/jailaudit.conf
fi

if [ "X${audit_path}" = "X" ]; then
    audit_path=/usr/local/jailaudit/reports
fi

if [ "X${tmp_path}" = "X" ]; then
    tmp_path=/usr/local/jailaudit/tmp
fi

if [ "X${hostname}" = "X" ]; then
    hostname=`/bin/hostname`
fi

action=$1
mailaddrs=$2
jailnames=$3
format=$4

PRINT_USAGE () {
    echo "Usage: `basename $0` <generate|mail> mailaddr <\"jailnames\"|ALL>"
    echo "       `basename $0` <list> mailaddr <\"jailnames\"|ALL> (LINEFORMAT)"
    echo "        mailaddr can be \"-\" for stdout"
    echo "        jailnames can be \"ALL\" for all audits or a list of jail-hostnames"
    echo "        LINEFORMAT: comma seperated list with elements of: jailname,jid,portname,category (default)"
    [ ! -z $1 ] && exit $1
}

case ${action} in
 generate)
    JLS() {
        /usr/sbin/jls jid host.hostname path | grep -v '/var/empty$'
    }

    # exit if no jails are running that we can process
    [ `JLS |wc -l` -gt 0 ] || exit 0

    echo
    echo "Downloading a current audit database:"

    # use pkgng if it is available in the hostsystem
	echo "using ${pkgng_bin} version `${pkgng_bin} -v`."
	${pkgng_bin} audit -F > /dev/null
	PORTLIST_CMD () {
	 ${pkgng_bin} -j ${jid} query -a '%n-%v'
	}
	GET_PKG_ORIGIN () { # $portname $jid origin
		local _port=$1
		local _jid=$2
		local _origin=`${pkgng_bin} -j ${_jid} query '%o' ${_port}`
		eval $3\=${_origin}
	}
	AUDIT_PORT() {
		${pkgng_bin} audit $1 | grep -vE '^[0-9]+ problem\(s\)' |grep -v 'is vulnerable:' > $2 2> /dev/null
	}
	PORT_VULNERABLE() {
		if [ -f $1 ] && [ -s $1 ]; then return 0; else return 1; fi
	}

    echo

    rm -rf ${tmp_path}/*
    rm -rf ${audit_path}/*
    rm -f ${tmp_path}/_jailaudit_allports

    for jid in `JLS|awk '{print $1}'`; do
        PORTLIST_CMD >> ${tmp_path}/_jailaudit_allports 2> /dev/null
    done

    for portname in `cat ${tmp_path}/_jailaudit_allports|sort|uniq`; do
        AUDIT_PORT $portname ${tmp_path}/_$portname
    done

    for jid in `JLS|awk '{print $1}'`; do
        pcount=0
        for portname in `PORTLIST_CMD`; do
            if PORT_VULNERABLE ${tmp_path}/_${portname}; then
	            GET_PKG_ORIGIN ${portname} ${jid} origin
	            echo "${portname} (${origin}) is vulnerable:" >> ${audit_path}/${jid}
                grep -v "problem(s) found." ${tmp_path}/_${portname} >> ${audit_path}/${jid} 2> /dev/null
                pcount=$((${pcount}+1))
            fi
        done
        echo "${pcount} problem(s) found." >> ${audit_path}/${jid}
    done

    cd ${audit_path}
    JLS | awk '{print $1" "$2"_"$1}' | xargs -n2 mv
    rm -rf ${tmp_path}/*
    exit
 ;;
 list)
	# this mode lists all affected packages with its category together 
	# with the jailname and JID they are installed in
	# very useful to script upgrade tools
	# default is: jailname,jid,portname,category

	# bail out if no jailnames and mailaddrs are given
	[ -z "${jailnames}" -o -z "${mailaddrs}" ] && PRINT_USAGE 1
	: ${format:=jailname,jid,portname,category}

    PRINTLINE () {
        local field
        local fields
        for field in $@; do
         case ${field} in jailname|jid|portname|category)
          [ ! -z "${fields}" ] && fields="${fields} \$${field}"
          [ -z "${fields}" ] && fields="\$${field}"
         ;; *) return 1;; esac
        done
        eval line=\"${fields}\"
        echo ${line}
    }

	if [ "X${jailnames}" = "XALL" ]; then
		reports=`ls -1 ${audit_path}`
	else
		for jailname in ${jailnames}; do
			_report=`ls -d1 ${audit_path}/${jailname}_*|sed s:${audit_path}/::g`
			reports="${reports} ${_report}"
		done
	fi

	for report in ${reports}; do

		if [ -f ${audit_path}/${report} ]; then

			jailname=${report%%_*}
			jid=${report##*_}

			for _port in `grep -E 'is vulnerable:$' ${audit_path}/${report} | awk -F'[ ()]' '{print $1"#"$3}' |sort | uniq`; do
				portname=${_port%\#*}
				category=${_port#*\#}
				PRINTLINE `echo ${format} |sed 's:\,: :g'` || PRINT_USAGE 1
			done
		fi

	done

 ;;
 mail)
    # bail out if no jailnames and mailaddrs are given
    [ -z "${jailnames}" -o -z "${mailaddrs}" ] && PRINT_USAGE 1

    rc=0
    tmpfile=${tmp_path}/_audit-${mailaddr}
    rm -f ${tmpfile}

    if [ "X${jailnames}" = "XALL" ]; then
        reports=`ls -1 ${audit_path}`
    else
	for jailname in ${jailnames}; do
		_report=`ls -d1 ${audit_path}/${jailname}_*|sed s:${audit_path}/::g`
		reports="${reports} ${_report}"
	done
    fi

    pcount=0

    for report in ${reports}; do

        if [ -f ${audit_path}/${report} ]; then
            if [ `grep -c '^0 problem(s) found.' ${audit_path}/${report}` = 0 ]; then
		tmpcount=`grep "problem(s) found." ${audit_path}/${report}|awk '{print $1}'`
                pcount=`expr "${pcount}" "+" "${tmpcount}"`

		jailname=${report%%_*}
		jid=${report##*_}

                echo "" >> ${tmpfile}
                echo "portaudit for jail: ${jailname} (JID: ${jid})" >> ${tmpfile}
                echo >> ${tmpfile}
                cat ${audit_path}/${report} >> ${tmpfile}
            fi
        else
            echo "" >> ${tmpfile}
            echo "error: \"${report}\" does not exist" >> ${tmpfile}
        fi

    done

    if [ -e ${tmpfile} ]; then
        rc=1
        if [ "X${mailaddrs}" = "X-" ]; then
            echo "portaudit for jails on ${hostname} - ${pcount} problem(s) found."
            cat ${tmpfile}
        else
            for mailaddr in ${mailaddrs}; do
                cat ${tmpfile} |mail -s "portaudit for jails on ${hostname} - ${pcount} problem(s) found." ${mailaddr}
            done
        fi
        rm -f ${tmpfile}
    fi
    exit ${rc}
 ;;
 *)
  PRINT_USAGE 1
 ;;
esac
