#! /usr/bin/env python
#
# Copyright (C) 2011-2015 uib GmbH - http://www.uib.de
# All rights reserved.
#

import codecs
import getopt
import grp
import json
import ldap
import os
import pwd
import re
import shutil
import socket
import sys
import time

import OPSI.System.Posix as Posix
import OPSI.Util.Task.ConfigureBackend as backendUtils
from OPSI.Backend.Backend import ExtendedConfigDataBackend
from OPSI.Backend.File import *
from OPSI.Backend.JSONRPC import *
from OPSI.Backend.LDAP import *
from OPSI.Backend.MySQL import *
from OPSI.Logger import LOG_DEBUG, LOG_NONE, LOG_INFO, LOG_NOTICE, Logger
from OPSI.Object import *
from OPSI.System import *
from OPSI.System.Posix import getDHCPDRestartCommand
from OPSI.Types import *
from OPSI.UI import *
from OPSI.Util.File import *
from OPSI.Util.File.Opsi import *
from OPSI.Util import *
from OPSI.Util.Task.Certificate import (DEFAULT_CERTIFICATE_PARAMETERS,
	OPSICONFD_CERTFILE, NoCertificateError, UnreadableCertificateError,
	createCertificate, loadConfigurationFromCertificate, renewCertificate)
from OPSI.Util.Task.CleanupBackend import cleanupBackend
from OPSI.Util.Task.ConfigureBackend.ConfigurationData import initializeConfigs
from OPSI.Util.Task.ConfigureBackend.MySQL import (
	DatabaseConnectionFailedException,
	configureMySQLBackend as configureMySQLBackendWithoutGUI
)
from OPSI.Util.Task.Rights import setRights
from OPSI.Util.Task.Sudoers import (
	patchSudoersFileForOpsi, patchSudoersFileToAllowRestartingDHCPD
)
from OPSI.Util.Task.UpdateBackend.MySQL import updateMySQLBackend


logger = Logger()

logger.setConsoleLevel(LOG_NOTICE)
logger.setConsoleColor(True)

LOG_FILE = u'/tmp/opsi-setup.log'
SMB_CONF = u'/etc/samba/smb.conf'
DHCPD_CONF = Posix.locateDHCPDConfig(u'/etc/dhcp3/dhcpd.conf')

OPSICONFD_USER = u'opsiconfd'
ADMIN_GROUP = u'opsiadmin'
CLIENT_USER = u'pcpatch'
FILE_ADMIN_GROUP = u'pcpatch'

OPSI_CONF = u'/etc/opsi/opsi.conf'
OPSI_GLOBAL_CONF = u'/etc/opsi/global.conf'

try:
	FILE_ADMIN_GROUP = OpsiConfFile(OPSI_CONF).getOpsiFileAdminGroup()
except Exception:
	FILE_ADMIN_GROUP = u'pcpatch'

sysConfig = {}
ipAddress = None


# TODO: use Classes in Posix.py
def getDistributor():
	distributor = ''
	try:
		f = os.popen('lsb_release -i 2>/dev/null')
		distributor = f.read().split(':')[1].strip()
		f.close()
	except:
		pass
	return distributor


def getDistribution():
	distribution = ''
	try:
		f = os.popen('lsb_release -d 2>/dev/null')
		distribution = f.read().split(':')[1].strip()
		f.close()
	except:
		pass
	return distribution


def isSamba4():
	samba4 = False
	try:
		smbd = which('smbd')
		result = execute('%s -V 2>/dev/null' % smbd)
		for line in result:
				if line.lower().startswith("version"):
						samba4 = line.split()[1].startswith('4')
	except Exception:
		pass
	return samba4


# TODO: use OPSI.System.Posix.Sysconfig for a more standardized approach
def getSysConfig():
	global sysConfig
	if sysConfig:
		return sysConfig

	sysConfig['distributor'] = getDistributor()
	sysConfig['distribution'] = getDistribution()

	if not sysConfig['distributor'] or not sysConfig['distribution']:
		logger.warning(u"Failed to get distributor/distribution")

	logger.notice(u"Getting current system config")
	if ipAddress:
		sysConfig['ipAddress'] = ipAddress
	try:
		sysConfig['fqdn'] = forceHostId(getfqdn(conf=OPSI_GLOBAL_CONF))
	except:
		raise Exception(u"Failed to get fully qualified domain name, got '%s'" % getfqdn(conf=OPSI_GLOBAL_CONF))

	sysConfig['hostname'] = sysConfig['fqdn'].split(u'.')[0]
	sysConfig['domain'] = u'.'.join(sysConfig['fqdn'].split(u'.')[1:])
	if 'ipAddress' not in sysConfig:
		sysConfig['ipAddress'] = socket.gethostbyname(sysConfig['fqdn'])
		if sysConfig['ipAddress'].split(u'.')[0] in ('127', '169'):
			sysConfig['ipAddress'] = None
	sysConfig['hardwareAddress'] = None

	for device in getEthernetDevices():
		devconf = getNetworkDeviceConfig(device)
		if devconf['ipAddress'] and devconf['ipAddress'].split(u'.')[0] not in ('127', '169'):
			if not sysConfig['ipAddress']:
				sysConfig['ipAddress'] = devconf['ipAddress']
			if (sysConfig['ipAddress'] == devconf['ipAddress']):
				sysConfig['netmask'] = devconf['netmask']
				sysConfig['hardwareAddress'] = devconf['hardwareAddress']
				break

	if not sysConfig['ipAddress']:
		raise Exception(u"Failed to get a valid ip address for fqdn '%s'" % sysConfig['fqdn'])

	if not sysConfig.get('netmask'):
		sysConfig['netmask'] = u'255.255.255.0'

	sysConfig['broadcast'] = u''
	sysConfig['subnet'] = u''
	for i in range(4):
		if sysConfig['broadcast']:
			sysConfig['broadcast'] += u'.'
		if sysConfig['subnet']:
			sysConfig['subnet']    += u'.'

		sysConfig['subnet'] += u'%d' % ( int(sysConfig['ipAddress'].split(u'.')[i]) & int(sysConfig['netmask'].split(u'.')[i]) )
		sysConfig['broadcast'] += u'%d' % ( int(sysConfig['ipAddress'].split(u'.')[i]) | int(sysConfig['netmask'].split(u'.')[i]) ^ 255 )

	sysConfig['winDomain'] = u''
	if os.path.exists(SMB_CONF):
		f = codecs.open(SMB_CONF, 'r', 'utf-8')
		for line in f.readlines():
			match = re.search('^\s*workgroup\s*=\s*(\S+)\s*$', line)
			if match:
				sysConfig['winDomain'] = match.group(1).upper()
				break
		f.close()

	logger.notice(u"System information:")
	logger.notice(u"   distributor  : %s" % sysConfig['distributor'])
	logger.notice(u"   distribution : %s" % sysConfig['distribution'])
	logger.notice(u"   ip address   : %s" % sysConfig['ipAddress'])
	logger.notice(u"   netmask      : %s" % sysConfig['netmask'])
	logger.notice(u"   subnet       : %s" % sysConfig['subnet'])
	logger.notice(u"   broadcast    : %s" % sysConfig['broadcast'])
	logger.notice(u"   fqdn         : %s" % sysConfig['fqdn'])
	logger.notice(u"   hostname     : %s" % sysConfig['hostname'])
	logger.notice(u"   domain       : %s" % sysConfig['domain'])
	logger.notice(u"   win domain   : %s" % sysConfig['winDomain'])

	return sysConfig


def configureSamba():
	logger.notice(u"Configuring samba")

	smb_init_command = u'service {name}'.format(name=Posix.getSambaServiceName(default="smbd"))

	f = codecs.open(SMB_CONF, 'r', 'utf-8')
	lines = f.readlines()
	f.close()
	newlines = []
	optPcbinShareFound = False
	depotShareFound = False
	depotShareRWFound = False
	configShareFound = False
	workbenchShareFound = False
	opsiImagesFound = False
	confChanged = False
	samba4 = isSamba4()

	for i in range(len(lines)):
		if (lines[i].lower().strip() == '; load opsi shares') and ((i+1) < len(lines)) and (lines[i+1].lower().strip() == 'include = /etc/samba/share.conf'):
			i += 1
			confChanged = True
			continue
		if (lines[i].lower().strip() == '[opt_pcbin]'):
			optPcbinShareFound = True
		elif (lines[i].lower().strip() == '[opsi_depot]'):
			depotShareFound = True
		elif (lines[i].lower().strip() == '[opsi_depot_rw]'):
			depotShareRWFound = True
		elif (lines[i].lower().strip() == '[opsi_images]'):
			opsiImagesFound = True
		elif (lines[i].lower().strip() == '[opsi_config]'):
			configShareFound = True
		elif (lines[i].lower().strip() == '[opsi_workbench]'):
			workbenchShareFound = True
		newlines.append(lines[i])

	if optPcbinShareFound:
		logger.warning(u"Share opt_pcbin configuration found in '%s'. You should use opsi_depot_rw instead, if you need a writeable depot-Share." % SMB_CONF)

	if not depotShareFound:
		logger.notice(u"   Adding share [opsi_depot]")
		confChanged = True
		newlines.append(u"[opsi_depot]\n")
		newlines.append(u"   available = yes\n")
		newlines.append(u"   comment = opsi depot share (ro)\n")
		newlines.append(u"   path = /var/lib/opsi/depot\n")
		newlines.append(u"   oplocks = no\n")
		newlines.append(u"   follow symlinks = yes\n")
		newlines.append(u"   level2 oplocks = no\n")
		newlines.append(u"   writeable = no\n")
		newlines.append(u"   invalid users = root\n")
		if samba4:
			newlines.append(u"   admin users = @%s\n" % FILE_ADMIN_GROUP)
		newlines.append(u"\n")

		depotDir = '/var/lib/opsi/depot'
		if not os.path.exists(depotDir):
			try:
				os.mkdir(depotDir)
				if os.path.exists("/opt/pcbin/install"):
					logger.warning(u"You have an old depot configuration. Using /opt/pcbin/install is depracted, please youse /var/lib/opsi/depot instead.")
			except Exception as e:
				logger.warning(u"Failed to create depot directory '%s': %s" % (depotDir, e))
	elif samba4:
		logger.notice(u"   Share opsi_depot found and samba 4 is detected. Trying to detect the executablefix for opsi_depot-Share")
		startpos = 0
		endpos = 0
		found = False
		fixedLines = []
		sectionFound = False
		for i in range(len(lines)):
			if lines[i].lower().strip() == '[opsi_depot]':
				startpos = endpos = i + 1
				sectionFound = True
				slicedList = lines[startpos:]
				for z in range(len(slicedList)):
					line = slicedList[z].lower().strip()
					if line == "admin users = @%s" % FILE_ADMIN_GROUP:
						logger.notice(u"   fix found, nothing to do")
						found = True
						break
					elif line.startswith("[") or not line:
						logger.notice(u"   End of section detected")
						break
					else:
						endpos += 1
			if sectionFound:
				break

		if not found:
			logger.notice(u"   Section found but don't inherits samba4 fix, trying to set the fix.")
			fixedLines = lines
			fixedLines.insert(endpos, u"   admin users = @%s\n" % FILE_ADMIN_GROUP)
			with codecs.open(SMB_CONF, 'w', 'utf-8') as f:
				f.writelines(fixedLines)
			logger.notice(u"   Reloading samba")
			try:
				execute(u'%s reload' % smb_init_command)
			except Exception as e:
				logger.warning(e)

	if not depotShareRWFound:
		logger.notice(u"   Adding share [opsi_depot_rw]")
		confChanged = True
		newlines.append(u"[opsi_depot_rw]\n")
		newlines.append(u"   available = yes\n")
		newlines.append(u"   comment = opsi depot share (rw)\n")
		newlines.append(u"   path = /var/lib/opsi/depot\n")
		newlines.append(u"   oplocks = no\n")
		newlines.append(u"   follow symlinks = yes\n")
		newlines.append(u"   level2 oplocks = no\n")
		newlines.append(u"   writeable = yes\n")
		newlines.append(u"   invalid users = root\n")
		newlines.append(u"\n")

	if not opsiImagesFound:
		logger.notice(u"   Adding share [opsi_images]")
		confChanged = True
		newlines.append(u"[opsi_images]\n")
		newlines.append(u"   available = yes\n")
		newlines.append(u"   comment = opsi ntfs images share (rw)\n")
		newlines.append(u"   path = /var/lib/opsi/ntfs-images\n")
		newlines.append(u"   oplocks = no\n")
		newlines.append(u"   level2 oplocks = no\n")
		newlines.append(u"   writeable = yes\n")
		newlines.append(u"   invalid users = root\n")
		newlines.append(u"\n")
		if not os.path.exists("/var/lib/opsi/ntfs-images"):
			logger.debug(u"Path:  /var/lib/opsi/ntfs-images not found: creating.")
			os.mkdir("/var/lib/opsi/ntfs-images")

	if not configShareFound:
		logger.notice(u"   Adding share [opsi_config]")
		confChanged = True
		newlines.append(u"[opsi_config]\n")
		newlines.append(u"   available = yes\n")
		newlines.append(u"   comment = opsi config share\n")
		newlines.append(u"   path = /var/lib/opsi/config\n")
		newlines.append(u"   writeable = yes\n")
		newlines.append(u"   invalid users = root\n")
		newlines.append(u"\n")

	if not workbenchShareFound:
		logger.notice(u"   Adding share [opsi_workbench]")
		confChanged = True
		newlines.append(u"[opsi_workbench]\n")
		newlines.append(u"   available = yes\n")
		newlines.append(u"   comment = opsi workbench\n")
		if (getSysConfig()['distribution'].lower().find('suse linux enterprise server') != -1):
			newlines.append(u"   path = /var/lib/opsi/workbench\n")
		else:
			newlines.append(u"   path = /home/opsiproducts\n")
		newlines.append(u"   writeable = yes\n")
		newlines.append(u"   invalid users = root\n")
		newlines.append(u"   create mask = 0660\n")
		newlines.append(u"   directory mask = 0770\n")
		newlines.append(u"\n")

	if confChanged:
		logger.notice(u"   Creating backup of %s" % SMB_CONF)
		shutil.copy(SMB_CONF, SMB_CONF + u'.' + time.strftime("%Y-%m-%d_%H:%M"))

		logger.notice(u"   Writing new smb.conf")
		f = codecs.open(SMB_CONF, 'w', 'utf-8')
		lines = f.writelines(newlines)
		f.close()

		logger.notice(u"   Reloading samba")
		try:
			execute(u'%s reload' % smb_init_command)
		except Exception as e:
			logger.warning(e)


