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

import cherrypy
import httplib
import os
import threading
import Queue
from optparse import OptionParser
import simplejson
import signal
import sys
import tempfile
import time
import traceback
import uuid
import datetime
import hashlib
import urllib
from ipaddr import IPAddress

import notifier
from daemon.runner import DaemonRunner, DaemonRunnerStopFailureError, DaemonRunnerStartFailureError

import univention.management.console.protocol as umcp
from univention.management.console.log import *
import univention.config_registry as ucr

configRegistry = ucr.ConfigRegistry()
configRegistry.load()

# global objects
try:
	_session_timeout = int(configRegistry.get('umc/http/session/timeout', 300))
except (TypeError, ValueError):
	CORE.process('Failed to read session timeout from UCR variable umc/http/session/timeout. Using default of 300 seconds')
	_session_timeout = 300
_thread_http = None


def logExceptions(f):
	def new_f(*args, **kwargs):
		try:
			return f(*args, **kwargs)
		except (cherrypy.HTTPError, cherrypy.HTTPRedirect):
			# ignore common cherrypy exceptions
			raise
		except (KeyboardInterrupt, SystemExit):
			# ignore
			raise
		except Exception as ex:
			CORE.error('Traceback in %s(%r, %r):\n%s' % (f.__name__, args, kwargs, traceback.format_exc()))
			raise
	new_f.__name__ = f.__name__
	return new_f


class SessionClient(object):

	def __init__(self, timeout=300, ip=None, cookie=False):
		CORE.info('SessionClient(0x%x): creating new session' % (id(self),))
		self.client = umcp.Client()
		self.client.signal_connect('authenticated', self._authenticated)
		self.client.signal_connect('response', self._response)
		try:
			self.client.connect()
			CORE.info('SessionClient(0x%x): connected to UMC server' % (id(self),))
		except umcp.NoSocketError:
			CORE.warn('SessionClient(0x%x): connection to UMC server failed' % (id(self),))
			raise umcp.NoSocketError('Connection failed')
		self._auth_response = None
		self._auth_response_queue = None
		self._requestid2response_queue = {}
		self._timeout = timeout
		self._time_remaining = timeout
		self._timer()  # launch the timer
		self._lock = threading.Lock()
		self.ip = ip
		self.cookie = cookie

	def renew_timer(self):
		"""Refresh the remaining time the session is will be held open."""
		self._time_remaining = self._timeout

	def _timer(self):
		"""In order to avoid problems when the system time is changed (e.g.,
		via rdate), we register a timer event that counts down the session
		timeout second-wise."""
		# count down the remaining time
		self._time_remaining -= 1

		if self._time_remaining <= 0:
			# session has timed out
			self._timed_out()
		else:
			# count down the timer second-wise (in order to avoid problems when
			# changing the system time, e.g. via rdate)
			notifier.timer_add(1000, self._timer)

	def _timed_out(self):
		"""Timer event: the session has expired and therefor the
		connection to the UMC server is closed as long as there are no
		open requests."""
		CORE.info('SessionClient(0x%x): timed out!' % (id(self),))
		if self._requestid2response_queue:
			CORE.info('SessionClient(0x%x): There are open requests (%s). Postpone session shutdown' % (id(self), ','.join(self._requestid2response_queue.keys())))
			self._timeout = 0  # wait until all requests are answered
		else:
			CORE.info('SessionClient(0x%x): Closing connection to UMC server' % (id(self),))
			self.client.disconnect()
		return False

	def _authenticated(self, success, status, message):
		"""Callback function for 'authenticated' from UMCP-Server."""
		CORE.process('SessionClient(0x%x): _authenticated: success=%s  status=%s  message=%s' % (id(self), success, status, message))
		self._auth_response.status = status
		self._auth_response.message = message
		self._auth_response_queue.put(self._auth_response)
		# release queue object
		self._auth_response_queue = None

	def authenticate_user(self, request, response_queue):
		"""Send authentication request to UMC server."""
		CORE.info('SessionClient(0x%x): authenticate_user: sending authentication request for user %s' % (id(self), request.body['username']))
		self._auth_response = umcp.Response(request)
		self._auth_response.body['sessionid'] = request.body.get('sessionid', '')
		self._auth_response_queue = response_queue
		self.client.authenticate(request.body['username'], request.body['password'], request.body['new_password'], request.body['locale'])

	def _response(self, response):
		"""Queue response from UMC server."""
		self._lock.acquire()
		try:
			try:
				# get and remove queue for response
				queue = self._requestid2response_queue.pop(response.id)
			except KeyError:
				CORE.process('SessionClient(0x%x): no request(%s) found: status=%s' % (id(self), response.id, response.status))
			else:
				CORE.info('SessionClient(0x%x): got response(%s): status=%s queue=0x%x' % (id(self), response.id, response.status, id(queue)))
				queue.put(response)
		finally:
			self._lock.release()

		# shutdown connection when the session is expired and there are no open requests
		if self._timeout == 0 and not self._requestid2response_queue:
			self.client.disconnect()

	def send_request(self, request, response_queue):
		"""Send request to UMC server."""
		CORE.info('SessionClient(0x%x): sending request(%s)' % (id(self), request.id))
		self._lock.acquire()
		try:
			self._requestid2response_queue[request.id] = response_queue
		finally:
			self._lock.release()
		self.client.request(request)
		self.renew_timer()

	def cleanup_request(self, request):
		"""Remove request from mapping."""
		self._lock.acquire()
		try:
			del self._requestid2response_queue[request.id]
		finally:
			self._lock.release()


