# -*- coding: utf-8 -*-
#
# Univention Samba
#  listener module: manages samba privileges
#
# Copyright 2011-2018 Univention GmbH
#
# http://www.univention.de/
#
# All rights reserved.
#
# The source code of this program is made available
# under the terms of the GNU Affero General Public License version 3
# (GNU AGPL V3) as published by the Free Software Foundation.
#
# Binary versions of this program provided by Univention to you as
# well as other copyrighted, protected or trademarked materials like
# Logos, graphics, fonts, specific documentations and configurations,
# cryptographic keys etc. are subject to a license agreement between
# you and Univention and not subject to the GNU AGPL V3.
#
# In the case you use this program under the terms of the GNU AGPL V3,
# the program is provided in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public
# License with the Debian GNU/Linux or Univention distribution in file
# /usr/share/common-licenses/AGPL-3; if not, see
# <http://www.gnu.org/licenses/>.

__package__ = ''  # workaround for PEP 366
import listener
import univention.debug
import copy
import ldb
from samba.dcerpc import security
from samba.auth import system_session
from samba.param import LoadParm

lp = LoadParm()
listener.setuid(0)
lp.load('/etc/samba/smb.conf')
listener.unsetuid()
SAMBA_POLICY_LDB = "/var/lib/samba/private/privilege.ldb"

name = 'samba-privileges'
description = 'Manages samba privileges'
filter = '(&(objectClass=univentionSambaPrivileges)(sambaSID=*))'
atributes = ['univentionSambaPrivilegeList', 'sambaSID']

def handler(dn, new, old):

	# deleted -> remove all privileges
	if old and not new:
		if old.get("univentionSambaPrivilegeList") and old.get("sambaSID"):
			univention.debug.debug(univention.debug.LISTENER, univention.debug.PROCESS, "%s: remove all samba privs (%s)" % (name, old["sambaSID"][0]))
			removePrivileges(old["sambaSID"][0], "*")

	# created
	if new and not old:
		if new.get("univentionSambaPrivilegeList") and new.get("sambaSID"):
			univention.debug.debug(univention.debug.LISTENER, univention.debug.PROCESS, "%s: add new samba privs (%s)" % (name, new["sambaSID"][0]))
			addPrivileges(new["sambaSID"][0], new["univentionSambaPrivilegeList"])

	# modified
	if new and old:

		newPrivs = new.get("univentionSambaPrivilegeList")
		oldPrivs = old.get("univentionSambaPrivilegeList")
		sid = new["sambaSID"][0]

		# removed
		if not newPrivs and oldPrivs:
			univention.debug.debug(univention.debug.LISTENER, univention.debug.PROCESS, "%s: remove all samba privs (%s)" % (name, sid))
			removePrivileges(sid, oldPrivs)
		# added
		if newPrivs and not oldPrivs:
			univention.debug.debug(univention.debug.LISTENER, univention.debug.PROCESS, "%s: add new samba privs (%s)" % (name, sid))
			addPrivileges(sid, newPrivs)

		# modified
		if newPrivs and oldPrivs and not newPrivs == oldPrivs:
			univention.debug.debug(univention.debug.LISTENER, univention.debug.PROCESS, "%s: modify samba privs (%s)" % (name, sid))
			removePrivileges(sid, oldPrivs)
			addPrivileges(sid, newPrivs)


def initialize():
	pass


def clean():
	pass


def postrun():
	pass


def addPrivileges(sambaSID, privileges):

	listener.setuid(0)

	try:
		privilege_ldb = ldb.Ldb(SAMBA_POLICY_LDB, session_info=system_session(), lp=lp)
                res = privilege_ldb.search('', ldb.SCOPE_SUBTREE, "(&($bjectClass=privilege)(objectSid=%s))" % sambaSID, attrs=["privilege"])
		if res:
                        record = res.msgs[0]
                        old_privs = record["privilege"]

			new_privs = copy.deepcopy(old_privs)
			for privilege in privileges:
				if security.privilege_id(privilege) == 0:
					univention.debug.error(univention.debug.LISTENER, univention.debug.INFO, "%s: ignoring invalid privilege %s (%s)" % (name, privilege, sambaSID))
					continue
				if privilege not in old_privs:
					new_privs.append(privilege)

			if new_privs != old_privs:
				msg = ldb.Message()
				msg.dn = ldb.Dn(privilege_ldb, record.dn)
				msg["priv"] = ldb.MessageElement([new_privs], ldb.FLAG_MOD_REPLACE, "privilege")
				privilege_ldb.modify(msg)
		else:
			new_privs = []
			for privilege in privileges:
				if security.privilege_id(privilege) == 0:
					univention.debug.error(univention.debug.LISTENER, univention.debug.INFO, "%s: ignoring invalid privilege %s (%s)" % (name, privilege, sambaSID))
					continue
				new_privs.append(privilege)
			privilege_ldb.add({
				"dn": "sid=%s" % sambaSID,
				"objectClass": "privilege",
				"privilege": new_privs
			})
	finally:
		listener.unsetuid()


def removePrivileges(sambaSID, privileges):

	listener.setuid(0)

	try:
		privilege_ldb = ldb.Ldb(SAMBA_POLICY_LDB, session_info=system_session(), lp=lp)
                res = privilege_ldb.search('', ldb.SCOPE_SUBTREE, "(&($bjectClass=privilege)(objectSid=%s))" % sambaSID, attrs=["privilege"])
		if res:
                        record = res.msgs[0]
			if privileges == "*":
				privilege_ldb.delete(record.dn)
				return

                        old_privs = record["privilege"]

			new_privs = copy.deepcopy(old_privs)
			for privilege in privileges:
				if security.privilege_id(privilege) == 0:
					univention.debug.error(univention.debug.LISTENER, univention.debug.INFO, "%s: ignoring invalid privilege %s (%s)" % (name, privilege, sambaSID))
					continue
				if privilege in old_privs:
					new_privs.remove(privilege)

			if new_privs != old_privs:
				if new_privs:
					msg = ldb.Message()
					msg.dn = ldb.Dn(privilege_ldb, record.dn)
					msg["priv"] = ldb.MessageElement([new_privs], ldb.FLAG_MOD_REPLACE, "privilege")
					privilege_ldb.modify(msg)
				else:
					privilege_ldb.delete("sid=%s" % sambaSID)
	finally:
		listener.unsetuid()