def configureDHCPD():
	logger.notice(u"Configuring dhcpd")

	if not os.path.exists(DHCPD_CONF):
		logger.warning("Can't find an dhcpd.conf. Aborting configuration.")
		return

	dhcpdConf = DHCPDConfFile(DHCPD_CONF)
	dhcpdConf.parse()

	confChanged = False
	if dhcpdConf.getGlobalBlock().getParameters_hash().get('use-host-decl-names', False):
		logger.info(u"   use-host-decl-names already enabled")
	else:
		confChanged = True
		dhcpdConf.getGlobalBlock().addComponent(
			DHCPDConf_Parameter(
				startLine=-1,
				parentBlock=dhcpdConf.getGlobalBlock(),
				key='use-host-decl-names',
				value=True
			)
		)

	subnets = dhcpdConf.getGlobalBlock().getBlocks('subnet', recursive=True)
	if not subnets:
		confChanged = True
		logger.notice(u"   No subnets found, adding subnet")
		dhcpdConf.getGlobalBlock().addComponent(
			DHCPDConf_Block(
				startLine=-1,
				parentBlock=dhcpdConf.getGlobalBlock(),
				type='subnet',
				settings=['subnet', getSysConfig()['subnet'], 'netmask', getSysConfig()['netmask']] ) )

	for subnet in dhcpdConf.getGlobalBlock().getBlocks('subnet', recursive=True):
		logger.info(u"   Found subnet %s/%s" % (subnet.settings[1], subnet.settings[3]))
		groups = subnet.getBlocks('group')
		if not groups:
			confChanged = True
			logger.notice(u"      No groups found, adding group")
			subnet.addComponent(
				DHCPDConf_Block(
					startLine=-1,
					parentBlock=subnet,
					type= 'group',
					settings=['group']
				)
			)

		for group in subnet.getBlocks('group'):
			logger.info(u"      Configuring group")
			params = group.getParameters_hash(inherit = 'global')
			if params.get('next-server'):
				logger.info(u"         next-server already set")
			else:
				confChanged = True
				group.addComponent(
					DHCPDConf_Parameter(
						startLine=-1,
						parentBlock=group,
						key='next-server',
						value=getSysConfig()['ipAddress']
					)
				)
				logger.notice(u"   next-server set to %s" % getSysConfig()['ipAddress'])
			if params.get('filename'):
				logger.info(u"         filename already set")
			else:
				confChanged = True
				filename = 'linux/pxelinux.0'
				if (getSysConfig()['distribution'].lower().find('suse linux enterprise server') != -1):
					filename = 'opsi/pxelinux.0'
				group.addComponent(
					DHCPDConf_Parameter(
						startLine=-1,
						parentBlock=group,
						key='filename',
						value=filename
					)
				)
				logger.notice(u"         filename set to %s" % filename)

	restartCommand = getDHCPDRestartCommand(default=u'/etc/init.d/dhcp3-server restart')
	if confChanged:
		logger.notice(u"   Creating backup of %s" % DHCPD_CONF)
		shutil.copy(DHCPD_CONF, DHCPD_CONF + u'.' + time.strftime("%Y-%m-%d_%H:%M"))

		logger.notice(u"   Writing new %s" % DHCPD_CONF)
		dhcpdConf.generate()

		logger.notice(u"   Restarting dhcpd")
		try:
			execute(restartCommand)
		except Exception as e:
			logger.warning(e)

	logger.notice(u"Configuring sudoers")
	patchSudoersFileToAllowRestartingDHCPD(restartCommand)

	opsiconfdUid  = pwd.getpwnam(OPSICONFD_USER)[2]
	adminGroupGid = grp.getgrnam(ADMIN_GROUP)[2]
	os.chown(DHCPD_CONF, opsiconfdUid, adminGroupGid)
	os.chmod(DHCPD_CONF, 0664)

	sysinfo = SysInfo()
	if 'Red Hat Enterprise Linux' in sysinfo.distribution or 'CentOS' in sysinfo.distribution:
		dhcpDir = os.path.dirname(DHCPD_CONF)
		if dhcpDir == '/etc':
			return

		logger.notice(
			'Detected Red Hat-family system. Providing rights on "{dir}" '
			'to group "{group}"'.format(dir=dhcpDir, group=ADMIN_GROUP)
		)
		os.chown(dhcpDir, -1, adminGroupGid)


def configureClientUser():
	logger.notice(u"Configuring client user %s" % CLIENT_USER)

	clientUserHome = pwd.getpwnam(CLIENT_USER)[5]
	sshDir = os.path.join(clientUserHome, '.ssh')

	if os.path.exists(sshDir):
		shutil.rmtree(sshDir)

	idRsa = os.path.join(sshDir, u'id_rsa')
	idRsaPub = os.path.join(sshDir, u'id_rsa.pub')
	authorizedKeys = os.path.join(sshDir, u'authorized_keys')
	if not os.path.exists(sshDir):
		os.mkdir(sshDir)
	if not os.path.exists(idRsa):
		logger.notice(u"   Creating RSA private key for user %s in '%s'" % (CLIENT_USER, idRsa))
		execute(u"%s -N '' -t rsa -f %s" % ( which('ssh-keygen'), idRsa))
	if not os.path.exists(authorizedKeys):
		f = codecs.open(idRsaPub, 'r', 'utf-8')
		f2 = codecs.open(authorizedKeys, 'w', 'utf-8')
		f2.write(f.read())
		f2.close()
		f.close()
	setRights(sshDir)

	password = None
	backend = None
	try:
		from OPSI.Backend.BackendManager import BackendManager
		backend = BackendManager(
			dispatchConfigFile=u'/etc/opsi/backendManager/dispatch.conf',
			backendConfigDir=u'/etc/opsi/backends',
			extensionConfigDir=u'/etc/opsi/backendManager/extend.d',
			depotBackend=True
		)
		depot = backend.host_getObjects(type='OpsiDepotserver', id=getSysConfig()['fqdn'])[0]

		configserverId = None
		for configserver in backend.host_getObjects(type='OpsiConfigserver'):
			if configserver.id == getSysConfig()['fqdn']:
				configserverId = None
				break
			else:
				configserverId = configserver.id

		if configserverId:
			try:
				jsonrpcBackend = JSONRPCBackend(address=configserverId, username=depot.id, password=depot.opsiHostKey)
				password = blowfishDecrypt(depot.opsiHostKey, jsonrpcBackend.user_getCredentials(username=u'pcpatch', hostId=depot.id)['password'])
				jsonrpcBackend.backend_exit()
			except Exception as e:
				logger.info(u"Failed to get client user (pcpatch) password from configserver: %s" % e)

		if not password:
			password = blowfishDecrypt(depot.opsiHostKey, backend.user_getCredentials(username=u'pcpatch', hostId=depot.id)['password'])
	except Exception as e:
		logger.info(u"Failed to get client user (pcpatch) password: %s" % e)
		password = randomString(12)

	if backend is not None:
		backend.backend_exit()

	logger.addConfidentialString(password)
	execute('opsi-admin -d task setPcpatchPassword "%s"' % password)


def update(fromVersion=None):
	# 3.x => 4.x
	if os.path.exists(u'/var/lib/opsi/products'):
		logger.notice(u"Found /var/lib/opsi/products, moving to /var/lib/opsi/repository")
		if not os.path.exists(u'/var/lib/opsi/repository'):
			os.mkdir(u'/var/lib/opsi/repository')
		for f in os.listdir(u'/var/lib/opsi/products'):
			shutil.move(os.path.join(u'/var/lib/opsi/products', f), os.path.join(u'/var/lib/opsi/repository', f))
		try:
			os.rmdir(u'/var/lib/opsi/products')
		except Exception as e:
			logger.warning(e)

	isConfigServer = False
	try:
		bdc = BackendDispatchConfigFile(u'/etc/opsi/backendManager/dispatch.conf')
		dispatchConfig = bdc.parse()
		for entry in dispatchConfig:
			(regex, backends) = entry
			if not re.search(regex, 'backend_createBase'):
				continue
			if not 'jsonrpc' in backends:
				isConfigServer = True
			break
	except Exception as e:
		logger.warning(e)

	if isConfigServer:
		try:
			from OPSI.Backend.BackendManager import BackendManager
			backend = BackendManager(
				dispatchConfigFile=u'/etc/opsi/backendManager/dispatch.conf',
				backendConfigDir=u'/etc/opsi/backends',
				extensionConfigDir=u'/etc/opsi/backendManager/extend.d',
				depotbackend=False
			)
			backend.backend_createBase()
			backend.backend_exit()
		except Exception as e:
			logger.warning(e)

	# 4.0 => 4.0.1
	"""
	link = '/var/lib/opsi/depot'
	source = '/opt/pcbin/install'
	if not os.path.exists(link) and os.path.exists(source):
		try:
			os.symlink(source, link)
		except Exception, e:
			logger.warning(u"Failed to create symlink '%s' pointing to '%s': %s" % (link, source, e))
	"""

	# 4.0.3 => 4.0.4
	depotDir = '/var/lib/opsi/depot'
	if not os.path.exists(depotDir):
		try:
			os.mkdir(depotDir)
			if os.path.exists("/opt/pcbin/install"):
				logger.warning(u"You have an old depot configuration. Using /opt/pcbin/install is depracted, please youse /var/lib/opsi/depot instead.")
		except Exception as e:
			logger.warning(u"Failed to create depot directory '%s': %s" % (depotDir, e))


	if isConfigServer:
		initializeConfigs()
	configureSamba()


def updateUniventionBackend():
	return updateLDAPBackend(backendConfigFile=u'/etc/opsi/backends/univention.conf')


