1#!/usr/bin/env python 2import sys 3import os 4import argparse 5 6class FileContextsNode: 7 path = None 8 fileType = None 9 context = None 10 Type = None 11 meta = None 12 stemLen = None 13 strLen = None 14 Type = None 15 line = None 16 def __init__(self, path, fileType, context, meta, stemLen, strLen, line): 17 self.path = path 18 self.fileType = fileType 19 self.context = context 20 self.meta = meta 21 self.stemLen = stemLen 22 self.strlen = strLen 23 self.Type = context.split(":")[2] 24 self.line = line 25 26metaChars = frozenset(['.', '^', '$', '?', '*', '+', '|', '[', '(', '{']) 27escapedMetaChars = frozenset(['\.', '\^', '\$', '\?', '\*', '\+', '\|', '\[', '\(', '\{']) 28 29def getStemLen(path): 30 global metaChars 31 stemLen = 0 32 i = 0 33 while i < len(path): 34 if path[i] == "\\": 35 i += 1 36 elif path[i] in metaChars: 37 break 38 stemLen += 1 39 i += 1 40 return stemLen 41 42 43def getIsMeta(path): 44 global metaChars 45 global escapedMetaChars 46 metaCharsCount = 0 47 escapedMetaCharsCount = 0 48 for c in metaChars: 49 if c in path: 50 metaCharsCount += 1 51 for c in escapedMetaChars: 52 if c in path: 53 escapedMetaCharsCount += 1 54 return metaCharsCount > escapedMetaCharsCount 55 56def CreateNode(line): 57 global metaChars 58 if (len(line) == 0) or (line[0] == '#'): 59 return None 60 61 split = line.split() 62 path = split[0].strip() 63 context = split[-1].strip() 64 fileType = None 65 if len(split) == 3: 66 fileType = split[1].strip() 67 meta = getIsMeta(path) 68 stemLen = getStemLen(path) 69 strLen = len(path.replace("\\", "")) 70 71 return FileContextsNode(path, fileType, context, meta, stemLen, strLen, line) 72 73def ReadFileContexts(files): 74 fc = [] 75 for f in files: 76 fd = open(f) 77 for line in fd: 78 node = CreateNode(line.strip()) 79 if node != None: 80 fc.append(node) 81 return fc 82 83# Comparator function for list.sort() based off of fc_sort.c 84# Compares two FileContextNodes a and b and returns 1 if a is more 85# specific or -1 if b is more specific. 86def compare(a, b): 87 # The regex without metachars is more specific 88 if a.meta and not b.meta: 89 return -1 90 if b.meta and not a.meta: 91 return 1 92 93 # The regex with longer stemlen (regex before any meta characters) is more specific. 94 if a.stemLen < b.stemLen: 95 return -1 96 if b.stemLen < a.stemLen: 97 return 1 98 99 # The regex with longer string length is more specific 100 if a.strLen < b.strLen: 101 return -1 102 if b.strLen < a.strLen: 103 return 1 104 105 # A regex with a fileType defined (e.g. file, dir) is more specific. 106 if a.fileType is None and b.fileType is not None: 107 return -1 108 if b.fileType is None and a.fileType is not None: 109 return 1 110 111 # Regexes are equally specific. 112 return 0 113 114def FcSort(files): 115 for f in files: 116 if not os.path.exists(f): 117 sys.exit("Error: File_contexts file " + f + " does not exist\n") 118 119 Fc = ReadFileContexts(files) 120 Fc.sort(cmp=compare) 121 122 return Fc 123 124def PrintFc(Fc, out): 125 if not out: 126 f = sys.stdout 127 else: 128 f = open(out, "w") 129 for node in Fc: 130 f.write(node.line + "\n") 131 132if __name__ == '__main__': 133 parser = argparse.ArgumentParser(description="SELinux file_contexts sorting tool.") 134 parser.add_argument("-i", dest="input", help="Path to the file_contexts file(s).", nargs="?", action='append') 135 parser.add_argument("-o", dest="output", help="Path to the output file", nargs=1) 136 args = parser.parse_args() 137 if not args.input: 138 parser.error("Must include path to policy") 139 if not not args.output: 140 args.output = args.output[0] 141 142 PrintFc(FcSort(args.input),args.output) 143