class LoginToken(object):

	def __init__(self, sessionid, username, timeout=None):
		self.sessionid = sessionid
		self.username = username
		if timeout is not None:
			self.timeout = timeout
		else:
			self.timeout = time.time() + int(configRegistry.get('umc/web/sso/timeout', 15))

	def repr(self):
		return '<LoginToken(username=%s, sessionid=%s, timeout=%d)>' % (self.username, self.sessionid, self.timeout)

	def is_valid(self):
		return self.timeout > time.time()


class LoginTokenDict(dict):

	def add_session(self, sessionid, username, timeout=None):
		""" adds a new session to dictionary """
		login_token = hashlib.sha256(sessionid).hexdigest()
		self[login_token] = LoginToken(sessionid, username, timeout)

	def remove_session(self, sessionid):
		""" remove specific session and all timed out sessions from dictionary """
		now = time.time()
		remove_list = [key for (key, value) in self.items() if value.sessionid == sessionid or value.timeout < now]
		for key in remove_list:
			del self[key]


class UMCP_Dispatcher(object):

	"""Dispatcher used to exchange the requests between CherryPy and UMC"""
	# sessionid ==> SessionClient
	sessions = {}
	# loginToken ==> LoginToken(sessionid, username, timeout)
	logintokens = LoginTokenDict()
	_queue_send = Queue.Queue()

	@classmethod
	@logExceptions
	def check_queue(cls):
		while True:
			try:
				queuerequest = cls._queue_send.get_nowait()
			except Queue.Empty:
				# Queue is empty - nothing to do (for now)
				return True
			CORE.info('UMCP_Dispatcher: check_queue: new request: 0x%x' % (id(queuerequest),))

			if not isinstance(queuerequest.sessionid, basestring) and queuerequest.sessionid is not None:
				CORE.process('UMCP_Dispatcher: check_queue: got invalid sessionid: %r' % (queuerequest.sessionid,))
			if not isinstance(queuerequest.request, umcp.Request):
				CORE.process('UMCP_Dispatcher: check_queue: got invalid UMCP request: %r' % (queuerequest.request,))
			if not isinstance(queuerequest.response_queue, Queue.Queue):
				CORE.process('UMCP_Dispatcher: check_queue: got invalid response_queue: %r' % (queuerequest.response_queue,))

			# stop here if sessionid is missing or unknown and request is no AUTH request
			try:
				client = cls.sessions[queuerequest.sessionid]
				if queuerequest.ip != client.ip and not (queuerequest.ip in ('127.0.0.1', '::1') and client.ip in ('127.0.0.1', '::1')):
					CORE.process('The sessionid (ip=%s) is not valid for this IP address (%s)' % (client.ip, queuerequest.ip))
					raise ValueError()
			except (KeyError, ValueError):
				if queuerequest.request.command != 'AUTH':
					CORE.process('UMCP_Dispatcher: check_queue: invalid session: sessionid=%r' % (queuerequest.sessionid))
					response = umcp.Response(queuerequest.request)
					response.status = httplib.UNAUTHORIZED  # set status to unauthorized
					queuerequest.response_queue.put(response)
					continue  # with next request

			if queuerequest.request.command == 'AUTH':
				# AUTH request
				CORE.info('UMCP_Dispatcher: check_queue: AUTH request')
				if not queuerequest.sessionid:
					CORE.info('UMCP_Dispatcher: check_queue: creating new sessionid')
					# FIXME: create nice session id via uuid or something else
					queuerequest.sessionid = str(uuid.uuid4())
				# create new session / umcp client
				# add a small offset to the timeout in order to avoid a mismatch between frontend and backend
				try:
					client = SessionClient(timeout=_session_timeout + 5, ip=queuerequest.ip, cookie=queuerequest.cookie)
				except Exception as e:
					CORE.process('Failed to create UMC connection: %s' % str(e))
					response = umcp.Response(queuerequest.request)
					response.status = httplib.UNAUTHORIZED  # set status to unauthorized
					queuerequest.response_queue.put(response)
					continue

				cls.sessions[queuerequest.sessionid] = client
				callback = notifier.Callback(cls.cleanup_session, queuerequest.sessionid)
				client.client.signal_connect('closed', callback)

				# save new sessionid in UMCP request so it can be returned after authentication
				queuerequest.request.body['sessionid'] = queuerequest.sessionid
				client.authenticate_user(queuerequest.request, queuerequest.response_queue)
			else:
				# COMMAND / GET / SET request
				CORE.info('UMCP_Dispatcher: check_queue: normal request')
				try:
					client.send_request(queuerequest.request, queuerequest.response_queue)
				except umcp.client.NotAuthenticatedError:
					response = umcp.Response(queuerequest.request)
					response.status = httplib.UNAUTHORIZED  # set status to unauthorized
					queuerequest.response_queue.put(response)
					# remove response_queue from internal mapping
					client.cleanup_request(queuerequest.request)

	@classmethod
	def cleanup_session(cls, sessionid):
		"""Removes a session when the connection to the UMC server has died or the session is expired"""
		CORE.info('Open sessions: %s' % (','.join(cls.sessions.keys())))
		try:
			del cls.sessions[sessionid]
			CORE.info('Cleaning up session %s' % sessionid)
		except KeyError as e:
			CORE.info('Session %s not found' % sessionid)
		# remove onetime session keys
		cls.logintokens.remove_session(sessionid)