def updateLDAPBackend(backendConfigFile=u'/etc/opsi/backends/ldap.conf'):
	l = {'socket': socket, 'os': os, 'sys': sys, 'module': '', 'config': {}}
	logger.info(u"Loading backend config '%s'" % backendConfigFile)
	execfile(backendConfigFile, l)
	config = l['config']
	config.update(backendConfig)
	baseDn = l['baseDn']
	logger.info(u"Current ldap backend config: %s" % config)

	logger.notice(u"Creating ldap backend instance")
	backend = ExtendedConfigDataBackend(LDAPBackend(**config))

	ldapSession = LDAPSession(address=config['address'], username=config['username'], password=config['password'])
	ldapSession.connect()

	logger.notice(u"Testing ldap schema")

	try:
		ldapObj = LDAPObject(u"cn=opsi-config-test,%s" % config['opsiBaseDn'])
		ldapObj.new("opsiProductOnClient")
		ldapObj.setAttribute('opsiProductId', ['test'])
		ldapObj.setAttribute('opsiClientId', ['test.domain.tld'])
		ldapObj.setAttribute('opsiProductType', ['LocalbootProduct'])
		ldapObj.writeToDirectory(ldapSession)
		ldapObj.deleteFromDirectory(ldapSession)
	except Exception as e:
		raise Exception(u'Test of opsi-ldap 4.0 schema failed: %s, Please verify opsi.schema and restart slapd.' % e)


	for container in ('configs', 'configStates', 'objectToGroups', 'productOnClients', 'productOnDepots', 'productPropertyStates'):
		ldapObj = LDAPObject(u"cn=%s,%s" % (container, config['opsiBaseDn']))
		if ldapObj.exists(ldapSession):
			logger.notice(u"Deleting container: %s" % ldapObj.getDn())
			ldapObj.deleteFromDirectory(ldapSession, recursive=True)

	backend.backend_createBase()

	logger.notice(u"Converting opsiHost")
	search = LDAPObjectSearch(ldapSession, baseDn, filter=u'(objectClass=opsiConfigserver)')
	for obj in search.getObjects():
		logger.info(u"Found config server: %s" % obj.getDn())
		try:
			obj.readFromDirectory(ldapSession)
			#hostId = forceHostId(obj.getCn())
			obj.removeObjectClass('opsiHost')
			obj.removeObjectClass('opsiDepotserver')
			obj.removeObjectClass('opsiConfigserver')
			obj.addObjectClass('OpsiHost')
			obj.addObjectClass('OpsiDepotserver')
			obj.addObjectClass('OpsiConfigserver')
			url = obj.getAttribute('opsiRepositoryLocalUrl', default="", valuesAsList=False).replace(u'/products', u'/repository')
			obj.setAttribute('opsiRepositoryLocalUrl', [url])
			url = obj.getAttribute('opsiRepositoryRemoteUrl', default="", valuesAsList=False).replace(u'/products', u'/repository')
			obj.setAttribute('opsiRepositoryRemoteUrl', [url])
			obj.setAttribute('opsiIsMasterDepot', ['TRUE'])
			obj.writeToDirectory(ldapSession)
		except Exception as e:
			obj.deleteFromDirectory(ldapSession)
			logger.error(e)

	search = LDAPObjectSearch(ldapSession, baseDn, filter=u'(objectClass=opsiDepotserver)')
	for obj in search.getObjects():
		logger.info(u"Found depot server: %s" % obj.getDn())
		try:
			obj.readFromDirectory(ldapSession)
			#hostId = forceHostId(obj.getCn())
			obj.removeObjectClass('opsiHost')
			obj.removeObjectClass('opsiDepotserver')
			obj.addObjectClass('OpsiHost')
			obj.addObjectClass('OpsiDepotserver')
			url = obj.getAttribute('opsiRepositoryLocalUrl', default="", valuesAsList=False).replace(u'/products', u'/repository')
			obj.setAttribute('opsiRepositoryLocalUrl', [url])
			url = obj.getAttribute('opsiRepositoryRemoteUrl', default="", valuesAsList=False).replace(u'/products', u'/repository')
			obj.setAttribute('opsiRepositoryRemoteUrl', [url])
			obj.setAttribute('opsiIsMasterDepot', ['TRUE'])
			obj.writeToDirectory(ldapSession)
		except Exception as e:
			obj.deleteFromDirectory(ldapSession)
			logger.error(e)

	search = LDAPObjectSearch(ldapSession, baseDn, filter=u'(objectClass=opsiClient)')
	for obj in search.getObjects():
		logger.info(u"Found client: %s" % obj.getDn())
		try:
			obj.readFromDirectory(ldapSession)
			#hostId = forceHostId(obj.getCn())
			obj.removeObjectClass('opsiHost')
			obj.removeObjectClass('opsiClient')
			obj.addObjectClass('OpsiHost')
			obj.addObjectClass('OpsiClient')
			obj.writeToDirectory(ldapSession)
		except Exception as e:
			obj.deleteFromDirectory(ldapSession)
			logger.error(e)

	serverIds = backend.host_getIdents(returnType='unicode', type=u'OpsiConfigserver')
	depotIds  = backend.host_getIdents(returnType='unicode', type=u'OpsiDepotserver')
	clientIds = backend.host_getIdents(returnType='unicode', type=u'OpsiClient')

	logger.notice(u"Converting opsiGeneralConfig")
	search = LDAPObjectSearch(ldapSession, config['opsiBaseDn'], filter=u'(objectClass=opsiGeneralConfig)')
	for obj in search.getObjects():
		try:
			obj.readFromDirectory(ldapSession)
			logger.info(u"Found general config: %s" % obj.getDn())
			hostId = forceHostId(obj.getCn())
			for opsiKeyValuePair in obj.getAttribute('opsiKeyValuePair', default=[], valuesAsList=True):
				try:
					logger.info(u"Converting general config: %s" % opsiKeyValuePair)
					(configId, value) = opsiKeyValuePair.split(u'=', 1)
					if hostId in serverIds:
						backend.config_createObjects(UnicodeConfig(id=configId, defaultValues=[value]))
					elif hostId in clientIds:
						backend.config_createObjects(UnicodeConfig(id=configId))
						backend.configState_createObjects(ConfigState(configId=configId, objectId=hostId, values=[value]))
				except Exception as e:
					logger.error(u"Failure while processing %s: %s" % (obj.getDn(), e))
		except Exception as e:
			logger.error(u"Failure while processing %s: %s" % (obj.getDn(), e))

	logger.notice(u"Converting opsiNetworkConfig")
	search = LDAPObjectSearch(ldapSession, config['opsiBaseDn'], filter=u'(objectClass=opsiNetworkConfig)')
	for obj in search.getObjects():
		try:
			obj.readFromDirectory(ldapSession)
			logger.info(u"Found network config: %s" % obj.getDn())
			hostId = forceHostId(obj.getCn())
			for (key, value) in obj.getAttributeDict(valuesAsList=False).items():
				try:
					if not value:
						continue
					configId = None
					multiValue = False
					if (key == 'opsiDepotserverReference'):
						configId = u'clientconfig.depot.id'
						serverObj = LDAPObject(value)
						value = None
						if serverObj.exists(ldapSession):
							serverObj.readFromDirectory(ldapSession)
							try:
								value = forceHostId(serverObj.getAttribute('opsiHostId', default=None, valuesAsList=False))
							except Exception as e:
								logger.debug(e)
						if not value:
							continue
					elif (key == 'opsiDepotDrive'):
						configId = u'clientconfig.depot.drive'
					elif (key == 'opsiNextBootServiceURL'):
						configId = u'clientconfig.configserver.url'
						if (value.find('/rpc') == -1):
							value = value + '/rpc'
						multiValue = True
					elif (key == 'opsiWinDomain'):
						configId = u'clientconfig.windows.domain'

					if not configId:
						continue

					logger.info(u"Converting network config %s" % key)

					if hostId in serverIds:
						backend.config_createObjects(UnicodeConfig(id=configId, defaultValues=[value], multiValue=multiValue))
					elif hostId in clientIds:
						backend.config_createObjects(UnicodeConfig(id=configId, multiValue=multiValue))
						backend.configState_createObjects(ConfigState(configId=configId, objectId=hostId, values=[value]))
				except Exception as e:
					logger.error(u"Failure while processing %s: %s" % (obj.getDn(), e))
		except Exception as e:
			logger.error(u"Failure while processing %s: %s" % (obj.getDn(), e))

	logger.notice(u"Converting opsiGroup")

	def convertGroupObject(obj, parentGroupId=None):
		try:
			groupId = forceGroupId(obj.getCn())

			search = LDAPObjectSearch(ldapSession, obj.getDn(), scope=ldap.SCOPE_ONELEVEL, filter=u'(objectClass=opsiGroup)')
			for obj2 in search.getObjects():
				convertGroupObject(obj2, parentGroupId=groupId)
			objectToGroups = []
			obj.readFromDirectory(ldapSession)
			for value in obj.getAttribute('uniqueMember', default=[], valuesAsList=True):
				try:
					hostObj = LDAPObject(value)
					value = None
					if hostObj.exists(ldapSession):
						hostObj.readFromDirectory(ldapSession)
						try:
							value = forceHostId(hostObj.getAttribute('opsiHostId', default=None, valuesAsList=False))
						except Exception as e:
							logger.debug(e)
					if not value:
						continue
					objectToGroups.append(ObjectToGroup(groupId=groupId, groupType='HostGroup', objectId=value))
				except Exception as e:
					logger.error(u"Failure while processing group member %s of group %s: %s" % (value, obj.getDn(), e))
			obj.deleteFromDirectory(ldapSession)
			backend.group_createObjects( HostGroup(id=groupId, parentGroupId=parentGroupId) )
			backend.objectToGroup_createObjects(objectToGroups)
		except Exception as e:
			logger.error(u"Failure while processing group %s: %s" % (obj.getDn(), e))

	search = LDAPObjectSearch(ldapSession, "cn=groups,%s" % config['opsiBaseDn'], scope=ldap.SCOPE_ONELEVEL, filter=u'(objectClass=opsiGroup)')
	for obj in search.getObjects():
		convertGroupObject(obj)

	localbootProductIds = []
	netbootProductIds = []
	deleteDns = []
	for objectClass in ('opsiLocalBootProduct', 'opsiNetBootProduct'):
		logger.notice(u"Converting %s" % objectClass)
		search = LDAPObjectSearch(ldapSession, config['opsiBaseDn'], filter=u'(objectClass=%s)' % objectClass)
		for obj in search.getObjects():
			try:
				obj.readFromDirectory(ldapSession)
				logger.info(u"Found product: %s" % obj.getDn())

				depotId = forceHostId( obj.getDn().split(',')[1].split('=')[1] )
				containerDn = ','.join(obj.getDn().split(',')[1:])
				if not containerDn in deleteDns:
					deleteDns.append(containerDn)

				Class = LocalbootProduct
				if (objectClass == 'opsiNetBootProduct'):
					Class = NetbootProduct

				product = Class(
					id= obj.getCn(),
					productVersion=obj.getAttribute('opsiProductVersion', default=None, valuesAsList=False),
					packageVersion=obj.getAttribute('opsiPackageVersion', default=None, valuesAsList=False),
					name=obj.getAttribute('opsiProductName', default=None, valuesAsList=False),
					licenseRequired=obj.getAttribute('opsiProductLicenseRequired', default=None, valuesAsList=False),
					setupScript=obj.getAttribute('opsiSetupScript', default=None, valuesAsList=False),
					uninstallScript=obj.getAttribute('opsiUninstallScript', default=None, valuesAsList=False),
					updateScript=obj.getAttribute('opsiUpdateScript', default=None, valuesAsList=False),
					alwaysScript=obj.getAttribute('opsiAlwaysScript', default=None, valuesAsList=False),
					onceScript=obj.getAttribute('opsiOnceScript', default=None, valuesAsList=False),
					priority=obj.getAttribute('opsiProductPriority', default=None, valuesAsList=False),
					description=obj.getAttribute('description', default=None, valuesAsList=False),
					advice=obj.getAttribute('opsiProductAdvice', default=None, valuesAsList=False),
					windowsSoftwareIds=obj.getAttribute('opsiWindowsSoftwareId', default=None, valuesAsList=True)
				)
				if (objectClass == 'opsiNetBootProduct'):
					if not product.id in netbootProductIds:
						netbootProductIds.append(product.id)
					product.setPxeConfigTemplate( obj.getAttribute('opsiPxeConfigTemplate', default = None, valuesAsList = False) )
				else:
					if not product.id in localbootProductIds:
						localbootProductIds.append(product.id)

				backend.product_createObjects(product)

				backend.productOnDepot_createObjects(
					ProductOnDepot(
						productId=product.getId(),
						productType=product.getType(),
						productVersion=product.getProductVersion(),
						packageVersion=product.getPackageVersion(),
						depotId=depotId,
						locked=obj.getAttribute('opsiProductIsLocked', default=False, valuesAsList=False)
					)
				)
			except Exception as e:
				logger.error(u"Failure while processing %s: %s" % (obj.getDn(), e))


	logger.notice(u"Converting opsiProductPropertyDefinition")
	search = LDAPObjectSearch(ldapSession, config['opsiBaseDn'], filter=u'(objectClass=opsiProductPropertyDefinition)')
	for obj in search.getObjects():
		try:
			obj.readFromDirectory(ldapSession)
			logger.info(u"Found product property: %s" % obj.getDn())
			#obj.deleteFromDirectory(ldapSession, recursive = True)


			productId = forceProductId( obj.getDn().split(',')[2].split('=')[1] )
			depotId   = forceHostId( obj.getDn().split(',')[3].split('=')[1] )

			productOnDepot = backend.productOnDepot_getObjects(productId=productId, depotId=depotId)
			if not productOnDepot:
				raise Exception(u"Product '%s' not found on depot '%s'" % (productId, depotId))
			productOnDepot = productOnDepot[0]

			defaultValues  = obj.getAttribute('opsiProductPropertyDefaultValue',  default=None, valuesAsList=True)
			possibleValues = obj.getAttribute('opsiProductPropertyPossibleValue', default=None, valuesAsList=True)
			backend.productProperty_createObjects(
				UnicodeProductProperty(
					productId=productOnDepot.productId,
					productVersion=productOnDepot.productVersion,
					packageVersion=productOnDepot.packageVersion,
					propertyId=obj.getCn().replace(' ', '_'),
					description=obj.getAttribute('description', default=None, valuesAsList=False),
					possibleValues=possibleValues,
					defaultValues=defaultValues,
					editable= bool(possibleValues),
				)
			)
			if defaultValues:
				backend.productPropertyState_createObjects(
					ProductPropertyState(
						productId=productOnDepot.productId,
						propertyId=obj.getCn().replace(' ', '_'),
						objectId=depotId,
						values=defaultValues
					)
				)
		except Exception as e:
			logger.error(u"Failure while processing %s: %s" % (obj.getDn(), e))

	logger.notice(u"Converting opsiProductDependency")
	search = LDAPObjectSearch(ldapSession, config['opsiBaseDn'], filter=u'(objectClass=opsiProductDependency)')
	for obj in search.getObjects():
		try:
			obj.readFromDirectory(ldapSession)
			logger.info(u"Found product dependency: %s" % obj.getDn())

			#cn=javavm,cn=setup,cn=productDependencies,cn=ooffice3,cn=exp-srv-005.uib.local,cn=products,cn=opsi,dc=uib,dc=local
			requiredProductId = forceProductId(obj.getDn().split(',')[0].split('=')[1])
			action = forceActionRequest(obj.getDn().split(',')[1].split('=')[1])
			productId = forceProductId(obj.getDn().split(',')[3].split('=')[1])
			depotId = forceHostId(obj.getDn().split(',')[4].split('=')[1])

			productOnDepot = backend.productOnDepot_getObjects(productId=productId, depotId=depotId)
			if not productOnDepot:
				raise Exception(u"Product '%s' not found on depot '%s'" % (productId, depotId))
			productOnDepot = productOnDepot[0]

			backend.productDependency_createObjects(
				ProductDependency(
					productId=productOnDepot.productId,
					productVersion=productOnDepot.productVersion,
					packageVersion=productOnDepot.packageVersion,
					productAction=action,
					requiredProductId=requiredProductId,
					requiredAction=obj.getAttribute('opsiActionRequired', default=None, valuesAsList=False),
					requiredInstallationStatus=obj.getAttribute('opsiInstallationStatusRequired', default=None, valuesAsList=False),
					requirementType=obj.getAttribute('opsiRequirementType', default=None, valuesAsList=False)
				)
			)
		except Exception as e:
			logger.error(u"Failure while processing %s: %s" % (obj.getDn(), e))

	for deleteDn in deleteDns:
		try:
			logger.notice(u"Deleting %s" % deleteDn)
			LDAPObject(deleteDn).deleteFromDirectory(ldapSession, recursive=True)
		except Exception as e:
			logger.error(e)

	logger.notice(u"Converting opsiProductState")
	contSearch = LDAPObjectSearch(ldapSession, "cn=productStates,%s" % config['opsiBaseDn'], scope=ldap.SCOPE_ONELEVEL, filter=u'(objectClass=organizationalRole)')
	for contObj in contSearch.getObjects():
		search = LDAPObjectSearch(ldapSession, contObj.getDn(), filter=u'(objectClass=opsiProductState)')
		for obj in search.getObjects():
			try:
				obj.readFromDirectory(ldapSession)
				logger.info(u"Found product state: %s" % obj.getDn())

				productType = 'LocalbootProduct'
				if obj.getCn() in netbootProductIds:
					productType = 'NetbootProduct'

				actionRequest = obj.getAttribute('opsiProductActionRequestForced', default=None, valuesAsList=False)
				installationStatus = obj.getAttribute('opsiProductInstallationStatus', default=None, valuesAsList=False)
				actionProgress = obj.getAttribute('opsiProductActionProgress', default=None, valuesAsList=False)
				actionResult = None
				targetConfiguration = None

				if installationStatus in ('failed',):
					actionResult = 'failed'
					installationStatus = 'not_installed'
				if installationStatus in ('installing',):
					actionProgress = 'installing'
					installationStatus = 'not_installed'
				try:
					installationStatus = forceInstallationStatus(installationStatus)
				except:
					installationStatus = 'not_installed'
				try:
					actionRequest = forceActionRequest(actionRequest)
				except:
					actionRequest = 'none'

				if (actionProgress == '{}'):
					actionProgress = None

				if (installationStatus == 'not_installed') and (actionRequest == 'none') and actionResult is None:
					continue

				if (installationStatus == 'installed'):
					targetConfiguration = 'installed'

				clientId = forceHostId( obj.getDn().split(',')[1].split('=')[1] )
				if not clientId in clientIds:
					continue

				backend.productOnClient_insertObject(
					ProductOnClient(
						productId=obj.getCn(),
						productType=productType,
						productVersion=obj.getAttribute('opsiProductVersion', default=None, valuesAsList=False),
						packageVersion=obj.getAttribute('opsiPackageVersion', default=None, valuesAsList=False),
						clientId=clientId,
						installationStatus=installationStatus,
						targetConfiguration=targetConfiguration,
						actionRequest=actionRequest,
						actionProgress=actionProgress
					)
				)
			except Exception as e:
				logger.error(u"Failure while processing %s: %s" % (obj.getDn(), e))

	logger.notice(u"Converting opsiProductProperty")
	search = LDAPObjectSearch(ldapSession, u'cn=productProperties,%s' % config['opsiBaseDn'], filter=u'(objectClass=opsiProductProperty)')
	for obj in search.getObjects():
		try:
			obj.readFromDirectory(ldapSession)
			logger.info(u"Found product property: %s" % obj.getDn())

			clientId = forceHostId( obj.getDn().split(',')[1].split('=')[1] )
			if not clientId in clientIds:
				continue

			productId = obj.getAttribute('opsiProductReference', valuesAsList=False).split(',')[0].split('=')[1]

			productPropertyStates = []
			for opsiKeyValuePair in obj.getAttribute('opsiKeyValuePair', default=[], valuesAsList=True):
				try:
					logger.info(u"Converting product property: %s" % opsiKeyValuePair)
					(propertyId, value) = opsiKeyValuePair.split(u'=', 1)
					if (value == ''):
						continue
					productPropertyStates.append(
						ProductPropertyState(
							productId=productId,
							propertyId=propertyId.replace(' ', '_'),
							objectId=clientId,
							values=[value]
						)
					)
				except Exception as e:
					logger.error(u"Failure while processing %s: %s" % (obj.getDn(), e))
			backend.productPropertyState_createObjects(productPropertyStates)
		except Exception as e:
			logger.error(u"Failure while processing %s: %s" % (obj.getDn(), e))

	for container in ('generalConfigs', 'networkConfigs', 'productClasses', 'productLicenses', 'productProperties', 'productStates'):
		ldapObj = LDAPObject(u"cn=%s,%s" % (container, config['opsiBaseDn']))
		if ldapObj.exists(ldapSession):
			logger.notice(u"Deleting container: %s" % ldapObj.getDn())
			ldapObj.deleteFromDirectory(ldapSession, recursive=True)


