#!/usr/bin/env python import sys import os import argparse class FileContextsNode: path = None fileType = None context = None Type = None meta = None stemLen = None strLen = None Type = None line = None def __init__(self, path, fileType, context, meta, stemLen, strLen, line): self.path = path self.fileType = fileType self.context = context self.meta = meta self.stemLen = stemLen self.strlen = strLen self.Type = context.split(":")[2] self.line = line metaChars = frozenset(['.', '^', '$', '?', '*', '+', '|', '[', '(', '{']) escapedMetaChars = frozenset(['\.', '\^', '\$', '\?', '\*', '\+', '\|', '\[', '\(', '\{']) def getStemLen(path): global metaChars stemLen = 0 i = 0 while i < len(path): if path[i] == "\\": i += 1 elif path[i] in metaChars: break stemLen += 1 i += 1 return stemLen def getIsMeta(path): global metaChars global escapedMetaChars metaCharsCount = 0 escapedMetaCharsCount = 0 for c in metaChars: if c in path: metaCharsCount += 1 for c in escapedMetaChars: if c in path: escapedMetaCharsCount += 1 return metaCharsCount > escapedMetaCharsCount def CreateNode(line): global metaChars if (len(line) == 0) or (line[0] == '#'): return None split = line.split() path = split[0].strip() context = split[-1].strip() fileType = None if len(split) == 3: fileType = split[1].strip() meta = getIsMeta(path) stemLen = getStemLen(path) strLen = len(path.replace("\\", "")) return FileContextsNode(path, fileType, context, meta, stemLen, strLen, line) def ReadFileContexts(files): fc = [] for f in files: fd = open(f) for line in fd: node = CreateNode(line.strip()) if node != None: fc.append(node) return fc # Comparator function for list.sort() based off of fc_sort.c # Compares two FileContextNodes a and b and returns 1 if a is more # specific or -1 if b is more specific. def compare(a, b): # The regex without metachars is more specific if a.meta and not b.meta: return -1 if b.meta and not a.meta: return 1 # The regex with longer stemlen (regex before any meta characters) is more specific. if a.stemLen < b.stemLen: return -1 if b.stemLen < a.stemLen: return 1 # The regex with longer string length is more specific if a.strLen < b.strLen: return -1 if b.strLen < a.strLen: return 1 # A regex with a fileType defined (e.g. file, dir) is more specific. if a.fileType is None and b.fileType is not None: return -1 if b.fileType is None and a.fileType is not None: return 1 # Regexes are equally specific. return 0 def FcSort(files): for f in files: if not os.path.exists(f): sys.exit("Error: File_contexts file " + f + " does not exist\n") Fc = ReadFileContexts(files) Fc.sort(cmp=compare) return Fc def PrintFc(Fc, out): if not out: f = sys.stdout else: f = open(out, "w") for node in Fc: f.write(node.line + "\n") if __name__ == '__main__': parser = argparse.ArgumentParser(description="SELinux file_contexts sorting tool.") parser.add_argument("-i", dest="input", help="Path to the file_contexts file(s).", nargs="?", action='append') parser.add_argument("-o", dest="output", help="Path to the output file", nargs=1) args = parser.parse_args() if not args.input: parser.error("Must include path to policy") if not not args.output: args.output = args.output[0] PrintFc(FcSort(args.input),args.output)