#! /usr/bin/env python
# -*- coding: utf-8 -*-

# This module is part of the desktop management solution opsi
# (open pc server integration) http://www.opsi.org

# Copyright (C) 2010-2019 uib GmbH
# http://www.uib.de/
# All rights reserved.

# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.

# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.

# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
"""
opsi-setup - swiss army knife for opsi administration.

:copyright: uib GmbH <info@uib.de>
:author: Jan Schneider <j.schneider@uib.de>
:author: Erol Ueluekmen <e.ueluekmen@uib.de>
:author: Niko Wenselowski <n.wenselowski@uib.de>
:license: GNU Affero General Public License version 3
"""

import codecs
import getopt
import json
import os
import pwd
import re
import shutil
import sys
import time

import OPSI.Util.Task.ConfigureBackend as backendUtils
from OPSI.Backend.BackendManager import BackendManager
from OPSI.Backend.JSONRPC import JSONRPCBackend
from OPSI.Config import DEFAULT_DEPOT_USER as CLIENT_USER
from OPSI.Logger import Logger, LOG_NOTICE, LOG_NONE, LOG_DEBUG, LOG_INFO
from OPSI.Object import OpsiDepotserver
from OPSI.System.Posix import (
	Distribution, execute, getLocalFqdn, getNetworkConfiguration, which)
from OPSI.Types import forceFilename, forceInt, forceIpAddress
from OPSI.UI import UIFactory
from OPSI.Util import blowfishDecrypt, randomString
from OPSI.Util.File.Opsi import BackendDispatchConfigFile
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.ConfigDefaults import editConfigDefaults
from OPSI.Util.Task.ConfigureBackend.ConfigurationData import (
	initializeConfigs, readWindowsDomainFromSambaConfig)
from OPSI.Util.Task.ConfigureBackend.DHCPD import configureDHCPD
from OPSI.Util.Task.ConfigureBackend.MySQL import (
	DatabaseConnectionFailedException,
	configureMySQLBackend as configureMySQLBackendWithoutGUI)
from OPSI.Util.Task.ConfigureBootimage import patchServiceUrlInDefaultConfigs
from OPSI.Util.Task.InitializeBackend import (
	_getServerConfig as getServerConfig, initializeBackends)
from OPSI.Util.Task.Rights import setPasswdRights, setRights
from OPSI.Util.Task.Samba import SMB_CONF, configureSamba
from OPSI.Util.Task.Sudoers import patchSudoersFileForOpsi
from OPSI.Util.Task.UpdateBackend.ConfigurationData import (
	getServerAddress, updateBackendData)
from OPSI.Util.Task.UpdateBackend.File import updateFileBackend
from OPSI.Util.Task.UpdateBackend.MySQL import updateMySQLBackend


LOG_FILE = u'/tmp/opsi-setup.log'

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

backendConfig = {}
ipAddress = None
sysConfig = {}


class CancelledByUserError(Exception):
	pass


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


def getSysConfig():
	global sysConfig
	if sysConfig:
		return sysConfig

	logger.notice(u"Getting current system config")

	distri = Distribution()
	sysConfig['distributor'] = distri.distributor
	sysConfig['distribution'] = getDistribution()

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

	sysConfig.update(getNetworkConfiguration(ipAddress))

	sysConfig['fqdn'] = getLocalFqdn()
	sysConfig['hostname'] = sysConfig['fqdn'].split(u'.')[0]
	sysConfig['domain'] = u'.'.join(sysConfig['fqdn'].split(u'.')[1:])
	sysConfig['winDomain'] = readWindowsDomainFromSambaConfig(SMB_CONF)

	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 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):
		with codecs.open(idRsaPub, 'r', 'utf-8') as f:
			with codecs.open(authorizedKeys, 'w', 'utf-8') as f2:
				f2.write(f.read())

	setRights(sshDir)
	setPasswordForClientUser()


