#!/usr/bin/python -E

#Author: Josh Adams <jadams@tresys.com>
#
# Copyright (C) 2010 Tresys Technology, LLC
#
#  This library 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; either
#  version 2 of the License, or (at your option) any later version.
#
#  This library 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
#  Lesser General Public License for more details.
#
#  You should have received a copy of the GNU Lesser General Public
#  License along with this library; if not, write to the Free Software
#  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
#
#  File: secstate.py
#  This file is deals with the command line interface of the secstate tool.

import sys
import ConfigParser
import os
from optparse import OptionParser

import openscap_api as oscap

import secstate

CONFIG_FILE='/etc/secstate/secstate.conf'

def usage():
    usage_str = """
%s <sub-command> [options]
Sub-commands:
    import          --  Validates and imports a benchmark and its associated OVAL into the system
    remove          --  Removes content that had been added via the 'import' command
    export          --  Exports imported content along with any changes that have been made
    select          --  Sets a portion of the benchmark (group/rule) to be selected
    deselect        --  Sets a portion of the benchmark (group/rule) to be deselected
    save            --  Saves changes made to the current benchmark to a named profile
    list            --  List the imported benchmarks
    show            --  Shows the information associated with a group, rule, or definition id
    search          --  Search through imported content
    remediate       --  Bring the local system into compliance with a benchmark
    audit           --  Audit the local system based the specified XCCDF/OVAL content\n""" % sys.argv[0]
    sys.stderr.write(usage_str)

sec_instance = secstate.Secstate(CONFIG_FILE)

def main():
    
    try:
        subcommand = sys.argv[1]
    except IndexError, e:
        usage()
        return -1

    arg_num = 2
    if (subcommand == '-c') or (subcommand == '--config'):
        if not sec_instance.setConfigFile(sys.argv[arg_num]):
            usage()
            return -1
        arg_num += 2
        try:
            subcommand = sys.argv[arg_num - 1]
        except IndexError:
            usage()
            return -1

    if subcommand == 'help':
        return help(sys.argv[arg_num:])

    elif subcommand == 'export':
        return export(sys.argv[arg_num:])

    elif subcommand == 'search':
        return search(sys.argv[arg_num:])

    elif subcommand == 'list':
        return list_content(sys.argv[arg_num:])

    elif subcommand == 'show':
        return show(sys.argv[arg_num:])

    elif os.geteuid() != 0:
        sys.stderr.write("secstate must be run as root!\n")
        return -1

    elif subcommand == 'import':
        return import_content(sys.argv[arg_num:])

    elif subcommand == 'remove':
        return remove_content(sys.argv[arg_num:])

    elif subcommand == 'select':
        return select(sys.argv[arg_num:], True)

    elif subcommand == 'deselect':
        return select(sys.argv[arg_num:], False)

    elif subcommand == 'audit':
        return audit(sys.argv[arg_num:])
    
    elif subcommand == 'remediate':
        return remediate(sys.argv[arg_num:])

    elif subcommand == 'mitigate':
        return mitigate(sys.argv[arg_num:])
        
    elif subcommand == 'save':
        return save_profile(sys.argv[arg_num:])

    else:
        sys.stderr.write("Uknown subcommand: %(command)s" % {'command':subcommand})
        usage()
        return -1

def help(arg):
    if arg == []:
        usage()
    elif arg[0] == 'import':
        import_content(['-h'])
    elif arg[0] == 'export':
        export(['-h'])
    elif arg[0] == 'remove':
        remove(['-h'])
    elif arg[0] == 'select':
        select(['-h'], None)
    elif arg[0] == 'deselect':
        select(['-h'], None)
    elif arg[0] == 'audit':
        audit(['-h'])
    elif arg[0] == 'search':
        search(['-h'])
    elif arg[0] == 'list':
        list_content(['-h'])
    elif arg[0] == 'show':
        show(['-h'])
    elif arg[0] == 'remediate':
        remediate(['-h'])
    elif arg[0] == 'save':
        save_profile(['-h'])
    else:
        sys.stderr.write("Unknown subcommand '%(command)s'\n" % {'command':arg[0]})
        usage()

    return 0