class UploadManager(dict):

	def add(self, request_id, store):
		tmpfile = tempfile.NamedTemporaryFile(prefix=request_id, dir=umcp.TEMPUPLOADDIR, delete=False)
		if hasattr(store, 'file') and store.file is None:
			tmpfile.write(store.value)
		else:
			tmpfile.write(store.file.read())
		tmpfile.close()
		if request_id in self:
			self[request_id].append(tmpfile.name)
		else:
			self[request_id] = [tmpfile.name]

		return tmpfile.name

	def cleanup(self, request_id):
		if request_id in self:
			filenames = self[request_id]
			for filename in filenames:
				if os.path.isfile(filename):
					os.unlink(filename)
			del self[request_id]
			return True

		return False

_upload_manager = UploadManager()


class QueueRequest(object):

	"""Element for the request queue containing the assoziated session
	ID, the request object, a response queue and the request ip address."""

	def __init__(self, sessionid, request, response_queue, ip, cookie=False):
		self.sessionid = sessionid
		self.request = request
		self.response_queue = response_queue
		self.ip = ip
		self.cookie = cookie


def default_error_page(status, message, traceback, version, result=None):
	""" The default error page for UMCP responses """
	cherrypy.response.headers['Content-type'] = umcp.MIMETYPE_JSON
	response = {
		'status': status,
		'message': cherrypy._cperror._escape(str(message), True)  # html escaped
	}
	if result:
		response['result'] = result
	return simplejson.dumps(response)


