Last active
March 1, 2022 11:03
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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