#!/usr/bin/python3
SVER = '3.0.0'
##############################################################################
# patgen.py - SCA Tool Python3 Pattern Generator
# Copyright (C) 2023 SUSE LLC
#
# Description:  Creates a pattern template for TIDs based on commandline
#               conditions.
# Modified:     2024 Feb 03
#
##############################################################################
#
#  This program is free software; you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation; version 2 of the License.
#
#  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, see <http://www.gnu.org/licenses/>.
#
#  Authors/Contributors:
#     Jason Record <jason.record@suse.com>
#
##############################################################################

import sys
import os
import re
import getopt
import signal
import datetime
import requests
import configparser
import patdevel as pd

# Global Options
title_string = "SCA Tool Python Pattern Generator"

# Class and Function Definitions

def usage():
	display = "  {:33s} {}"
	print("Usage:")
	print("  " + str(os.path.basename(__file__)) + " [OPTIONS] <class,category,component,filename,tid#[,bug#]>")
	print()
	print("Description:")
	print("  Used to create an initial python3 script template for an SCA pattern. Modify the template script")
	print("  to accurately identify the issue and record it on the SCA Report as needed.")
	print()
	print("  Ordering, Stacked: kernel > package > service > conditions")
	print("  Ordering, Flat:    kernel   package   service   conditions")
	print()
	print("  Documentation: /usr/share/doc/packages/sca-patterns-devel/index.html")
	print()
	print("OPTIONS")
	print(display.format("-h, --help", "Display this help"))
	print(display.format("-o, --no-validation", "Ignore invalid solution links"))
	print(display.format("-d, --no-duplicates", "Don't check for duplicate patterns"))
	print(display.format("-g, --gen", "Selet pattern template generation. Default: 2, Valid: 1 or 2"))
	print(display.format("-c <0-3>, --conditions=<0-3>", "Number of conditional functions to include, default=0"))
	print(display.format("-k <ver>, --kernel-version=<ver>", "The kernel's version where the issue is fixed"))
	print(display.format("-r <name>, --rpm=<name>", "The affected RPM package name"))
	print(display.format("-p <ver>, --package-version=<ver>", "The package's version where the issue is fixed"))
	print(display.format("-s <name>, --service=<name>", "The systemd service name affected"))
	print(display.format("-u <[tag=]url>, --url=<[tag=]url>", "Additional solution link URL. You can also enter a CVE number like \"CVE-2022-23303\""))
	print(display.format("-f, --flat", "All requested conditions are tested independently and not included in stacked order"))
	print(display.format('-l <level>, --log_level <level>', "Set log level, default: Minimal"))
	print(display.format('', "0 Quiet, 1 Minimal, 2 Normal, 3 Verbose, 4 Debug"))
	print()
	print("METADATA")
	print("  class:        SLE,HAE,SUMA,Security,Custom")
	print("  category:     Category name string")
	print("  component:    Component name string")
	print("  filename:     Pattern filename (TID number will be added automatically)")
	print("  tid#:         TID number only")
	print("  bug#:         Bug number only (optional)")
	print()

def option_error(msg):
	print(msg)
	print()
	usage()
	sys.exit(1)

def signal_handler(sig, frame):
	print("\n\nAborting...\n")
	sys.exit(1)

##############################################################################
# Main
##############################################################################

def main(argv):
	"main entry point"
	global SVER, title_string

	given_conditions = 0
	conditions_min = 1
	conditions_max = 3
	metadata_min = 5
	metadata_max = 6
	package_name = ''
	package_version = '0'

	if( os.path.exists(pd.config_file) ):
		config.read(pd.config_file)
		url_base = pd.config_entry(config.get("Security", "archive_url"))
		pat_dir = pd.config_entry(config.get("Security", "pat_dir"), '/')
		pat_logs_dir = pd.config_entry(config.get("Security", "pat_logs"), '/')
	else:
		pd.title(title_string, SVER)
		print("Error: File not found - " + pd.config_file + "\n")
		sys.exit(1)

	pat = pd.PatternTemplate(title_string, SVER, config, msg)

	try:
		(optlist, args) = getopt.gnu_getopt(argv[1:], "hc:dg:fk:r:p:s:u:ol:", ["help", "conditions=", "no-duplicates", "gen=", "flat", "kernel-version=", "rpm=", "package-version=", "service=", "url=", "no-validation", "log_level="])
	except getopt.GetoptError as exc:
		pd.title(title_string, SVER)
		print("Error:", exc, file=sys.stderr)
		sys.exit(2)
	for opt, arg in optlist:
		if opt in {"-h", "--help"}:
			pd.title(title_string, SVER)
			usage()
			sys.exit(0)
		elif opt in {"-c", "--conditions"}:
			if( arg.isdigit() ):
				given_conditions = int(arg)
				if( given_conditions > conditions_max ):
					pd.title(title_string, SVER)
					opt_error("Error: Invalid number of conditions, range is 0-3")
				else:
					pat.set_conditions(given_conditions)
			else:
				pd.title(title_string, SVER)
				opt_error("Error: Integer required for conditions, range is 0-3")
		elif opt in {"-d", "--no-duplicates"}:
			pat.set_check_duplicates(False)
		elif opt in {"-g", "--gen"}:
			pat.set_generation(arg)
		elif opt in {"-f", "--flat"}:
			pat.set_flat(True)
		elif opt in {"-k", "--kernel-version"}:
			pat.set_kernel(arg)
		elif opt in {"-r", "--rpm"}:
			package_name = arg
		elif opt in {"-p", "--package-version"}:
			package_version = arg
		elif opt in {"-s", "--service"}:
			pat.set_service(arg)
		elif opt in {"-u", "--url"}:
			pat.set_other_url(arg)
		elif opt in {"-o", "--no-validation"}:
			pat.set_override_validation(True)
		elif opt in {"-l", "--log_level"}:
			user_logging = msg.validate_level(arg)
			if( user_logging >= msg.LOG_QUIET ):
				msg.set_level(user_logging)
			else:
				print("Warning: Invalid log level, using instance default")

	if( msg.get_level() > msg.LOG_QUIET ):
		pd.title(title_string, SVER)

	msg.normal("Log Level", msg.get_level_str())

	if( len(package_name) > 0 ): # package_name without a package_version is ok
		pat.set_package(package_name, package_version)
	elif( len(package_version) > 1 ): # package_version without a package_name is not allowed
			option_error("Error: Missing affected RPM package name")

	if len(args) > 0:
		given_metadata = args[0].split(",")
		given_metadata_count = len(given_metadata)
		if( given_metadata_count < metadata_min ):
			option_error("Error: Insufficent metadata elements")
		elif( given_metadata_count > metadata_max ):
			option_error("Error: Too many metadata elements")
	else:
		option_error("Error: Missing pattern metadata")

	pat.set_metadata(given_metadata)
	pat.create_pattern()
	pat.show_summary()

# Entry point
if __name__ == "__main__":
	signal.signal(signal.SIGINT, signal_handler)
	config = configparser.ConfigParser(interpolation=configparser.ExtendedInterpolation())
	msg = pd.DisplayMessages()
	main(sys.argv)