class UMC_HTTPError(cherrypy.HTTPError):

	""" HTTPError which sets a error result """

	def __init__(self, status=500, message=None, body=None):
		cherrypy.HTTPError.__init__(self, status, message)
		self.body = body

	def set_response(self):
		cherrypy._cperror.clean_headers(self.status)

		cherrypy.response.status = self.status
		content = default_error_page(self.status, self._message, None, None, self.body)
		cherrypy.response.body = content

		cherrypy.response.headers['Content-Length'] = len(content)

		cherrypy._cperror._be_ie_unfriendly(self.status)


class CPgeneric(object):

	# NOTE: only use CORE.process, _not_ CORE.error; since CORE.error writes as
	#	   well to /var/log/syslog, this seems to cause problems with cherrypy.
	# (Bug #22634)
	_logOptions = {
		'error': CORE.process,
		'warn': CORE.warn,
		'info': CORE.info,
	}

	@property
	def name(self):
		"""returns class name"""
		return self.__class__.__name__

	def _log(self, loglevel, _msg):
		remote = cherrypy.request.remote
		msg = '%s (%s:%s) %s' % (self.name, get_ip_address(), remote.port, _msg)
		self._logOptions.get(loglevel, CORE.info)(msg)

	def get_request(self, path, args):
		return umcp.Request(['generic'], opts={})

	@logExceptions
	def get_session_id(self):
		"""get the current session ID, either set via headers or cookie."""
		# check for a valid session key
		req = cherrypy.request
		if req.cookie.get('UMCSessionId'):
			sessionid = req.cookie['UMCSessionId'].value
		else:
			# a sessionid is required to use this command
			self._log('info', 'no sessionid found')
			raise cherrypy.HTTPError(httplib.UNAUTHORIZED)

		self._log('info', 'found sessionid')
		self._log(99, 'sessionid="%s"' % (sessionid))
		return sessionid

	def set_cookies(self, sessionid=None, username=None):
		# set the cookie once during successful authentication
		# force expiration of cookie in 5 years from now on...
		# IE does not support max-age
		expiration = datetime.datetime.now()
		expiration = expiration.replace(year=expiration.year + 5)
		expirationStr = expiration.strftime("%a, %d-%b-%Y %H:%M:%S GMT")
		if sessionid:
			cherrypy.response.cookie['UMCSessionId'] = sessionid
			cherrypy.response.cookie['UMCSessionId']['expires'] = expirationStr
			cherrypy.response.cookie['UMCSessionId']['version'] = 1
			cherrypy.response.cookie['UMCSessionId']['path'] = '/'
		if username:
			cherrypy.response.cookie['UMCUsername'] = username
			cherrypy.response.cookie['UMCUsername']['expires'] = expirationStr
			cherrypy.response.cookie['UMCUsername']['version'] = 1
			cherrypy.response.cookie['UMCUsername']['path'] = '/'

	@logExceptions
	def load_json(self, body):
		try:
			json = simplejson.loads(body)
			if not isinstance(json, dict):
				raise cherrypy.HTTPError(httplib.BAD_REQUEST, 'JSON document have to be dict')
		except ValueError:
			self._log('error', 'cannot parse JSON body')
			raise cherrypy.HTTPError(httplib.BAD_REQUEST, 'Invalid JSON document')
		return json

	@cherrypy.expose
	@logExceptions
	def default(self, *path, **kwargs):
		self._log('info', 'got new request')

		# get the session id from the request
		sessionid = self.get_session_id()

		if cherrypy.request.headers.get('Content-Type', '').startswith('application/json'):  # normal (json) request
			# get body and parse json
			body = '{}'
			if cherrypy.request.method in cherrypy.request.methods_with_bodies:
				if not cherrypy.request.headers.get(u"Content-Length"):
					self._log('warn', 'missing Content-Length header')
					raise cherrypy.HTTPError(httplib.LENGTH_REQUIRED, 'Missing Content-Length header')
				body = cherrypy.request.body.read()

			args = self.load_json(body)
		else:
			# request is not json
			args = {'options': kwargs}
			if 'flavor' in kwargs:
				args['flavor'] = kwargs['flavor']

		return self.get_response(sessionid, path, args)

	@logExceptions
	def get_response(self, sessionid, path, args):
		# create new UMCP request
		req = self.get_request('/'.join(path), args)

		# create new response queue
		response_queue = Queue.Queue()

		# send request to UMC server
		request = QueueRequest(sessionid, req, response_queue, get_ip_address())
		UMCP_Dispatcher._queue_send.put(request)

		self._log('info', 'pushed request(0x%x) to queue(0x%x) - waiting for response (sessionid="%s")' % (id(req), id(response_queue), sessionid))
		response = response_queue.get()
		self._log('info', 'got response(0x%x) from queue(0x%x): status=%s (sessionid="%s")' % (id(response), id(response_queue), response.status, sessionid))

		status = response.status
		body = response.body
		if status is None:
			# if status is not set, treat as SUCCESS. See Bug #29957
			# UMCP responses which are not json do not have a status
			status = 200
		if 200 <= status < 300:
			if response.mimetype == umcp.MIMETYPE_JSON:
				# convert data to JSON
				body = simplejson.dumps(response.body)

			# return response
			cherrypy.response.headers['Content-Type'] = response.mimetype
			cherrypy.response.status = status
			return body

		# TODO: 3xx handling

		# something bad happened
		self._log('error', 'response status code: %s' % response.status)
		self._log('error', 'response message: %s' % response.message)
		self._log('error', 'response result: %s' % response.result)
		raise UMC_HTTPError(response.status, response.message, response.result)


