#!/usr/bin/env python2

# This script allows you to provide a fix version for a particulare package/CVE combination and it will update the CVE
# based on a comparison of the release version against the fix version.
#
# Example: 
#
# English Translation: Set the status in CVE-2013-4116 for the package "npm" to "not-affected (RELEASE_VERSION)" where
#                      the fix version 1.3.10~dfsg-1 is less-than-or-equal-to the release version.
#
# $> ./scripts/set-status-by-version --p npm -f 1.3.10~dfsg-1 -c CVE-2013-4116 -b le -s "not-affected"
#    trusty: 1.3.10~dfsg-1, Pocket: release, Component: universe
#    CVE-2013-4116... 
#      trusty_npm updated
#    
#    xenial: 3.5.2-0ubuntu4, Pocket: release, Component: universe
#    CVE-2013-4116... 
#      xenial_npm updated
#    
#    bionic: 3.5.2-0ubuntu4, Pocket: release, Component: universe
#    CVE-2013-4116... 
#      bionic_npm updated
#    
#    cosmic: 3.5.2-0ubuntu4, Pocket: release, Component: universe
#    CVE-2013-4116... 
#      cosmic_npm updated
#
# $> git diff active/CVE-2013-4116
#
# diff --git a/active/CVE-2013-4116 b/active/CVE-2013-4116
# index b1e37492f2..f661f8185d 100644
# --- a/active/CVE-2013-4116
# +++ b/active/CVE-2013-4116
# @@ -24,15 +24,15 @@ precise/esm_npm: DNE (precise was needs-triage)
#  quantal_npm: ignored (reached end-of-life)
#  raring_npm: ignored (reached end-of-life)
#  saucy_npm: ignored (reached end-of-life)
# -trusty_npm: needs-triage
# +trusty_npm: not-affected (1.3.10~dfsg-1)
#  utopic_npm: ignored (reached end-of-life)
#  vivid_npm: ignored (reached end-of-life)
#  vivid/stable-phone-overlay_npm: DNE
#  vivid/ubuntu-core_npm: DNE
#  wily_npm: ignored (reached end-of-life)
# -xenial_npm: needs-triage
# +xenial_npm: not-affected (3.5.2-0ubuntu4)
#  yakkety_npm: ignored (reached end-of-life)
#  zesty_npm: ignored (reached end-of-life)
#  artful_npm: ignored (reached end-of-life)
# -bionic_npm: needs-triage
# -devel_npm: needs-triage
# +bionic_npm: not-affected (3.5.2-0ubuntu4)
# +devel_npm: not-affected (3.5.2-0ubuntu4)


import optparse
import subprocess
import sys

def die_on_missing_argument(opt):
    if not opt.package:
        parser.error("You must supply a package with the '-p' or '--package' flag")
        sys.exit(1)
    if not opt.fixed_version:
        parser.error("You must supply a fixed version with the '-f' or '--fixed-version' flag")
        sys.exit(1)
    if not opt.cve:
        parser.error("You must supply a cve with the '-c' or '--cve' flag")
        sys.exit(1)

def get_devel_release_name():
    command = ['distro-info', '--devel']
    p = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    command_output=p.stdout.read()
    retcode=p.wait();
    if retcode != 0:
        print("An unknown error occurred when running `distro-info --devel`")
        sys.exit(1)

    return command_output.strip()

def run_umt_search(package):
    command = ['umt', 'search', package]

    p = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    command_output=p.stdout.read()
    retcode=p.wait();

    umt_search_output = command_output.splitlines()
    if len(umt_search_output) == 7  or retcode != 0:
        print("An unknown error occurred when running `umt search " + package + "`");
        sys.exit(1);

    return umt_search_output

def compare_versions(fixed_version, binary_operator, release_version):
    command = ['dpkg', '--compare-versions', fixed_version, binary_operator, release_version]
    p = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    return p.wait();

def run_mass_cve_edit(package, ubuntu_release, state, release_version, cve):
    command = ['scripts/mass-cve-edit', '-p', package, '-r', ubuntu_release, '-s', state, '-v', release_version, cve]
    p = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    command_output=p.stdout.read()
    retcode=p.wait();
    print command_output
    if retcode != 0:
        print("An unknown error occurred when running `mass_cve_edit`")
        sys.exit(1)



parser = optparse.OptionParser()
parser.add_option("-p", "--package", help="The package that the CVE applies to", action="store", type="string")
parser.add_option("-s", "--state", help="The state of the package (see scripts/mass-cve-edit for more details). Default: 'not-affected'", action="store", type="string")
parser.add_option("-f", "--fixed-version", help="The version of the package that contains the fix (is not vulnerable)", action="store", type="string")
parser.add_option("-b", "--binary-operator", help="A binary operator that will compare the ubuntu release version of the package to the fixed version (see the dpkg(1) man page for more details). Default: 'lt'", action="store", type="string")
parser.add_option("-c", "--cve", help="The CVE that will be updated", action="store", type="string")
(opt, args) = parser.parse_args()
die_on_missing_argument(opt)

package = opt.package
state = opt.state
fixed_version = opt.fixed_version
cve = opt.cve
binary_operator = "lt"
state = 'not-affected'
devel_release = get_devel_release_name()

if opt.binary_operator:
    binary_operator = opt.binary_operator

if opt.state:
    state = opt.state

umt_search_output = run_umt_search(package)
umt_search_output = umt_search_output[4:]

for release in umt_search_output:
    if len(release) == 0: #this is a blank line, there are no more ubuntu versions to review
        break
    print release
    ubuntu_release = release.split(',')[0].split(':')[0].strip()
    release_version = release.split(',')[0].split(':')[1].strip()
    version_comparison_result = compare_versions(fixed_version, binary_operator, release_version)
    if version_comparison_result == 0:
        if ubuntu_release == devel_release:
            ubuntu_release = 'devel'
        run_mass_cve_edit(package, ubuntu_release, state, release_version, cve)
