Commit 58cd288f authored by David Spencer's avatar David Spencer

pkgsrc: Implement check.

parent fc04005e
......@@ -113,12 +113,11 @@ relevant 'check' script.
## TODO
* slackware-stable
* pkgsrc check
* USN update and check
* slackware-stable package list
* installed packages on the current host
* Debian vuln list update and check
* Combine reports into one big report
* Notifications
* Documentation
* 'patched' and 'ignored'
* Package name aliases
* installed packages on the current host
#!/bin/sh
# BadNews vulnerability monitor
# Check a package list against the pkgsrc database
#!/usr/bin/python3
"""
BadNews vulnerability monitor
Check a package list against the pkgsrc database
"""
#-----------------------------------------------------------------------
set -eu
import sys
import os
import re
import fnmatch
REPORTNAME="${REPORTNAME:-report}"
import braceexpand # https://pypi.org/project/braceexpand/
#-----------------------------------------------------------------------
# Pathnames.
echo "pkgsrc check is currently unimplemented." | tee "$REPORTNAME".txt
vulnlistpath="pkg-vulnerabilities.txt"
if len(sys.argv) >= 2:
pkglistpath=sys.argv[1]
else:
sys.exit("Argument missing")
reportpath=os.environ.get("REPORTNAME","report")+".csv"
#-----------------------------------------------------------------------
# Read the package list and store it in 'plist'.
#
# We store the package list and match the vulnerability list against it,
# instead of the other way round, mostly because the package names in
# the package list are plain strings (the vulnerability list uses globs).
# Also, the package list has a simpler structure and is likely to be
# shorter than the vulnerability list.
pkglistfile=open(pkglistpath,'r')
class Pkg:
"""
Structure describing a single package record
"""
def __init__(p, pkgnam, pkgver, patched, ignored):
p.name = pkgnam
p.version = pkgver
p.patched = patched
p.ignored = ignored
plist=[]
# A simple list of Pkg instances
# (don't use a dictionary -- we don't do lookups, and there may be
# multiple versions of a package)
recnum=0
for pkgrec in pkglistfile:
recnum=recnum+1
try:
pkgnam, pkgver, pkgpatched, pkgignored = pkgrec.rstrip().split(",")
except:
print("Invalid pkglist format, line {:d}".format(recnum),file=sys.stderr)
continue
plist.append(Pkg(pkgnam,pkgver,pkgpatched,pkgignored))
pkglistfile.close()
print("Processed {:d} records from {:s}".format(recnum,pkglistpath))
#-----------------------------------------------------------------------
# Read and match the vulnerability list.
vulnlist=open(vulnlistpath,'r')
report=open(reportpath,'w')
# The pkgsrc pkg-vulnerabilities.txt file has the record format
# <vexpr><space><vcategory><space><vurl>
# where
# <vexpr> is a conditional expression to match a package name and
# version (see below). The values are not unique.
# <vcategory> is a text string describing the type of vulnerability
# <vurl> is a URL where more info can be found
# <space> is a single space character
#
# The first field <vexpr> is one of the following:
# <vbrace><cond1><cond2>
# <vbrace><cond1>
# <vbrace>
# where
# <vbrace> is a shell-style glob possibly needing brace expansion
# <cond> is a condition, comprising <op><version>, where
# <op> is one of < <= = >= >
# <version> is a simple version string (package-specific format)
class Cond():
"""
A condition that comprises an operator <op> and a version <ver>.
"""
def __init__(c, op, version):
c.op = op
c.version = version
def printvulns(vglob,vcondlist,vcategory,vurl):
"""
Check all packages 'p' in the global 'plist'
Print a report record if
* 'p.name' =~ 'vglob', and
* 'p.version' 'vcond.op' 'vcond.version', for all vcond in vcondlist
"""
exit 0
for p in plist:
# explictly compare in lower-case, because we want to be case
# insensitive, but these aren't filenames and we don't want
# to be at the mercy Python being "clever" about the host OS
matched = fnmatch.fnmatchcase(p.name.lower(),vglob.lower())
if matched:
vulnerable=True
for vcond in vcondlist:
if vcond.op == "<=":
matched = matched and p.version <= vcond.version
elif vcond.op == ">=":
matched = matched and p.version >= vcond.version
elif vcond.op == "<":
matched = matched and p.version < vcond.version
elif vcond.op == "=":
matched = matched and p.version == vcond.version
elif vcond.op == ">":
matched = matched and p.version > vcond.version
else:
# silently drop the condition
continue
if matched:
# we have a vulnerability!
print("{:s},{:s},{:s},{:s},{:s},{:s}".format(
p.name, p.version, p.patched, p.ignored,
vcategory, vurl))
recnum=0
for vrec in vulnlist:
recnum=recnum+1
try:
vexpr, vcategory, vurl = vrec.rstrip().split(" ")
except:
print("Invalid vulnlist format, line {:d}".format(recnum),file=sys.stderr)
continue
vexprchunks=re.split("(<=|>=|<|=|>)",vexpr)
numchunks=len(vexprchunks)
if numchunks == 1:
vcondlist=[]
elif numchunks == 3:
vcondlist=[Cond(vexprchunks[1],vexprchunks[2])]
elif numchunks == 5:
vcondlist=[Cond(vexprchunks[1],vexprchunks[2]), Cond(vexprchunks[3],vexprchunks[4])]
else:
print("Invalid vulnlist format, line {:d}".format(recnum),file=sys.stderr)
continue
for vglob in list(braceexpand.braceexpand(vexprchunks[0])):
printvulns(vglob,vcondlist,vcategory,vurl)
vulnlist.close()
print("Processed {:d} records from {:s}".format(recnum,vulnlistpath))
report.close()
print("Wrote "+reportpath)
#-----------------------------------------------------------------------
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment