#!/opt/thinlinc/libexec/python3
# -*-mode: python; coding: utf-8 -*-
#
# Copyright 2002-2021 Cendio AB.
# For more information, see http://www.cendio.com

import os
import sys
import tempfile
import shutil
import glob
import locale
import subprocess

from thinlinc import prefix
from thinlinc.selinux import get_policy_name, find_makefile

def fix_mappings(fn, default):
	candidate = None
	mls = None

	# First check if this file is already configured
	with open(fn, "rt") as f:
		for line in f:
			if line.find("thinlinc_session_t") != -1:
				return

	with open(fn, "rt") as f:
		for line in f:
			fields = line.split()
			if len(fields) < 2:
				continue

			# Prefer to copy remote_login_t
			if fields[0].startswith("system_r:remote_login_t"):
				if len(fields[0].split(":")) > 2:
					mls = fields[0].split(":", 2)[2]
				else:
					mls = None
				candidate = " ".join(fields[1:])
				break

			# But use these if we can't find anything else
			if fields[0].startswith("system_r:local_login_t") or \
			   fields[0].startswith("system_r:sshd_t") or \
			   fields[0].startswith("system_r:xdm_t"):
				if len(fields[0].split(":")) > 2:
					mls = fields[0].split(":", 2)[2]
				else:
					mls = None
				candidate = " ".join(fields[1:])

	if candidate is None:
		print("Could not find template type. Using default fallback.", file=sys.stderr)

		candidate = default
		mls = "s0"

	src = "system_r:thinlinc_session_t"
	if mls is not None:
		src += ":%s" % mls

	with open(fn, "at") as f:
		f.write("%s\t%s\n" % (src, candidate))

def configure_users():
	name = get_policy_name()

	print("Updating default context mappings...")
	sys.stdout.flush()

	try:
		fix_mappings("/etc/selinux/%s/contexts/default_contexts" % name,
		             "user_r:user_t:s0 staff_r:staff_t:s0 unconfined_r:unconfined_t:s0")
	except OSError as e:
		print("Could not update default context mappings:", e, file=sys.stderr)
		return 1

	if name is None:
		print("No or empty SELINUXTYPE in SELinux config", file=sys.stderr)
        
	for fn in glob.glob("/etc/selinux/%s/contexts/users/*" % name):
		user = os.path.basename(fn)

		if user.endswith("_u"):
			default = "%s_r:%s_t:s0" % (user[:-2], user[:-2])
		else:
			default = "unconfined_r:unconfined_t"

		print("Updating %s context mappings..." % user)
		sys.stdout.flush()

		try:
			fix_mappings(fn, default)
		except OSError as e:
			print("Could not update context mapping for %s:" % user, e, file=sys.stderr)

	return 0

def compile_and_install(makefile, source_dir, name):
	dir = tempfile.mkdtemp()

	try:
		for suffix in ('te', 'if', 'fc'):
			shutil.copy("%s/%s.%s" % (source_dir, name, suffix), dir)

		print("Compiling %s policy module..." % name)
		sys.stdout.flush()

		ret = subprocess.call(["make", "-C", dir, "-f", makefile, "%s.pp" % name])
		if ret != 0:
			return ret

		print("Installing %s policy module..." % name)
		sys.stdout.flush()

		ret = subprocess.call(["semodule", "-i", "%s/%s.pp" % (dir, name)])
		if ret != 0:
			return ret

		return 0
	finally:
		shutil.rmtree(dir)

def restorecon():
	print("Fixing context on files...")
	sys.stdout.flush()

	ret = subprocess.call(["restorecon", "-R", "-v", "-i",
			       "-e", "/var/opt/thinlinc/sessions",
	                       "/opt/thinlinc", "/var/opt/thinlinc",
	                       "/var/run/thinlinc"])
	if ret != 0:
		return ret

	# non-recursive restorecon fixes only the sessions dir
	ret = subprocess.call(["restorecon", "-v", "-i",
		"/var/opt/thinlinc/sessions"])
	if ret != 0:
		return ret

	dirs = os.listdir("/var/opt/thinlinc/sessions")
	for dir in dirs:
		ret = restorecon_user_session_dir(dir)
		if ret != 0:
			return ret

	return 0

def restorecon_user_session_dir(user):
	"""Run restorecon on a user session dir, excluding the drives folder
	because restorecon can hang on NFS mounts."""

	restorecon_argv = ["restorecon", "-R", "-v", "-i"]
	basedir = "/var/opt/thinlinc/sessions/%s" % user
	for dir in os.listdir(basedir):
		drive_dir = "%s/%s/drives" % (basedir, dir)
		if os.path.exists(drive_dir):
			restorecon_argv.append("-e")
			restorecon_argv.append(drive_dir)

	restorecon_argv.append(basedir)

	return subprocess.call(restorecon_argv)

def main():
	try:
		locale.setlocale(locale.LC_ALL, "")
	except locale.Error:
		pass

	makefile = find_makefile()
	if makefile is None:
		print("Could not find the development files for your SELinux policy.", file=sys.stderr)
		sys.exit(1)

	ret = configure_users()
	if ret != 0:
		sys.exit(ret)

	src_dir = "%s/share/selinux" % prefix.get_tl_prefix()
	ret = compile_and_install(makefile, src_dir, "thinlinc")
	if ret != 0:
		sys.exit(ret)

	ret = restorecon()
	if ret != 0:
		sys.exit(ret)

if __name__ == "__main__":
	main()