class CPSingleSignOn(CPgeneric):

	@cherrypy.expose
	@logExceptions
	def default(self, *path, **kwargs):
		self._log('info', 'got SSO request %r %r' % (path, kwargs))
		login_token = UMCP_Dispatcher.logintokens.get(kwargs.get('loginToken'))
		if login_token and not login_token.is_valid():
			login_token = None

		self._log('info', 'found login_token=%r' % (login_token,))
		if login_token:
			# update IP address of sesssion ==> address changes during single sign on
			UMCP_Dispatcher.sessions[login_token.sessionid].ip = get_ip_address()

			self.set_cookies(sessionid=login_token.sessionid, username=login_token.username)

			# invalidate login token after first use
			UMCP_Dispatcher.logintokens.remove_session(login_token.sessionid)
		else:
			self._log('error', 'Unknown SSO token: %r' % kwargs)

		# Redirect in any case to UMC start page
		targeturl = '/univention-management-console/'

		# copy query string
		query_args = {}
		query_args.update(kwargs)
		# remove some arguments from query string
		for remove_arg in ('loginToken', 'username', 'password'):
			if remove_arg in query_args:
				query_args.pop(remove_arg)
		if query_args:
			targeturl += '?%s' % urllib.urlencode(query_args)

		scheme = 'https' if cherrypy.request.headers.get('X-Forwarded-Proto') == 'on' else 'http'
		targeturl = '%s://%s%s' % (scheme, cherrypy.request.base.split('://')[1], targeturl)

		self._log('info', 'redirecting to %s' % targeturl)
		raise cherrypy.HTTPRedirect(targeturl)


class CPGet(CPgeneric):

	@logExceptions
	def get_request(self, path, args):
		if not path:
			self._log('error', 'get_request: path is empty')
			raise cherrypy.HTTPError(httplib.NOT_FOUND)

		return umcp.Request('GET', arguments=[path], options=args.get('options', {}))


