#!/usr/bin/env python3
# Author: Kees Cook <kees@ubuntu.com>
# Author: Marc Deslauriers <marc.deslauriers@ubuntu.com>
# Copyright (C) 2011,2012 Canonical Ltd.
#
# This script is distributed under the terms and conditions of the GNU General
# Public License, Version 2 or later. See http://www.gnu.org/copyleft/gpl.html
# for details.
#
# Will report which CVEs are pending to be fixed (or marked released) since
# a given version of a specific package.

import argparse
import cve_lib
import sys
from source_map import version_compare

parser = argparse.ArgumentParser(description="Report which CVEs are pending to be fixed (or marked released) since a given version of a specific package")
parser.add_argument("-r", "--release", help="Specify comma-separated list of which release to limit the search to (default is all)")
parser.add_argument("-d", "--debug", help="Report additional debugging while processing", action='store_true')
parser.add_argument("-s", "--states", help="Report CVE states with each CVE", action='store_true')
parser.add_argument("-D", "--descriptions", help="Report USN descriptions with each CVE (exit 1 when missing some)", action='store_true')
parser.add_argument("-a", "--add-cves", help="Add specific CVEs to the report (comma-separated)", action='store', metavar='CVE[,CVE...]', default="")
parser.add_argument("-n", "--no-warn", help="Don't warn about pending CVE version mismatch with passed kernel version", dest='do_warn', action='store_false', default=True, )
parser.add_argument("-f", "--fixes", help="Report fixes associated with the package", action='store_true')
parser.add_argument("pkg", metavar="PACKAGE", help="Source package to query about")
parser.add_argument("prev_version", metavar="PREVIOUS_VERSION", help="Previous released version of PACKAGE")
parser.add_argument("curr_version", metavar="CURRENT_VERSION", help="Current or pending version of PACKAGE")
opt = parser.parse_args()

releases = None
if opt.release:
    releases = opt.release.split(',')

forced_cves = set()
if len(opt.add_cves) > 0:
    for cve in opt.add_cves.split(','):
        forced_cves.add(cve)

active, embargoed = cve_lib.get_cve_list()
cves = cve_lib.load_all(active, embargoed)

rc = 0
reportable = set(['pending', 'released', 'released-esm'])
fixed = set()
warn_version_mismatch = False
for name in cves:
    cve = cves[name]
    if opt.pkg not in cve['pkgs']:
        continue
    for rel in cve['pkgs'][opt.pkg]:
        if ((rel in ['upstream', 'product', 'snap']) or
            (releases and rel not in releases)):
            continue

        state, version = cve['pkgs'][opt.pkg][rel]
        if state not in reportable:
            continue
        if version_compare(opt.prev_version, version) >= 0 and name not in forced_cves:
            continue
        if version_compare(version, opt.curr_version) > 0 and name not in forced_cves:
            continue

        report = name

        if opt.states:
            report += " %s %s %s %s" % (opt.pkg, rel, state, version)

        if opt.fixes:
            patch_section = 'Patches_%s' % ('linux' if opt.pkg in cve_lib.kernel_srcs else opt.pkg)
            if patch_section in cve:
                for line in cve[patch_section].split('\n'):
                    line.strip()
                    if line.lstrip().startswith('break-fix:'):
                        report += '\n %s' % line.split()[2]

        if opt.descriptions:
            desc = cve['Ubuntu-Description'].strip()
            if len(desc):
                desc = " " + "\n ".join(desc.split('\n'))
            else:
                desc = " [XXX-NEEDED-XXX]"
                rc = 1
            report += "\n" + desc

            # issue a warning if pending/released version is not the same as
            # the current version
            if version_compare(version, opt.curr_version) < 0 and opt.do_warn and name not in forced_cves:
                report += "\n[== WARNING version %s is older than %s WARNING ==]\n" % (version, opt.curr_version)
                warn_version_mismatch = True

        fixed.add(report)

for cve in sorted(fixed):
    print(cve)

if opt.do_warn and warn_version_mismatch:
    print("========================================================")
    print("Mismatch between expected versions and pending versions,")
    print("Is this an embargoed security update?")

sys.exit(rc)
