# -*- coding: utf-8 -*-
#
# Copyright 2004-2022 Univention GmbH
#
# https://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
# <https://www.gnu.org/licenses/>.
"""
adduser part for the command line interface
"""
import os
import getopt
import subprocess
import six
from ldap.filter import filter_format
import univention.debug as ud
import univention.config_registry
import univention.admin.uldap
import univention.admin.config
import univention.admin.modules
import univention.admin.objects
import univention.admin.handlers.users.user
import univention.admin.handlers.groups.group
import univention.admin.handlers.computers.windows
[docs]def status(msg):
	# univention-adduser is called by Samba when doing "vampire." Since
	# vampire produces a lot of output, and we'd like to print a moderate
	# log, we prepend UNIVENTION to our output. That way we can identify
	# distinguish them from all of Samba's log messages
	out = 'UNIVENTION %s' % (msg.encode('utf-8') if six.PY2 else msg,)
	return out 
[docs]def nscd_invalidate(table):
	if table:
		ud.debug(ud.ADMIN, ud.INFO, 'NSCD: --invalidate %s' % (table,))
		try:
			subprocess.check_call(['/usr/sbin/nscd', '--invalidate', table], close_fds=True)
		except (EnvironmentError, subprocess.CalledProcessError):
			ud.debug(ud.ADMIN, ud.INFO, 'NSCD: failed')
		else:
			ud.debug(ud.ADMIN, ud.INFO, 'NSCD: ok') 
[docs]def get_user_object(user, position, lo):
	try:
		# user Account
		return univention.admin.modules.lookup(univention.admin.handlers.users.user, None, lo, scope='domain', base=position.getDn(), filter=filter_format(u'(username=%s)', [user]), required=True, unique=True)[0]
	except Exception:
		# machine Account
		for handler in [univention.admin.handlers.computers.windows, univention.admin.handlers.computers.domaincontroller_master, univention.admin.handlers.computers.domaincontroller_slave, univention.admin.handlers.computers.domaincontroller_backup, univention.admin.handlers.computers.memberserver]:
			try:
				return univention.admin.modules.lookup(handler, None, lo, scope='domain', base=position.getDn(), filter=filter_format(u'(uid=%s)', [user]), required=True, unique=True)[0]
			except Exception:
				continue
	return 'ERROR: account not found, nothing modified' 
def _decode(args):
	for arg in args:
		try:
			yield arg.decode('utf-8')
		except UnicodeDecodeError:
			yield arg.decode('latin-1')