def import_content(arguments):
    parser = OptionParser(usage="secstate import [options] <ContentFile>")
    parser.add_option('-p', '--puppet', action='store_true', dest='puppet', default=False,
                      help="Imports the specified puppet content")
    parser.add_option('--profile', action='store', type='string', dest='profile', default=secstate.NONE_PROFILE,
                      help="Selects the active profile")
    (options, args) = parser.parse_args(arguments)

    for arg in args:
        content = sec_instance.import_content(arg, options.puppet, save=True, active_profile=options.profile)
        if content == None:
            return -1

def export(arguments):
    parser = OptionParser(usage="secstate export [options] <ContentID> <OutputFile>")
    parser.add_option('-o', '--original', action='store_true', dest='original', default=False,
                      help="Exports the specified XCCDF content to the specified file")
    (options, args) = parser.parse_args(arguments)
    for arg in args[1:]:
        if not sec_instance.export(args[0], arg, options.original):
            return -1

def remove_content(arguments):
    parser = OptionParser(usage="secstate remove [options] <ContentID>")
    (options, args) = parser.parse_args(arguments)
    for arg in args:
        if (not (sec_instance.remove_content(arg))):
            return -1

def select(arguments, value):
    sel = ""
    if not value:
        sel = "de"

    parser = OptionParser(usage="secstate %(sel)sselect [options] <ContentID> [GroupID|RuleID|ProfileID]" % {'sel':sel})
    parser.add_option('-r', '--recurse', action='store_true', dest='recurse', default=False,
                      help="Recursively %(sel)sselect rules inside of groups or benchmarks" % {'sel':sel})
    (options, args) = parser.parse_args(arguments)
    if len(args) == 1:
        if not sec_instance.select(args[0], args[0], value, recurse=options.recurse):
            return -1
    else:
        for arg in args[1:]:
            if (not (sec_instance.select(args[0], arg, value, recurse=options.recurse))):
                return -1

def audit(arguments):
    parser = OptionParser(usage="secstate audit [options] [ContentID|ContentFile]")
    parser.add_option('-p', '--profile', action='store', type='string', dest='profile',
                      default=None, help="Specifies the profile to use when auditing the system")
    parser.add_option('-o', '--output', action='store', type='string', dest='output',
                      default=None, help="Specifies the directory to store results")
    parser.add_option('--no-xml', action='store_false', dest='xml',default=True,
                      help="Disable XML results")
    parser.add_option('--no-html', action='store_false', dest='html',default=True,
                      help="Disable HTML results")
    parser.add_option('-v', '--verbose', action='store_true', dest='verbose', default=False,
                      help="Prints out extra information during the audit process")
    parser.add_option('-a', '--all', action='store_true', dest='all', default=False,
                      help="Audit everything regardless of selection status")
    parser.add_option('-r', '--rule', action='store', type='string', dest='rule', default=None,
                      help="Audit only the specified rule")
    (options, args) = parser.parse_args(arguments)
    if (not (sec_instance.audit(args, all=options.all, verbose=options.verbose, profile=options.profile, results_dir=options.output, output_xml=options.xml, output_html=options.html, rule=options.rule))):
        return -1

