#!/usr/bin/env python3
# Copyright 2014-2015, Tresys Technology, LLC
#
# This file is part of SETools.
#
# SETools 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.
#
# SETools 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 SETools.  If not, see <http://www.gnu.org/licenses/>.
#

import setools
import argparse
import sys
import logging
import signal

signal.signal(signal.SIGPIPE, signal.SIG_DFL)

parser = argparse.ArgumentParser(
    description="SELinux policy rule search tool.",
    epilog="TE/MLS rule searches cannot be mixed with RBAC rule searches.")
parser.add_argument("--version", action="version", version=setools.__version__)
parser.add_argument("policy", help="Path to the SELinux policy to search.", nargs="?")
parser.add_argument("-v", "--verbose", action="store_true",
                    help="Print extra informational messages")
parser.add_argument("--debug", action="store_true", dest="debug", help="Enable debugging.")

rtypes = parser.add_argument_group("TE Rule Types")
rtypes.add_argument("-A", action="store_true", help="Search allow and allowxperm rules.")
rtypes.add_argument("--allow", action="append_const",
                    const=setools.TERuletype.allow, dest="tertypes",
                    help="Search allow rules.")
rtypes.add_argument("--allowxperm", action="append_const",
                    const=setools.TERuletype.allowxperm, dest="tertypes",
                    help="Search allowxperm rules.")
rtypes.add_argument("--auditallow", action="append_const",
                    const=setools.TERuletype.auditallow, dest="tertypes",
                    help="Search auditallow rules.")
rtypes.add_argument("--auditallowxperm", action="append_const",
                    const=setools.TERuletype.auditallowxperm, dest="tertypes",
                    help="Search auditallowxperm rules.")
rtypes.add_argument("--dontaudit", action="append_const",
                    const=setools.TERuletype.dontaudit, dest="tertypes",
                    help="Search dontaudit rules.")
rtypes.add_argument("--dontauditxperm", action="append_const",
                    const=setools.TERuletype.dontauditxperm, dest="tertypes",
                    help="Search dontauditxperm rules.")
rtypes.add_argument("--neverallow", action="append_const",
                    const=setools.TERuletype.neverallow, dest="tertypes",
                    help="Search neverallow rules.")
rtypes.add_argument("--neverallowxperm", action="append_const",
                    const=setools.TERuletype.neverallowxperm, dest="tertypes",
                    help="Search neverallowxperm rules.")
rtypes.add_argument("-T", "--type_trans", action="append_const",
                    const=setools.TERuletype.type_transition, dest="tertypes",
                    help="Search type_transition rules.")
rtypes.add_argument("--type_change", action="append_const",
                    const=setools.TERuletype.type_change, dest="tertypes",
                    help="Search type_change rules.")
rtypes.add_argument("--type_member", action="append_const",
                    const=setools.TERuletype.type_member, dest="tertypes",
                    help="Search type_member rules.")
rbacrtypes = parser.add_argument_group("RBAC Rule Types")
rbacrtypes.add_argument("--role_allow", action="append_const",
                        const=setools.RBACRuletype.allow, dest="rbacrtypes",
                        help="Search role allow rules.")
rbacrtypes.add_argument("--role_trans", action="append_const",
                        const=setools.RBACRuletype.role_transition, dest="rbacrtypes",
                        help="Search role_transition rules.")

mlsrtypes = parser.add_argument_group("MLS Rule Types")
mlsrtypes.add_argument("--range_trans", action="append_const",
                       const=setools.MLSRuletype.range_transition, dest="mlsrtypes",
                       help="Search range_transition rules.")

expr = parser.add_argument_group("Expressions")
expr.add_argument("-s", "--source",
                  help="Source type/role of the TE/RBAC rule.")
expr.add_argument("-t", "--target",
                  help="Target type/role of the TE/RBAC rule.")
expr.add_argument("-c", "--class", dest="tclass",
                  help="Comma separated list of object classes")
expr.add_argument("-p", "--perms", metavar="PERMS",
                  help="Comma separated list of permissions.")
expr.add_argument("-x", "--xperms", metavar="XPERMS",
                  help="Comma separated list of extended permissions.")
expr.add_argument("-D", "--default",
                  help="Default of the rule. (type/role/range transition rules)")
expr.add_argument("-b", "--bool", dest="boolean", metavar="BOOL",
                  help="Comma separated list of Booleans in the conditional expression.")

opts = parser.add_argument_group("Search options")
opts.add_argument("-eb", action="store_true", dest="boolean_equal",
                  help="Match Boolean list exactly instead of matching any listed Boolean.")
opts.add_argument("-ep", action="store_true", dest="perms_equal",
                  help="Match permission set exactly instead of matching any listed permission.")
opts.add_argument("-ex", action="store_true", dest="xperms_equal",
                  help="Match extended permission set exactly instead of matching any listed "
                  "permission.")
