#!/usr/bin/env python
# -*- coding: utf-8 -*-

# (C) Copyright 2017 Inova Development Inc.
# All Rights Reserved
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""
This script executes the ServerSweep functions to sweep a set of ip address
and ports for possible WBEM Servers.

This is a temporary version of the code in a separate script until it is
integrated into a single multi-level script.

NOTE: This script must be executed in admin mode because python does not
allow the current port inspect test in user mode.

"""
from __future__ import absolute_import

import sys
import os
import argparse as _argparse
from smipyping import ServerSweep, TargetsData, \
    DEFAULT_SWEEP_PORT, DEFAULT_CONFIG_FILE, SCAN_TYPES

from smipyping._configfile import read_config
from smipyping._cliutils import SmiSmartFormatter, check_negative_int
from smipyping._utilities import display_argparser_args
from smipyping._logging import SmiPypingLoggers, LOG_LEVELS


def create_sweep_argparser(prog_name):
    """
    Create the argument parser for server sweep cmd line.

    Returns the created parser.
    """
    prog = prog_name  # Name of the script file invoking this module
    usage = '%(prog)s [options] subnet [subnet] ...'
    desc = 'Sweep possible WBEMServer ports across a range of IP subnets '\
           'and ports to find existing running WBEM servers.'
    epilog = """
Examples:
  %s 10.1.132-134
        The above example will scan the subnets 10.1.134, 10.1.133, and
        10.1.134 from 1 to 254 for the 4 the octet of ip addresses. This
        is explicitly defined by 10.1.132:124.1:254
  %s 10.1.134
        Scan a complete Class C subnet, 10.1.134 for the default port (5989)
  %s 10.1.132,134 -p 5989 5988
        Scan 10.1.132.1:254 and 10.1.134.1:254 for ports 5988 and 5989

