#!/usr/bin/env python3
#
# Copyright (c) 2018 by Cisco Systems, Inc.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 as
# published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#

""" Generate CVE report for the given CVE manifest
"""

import sys
import textwrap
import argparse
import logging
import logging.config
import cvert

def report_foss():
    """Generate CVE report"""

    parser = argparse.ArgumentParser(
        formatter_class=argparse.RawDescriptionHelpFormatter,
        description=textwrap.dedent("""
        Generate CVE report for the given CVE manifest.
        """),
        epilog=textwrap.dedent("""
        @ run examples:

        # Download (update) NVD feeds in "nvdfeed" directory
        # and prepare the report for the "cve-manifest" file
        %% %(prog)s --feed-dir nvdfeed --output report-foss.txt cve-manifest

        # Use existed NVD feeds in "nvdfeed" directory
        # and prepare the report for the "cve-manifest" file
        %% %(prog)s --offline --feed-dir nvdfeed --output report-foss.txt cve-manifest

        # (faster) Restore CVE dump from "cvedump" (must exist)
        # and prepare the report for the "cve-manifest" file
        %% %(prog)s --restore cvedump --output report-foss.txt cve-manifest

        # Restore CVE dump from "cvedump" (must exist)
        # and prepare the extended report for the "cve-manifest" file
        %% %(prog)s --restore cvedump --show-description --show-reference --output report-foss.txt cve-manifest

        @ manifest example:

        bash,4.2,CVE-2014-7187
        python,2.7.35,
        python,3.5.5,CVE-2017-17522 CVE-2018-1061

        @ report example output:

        . patched | 10.0 | CVE-2014-7187      | bash | 4.2
        . patched |  7.5 | CVE-2018-1061      | python | 3.5.5
        . patched |  8.8 | CVE-2017-17522     | python | 3.5.5
        unpatched | 10.0 | CVE-2014-6271      | bash | 4.2
        unpatched | 10.0 | CVE-2014-6277      | bash | 4.2
        unpatched | 10.0 | CVE-2014-6278      | bash | 4.2
        unpatched | 10.0 | CVE-2014-7169      | bash | 4.2
        unpatched | 10.0 | CVE-2014-7186      | bash | 4.2
        unpatched |  4.6 | CVE-2012-3410      | bash | 4.2
        unpatched |  8.4 | CVE-2016-7543      | bash | 4.2
        unpatched |  5.0 | CVE-2010-3492      | python | 2.7.35
        unpatched |  5.3 | CVE-2016-1494      | python | 2.7.35
        unpatched |  6.5 | CVE-2017-18207     | python | 3.5.5
        unpatched |  6.5 | CVE-2017-18207     | python | 2.7.35
        unpatched |  7.1 | CVE-2013-7338      | python | 2.7.35
        unpatched |  7.5 | CVE-2018-1060      | python | 3.5.5
        unpatched |  8.8 | CVE-2017-17522     | python | 2.7.35
        """))

    group = parser.add_mutually_exclusive_group(required=True)
    group.add_argument("-f", "--feed-dir", help="feeds directory")
    group.add_argument("-d", "--restore", help="load CVE data structures from file",
                       metavar="FILENAME")
    parser.add_argument("--offline", help="do not update from NVD site",
                        action="store_true")
    parser.add_argument("-o", "--output", help="save report to the file")
    parser.add_argument("--show-description", help='show "Description" in the report',
                        action="store_true")
    parser.add_argument("--show-reference", help='show "Reference" in the report',
                        action="store_true")
    parser.add_argument("--debug", help="print debug messages",
                        action="store_true")

    parser.add_argument("cve_manifest", help="file with a list of packages, "
                        "each line contains three comma separated values: name, "
                        "version and a space separated list of patched CVEs, "
                        "e.g.: python,3.5.5,CVE-2017-17522 CVE-2018-1061",
                        metavar="cve-manifest")

    args = parser.parse_args()

    logging.config.dictConfig(cvert.logconfig(args.debug))

    cve_manifest = {}

    with open(args.cve_manifest, "r") as fil:
        for lin in fil:
            lin = lin.rstrip()

            # skip empty lines
            if not lin:
                continue

            product, version, patched = lin.split(",", maxsplit=3)

            if product in cve_manifest:
                cve_manifest[product][version] = patched.split()
            else:
                cve_manifest[product] = {
                    version: patched.split()
                }

    if args.restore:
        cve_struct = cvert.load_cve(args.restore)
    elif args.feed_dir:
        cve_struct = cvert.update_feeds(args.feed_dir, args.offline)

    if not cve_struct and args.offline:
        parser.error("No CVEs found. Try to turn off offline mode or use other file to restore.")

    if args.output:
        output = open(args.output, "w")
    else:
        output = sys.stdout

    report = cvert.generate_report(cve_manifest, cve_struct)

    cvert.print_report(report,
                       show_description=args.show_description,
                       show_reference=args.show_reference,
                       output=output)

    if args.output:
        output.close()


if __name__ == "__main__":
    report_foss()