class IPAddresses(object):

	@cherrypy.expose
	@logExceptions
	def default(self, *a, **kw):
		try:
			addresses = self.addresses
		except ValueError:
			# hacking attempt
			addresses = [cherrypy.request.remote.ip]
		return simplejson.dumps(addresses)

	@property
	def addresses(self):
		addresses = cherrypy.request.headers.get('X-FORWARDED-FOR', cherrypy.request.remote.ip).split(',') + [cherrypy.request.remote.ip]
		addresses = set(map(IPAddress, map(str.strip, addresses)))
		addresses.discard(IPAddress('::1'))
		addresses.discard(IPAddress('127.0.0.1'))
		return tuple(address.exploded for address in addresses)


class CPSet(CPgeneric):

	@logExceptions
	def get_request(self, path, args):
		return umcp.Request('SET', options=args.get('options', {}))


class CPUpload(CPgeneric):

	@logExceptions
	def get_request(self, path, args):
		self._log('info', 'Handle upload command')
		global _upload_manager
		req = umcp.Request('UPLOAD', arguments=[path])

		options = []
		body = {}
		for iid, ifield in args.iteritems():
			if isinstance(ifield, cherrypy._cpreqbody.Part):
				# field is a FieldStorage object
				store = ifield
				tmpfile = _upload_manager.add(req.id, store)

				# check if filesize is allowed
				st = os.stat(tmpfile)
				max_size = int(configRegistry.get('umc/server/upload/max', 64)) * 1024
				if st.st_size > max_size:
					self._log('warn', 'file of size %d could not be uploaded' % (st.st_size))
					raise cherrypy.HTTPError(httplib.BAD_REQUEST, 'The size of the uploaded file is too large')

				# check if enough free space is available
				min_size = int(configRegistry.get('umc/server/upload/min_free_space', 51200))  # kilobyte
				s = os.statvfs(tmpfile)
				free_disk_space = s.f_bavail * s.f_frsize / 1024  # kilobyte
				if free_disk_space < min_size:
					self._log('error', 'there is not enough free space to upload files')
					raise cherrypy.HTTPError(httplib.BAD_REQUEST, 'There is not enough free space on disk')

				filename = store.filename
				# some security
				for c in ('<>/'):
					filename = filename.replace(c, '_')

				options.append({'filename': filename, 'name': store.name, 'tmpfile': tmpfile})
			elif isinstance(ifield, basestring):
				# field is a string :)
				body[iid] = ifield
			else:
				# we cannot handle any other type
				CORE.warn('Unknown type of multipart/form entry: %s=%s' % (iid, ifield))

		req.body = body
		req.body['options'] = options
		return req

	@cherrypy.expose
	@logExceptions
	def default(self, *path, **kwargs):
		self._log('info', 'got new request')

		# get the session id from the request
		sessionid = self.get_session_id()

		if not cherrypy.request.headers.get('Content-Type', '').startswith('multipart/form-data'):
			raise cherrypy.HTTPError(httplib.BAD_REQUEST, 'Content type and URL do not match')

		response = self.get_response(sessionid, path, kwargs)

		# check if the request is a iframe upload
		if 'iframe' in kwargs and (kwargs['iframe'] not in ('false', False, 0, '0')):
			# this is a workaround to make iframe uploads work, they need the textarea field
			cherrypy.response.headers['Content-Type'] = umcp.MIMETYPE_HTML
			return '<html><body><textarea>%s</textarea></body></html>' % (response)

		return response


class CPCommand(CPgeneric):

	@logExceptions
	def get_request(self, path, args):
		if not path:
			self._log('error', 'get_request: path is empty')
			raise cherrypy.HTTPError(httplib.NOT_FOUND)

		req = umcp.Command([path], options=args.get('options', {}))
		if 'flavor' in args:
			req.flavor = args['flavor']

		return req