def updateFileBackend():
	backendConfigFile = u'/etc/opsi/backends/file.conf'

	if not os.path.isfile(backendConfigFile):
		raise Exception("Configuration file does not exist: '%s'" % (backendConfigFile))

	logger.notice(u"Loading backend config '%s'" % backendConfigFile)
	l = {'socket': socket, 'os': os, 'sys': sys, 'module': '', 'config': {}}
	execfile(backendConfigFile, l)
	config = l['config']
	config.update(backendConfig)
	logger.info(u"Current file backend config: %s" % config)

	#test for version 40
	newConfigFile = os.path.join(os.path.dirname(config["baseDir"]), u'config', u'config.ini')
	if os.path.isfile(newConfigFile):
		raise Exception(u"Detected a file ('%s') that should only be in opsi version 4.0, update denied!" % (newConfigFile))

	logger.notice(u"Creating file backend instance")
	backend = ExtendedConfigDataBackend(FileBackend(**config))

	backupDir = os.path.join(os.path.dirname(config["baseDir"]), u'backup_%s' % time.time())

	backupClientConfigDir   = os.path.join(backupDir, u'clients')
	backupDepotConfigDir    = os.path.join(backupDir, u'depots')
#	backupProductDir        = os.path.join(backupDir, u'products')
#	backupAuditDir          = os.path.join(backupDir, u'audit')
#	backupClientTemplateDir = os.path.join(backupDir, u'templates')

	logger.notice(u"Backing up current directory '%s' to '%s'" % (config["baseDir"], backupDir))
	if not os.path.isdir(config["baseDir"]):
		raise Exception("Base directory given from file '%s' does not exist: '%s'" % (backendConfigFile, config["baseDir"]))

	noClientDir = False
	if not os.path.isdir(os.path.join(config["baseDir"], u'clients')):
		noClientDir = True
		logger.warning("Client directory does not exist: '%s'" % (os.path.join(config["baseDir"], u'clients')))

	noDepotDir = False
	if not os.path.isdir(os.path.join(config["baseDir"], u'depots')):
		noDepotDir = True
		logger.warning("Depot directory does not exist: '%s'" % (os.path.join(config["baseDir"], u'depots')))

	shutil.move(config["baseDir"], backupDir)

	backend.backend_createBase()

	opsiHostKeys = {}
	if os.path.isfile(config["hostKeyFile"]):
		logger.notice(u"Backing up hostKeyFile '%s' to '%s'" % (config["hostKeyFile"], backupDir))
		shutil.copy(config["hostKeyFile"], backupDir)
		lines = []
		f = codecs.open(config["hostKeyFile"], 'r', 'utf-8')
		for line in f.readlines():
			line = line.strip()
			if not line:
				continue
			if line[0] in ('#', ';'):
				continue
			try:
				(hostId, key) = line.strip().split(':')
				hostId = forceHostId(hostId.replace('_', '-'))
			except Exception as e:
				logger.error(u"Bad line in %s: '%s': %s" % (config["hostKeyFile"], line, e))
				continue
			lines.append(u"%s:%s\n" % (hostId, key))
		f.close()
		f = codecs.open(config["hostKeyFile"], 'w', 'utf-8')
		f.writelines(lines)
		f.close()
		opsiHostKeys = HostKeyFile(filename = config["hostKeyFile"]).parse()
		os.remove(config["hostKeyFile"])
	else:
		logger.warning(u"Host key file does not exist: '%s'" % (config["hostKeyFile"]))

	lockedList = {}
	if os.path.isfile(os.path.join(backupDepotConfigDir, u'product.locks')):
		logger.info(u"Getting information on product.locks" % ())
		try:
			iniFile = IniFile(filename=os.path.join(backupDepotConfigDir, u'product.locks'), ignoreCase=False)
			ini = iniFile.parse()

			for proId in ini.sections():
				for depId in ini.options(proId):
					if (ini.get(proId, depId) == u'locked'):
						key = u'%s' % (proId.lower() + '#' + depId.lower())
						lockedList[key] = True
		except Exception as e:
			logger.error(u"Could not get all information on product.locks: %s" % (e))
	else:
		logger.warning(u"Product locks fils does not exist: '%s'" % (os.path.join(backupDepotConfigDir, u'product.locks')))

	#needed for configStates
	updatedConfigs = []

	logger.notice(u"Updating depots ...")
	if not noDepotDir:
		for depotId in os.listdir(backupDepotConfigDir):
			oldPath = os.path.join(backupDepotConfigDir, depotId)
			oldDepot = os.path.join(oldPath, u'depot.ini')
			if (not oldDepot.endswith('.ini')) or (not os.path.isfile(oldDepot)):
				logger.debug2(u"Ignoring '%s'" % (oldPath))
				continue
			try:
				depotId = forceHostId(depotId)
			except Exception as e:
				logger.error(u"Invalid depot: '%s': %s" % (oldPath, e))
				continue

			logger.notice(u"Updating depot '%s'" % depotId)

			oldDepotDict = {
				'id': depotId,
				'opsiHostKey': None,
				'depotLocalUrl': None,
				'depotRemoteUrl': None,
				'repositoryLocalUrl': None,
				'repositoryRemoteUrl': None,
				'description': None,
				'notes': None,
				'hardwareAddress': None,
				'ipAddress': None,
				'inventoryNumber': None,
				'networkAddress': None,
				'maxBandwidth': None,
				'isMasterDepot': True
			}

			if depotId in opsiHostKeys.keys():
				oldDepotDict['opsiHostKey'] = opsiHostKeys[depotId]

			updatedProductPropertyStates = []
			try:
				iniFile = IniFile(filename=oldDepot)
				ini = iniFile.parse()

				if ini.has_option('depotshare', 'localurl'):
					oldDepotDict['depotLocalUrl'] = ini.get('depotshare', 'localurl')
				if ini.has_option('depotshare', 'remoteurl'):
					oldDepotDict['depotRemoteUrl'] = ini.get('depotshare', 'remoteurl')
				if ini.has_option('repository', 'localurl'):
					oldDepotDict['repositoryLocalUrl'] = ini.get('repository', 'localurl').replace(u'/products', u'/repository')
				if ini.has_option('repository', 'remoteurl'):
					oldDepotDict['repositoryRemoteUrl'] = ini.get('repository', 'remoteurl').replace(u'/products', u'/repository')
				if ini.has_option('repository', 'maxbandwidth'):
					oldDepotDict['maxBandwidth'] = ini.get('repository', 'maxbandwidth')
				if ini.has_option('depotserver', 'description'):
					oldDepotDict['description'] = ini.get('depotserver', 'description')
				if ini.has_option('depotserver', 'notes'):
					oldDepotDict['notes'] = ini.get('depotserver', 'notes')
				if ini.has_option('depotserver', 'network'):
					oldDepotDict['networkAddress'] = ini.get('depotserver', 'network')
				if ini.has_option('depotserver', 'hardwareaddress'):
					oldDepotDict['hardwareAddress'] = ini.get('depotserver', 'hardwareaddress')
				if ini.has_option('depotserver', 'ipaddress'):
					oldDepotDict['ipAddress'] = ini.get('depotserver', 'ipaddress')
				if ini.has_option('depotserver', 'inventorynumber'):
					oldDepotDict['inventoryNumber'] = ini.get('depotserver', 'inventorynumber')

			except Exception as e:
				logger.warning(u"Could not get all information on depot: %s" % (e))

			backend.host_createObjects([OpsiDepotserver.fromHash(oldDepotDict)])

			logger.notice(u"Updating products on depot '%s'" % (depotId))
			productFilenames = []

			for productPath in (os.path.join(oldPath, u'products', u'localboot'), os.path.join(oldPath, u'products', u'netboot')):
				if not os.path.isdir(productPath):
					logger.warning(u"Path does not exist, skipping: '%s'" % (productPath))
					continue

				for productId in os.listdir(productPath):
					try:
						productFilename = os.path.join(productPath, forceProductId(productId))
						if not (os.path.isfile(productFilename)):
							logger.debug2(u"Ignoring '%s'" % (productFilename))
							continue
						productFilenames.append(productFilename)
					except Exception as e:
						logger.warning(u"Invalid product: '%s' in '%s': %s" % (productId, productPath, e))
						continue

			updatedProductDependencies = []
			for productFilename in productFilenames:
				packageControlFile = PackageControlFile(filename=productFilename)
				oldProduct = None

				try:
					oldProduct = packageControlFile.getProduct()
				except Exception as e:
					logger.error(u"Invalid product '%s': %s" % (productFilename, e))
					continue

				backend.product_createObjects([oldProduct])

				logger.info(u"Updating ProductProperties in '%s'" % (oldProduct.getId()))
				for pp in packageControlFile.getProductProperties():
					try:
						pp.setPropertyId(pp.getPropertyId().replace(u' ', u'_'))
						if pp.getPossibleValues():
							pp.setEditable(False)
						backend.productProperty_createObjects([pp])
					except Exception as e:
						logger.error(u"Could not create ProductProperty '%s': %s" % (pp.getIdent(), e))

					updatedProductPropertyStates.append(
						ProductPropertyState.fromHash(
							{
								'productId': pp.getProductId(),
								'propertyId': pp.getPropertyId().replace(u' ', u'_'),
								'objectId': depotId,
								'values': pp.getDefaultValues()
							}
						)
					)

				#logger.notice(u"Updating ProductDependencies in '%s'" % (depotId))
				#will be updated after all products
				#updatedProductDependencies = []
				for pd in packageControlFile.getProductDependencies():
					updatedProductDependencies.append(pd)

				logger.info(u"Updating ProductOnDepot on '%s'" % (depotId))

				locked = None
				if (len(lockedList) > 0):
					try:
						key = u'%s' % (oldProduct.getId().lower() + '#' + depotId.lower())
						locked = lockedList[key]
					except:
						pass

				backend.productOnDepot_createObjects([
						ProductOnDepot.fromHash({
							'productId': oldProduct.getId(),
							'productType': oldProduct.getType(),
							'productVersion': oldProduct.getProductVersion(),
							'packageVersion': oldProduct.getPackageVersion(),
							'depotId': depotId,
							'locked': locked
						})
				])

			logger.notice(u"Updating ProductDependencies on '%s'" % (depotId))
			for pd in updatedProductDependencies:
				try:
					backend.productDependency_createObjects([pd])
				except Exception as e:
					logger.error(u"Could not create ProductDependency '%s': %s" % (pd.getIdent(), e))

			logger.notice(u"Updating ProductPropertyStates in '%s'" % (depotId))
			for pps in updatedProductPropertyStates:
				try:
					backend.productPropertyState_createObjects([pps])
				except Exception as e:
					logger.error(u"Could not create ProductPropertyState '%s': %s" % (pps.getIdent(), e))
	else:
		logger.info(u"No depot folder, skipping depots.")

	logger.notice(u"Updating Configs ..." % ())
	try:
		iniFile = IniFile(filename=os.path.join(backupDir, u'global.ini'), ignoreCase=False)
		ini = iniFile.parse()

		for section in ini.sections():
			if section.lower() not in (u'networkconfig', u'generalconfig'):
				logger.warning(u"Unknown section in global.ini: %s" % (section))
				continue

			configId = None
			value = None

			for option in ini.options(section):
				configId = configId = option.lower()
				value = ini.get(section, option)

				multiValue = False
				try:
					if (section.lower() == u'networkconfig'):
						if (option.lower() == u'depotid'):
							configId = u'clientconfig.depot.id'
						elif (option.lower() == u'depotdrive'):
							configId = u'clientconfig.depot.drive'
						elif (option.lower() == u'nextbootserviceurl'):
							configId = u'clientconfig.configserver.url'
							if (value.find('/rpc') == -1):
								value = value + '/rpc'
							multiValue = True
						elif (option.lower() == u'windomain'):
							configId = u'clientconfig.windows.domain'
				except Exception as e:
					logger.error(u"Error in option '%s' in section '%s': '%s'" % (option, section, e))
					continue

				updatedConfigs.append(UnicodeConfig(id=configId, defaultValues=[value], multiValue=multiValue))
	except Exception as e:
		logger.error(u"Failed to update config '%s': '%s'" % (os.path.join(backupDir, u'global.ini'), e))

	for c in updatedConfigs:
		try:
			backend.config_createObjects([c])
		except Exception as e:
			logger.error(u"Could not create config '%s': %s" % (c.getIdent(), e))

	logger.notice(u"Updating clients ...")
	if not noClientDir:
		for filename in os.listdir(backupClientConfigDir):
			updatedConfigStates = []

			oldClient = os.path.join(backupClientConfigDir, filename)
			if not (filename.endswith('.ini')) or not os.path.isfile(oldClient):
				logger.debug2(u"Ignoring client ini '%s'" % (filename))
				continue
			try:
				clientId = forceHostId(filename[:-4].replace('_', '-'))
			except Exception as e:
				logger.error(u"Invalid client id '%s': %s" % (filename[:-4], e))
				continue

			logger.notice(u"Updating client '%s'" % clientId)

			oldClientDict = {
				'id': clientId,
				'opsiHostKey': None,
				'description': None,
				'notes': None,
				'hardwareAddress': None,
				'ipAddress': None,
				'inventoryNumber': None,
				'created': None,
				'lastSeen': None
			}

			if clientId in opsiHostKeys.keys():
				oldClientDict['opsiHostKey'] = opsiHostKeys[clientId]

			updatedProductPropertyStates = []
			updatedProductOnClients = []
			try:
				iniFile = IniFile(filename=oldClient)
				ini = iniFile.parse()

				if ini.has_option('info', 'description'):
					oldClientDict['description'] = ini.get('info', 'description')
				if ini.has_option('info', 'notes'):
					oldClientDict['notes'] = ini.get('info', 'notes')
				if ini.has_option('info', 'macaddress'):
					oldClientDict['hardwareAddress'] = ini.get('info', 'macaddress')
				if ini.has_option('info', 'ipaddress'):
					oldClientDict['ipAddress'] = ini.get('info', 'ipaddress')
				if ini.has_option('info', 'inventorynumber'):
					oldClientDict['inventoryNumber'] = ini.get('info', 'inventorynumber')
				if ini.has_option('info', 'created'):
					oldClientDict['created'] = ini.get('info', 'created')
				if ini.has_option('info', 'lastSeen'):
					oldClientDict['lastSeen'] = ini.get('info', 'lastSeen')

				for section in ini.sections():
					if (section.lower().endswith(u'-install')):
						for option in ini.options(section):
							try:
								updatedProductPropertyStates.append(
									ProductPropertyState.fromHash(
										{
										'productId':  section[:-8],
										'propertyId': option,
										'objectId':   clientId,
										'values':     [ini.get(section, option)]
										}
										)
									)
							except Exception as e:
								logger.error(u"Exception while creating ProductPropertyState from '%s': %s" % (section, e))

					elif (section.lower().endswith(u'_product_states')):
						for productId in ini.options(section):
							try:
								poc = {
									'productId': productId,
									'productType': section[:-15],
									'clientId': clientId,
									'installationStatus': u'not_installed',
									'actionRequest': u'none',
									'actionProgress': None,
									'targetConfiguration': None,
									'productVersion': None,
									'packageVersion': None,
									'modificationTime': None
								}

								try:
									(poc['installationStatus'], poc['actionRequest']) = ini.get(section, productId).split(u':')
								except Exception as e:
									logger.error(u"Failed to get state from '%s': %s" % (ini.get(section, productId), e))

								if poc['installationStatus'] not in (u'installed', u'not_installed'):
									poc['actionProgress'] = poc['installationStatus']
									poc['installationStatus'] = u'not_installed'

								if not poc['actionRequest'] in (u'setup', u'uninstall', u'update', u'always', u'once', u'custom', u'none'):
									poc['actionRequest'] = u'none'

								if (poc['installationStatus'] == 'not_installed') and (poc['actionRequest'] == 'none'):
									continue

								if ini.has_section(productId + u'-state'):
									if (ini.get(productId + u'-state', 'productversion') != u''):
										poc['productVersion']   = ini.get(productId + u'-state', 'productversion')
									if (ini.get(productId + u'-state', 'packageversion') != u''):
										poc['packageVersion']   = ini.get(productId + u'-state', 'packageversion')
									if ini.has_option(productId + u'-state', 'laststatechange'):
										poc['modificationTime'] = ini.get(productId + u'-state', 'laststatechange')

								updatedProductOnClients.append(ProductOnClient.fromHash(poc))
							except Exception as e:
								logger.error(u"Exception while creating ProductOnClient from '%s': %s" % (oldClient, e))
					elif (section.lower() in (u'networkconfig', u'generalconfig')):
						for option in ini.options(section):
							configId = configId = option.lower()
							value = ini.get(section, option)

							try:
								if (section.lower() == u'networkconfig'):
									if (option.lower() == u'depotid'):
										configId = u'clientconfig.depot.id'
									elif (option.lower() == u'depotdrive'):
										configId = u'clientconfig.depot.drive'
									elif (option.lower() == u'nextbootserviceurl'):
										configId = u'clientconfig.configserver.url'
										if (value.find('/rpc') == -1):
											value = value + '/rpc'
									elif (option.lower() == u'windomain'):
										configId = u'clientconfig.windows.domain'
							except Exception as e:
								logger.error(u"%s" % (e))
								continue

							updatedConfigStates.append(ConfigState(configId=configId, objectId=clientId, values=[value]))

			except Exception as e:
				logger.warning(u"Could not get all information on client: %s" % e)

			backend.host_createObjects([OpsiClient.fromHash(oldClientDict)])

			for cs in updatedConfigStates:
				#TODO: check, if configState.configId exists
				try:
					backend.configState_createObjects([cs])
				except Exception as e:
					logger.error(u"Could not create configState '%s': %s" % (cs.getIdent(), e))

			logger.info(u"Updating ProductOnClients in '%s'" % (clientId))
			for poc in updatedProductOnClients:
				try:
					backend.productOnClient_createObjects([poc])
				except Exception as e:
					logger.error(u"Could not create ProductOnClient '%s': %s" % (poc.getIdent(), e))


			logger.info(u"Updating ProductPropertyStates in '%s'" % (clientId))
			for pps in updatedProductPropertyStates:
				try:
					backend.productPropertyState_createObjects([pps])
				except Exception as e:
					logger.error(u"Could not create ProductPropertyState '%s': %s" % (pps.getIdent(), e))
	else:
		logger.error(u"No client folder, skipping clients.")

	logger.notice(u"Updating Groups ..." % ())
	updatedGroups = []
	updatedObjectToGroups = []
	backupClientGroupsFile = os.path.join(backupDir, u'clientgroups.ini')
	if os.path.isfile(backupClientGroupsFile):
		iniFile = IniFile(filename=backupClientGroupsFile)
		ini = iniFile.parse()

		parentGroupIdList= []
		for section in ini.sections():
			try:
				g = {
					'id': forceGroupId(section),
					'description': None,
					'notes': None,
					'parentGroupId': None
				}

				for option in ini.options(section):
					if (option.lower() == u'parentgroupid'):
						updatedGroup['parentGroupId'] = ini.get(section, option)
						if (not ini.get(section, option) in parentGroupIdList):
							parentGroupIdList.append(ini.get(section, option))
						continue

					if (ini.get(section, option) == 0):
						logger.debug2(u"Ignoring objectId '%s' in '%s' (value is 0)" % (option, section))
						continue

					otg = {
						'groupId' : section,
						'groupType' : 'HostGroup',
						'objectId' : forceObjectId(option)
					}

					updatedObjectToGroups.append(ObjectToGroup.fromHash(otg))

				updatedGroups.append(HostGroup.fromHash(g))
			except Exception as e:
				logger.error(u"Exception while creating Group from '%s': %s" % (section, e))

		#switching groups with parentGroupId after parentGroup
		switchedGroups = []
		unsortedGroups = updatedGroups
		counter = -1
		while (len(switchedGroups) != len(updatedGroups)):
			if (counter > 1000): #TODO: check for unresolved parentGroupIds
				logger.error(u"Unresolved parentGroupIdList: %s" % (parentGroupIdList))
				break
			else:
				counter =+ 1

			nowUnsorted = []
			for ug in unsortedGroups:
				if (ug.getParentGroupId() is None) or (not ug.getParentGroupId() in parentGroupIdList):
					switchedGroups.append(ug)
					if (ug.getId() in parentGroupIdList):
						parentGroupIdList.remove(ug.getId())
				else:
					nowUnsorted.append(ug)

			unsortedGroups = nowUnsorted

		updatedGroups = switchedGroups

		for g in updatedGroups:
			try:
				backend.group_createObjects([g])
			except Exception as e:
				logger.error(u"Could not create Group '%s': %s" % (g.getIdent(), e))

		for otg in updatedObjectToGroups:
			try:
				backend.objectToGroup_createObjects([otg])
			except Exception as e:
				logger.error(u"Could not create ObjectToGroup '%s': %s" % (otg.getIdent(), e))
	else:
		logger.error(u"No groups file, skipping groups.")

	#TODO: ignore?
	#AuditSoftwares/AuditSoftwareOnClients/AuditHardwares/AuditHardwareOnHosts


