diff --git a/ports-mgmt/portaudit/Makefile b/ports-mgmt/portaudit/Makefile index 885dc27..6083235 100644 --- a/ports-mgmt/portaudit/Makefile +++ b/ports-mgmt/portaudit/Makefile @@ -37,7 +37,8 @@ SED_SCRIPT= -e 's|%%PREFIX%%|${PREFIX}|g' \ -e "s|%%BZIP2_CMD%%|${BZIP2_CMD}|g" \ do-build: -.for f in portaudit-cmd.sh portaudit.sh portaudit.1 portaudit.conf +.for f in portaudit-cmd.sh portaudit-checknew.sh portaudit.sh \ + portaudit.1 portaudit.conf @${SED} ${SED_SCRIPT} ${FILESDIR}/${f} >${WRKDIR}/${f} .endfor @@ -54,6 +55,7 @@ pre-install: do-install: @${INSTALL_SCRIPT} ${WRKDIR}/portaudit-cmd.sh ${PREFIX}/sbin/portaudit + @${INSTALL_SCRIPT} ${WRKDIR}/portaudit-checknew.sh ${PREFIX}/sbin/portaudit-checknew @${INSTALL_DATA} ${WRKDIR}/portaudit.conf ${PREFIX}/etc/portaudit.conf.sample @${INSTALL_MAN} ${WRKDIR}/portaudit.1 ${MAN1PREFIX}/man/man1 @${MKDIR} ${PERIODICDIR}/security diff --git a/ports-mgmt/portaudit/files/portaudit-checknew.sh b/ports-mgmt/portaudit/files/portaudit-checknew.sh new file mode 100755 index 0000000..395a8c3 --- /dev/null +++ b/ports-mgmt/portaudit/files/portaudit-checknew.sh @@ -0,0 +1,204 @@ +#!/bin/sh +# $FreeBSD$ +# +# Script to check if newer port versions then the ones installed +# in the system are available. Uses downloaded ports index from +# the master site. +# +# Utility acts as a filter: reads input strings, processes them +# and outputs the result to the standard output. Each input string +# is treated as the collection of '|'-separated fields. First field +# should be the package name with version, other fields can have +# any contents -- they won't be touched. But there is one exception: +# if '-o fieldnum' option is given, then the named field will be +# treated as the package origin. +# +# One more field can be added -- the newest port version, if it can +# be deduced. This field will be added to the end fieldset. +# +# For any error the utility will just output the unmodified input +# contents. +# +# Eygene Ryabinkin, rea-fbsd@codelabs.ru. September 2008. + +URL='http://www.freebsd.org/ports/INDEX-%d.bz2' +PKGBASE='/var/db/ports-mgmt/portaudit/pkg' + +# Obtains FreeBSD major version number from uname output. +freebsd_major () { + uname -r | cut -f1 -d- | cut -f1 -d. +} + +# Returns the OS version for the INDEX file. It is just a wrapper +# for the freebsd_major routine that applies the fixups for the +# known unsupported versions. +index_version () { + local major + + major=`freebsd_major` + case "$major" in + 1|2|3|4) + major=5 + ;; + esac + echo "$major" +} + +# Guesses (installed) port origin. +# Arguments: +# - port name with version specification; +# - name of file with the ports INDEX. +guess_origin () { + local contents origin + + if [ -z "$1" ]; then + echo "guess_origin(): called without arguments" >&2 + exit 255 + fi + if [ -z "$2" ]; then + echo "guess_origin(): called without second argument" >&2 + exit 255 + fi + + contents="$PKGBASE"/"$1"/"+CONTENTS" + if [ -s "$contents" ]; then + origin=`grep '^@comment ORIGIN:' "$contents" | \ + sed -e's/^@comment ORIGIN://'` + if [ -n "$origin" ]; then + echo "$origin" + return + fi + fi + # Not yet implemented: loop over INDEX file and try to get + # the origin from the matched ports description. +} + +# Fetches bzipped port index file and outputs it to the stdout +# Arguments: +# - URL to get file from. +# +# Uses 'opt_cachedir' (if it is not empty) as the name of the directory +# to place the downloaded file to. Download is made in the mirror mode, +# so if file's timestamps and size are checked and no download takes +# place if the remote file has the same characteristics. +fetch_index () { + if [ -z "$1" ]; then + echo "fetch_index(): called without arguments" >&2 + exit 255 + fi + + if [ -z "$opt_cachedir" -o ! -d "$opt_cachedir" ]; then + fetch -qpo - "$1" + return + fi + + local outfile="$opt_cachedir"/`basename "$1"` + fetch -mpo "$outfile" "$1" && cat "$outfile" +} + +TMPINDEX=`mktemp -q -t versionaudit-INDEX` +if [ -z "$TMPINDEX" ]; then + echo "Unable to create temporary file for ports index." >&2 + cat + exit 1 +fi + +trap "rm -f \"$TMPINDEX\"" 0 1 2 3 15 + +opt_cachedir= +opt_originfld= + +usage () { + echo "Usage: $0 [-c cachedir] [-u URL] [-o origin_fieldnum]" >&2 +} + +while getopts c:u:o: opt; do + case "$opt" in + c) + opt_cachedir="$OPTARG" + ;; + u) + URL="$OPTARG" + ;; + o) + if [ -n "`echo "$OPTARG" | tr -d [[:digit:]]`" ]; then + usage + fi + opt_originfld="$(($OPTARG - 1))" + ;; + ?) + usage + exit 2 + ;; + esac +done + +if ! [ -w "$opt_cachedir" ]; then + cat << EOF >&2 +------------------------------------------------------------------------ +'$opt_cachedir' is not writable to you! +Will not cache INDEX for reusing. +------------------------------------------------------------------------ +EOF +fi + +iver=`index_version` +# Our URL can contain no format arguments, so we should shut up +# printf to disable messages like 'no format argument'. +url="`printf "$URL" "$iver" 2>/dev/null`" +# We need only first and second fields, so we're stripping out +# other fields to make resulting INDEX to be shorter -- this will +# slightly speed-up grepping. +fetch_index "$url" | case X"$url" in +*.bz2) + bunzip2 -c 2>/dev/null + ;; +*.gz) + gunzip -c 2>/dev/null + ;; +*) + cat + ;; +esac | cut -f 1-2 -d '|' > "$TMPINDEX" +if ! [ -s "$TMPINDEX" ]; then + echo "Unable to download port index from '$url'" >&2 + cat + exit 1 +fi + +IFS='|' +while read portspec rest; do + portname=`echo "$portspec" | sed -e's/-[^-]*$//'` + if [ -z "$opt_originfld" ]; then + origin=`guess_origin "$portspec" "$TMPINDEX"` + else + origin="`echo "$rest" | cut -d '|' -f "$opt_originfld"`" + fi + if [ -z "$origin" ]; then + echo "Unable to get port origin for '$portspec'." >&2 + continue + fi + # The while cycle is hackish: we make "$?" non-zero if no + # matches were found and it is set to zero if match was + # found. All exit paths from the cycle should set "$?" + # properly. + # + # Another way to proceed is to make subroutine that will + # either print something or not, because we can't pass + # variables outside the while loop -- it is done in the + # separate process. + grep "^$portname-" "$TMPINDEX" | while read nportspec ndir nrest + do + nportname=`echo "$nportspec" | sed -e's/-[^-]*$//'` + norigin=`echo "$ndir" | sed -e's|^/[^/]*/[^/]*/||'` + if [ "$nportname" = "$portname" -a \ + "$norigin" = "$origin" ]; then + echo "$portspec|$rest|$nportspec" + true + break + fi + false + done || echo "$portspec|$rest" +done + +exit 0 diff --git a/ports-mgmt/portaudit/files/portaudit-cmd.sh b/ports-mgmt/portaudit/files/portaudit-cmd.sh index c0eb67b..a3b68d1 100755 --- a/ports-mgmt/portaudit/files/portaudit-cmd.sh +++ b/ports-mgmt/portaudit/files/portaudit-cmd.sh @@ -43,6 +43,9 @@ portaudit_confs() : ${portaudit_fixed=""} + : ${portaudit_pkg_audit="/usr/sbin/pkg_audit"} + : ${portaudit_pkg_index="http://www.freebsd.org/ports/INDEX-%d.bz2"} + if [ -r %%PREFIX%%/etc/portaudit.conf ]; then . %%PREFIX%%/etc/portaudit.conf fi @@ -125,49 +128,111 @@ portaudit_prerequisites() return 0 } +# +# We expect the input with 5 fields: pkg-name, pkg-glob, references, +# description and origin. +# +# If we're searching for the new port versions, we will output 5 +# fields: pkg-name, pkg-glob, references, description, newest-version. +# +# If we're not searching for the new port versions, there will be +# only 4 fields: pkg-name, pkg-glob, references and description. +# +checknew() +{ + if [ -n "$opt_checknew" ]; then + portaudit-checknew -c "${portaudit_dir}" -o 5 \ + -u "${portaudit_pkg_index}" | \ + cut -d '|' -f1-4,6 + else + cut -d '|' -f1-4 + fi +} + +# +# Helper for audit_installed that actually finds vulnerable packages. +# +# It processes the auditfile entries (that are read from the stdin) +# in the form "glob|refs|desc" and outputs entries in the form +# "pkgname|glob|refs|desc|origin", where "pkgname" is the matched +# package name and "origin" is the package origin. The latter can be +# empty. +# +findvuln_installed() +{ + local fixedre=`echo -n $portaudit_fixed | tr -c '[:alnum:]- \t\n' 'x' | tr -s ' \t\n' '|'` + + if [ -x "${portaudit_pkg_audit}" ]; then + "${portaudit_pkg_audit}" -o | awk -F \| \ + -v fixedre="$fixedre" -v opt_restrict="$opt_restrict" \ + ' +opt_restrict && $3 !~ opt_restrict { next } +$2 ~ /^FreeBSD[<=>!]/ && fixedre && $3 ~ fixedre { next } +{ print } +' + + return + fi + + local installedre=`$pkg_info -aE | sed -e 's/-[^-]*$//g' | paste -s -d '|' -` + local osversion=`sysctl -n kern.osreldate` + + awk -F\| \ + -v fixedre="$fixedre" -v installedre="$installedre" \ + -v pkg_version="$pkg_version" -v pkg_info="$pkg_info" \ + -v osversion="$osversion" -v opt_restrict="$opt_restrict" \ + ' +/^(#|\$)/ { next } +opt_restrict && $2 !~ opt_restrict { next } +$1 ~ /^FreeBSD[<=>!]/ { + if (fixedre && $2 ~ fixedre) next + if (!system(pkg_version " -T \"FreeBSD-" osversion "\" \"" $1 "\"")) { + printf("FreeBSD-%s|%s\n", osversion, $0); + } + next +} +$1 ~ /^[^{}*?]*[<=>!]/ { + if ($1 !~ "^(" installedre ")[<=>!]") next; +} +{ + cmd=pkg_info " -E \"" $1 "\"" + while((cmd | getline pkg) > 0) { + printf("%s|%s|\n", pkg, $0); + } + close(cmd) +} +' +} + audit_installed() { local rc=0 local osversion=`sysctl -n kern.osreldate` - fixedre=`echo -n $portaudit_fixed | tr -c '[:alnum:]- \t\n' 'x' | tr -s ' \t\n' '|'` - installedre=`$pkg_info -aE | sed -e 's/-[^-]*$//g' | paste -s -d '|' -` - - extract_auditfile | awk -F\| "$PRINTAFFECTED_AWK"' - BEGIN { vul=0; fixedre="'"$fixedre"'" } - /^(#|\$)/ { next } - $2 !~ /'"$opt_restrict"'/ { next } - $1 ~ /^FreeBSD[<=>!]/ { - if (fixedre && $2 ~ fixedre) next - if (!system("'"$pkg_version"' -T \"FreeBSD-'"$osversion"'\" \"" $1 "\"")) { - print_affected("FreeBSD-'"$osversion"'", \ - "To disable this check add the uuid to \`portaudit_fixed'"'"' in %%PREFIX%%/etc/portaudit.conf") - } - next - } - $1 ~ /^[^{}*?]*[<=>!]/ { - if ($1 !~ "^('"$installedre"')[<=>!]") next; - } - { - cmd="'"$pkg_info"' -E \"" $1 "\"" - while((cmd | getline pkg) > 0) { - vul++ - print_affected(pkg, "") - } - close(cmd) - } - END { - if ("'$opt_quiet'" == "false") { - print vul " problem(s) in your installed packages found." - } - if (vul > 0) { - if ("'$opt_quiet'" == "false") { - print "\nYou are advised to update or deinstall" \ - " the affected package(s) immediately." - } - exit(1) - } + extract_auditfile | findvuln_installed | checknew | \ + awk -F\| -v pkg_version="$pkg_version" "$PRINTAFFECTED_AWK"' +BEGIN { vul=0; } +$1 ~ /^FreeBSD-/ { + print_affected($1, $2, $3, $4, "", \ + "To disable this check add the uuid to \`portaudit_fixed'"'"' in %%PREFIX%%/etc/portaudit.conf") + next +} +{ + print_affected($1, $2, $3, $4, $5, ""); + vul++; +} +END { + if ("'$opt_quiet'" == "false") { + print vul " problem(s) in your installed packages found." + } + if (vul > 0) { + if ("'$opt_quiet'" == "false") { + print "\nYou are advised to update or deinstall" \ + " the affected package(s) immediately." } + exit(1) + } +} ' || rc=$? return $rc @@ -207,7 +272,7 @@ audit_file() if ($2 !~ /'"$opt_restrict"'/) continue vul++ - print_affected(pkg, "") + print_affected(pkg, $1, $2, $3, "", "") } close(cmd) } @@ -244,7 +309,8 @@ audit_args() ' | $pkg_version -T "$1" -`; then VULCNT=$(($VULCNT+1)) echo "$VLIST" | awk -F\| "$PRINTAFFECTED_AWK"' - { print_affected("'"$1"'", "") } + { print_affected("'"$1"'", + $1, $2, $3, "", "") } ' fi ;; @@ -277,7 +343,7 @@ audit_cwd() { print } ' | $pkg_version -T "$PKGNAME" -`; then echo "$VLIST" | awk -F\| "$PRINTAFFECTED_AWK"' - { print_affected("'"$PKGNAME"'", "") } + { print_affected("'"$PKGNAME"'", $1, $2, $3, "", "") } ' return 1 fi @@ -347,12 +413,13 @@ opt_restrict= opt_verbose=false opt_version=false opt_expiry= +opt_checknew= if [ $# -eq 0 ] ; then opt_audit=true fi -while getopts aCdf:Fqr:vVX: opt; do +while getopts aCdf:Fnqr:vVX: opt; do case "$opt" in a) opt_audit=true;; @@ -364,6 +431,8 @@ while getopts aCdf:Fqr:vVX: opt; do opt_file="$OPTARG";; F) opt_fetch=true;; + n) + opt_checknew=true;; q) opt_quiet=true;; r) @@ -375,7 +444,7 @@ while getopts aCdf:Fqr:vVX: opt; do X) opt_expiry="$OPTARG";; ?) - echo "Usage: $0 -aCdFVvq [-X days] [-r pattern] [-f file] [pkg-name ...]" + echo "Usage: $0 -aCdFnVvq [-X days] [-r pattern] [-f file] [pkg-name ...]" exit 2;; esac done @@ -423,40 +492,73 @@ fi prerequisites_checked=false +# This awk code demands 'pkg_version' variable to be set +# if 'newver' variable is non-empty. +NEWVERS_AWK=' + +nparts=split(thepkg[1], verarr, /-/) +gtglob=verarr[1] +for (i=2; i" verarr[i] + +updated="" +avail=0 +if (system(pkg_version " -T \"" newver "\" '\''" gtglob "'\''") == 0) { + updated="available, " newver + avail=1 +} else { + updated="not available" +} +if (avail != 0) { + if (system(pkg_version " -T \"" newver "\" '\''" glob "'\''") == 0) + updated=updated ", still vulnerable" + else + updated=updated ", not vulnerable" +} +print "Updated package: " updated +' + if $opt_quiet; then PRINTAFFECTED_AWK=' - function print_affected(apkg, note) { - print apkg - } - ' +function print_affected(apkg, glob, refs, descr, newver, note) { + print apkg +} +' elif $opt_verbose; then PRINTAFFECTED_AWK=' - function print_affected(apkg, note) { - split(apkg, thepkg) - print "Affected package: " thepkg[1] " (matched by " $1 ")" - print "Type of problem: " $3 "." - split($2, ref, / /) - for (r in ref) - print "Reference: <" ref[r] ">" - if (note) - print "Note: " note - print "" - } - ' +function print_affected(apkg, glob, refs, descr, newver, note) { + split(apkg, thepkg) + print "Affected package: " thepkg[1] " (matched by " glob ")" + print "Type of problem: " descr "." + split(refs, ref, / /) + for (r in ref) + print "Reference: <" ref[r] ">" + if (note) + print "Note: " note + if (newver) { +'"$NEWVERS_AWK"' + } + print "" +} +' else PRINTAFFECTED_AWK=' - function print_affected(apkg, note) { - split(apkg, thepkg) - print "Affected package: " thepkg[1] - print "Type of problem: " $3 "." - split($2, ref, / /) - for (r in ref) - print "Reference: <" ref[r] ">" - if (note) - print "Note: " note - print "" - } - ' +function print_affected(apkg, glob, refs, descr, newver, note) { + split(apkg, thepkg) + print "Affected package: " thepkg[1] + print "Type of problem: " descr "." + split(refs, ref, / /) + for (r in ref) + print "Reference: <" ref[r] ">" + if (note) + print "Note: " note + if (newver) { +'"$NEWVERS_AWK"' + } + print "" +} +' fi if $opt_audit; then diff --git a/ports-mgmt/portaudit/files/portaudit.1 b/ports-mgmt/portaudit/files/portaudit.1 index c982b41..5cb0ec2 100644 --- a/ports-mgmt/portaudit/files/portaudit.1 +++ b/ports-mgmt/portaudit/files/portaudit.1 @@ -42,7 +42,7 @@ .Sh SYNOPSIS . .Nm -.Op Fl aCdFqvV +.Op Fl aCdFnqvV .Op Fl X Ar days .Op Fl f Ar file .Op Fl r Ar eregex @@ -85,6 +85,8 @@ Print the creation date of the database. Fetch the current database from the .Fx servers. +.It Fl n +Check if new versions of vulnerable ports are present. .It Fl q Quiet mode. .It Fl V diff --git a/ports-mgmt/portaudit/files/portaudit.conf b/ports-mgmt/portaudit/files/portaudit.conf index 4eb08d7..5882e97 100644 --- a/ports-mgmt/portaudit/files/portaudit.conf +++ b/ports-mgmt/portaudit/files/portaudit.conf @@ -17,3 +17,13 @@ # this vulnerability has been fixed in your FreeBSD version #portaudit_fixed="d2102505-f03d-11d8-81b0-000347a4fa7d" + +# this command will be used to find the vulnerable packages +# instead of awk(1) script. +# portaudit_pkg_audit="/usr/sbin/pkg_audit" + +# this will override the default location of the package +# INDEX file. One can place '%d' modifier inside this option -- +# it will be substituted with the FreeBSD release name (or the +# lowest FreeBSD version for what INDEX files are still built). +# portaudit_pkg_index="http://www.freebsd.org/ports/INDEX-%d.bz2" diff --git a/ports-mgmt/portaudit/pkg-plist b/ports-mgmt/portaudit/pkg-plist index 8edf7bb..3c9f775 100644 --- a/ports-mgmt/portaudit/pkg-plist +++ b/ports-mgmt/portaudit/pkg-plist @@ -1,4 +1,5 @@ sbin/portaudit +sbin/portaudit-checknew etc/portaudit.conf.sample %%PERIODICDIR%%/security/410.portaudit @dirrmtry %%PERIODICDIR%%/security