class CPAuth(CPgeneric):

	@cherrypy.expose
	@logExceptions
	def default(self, **kw):
		remote = cherrypy.request.remote
		CORE.info('CPRoot/auth: got new auth request (%s:%s <=> %s)' % (get_ip_address(), remote.port, remote.name))

		content_length = cherrypy.request.headers.get("Content-Length")
		if not content_length and content_length != 0:
			CORE.process('CPRoot/auth: missing Content-Length header')
			raise cherrypy.HTTPError(httplib.LENGTH_REQUIRED)

		# get body and parse json
		body = ''
		if cherrypy.request.method in cherrypy.request.methods_with_bodies:
			max_length = 512
			if content_length <= max_length:
				raise cherrypy.HTTPError(httplib.REQUEST_ENTITY_TOO_LARGE, 'Request data is too large, allowed length is %d' % max_length)
			body = cherrypy.request.body.read()

		json = self.load_json(body)

		CORE.info('CPRoot/command: request: command=%s' % cherrypy.request.path_info)

		# create new UMCP request
		req = umcp.Request('AUTH')
		req.body['username'] = json['options'].get('username', '')
		req.body['password'] = json['options'].get('password', '')
		req.body['new_password'] = json['options'].get('new_password')
		req.body['locale'] = (cherrypy.request.headers.values('Accept-Language') or ['C'])[0].replace('-', '_')

		# create new response queue
		response_queue = Queue.Queue()

		# send request to UMC server
		cookie = not bool(json['options'].get('version', ''))
		request = QueueRequest(None, req, response_queue, get_ip_address(), cookie=cookie)
		UMCP_Dispatcher._queue_send.put(request)

		CORE.info('CPRoot/auth: pushed request to queue - waiting for response')
		response = response_queue.get()
		CORE.info('CPRoot/auth: got response with status %s' % response.status)

		if response.status == umcp.SUCCESS and response.body.get('sessionid'):
			CORE.info('CPRoot/auth: creating session with sessionid=%s' % response.body.get('sessionid'))

			# add new one time session
			UMCP_Dispatcher.logintokens.add_session(response.body.get('sessionid'), json['options'].get('username', ''))

			self.set_cookies(sessionid=response.body.get('sessionid'))

			return ""

		CORE.process('CPRoot/auth: username: %s, status code: %s' % (json.get('username'), response.status))
		raise cherrypy.HTTPError(response.status, response.message)


class CPRoot(object):

	@cherrypy.expose
	@logExceptions
	def index(self, **kw):
		"""
		http://localhost:<ucr:umc/http/port>/
		"""
		raise cherrypy.HTTPError(httplib.NOT_FOUND)


def get_ip_address():
	"""get the IP address of client by last entry (from apache) in X-FORWARDED-FOR header"""
	return cherrypy.request.headers.get('X-FORWARDED-FOR', cherrypy.request.remote.ip).rsplit(', ', 1).pop()


@logExceptions
def run_cherrypy():
	# TODO FIXME Folgenden Configeintrag einbauen, wenn loglevel in (0,1,2)
	# 'server.environment': 'production',
	cherrypy.config.update({
		'server.socket_port': int(configRegistry.get('umc/http/port', 8090)),
		'server.socket_host': configRegistry.get('umc/http/interface', '127.0.0.1'),
		'server.request_queue_size': 100,
		'response.timeout': _session_timeout,
		'engine.autoreload_on': False,
		'tools.response_headers.on': True,
		'tools.response_headers.headers': [
			('Content-Type', umcp.MIMETYPE_JSON)
		],
		'error_page.default': default_error_page
	})
	cherrypy.tools.proxy(base=None, local='X-Forwarded-Host', remote='X-Forwarded-For', scheme='X-Forwarded-Proto')

	root = CPRoot()
	root.command = CPCommand()
	root.auth = CPAuth()
	root.get = CPGet()
	root.set = CPSet()
	root.upload = CPUpload()
	root.sso = CPSingleSignOn()
	root.get.ipaddress = IPAddresses()

	cherrypy.quickstart(root=root)