def setPasswordForClientUser():
	fqdn = getSysConfig()['fqdn']

	password = None
	backend = None
	try:
		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=fqdn)[0]

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

		if configserverId:
			jsonrpcBackend = None
			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'])
			except Exception as error:
				logger.info(u"Failed to get client user (pcpatch) password from configserver: %s" % error)
			finally:
				if jsonrpcBackend is not None:
					jsonrpcBackend.backend_exit()

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

	if not password:
		logger.warning("No password for pcpatch found. Generating random password.")
		password = randomString(32)

	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 'jsonrpc' not in backends:
				isConfigServer = True
			break
	except Exception as e:
		logger.warning(e)

	if isConfigServer:
		try:
			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.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()

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

		updateBackendData(backend)  # opsi 4.0 -> 4.1

	configureSamba()


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 key not 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(username=None, password=None, configserver=None):
	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

	if username and password and configserver:
		logger.notice(u"Credentials are given, trying to registering depot noninteractive.")
		config['address'] = configserver
		adminUser = username
		adminPass = password

		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:
			logger.error(u"Failed to connect to config server '%s' as user '%s': %s" % (config['address'], adminUser, e))
		logger.notice(u"Successfully connected to config server '%s' as user '%s'" % (config['address'], adminUser))

		fqdn = getLocalFqdn()
		depots = jsonrpcBackend.host_getObjects(id=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' % fqdn
			if not depot.masterDepotId:
				depot.masterDepotId = None

			if not depot.workbenchLocalUrl:
				depot.workbenchLocalUrl = u'file:///var/lib/opsi/workbench'

			if not depot.workbenchRemoteUrl:
				depotAddress = getServerAddress(depot.depotRemoteUrl)
				remoteWorkbenchPath = u'smb://{}/opsi_workbench'.format(depotAddress)
				depot.workbenchRemoteUrl = remoteWorkbenchPath
		else:
			serverConfig = getServerConfig(fqdn, getSysConfig())

			for key in ('description', 'notes', 'hardwareAddress', 'ipAddress', 'inventoryNumber'):
				if not serverConfig[key]:
					# We want to make sure this is set to an empty
					# string for the UI
					serverConfig[key] = u''

			depot = OpsiDepotserver(**serverConfig)
	else:
		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=fqdn)
			try:
				depot = depots[0]

				if depot.getType() == 'OpsiClient':
					deleteClient = ui.yesno(
						title=u'ID already in use',
						text=u'''We already have an client with the ID %s.
We can not register a new depot with the same ID.
Should the client be deleted to free the ID?
This will remove the client and it's settings from opsi.''' % depot.id,
						okLabel=u'Delete client',
						cancelLabel=u'Cancel'
					)

					if not deleteClient:
						raise CancelledByUserError(u'Cancelled')

					jsonrpcBackend.host_delete(id=depot.id)

					raise ValueError("We want defaults.")

				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' % fqdn

				if not depot.workbenchLocalUrl:
					depot.workbenchLocalUrl = u'file:///var/lib/opsi/workbench'

				if not depot.workbenchRemoteUrl:
					depotAddress = getServerAddress(depot.depotRemoteUrl)
					remoteWorkbenchPath = u'smb://{}/opsi_workbench'.format(depotAddress)
					depot.workbenchRemoteUrl = remoteWorkbenchPath
			except (IndexError, ValueError):
				serverConfig = getServerConfig(fqdn, getSysConfig())

				for key in ('description', 'notes', 'hardwareAddress', 'ipAddress', 'inventoryNumber'):
					if not serverConfig[key]:
						# We want to make sure this is set to an empty
						# string for the UI
						serverConfig[key] = u''

				depot = OpsiDepotserver(**serverConfig)

			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"Local workbench url", "value": depot.workbenchLocalUrl},
					{"name": u"Remote workbench url", "value": depot.workbenchRemoteUrl},
					{"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.setWorkbenchLocalUrl(values[12].get('value'))
				except Exception as e:
					if not error:
						error = u'Workbench local url invalid'

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

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

				try:
					if values[15].get('value'):
						depot.setMasterDepotId(values[15].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)

	# We want to keep lines that are currently commented out and only
	# replace the currently active backend configuration
	with codecs.open(dispatchConfigFile, 'r', 'utf-8') as originalDispatchConfig:
		lines = [
			line for line in originalDispatchConfig
			if line.strip().startswith((';', '#'))
		]

	with codecs.open(dispatchConfigFile, 'w', 'utf-8') as newDispatchConfig:
		newDispatchConfig.writelines(lines)
		newDispatchConfig.write("backend_.* : jsonrpc, opsipxeconfd, dhcpd\n")
		newDispatchConfig.write(".*         : jsonrpc\n")
	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 makeCert():
		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"] = getLocalFqdn()
	except NoCertificateError:
		certificateExisted = False
		certparams = DEFAULT_CERTIFICATE_PARAMETERS
		certparams["commonName"] = getLocalFqdn()

	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))

		makeCert()
		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"] or ''},
				{"name": u"State", "value": certparams["state"] or ''},
				{"name": u"Locality", "value": certparams["locality"] or ''},
				{"name": u"Organization", "value": certparams["organization"] or ''},
				{"name": u"OrganizationUnit", "value": certparams["organizationalUnit"] or ''},
				{"name": u"Hostname", "value": certparams["commonName"] or ''},
				{"name": u"Emailaddress", "value": certparams["emailAddress"] or ''},
				{"name": u"Expires (Years)", "value": certparams["expires"] or ''},
			]
			values = ui.getValues(title=u'Renew opsiconfd Certificate', width=70, height=15, entries=values)

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

			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"]

			error = None

			if error is None:
				if certparams["commonName"] != getLocalFqdn():
					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)

	makeCert()
	setPasswdRights()
	setRights(OPSICONFD_CERTFILE)
	restartServices()


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-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-file', 'edit-config-defaults',
				'cleanup-backend', 'update-from=', 'binddata=',
				'renew-opsiconfd-cert', 'patch-sudoers-file', 'unattended='
			]
		)
	except Exception:
		usage()
		raise

	global backendConfig
	global ipAddress
	task = None
	updateFrom = None
	autoConfigureSamba = False
	autoConfigureDhcpd = False
	username = None
	password = None
	configserver = None
	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"):
			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-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 == "--binddata"):
			# Not documented option for automatic use from register-depot task in univention-join-script
			i = 0
			arglist = arg.split(" ")
			for entry in arglist:
				if "configserver" in entry:
					configserver = arglist[i + 1]
				elif "binddn" in entry:
					temp = arglist[i + 1]
					if "=" in temp:
						username = temp.split("=")[1].split(",")[0]
				elif "bindpw" in entry:
					password = arglist[i + 1]
				if i >= len(arglist) - 2:
					break
				else:
					i += 1
		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]))
		elif task == 'register-depot':
			pass
		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(ipAddress)
		configureClientUser()
		with BackendManager() as backend:
			patchServiceUrlInDefaultConfigs(backend)

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

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

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

	elif (task == 'register-depot'):
		registerDepot(username, password, configserver)
		configureClientUser()
		with BackendManager() as backend:
			patchServiceUrlInDefaultConfigs(backend)

	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)