def remediate(arguments):
    parser = OptionParser(usage="secstate remediate [options] [BenchmarkID|BenchmarkFile]")
    parser.add_option('-x', '--xccdf-results', action='store', type='string', dest='xccdf_results', metavar='FILE',
                      help='XCCDF results file to provide for selective remediation')
    parser.add_option('-l', '--log-dest', action='store', type='string', dest='log_dest', metavar='FILE',
                      help='Output logs to FILE instead of stdout')
    parser.add_option('-n', '--noop', action='store_true', dest='noop', default=False,
                      help='Run puppet in noop mode')
    parser.add_option('-p', '--profile', action='store', type='string', dest='profile',
                      default=None, help="Specifies the profile to use when remediating the system")
    parser.add_option('-v', '--verbose', action='store_false', dest='verbose', help="Prints out extra information during the remediate process")
    parser.add_option('-y', '--yes', action='store_true', dest='yes', help="Respond 'yes' to all prompts")
    options, args = parser.parse_args(arguments)
   
    kwargs = {}   

    kwargs['args'] = args
    kwargs['verbose'] = options.verbose
    kwargs['yes_all'] = options.yes

    if options.log_dest and not os.path.exists(options.log_dest):
        LOG_FILE = open(options.log_dest, "w")
        LOG_FILE.close()
        kwargs['log_dest'] = os.path.abspath(options.log_dest)
        
    if options.xccdf_results and not os.path.exists(options.xccdf_results):
        sys.stderr.write('Error: XCCDF results file does not exist : %s\n' % options.xccdf_results)
        return -1
    elif options.xccdf_results:
        kwargs['xccdf_results'] = options.xccdf_results

    kwargs['noop'] = options.noop
    
    if not sec_instance.remediate_puppet(**kwargs):
        return -1

def search(arguments):
    if arguments == []:
        arguments.append('-h')
    parser = OptionParser(usage="secstate search [options] <string>")
    parser.add_option('-v', '--verbose', action='store_true', dest='verbose', default=False,
                      help="Show extra information from the search")
    parser.add_option('-r', '--reverse', action='store_true', dest='reverse', default=False,
                      help="Search for an XCCDF rule based on an OVAL definition id")
    (options, args) = parser.parse_args(arguments)
    if not sec_instance.search(args[0], options.verbose, options.reverse):
        return -1

def list_content(arguments):
    parser = OptionParser(usage="secstate list [options] [ContentID]")
    parser.add_option('-r', '--recurse', action='store_true', dest='recurse', default=False,
                      help="Recurse through an items content")
    parser.add_option('-a', '--all', action='store_true', dest='all', default=False,
                      help="Show items regardless of selection status")
    (options, args) = parser.parse_args(arguments)
    if args == []:
        if not sec_instance.list_content(recurse=options.recurse, show_all=options.all):
            return -1

    else:
        for arg in args:
            if not sec_instance.list_content(arg, options.recurse, options.all):
                return -1
    return 0

def show(arguments):
    parser = OptionParser(usage="secstate show [options] <ContentID>")
    parser.add_option('-v', '--verbose', action='store_true', dest='verbose', default=False,
                      help="Show extra information about the item being shown")
    (options, args) = parser.parse_args(arguments)
    for arg in args:
        if not sec_instance.show(arg, options.verbose):
            return -1
    return 0

def save_profile(arguments):
    parser = OptionParser(usage="secstate save [options] <BenchmarkID> <ProfileName>")
    (options, args) = parser.parse_args(arguments)
    if len(args) != 2:
        sys.stderr.write("Wrong number of arguments passed to save\n'secstate save [options] <benchmark> <profile name>'\n")
        return -1

    if not sec_instance.save_profile(args[0], args[1]):
        return -1

def mitigate(arguments):
    parser = OptionParser(usage="secstate mitigate [options] <BenchmarkID> <RuleID>")
    parser.add_option('-r', '--remark', action='store', type='string', dest='remark', default=None,
                      help="Show extra information about the item being shown")
    parser.add_option('-a', '--authority', action='store', type='string', dest='authority', default=None,
                      help="Show extra information about the item being shown")
    (options, args) = parser.parse_args(arguments)
    if options.remark == None:
        options.remark = raw_input("Please enter a remark for this mitigation.  Press Enter when finished\n")
    if len(args) != 2:
        sys.stderr.write("Wrong number of arguments passed to remediate\n'secstate remediate [options] <ContentID> <ItemID> <Remark>'\n")
        return -1

    if not sec_instance.mitigate(args[0], args[1], options.remark, options.authority):
        return -1

if __name__ == '__main__':
    sys.exit(main())

