From 7099192e5fde38e607121e367d05af694ab4c332 Mon Sep 17 00:00:00 2001 From: Eygene Ryabinkin Date: Sun, 5 Oct 2008 22:27:53 +0400 Subject: [PATCH 4/4] Implement checking for a new package versions Flag '-n' tells portaudit to check if updated packages are available. It is currently done only for the 'all installed packages' mode, i.e. when '-a' flag is given. To do this, one additional utility, portaudit-checknew, is introduced. It downloads the ports INDEX file for the current FreeBSD version and checks if new versions of packages are available. This utility is spawned at the late stage when vulnerable packages are already determined and thus only they are checked. So the number of input items shouldn't be very large and portaudit-checknew is a plain shell script. Two new configuration variables were introduced: - portaudit_pkg_audit, specifies the location of pkg_audit binary; - portaudit_pkg_index, specifies the URL for the INDEX file; * can carry '%d' format specifier that will be substituted with the applicable FreeBSD version; * any URL scheme, recognizable by the fetch(1) utility, is supported. Signed-off-by: Eygene Ryabinkin --- ports-mgmt/portaudit/Makefile | 4 +- ports-mgmt/portaudit/files/portaudit-checknew.sh | 204 ++++++++++++++++++++++ ports-mgmt/portaudit/files/portaudit-cmd.sh | 90 ++++++++-- ports-mgmt/portaudit/files/portaudit.1 | 4 +- ports-mgmt/portaudit/files/portaudit.conf | 6 + ports-mgmt/portaudit/pkg-plist | 1 + 6 files changed, 292 insertions(+), 17 deletions(-) create mode 100755 ports-mgmt/portaudit/files/portaudit-checknew.sh 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 32c121d..a3b68d1 100755 --- a/ports-mgmt/portaudit/files/portaudit-cmd.sh +++ b/ports-mgmt/portaudit/files/portaudit-cmd.sh @@ -44,6 +44,7 @@ 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 @@ -128,18 +129,41 @@ portaudit_prerequisites() } # +# 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", where "pkgname" is the matched package name. +# "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}" | awk -F \| \ + "${portaudit_pkg_audit}" -o | awk -F \| \ -v fixedre="$fixedre" -v opt_restrict="$opt_restrict" \ ' opt_restrict && $3 !~ opt_restrict { next } @@ -173,7 +197,7 @@ $1 ~ /^[^{}*?]*[<=>!]/ { { cmd=pkg_info " -E \"" $1 "\"" while((cmd | getline pkg) > 0) { - printf("%s|%s\n", pkg, $0); + printf("%s|%s|\n", pkg, $0); } close(cmd) } @@ -185,16 +209,16 @@ audit_installed() local rc=0 local osversion=`sysctl -n kern.osreldate` - extract_auditfile | findvuln_installed | \ - awk -F\| "$PRINTAFFECTED_AWK"' + 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, \ + 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, ""); + print_affected($1, $2, $3, $4, $5, ""); vul++; } END { @@ -248,7 +272,7 @@ audit_file() if ($2 !~ /'"$opt_restrict"'/) continue vul++ - print_affected(pkg, $1, $2, $3, "") + print_affected(pkg, $1, $2, $3, "", "") } close(cmd) } @@ -286,7 +310,7 @@ audit_args() VULCNT=$(($VULCNT+1)) echo "$VLIST" | awk -F\| "$PRINTAFFECTED_AWK"' { print_affected("'"$1"'", - $1, $2, $3, "") } + $1, $2, $3, "", "") } ' fi ;; @@ -319,7 +343,7 @@ audit_cwd() { print } ' | $pkg_version -T "$PKGNAME" -`; then echo "$VLIST" | awk -F\| "$PRINTAFFECTED_AWK"' - { print_affected("'"$PKGNAME"'", $1, $2, $3, "") } + { print_affected("'"$PKGNAME"'", $1, $2, $3, "", "") } ' return 1 fi @@ -389,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;; @@ -406,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) @@ -417,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 @@ -465,15 +492,42 @@ 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, glob, refs, descr, note) { +function print_affected(apkg, glob, refs, descr, newver, note) { print apkg } ' elif $opt_verbose; then PRINTAFFECTED_AWK=' -function print_affected(apkg, glob, refs, descr, note) { +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 "." @@ -482,12 +536,15 @@ function print_affected(apkg, glob, refs, descr, note) { print "Reference: <" ref[r] ">" if (note) print "Note: " note + if (newver) { +'"$NEWVERS_AWK"' + } print "" } ' else PRINTAFFECTED_AWK=' -function print_affected(apkg, glob, refs, descr, note) { +function print_affected(apkg, glob, refs, descr, newver, note) { split(apkg, thepkg) print "Affected package: " thepkg[1] print "Type of problem: " descr "." @@ -496,6 +553,9 @@ function print_affected(apkg, glob, refs, descr, note) { print "Reference: <" ref[r] ">" if (note) print "Note: " note + if (newver) { +'"$NEWVERS_AWK"' + } print "" } ' 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 9ea2c4a..5882e97 100644 --- a/ports-mgmt/portaudit/files/portaudit.conf +++ b/ports-mgmt/portaudit/files/portaudit.conf @@ -21,3 +21,9 @@ # 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 -- 1.6.2.4