#!/usr/bin/env python

from optparse import OptionParser
from univention.config_registry import ConfigRegistry
from univention.config_registry import handler_unset
from univention.uldap import getMachineConnection
import sys
import getpass
import os
import psutil
import re
import subprocess
import time
import univention.admin.config
import univention.admin.modules
import univention.admin.uldap
import univention.uldap

ACCEPTED_ROLES = [ # List of tuples (server/role, ucsschool/singlemaster)
		('domaincontroller_master', True),
		('domaincontroller_slave', False),
		]

ucr = ConfigRegistry()
ucr.load()

class SimpleSamba(object):

	def __init__(self,):
		pass

	def stop(self,):
		for script in ('/etc/init.d/samba', '/etc/init.d/samba4', 'etc/init.d/samba-ad-dc'):
			if os.path.isfile(script):
				pop = subprocess.Popen([script, 'stop'])
				pop.communicate()
				if not pop.returncode:
					self.wait_to_stop()

	def start(self,):
		for script in ('/etc/init.d/samba', '/etc/init.d/samba4', 'etc/init.d/samba-ad-dc'):
			if os.path.isfile(script):
				pop = subprocess.Popen([script, 'start'])
				pop.communicate()
				if not pop.returncode:
					self.wait_to_start()

	def restart(self):
		if self.is_running():
			self.stop()
		self.start()

	def is_running(self,):
		try:
			samba = [(x.pid, x.name) for x in psutil.process_iter() if x.name=='samba']
			smbd = [(x.pid, x.name) for x in psutil.process_iter() if x.name=='smbd']
		except psutil.NoSuchProcess:
			pass
		else:
			return smbd or samba

	def wait_to_stop(self,duration=60):
		print '** Waiting for samba to STOP ... (max %dsec)' % duration
		for i in xrange(duration):
			if not self.is_running():
				break
			else:
				time.sleep(1)

	def wait_to_start(self,duration=60):
		print '** Waiting for samba to START ... (max %dsec)' % duration
		for i in xrange(duration):
			if self.is_running():
				break
			else:
				time.sleep(1)


class Member(object):

	def __init__(self, dn, access, usersmod, force=False):
		self.dn = dn
		self.access = access
		self.force = force
		self.user_obj = usersmod.object(None, access, None, dn)
		self.user_obj.open()
		self.old_home = '/home/%s' % (self.name(),)
		self.new_home = '/home/%s/%s/%s' % (self.school(), self.role(), self.name())

	def name(self):
		return self.user_obj['username']

	def role(self):
		found = re.search(r'cn=(schueler|lehrer|mitarbeiter|lehrer und mitarbeiter),', self.dn)
		if found:
			if found.groups()[0] == 'lehrer und mitarbeiter':
				return 'lehrer'
			return found.groups()[0]

	def school(self):
		found = re.search(r'ou=([^,]+),', self.dn)
		if found:
			return found.groups()[0]

	def home_directory(self,):
		return self.user_obj['unixhome']

	def alter_home_directory(self,):
		if not self.role(): # skipping e.g. exam users
			return

		if self.user_obj['unixhome'] == self.new_home:
			if self.old_home_directory_exists() and not self.new_home_directory_exists():
				self._move_home_directory()
			else:
				print 'User %s has already been converted.' % (self.user_obj['username'], )
		elif self.user_obj['unixhome'] == self.old_home or self.force:
			self.user_obj['unixhome'] = self.new_home
			self.user_obj.modify()
			self._move_home_directory()
		else:
			print 'WARNING: old home directory of user %s is not set to the expected value' % (self.user_obj['username'], )
			print '         current value: %r' % (self.user_obj['unixhome'], )
			print '         expected value: %r' % (self.old_home, )
			print '         Skipping user with DN %r' % (self.user_obj.dn,)

	def old_home_directory_exists(self):
		return os.path.isdir(self.old_home)

	def new_home_directory_exists(self):
		return os.path.isdir(self.new_home)

	def _move_home_directory(self):
		if self.old_home_directory_exists():
			print 'Moving home directory of user %s' % self.name()
			# use system's mv command to keep permissions AND owners (shutil.move might loose them over partition boundaries)
			subprocess.call(['mv', '--', self.old_home, self.new_home])
		else:
			if self.new_home_directory_exists():
				print 'Home directory of user %s has already been updated to the new directory structure' % (self.name(),)
			else:
				print 'Home directory of user %s does not exist' % (self.name(),)