def configureMySQLBackend(unattendedConfiguration=None):
	def notifyFunction(message):
		logger.notice(message)
		messageBox.addText(u"{0}\n".format(message))

	def errorFunction(message):
		logger.error(message)
		ui.showError(
			text=message, width=70, height=6,
			title=u'Problem configuring MySQL backend'
		)

	dbAdminUser = u'root'
	dbAdminPass = u''
	config = backendUtils.getBackendConfiguration(u'/etc/opsi/backends/mysql.conf')
	messageBox = None

	if unattendedConfiguration is not None:
		errorTemplate = u"Missing '{key}' in unattended configuration."
		for key in ('dbAdminUser', 'dbAdminPass'):
			if not key in unattendedConfiguration:
				raise Exception(errorTemplate.format(key=key))

		dbAdminUser = unattendedConfiguration['dbAdminUser']
		dbAdminPass = unattendedConfiguration['dbAdminPass']
		# User / PW must not show in config file -> delete from config.
		for key in ('dbAdminUser', 'dbAdminPass'):
			del unattendedConfiguration[key]

		config.update(unattendedConfiguration)

		logger.debug(u"Configuration for unattended mysql configuration: {0}".format(config))
		configureMySQLBackendWithoutGUI(
			dbAdminUser, dbAdminPass, config, getSysConfig(),
			additionalBackendConfig=backendConfig,
		)
		return

	consoleLevel = logger.getConsoleLevel()
	logger.setConsoleLevel(LOG_NONE)
	ui = UIFactory(type='snack')
	try:
		while True:
			values = [
				{"name": u"Database host", "value": config['address']},
				{"name": u"Database admin user", "value": dbAdminUser},
				{"name": u"Database admin password", "value": dbAdminPass, "password": True},
				{"name": u"Opsi database name", "value": config['database']},
				{"name": u"Opsi database user", "value": config['username']},
				{"name": u"Opsi database password", "value": config['password'], "password": True}
			]
			values = ui.getValues(title=u'MysQL config', width=70, height=15, entries=values)
			if values is None:
				raise Exception(u"Canceled")

			config['address'] = values[0]["value"]
			dbAdminUser = values[1]["value"]
			dbAdminPass = values[2]["value"]
			config['database'] = values[3]["value"]
			config['username'] = values[4]["value"]
			config['password'] = values[5]["value"]

			messageBox = ui.createMessageBox(
				width=70, height=20, title=u'MysQL config', text=u''
			)

			try:
				configureMySQLBackendWithoutGUI(
					dbAdminUser, dbAdminPass,
					config, getSysConfig(),
					additionalBackendConfig=backendConfig,
					notificationFunction=notifyFunction,
					errorFunction=errorFunction
				)
				break
			except DatabaseConnectionFailedException:
				messageBox.hide()

		time.sleep(2)
		ui.showMessage(
			width=70, height=4,
			title=u'Success', text=u"MySQL Backend configuration done"
		)
	finally:
		if messageBox is not None:
			messageBox.hide()

		ui.exit()
		logger.setConsoleLevel(consoleLevel)


