1#!/usr/bin/python -E
2
3
4from __future__ import print_function
5import os
6import errno
7import shutil
8import sys
9from optparse import OptionParser
10
11import ctypes
12
13sepol = ctypes.cdll.LoadLibrary('libsepol.so.1')
14
15try:
16	import selinux
17	import semanage
18except:
19	print("You must install libselinux-python and libsemanage-python before running this tool", file=sys.stderr)
20	exit(1)
21
22
23def copy_file(src, dst):
24	if DEBUG:
25		print("copying %s to %s" % (src, dst))
26	try:
27		shutil.copy(src, dst)
28	except OSError as the_err:
29		(err, strerr) = the_err.args
30		print("Could not copy %s to %s, %s" %(src, dst, strerr), file=sys.stderr)
31		exit(1)
32
33
34def create_dir(dst, mode):
35	if DEBUG: print("Making directory %s" % dst)
36	try:
37		os.makedirs(dst, mode)
38	except OSError as the_err:
39		(err, stderr) = the_err.args
40		if err == errno.EEXIST:
41			pass
42		else:
43			print("Error creating %s" % dst, file=sys.stderr)
44			exit(1)
45
46
47def create_file(dst):
48	if DEBUG: print("Making file %s" % dst)
49	try:
50		open(dst, 'a').close()
51	except OSError as the_err:
52		(err, stderr) = the_err.args
53		print("Error creating %s" % dst, file=sys.stderr)
54		exit(1)
55
56
57def copy_module(store, name, base):
58	if DEBUG: print("Install module %s" % name)
59	(file, ext) = os.path.splitext(name)
60	if ext != ".pp":
61		# Stray non-pp file in modules directory, skip
62		print("warning: %s has invalid extension, skipping" % name, file=sys.stderr)
63		return
64	try:
65		if base:
66			root = oldstore_path(store)
67		else:
68			root = oldmodules_path(store)
69
70		bottomdir = bottomdir_path(store)
71
72		os.mkdir("%s/%s" % (bottomdir, file))
73
74		copy_file(os.path.join(root, name), "%s/%s/hll" % (bottomdir, file))
75
76		# This is the ext file that will eventually be used to choose a compiler
77		efile = open("%s/%s/lang_ext" % (bottomdir, file), "w+", 0o600)
78		efile.write("pp")
79		efile.close()
80
81	except:
82		print("Error installing module %s" % name, file=sys.stderr)
83		exit(1)
84
85
86def disable_module(file, name, disabledmodules):
87	if DEBUG: print("Disabling %s" % name)
88	(disabledname, disabledext) = os.path.splitext(file)
89	create_file("%s/%s" % (disabledmodules, disabledname))
90
91def migrate_store(store):
92
93	oldstore = oldstore_path(store);
94	oldmodules = oldmodules_path(store);
95	disabledmodules = disabledmodules_path(store);
96	newstore = newstore_path(store);
97	newmodules = newmodules_path(store);
98	bottomdir = bottomdir_path(store);
99
100	print("Migrating from %s to %s" % (oldstore, newstore))
101
102	# Build up new directory structure
103	create_dir("%s/%s" % (newroot_path(), store), 0o755)
104	create_dir(newstore, 0o700)
105	create_dir(newmodules, 0o700)
106	create_dir(bottomdir, 0o700)
107	create_dir(disabledmodules, 0o700)
108
109	# Special case for base since it was in a different location
110	copy_module(store, "base.pp", 1)
111
112	# Dir structure built, start copying files
113	for root, dirs, files in os.walk(oldstore):
114		if root == oldstore:
115			# This is the top level directory, need to move
116			for name in files:
117				# Check to see if it is in TOPPATHS and copy if so
118				if name in TOPPATHS:
119					if name == "seusers":
120						newname = "seusers.local"
121					else:
122						newname = name
123					copy_file(os.path.join(root, name), os.path.join(newstore, newname))
124
125		elif root == oldmodules:
126			# This should be the modules directory
127			for name in files:
128				(file, ext) = os.path.splitext(name)
129				if name == "base.pp":
130					print("Error installing module %s, name conflicts with base" % name, file=sys.stderr)
131					exit(1)
132				elif ext == ".disabled":
133					disable_module(file, name, disabledmodules)
134				else:
135					copy_module(store, name, 0)
136
137def rebuild_policy():
138	# Ok, the modules are loaded, lets try to rebuild the policy
139	print("Attempting to rebuild policy from %s" % newroot_path())
140
141	curstore = selinux.selinux_getpolicytype()[1]
142
143	handle = semanage.semanage_handle_create()
144	if not handle:
145		print("Could not create semanage handle", file=sys.stderr)
146		exit(1)
147
148	semanage.semanage_select_store(handle, curstore, semanage.SEMANAGE_CON_DIRECT)
149
150	if not semanage.semanage_is_managed(handle):
151		semanage.semanage_handle_destroy(handle)
152		print("SELinux policy is not managed or store cannot be accessed.", file=sys.stderr)
153		exit(1)
154
155	rc = semanage.semanage_access_check(handle)
156	if rc < semanage.SEMANAGE_CAN_WRITE:
157		semanage.semanage_handle_destroy(handle)
158		print("Cannot write to policy store.", file=sys.stderr)
159		exit(1)
160
161	rc = semanage.semanage_connect(handle)
162	if rc < 0:
163		semanage.semanage_handle_destroy(handle)
164		print("Could not establish semanage connection", file=sys.stderr)
165		exit(1)
166
167	semanage.semanage_set_rebuild(handle, 1)
168
169	rc = semanage.semanage_begin_transaction(handle)
170	if rc < 0:
171		semanage.semanage_handle_destroy(handle)
172		print("Could not begin transaction", file=sys.stderr)
173		exit(1)
174
175	rc = semanage.semanage_commit(handle)
176	if rc < 0:
177		print("Could not commit transaction", file=sys.stderr)
178
179	semanage.semanage_handle_destroy(handle)
180
181
182def oldroot_path():
183	return "%s/etc/selinux" % ROOT
184
185def oldstore_path(store):
186	return "%s/%s/modules/active" % (oldroot_path(), store)
187
188def oldmodules_path(store):
189	return "%s/modules" % oldstore_path(store)
190
191def disabledmodules_path(store):
192	return "%s/disabled" % newmodules_path(store)
193
194def newroot_path():
195	return "%s%s" % (ROOT, PATH)
196
197def newstore_path(store):
198	return "%s/%s/active" % (newroot_path(), store)
199
200def newmodules_path(store):
201	return "%s/modules" % newstore_path(store)
202
203def bottomdir_path(store):
204	return "%s/%s" % (newmodules_path(store), PRIORITY)
205
206
207if __name__ == "__main__":
208
209	parser = OptionParser()
210	parser.add_option("-p", "--priority", dest="priority", default="100",
211			  help="Set priority of modules in new store (default: 100)")
212	parser.add_option("-s", "--store", dest="store", default=None,
213			  help="Store to read from and write to")
214	parser.add_option("-d", "--debug", dest="debug", action="store_true", default=False,
215			  help="Output debug information")
216	parser.add_option("-c", "--clean", dest="clean", action="store_true", default=False,
217			  help="Clean old modules directory after migrate (default: no)")
218	parser.add_option("-n", "--norebuild", dest="norebuild", action="store_true", default=False,
219			  help="Disable rebuilding policy after migration (default: no)")
220	parser.add_option("-P", "--path", dest="path",
221			  help="Set path for the policy store (default: /var/lib/selinux)")
222	parser.add_option("-r", "--root", dest="root",
223			  help="Set an alternative root for the migration (default: /)")
224
225	(options, args) = parser.parse_args()
226
227	DEBUG = options.debug
228	PRIORITY = options.priority
229	TYPE = options.store
230	CLEAN = options.clean
231	NOREBUILD = options.norebuild
232	PATH = options.path
233	if PATH is None:
234		PATH = "/var/lib/selinux"
235
236	ROOT = options.root
237	if ROOT is None:
238		ROOT = ""
239
240	# List of paths that go in the active 'root'
241	TOPPATHS = [
242		"commit_num",
243		"ports.local",
244		"interfaces.local",
245		"nodes.local",
246		"booleans.local",
247		"file_contexts.local",
248		"seusers",
249		"users.local",
250		"users_extra",
251		"users_extra.local",
252		"disable_dontaudit",
253		"preserve_tunables",
254		"policy.kern",
255		"file_contexts",
256		"homedir_template"]
257
258
259	create_dir(newroot_path(), 0o755)
260
261	stores = None
262	if TYPE is not None:
263		stores = [TYPE]
264	else:
265		stores = os.listdir(oldroot_path())
266
267	# find stores in oldroot and migrate them to newroot if necessary
268	for store in stores:
269		if not os.path.isdir(oldmodules_path(store)):
270			# already migrated or not an selinux store
271			continue
272
273		if os.path.isdir(newstore_path(store)):
274			# store has already been migrated, but old modules dir still exits
275			print("warning: Policy type %s has already been migrated, but modules still exist in the old store. Skipping store." % store, file=sys.stderr)
276			continue
277
278		migrate_store(store)
279
280		if CLEAN is True:
281			def remove_error(function, path, execinfo):
282				print("warning: Unable to remove old store modules directory %s. Cleaning failed." % oldmodules_path(store), file=sys.stderr)
283			shutil.rmtree(oldmodules_path(store), onerror=remove_error)
284
285	if NOREBUILD is False:
286		rebuild_policy()
287
288