""" % (prog, prog, prog)

    argparser = _argparse.ArgumentParser(
        prog=prog, usage=usage, description=desc, epilog=epilog,
        formatter_class=SmiSmartFormatter)

    pos_arggroup = argparser.add_argument_group(
        'Positional arguments')
    pos_arggroup.add_argument(
        'subnet', metavar='subnet', nargs='+',
        help='R|IP subnets to scan (ex. 10.1.132). Multiple subnets\n '
             'allowed. Each subnet string is itself a definition that\n'
             'consists of period separated octets that are used to\n'
             'create the individual ip addresses to be tested:\n'
             '  * Integers: Each integer is in the range 0-255\n'
             '      ex. 10.1.2.9\n'
             '  * Octet range definition: A range expansion is in the\n'
             '     form: int-int which defines the mininum and maximum\n'
             '      values for that octet (ex 10.1.132-134) or\n'
             '  * Integer lists: A range list is in the form:\n'
             '     int,int,int\n'
             '     and defines the set of values for that octet.\n'
             'Missing octet definitions are expanded to the value\n'
             'range defined by the min and max octet value parameters\n'
             'All octets of the ip address can use any of the 3\n'
             'definitions.\n'
             'Examples: 10.1.132,134 expands to addresses in 10.1.132\n'
             'and 10.1.134. where the last octet is the range 1 to 254')

    subnet_arggroup = argparser.add_argument_group(
        'Scan related options',
        'Specify parameters of the subnet scan')
    subnet_arggroup.add_argument(
        '--min_octet_val', '-m', metavar='Min', nargs='?', default=1,
        type=check_negative_int,
        help='Minimum expanded value for any octet that is not specifically '
             ' included in a net definition. Default = 1')
    subnet_arggroup.add_argument(
        '--max_octet_val', '-e', metavar='Max', nargs='?', default=254,
        type=check_negative_int,
        help='Minimum expanded value for any octet that is not specifically '
             ' included in a net definition. Default = 254')
    subnet_arggroup.add_argument(
        '--port', '-p', nargs='*', action='store', type=check_negative_int,
        default=DEFAULT_SWEEP_PORT,
        help='Port(s) to test. This argument may be define multiple ports. '
             ' Ex. -p 5988 5989. Default=%s' % DEFAULT_SWEEP_PORT)

    general_arggroup = argparser.add_argument_group(
        'General options')
    general_arggroup.add_argument(
        '--config_file', '-c',
        default=DEFAULT_CONFIG_FILE,
        help='Use config file to determine if server is in database')
    general_arggroup.add_argument(
        '-D', '--dbtype', metavar='DBTYPE',
        help=('DBTYPE to use. Overrides config file.'))
    general_arggroup.add_argument(
        '--no_threads', action='store_true', default=False,
        help='If set, uses non-threaded implementation for pings. The '
             'non-threaded implementation takes MUCH longer to execute but'
             'is provided in case threading causes issues in a particular '
             'user environment.')
    general_arggroup.add_argument(
        '--verbose', '-v', action='store_true', default=False,
        help='If set output detail displays as test proceeds')
    general_arggroup.add_argument(
        '--loglevel', '-l', default='debug', metavar='LOGLEVEL',
        help=('Set log level: %s' % LOG_LEVELS))
    general_arggroup.add_argument(
        '--scantype', '-s', default='tcp', metavar='SCANTYPE',
        help=('Set scan type: %s. Some scan types require privilege mode.'
              % SCAN_TYPES))
    general_arggroup.add_argument(
        '--dryrun', action='store_true', default=False,
        help='Display list of systems/ports to be scanned but do not scan. '
             ' This is a diagnostic tool')
    return argparser


def parse_sweep_args(argparser):
    """
    Process the cmdline arguments including any default substitution.

    This is based on the argparser defined by the create... function.

    Either returns the args or executes argparser.error
    """
    args = argparser.parse_args()

    if not args.subnet:
        argparser.error('No Subnet specified. At least one subnet required.')

    # set default port if none provided.
    if args.port is None:
        # This is from a variable in config.py
        args.port = [DEFAULT_SWEEP_PORT]

    if args.scantype not in SCAN_TYPES:
        argparser.error('Optional scantype %s must be one of %s' %
                        args.scantype, SCAN_TYPES)

    if args.verbose:
        display_argparser_args(args)

    return args


def main(prog_name):
    """
        Port sweep executable main. This function parses the
        input arguments, gets any required target data and
        creates an instance of the ServerSweep class.
        It uses that instance to sweep for open servers and then
        generate a result report.
    """
    argparser = create_sweep_argparser(prog_name)
    args = parse_sweep_args(argparser)

    if args.config_file is not None:
        if not args.dbtype:
            general = read_config(args.config_file, 'general')
            if general['dbtype']:
                dbtype = general['dbtype']
            else:
                argparser.error('Error: No dbtype in config file or cmd line')
        else:
            dbtype = args.dbtype
        db_config = read_config(args.config_file, dbtype)
        db_config['directory'] = os.path.dirname(args.config_file)
        print('Config File db info  type %s, details %s' % (dbtype,
                                                            db_config))
        target_data = TargetsData.factory(db_config, dbtype,
                                          args.verbose)
    else:
        target_data = None
    SmiPypingLoggers.create_logger('sweep', log_dest='file',
                                   log_filename='serversweep.log',
                                   log_level=args.loglevel)

    # Sweep the servers and display result
    if args.scantype != 'tcp':
        print('WARNING: serversweep requires privilege mode for the %s '
              'scantype' % args.scantype)
        
    print('Start sweep for subnets %s, port %s, range %s:%s' %
          (args.subnet, args.port, args.min_octet_val, args.max_octet_val))
    sweep = ServerSweep(args.subnet,
                        args.port,
                        target_data=target_data,
                        no_threads=args.no_threads,
                        min_octet_val=args.min_octet_val,
                        max_octet_val=args.max_octet_val,
                        verbose=args.verbose,
                        scan_type=args.scantype)

    if args.dryrun:
        sweep.list_subnets_to_scan()
    else:
        print('The sweep may take several minutes')

        open_servers = sweep.sweep_servers()
        sweep.print_open_hosts_report(open_servers)


if __name__ == '__main__':
    sys.exit(main(os.path.basename(sys.argv[0])))
