#!/usr/bin/env python2

# Author: Jamie Strandboge <jamie@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.
from __future__ import print_function

import cve_lib
import datetime
import re
import sys
import optparse
import random
import signal
import subprocess

def subprocess_setup():
    # Python installs a SIGPIPE handler by default. This is usually not what
    # non-Python subprocesses expect.
    signal.signal(signal.SIGPIPE, signal.SIG_DFL)

def cmd(command, input = None, stderr = subprocess.STDOUT, stdout = subprocess.PIPE, stdin = None, timeout = None):
    '''Try to execute given command (array) and return its stdout, or return
    a textual error if it failed.'''

    try:
        sp = subprocess.Popen(command, stdin=stdin, stdout=stdout, stderr=stderr, close_fds=True, preexec_fn=subprocess_setup)
    except OSError as e:
        return [127, str(e)]

    out, outerr = sp.communicate(input)
    # Handle redirection of stdout
    if out == None:
        out = ''
    # Handle redirection of stderr
    if outerr == None:
        outerr = ''
    return [sp.returncode, out+outerr]

def cmd_pipe(command1, command2, input = None, stderr = subprocess.STDOUT, stdin = None):
    '''Try to pipe command1 into command2.'''
    try:
        sp1 = subprocess.Popen(command1, stdin=stdin, stdout=subprocess.PIPE, stderr=stderr, close_fds=True)
        sp2 = subprocess.Popen(command2, stdin=sp1.stdout, stdout=subprocess.PIPE, stderr=stderr, close_fds=True)
    except OSError as e:
        return [127, str(e)]

    out = sp2.communicate(input)[0]
    return [sp2.returncode, out]

def is_candidate(s):
    '''Determine if this is a candidate'''

    if re.search(r' (1|2) total,', s) == None:
        # too many CVEs
        return False

    if '1 total, 1 negligible' in s:
        # skip 1 negligible
        return False

    if re.search(r' (high|critical)', s.split(':')[0]):
        # high and higher is good
        return True

    return True

parser = optparse.OptionParser()
parser.add_option("-a", "--all-releases", help="Show suggestions in all releases, not just the latest stable and last LTS", action="store_true")
parser.add_option("-m", "--only-supported", help="Show only those CVEs that are supported", action="store_true")
parser.add_option("-u", "--not-supported", help="Show only those CVEs that aren't supported", action="store_true")
parser.add_option("-d", "--debug", help="Report debug information while loading", action="store_true")
parser.add_option("-w", "--format-wiki", help="Wiki format", action="store_true")
parser.add_option("-n", "--number", help="Number of suggestions")
(opt, args) = parser.parse_args()

releases = cve_lib.releases
if not opt.all_releases:
    # should contain most recent LTS and most recent non-LTS
    releases = ['focal', 'groovy']

cp_args = []
for r in releases:
    cp_args.append('-r')
    cp_args.append(r)

if opt.only_supported:
    cp_args.append('-m')
elif opt.not_supported:
    cp_args.append('-u')

ignored_args = [
                '-X', 'xulrunner',
                '-X', 'xulrunner-1.9',
                '-X', 'xulrunner-1.9.1',
                '-X', 'xulrunner-1.9.2',
                '-X', 'xulrunner-2.0',
               ]

# For now, just parse cve_packages output
rc, report = cmd(['./scripts/cve_packages'] + cp_args + ignored_args)

if rc != 0:
    print("Error running cve_packages", file=sys.stderr)
    print(report, file=sys.stderr)
    sys.exit(rc)

lines = report.splitlines()
maxind = len(lines)-1

number = 5
if opt.number:
     try:
         number = int(opt.number)
     except:
         print("WARN: ignoring '-n'. Should be a number. Defaulting to %d" % number, file=sys.stderr)
         pass
chosen = []
while len(chosen) < number:
    tmp = lines[random.randint(0, maxind)]
    if re.search(r'^[0-9]', tmp) and is_candidate(tmp) and tmp not in chosen:
        chosen.append(tmp)

if opt.format_wiki:
    # IMPORTANT: Do not edit the below line as it is used by
    # MeetingLogs/Security/*
    print("==== Highlighted packages: ====")

for e in chosen:
    if opt.debug:
        print("DEBUG: %s" % e, file=sys.stderr)

    pkg = e.split(':')[0].split()[1]
    rc, report = cmd(['./scripts/pkg_status', pkg])
    if rc != 0:
        print("Error running pkg_status", file=sys.stderr)
        print(report, file=sys.stderr)
        sys.exit(rc)

    cves = []
    for line in report.splitlines():
        if not line.startswith('CVE'):
            continue
        # pkg_status gives us all the CVEs for a package, not just the ones for
        # the releases, so only include ones with for our releases that are
        # open
        cve = line.split(':')[0].split()[0]
        fn = '%s/%s' % (cve_lib.active_dir, cve)
        cve_data = cve_lib.load_cve(fn)
        for r in releases:
            if pkg not in cve_data['pkgs']:
                print("Error: could not find '%s' in '%s'" % (pkg, fn), file=sys.stderr)
                sys.exit(1)
            if r not in cve_data['pkgs'][pkg]:
                continue

            state, notes = cve_data['pkgs'][pkg][r]
            closed = ['released', 'not-affected', 'ignored', 'pending']
            if state not in closed:
                cves.append(cve)
                break

    if opt.debug:
        print("DEBUG: %s" % report, file=sys.stderr)

    if opt.format_wiki:
        print(" * [[https://people.canonical.com/~ubuntu-security/cve/pkg/%s.html|%s]]:" % (pkg, pkg), end=' ')
        for c in cves:
            year = c.split('-')[1]
            print("[[https://people.canonical.com/~ubuntu-security/cve/%s/%s.html|%s]]" % (year, c, c), end=' ')
        print("")
    else:
        print("%s: %s" % (pkg, " ".join(cves)))

if opt.format_wiki:
    # IMPORTANT: Do not edit the below line as it is used by
    # SecurityTeam/GettingInvolved and MeetingLogs/Security/*
    print("Last updated: %s-%0.2d-%0.2d" % (datetime.date.today().year, int(datetime.date.today().month), int(datetime.date.today().day)))