[docs]def doit(arglist):
	ud.init('/var/log/univention/directory-manager-cmd.log', ud.FLUSH, ud.FUNCTION)
	out = []
	configRegistry = univention.config_registry.ConfigRegistry()
	configRegistry.load()
	op = 'add'
	scope = 'user'
	cmd = os.path.basename(arglist[0])
	if cmd == 'univention-addgroup':
		scope = 'group'
		op = 'add'
	elif cmd == 'univention-deluser':
		scope = 'user'
		op = 'del'
	elif cmd == 'univention-delgroup':
		scope = 'group'
		op = 'del'
	elif cmd == 'univention-addmachine':
		scope = 'machine'
		op = 'add'
	elif cmd == 'univention-delmachine':
		scope = 'machine'
		op = 'del'
	elif cmd == 'univention-setprimarygroup':
		scope = 'user'
		op = 'primarygroup'
	opts, args = getopt.getopt(arglist[1:], '', ['status-fd=', 'status-fifo='])
	try:
		lo, position = univention.admin.uldap.getAdminConnection()
	except Exception as exc:
		ud.debug(ud.ADMIN, ud.WARN, 'authentication error: %s' % (exc,))
		try:
			lo, position = univention.admin.uldap.getMachineConnection()
		except Exception as exc2:
			ud.debug(ud.ADMIN, ud.WARN, 'authentication error: %s' % (exc2,))
			out.append('authentication error: %s' % (exc,))
			out.append('authentication error: %s' % (exc2,))
			return out
	if six.PY2:
		args = list(_decode(args))
	univention.admin.modules.update()
	if len(args) == 1:
		if scope == 'machine':
			machine = args[0]
			if machine.endswith('$'):
				machine = machine[:-1]
			if configRegistry.get('samba/defaultcontainer/computer'):
				position.setDn(configRegistry['samba/defaultcontainer/computer'])
			else:
				position.setDn(univention.admin.config.getDefaultContainer(lo, 'computers/windows'))
		elif scope == 'group':
			group = args[0]
			if configRegistry.get('samba/defaultcontainer/group'):
				position.setDn(configRegistry['samba/defaultcontainer/group'])
			else:
				position.setDn(univention.admin.config.getDefaultContainer(lo, 'groups/group'))
		else:
			user = args[0]
			if configRegistry.get('samba/defaultcontainer/user'):
				position.setDn(configRegistry['samba/defaultcontainer/user'])
			else:
				position.setDn(univention.admin.config.getDefaultContainer(lo, 'users/user'))
		action = op + scope
	elif len(args) == 2:
		user, group = args
		if op == 'del':
			action = 'deluserfromgroup'
		elif op == 'primarygroup':
			action = 'setprimarygroup'
		else:
			action = 'addusertogroup'
	else:
		return out
	if action == 'adduser':
		out.append(status(u'Adding user %s' % (user,)))
		object = univention.admin.handlers.users.user.object(None, lo, position=position)
		object.open()
		object['username'] = user
		object['lastname'] = user.encode('utf-8').decode('ASCII')
		object['password'] = subprocess.check_output(['/usr/bin/makepasswd', '--minchars=8'], close_fds=True).strip().decode('ASCII', 'ignore')
		object['primaryGroup'] = univention.admin.config.getDefaultValue(lo, 'group')
		object.create()
		nscd_invalidate('passwd')
	elif action == 'deluser':
		out.append(status(u'Removing user %s' % (user,)))
		object = univention.admin.modules.lookup(univention.admin.handlers.users.user, None, lo, scope='domain', base=position.getDomain(), filter=filter_format(u'(username=%s)', [user]), required=True, unique=True)[0]
		object.open()
		object.remove()
		nscd_invalidate('passwd')
	elif action == 'addgroup':
		out.append(status(u'Adding group %s' % (group,)))
		object = univention.admin.handlers.groups.group.object(None, lo, position=position)
		object.open()
		object.options = ['posix']
		object['name'] = group
		object.create()
		nscd_invalidate('group')
	elif action == 'delgroup':
		out.append(status(u'Removing group %s' % (group,)))
		object = univention.admin.modules.lookup(univention.admin.handlers.groups.group, None, lo, scope='domain', base=position.getDomain(), filter=filter_format(u'(name=%s)', [group]), required=True, unique=True)[0]
		object.open()
		object.remove()
		nscd_invalidate('group')
	elif action == 'addusertogroup':
		if group in configRegistry.get('samba/addusertogroup/filter/group', '').split(','):
			out.append(status(u'addusertogroup: filter protects group "%s"' % (group,)))
			return out
		out.append(status(u'Adding user %s to group %s' % (user, group)))
		groupobject = univention.admin.modules.lookup(univention.admin.handlers.groups.group, None, lo, scope='domain', base=position.getDn(), filter=filter_format(u'(name=%s)', [group]), required=True, unique=True)[0]
		groupobject.open()
		userobject = get_user_object(user, position, lo)
		if isinstance(userobject, six.string_types):
			out.append(userobject)
			return out
		if userobject.dn not in groupobject['users']:
			if groupobject['users'] == [''] or groupobject['users'] == []:
				groupobject['users'] = [userobject.dn]
			else:
				groupobject['users'].append(userobject.dn)
			groupobject.modify()
			nscd_invalidate('group')
	elif action == 'deluserfromgroup':
		out.append(status(u'Removing user %s from group %s' % (user, group)))
		groupobject = univention.admin.modules.lookup(univention.admin.handlers.groups.group, None, lo, scope='domain', base=position.getDn(), filter=filter_format(u'(name=%s)', [group]), required=True, unique=True)[0]
		groupobject.open()
		userobject = get_user_object(user, position, lo)
		if isinstance(userobject, six.string_types):
			out.append(userobject)
			return out
		userobject.open()
		if userobject.dn in groupobject['users'] and userobject['primaryGroup'] != groupobject.dn:
			groupobject['users'].remove(userobject.dn)
			groupobject.modify()
			nscd_invalidate('group')
	elif action == 'addmachine':
		out.append(status(u'Adding machine %s' % (machine,)))
		object = univention.admin.handlers.computers.windows.object(None, lo, position=position)
		object.open()
		object.options = ['posix']
		object['name'] = machine
		object['primaryGroup'] = univention.admin.config.getDefaultValue(lo, 'computerGroup')
		object.create()
		nscd_invalidate('hosts')
		nscd_invalidate('passwd')
	elif action == 'delmachine':
		out.append(status(u'Removing machine %s' % (machine,)))
		object = univention.admin.modules.lookup(univention.admin.handlers.computers.windows, None, lo, scope='domain', base=position.getDomain(), filter=filter_format(u'(name=%s)', [machine]), required=True, unique=True)[0]
		object.open()
		object.remove()
		nscd_invalidate('hosts')
	elif action == 'setprimarygroup':
		out.append(status(u'Set primary group %s for user %s' % (group, user)))
		try:
			groupobject = univention.admin.modules.lookup(univention.admin.handlers.groups.group, None, lo, scope='domain', base=position.getDn(), filter=filter_format(u'(name=%s)', [group]), required=True, unique=True)[0]
		except Exception:
			out.append('ERROR: group not found, nothing modified')
			return out
		groupobject.open()
		userobject = get_user_object(user, position, lo)
		if isinstance(userobject, six.string_types):
			out.append(userobject)
			return out
		if 'samba' in userobject.options:
			userobject.options.remove('samba')
		userobject.open()
		if userobject.has_property('primaryGroup'):
			userobject['primaryGroup'] = groupobject.dn
		elif userobject.has_property('machineAccountGroup'):
			userobject['machineAccountGroup'] = groupobject.dn
		else:
			out.append('ERROR: unknown group attribute, nothing modified')
			return out
		userobject.modify()
		if userobject.dn not in groupobject['users']:
			groupobject['users'].append(userobject.dn)
			groupobject.modify()
		nscd_invalidate('group')
		nscd_invalidate('passwd')
	return out 
if __name__ == '__main__':
	import sys
	print('\n'.join(doit(sys.argv)))