#!/usr/bin/python3
# -*- coding: utf-8 -*-
#
# Copyright (c) 2019-2020 AT&T intellectual property.
# All rights reserved.
#
# Copyright (c) 2014, 2016-2017 Brocade Communications Systems, Inc.
# All rights reserved.
#
# SPDX-License-Identifier: GPL-2.0-only

import os
import sys
import subprocess

from Vyatta import SSSD

SERVICE_USERS = 'service-users'
CFG_SERVICE_USERS = 'resources ' + SERVICE_USERS
SCRIPT_UPDATE_LOCAL = '/opt/vyatta/sbin/vyatta_update_local_service_users'

# This VyattaConfig is just a temporary wrapper for the CLI API
# Rebase once final Python VyattaConfig bindings are available.
class VyattaConfig(object):

	VYATTA_CLI = 'cli-shell-api'

	def __init__(self):
		self.level = None

	def _popen(self, cmd):
		return subprocess.Popen([self.VYATTA_CLI] + cmd,
								stdout=subprocess.PIPE,
								universal_newlines=True)

	# subprocess.check_output not available with python 2.6, but starting with 2.7
	def _check_output(self, cmd):
		p = self._popen(cmd)
		return p.communicate()[0]

	def _check_rc(self, cmd):
		p = self._popen(cmd)
		p.communicate()
		if p.returncode == 0:
			return True
		else:
			return False

	def inSession(self):
		return self._check_rc(['inSession'])

	def setLevel(self, level):
		self.level = level.split(' ');

	def listNodes(self):
		out = self._check_output(['listNodes'] + self.level)
		ret = out.replace("'", "").split(' ')
		if ret == ['']:
			return []

		return ret

	def exists(self, field):
		if type(field) == str:
			field = [field]

		return self._check_rc(['exists'] + self.level + field)

	def returnValue(self, field):
		if type(field) == str:
			field = [field]

		ret = self._check_output(['returnValue'] + self.level + field)
		return ret.replace("'", "")


def update_ldap_profile(sssdobj, profile):
	ldap_options = {}
	config = VyattaConfig()
	config.setLevel(CFG_SERVICE_USERS + ' ldap ' + profile)

	for opt in sssdobj.LDAP_OPTS:
		sssd_optkey = sssdobj.LDAP_OPTS[opt]

		# Turn opt fields like "tls cacert" into a list: ['tls', 'cacert']
		# Otherwise the CLI API will handle this is sone string, and the CLI
		# entry will not be found.
		opt = opt.split(' ')
		if not config.exists(opt):
			continue

		ldap_options[sssd_optkey] = config.returnValue(opt)

	sssdobj.setup_ldap(profile, ldap_options)

def get_ldap_domains():
	config = VyattaConfig()
	config.setLevel(CFG_SERVICE_USERS + ' ldap')

	return config.listNodes()

def update_ldap_profiles(sssdobj):
	config = VyattaConfig()
	config.setLevel(CFG_SERVICE_USERS + ' ldap')

	for profile in get_ldap_domains():
		update_ldap_profile(sssdobj, profile)

def update_service_indepdent_pam_config(auth_methods):

	pam_config_fn = "/etc/pam.d/vyatta-" + SERVICE_USERS + ".conf"
	pam_config = """##
## This file is auto-generated by vyatta-sssd.
## Do not edit, all changes will be lost!
##
"""

	pam_deny = """
auth required pam_deny.so
account required pam_deny.so
"""

	domain_str = ""
	first_domain = True
	domain_list = []

	for auth in auth_methods:
		if auth == "local":
			domain_list.append(auth)
		elif auth == "ldap":
			for d in get_ldap_domains():
				domain_list.append(d)


	for d in domain_list:
		if first_domain:
			first_domain = False
		else:
			domain_str += ","

		domain_str += d

	pam_sss = """
auth sufficient pam_sss.so domains=%s
account sufficient pam_sss.so domains=%s
""" % (domain_str, domain_str)

	if len(auth_methods) == 0:
		pam_sss = ""

	pam_config += pam_sss + pam_deny

	with open(pam_config_fn, 'w') as f:
		f.write(pam_config)



def main():
	config = VyattaConfig()

	if not config.inSession():
		sys.stderr.write('%s needs to be run in configuration mode. Aborting.\n'
				  % (os.path.basename(sys.argv[0])))
		sys.exit(1)

	sssdobj = SSSD.SSSD()

	config.setLevel(CFG_SERVICE_USERS)

	# First detect deleted domains
	for domain in sssdobj.list_domains():
		if domain.startswith("vyatta_system_"):
			continue

		if config.exists(domain):
			continue

		# If domain got deleted, remove it from SSSD configuration

		# In case of the local domain, clean-up first.
		if domain == 'local':
			p = subprocess.Popen([SCRIPT_UPDATE_LOCAL],
								 stderr=subprocess.PIPE,
								 universal_newlines=True)
			ret = p.communicate()
			if p.returncode:
				sys.stderr.write('Failed to clean-up local service-users:\n%s' % (ret[1]))
				sys.exit(1)


		sssdobj.delete_domain(domain)

	methods = config.listNodes()
	for method in methods:
		if method == 'ldap':
			update_ldap_profiles(sssdobj);
		elif method == 'local':
			sssdobj.setup_local()
			sssdobj.commit()

			p = subprocess.Popen([SCRIPT_UPDATE_LOCAL],
								 stderr=subprocess.PIPE,
								 universal_newlines=True)
			ret = p.communicate()
			if p.returncode:
				sys.stderr.write('Failed to setup local service-users:\n%s' % (ret[1]))
				sys.exit(1)



		else:
			sys.stderr.write('Unknown service-users authentication'
					 'method "%s". Aborting.\n' % (method))
			sys.exit(1)



	sssdobj.commit()
	update_service_indepdent_pam_config(methods)
	sys.exit(0)


if __name__ == '__main__':
	main()


