Skip to content

Instantly share code, notes, and snippets.

@thesp0nge
Last active March 1, 2022 11:03
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save thesp0nge/94f9d336a081a3fefba6ca61d787a28b to your computer and use it in GitHub Desktop.
Save thesp0nge/94f9d336a081a3fefba6ca61d787a28b to your computer and use it in GitHub Desktop.
A slightly hacked version of apt-check that takes care only about security packages that need an update.
#!/usr/bin/python3
#
# apt-security-check - paolo@codiceinsicuro.it
#
# A slightly hacked version of apt-check that takes care only about security
# packages that need an update.
#
# Tested on Ubuntu 16.04.5 LTS, 18.04.1 LTS
import apt # for progress bar
import apt_pkg
import lsb_release
import requests
from bs4 import BeautifulSoup
import socket
import os
import sys
# from optparse import OptionParser
import argparse
import gettext
DISTRO = lsb_release.get_lsb_information()['CODENAME'].rstrip().lower()
VERSION= "1.2"
def _(msg):
return gettext.dgettext("update-notifier", msg)
def _handleException(type, value, tb):
sys.stderr.write("E: " + _("Unknown Error: '%s' (%s)") % (type, value))
sys.exit(-1)
def clean(cache, depcache):
" unmark (clean) all changes from the given depcache "
# mvo: looping is too inefficient with the new auto-mark code
# for pkg in cache.Packages:
# depcache.MarkKeep(pkg)
depcache.init()
def saveDistUpgrade(cache, depcache):
""" this function mimics a upgrade but will never remove anything """
depcache.upgrade(True)
if depcache.del_count > 0:
clean(cache, depcache)
depcache.upgrade()
def isSecurityUpgrade(ver):
" check if the given version is a security update (or masks one) "
security_pockets = [("Ubuntu", "%s-security" % DISTRO),
("gNewSense", "%s-security" % DISTRO),
("Debian", "%s-updates" % DISTRO)]
for (file, index) in ver.file_list:
for origin, archive in security_pockets:
if (file.archive == archive and file.origin == origin):
return True
return False
def write_package_names(outstream, cache, depcache):
" write out package names that change to outstream "
pkgs = [pkg for pkg in cache.packages if depcache.marked_install(pkg) or
depcache.marked_upgrade(pkg)]
outstream.write("\n".join([p.name for p in pkgs]))
def write_full_changelog(outstream, packages):
if len(packages) == 0:
outstream.write("no security updates for %s\n" % socket.gethostname())
return
cache = apt_pkg.Cache()
dep_cache = apt_pkg.DepCache(cache)
outstream.write("security updates for %s\n" % socket.gethostname())
for p in packages:
pkg = cache[p]
inst_ver = pkg.current_ver
cand_ver = dep_cache.get_candidate_ver(pkg)
outstream.write("%s is going to be updated from version %s to version %s\n" % (pkg.name, inst_ver.ver_str, cand_ver.ver_str))
url="https://launchpad.net/" + lsb_release.get_lsb_information()['ID'].lower() + "/+source/" + pkg.name + "/" + cand_ver.ver_str
try:
r = requests.get(url)
data=r.text
soup = BeautifulSoup(data, "html.parser")
text=soup.find('pre', class_='changelog').get_text()
outstream.write("%s\n" % text)
except:
outstream.write("No changelog available at %s\n" % url)
def write_human_readable_summary(outstream, security_updates):
" write out human summary summary to outstream "
outstream.write(gettext.dngettext("update-notifier",
"%i update is a security update.",
"%i updates are security updates.",
security_updates) % security_updates)
outstream.write("\n")
def init():
" init the system, be nice "
# FIXME: do a ionice here too?
os.nice(19)
apt_pkg.init()
def run(options=None):
security_pkg_list=[]
# get caches
try:
cache = apt_pkg.Cache(apt.progress.base.OpProgress())
except SystemError as e:
sys.stderr.write("E: " + _("Error: Opening the cache (%s)") % e)
sys.exit(-1)
depcache = apt_pkg.DepCache(cache)
if depcache.broken_count > 0:
sys.stderr.write("E: " + _("Error: BrokenCount > 0"))
sys.exit(-1)
# do the upgrade (not dist-upgrade!)
try:
saveDistUpgrade(cache, depcache)
except SystemError as e:
sys.stderr.write("E: " + _("Error: Marking the upgrade (%s)") % e)
sys.exit(-1)
# we need another cache that has more pkg details
with apt.Cache() as aptcache:
for pkg in cache.packages:
# skip packages that are not marked upgraded/installed
if not (depcache.marked_install(pkg) or depcache.marked_upgrade(pkg)):
continue
# check if this is really a upgrade or a false positive
# (workaround for ubuntu #7907)
inst_ver = pkg.current_ver
cand_ver = depcache.get_candidate_ver(pkg)
if cand_ver == inst_ver:
continue
# check for security upgrades
if isSecurityUpgrade(cand_ver):
security_pkg_list.append(pkg.name)
continue
# now check for security updates that are masked by a
# candidate version from another repo (-proposed or -updates)
for ver in pkg.version_list:
if (inst_ver and apt_pkg.version_compare(ver.ver_str, inst_ver.ver_str) <= 0):
# print("skipping '%s' " % ver.VerStr)
continue
if isSecurityUpgrade(ver):
security_pkg_list.append(pkg.name)
break
return(security_pkg_list)
if __name__ == "__main__":
# setup a exception handler to make sure that uncaught stuff goes
# to the notifier
# sys.excepthook = _handleException
# gettext
APP = "update-notifier"
DIR = "/usr/share/locale"
gettext.bindtextdomain(APP, DIR)
gettext.textdomain(APP)
# check arguments
parser = argparse.ArgumentParser(description="A slightly hacked version of apt-check that takes care only about security packages that need an update.")
parser.add_argument("-c", "--full-changelog", action="store_true", dest="full_changelog", help=_("Prints a detailed information for each packed marked for a security update, with the target version and the changelog"))
parser.add_argument("-p", "--package-names", action="store_true", dest="show_package_names", help=_("Show the packages that are going to be installed/upgraded for security reasons"))
parser.add_argument("-H", "--human-readable", action="store_true", dest="readable_output", help=_("Show human readable output on stdout"))
parser.add_argument("-v", "--version", action="store_true", dest="version", help=_("Show version number"))
options = parser.parse_args()
if options.version:
print("%s" % VERSION)
sys.exit(1)
# run it
init()
packages=run(options)
if options and options.show_package_names:
if len(packages) == 0:
sys.stderr.write("There are no security updates\n")
else:
for pkg in packages:
sys.stderr.write(pkg)
sys.stderr.write("\n")
# write_package_names(sys.stderr, cache, depcache)
elif options and options.readable_output:
write_human_readable_summary(sys.stdout, len(packages))
elif options and options.full_changelog:
write_full_changelog(sys.stdout, packages)
else:
# print the number of security upgrades
sys.stderr.write("%s" % len(packages))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment