diff --git a/BUGS b/BUGS
new file mode 100644
index 0000000..7c29a9c
--- /dev/null
+++ b/BUGS
@@ -0,0 +1,25 @@
+Known bugs:
+-----------
+
+Patchlevels are not dealt with correctly. That is, if, for example, the
+package listed in the vulnerabilities file is marked as "foo-1.2pl3" and a
+package with a tiny version such as "foo-1.2.1" is installed, it may falsely
+match. That is, comparison of "foo-1.2pl3" and "foo-1.2.1" claims that the
+patchlevel version is higher. (The converse scenario also holds.)
+
+This is a restriction of the used distutils.versions.LooseVersion
+implementation. Presumably, the assumption is that a piece of software
+wouldn't mix patchlevels with tiny versions (?). Note that the expensive
+shell-out to parse_version(1) wouldn't solve this problem either: that program
+operates on the same assumption.
+
+----
+
+Deeply nested brace expansions are not correctly dealt with. The
+braceExpansion function is able to handle simply nested expansions such as
+"foo-{,bar{-baz,-bla}}", but deeper levels of nesting may not yield the
+expected results.
+
+For the purposes of the vulnerability list, this seems acceptable for the time
+being, as deeply nested version strings are not found. An alternative (albeit
+very expensive) would be to shell out to zsh to do brace expansion.
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..eec3aca
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,34 @@
+Software Copyright License Agreement (BSD License)
+
+Copyright (c) 2010, Yahoo! Inc.
+All rights reserved.
+
+Redistribution and use of this software in source and binary forms, with
+or without modification, are permitted provided that the following
+conditions are met:
+
+* Redistributions of source code must retain the above
+ copyright notice, this list of conditions and the
+ following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the
+ following disclaimer in the documentation and/or other
+ materials provided with the distribution.
+
+* Neither the name of Yahoo! Inc. nor the names of its
+ contributors may be used to endorse or promote products
+ derived from this software without specific prior
+ written permission of Yahoo! Inc.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..fb6c07a
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,59 @@
+# Copyright (c) 2008,2010 Yahoo! Inc.
+#
+# This example Makefile can be used to maintain vulnerability list.
+# See 'make help' for more information.
+
+# Location to which to upload the vlists.
+LOCATION=":~/public_html/yvc/"
+FBVLIST=fbvlist
+RH4VLIST=rh4vlist
+RH5VLIST=rh5vlist
+LISTS= ${RH5VLIST} ${RH4VLIST} ${FBVLIST}
+
+GONERS= ${RH5VLIST}.in ${RH4VLIST}.in ${FBVLIST}.in \
+ com.redhat.rhsa-all.xml.bz2
+
+date!=date
+
+all: sign upload
+
+help:
+ @echo "The following targets are available:"
+ @echo "all sign + upload"
+ @echo "clean remove any interim files"
+ @echo "help print this help"
+ @echo "sign sign the vulnerability list"
+ @echo "upload upload the vulnerability list"
+
+sign: ${LISTS}
+
+${FBVLIST}: ${FBVLIST}.in
+ gpg -o ${FBVLIST} --clearsign ${FBVLIST}.in
+ chmod a+r ${FBVLIST}
+
+${FBVLIST}.in:
+ @echo "# Generated on ${date}" > ${FBVLIST}.in
+ perl ./misc/harvest_freebsd_yvc.pl >> ${FBVLIST}.in
+
+
+${RH4VLIST}: ${RH4VLIST}.in
+ gpg -o ${RH4VLIST} --clearsign ${RH4VLIST}.in
+ chmod a+r ${RH4VLIST}
+
+${RH4VLIST}.in:
+ python ./misc/redhat_oval_to_yvc.py 4 > ${RH4VLIST}.in
+
+
+${RH5VLIST}: ${RH5VLIST}.in
+ gpg -o ${RH5VLIST} --clearsign ${RH5VLIST}.in
+ chmod a+r ${RH5VLIST}
+
+${RH5VLIST}.in:
+ python ./misc/redhat_oval_to_yvc.py 5 > ${RH5VLIST}.in
+
+
+upload: sign
+ scp ${LISTS} ${LOCATION}
+
+clean:
+ rm -f ${LISTS} ${GONERS}
diff --git a/README b/README
index 24a38c6..391a96c 100644
--- a/README
+++ b/README
@@ -1,5 +1,3 @@
-This is a placeholder README for the 'yvc' utility.
-
'yvc' is a software package vulnerability checker.
'yvc' compares the given package name against the list of known
@@ -9,9 +7,9 @@ further information for each vulnerable package.
'yvc' was conceptually based on NetBSD's audit-packages program (see
http://www.netbsd.org/support/security/#check-pkgsrc) and was written by
-Jan Schaumann at Yahoo! Inc.
+Jan Schaumann in 2008 while working at Yahoo!
+Inc. Yahoo! open sourced this tool in the hopes that it will be useful to
+other people -- unless otherwise noted, all files are released under the
+terms of a 3-clause BSD license as noted in the file LICENSE.
The 'y' in yvc can stand for a number of things. Make up your own.
-
-'yvc' will be made available to the world at large in the very exciting
-and very near future.
diff --git a/TODO b/TODO
new file mode 100644
index 0000000..99ed10a
--- /dev/null
+++ b/TODO
@@ -0,0 +1,9 @@
+
+package for public:
+ - identify required packages
+ - write configure script to handle fetch-vlist:
+ - determine appropriate vlists to use
+ - provide option for place to upload/download
+ - write python magic to install correctly
+
+review helper scripts to ensure they work (efficiently) on all platforms
diff --git a/bin/fetch-vlist.sh b/bin/fetch-vlist.sh
new file mode 100755
index 0000000..7b1d088
--- /dev/null
+++ b/bin/fetch-vlist.sh
@@ -0,0 +1,205 @@
+#! /bin/sh
+#
+# Copyright (c) 2008,2009,2010 Yahoo! Inc.
+#
+# Originally written by Jan Schaumann in July 2008.
+#
+# The fetch-vlist tool is used to download the vulnerability lists to be
+# used by the 'yvc' tool. After downloading them, it will verify the PGP
+# signature and, if it checks out, install the files in the final
+# destination.
+
+# Only used during development:
+# set -eu
+
+###
+### Globals
+###
+
+DONT=""
+EXIT_VALUE=0
+GPG="gpg"
+GPG_FLAGS="--verify -q"
+GPG_REDIR="2>/dev/null"
+IGNORE_PGP_ERRS=0
+PROGNAME="${0##*/}"
+TMPFILES=""
+
+##
+## Modify this section to specify where to fetch your vlists from.
+##
+NLISTS=4
+VLIST1="http://ftp.netbsd.org/pub/NetBSD/packages/vulns/pkg-vulnerabilities"
+VLIST1_LOCATION="/usr/local/var/var/yvc/nbvlist"
+VLIST2="http:///yvc/fbvlist"
+VLIST2_LOCATION="/usr/local/var/yvc/fbvlist"
+VLIST3="http:///yvc/rh4vlist"
+VLIST3_LOCATION="/usr/local/var/yvc/rh4vlist"
+VLIST4="http:///yvc/rh5vlist"
+VLIST4_LOCATION="/usr/local/var/yvc/rh5vlist"
+
+WGET="wget"
+WGET_FLAGS="-t 1 -T 10 -q"
+
+###
+### Functions
+###
+
+# function : cleanup
+# purose : exit handler to remove any temporarily created files
+
+cleanup() {
+ rm -f ${TMPFILES}
+}
+
+# function : error
+# purpose : print message to stderr and exit 1
+# input : any string
+# output : input is echo'd to stderr, program aborted
+
+error() {
+ warn ${1}
+ exit 1
+}
+
+# function : warn
+# purpose : print message to stderr
+# input : any string
+# output : input is echo'd to stderr
+# sets EXIT_VALUE to 1 to indicate failure
+
+warn() {
+ echo "${PROGNAME}: ${1}" >&2
+ EXIT_VALUE=1
+}
+
+# function : fetchVerifyInstall
+# purpose : fetch, verify and install all vlists
+# input : none
+# result : all files are fetched, verified and installed into their
+# final location; any errors encountered are caught and an
+# appropriate error message printed
+
+fetchVerifyInstall() {
+ local n
+
+ n=1
+ while [ $n -le ${NLISTS} ]; do
+ local tmpfile=$(mktemp /tmp/${PROGNAME}.XXXXXX)
+ local list=$(eval echo \$VLIST${n})
+ local target=$(eval echo \$VLIST${n}_LOCATION)
+
+ TMPFILES="${TMPFILES} ${tmpfile}"
+ n=$(( $n + 1 ))
+
+ fetchList ${tmpfile} ${list} || {
+ warn "Unable to fetch ${list}."
+ continue
+ }
+
+ verifySignature ${tmpfile} || {
+ if [ ${IGNORE_PGP_ERRS} -ne 1 ]; then
+ warn "Unable to verify signature of ${list}."
+ continue
+ fi
+ }
+
+ installFile ${tmpfile} ${target} || {
+ warn "Unable to install ${tmpfile} as ${target}."
+ continue
+ }
+ done
+}
+
+# function : fetchList
+# purpose : download the list from the given URL into a temporary
+# location
+# input : temporary file, list URL
+# returns : exit value of wget command
+
+fetchList() {
+ local tmpfile=${1}
+ local url=${2}
+
+ ${DONT} ${WGET} -O ${tmpfile} ${WGET_FLAGS} ${url}
+}
+
+# function : installFile
+# purpose : install the temporary file into the final destination if
+# needed
+# input : temporary file, final location
+
+installFile() {
+ local tmpfile=${1}
+ local final=${2}
+
+ ${DONT} cmp -s ${tmpfile} ${final} || {
+ ${DONT} mv ${tmpfile} ${final} && \
+ ${DONT} chmod 444 ${final}
+ }
+}
+
+# function : usage
+# purpose : print a usage summary
+# returns : nothing, usage printed to stdout
+
+usage() {
+ echo "Usage: ${PROGNAME} [-dhiv]"
+ echo " -d don't do anything, just report what would be done"
+ echo " -h print this help and exit"
+ echo " -i ignore any pgp errors"
+ echo " -v be verbose"
+}
+
+# function : verifySignature
+# purpose : verify the pgp signature on the given file
+# input : filename
+# returns : retval of gpg command
+
+verifySignature() {
+ local file=${1}
+ ${DONT} eval ${GPG} ${GPG_FLAGS} ${file} ${GPG_REDIR}
+}
+
+###
+### Main
+###
+
+trap cleanup 0
+
+while getopts 'dhiv' opt; do
+ case ${opt} in
+ d)
+ DONT="echo"
+ ;;
+ h|\?)
+ usage
+ exit 0
+ # NOTREACHED
+ ;;
+ i)
+ IGNORE_PGP_ERRS=1
+ ;;
+ v)
+ WGET_FLAGS="-v"
+ GPG_FLAGS="${GPG_FLAGS} -v"
+ GPG_REDIR=""
+ ;;
+ *)
+ usage
+ exit 1
+ # NOTREACHED
+ ;;
+ esac
+done
+shift $(( ${OPTIND} - 1 ))
+
+if [ $# -ne 0 ]; then
+ usage
+ exit 1
+ # NOTREACHED
+fi
+
+fetchVerifyInstall
+
+exit ${EXIT_VALUE}
diff --git a/bin/run-yvc.py b/bin/run-yvc.py
new file mode 100755
index 0000000..9ea91cb
--- /dev/null
+++ b/bin/run-yvc.py
@@ -0,0 +1,18 @@
+#! /usr/local/bin/python2.5
+#
+# Copyright (c) 2008,2010 Yahoo! Inc.
+#
+# Originally written by Jan Schaumann in July 2008.
+#
+# The entire functionality of the yvc(1) tool is found in the
+# yahoo.yvc.Checker class. This script just invokes the 'main' function
+# provided by yahoo.yvc.
+
+###
+### Main
+###
+
+if __name__ == "__main__":
+ import sys
+ from yahoo.yvc import main
+ main(sys.argv[1:])
diff --git a/conf/yvc.conf b/conf/yvc.conf
new file mode 100644
index 0000000..5eb21e4
--- /dev/null
+++ b/conf/yvc.conf
@@ -0,0 +1,19 @@
+# This is the default configuration file for yvc(1). See yvc.conf(5) for
+# details.
+
+# This section is required, don't remove it.
+[YVC]
+
+# A list of vulnerability types that should be ignored.
+# See yvc(1) for the exhaustive list of possible vulnerability types.
+# For example:
+# IGNORE_TYPES = denial-of-service, permissions-race
+
+# A list of URLs that should be ignored. For example:
+# IGNORE_URLS = http://online.securityfocus.com/archive/1/272180
+
+# The files in which the list of vulnerabilities are found.
+VLISTS = /usr/local/var/yvc/fbvlist
+
+# Level of verbosity.
+#VERBOSITY = 1
diff --git a/doc/ContributionLicenseAgreementYahoo.pdf b/doc/ContributionLicenseAgreementYahoo.pdf
new file mode 100644
index 0000000..66b445b
Binary files /dev/null and b/doc/ContributionLicenseAgreementYahoo.pdf differ
diff --git a/doc/html/Makefile b/doc/html/Makefile
new file mode 100644
index 0000000..232be08
--- /dev/null
+++ b/doc/html/Makefile
@@ -0,0 +1,18 @@
+# A Makefile to create html docs from the man pages.
+
+MANPAGES=../man/fetch-vlist.1 ../man/yvc.1 ../man/yvc.conf.5
+
+all: html
+
+html: ${MANPAGES}
+.for m in ${MANPAGES}
+ nroff -man ${m} | rman -f html > ${m:S/..\/man\///}.html
+.endfor
+
+clean:
+.for m in ${MANPAGES}
+ @rm -f ${m:S/..\/man\///}.html
+.endfor
+
+upload:
+ scp *.html :~/public_html/yvc/
diff --git a/doc/html/index.html b/doc/html/index.html
new file mode 100644
index 0000000..9b29974
--- /dev/null
+++ b/doc/html/index.html
@@ -0,0 +1,77 @@
+
+
+ yvc -- a software package vulnerability check
+
+
+ yvc -- a software package vulnerability check
+
+ yvc compares the given package name against the list of
+ known vulnerabilities and reports any security issues. This output
+ contains the name and version of the package, the type of vulnerability,
+ and a URL for further information for each vulnerable package.
+
+
+ yvc was conceptually based on NetBSD's
+ audit-packages program and was written by Jan Schaumann in 2008 while
+ working at Yahoo! Inc. Yahoo! open sourced the tool in the hopes that
+ it will be useful to other people -- unless otherwise noted, all files
+ are released under the terms of a 3-clause BSD license as noted in the
+ file LICENSE.
+
+
+ The 'y' in yvc can stand for a number of things. Make up your own.
+
+ The sources to yvc can be found at GitHub:
+ http://github.com/jschauma/yvc.
+
+ Vulnerability lists
+
+ The following lists of known vulnerabilities are available:
+
+
+ Common usage
+
+ It is recommended for users of this package to run the fetch-vlist periodically from cron.
+ It is also recommended to run the yvc command regularly
+ from cron.
+ An example crontab would look like this:
+
+0 3 * * * /usr/local/bin/fetch-vlist
+0 4 * * * rpm -qa | /usr/local/bin/yvc
+
+
+
+ Of course you can also invoke yvc manually. See the examples in the manual page for details.
+
+ Manual pages
+
+
+
+
diff --git a/doc/man/fetch-vlist.1 b/doc/man/fetch-vlist.1
new file mode 100644
index 0000000..9e1a999
--- /dev/null
+++ b/doc/man/fetch-vlist.1
@@ -0,0 +1,80 @@
+.\" Copyright (c) 2008,2009,2010 Yahoo! Inc.
+.\"
+.Dd September 30, 2010
+.Dt FETCH-VLIST 1
+.Os
+.Sh NAME
+.Nm fetch-vlist
+.Nd fetch and install vulnerbility lists
+.Sh SYNOPSIS
+.Nm
+.Op Fl dhiv
+.Sh DESCRIPTION
+The
+.Nm
+tool downloads and installs the vulnerability lists used by
+.Xr yvc 1 .
+Each list is expected to be PGP signed;
+.Nm
+will verify the signature after donwloading the file.
+.Pp
+Originally, the lists are fetched into a temporary location.
+If a fetched file is different from the currently used version, then it is
+installed.
+.Sh OPTIONS
+The following options are supported by
+.Nm :
+.Bl -tag -width _h
+.It Fl d
+Don't do anything, just report what would be done.
+.It Fl h
+Print a short usage statement and exit.
+.It Fl i
+Ignore any errors due to the PGP signature.
+Errors may include the inability to verify the signature because the
+public key is not in the used keyring or an actual signature mismatch.
+.It Fl v
+Be verbose.
+.El
+.Sh LISTS
+The following lists may be downloaded and installed by
+.Nm :
+.Bl -tag -width nbvlist_
+.It fbvlist
+A list of vulnerabilities known in the FreeBSD ports collection, derived
+from http://www.freebsd.org/ports/portaudit/ and fetched from
+http:///yvc/fbvlist.
+.It nbvlist
+A list of vulnerabilities provided by the NetBSD Project.
+See http://www.netbsd.org/support/security/#check-pkgsrc for details.
+Retrieved from
+http://ftp.netbsd.org/pub/NetBSD/packages/vulns/pkg-vulnerabilities.
+.It rh4vlist
+A list of vulnerabilities known in RHEL4, derived from
+http://www.redhat.com/security/data/oval/com.redhat.rhsa-all.xml.bz2 and
+fetched from
+http:///yvc/rh4vlist.
+.It rh5vlist
+A list of vulnerabilities known in RHEL5, derived from
+http://www.redhat.com/security/data/oval/com.redhat.rhsa-all.xml.bz2 and
+fetched from
+http:///yvc/rh5vlist.
+.El
+.Sh EXIT STATUS
+.Ex -std
+.Sh FILES
+.Bl -tag -width _home_y_var_yvc_
+.It /home/y/var/yvc
+The final directory into which the file is installed.
+.El
+.Sh SEE ALSO
+.Xr yvc 1
+.Sh HISTORY
+.Nm
+was conceptually based on NetBSD's "download-vulnerability-list" command.
+It was originally written by
+.An Jan Schaumann
+.Aq jschauma@yahoo-inc.com
+in July 2008.
+.Sh BUGS
+Please report bugs and feature requests to the author.
diff --git a/doc/man/yvc.1 b/doc/man/yvc.1
new file mode 100644
index 0000000..5345498
--- /dev/null
+++ b/doc/man/yvc.1
@@ -0,0 +1,204 @@
+.\" Copyright (c) 2008,2009,2010 Yahoo! Inc.
+.\"
+.Dd September 30, 2010
+.Dt YVC 1
+.Os
+.Sh NAME
+.Nm yvc
+.Nd a software package vulnerability checker
+.Sh SYNOPSIS
+.Nm
+.Op Fl hv
+.Op Fl c Ar file
+.Op Fl l Ar file
+.Op Ar pkg Oo Ar ... Oc
+.Sh DESCRIPTION
+The
+.Nm
+tool compares the given package name against the list of known
+vulnerabilities and reports any security issues.
+This output contains the name and version of the package, the type of
+vulnerability, and a URL for further information for each vulnerable package.
+.Sh OPTIONS
+The following options are supported by
+.Nm :
+.Bl -tag -width l_file_
+.It Fl c Ar file
+Read configuration from
+.Ar file
+(default: /usr/local/etc/yvc.conf).
+.It Fl h
+Print a short usage statement and exit.
+.It Fl l Ar file
+Check against the list of vulnerabilities provided in
+.Ar file .
+Can be used multiple times.
+.It Fl v
+Be verbose.
+Can be used multiple times.
+.El
+.Sh INPUT
+.Nm
+takes as input a list of package names.
+If no package names are given on the command-line,
+.Nm
+will read them from stdin.
+.Pp
+Input from stdin and from the command-line can be combined: if
+.Nm
+encounters "-" as an argument, it will read from stdin at that point.
+.Sh DETAILS
+.Nm
+will then try to match each package against a list of known
+vulnerabilities.
+.Pp
+The list of known vulnerabilities is taken from a text file.
+In this file, each line lists the package and vulnerable versions, the type of
+exploit, and an Internet address for further information:
+.Bl -item
+.It
+.Aq package pattern
+.Aq type
+.Aq url
+.El
+.Pp
+The type of exploit can be any text, although
+some common types of exploits listed are:
+.Bl -bullet -compact -offset indent
+.It
+cross-site-html
+.It
+cross-site-scripting
+.It
+denial-of-service
+.It
+file-permissions
+.It
+local-access
+.It
+local-code-execution
+.It
+local-file-read
+.It
+local-file-removal
+.It
+local-file-write
+.It
+local-root-file-view
+.It
+local-root-shell
+.It
+local-symlink-race
+.It
+local-user-file-view
+.It
+local-user-shell
+.It
+privacy-leak
+.It
+remote-code-execution
+.It
+remote-command-inject
+.It
+remote-file-creation
+.It
+remote-file-read
+.It
+remote-file-view
+.It
+remote-file-write
+.It
+remote-key-theft
+.It
+remote-root-access
+.It
+remote-root-shell
+.It
+remote-script-inject
+.It
+remote-server-admin
+.It
+remote-use-of-secret
+.It
+remote-user-access
+.It
+remote-user-file-view
+.It
+remote-user-shell
+.It
+unknown
+.It
+weak-authentication
+.It
+weak-encryption
+.It
+weak-ssl-authentication
+.El
+.Pp
+The list of vulnerabilities is stored per default in two files under
+/usr/local/var/yvc/.
+.Sh CONFIGURATION
+At startup,
+.Nm
+reads the system-wide configuration file /usr/local/etc/yvc.conf.
+See
+.Xr yvc.conf 5
+for details.
+.Sh EXAMPLES
+.Nm
+can be run via
+.Xr cron 8 ,
+to check the installed packages on a regular basis.
+One might wish to invoke
+.Nm
+as:
+.Bd -literal -offset indent
+pkg_info | awk '{print $1}' | yvc
+.Ed
+.Pp
+To check the packages 'zsh-4.2.6' and 'sudo-1.6.8pl1' against any known
+vulnerabilities:
+.Bd -literal -offset indent
+yvc zsh-4.2.6 sudo-1.6.8pl1
+.Ed
+.Pp
+To check all rpms on the host
+\'hostname.yahoo.com':
+.Bd -literal -offset indent
+ssh hostname.yahoo.com "rpm -qa" | yvc
+.Ed
+.Sh EXIT STATUS
+.Ex -std
+.Sh FILES
+.Bl -tag -width _home_y_var_yvc_nbvlist_
+.It /usr/local/etc/yvc.conf
+The
+.Nm
+configuration file.
+.It /usr/local/var/yvc/fbvlist
+A list of known vulnerabilities in the FreeBSD ports collection derived
+from http://www.freebsd.org/ports/portaudit/.
+.It /usr/local/var/yvc/nbvlist
+A list of vulnerabilities provided by the NetBSD Project.
+See http://www.netbsd.org/support/security/#check-pkgsrc for details.
+.It /usr/local/var/yvc/rh4vlist
+A list of vulnerabilities known in RHEL4, derived from
+http://www.redhat.com/security/data/oval/com.redhat.rhsa-all.xml.bz2 .
+.It /usr/local/var/yvc/rh5vlist
+A list of vulnerabilities known in RHEL5, derived from
+http://www.redhat.com/security/data/oval/com.redhat.rhsa-all.xml.bz2 .
+.El
+.Sh SEE ALSO
+.Xr fetch-vlist 1 ,
+.Xr rpm 1 ,
+.Xr yinst 1 ,
+.Xr yvc.conf 5
+.Sh HISTORY
+.Nm
+was conceptually based on NetBSD's "audit-packages" command.
+It was originally written by
+.An Jan Schaumann
+.Aq jschauma@yahoo-inc.com
+in July 2008.
+.Sh BUGS
+Please report bugs and feature requests to the author.
diff --git a/doc/man/yvc.conf.5 b/doc/man/yvc.conf.5
new file mode 100644
index 0000000..b726588
--- /dev/null
+++ b/doc/man/yvc.conf.5
@@ -0,0 +1,67 @@
+.\" $Id: yvc.conf.5 4 2010-09-30 14:35:04Z jans $
+.\" $URL: svn+ssh://svn.corp.yahoo.com/yahoo/tools/yvc/branches/outgoing/doc/man/yvc.conf.5 $
+.\"
+.\" Copyright (c) 2008 Yahoo! Inc.
+.\"
+.Dd October 31, 2009
+.Dt YVC.CONF 1
+.Os
+.Sh NAME
+.Nm yvc.conf
+.Nd
+.Xr yvc 1
+configuration file
+.Sh DESCRIPTION
+The
+.Nm
+file specifies the various configuration options for
+.Xr yvc 1 .
+.Pp
+The
+.Nm
+file consists of sections and parameters.
+A section begins with the name of the section in square brackets and continues
+until the next section begins or the end the file is reached.
+.Pp
+Sections contain parameters of the form 'name: value'.
+.\" .Pp
+.\" The values can contain format strings which refer to other values in the same
+.\" section.
+.\" .Pp
+.\" For example:
+.\" .Bd -literal -offset indent
+.\" something: %(dir)s/whatever
+.\" .Ed
+.\" .Pp
+.\" would resolve the "%(dir)s" to the value of dir.
+.Pp
+A line starting with the '#' character is ignored.
+.Sh SECTIONS
+At the moment, the only section supported is "[YVC]".
+.Sh PARAMETERS
+The following parameters are understood:
+.Bl -tag -width LIST_OF_VULNERABILITIES_
+.It IGNORE_TYPES
+A list of vulnerability types that should be ignored.
+.It IGNORE_URLS
+A list of URLs that should be ignored.
+That is, if a package is found to be vulnerable for the given URL, ignore it.
+.It VLISTS
+The file(s) in which the list(s) of vulnerabilities is/are found.
+Defaults to /home/y/var/yvc/yvlist and a platform-specific vlist.
+.It VERBOSITY
+Level of verbosity.
+Valid values are '0' (no output except for vulnerable packages), '1' (some
+output), '2' (a lot of output).
+Defaults to '0'.
+.El
+.Sh SEE ALSO
+.Xr fetch-vlist 1 ,
+.Xr rpm 1 ,
+.Xr yinst 1 ,
+.Xr yvc 1
+.Sh HISTORY
+Support for the
+.Nm
+file was built into the first release of
+.Xr yvc 1 .
diff --git a/lib/yvc.py b/lib/yvc.py
new file mode 100644
index 0000000..2927277
--- /dev/null
+++ b/lib/yvc.py
@@ -0,0 +1,565 @@
+"""a software packages vulnerability checker
+
+An interface to compare given package names against the list of known
+vulnerabilities and report any security issues. This interface is used by
+the yvc(1) tool, though it's possible that it might prove useful for other
+tools.
+
+yvc was based conceptually on NetBSD's audit-packages(1) command.
+
+"""
+
+# Copyright (c) 2008,2010 Yahoo! Inc.
+#
+# Originally written by Jan Schaumann in July 2008.
+
+import ConfigParser
+
+from distutils.version import LooseVersion
+from fnmatch import fnmatch
+import getopt
+import logging
+import os
+import re
+import stat
+import string
+import subprocess
+import sys
+
+###
+### Classes
+###
+
+class Checker(object):
+ """A Software Package Vulnerability Checker
+
+ The main interface of the 'yvc' program. Its member functions are used to
+ run the program, it's private members are (mostly) configuration options
+ that can be set via the command-line.
+ """
+
+ EXIT_ERROR = 1
+ EXIT_SUCCESS = 0
+
+ def __init__(self):
+ """Construct a Checker object with default values."""
+
+ self.__opts = {
+ "cfg_file" : "/usr/local/etc/yvc.conf",
+ "ignore_types" : None,
+ "ignore_urls" : None,
+ "vlists" : [],
+ "verbosity" : logging.WARNING
+ }
+
+ self.__frobbed = {}
+
+ self.__list_opts = [ "ignore_types", "ignore_urls" ]
+ self.__int_opts = [ "verbosity" ]
+
+ self.__cfg_section = "YVC"
+
+ self.__vulns = []
+
+
+ def _setVerbosity(self, f):
+ """set the verbosity based on the given factor"""
+
+ n = int(f)
+ v = self.getOpt("verbosity")
+
+ if (v > logging.INFO):
+ v = logging.INFO
+ if (n > 1):
+ # XXX: magic number; logging uses specific numbers, but
+ # has no specified increment
+ v -= (5 * n)
+
+ # The logging module treats 0 as 'unset'.
+ if v < 1:
+ v = 1
+ self.setOpt("verbosity", v)
+ logging.basicConfig(level=self.getOpt("verbosity"),
+ format='%(message)s')
+
+
+ class Usage(Exception):
+ """A simple exception that provides a usage statement and a return code."""
+
+ def __init__(self, rval):
+ self.err = rval
+ self.msg = 'Usage: %s [-hv] [-c file] [-l file] [pkg [...]]\n' \
+ % os.path.basename(sys.argv[0])
+ self.msg += '\t-c file read configuration from file\n'
+ self.msg += '\t-h print this message and exit\n'
+ self.msg += '\t-l file check against the list of vulnerabilities provided in file\n'
+ self.msg += '\t-v be verbose\n'
+
+
+ def checkPackage(self, package):
+ """check a given package against all vulnerabilities and report results
+
+ Arguments:
+ package -- package name to check
+ """
+
+ logging.info("Checking package '%s'..." % package)
+ pkg = os.path.basename(package)
+ for v in self.__vulns:
+ if self.ignore(v):
+ continue
+ logging.log(15, "Checking package '%s' against %s..." % (package, v.url))
+ if v.match(pkg):
+ print "Package %s has a %s vulnerability, see: %s" % \
+ (package, v.type, v.url)
+
+
+ def getOpt(self, opt):
+ """Retrieve the given configuration option.
+
+ Returns:
+ The value for the given option if it exists, None otherwise.
+ """
+
+ try:
+ r = self.__opts[opt]
+ except ValueError:
+ r = None
+
+ return r
+
+
+ def ignore(self, v):
+ """determine whether or not to ignore a given Vulnerability
+
+ Arguments:
+ v -- a Vulnerability
+
+ Returns:
+ True or False
+ """
+
+ if self.__opts["ignore_types"]:
+ try:
+ i = self.__opts["ignore_types"].index(v.type)
+ logging.log(15, "Ignoring vulnerability %s based on type %s."
+ % (v.url, v.type))
+ return True
+ except ValueError:
+ pass
+
+ if self.__opts["ignore_urls"]:
+ try:
+ i = self.__opts["ignore_urls"].index(v.url)
+ logging.log(15, "Ignoring vulnerability %s based on URL." % v.url)
+ return True
+ except ValueError:
+ pass
+
+ return False
+
+
+ def makeV(self, line):
+ """create a Vulnerability object from the given line
+
+ Arguments:
+ line -- a line from a vulnerability list, expected to be in the
+ format "nametypeurl"
+
+ Returns:
+ None -- input line was not in expected format; or
+ an object of type Vulnerability
+ """
+
+ v = None
+
+ pattern = re.compile('(?P^[^#\s]+)\s+(?P[^\s]+)\s+(?P.*)$')
+ rem = pattern.match(line)
+
+ if rem:
+ v = Vulnerability(rem.group('pattern'),
+ rem.group('type'),
+ rem.group('url'))
+
+ return v
+
+
+ def parseConfig(self, cfile):
+ """parse the configuration file and set appropriate variables
+
+ This function may throw an exception if it can't read or parse the
+ configuration file (for any reason).
+
+ Arguments:
+ cfile -- the configuration file to parse
+ """
+
+ cfg = ConfigParser.ConfigParser()
+ try:
+ f = file(cfile)
+ except IOError, e:
+ logging.error("Unable to open config file '%s': %s" % \
+ (self.__opts["cfg_file"], e.strerror))
+ raise
+
+ try:
+ cfg.readfp(f)
+ f.close()
+ except ConfigParser.ParsingError, e:
+ logging.error("Unable to parse config file: %s" % e.__repr__())
+ raise
+ # NOTREACHED
+
+ if not cfg.has_section(self.__cfg_section):
+ raise ConfigParser.NoSectionError("Default section \"%s\" not found in %s."
+ % (self.__cfg_section, self.__opts["cfg_file"]))
+
+ for key in self.__opts:
+ v = None
+
+ if key in self.__frobbed:
+ v = self.__frobbed[key]
+ else:
+ if cfg.has_option(self.__cfg_section, key):
+ v = cfg.get(self.__cfg_section, key)
+
+ if v:
+ if (key == "verbosity"):
+ self._setVerbosity(v)
+ elif (key == "vlists"):
+ if type(v) is str:
+ lists = v.split()
+ elif type(v) is list:
+ lists = v
+ else:
+ logging.error("'%s' is of type %s??" % (key, type(v)))
+ continue
+ self.__opts[key] = lists
+ else:
+ self.__opts[key] = v
+
+
+ def parseList(self, list):
+ """parse the vulnerability list and build a list of vulnerabilities
+
+ This function may throw an exception if it can't read or parse the
+ configuration file (for any reason).
+
+ Arguments:
+ list -- the file containing the vulnerabilities
+ """
+
+ logging.info("Parsing vulnerability list (%s)." % list)
+ try:
+ f = file(list)
+ except IOError, e:
+ logging.error("Unable to open list of vulnerabilities '%s': %s" % (list, e))
+ raise
+
+ line = f.readline()
+ while len(line) != 0:
+ v = self.makeV(line)
+ if v and not self.ignore(v):
+ self.__vulns.append(v)
+ line = f.readline()
+
+ f.close()
+
+
+ def parseOptions(self, inargs):
+ """Parse given command-line options and set appropriate attributes.
+
+ Arguments:
+ inargs -- arguments to parse
+
+ Returns:
+ the list of arguments remaining after all flags have been
+ processed
+
+ Raises:
+ Usage -- if '-h' or invalid command-line args are given
+ """
+
+ try:
+ opts, args = getopt.getopt(inargs, "c:hl:v")
+ except getopt.GetoptError:
+ raise self.Usage(self.EXIT_ERROR)
+
+ for o, a in opts:
+ if o in ("-c"):
+ self.setOpt("cfg_file", a)
+ if o in ("-h"):
+ raise self.Usage(self.EXIT_SUCCESS)
+ if o in ("-l"):
+ vlists = []
+ if ("vlists" in self.__frobbed):
+ vlists = self.getOpt("vlists")
+ vlists.append(a)
+ self.setOpt("vlists", vlists)
+ if o in ("-v"):
+ self._setVerbosity(1)
+
+ return args
+
+
+ def setOpt(self, opt, val):
+ """Set the given option to the provided value"""
+
+ self.__opts[opt] = val
+ self.__frobbed[opt] = val
+
+
+ def verifyOptions(self):
+ """make sure that all given options (from command-line or config file) are
+ valid"""
+
+ for opt in self.__list_opts:
+ if self.__opts[opt]:
+ self.__opts[opt] = self.__opts[opt].split()
+
+ for opt in self.__int_opts:
+ if type(self.__opts[opt]) is not int:
+ try:
+ self.__opts[opt] = string.atoi(self.__opts[opt])
+ except ValueError:
+ logging.error("Invalid value for configuration option '%s': %s"
+ % (opt, self.__opts[opt]))
+ raise
+
+
+class Vulnerability(object):
+ """An object representing a vulnerability.
+
+ A Vulnerability consists of a package name-version pattern, a
+ vulnerability type and a URL providing more information about the given
+ vulnerability. Each such attribute is represented as a string and can
+ trivially be accessed via the members 'pattern', 'type' and 'url'.
+ """
+
+ def __init__(self, pattern, type, url):
+ """Construct a Vulnerability with the given attributes."""
+
+ self.pattern = pattern
+ self.type = type
+ self.url = url
+
+
+ def match(self, pkg):
+ """Compare a given name-version pair to the object's pattern.
+
+ This function determines if a given name-version pair matches this
+ object's pattern. Since a Vulnerability's pattern may contain brace
+ expansion or fnmatch-like expressions as well as an operation, a
+ simple string comparison is not sufficient.
+
+ Arguments:
+ pkg -- a package name and version string, eg "package-1.2.3"
+
+ Returns: True or False
+ """
+
+ # version comparison
+ # - if pkg-name and pattern are identical
+ # - return true
+ # - if pattern matches {, then
+ # - perform brace expansion
+ # for each pattern name
+ # - if pattern matches <=>
+ # - if name portion matches pkg-name portion
+ # - construct a LooseVersion from pkg-name
+ # - if version matches <=> (ie we had ">n -1):
+ patterns = braceExpand(self.pattern)
+
+ for pat in patterns:
+ m = re.search('(?P[^<=>]+)(?P[<=>]+)(?P.*)', pat)
+ if m:
+ name = m.group('name')
+ cmp = m.group('cmp')
+ version = m.group('version')
+
+ pname = re.sub(r'(.*)-.*', r'\1', pkg)
+ if pname == name:
+ pkgversion = LooseVersion(pkg)
+ m = re.search('(?P[^<=>]*)(?P[<=>]+)(?P.*)', version)
+ if m:
+ min = m.group('min')
+ cmp2 = m.group('cmp2')
+ max = m.group('max')
+ minversion = LooseVersion(name + "-" + min)
+ maxversion = LooseVersion(name + "-" + max)
+ return (versionCompare(pkgversion, cmp, minversion) and
+ versionCompare(pkgversion, cmp2, maxversion))
+ else:
+ patternversion = LooseVersion(name + "-" + version)
+ return versionCompare(pkgversion, cmp, patternversion)
+ else:
+ if fnmatch(pkg, pat):
+ return True
+
+ return False
+
+###
+### Utility functions
+###
+
+def braceExpand(input):
+ """expand a string possibly containing a brace expansion
+
+ This functions expands an input string to a list of strings based on brace
+ expansion somewhat similar to zsh(1). It does not perform numeric range
+ expansion, however.
+
+ As an example, given the input string "foo-1{,b,-bar}", this function will
+ return [ "foo-1", "foo-1b", "foo-1-bar" ].
+
+ Simply nested expansions may also be resolved recursively. Note, however,
+ that this is not supposed to be fully compatible with zsh(1) style
+ expansions -- if more complex expansions are needed, consider shelling out
+ to "echo echo string | zsh".
+
+ Arguments:
+ input -- any string
+
+ Returns
+ A list of strings, possibly containing the input string as a single
+ element.
+ """
+
+ m = re.search(r'(?P.*?){(?P[^{}]+)}(?P.*)', input)
+ if not m:
+ return [ input ]
+
+ expanded = []
+ term = m.group('term')
+ suffixes = m.group('suffixes')
+ rest = m.group('rest')
+
+ # Nested zero-matches such as "foo-{,bar{this,something}}" shouldn't get the
+ # base term appended multiple times. Since we expand from the inside out,
+ # add the base term once, then remove empty item.
+ m = re.search(r'(?P.*){,', term)
+ if m:
+ expanded.append(m.group('base'))
+ term = re.sub(r'{,', r'{', term, 1)
+
+ # suffixes can be expansion, too -- recurse
+ expansions = braceExpand(suffixes)
+ for exp in expansions:
+ for x in exp.split(","):
+ # Fully expanded suffixes can also be expansions themselves!
+ # Recurse!
+ s = braceExpand(term + x + rest)
+ expanded += s
+
+ return expanded
+
+
+def versionCompare(v1, op, v2):
+ """compare two versions based on the given operator
+
+ Arguments:
+ v1 -- a string or Version object
+ op -- operator, indicating type of comparison (">", ">=", "<", "<=")
+ v2 -- a string or Version object
+
+ Returns:
+ True -- if ( v1 op v2 ) satisfies the comparison
+ False -- otherwise
+
+ ie, effectively returns "v1 op b2"
+ """
+
+ if (op == ">="):
+ return (v1 >= v2)
+ elif (op == ">"):
+ return (v1 > v2)
+ elif (op == "<"):
+ return (v1 < v2)
+ elif (op == "<="):
+ return (v1 <= v2)
+ elif (op == "=="):
+ return (v1 == v2)
+ else:
+ # shouldn't happen
+ logging.error("Unexpected operand: %s (%s %s %s)" %
+ (op, v1, op, v2))
+ return False
+
+###
+### A 'main' for the yvc(1) program.
+###
+
+def doStdin(checker):
+ """check packages from stdin
+
+ Arguments:
+ checker -- an instance of a yvc Checker
+ """
+
+ while 1:
+ line = sys.stdin.readline()
+ if not line:
+ break
+ pkgs = line.split()
+ for p in pkgs:
+ checker.checkPackage(p)
+
+
+def main(args):
+ """Run the yvc(1) program.
+
+ Arguments:
+ args -- command-line arguments
+ """
+
+ try:
+ checker = Checker()
+ try:
+ args = checker.parseOptions(args)
+ except checker.Usage, u:
+ if (u.err == checker.EXIT_ERROR):
+ out = sys.stderr
+ else:
+ out = sys.stdout
+ out.write(u.msg)
+ sys.exit(u.err)
+ # NOTREACHED
+
+ try:
+ checker.parseConfig(checker.getOpt("cfg_file"))
+ checker.verifyOptions()
+ for vlist in checker.getOpt("vlists"):
+ checker.parseList(vlist)
+ except Exception, e:
+ logging.error(e)
+ sys.exit(checker.EXIT_ERROR)
+ # NOTREACHED
+
+ if args:
+ for p in args:
+ if (p == "-"):
+ doStdin(checker)
+ else:
+ checker.checkPackage(p)
+ else:
+ doStdin(checker)
+
+ except KeyboardInterrupt:
+ # catch ^C, so we don't get a "confusing" python trace
+ sys.exit(checker.EXIT_ERROR)
diff --git a/misc/harvest_freebsd_yvc.pl b/misc/harvest_freebsd_yvc.pl
new file mode 100755
index 0000000..8f4f935
--- /dev/null
+++ b/misc/harvest_freebsd_yvc.pl
@@ -0,0 +1,171 @@
+#! /usr/local/bin/perl -w
+#
+# Copyright (c) 2009,2010 Yahoo! Inc.
+#
+# Originally written by Joshua Moss in March 2009.
+#
+# This program fetches the list of known vulnerabilities in the FreeBSD
+# ports collection from http://www.freebsd.org/ports/portaudit/ and
+# generates a yvc(1) compatible vlist.
+
+use strict;
+use IO::Socket;
+
+use constant FBSD_PORTAUDIT => "http://www.freebsd.org/ports/portaudit/";
+
+# each element stores a hash which contains unique vuln id, description,
+# packages (vulns)
+my @VULN_INFO;
+
+###
+### Subroutines
+###
+
+# function : get_freebsd_vulns
+# purpose : loop over the list of vulnerabilities and populate the global
+# array with hashes containing an id and a description
+# inputs : none
+# returns : void, global @VULN_INFO has been populated
+
+sub get_freebsd_vulns {
+
+ my $vuln_regex = qr/([a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12})\.html\"\>\s*([^<]+)/;
+
+ foreach my $line (get_page(FBSD_PORTAUDIT)) {
+ next unless ($line =~ m/$vuln_regex/);
+ # unique vuln id, description
+ push(@VULN_INFO, { id => $1, desc => $2 });
+ }
+
+ return;
+}
+
+# function : get_freebsd_vuln_detail
+# purpose : loop over all vulnerabilities and fetch their respective page,
+# then parse that page and populate the 'vulns' field of this
+# vulnerabilitie's hash
+# inputs : none
+# returns : void, populates @VULN_INFO further
+
+sub get_freebsd_vuln_detail {
+ my $affects;
+
+ for (my $i=0; $i< scalar (@VULN_INFO); $i++) {
+
+ my $vuln_page = FBSD_PORTAUDIT . $VULN_INFO[$i]->{'id'} . '.html';
+ $affects = 0;
+
+ foreach my $line (get_page($vuln_page)) {
+ if ($line =~/Affects\:/) {
+ $affects = 1;
+ next;
+ }
+
+ if ($affects && $line =~ m/li>\s*([^<]+)) {
+ push(@{ $VULN_INFO[$i]->{'vulns'} }, $1);
+ }
+
+ if ($affects && $line =~ m/\/ul/) {
+ $affects = 0;
+ next;
+ }
+ }
+ }
+
+ return;
+}
+
+# function : print_freebsd_vuln_yvc
+# purpose : iterate over all vulnerabilities and print them in the desired
+# format
+# inputs : none
+# returns : void, output is printed to stdout
+
+sub print_freebsd_vuln_yvc {
+
+ my $desc_regex = qr/(multiple-vulnerabilities|denial-of-service|cross-site-request-forgery|remote-dos|xss|cross-site-scripting|arbitrary-code-execution|script-insertion|input-validation|directory-traversal|heap-overflow|buffer-overflow|stack-overflow|session-hijacking|command-execution|local-privilege-escalation|command-injection|information-disclosure|arbitrary-file-disclosure|arbitrary-script-execution)/;
+
+ # reverse just to be consistent with pkgsrc vlist ordering
+ for (my $i=scalar(@VULN_INFO); $i>=0; $i--) {
+
+ next unless ($VULN_INFO[$i]->{'vulns'});
+
+ foreach my $pkg (sort @{ $VULN_INFO[$i]->{'vulns'} } ) {
+ next unless ($pkg =~/[a-z]/);
+
+ $pkg =~ s/\<\;/\/g;
+ #$pkg =~ s/=/-/g;
+ $pkg =~ s/\s+//g;
+
+ $VULN_INFO[$i]->{'desc'} = lc( $VULN_INFO[$i]->{'desc'} );
+ $VULN_INFO[$i]->{'desc'} =~ s/^\s*[a-z0-9]+\s+\-{2,}\s*//;
+ $VULN_INFO[$i]->{'desc'} =~ s/[^a-z0-9]/-/g;
+ $VULN_INFO[$i]->{'desc'} =~ s/-{2,}/-/g;
+ $VULN_INFO[$i]->{'desc'} =~ s/^-+//g;
+ $VULN_INFO[$i]->{'desc'} =~ s/-+$//g;
+ $VULN_INFO[$i]->{'desc'} =~ s/-vulnerability$//;
+
+ # shorten some of these long descriptions to their core issue
+ if ($VULN_INFO[$i]->{'desc'} =~/$desc_regex/) {
+ $VULN_INFO[$i]->{'desc'} = $1;
+ $VULN_INFO[$i]->{'desc'} =~s/^dos$/denial-of-service/;
+ $VULN_INFO[$i]->{'desc'} =~s/^xss$/cross-site-scripting/;
+ }
+
+ printf("%s\t%s\t%s\n", $pkg, $VULN_INFO[$i]->{'desc'},
+ FBSD_PORTAUDIT . $VULN_INFO[$i]->{'id'} . '.html');
+ }
+ }
+
+ return;
+}
+
+# function : get_page
+# purpose : retrieve the given document and return the contents as an array
+# inputs : URI
+# returns : an array of lines
+
+sub get_page {
+ my ($url) = @_;
+
+ my ($port, $host, $uri);
+
+ if ($url =~s/^(https?):\/+([^\/]+)(\/.*)$//) {
+ $port = ($1 eq 'https') ? 443 : 80;
+ $host = $2;
+ $uri = $3;
+ } else {
+ return undef;
+ }
+
+ my $PAGE = IO::Socket::INET->new(PeerAddr=>$host,
+ PeerPort=>$port,
+ Proto=>"tcp",
+ Timeout=>7) or return;
+
+ my $timeout = 30;
+ print $PAGE "GET $uri HTTP/1.0\r\n",
+ "Host: $host\r\n\r\n";
+
+ alarm($timeout);
+
+ my @page = <$PAGE>;
+
+ return @page;
+}
+
+###
+### Main
+###
+
+# scrape the main listing
+get_freebsd_vulns();
+
+# dive into each page
+get_freebsd_vuln_detail();
+
+# clean up and print out
+print_freebsd_vuln_yvc();
+
+exit(0);
diff --git a/misc/redhat_oval_to_yvc.py b/misc/redhat_oval_to_yvc.py
new file mode 100755
index 0000000..540b0b5
--- /dev/null
+++ b/misc/redhat_oval_to_yvc.py
@@ -0,0 +1,93 @@
+#!/usr/local/bin/python
+
+#
+# Copyright (c) 2009,2010 Yahoo! Inc.
+#
+# Originally written by Joshua Moss in October 2009.
+#
+# This program reads the Open Vulnerability and Assessment Language (OVAL)
+# file, available from http://www.redhat.com/security/data/oval/com.redhat.rhsa-all.xml.bz2
+# and generates a yvc(1) compatible vlist.
+#
+
+import re
+import sys
+import socket
+import os.path
+import xml.dom.minidom
+import bz2
+import urllib
+import time
+
+# Source of the oval xml.bz2 file
+oval_url = 'http://www.redhat.com/security/data/oval/com.redhat.rhsa-all.xml.bz2'
+
+# Destination of the oval xml.bz2 file
+oval_bz2 = './com.redhat.rhsa-all.xml.bz2'
+
+# Usage
+if len(sys.argv) != 2:
+ print "Usage: %s %s" % (sys.argv[0], '<4|5>')
+ sys.exit(1)
+
+
+###
+### Subroutines
+###
+
+
+# function : download_redhat_oval_bz2
+# purpose : fetches the xml.bz2 file from redhat and write to local dir on disk
+# inputs : none
+# returns : void
+
+def download_redhat_oval_bz2():
+ if not os.path.isfile(oval_bz2): # XXX remove this if you want to download every time, rather than handle via make clean
+ socket.setdefaulttimeout(45)
+ urllib.urlretrieve(oval_url, oval_bz2) # in, out
+ urllib.urlcleanup()
+
+
+
+# function : print_redhat_yvc
+# purpose : traverses the xml dom for vulnerability information and prints out in the desired
+# format
+# inputs : numeric string, appends to regex search for 'el'
+# returns : void, output is printed to stdout
+
+def print_redhat_yvc(version = '[45]'):
+
+ print '# Generated on ' + time.strftime("%a %b %e %H:%M:%S %Z %Y") # Wed Oct 28 12:05:05 PDT 2009
+
+ arch = bz2.BZ2File(oval_bz2, 'r')
+ document = xml.dom.minidom.parse(arch)
+
+ apps = {} # they seem to keep their tests in chronological order, we only want the latest
+ definitions = document.getElementsByTagName('definition')
+ for d in definitions:
+ #description = d.getElementsByTagName('metadata')[0].getElementsByTagName('description')[0].lastChild.nodeValue # TODO parse descriptions for keywords
+ title = d.getElementsByTagName('metadata')[0].getElementsByTagName('title')[0].lastChild.nodeValue.split(': ')[1].replace('\n', '')
+ title = re.sub('[^a-zA-Z0-9]+', '-', title).rstrip('-').lower()
+ ref_url = d.getElementsByTagName('reference')[0].getAttribute('ref_url')
+
+ criterions = d.getElementsByTagName('criterion')
+ for c in criterions:
+ criteria = c.getAttribute('comment').replace('\t', ' ')
+
+ if re.search('el'+version, criteria, re.I) == None:
+ continue # skipping lines that don't match version
+
+ criteria = re.sub('\d+:', '', criteria)
+ criteria = criteria.replace(' is earlier than ','<')
+
+ app = criteria.split('<')[0]
+
+ apps[app] = "%s\t%s\t%s" % (criteria, title, ref_url)
+
+ for app in apps:
+ print apps[app]
+
+
+download_redhat_oval_bz2()
+print_redhat_yvc(sys.argv[1])
+sys.exit(0)
diff --git a/test/Makefile b/test/Makefile
new file mode 100644
index 0000000..780f04d
--- /dev/null
+++ b/test/Makefile
@@ -0,0 +1,12 @@
+# Copyright (c) 2008 Yahoo! Inc.
+#
+# Originally written by Jan Schaumann in July 2008.
+#
+# A Makefile to run all tests in this directory.
+
+all: py
+
+py:
+ @for p in *.py; do \
+ python $$p; \
+ done
diff --git a/test/test.py b/test/test.py
new file mode 100755
index 0000000..d3d727c
--- /dev/null
+++ b/test/test.py
@@ -0,0 +1,295 @@
+#! /usr/local/bin/python
+#
+# Copyright (c) 2008, Yahoo! Inc.
+#
+# Originally written by Jan Schaumann in July
+# 2008.
+#
+# A unittest for functionality in yvc.py.
+
+import sys
+sys.path.append("../lib/")
+
+from distutils.version import LooseVersion
+import ConfigParser
+import logging
+import unittest
+
+import yvc
+
+class TestYvc(unittest.TestCase):
+
+ def setUp(self):
+ self.yvc = yvc.Checker()
+
+
+ def testDefaults(self):
+ cfg_opts = {
+ "cfg_file" : "/usr/local/etc/yvc.conf",
+ "ignore_types" : None,
+ "ignore_urls" : None,
+ "vlists" : [],
+ "verbosity" : logging.WARNING
+ }
+
+ for key, val in cfg_opts.iteritems():
+ self.assertEqual(self.yvc.getOpt(key), val)
+
+
+ def testUsageHelp(self):
+ opts = [ "-h" ]
+ # -h triggers usage
+ self.assertRaises(yvc.Checker.Usage, self.yvc.parseOptions, opts)
+ try:
+ self.yvc.parseOptions(opts)
+ except yvc.Checker.Usage, u:
+ self.assertEqual(yvc.Checker.EXIT_SUCCESS, u.err)
+
+
+ def testUsageAddList(self):
+ opts = [ "-l", "/dev/null", "-l", "/whatever" ]
+ vlist = [ "/dev/null", "/whatever" ]
+ self.yvc.parseOptions(opts)
+ self.assertEqual(self.yvc.getOpt("vlists"), vlist)
+
+
+ def testUsageMissingArg(self):
+ # -c requires an argument
+ opts = [ "-c" ]
+ self.assertRaises(yvc.Checker.Usage, self.yvc.parseOptions, opts)
+ try:
+ self.yvc.parseOptions(opts)
+ except yvc.Checker.Usage, u:
+ self.assertEqual(yvc.Checker.EXIT_ERROR, u.err)
+
+
+ def testUsageAllValid(self):
+ opts = [ "-c", "/dev/null", "-l", "/dev/null", "-v" ]
+ # need to assert that this does NOT raise Usage
+ try:
+ self.assertRaises(yvc.Checker.Usage, self.yvc.parseOptions, opts)
+ except self.failureException:
+ pass
+
+ def testParseConfig(self):
+ cfg = "../conf/yvc.conf"
+ opts = [ "-c", cfg ]
+ # parsing a correct file works
+ self.yvc.parseConfig(cfg)
+
+ # parsing an empty file raises an error
+ self.assertRaises(ConfigParser.NoSectionError, self.yvc.parseConfig, "/dev/null")
+
+ # passing a single vlist overrides defaults from config file
+ opts = [ "-c", cfg, "-l", "../yvlist" ]
+ self.yvc.parseOptions(opts)
+ self.yvc.parseConfig(cfg)
+ self.assertEqual([ "../yvlist" ], self.yvc.getOpt("vlists"))
+
+
+ def testSetOpts(self):
+ f = self.yvc.getOpt("cfg_file")
+ self.yvc.setOpt("cfg_file", "invalid")
+ self.assertEqual("invalid", self.yvc.getOpt("cfg_file"))
+ self.yvc.setOpt("cfg_file", f)
+
+
+ def testMakeValidV(self):
+ v = self.yvc.makeV("cfengine<1.5.3nb3 remote-root-shell ftp://ftp.NetBSD.org/pub/NetBSD/security/advisories/NetBSD-SA2000-013.txt.asc")
+ self.assertEqual("cfengine<1.5.3nb3", v.pattern)
+ self.assertEqual("remote-root-shell", v.type)
+ self.assertEqual("ftp://ftp.NetBSD.org/pub/NetBSD/security/advisories/NetBSD-SA2000-013.txt.asc", v.url)
+
+ def testMakeCommentV(self):
+ v = self.yvc.makeV("# a comment of some sort")
+ self.assertEqual(None, v)
+
+
+ def testMakeInvalidV(self):
+ v = self.yvc.makeV("alinewithout anything")
+ self.assertEqual(None, v)
+
+
+ def testVulnerabilities(self):
+ v1 = yvc.Vulnerability("foo-1.2", "local-root-shell",
+ "http://www.nowhere.com")
+ self.assertEqual(True, v1.match("foo-1.2"))
+ self.assertEqual(False, v1.match("foo-2.1"))
+
+
+ def testBraceExpand(self):
+ input = "foo-1.2"
+ self.assertEqual([ input ], yvc.braceExpand(input))
+
+
+ def testBraceExpandSuffixes(self):
+ input = "foo-1.2{,-bar,12}"
+ output = [ "foo-1.2", "foo-1.2-bar", "foo-1.212" ]
+ self.assertEqual(output, yvc.braceExpand(input))
+
+
+ def testBraceExpandPrefixSuffixes(self):
+ input = "{this-,that-}foo-1.2{,-bar,12}"
+ output = [ "this-foo-1.2", "this-foo-1.2-bar", "this-foo-1.212",
+ "that-foo-1.2", "that-foo-1.2-bar", "that-foo-1.212" ]
+ self.assertEqual(output, yvc.braceExpand(input))
+
+
+ def testBraceExpandNested(self):
+ input = "foo-1.2{,-bar{-baz,-blog}}"
+ output = [ "foo-1.2", "foo-1.2-bar-baz", "foo-1.2-bar-blog" ]
+ self.assertEqual(output, yvc.braceExpand(input))
+
+
+ def testBraceExpandSuffixesTrailing(self):
+ input = "foo-1.2{,-bar,12}-bar"
+ output = [ "foo-1.2-bar", "foo-1.2-bar-bar", "foo-1.212-bar" ]
+ self.assertEqual(output, yvc.braceExpand(input))
+
+
+ def testVersionCompareLarger(self):
+ s1 = LooseVersion("foo-1.2")
+ s2 = LooseVersion("foo-1.3")
+ self.assertTrue(yvc.versionCompare(s1, "<", s2))
+ self.assertTrue(yvc.versionCompare(s1, "<=", s2))
+ self.assertFalse(yvc.versionCompare(s1, ">", s2))
+ self.assertFalse(yvc.versionCompare(s1, ">=", s2))
+
+
+ def testVersionCompareSmaller(self):
+ s1 = LooseVersion("RealPlayerGold-10.0.9.809.20070726")
+ s2 = LooseVersion("RealPlayerGold-10.0.0.809.20070726")
+ self.assertTrue(yvc.versionCompare(s1, ">", s2))
+ self.assertTrue(yvc.versionCompare(s1, ">=", s2))
+ self.assertFalse(yvc.versionCompare(s1, "<", s2))
+ self.assertFalse(yvc.versionCompare(s1, "<=", s2))
+
+
+ def testVersionCompareEqual(self):
+ s1 = LooseVersion("ports/ldconfig_compat-1.0_8.1yahoo")
+ s2 = s1
+ self.assertFalse(yvc.versionCompare(s1, ">", s2))
+ self.assertTrue(yvc.versionCompare(s1, ">=", s2))
+ self.assertFalse(yvc.versionCompare(s1, "<", s2))
+ self.assertTrue(yvc.versionCompare(s1, "<=", s2))
+
+
+ def testMatchSimpleSmaller(self):
+ v = self.yvc.makeV("cfengine<1.5.3nb3 remote-root-shell ftp://ftp.NetBSD.org/pub/NetBSD/security/advisories/NetBSD-SA2000-013.txt.asc")
+ self.assertTrue(v.match("cfengine-1.5.2nb2"))
+ self.assertTrue(v.match("cfengine-1.5"))
+ self.assertFalse(v.match("cfengine-1.5.3nb3"))
+ self.assertFalse(v.match("cfengine-1.5.3nb4"))
+ self.assertFalse(v.match("cfengine-1.6"))
+ self.assertFalse(v.match("something-1.5.3nb3"))
+
+
+ def testMatchSimpleSmallerEqual(self):
+ v = self.yvc.makeV("pine<=4.21 remote-root-shell ftp://ftp.FreeBSD.org/pub/FreeBSD/CERT/advisories/FreeBSD-SA-00:59.pine.asc")
+ self.assertTrue(v.match("pine-4.20"))
+ self.assertTrue(v.match("pine-4.21"))
+ self.assertFalse(v.match("pine-4.22"))
+ self.assertFalse(v.match("anything-4.21"))
+
+
+ def testMatchExact(self):
+ v = self.yvc.makeV("ap-php-4.0.4 remote-code-execution http://security.e-matters.de/advisories/012002.html")
+ self.assertTrue(v.match("ap-php-4.0.4"))
+ self.assertFalse(v.match("ap-php-4.0.3"))
+ self.assertFalse(v.match("ap-php-4.0.5"))
+ self.assertFalse(v.match("whatever"))
+
+
+ def testMatchSimpleRange(self):
+ v = self.yvc.makeV("apache-2.0.3[0-3]* remote-root-shell http://httpd.apache.org/info/security_bulletin_20020617.txt")
+ self.assertFalse(v.match("apache-2.0.3"))
+ self.assertTrue(v.match("apache-2.0.30"))
+ self.assertTrue(v.match("apache-2.0.31"))
+ self.assertTrue(v.match("apache-2.0.32"))
+ self.assertTrue(v.match("apache-2.0.33"))
+ self.assertFalse(v.match("apache-2.0.2"))
+ self.assertFalse(v.match("apache-2.0.299"))
+ self.assertFalse(v.match("apache-2.0.4"))
+
+
+ def testMatchExpansion(self):
+ v = self.yvc.makeV("kdenetwork-3.0.4{,nb1} remote-root-shell http://www.kde.org/info/security/advisory-20021111-2.txt")
+ self.assertTrue(v.match("kdenetwork-3.0.4"))
+ self.assertTrue(v.match("kdenetwork-3.0.4nb1"))
+ self.assertFalse(v.match("kdenetwork-3.0.4nb2"))
+ self.assertFalse(v.match("kdenetwork-3.0.3nb2"))
+
+
+ def testMatchExpansionSmaller(self):
+ v = self.yvc.makeV("mozilla{,-bin,-gtk2,-gtk2-bin}<1.7.10 http-frame-spoof http://secunia.com/advisories/15601/")
+ self.assertTrue(v.match("mozilla-1.6"))
+ self.assertTrue(v.match("mozilla-bin-1.7.9"))
+ self.assertTrue(v.match("mozilla-gtk2-1.7"))
+ self.assertTrue(v.match("mozilla-gtk2-bin-1.7"))
+
+
+ def testMatchSimpleGreater(self):
+ v = self.yvc.makeV("gnupg-devel>=1.9.23 buffer-overflow http://lists.gnupg.org/pipermail/gnupg-announce/2006q4/000241.html")
+ self.assertTrue(v.match("gnupg-devel-1.9.23"))
+ self.assertTrue(v.match("gnupg-devel-1.9.23.1"))
+ self.assertTrue(v.match("gnupg-devel-1.9.24"))
+ self.assertFalse(v.match("gnupg-devel-1.9.22"))
+ self.assertFalse(v.match("gnupg-devel-1.9.22.100"))
+
+
+ def testMatchSubstring(self):
+ v = self.yvc.makeV("dia>=0.87 arbitrary-code-execution http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2006-1550")
+ self.assertFalse(v.match("dialog-1.0.20050911"))
+ self.assertTrue(v.match("dia-1.0.20050911"))
+
+
+ def testMatchExpandRange(self):
+ v = self.yvc.makeV("acroread{,5,7}-[0-9]* multiple-unspecified http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2008-0655")
+ self.assertTrue(v.match("acroread5-5.10nb1"))
+
+
+ def testMatchGreaterEqualAndSmaller(self):
+ v = self.yvc.makeV("php>=5<5.1.0 inject-smtp-headers http://cve.mitre.org/cgi-bin/cvename.cgi?name=CAN-2005-3883")
+ self.assertTrue(v.match("php-5.0.8"))
+ self.assertTrue(v.match("php-5"))
+ self.assertTrue(v.match("php-5.0.999999"))
+ self.assertFalse(v.match("php-4.99"))
+ self.assertFalse(v.match("php-5.1.0"))
+
+
+ def testMatchGreaterEqualAndSmallerEqual(self):
+ v = self.yvc.makeV("php>=5.1<=5.3.2 inject-smtp-headers http://cve.mitre.org/cgi-bin/cvename.cgi?name=CAN-2005-3883")
+ self.assertTrue(v.match("php-5.1"))
+ self.assertTrue(v.match("php-5.3.2"))
+ self.assertTrue(v.match("php-5.1.1"))
+ self.assertTrue(v.match("php-5.3.0"))
+ self.assertFalse(v.match("php-4.99"))
+ self.assertFalse(v.match("php-5.0.1"))
+ self.assertFalse(v.match("php-5.3.3"))
+ self.assertFalse(v.match("php-6"))
+
+
+ def testMatchPatchLevel(self):
+ v = self.yvc.makeV("python24<2.4nb4 remote-code-execution http://www.python.org/security/PSF-2005-001/")
+ # XXX: this currently fails since LooseVersion doesn't handle
+ # patchlevel as we'd expect
+ #self.assertFalse(v.match("python24-2.4.3nb3"))
+ #self.assertFalse(v.match("python24-2.4.1"))
+ self.assertTrue(v.match("python24-2.4"))
+ self.assertTrue(v.match("python24-2.4nb3"))
+ self.assertTrue(v.match("python24-2.4nb1"))
+
+
+ def testMatchPatchLevelReverse(self):
+ v = self.yvc.makeV("ruby18-base<1.8.6.114 access-validation-bypass http://preview.ruby-lang.org/en/news/2008/03/03/webrick-file-access-vulnerability/")
+ # XXX: this currently fails since LooseVersion doesn't handle
+ # patchlevel as we'd expect
+ #self.assertTrue(v.match("ruby18-base-1.8.6nb1"))
+ self.assertTrue(v.match("ruby18-base-1.8.6.113"))
+ self.assertFalse(v.match("ruby18-base-1.8.6.114"))
+ self.assertFalse(v.match("ruby18-base-1.8.6.115"))
+ self.assertFalse(v.match("ruby18-base-1.8.7"))
+
+
+if __name__ == '__main__':
+ unittest.main()