def registerDepot():
	backendConfigFile = u'/etc/opsi/backends/jsonrpc.conf'
	dispatchConfigFile = u'/etc/opsi/backendManager/dispatch.conf'

	getSysConfig()
	config = backendUtils.getBackendConfiguration(backendConfigFile)
	config.update(backendConfig)
	logger.info(u"Current jsonrpc backend config: %s" % config)

	jsonrpcBackend = None
	depot = None

	consoleLevel = logger.getConsoleLevel()
	logger.setConsoleLevel(LOG_NONE)
	ui = UIFactory(type = 'snack')
	try:
		adminUser = u'root'
		adminPass = u''
		messageBox = None
		while True:
			values = [
				{ "name": u"Config server", "value": config['address'] },
				{ "name": u"Opsi admin user", "value": adminUser },
				{ "name": u"Opsi admin password", "value": adminPass, "password": True }
			]
			values = ui.getValues(title=u'Config server connection', width=70, height=10, entries=values)
			if values is None:
				raise Exception(u"Canceled")

			config['address'] = values[0]["value"]
			adminUser = values[1]["value"]
			adminPass = values[2]["value"]

			messageBox = ui.createMessageBox(width=70, height=20, title=u'Register depot', text=u'')
			# Connect to config server
			logger.notice(u"Connecting to config server '%s' as user '%s'" % (config['address'], adminUser))
			messageBox.addText(u"Connecting to config server '%s' as user '%s'\n" % (config['address'], adminUser))

			try:
				jsonrpcBackend = JSONRPCBackend(address=config['address'], username=adminUser, password=adminPass)
				if not jsonrpcBackend.accessControl_userIsAdmin():
					raise Exception(u"User '%s' is not an admin user" % adminUser)
			except Exception as e:
				messageBox.hide()
				logger.error(u"Failed to connect to config server '%s' as user '%s': %s" % (config['address'], adminUser, e))
				ui.showError(text=u"Failed to connect to config server '%s' as user '%s': %s" % (config['address'], adminUser, e),
						title=u'Failed to connect', width=70, height=6, seconds=0)
				continue
			logger.notice(u"Successfully connected to config server '%s' as user '%s'" % (config['address'], adminUser))
			messageBox.addText(u"Successfully connected to config server '%s' as user '%s'\n" % (config['address'], adminUser))
			break

		depots = jsonrpcBackend.host_getObjects(id=getSysConfig()['fqdn'])
		if depots:
			# Already exists
			depot = depots[0]
			if not depot.depotWebdavUrl:
				depot.depotWebdavUrl = u''
			if not depot.masterDepotId:
				depot.masterDepotId = u''
			if not depot.hardwareAddress:
				depot.hardwareAddress = getSysConfig()['hardwareAddress'] or u''
			if not depot.ipAddress:
				depot.ipAddress = getSysConfig()['ipAddress'] or u''
			if not depot.networkAddress:
				depot.ipAddress = u'%s/%s' % (getSysConfig()['subnet'], getSysConfig()['netmask'])
			if not depot.depotWebdavUrl:
				depot.depotWebdavUrl = u'webdavs://%s:4447/depot' % getSysConfig()['fqdn']
		else:
			depotLocalUrl = u'file:///var/lib/opsi/depot'
			depotRemoteUrl = u'smb://%s/opsi_depot' % getSysConfig()['hostname']

			depot = OpsiDepotserver(
					id=getSysConfig()['fqdn'],
					opsiHostKey=None,
					depotLocalUrl=depotLocalUrl,
					depotRemoteUrl=depotRemoteUrl,
					depotWebdavUrl=u'webdavs://%s:4447/depot' % getSysConfig()['fqdn'],
					repositoryLocalUrl=u'file:///var/lib/opsi/repository',
					repositoryRemoteUrl=u'webdavs://%s:4447/repository' % getSysConfig()['fqdn'],
					description=u'',
					notes=u'',
					hardwareAddress=getSysConfig()['hardwareAddress'] or u'',
					ipAddress=getSysConfig()['ipAddress'] or u'',
					inventoryNumber=u'',
					networkAddress=u'%s/%s' % (getSysConfig()['subnet'], getSysConfig()['netmask']),
					maxBandwidth=0,
					isMasterDepot=True,
					masterDepotId=None,
			)

		while True:
			if depot.maxBandwidth < 0:
				depot.maxBandwidth = 0
			if depot.maxBandwidth > 0:
				depot.maxBandwidth = int(depot.maxBandwidth/1000)

			values = [
				{"name": u"Description", "value": depot.description},
				{"name": u"Inventory number", "value": depot.inventoryNumber},
				{"name": u"Notes", "value": depot.notes},
				{"name": u"Ip address", "value": depot.ipAddress},
				{"name": u"Hardware address", "value": depot.hardwareAddress},
				{"name": u"Network address", "value": depot.networkAddress},
				{"name": u"Maximum bandwidth (kbyte/s)", "value": depot.maxBandwidth         },
				{"name": u"Local depot url", "value": depot.depotLocalUrl},
				{"name": u"Remote depot url", "value": depot.depotRemoteUrl},
				{"name": u"Depot webdav url", "value": depot.depotWebdavUrl},
				{"name": u"Local repository url", "value": depot.repositoryLocalUrl},
				{"name": u"Remote repository url", "value": depot.repositoryRemoteUrl},
				{"name": u"Is master depot", "value": depot.isMasterDepot},
				{"name": u"Master depot id", "value": depot.masterDepotId or u'' },

			]
			values = ui.getValues(title=u'Depot server settings', width=70, height=16, entries=values)
			if values is None:
				raise Exception(u"Canceled")

			error = None
			try:
				depot.setDescription( values[0].get('value') )
			except Exception as e:
				if not error: error = u'Invalid description'

			try:
				depot.setInventoryNumber( values[1].get('value') )
			except Exception as e:
				if not error: error = u'Inventory number invalid'

			try:
				depot.setNotes( values[2].get('value') )
			except Exception as e:
				if not error: error = u'Invalid notes'

			try:
				depot.setIpAddress( values[3].get('value') )
			except Exception as e:
				if not error: error = u'Invalid ip address'

			try:
				depot.setHardwareAddress( values[4].get('value') )
			except Exception as e:
				if not error: error = u'Invalid hardware address'

			try:
				depot.setNetworkAddress( values[5].get('value') )
			except Exception as e:
				if not error: error = u'Invalid network address'

			try:
				depot.setMaxBandwidth( forceInt(values[6].get('value'))*1000 )
			except Exception as e:
				if not error: error = u'Invalid maximum bandwidth'

			try:
				depot.setDepotLocalUrl( values[7].get('value') )
			except Exception as e:
				if not error: error = u'Depot local url invalid'

			try:
				depot.setDepotRemoteUrl( values[8].get('value') )
			except Exception as e:
				if not error: error = u'Depot remote url invalid'

			try:
				if values[9].get('value'):
					depot.setDepotWebdavUrl( values[9].get('value') )
				else:
					depot.depotWebdavUrl = None
			except Exception as e:
				if not error: error = u'Depot webdav url invalid'

			try:
				depot.setRepositoryLocalUrl( values[10].get('value') )
			except Exception as e:
				if not error: error = u'Repository local url invalid'

			try:
				depot.setRepositoryRemoteUrl( values[11].get('value') )
			except Exception as e:
				if not error: error = u'Repository remote url invalid'

			try:
				depot.setIsMasterDepot( values[12].get('value') )
			except Exception as e:
				if not error: error = u'Invalid value for is master depot'

			try:
				if values[13].get('value'):
					depot.setMasterDepotId( values[13].get('value') )
				else:
					depot.masterDepotId = None
			except Exception as e:
				if not error: error = u'Master depot id invalid'

			if error:
				ui.showError(title=u'Bad value', text=error, width=50, height=5)
				continue

			break
	finally:
		ui.exit()
		logger.setConsoleLevel(consoleLevel)

	logger.notice(u"Creating depot '%s'" % depot.id)
	jsonrpcBackend.host_createObjects([ depot ])

	logger.notice(u"Getting depot '%s'" % depot.id)
	depots = jsonrpcBackend.host_getObjects(id = depot.id)
	if not depots:
		raise Exception(u"Failed to create depot")
	depot = depots[0]
	config['username'] = depot.id
	config['password'] = depot.opsiHostKey
	jsonrpcBackend.backend_exit()

	logger.notice(u"Testing connection to config server as user '%s'" % config['username'])
	try:
		jsonrpcBackend = JSONRPCBackend(address=config['address'], username=config['username'], password=config['password'])
	except Exception as e:
		raise Exception(u"Failed to connect to config server as user '%s': %s" % (config['username'], e))
	logger.notice(u"Successfully connected to config server as user '%s'" % config['username'])

	backendUtils.updateConfigFile(backendConfigFile, config)

	logger.notice(u"Updating dispatch config '%s'" % dispatchConfigFile)
	lines = []
	f = codecs.open(dispatchConfigFile, 'r', 'utf-8')
	for line in f.readlines():
		if line.strip() and line.strip()[0] not in (';', '#'):
			break
		lines.append(line)
	f.close()
	f = codecs.open(dispatchConfigFile, 'w', 'utf-8')
	f.writelines(lines)
	f.write("backend_.* : jsonrpc, opsipxeconfd, dhcpd\n")
	f.write(".*         : jsonrpc\n")
	f.close()
	logger.notice(u"Dispatch config '%s' updated" % dispatchConfigFile)

	setRights()
	restartServices()