def server_role():
	return (
		ucr.get('server/role'),
		ucr.is_true('ucsschool/singlemaster', False)
	)

def update_roleshare_ucr_variable():
	if 'ucsschool/import/roleshare' in ucr:
		print 'Unsetting UCR variable ucsschool/import/roleshare...'
		handler_unset(['ucsschool/import/roleshare'])
		print 'done'

def main():
	usage = "%prog <group name>"
	parser = OptionParser(usage=usage)
	parser.add_option('--username', action='store', dest='username', help = 'Username for LDAP connection' )
	parser.add_option('--password', action='store', dest='password', help = 'Password for LDAP connection' )
	parser.add_option('--force', action='store_true', dest='force', default=False, help = 'always convert home directory, even if the old value does not match to the expected value' )
	(options, args) = parser.parse_args()

	if len(args) < 1:
		parser.error('A group name has to be specified!')
		sys.exit(2)
	role = server_role()
	if role not in ACCEPTED_ROLES:
		print 'This script can be executed in the following environments only:'
		print '- domaincontroller master in UCS@school single server environment'
		print '- domaincontroller slave in UCS@school multi server environment'
		print 'Stopping here.'
		sys.exit(3)
	groupname = args[0]

	# Get username and password for ldap authentication
	if options.username and options.password:
		try:
			lo = getMachineConnection()
			binddn = lo.search(filter="(uid=%s)" % (options.username,))[0][0]
			print 'Connecting as %s to LDAP...' % (binddn, )
			access = univention.admin.uldap.access(
					host=ucr.get('ldap/master'),
					port=int(ucr.get('ldap/master/port', '7389')),
					base=ucr.get('ldap/base'),
					binddn=binddn,
					bindpw=options.password)
		except IndexError:
			print "ERROR: user %s does not exist" % (options.username, )
			sys.exit(4)
		except univention.admin.uexceptions.authFail:
			print 'ERROR: username or password is incorrect'
			sys.exit(5)
		position = univention.admin.uldap.position(ucr['ldap/base'])
	else:
		if role[0] == 'domaincontroller_slave':
			user = raw_input('User: ')
			bindpwd = getpass.getpass('Password: ')
			try:
				lo = getMachineConnection()
				binddn = lo.search(filter="(uid=%s)" % (user,))[0][0]
				print 'Connecting as %s to LDAP...' % (binddn, )
				access = univention.admin.uldap.access(
						host=ucr.get('ldap/master'),
						port=int(ucr.get('ldap/master/port', '7389')),
						base=ucr.get('ldap/base'),
						binddn=binddn,
						bindpw=bindpwd)
			except IndexError:
				print "ERROR: user %s does not exist" % (user,)
				sys.exit(4)
			except univention.admin.uexceptions.authFail:
				print 'ERROR: username or password is incorrect'
				sys.exit(5)
			position = univention.admin.uldap.position(ucr['ldap/base'])
		else:
			access, position = univention.admin.uldap.getAdminConnection()

	update_roleshare_ucr_variable()

	config = univention.admin.config.config()
	univention.admin.modules.update()
	usersmod = univention.admin.modules.get("users/user")
	univention.admin.modules.init(access, position, usersmod)
	groupmod = univention.admin.modules.get("groups/group")
	univention.admin.modules.init(access, position, groupmod)
	groupobjlist = groupmod.lookup(config, access, 'name=%s' % (groupname,))

	try:
		group = groupobjlist[0]
	except IndexError:
		print "ERROR: group %s does not exist!" % (groupname,)
		sys.exit(6)
	if not group.open():
		members = [Member(x, access, usersmod) for x in group['users'] if re.match(
			r'^uid=.+,ou=([^,]+),%s$' % ucr.get('ldap/base'), x)]
		if members:
			samba = SimpleSamba()
			samba.stop()
			try:
				for member in members:
					member.alter_home_directory()
			except univention.admin.uexceptions.permissionDenied:
				print 'ERROR: user with LDAP binddn %s has insufficient access privileges' % (binddn,)
			samba.restart()
		else:
			print 'The specified group %r contains no members.' % (groupname,)
			sys.exit(0)


if __name__ == '__main__':
	main()