class UMC_HTTP_Daemon(DaemonRunner):

	def __init__(self):
		self.parser = OptionParser()
		self.parser.add_option('-n', '--no-daemon', action='store_false',
			dest='daemon_mode', default=True,
			help='if set the process will not fork into the background'
		)
		try:
			default_debug = int(configRegistry.get('umc/server/debug/level', '1'))
		except:
			default_debug = 1
		self.parser.add_option('-d', '--debug', action='store', type='int', dest='debug', default=default_debug,
			help='if given than debugging is activated and set to the specified level [default: %default]'
		)
		self.parser.add_option('-L', '--log-file', action='store', dest='logfile', default='management-console-web-server',
			help='specifies an alternative log file [default: %default]'
		)
		(self.options, self.arguments) = self.parser.parse_args()

		# cleanup environment
		os.environ.clear()  # supported since python 2.6
		os.environ['PATH'] = '/bin:/sbin:/usr/bin:/usr/sbin'

		# init logging
		if not self.options.daemon_mode:
			debug_fd = log_init('/dev/stderr', self.options.debug)
		else:
			debug_fd = log_init(self.options.logfile, self.options.debug)

		# default action: start
		if not self.arguments:
			sys.argv[1:] = ['start']
		elif self.arguments:
			sys.argv[1:] = self.arguments

		# for daemon runner
		if self.options.daemon_mode:
			self.stdin_path = os.path.devnull
			self.stdout_path = os.path.devnull
			self.stderr_path = os.path.devnull
		else:
			self.stdin_path = '/dev/stdin'
			self.stdout_path = '/dev/stdout'
			self.stderr_path = '/dev/stderr'
		self.pidfile_path = '/var/run/umc-web-server.pid'
		self.pidfile_timeout = 10

		# init daemon runner
		DaemonRunner.__init__(self, self)
		self.daemon_context.detach_process = self.options.daemon_mode
		self.daemon_context.files_preserve = [debug_fd]

	def _restart(self):
		"""Handler for the restart action. """
		if self.pidfile.is_locked():
			CORE.process('Stopping UMC web server ...')
			self._stop()

		CORE.process('Starting UMC web server ...')
		self._start()

	def _crestart(self):
		"""Handler for the crestart action. """
		if not self.pidfile.is_locked():
			CORE.process('The UMC web server will not be restarted as it is not running currently')
			return

		CORE.process('Stopping UMC web server ...')
		self._stop()
		CORE.process('Starting UMC web server ...')
		self._start()

	DaemonRunner.action_funcs['restart'] = _restart
	DaemonRunner.action_funcs['crestart'] = _crestart

	def _terminate_daemon_process(self):
		""" Terminate the daemon process specified in the current PID file.
				"""
		pid = self.pidfile.read_pid()
		try:
			os.kill(pid, signal.SIGTERM)
		except OSError as exc:
			raise DaemonRunnerStopFailureError(	"Failed to terminate %(pid)d: %(exc)s" % vars())

		if self.pidfile.is_locked():
			CORE.process('The UMC web server is still running. Will wait for 5 seconds')
			count = 10
			while count:
				time.sleep(0.5)
				if not self.pidfile.is_locked():
					break
				count -= 1
			if self.pidfile.is_locked():
				CORE.process('The UMC web server is still running. Kill it!')
				os.kill(pid, signal.SIGKILL)
				self.pidfile.break_lock()

	def _usage_exit(self, argv):
		self.parser.error('invalid action')
		sys.exit(1)

	def run(self):
		# start webserver as separate thread
		_thread_http = threading.Thread(target=run_cherrypy)
		_thread_http.deamon = True
		_thread_http.start()

		try:
			# start notifier loop
			notifier.init(notifier.GENERIC)
			notifier.dispatcher_add(UMCP_Dispatcher.check_queue)
			notifier.loop()
		except (SystemExit, KeyboardInterrupt) as e:
			# stop the web server
			CORE.info('stopping cherrypy: %s' % str(e))
			cherrypy.engine.exit()
			CORE.info('cherrypy stopped')

if __name__ == '__main__':
	http_daemon = UMC_HTTP_Daemon()
	try:
		http_daemon.do_action()
	except DaemonRunnerStopFailureError as e:
		CORE.process('Failed to shutdown server gracefully (may be its already dead): %s' % str(e))
	except DaemonRunnerStartFailureError as e:
		CORE.process('Failed to start server: %s' % str(e))