def restartServices():
	""" Restart *opsiconfd* and *opsipxeconfd* """
	logger.notice(u"Restarting opsi webservice")
	execute("service opsiconfd restart")
	logger.notice(u"Restarting PXE service")
	execute("service opsipxeconfd restart")


def renewOpsiconfdCert(unattendedConfiguration=None):
	def renewCert():
		if certificateExisted:
			renewCertificate(
				yearsUntilExpiration=certparams['expires'],
				config=certparams
			)
		else:
			createCertificate(config=certparams)

	try:
		which("ucr")
		logger.notice(u"Don't use recreate method on UCS-Systems")
		return
	except Exception:
		pass

	certificateExisted = True
	try:
		certparams = loadConfigurationFromCertificate()
	except UnreadableCertificateError as err:
		logger.notice(
			u'Using default values because reading old certificate '
			u'failed: {0}'.format(err)
		)
		certparams = DEFAULT_CERTIFICATE_PARAMETERS
		certparams["commonName"] = forceHostId(getfqdn(conf=OPSI_GLOBAL_CONF))
	except NoCertificateError:
		certificateExisted = False
		certparams = DEFAULT_CERTIFICATE_PARAMETERS
		certparams["commonName"] = forceHostId(getfqdn(conf=OPSI_GLOBAL_CONF))

	if 'expires' not in certparams:
		certparams['expires'] = "2"  # Not included in existing cert

	if unattendedConfiguration is not None:
		logger.debug(u"Unattended certificate config: {0}".format(unattendedConfiguration))
		certparams.update(unattendedConfiguration)
		logger.debug(u"Configuration for unattended certificate renewal: {0}".format(certparams))

		renewCert()
		setPasswdRights()
		setRights(OPSICONFD_CERTFILE)
		restartServices()
		return

	consoleLevel = logger.getConsoleLevel()
	logger.setConsoleLevel(LOG_NONE)
	ui = UIFactory(type='snack')

	try:
		while True:
			values = [
				{ "name": u"Country", "value": certparams["country"] },
				{ "name": u"State", "value": certparams["state"] },
				{ "name": u"Locality", "value": certparams["locality"] },
				{ "name": u"Organization", "value": certparams["organization"] },
				{ "name": u"OrganizationUnit", "value": certparams["organizationalUnit"] },
				{ "name": u"Hostname", "value": certparams["commonName"] },
				{ "name": u"Emailaddress", "value": certparams["emailAddress"] },
				{ "name": u"Expires (Years)", "value": certparams["expires"] },
			]
			values = ui.getValues(title=u'Renew opsiconfd Certificate', width=70, height=15, entries=values)

			if values is None:
				raise Exception(u"Canceled")

			error = None

			certparams["country"] = values[0]["value"]
			certparams["state"] = values[1]["value"]
			certparams["locality"] = values[2]["value"]
			certparams["organization"] = values[3]["value"]
			certparams["organizationalUnit"] = values[4]["value"]
			certparams["commonName"] = values[5]["value"]
			certparams["emailAddress"] = values[6]["value"]

			if error is None:
				if not certparams["commonName"] == forceHostId(getfqdn(conf=OPSI_GLOBAL_CONF)):
					error = "Hostname must be the FQDN from Server"

			if error is None:
				try:
					certparams["expires"] = forceInt(values[7]["value"])
				except Exception:
					error = u'No valid years for expiredate given, must be an integer'

			if error:
				ui.showError(title=u'Bad value', text=error, width=50, height=5)
				continue

			break
	finally:
		ui.exit()
		logger.setConsoleLevel(consoleLevel)

	renewCert()
	setPasswdRights()
	setRights(OPSICONFD_CERTFILE)
	restartServices()


def setPasswdRights():
	""" Setting correct permissions on ``/etc/opsi/passwd`` """
	logger.notice(u"Setting rights")
	opsiconfdUid  = pwd.getpwnam(OPSICONFD_USER)[2]
	adminGroupGid = grp.getgrnam(ADMIN_GROUP)[2]
	os.chown(u'/etc/opsi/passwd', opsiconfdUid, adminGroupGid)
	os.chmod(u'/etc/opsi/passwd', 0660)


