'''
This is the web interface for a fileserver managing encrypted filesystems.
 
It was originally written in bash/perl. Now a complete rewrite is in
progress. So things might be confusing here. Hopefully not for long.
:)
'''

# check python version
import sys
(ver_major, ver_minor, ver_sub, ver_desc, ver_subsub) = sys.version_info
if (ver_major < 2) or ((ver_major == 2) and (ver_minor < 4)):
	sys.stderr.write("You need a python version >= 2.4\nCurrent version is:\n %s\n" % sys.version)
	sys.exit(1)

import cryptobox.core.container as cbxContainer
from cryptobox.core.exceptions import *
import re
import os
import cryptobox.core.tools as cbxTools
import subprocess


VERSION = "0.3~1"

class CryptoBox:
	'''this class rules them all!

	put things like logging, conf and oter stuff in here,
	that might be used by more classes, it will be passed on to them'''


	def __init__(self, config_file=None):
		import cryptobox.core.settings as cbxSettings
		self.log = self.__getStartupLogger()
		self.prefs = cbxSettings.CryptoBoxSettings(config_file)
		self.__runTests()


	def __getStartupLogger(self):	
		import logging
		'''initialises the logging system
		
		use it with: 'self.log.[debug|info|warning|error|critical](logmessage)'
		all classes should get the logging instance during __init__:
			self.log = logging.getLogger("CryptoBox")

		first we output all warnings/errors to stderr
		as soon as we opened the config file successfully, we redirect debug output
		to the configured destination'''
		## basicConfig(...) needs python >= 2.4
		try:
			log_handler = logging.getLogger("CryptoBox")
			logging.basicConfig(
				format='%(asctime)s CryptoBox %(levelname)s: %(message)s',
				stderr=sys.stderr)
			log_handler.setLevel(logging.ERROR)
			log_handler.info("loggingsystem is up'n running")
			## from now on everything can be logged via self.log...
		except:
			raise CBEnvironmentError("couldn't initialise the loggingsystem. I give up.")
		return log_handler


	# do some initial checks
	def __runTests(self):
		self.__runTestUID()
		self.__runTestRootPriv()

	
	def __runTestUID(self):
		if os.geteuid() == 0:
			raise CBEnvironmentError("you may not run the cryptobox as root")


	def __runTestRootPriv(self):
		"""try to run 'super' with 'CryptoBoxRootActions'"""
		try:
			devnull = open(os.devnull, "w")
		except IOError:
			raise CBEnvironmentError("could not open %s for writing!" % os.devnull)
		try:
			prog_super = self.prefs["Programs"]["super"]
		except KeyError:
			raise CBConfigUndefinedError("Programs", "super")
		try:
			prog_rootactions = self.prefs["Programs"]["CryptoBoxRootActions"]
		except KeyError:
			raise CBConfigUndefinedError("Programs", "CryptoBoxRootActions")
		try:
			proc = subprocess.Popen(
				shell = False,
				stdout = devnull,
				stderr = devnull,
				args = [prog_super, prog_rootactions, "check"])
		except OSError:
			raise CBEnvironmentError("failed to execute 'super' (%s)" % self.prefs["Programs"]["super"])
		proc.wait()
		if proc.returncode != 0:
			raise CBEnvironmentError("failed to call CryptoBoxRootActions (%s) via 'super' - maybe you did not add the appropriate line to /etc/super.tab?" % prog_rootactions)


	# this method just demonstrates inheritance effects - may be removed
	def cbx_inheritance_test(self, string="you lucky widow"):
		self.log.info(string)
	

