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