def initializeBackends():
	if not os.path.exists(u'/etc/opsi/passwd'):
		f = codecs.open(u'/etc/opsi/passwd', 'w', 'utf-8')
		f.close()
		setPasswdRights()

	from OPSI.Backend.BackendManager import BackendManager
	backend = BackendManager(
		dispatchConfigFile=u'/etc/opsi/backendManager/dispatch.conf',
		backendConfigDir=u'/etc/opsi/backends',
		extensionConfigDir=u'/etc/opsi/backendManager/extend.d',
		depotbackend=False
	)
	backend.backend_createBase()

	logger.notice(u"Try to find a Configserver.")
	configServer = backend.host_getObjects(type='OpsiConfigserver')
	if not configServer and not backend.host_getIdents(type='OpsiConfigserver', id=getSysConfig()['fqdn']):
		depot = backend.host_getObjects(type='OpsiDepotserver', id=getSysConfig()['fqdn'])
		if not depot:
			logger.notice(u"Creating config server '%s'" % getSysConfig()['fqdn'])
			#depotLocalUrl  = u'file:///opt/pcbin/install'
			#depotRemoteUrl = u'smb://%s/opt_pcbin/install' % getSysConfig()['hostname']
			#if (getSysConfig()['distribution'].lower().find('suse linux enterprise server') != -1):
			depotLocalUrl = u'file:///var/lib/opsi/depot'
			depotRemoteUrl = u'smb://%s/opsi_depot' % getSysConfig()['hostname']

			backend.host_createOpsiConfigserver(
				id=getSysConfig()['fqdn'],
				opsiHostKey=None,
				depotLocalUrl=depotLocalUrl,
				depotRemoteUrl=depotRemoteUrl,
				depotWebdavUrl=u'webdavs://%s:4447/depot' % getSysConfig()['fqdn'],
				repositoryLocalUrl=u'file:///var/lib/opsi/repository',
				repositoryRemoteUrl=u'webdavs://%s:4447/repository' % getSysConfig()['fqdn'],
				description=None,
				notes=None,
				hardwareAddress=getSysConfig()['hardwareAddress'],
				ipAddress=getSysConfig()['ipAddress'],
				inventoryNumber=None,
				networkAddress=u'%s/%s' % (getSysConfig()['subnet'], getSysConfig()['netmask']),
				maxBandwidth=0,
				isMasterDepot=True,
				masterDepotId=None,
			)
			configServer = backend.host_getObjects(type='OpsiConfigserver', id=getSysConfig()['fqdn'])
		else:
			logger.notice(u"Converting depot server '%s' to config server" % getSysConfig()['fqdn'])
			configServer = OpsiConfigserver.fromHash(depot[0].toHash())
			backend.host_createObjects(configServer)

			# list expected in further processing
			configServer = [configServer]
	else:
		depot = backend.host_getObjects(type='OpsiDepotserver', id=getSysConfig()['fqdn'])
		if not depot:
			logger.notice(u"Creating depot server '%s'" % getSysConfig()['fqdn'])
			#depotLocalUrl  = u'file:///opt/pcbin/install'
			#depotRemoteUrl = u'smb://%s/opt_pcbin/install' % getSysConfig()['hostname']
			#if (getSysConfig()['distribution'].lower().find('suse linux enterprise server') != -1):
			depotLocalUrl = u'file:///var/lib/opsi/depot'
			depotRemoteUrl = u'smb://%s/opsi_depot' % getSysConfig()['hostname']

			depotServer = backend.host_createOpsiDepotserver(
				id=getSysConfig()['fqdn'],
				opsiHostKey=None,
				depotLocalUrl=depotLocalUrl,
				depotRemoteUrl=depotRemoteUrl,
				depotWebdavUrl=u'webdavs://%s:4447/depot' % getSysConfig()['fqdn'],
				repositoryLocalUrl=u'file:///var/lib/opsi/repository',
				repositoryRemoteUrl=u'webdavs://%s:4447/repository' % getSysConfig()['fqdn'],
				description=None,
				notes=None,
				hardwareAddress=getSysConfig()['hardwareAddress'],
				ipAddress=getSysConfig()['ipAddress'],
				inventoryNumber=None,
				networkAddress=u'%s/%s' % (getSysConfig()['subnet'], getSysConfig()['netmask']),
				maxBandwidth=0,
				isMasterDepot=True,
				masterDepotId=None,
			)

	if configServer:
		if configServer[0].id == getSysConfig()['fqdn']:
			configServer = backend.host_getObjects(type='OpsiConfigserver')
			if not configServer:
				raise Exception(u"Config server '%s' not found" % getSysConfig()['fqdn'])
			configServer = configServer[0]
			if getSysConfig()['ipAddress']:
				configServer.setIpAddress(getSysConfig()['ipAddress'])
			if getSysConfig()['hardwareAddress']:
				configServer.setHardwareAddress(getSysConfig()['hardwareAddress'])

			#make sure the config server is present in all backends or we get reference error later on
			backend.host_insertObject(configServer)

	initializeConfigs(backend=backend, configServer=configServer)
	backend.backend_exit()

	depotDir = '/var/lib/opsi/depot'
	if not os.path.exists(depotDir):
		try:
			os.mkdir(depotDir)
			if os.path.exists("/opt/pcbin/install"):
				logger.warning(u"You have an old depot configuration. Using /opt/pcbin/install is depracted, please use /var/lib/opsi/depot instead.")
		except Exception as error:
			logger.warning(u"Failed to create depot directory '%s': %s" % (depotDir, error))


def editConfigDefaults():
	from OPSI.Backend.BackendManager import BackendManager
	backend = BackendManager(
		dispatchConfigFile=u'/etc/opsi/backendManager/dispatch.conf',
		backendConfigDir=u'/etc/opsi/backends',
		extensionConfigDir=u'/etc/opsi/backendManager/extend.d',
		depotbackend=False
	)
	configs = backend.config_getObjects()

	consoleLevel = logger.getConsoleLevel()
	logger.setConsoleLevel(LOG_NONE)
	ui = UIFactory(type='snack')
	try:
		while True:
			entries = []
			maxConfigIdLen = 0
			for config in configs:
				if u'configed.saved_search.' in config.id:
					continue

				if (len(config.id) > maxConfigIdLen):
					maxConfigIdLen = len(config.id)
			format = u"%-10s %-" + str(maxConfigIdLen) + "s = %s"
			for config in configs:
				type = '[unicode]'
				if (config.getType() == 'BoolConfig'):
					type = '[bool]'

				if u'configed.saved_search.' in config.id:
					continue

				values = u', '.join(forceUnicodeList(config.defaultValues))
				if len(values) > 60:
					values = values[:60] + '...'
				entries.append(
					{
						"id": config.id,
						"name": format % (type, config.id, values)
					}
				)

			selection = ui.getSelection(
				entries, radio=True,
				width=100, height=10,
				title=u'Please select config value to change',
				okLabel='Change', cancelLabel='Quit'
			)

			if not selection:
				return

			configId = None
			for entry in entries:
				if (selection[0] == entry['name']):
					configId = entry['id']
					break

			selectedConfig = -1
			for i in range(len(configs)):
				if (configs[i].id == configId):
					selectedConfig = i
					break

			addNewValue = False
			cancelLabel = u'Back'
			title = u'Edit default values for: %s' % configs[selectedConfig].id
			text = configs[selectedConfig].description or u''
			if configs[selectedConfig].possibleValues:
				entries = []
				for possibleValue in configs[selectedConfig].possibleValues:
					entries.append({'name': possibleValue, 'value': possibleValue, 'selected': possibleValue in configs[selectedConfig].defaultValues})
				radio = not configs[selectedConfig].multiValue
				if configs[selectedConfig].editable:
					entries.append({'name': '<other value>', 'value': '<other value>', 'selected': False})
				selection = ui.getSelection(entries, radio=radio, width=65, height=10, title=title, text=text, cancelLabel=cancelLabel)

				if selection is None:
					continue
				if "<other value>" in selection:
					addNewValue = True
				else:
					configs[selectedConfig].setDefaultValues(selection)
			else:
				addNewValue = True

			if addNewValue:
				default = u''
				if configs[selectedConfig].defaultValues:
					default = configs[selectedConfig].defaultValues[0]
				value = ui.getValue(width=65, height=13, title=title, default=default, password=False, text=text, cancelLabel=cancelLabel)
				if value is None:
					continue

				possibleValues = configs[selectedConfig].getPossibleValues()
				if not value in possibleValues:
					possibleValues.append(value)
					configs[selectedConfig].setPossibleValues(possibleValues)
				configs[selectedConfig].setDefaultValues(value)

			backend.config_updateObjects([configs[selectedConfig]])

	finally:
		ui.exit()
		logger.setConsoleLevel(consoleLevel)


def usage():
	print u"\nUsage: %s [options]" % os.path.basename(sys.argv[0])
	print u""
	print u"Options:"
	print u"   -h, --help  show this help"
	print u"   -l          log-level 0..9"
	print u""
	print u"   --log-file <path>             path to log file"
	print u"   --backend-config <json hash>  overwrite backend config hash values"
	print u"   --ip-address <ip>             force to this ip address (do not lookup by name)"
	print u"   --register-depot              register depot at config server"
	print u"   --set-rights [path]           set default rights on opsi files (in [path] only)"
	print u"   --init-current-config         init current backend configuration"
	print u"   --update-from=<version>       update from opsi version <version>"
	print u"   --update-mysql                update mysql backend"
	print u"   --update-ldap                 update ldap backend"
	print u"   --update-univention           update univention backend"
	print u"   --update-file                 update file backend"
	print u"   --configure-mysql             configure mysql backend"
	print u"   --edit-config-defaults        edit global config defaults"
	print u"   --cleanup-backend             cleanup backend"
	print u"   --auto-configure-samba        patch smb.conf"
	print u"   --auto-configure-dhcpd        patch dhcpd.conf"
	print u"   --renew-opsiconfd-cert        renew opsiconfd-cert"
	print u"   --patch-sudoers-file	         patching sudoers file for tasks in opsiadmin context."
	print u""


def main():
	if (os.geteuid() != 0):
		raise Exception(u"This script must be startet as root")

	try:
		(opts, args) = getopt.getopt(sys.argv[1:], "hl:",
			[
				'help', 'log-file=', 'ip-address=', 'backend-config=',
				'init-current-config', 'set-rights', 'auto-configure-samba',
				'auto-configure-dhcpd', 'register-depot', 'configure-mysql',
				'update-mysql', 'update-ldap', 'update-univention',
				'update-file', 'edit-config-defaults', 'cleanup-backend',
				'update-from=', 'renew-opsiconfd-cert', 'patch-sudoers-file',
				'unattended='
			]
		)

	except Exception:
		usage()
		raise

	task = None
	updateFrom = None
	autoConfigureSamba = False
	autoConfigureDhcpd = False
	global backendConfig
	backendConfig = {}
	unattended = None

	for (opt, arg) in opts:
		if opt in ("-h", "--help"):
			usage()
			return
		elif (opt == "--log-file"):
			logger.setLogFile(arg)
			logger.setFileLevel(LOG_DEBUG)
		elif (opt == "-l"):
			logger.setConsoleLevel(int(arg))
		elif (opt == "--ip-address"):
			global ipAddress
			ipAddress = forceIpAddress(arg)
		elif (opt == "--backend-config"):
			backendConfig = json.loads(arg)
		elif (opt == "--init-current-config"):
			task = 'init-current-config'
		elif (opt == "--set-rights"):
			task = 'set-rights'
		elif (opt == "--register-depot"):
			task = 'register-depot'
		elif (opt == "--configure-mysql"):
			task = 'configure-mysql'
		elif (opt == "--update-mysql"):
			task = 'update-mysql'
		elif (opt == "--update-ldap"):
			task = 'update-ldap'
		elif (opt == "--update-univention"):
			task = 'update-univention'
		elif (opt == "--update-file"):
			task = 'update-file'
		elif (opt == "--edit-config-defaults"):
			task = 'edit-config-defaults'
		elif (opt == "--cleanup-backend"):
			task = 'cleanup-backend'
		elif (opt == "--update-from"):
			updateFrom = arg
		elif (opt == "--auto-configure-samba"):
			autoConfigureSamba = True
		elif (opt == "--auto-configure-dhcpd"):
			autoConfigureDhcpd = True
		elif (opt == "--renew-opsiconfd-cert"):
			task = "renew-opsiconfd-cert"
		elif (opt == "--patch-sudoers-file"):
			task = "patch-sudoers-file"
		elif opt == '--unattended':
			logger.debug(u'Got unattended argument: {0}'.format(arg))

			if args and not arg.strip().endswith('}'):
				logger.debug("Probably wrong reading of arguments by getopt.")

				tempArgs = [arg]
				while args and not tempArgs[-1].strip().endswith('}'):
					tempArgs.append(args.pop(0))
					logger.debug("temp arguments are: {0}".format(tempArgs))

				arg = ' '.join(tempArgs)
				del tempArgs

			unattended = json.loads(arg)

	path = u'/'
	if len(args) > 0:
		logger.debug("Additional arguments are: {0}".format(args))
		if task == 'set-rights' and len(args) == 1:
			path = os.path.abspath(forceFilename(args[0]))
		else:
			usage()
			raise Exception(u"Too many arguments")

	if autoConfigureSamba:
		configureSamba()

	if autoConfigureDhcpd:
		configureDHCPD()

	if (task == 'set-rights'):
		setRights(path)

	elif (task == 'init-current-config'):
		initializeBackends()
		configureClientUser()

	elif task == 'configure-mysql':
		configureMySQLBackend(unattended)

	elif (task == 'update-mysql'):
		updateMySQLBackend(additionalBackendConfiguration=backendConfig)
		update()

	elif (task == 'update-ldap'):
		updateLDAPBackend()
		update()

	elif (task == 'update-univention'):
		updateUniventionBackend()
		update()

	elif (task == 'update-file'):
		updateFileBackend()
		update()

	elif (task == 'register-depot'):
		registerDepot()
		configureClientUser()

	elif (task == 'edit-config-defaults'):
		editConfigDefaults()

	elif (task == 'cleanup-backend'):
		cleanupBackend()

	elif (task == "renew-opsiconfd-cert"):
		renewOpsiconfdCert(unattended)

	elif (task == "patch-sudoers-file"):
		patchSudoersFileForOpsi()

	elif (updateFrom):
		update(updateFrom)

	elif not autoConfigureSamba and not autoConfigureDhcpd:
		usage()
		sys.exit(1)


if (__name__ == "__main__"):
	logger.setLogFormat(u'[%l] [%D] %M (%F|%N)')
	logger.setLogFile(LOG_FILE)
	logger.setFileLevel(LOG_INFO)
	exception = None
	try:
		main()
	except SystemExit:
		pass
	except Exception as exception:
		logger.logException(exception)
		print >> sys.stderr, u"\nERROR: %s\n" % exception
		sys.exit(1)

	sys.exit(0)