opts.add_argument("-ds", action="store_false", dest="source_indirect",
                  help="Match source attributes directly instead of matching member types/roles.")
opts.add_argument("-dt", action="store_false", dest="target_indirect",
                  help="Match target attributes directly instead of matching member types/roles.")
opts.add_argument("-rs", action="store_true", dest="source_regex",
                  help="Use regular expression matching for the source type/role.")
opts.add_argument("-rt", action="store_true", dest="target_regex",
                  help="Use regular expression matching for the target type/role.")
opts.add_argument("-rc", action="store_true", dest="tclass_regex",
                  help="Use regular expression matching for the object class.")
opts.add_argument("-rd", action="store_true", dest="default_regex",
                  help="Use regular expression matching for the default type/role.")
opts.add_argument("-rb", action="store_true", dest="boolean_regex",
                  help="Use regular expression matching for Booleans.")

args = parser.parse_args()

if args.A:
    try:
        args.tertypes.extend([setools.TERuletype.allow, setools.TERuletype.allowxperm])
    except AttributeError:
        args.tertypes = [setools.TERuletype.allow, setools.TERuletype.allowxperm]

if not args.tertypes and not args.mlsrtypes and not args.rbacrtypes:
    parser.error("At least one rule type must be specified.")

if args.debug:
    logging.basicConfig(level=logging.DEBUG,
                        format='%(asctime)s|%(levelname)s|%(name)s|%(message)s')
elif args.verbose:
    logging.basicConfig(level=logging.INFO, format='%(message)s')
else:
    logging.basicConfig(level=logging.WARNING, format='%(message)s')

try:
    p = setools.SELinuxPolicy(args.policy)

    if args.tertypes:
        q = setools.TERuleQuery(p,
                                ruletype=args.tertypes,
                                source=args.source,
                                source_indirect=args.source_indirect,
                                source_regex=args.source_regex,
                                target=args.target,
                                target_indirect=args.target_indirect,
                                target_regex=args.target_regex,
                                tclass_regex=args.tclass_regex,
                                perms_equal=args.perms_equal,
                                xperms_equal=args.xperms_equal,
                                default=args.default,
                                default_regex=args.default_regex,
                                boolean_regex=args.boolean_regex,
                                boolean_equal=args.boolean_equal)

        # these are broken out from the above statement to prevent making a list
        # with an empty string in it (split on empty string)
        if args.tclass:
            if args.tclass_regex:
                q.tclass = args.tclass
            else:
                q.tclass = args.tclass.split(",")

        if args.perms:
            q.perms = args.perms.split(",")

        if args.xperms:
            xperms = []
            for item in args.xperms.split(","):
                rng = item.split("-")
                if len(rng) == 2:
                    xperms.append((int(rng[0], base=16), int(rng[1], base=16)))
                elif len(rng) == 1:
                    xperms.append((int(rng[0], base=16), int(rng[0], base=16)))
                else:
                    parser.error("Enter an extended permission or extended permission range, e.g. "
                                 "0x5411 or 0x8800-0x88ff.")

            q.xperms = xperms

        if args.boolean:
            if args.boolean_regex:
                q.boolean = args.boolean
            else:
                q.boolean = args.boolean.split(",")

        for r in sorted(q.results()):
            print(r)

    if args.rbacrtypes:
        q = setools.RBACRuleQuery(p,
                                  ruletype=args.rbacrtypes,
                                  source=args.source,
                                  source_indirect=args.source_indirect,
                                  source_regex=args.source_regex,
                                  target=args.target,
                                  target_indirect=args.target_indirect,
                                  target_regex=args.target_regex,
                                  default=args.default,
                                  default_regex=args.default_regex,
                                  tclass_regex=args.tclass_regex)

        # these are broken out from the above statement to prevent making a list
        # with an empty string in it (split on empty string)
        if args.tclass:
            if args.tclass_regex:
                q.tclass = args.tclass
            else:
                q.tclass = args.tclass.split(",")

        for r in sorted(q.results()):
            print(r)

    if args.mlsrtypes:
        q = setools.MLSRuleQuery(p,
                                 ruletype=args.mlsrtypes,
                                 source=args.source,
                                 source_indirect=args.source_indirect,
                                 source_regex=args.source_regex,
                                 target=args.target,
                                 target_indirect=args.target_indirect,
                                 target_regex=args.target_regex,
                                 tclass_regex=args.tclass_regex,
                                 default=args.default)

        # these are broken out from the above statement to prevent making a list
        # with an empty string in it (split on empty string)
        if args.tclass:
            if args.tclass_regex:
                q.tclass = args.tclass
            else:
                q.tclass = args.tclass.split(",")

        for r in sorted(q.results()):
            print(r)

except Exception as err:
    if args.debug:
        raise
    else:
        print(err)

    sys.exit(1)