# RFC: why should CryptoBoxProps inherit CryptoBox? [l]
# RFC: shouldn't we move all useful functions of CryptoBoxProps to CryptoBox? [l]
class CryptoBoxProps(CryptoBox):
	'''Get and set the properties of a CryptoBox

	This class contains all available devices that may be accessed.
	All properties of the cryptobox can be accessed by this class.
	'''

	def __init__(self, config_file=None):
		'''read config and fill class variables'''
		CryptoBox.__init__(self, config_file)
		self.reReadContainerList()
	

	def reReadContainerList(self):
		self.log.debug("rereading container list")
		self.containers = []
		for device in cbxTools.getAvailablePartitions():
			if self.isDeviceAllowed(device) and not self.isConfigPartition(device):
				self.containers.append(cbxContainer.CryptoBoxContainer(device, self))
		## sort by container name
		self.containers.sort(cmp = lambda x,y: x.getName() < y.getName() and -1 or 1)
	

	def isConfigPartition(self, device):
		proc = subprocess.Popen(
			shell = False,
			stdout = subprocess.PIPE,
			args = [
				self.prefs["Programs"]["blkid"],
				"-c", os.path.devnull,
				"-o", "value",
				"-s", "LABEL",
				device])
		(output, error) = proc.communicate()
		return output.strip() == self.prefs["Main"]["ConfigVolumeLabel"]


	def isDeviceAllowed(self, devicename):
		"check if a device is white-listed for being used as cryptobox containers"
		import types
		allowed = self.prefs["Main"]["AllowedDevices"]
		if type(allowed) == types.StringType: allowed = [allowed]
		for a_dev in allowed:
			"remove double dots and so on ..."
			real_device = os.path.realpath(devicename)
			if a_dev and re.search('^' + a_dev, real_device):	return True
		return False


	def getLogData(self, lines=None, maxSize=None):
		"""get the most recent log entries of the cryptobox

		the maximum number and size of these entries can be limited by 'lines' and 'maxSize'
		"""
		# return nothing if the currently selected log output is not a file
		try:
			if self.prefs["Log"]["Destination"].upper() != "FILE": return []
			log_file = self.prefs["Log"]["Details"]
		except KeyError:
			self.log.error("could not evaluate one of the following config settings: [Log]->Destination or [Log]->Details")
			return []
		try:
			fd = open(log_file, "r")
			if maxSize:	fd.seek(-maxSize, 2)	# seek relative to the end of the file
			content = fd.readlines()
			fd.close()
		except IOError:
			self.log.warn("failed to read the log file (%s)" % log_file)
			return []
		if lines:	content = content[-lines:]
		content.reverse()
		return content


	def getContainerList(self, filterType=None, filterName=None):
		"retrieve the list of all containers of this cryptobox"
		try:
			result = self.containers[:]
			if filterType != None:
				if filterType in range(len(cbxContainer.Types)):
					return [e for e in self.containers if e.getType() == filterType]
				else:
					self.log.info("invalid filterType (%d)" % filterType)
					result.clear()
			if filterName != None:
				result = [e for e in self.containers if e.getName() == filterName]
			return result
		except AttributeError:
			return []


	def getContainer(self, device):
		"retrieve the container element for this device"
		all = [e for e in self.getContainerList() if e.device == device]
		if all:
			return all[0]
		else:
			return None


	def getAvailableLanguages(self):
		'''reads all files in path LangDir and returns a list of
		basenames from existing hdf files, that should are all available
		languages'''
		languages = [ f.rstrip(".hdf")
			for f in os.listdir(self.prefs["Locations"]["LangDir"])
			if f.endswith(".hdf") ]
		if len(languages) < 1:
			self.log.error("No .hdf files found! The website won't render properly.")
		return languages


	def sendEventNotification(self, event, event_infos):
		"""call all available scripts in the event directory with some event information"""
		event_dir = self.prefs["Locations"]["EventDir"]
		for fname in os.listdir(event_dir):
			real_fname = os.path.join(event_dir, fname)
			if os.path.isfile(real_fname) and os.access(real_fname, os.X_OK):
				cmd_args = [ self.prefs["Programs"]["super"],
					self.prefs["Programs"]["CryptoBoxRootActions"],
					"event", real_fname, event]
				cmd_args.extend(event_infos)
				proc = subprocess.Popen(
					shell = False,
					stdout = subprocess.PIPE,
					stderr = subprocess.PIPE,
					args = cmd_args)
				(stdout, stderr) = proc.communicate()
				if proc.returncode != 0:
					self.log.warn("an event script (%s) failed (exitcode=%d) to handle an event (%s): %s" % (real_fname, proc.returncode, event, stderr.strip()))
				else:
					self.log.info("event handler (%s) finished successfully: %s" % (real_fname, event))


if __name__ == "__main__":
	cb = CryptoBoxProps()

