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

import time
import re

from .plugin import ModList
from cplCommand import LocalClient, SSHClient


msg_str = {
	'id0101' : 'no data found, nothing to do',
	'id0102' : 'seting \'{0}\' to: \'{1}\'',
	'id0103' : 'nothing to do for: \'{0}\'',
	'id0104' : 'workaround for gosa bug',
	'id0105' : 'can not build fqdn since domain name is not available',
	'id0106' : 'MessageSize should be smaller than MailboxSize and larger than HeaderSize (0 is equivalent to no limit)',
	'id0107' : 'ldap value \'{0}\' isn\'t implemented jet',
	'id0108' : 'fetched postfix server: \'{0}\'',
	'id0109' : 'using local client',
	'id0110' : 'using remote client',
	'id0111' : 'unable to extract postfix server from ldap',
	'id0112' : 'unable to find a propper postfix host, or error connecting',
	'id0113' : 'fetching configuration for: \'{0}\'',
	'id0114' : 'postconf returned following error:\n\'{0}\'',
	'id0115' : 'postconf returned:\n\'{0}\'',
	'id0116' : 'looking up: \'{0}\'',
	'id0117' : 'using postfix host from config: \'{0}\'',
	'id0118' : 'no postfix host in config - looking in ldap',
	'id0119' : 'list of networks found: \'{0}\'',
	'id0120' : 'removed localnet from list',
	'id0121' : 'nothing to do for localnet',
	'id0122' : 'adding localnet to list',
	'id0123' : 'startting up: \'{0}\'',
	'id0124' : 'successfully updated postfix config',
	'id0125' : 'no daemon action to perform',
	'id0126' : 'checking daemon status',
	'id0127' : 'trying to {0} daemon',
	'id0128' : 'daemon status: {0}',
	'id0129' : 'wrong daemon status: daemon is {0} and was expected to be {1}',
	'id0130' : 'performing: \'daemon {0}\'',
	'id0131' : 'an error occured, performing daemon {0}',
	'id0132' : 'daemon successfully {0}',
	'id0133' : 'retrying to {0} daemon (count={1})',
	'id0134' : 'removing action flag \'{0}\' from ldap',
	'id0135' : 'leaving action flag in ldap unchanged',
	'id0136' : 'option \'{0}\' is sync in ldap and postconf - removing from changelist',
	'id0137' : 'option \'{0}\' changed in ldap - keeping in changelist',
	'id0138' : 'unable to {0} daemon',
	}

cmd_str = {
	'id0101' : 'postconf {0}',
	'id0102' : 'postconf -e {0}',
	'id0103' : 'postfix status',
	'id0104' : 'postfix {0}',
	}

re_str = {
	'id0101' : r'^(?:(?P<name>[\w.-]+)@)?(?P<dom>[a-zA-Z0-9.-]+)$',
	}



class SrvPostfix(object, metaclass=ModList):

	def __init__(self, postcmd):

		self.class_name = self.__class__.__name__ 
		mod_name = self.class_name+'.__init__'

		# TODO: we need this as long as this class is not inherited from plugin
		# get config-, logger-, ldap- and exit-class
		self.config = postcmd.getConfig()
		self.logger = postcmd.getLogger()
		self.exit = postcmd.getExit()
		self.gosa_ldap = postcmd.getGosaLdap()

		self.cmd_client = None

		# get data
		self.dn = postcmd.data
		self.cn = self.dn.split(',')[0].split('=')[1]

		# get module and host configuration
		self.__getConfig()

		self.postfix_opts = [
			'mynetworks',
			'relayhost',
			'mydomain',
			'myhostname',
			'mailbox_size_limit',
			'message_size_limit',
			'header_size_limit',
			'virtual_alias_domains',
			]


	def __getConfig(self):

		mod_name = self.class_name+'.__getConfig'

		mod_cfg_def = {
			'hkeysave' : False,
			'hkeypol' : 'reject',
			}

		mod_cfg_ver = {
			'hkeysave' : ('bool', ),
			'hkeypol' : ('str', ),
			}

		host_cfg_def = {
			'host' : None,
			'username' : None,
			'password': None,
			'keyfile' : None,
			'port' : None,
			'skip_va_dom' : True,
			}

		host_cfg_ver = {
			'host' : ('str', ),
			'username' : ('str', ),
			'password': ('str', ),
			'keyfile' : ('str', ),
			'port' : ('int', ),
			'skip_va_dom' : ('bool', ),
			}

		# get module configuration
		self.logger.debug(msg_str['id0113'].format(self.class_name))
		self.config.updateDefaults(self.class_name, mod_cfg_def)
		self.config.updateVerify(mod_cfg_ver)
		self.module_cfg = self.config.getSection(self.class_name)

		# postfix host spezific configuration
		self.logger.debug(msg_str['id0113'].format(self.class_name+'/'+self.cn))
		self.config.updateDefaults(self.class_name+'/'+self.cn, host_cfg_def)
		self.config.updateVerify(host_cfg_ver)
		self.host_cfg = self.config.getSection(self.class_name+'/'+self.cn)


	def getLdapData(self):

		mod_name = self.class_name+'.getLdapData'

		ldap_data, msg = self.gosa_ldap.getAttributeList(self.dn)

		if ldap_data == None:
			self.exit(1, msg, mod_name)
		else:
			self.logger.debug(msg)

		return ldap_data


	# mv to postfix_s_class.py
	# cmd_str id0101
	# msg_str id0116 id0114 id0115
	def getPostConf(self, opt_lst):

		mod_name = self.class_name+'.getPostConf'

		# call postconf on client
		self.logger.debug(msg_str['id0116'].format(opt_lst))
		value, std_out, std_err = self.cmd_client.runCmd(cmd_str['id0101'].format(' '.join(opt_lst)))

		# extract postconf option values from std_out
		pf_data = {}
		for line in std_out:
			opt, sep, val = [p.strip() for p in line.partition('=')]
			pf_data[opt] = val

		# make one sting from std_err, if existant
		std_err_str = '\n'.join(std_err)

		# evaluate error
		if std_err_str != '' or value != 0:
			#self.logger.warn(msg_str['id0114'].format(std_err_str))
			self.exit(1, msg_str['id0114'].format(std_err_str))
		else:
			self.logger.debug(msg_str['id0115'].format(pf_data))

		return pf_data


	def postfixToLdap(self, pf_data, skip_va_dom=True):

		mod_name = self.class_name+'.postfixToLdap'

		if len(pf_data) == 0:

			self.logger.debug(msg_str['id0101'])
			return

		postfix_cfg = {}

		# extracting postfixMyNetworks
		pf_var = 'mynetworks'
		g_var = 'postfixMyNetworks'

		try:
			# split string output in a list
			mynetworks_lst = pf_data[pf_var].split(',')
			self.logger.debug(msg_str['id0119'].format(mynetworks_lst))
			# remove (if existant) localnet
			mynetworks_lst.remove('127.0.0.0/8')
			self.logger.debug(msg_str['id0120'])

		except ValueError:
			# nothing to do for localnet
			self.logger.debug(msg_str['id0121'])

		except KeyError:
			# got no value
			self.logger.debug(msg_str['id0103'].format(g_var))

		else:
			# join net list back to a string
			postfix_cfg[g_var] = [','.join(mynetworks_lst)]
			self.logger.debug(msg_str['id0102'].format(g_var, postfix_cfg[g_var]))

		# extracting relayhost, mydomain
		pf_tbl = {
			'relayhost' : 'postfixRelayhost',
			'mydomain' : 'postfixMyDomain',
			}

		for pf_var in pf_tbl.keys():
			g_var = pf_tbl[pf_var]

			try:
				postfix_cfg[g_var] = [pf_data[pf_var]]

				self.logger.debug(msg_str['id0102'].format(g_var, postfix_cfg[g_var]))

			except KeyError:
				# got no value
				self.logger.debug(msg_str['id0103'].format(g_var))

		# extracting myhostname
		pf_var = 'myhostname'
		g_var = 'postfixMyHostname'

		try:
			# only store shorthand hostname
			postfix_cfg[g_var] = [pf_data[pf_var].partition('.')[0]]

			self.logger.debug(msg_str['id0102'].format(g_var, postfix_cfg[g_var]))

		except KeyError:
			# got no value
			self.logger.debug(msg_str['id0103'].format(g_var))

		# extracting size limits
		pf_tbl = {
			'mailbox_size_limit' : 'postfixMailboxSizeLimit',
			'message_size_limit' : 'postfixMessageSizeLimit',
			'header_size_limit' : 'postfixHeaderSizeLimit',
			}

		for pf_var in pf_tbl.keys():
			g_var = pf_tbl[pf_var]

			try:
				# transform between bytes and kbytes
				postfix_cfg[g_var] = [str(int(pf_data[pf_var]) / 1024)]

				self.logger.debug(msg_str['id0102'].format(g_var, postfix_cfg[g_var]))

			except KeyError:
				self.logger.debug(msg_str['id0103'].format(g_var))

		# little sanity check
		if	(int(postfix_cfg['postfixMailboxSizeLimit'][0]) != 0 and \
			int(postfix_cfg['postfixMailboxSizeLimit'][0]) < int(postfix_cfg['postfixMessageSizeLimit'][0])) or \
			(int(postfix_cfg['postfixMessageSizeLimit'][0]) != 0 and \
			int(postfix_cfg['postfixMessageSizeLimit'][0]) < int(postfix_cfg['postfixHeaderSizeLimit'][0])):

			self.logger.warn(msg_str['id0106'])

		# extracting  virtual_alias_domains
		if not skip_va_dom:
			pf_var = 'virtual_alias_domains'
			g_var = 'postfixMyDestinations'

			try:
				# split domain string in a list
				postfix_cfg[g_var] = pf_data[pf_var].split(',')

				self.logger.debug(msg_str['id0102'].format(g_var, postfix_cfg[g_var]))

			except KeyError:
				self.logger.debug(msg_str['id0103'].format(g_var))

		# extracting postfixSenderRestrictions, postfixRecipientRestrictions, postfixTransportTable
		# not implemented jet

		return postfix_cfg


	# mv to postfix_s_class.py
	# cmd_str id0102
	# msg_str id0101 id0114 id0124
	def writeCfgToPostfix(self, postfix_cfg):

		mod_name = self.class_name+'.writeCfgToPostfix'

		if len(postfix_cfg) == 0:

			self.logger.debug(msg_str['id0101'])
			return

		# call postconf with a string: 'option=value option=value ...'
		value, std_out, std_err = self.cmd_client.runCmd(cmd_str['id0102'].format( \
			' '.join([opt+'='+str(postfix_cfg[opt]) for opt in postfix_cfg])))

		# make one sting from std_err, if existant
		std_err_str = '\n'.join(std_err)

		# evaluate error
		if std_err_str != '' or value != 0:
			self.exit(1, msg_str['id0114'].format(std_err_str))
		else:
			self.logger.debug(msg_str['id0124'])



	def ldapToPostfix(self, ldap_data, skip_va_dom=True):

		mod_name = self.class_name+'.ldapToPostfix'

		if len(ldap_data) == 0:

			self.logger.debug(msg_str['id0101'])
			return

		postfix_cfg = {}

		# FIXME: normally not comma separated
		# extracting mynetworks
		try:
			pf_var = 'mynetworks'
			g_var = 'postfixMyNetworks'
			postfix_cfg[pf_var] = ldap_data[g_var][0]

			if '127.0.0.0/8' not in postfix_cfg[pf_var]:
				self.logger.debug(msg_str['id0122'])
				postfix_cfg[pf_var] = '127.0.0.0/8,'+postfix_cfg[pf_var]

			self.logger.debug(msg_str['id0102'].format(pf_var, postfix_cfg[pf_var]))

		except KeyError:
			self.logger.debug(msg_str['id0103'].format(pf_var))

		# extracting myhostname, relayhost
		# for myhostname we set only the name here and add the domain part later
		pf_tbl = {
			'postfixMyHostname' : 'myhostname',
			'postfixRelayhost' : 'relayhost',
			}

		for g_var in pf_tbl.keys():
			try:
				pf_var = pf_tbl[g_var]
				postfix_cfg[pf_var] = ldap_data[g_var][0]

				self.logger.debug(msg_str['id0102'].format(pf_var, postfix_cfg[pf_var]))

			except KeyError:
				self.logger.debug(msg_str['id0103'].format(pf_var))

		# extracting mydomain
		try:
			pf_var = 'mydomain'
			pf_var2 = 'myhostname'
			g_var = 'postfixMyDomain'
			postfix_cfg[pf_var] = ldap_data[g_var][0]

			# workaround for a gosa bug
			if postfix_cfg[pf_var] == postfix_cfg[pf_var2]:
				self.logger.debug(msg_str['id0104'])
				del(postfix_cfg[pf_var])
				raise KeyError

			self.logger.debug(msg_str['id0102'].format(pf_var, postfix_cfg[pf_var]))

			# adding domain name to hosthame
			postfix_cfg[pf_var2] += '.'+postfix_cfg[pf_var]
			self.logger.debug(msg_str['id0102'].format(pf_var2, postfix_cfg[pf_var2]))

		except KeyError:
			self.logger.debug(msg_str['id0103'].format(pf_var))

			if postfix_cfg.has_key(pf_var2):
				self.logger.info(msg_str['id0105'])

		# extracting size limits
		pf_tbl = {
			'postfixMailboxSizeLimit' : 'mailbox_size_limit',
			'postfixMessageSizeLimit' : 'message_size_limit',
			'postfixHeaderSizeLimit' : 'header_size_limit',
			}

		for g_var in pf_tbl.keys():
			try:
				pf_var = pf_tbl[g_var]
				# transform between kbytes and bytes
				postfix_cfg[pf_var] = str(int(ldap_data[g_var][0]) * 1024)

				self.logger.debug(msg_str['id0102'].format(pf_var, postfix_cfg[pf_var]))

			except KeyError:
				self.logger.debug(msg_str['id0103'].format(pf_var))

		# little sanity check
		if	(int(postfix_cfg['mailbox_size_limit']) != 0 and \
			int(postfix_cfg['mailbox_size_limit']) < int(postfix_cfg['message_size_limit'])) or \
			(int(postfix_cfg['message_size_limit']) != 0 and \
			int(postfix_cfg['message_size_limit']) < int(postfix_cfg['header_size_limit'])):

			self.logger.warn(msg_str['id0106'])

		# extracting  virtual_alias_domains
		if not skip_va_dom:
			try:
				pf_var = 'virtual_alias_domains'
				g_var = 'postfixMyDestinations'

				# join domain list into a string
				postfix_cfg[pf_var] = ','.join(ldap_data[g_var])

				self.logger.debug(msg_str['id0102'].format(pf_var, postfix_cfg[pf_var]))

			except KeyError:
				del(postfix_cfg[pf_var])
				self.logger.debug(msg_str['id0103'].format(pf_var))

		# extracting postfixSenderRestrictions, postfixRecipientRestrictions, postfixTransportTable
		# not implemented jet

		"""
		pf_tbl = {
			'postfixSenderRestrictions' : None,
			'postfixRecipientRestrictions' : None,
			# 'postfixTransportTable' : None,
			}

		filter_tab = {
			'OK' : 'none',
			'REJECT' : 'msg',
			'DEFER' : 'msg',
			'DEFER_IF_REJECT' : 'msg',
			'DEFER_IF_PERMIT' : 'msg',
			'BCC' : 'email',
			'DISCARD' : 'msg',
			'DUNNO' : 'none',
			'FILTER' : 'nexthop',
			'HOLD' : 'msg',
			'PREPEND' : 'header',
			'REDIRECT' : 'email',
			'WARN' : 'msg',
			}

		def chkMail(mail_addr):

			re_mail = re.compile(re_str['id0101'])
			try:
				return re_mail.match(mail_addr).groupdict()

			except AttributeError:
				return None


		for g_var in pf_tbl.keys():

			table = {}
			print(ldap_data[g_var])
			# dissect table strings into their values
			for val_str in ldap_data[g_var]:
				print(val_str)
				# seperate line number
				val_lst = val_str.split(': ')
				print(val_lst)
				# seperate 'collumns'
				val_no = int(val_lst[0].strip())
				val_lst = val_lst[1].split(' ')

				if val_lst[0] == '':
					pass
					# remove line

				if val_lst[1] not in filter_tab.keys():
					pass
					# remove line



				table[int(val_lst[0].strip())] = val_lst[1].split(' ')


			print(table)

			print(chkMail('c.s@mail.architektur.tu-darmstadt.de'))



			#try:
			#	self.logger.info(msg_str['id0107'].format(g_var))

			#except KeyError:
			#	pass
		"""

		return postfix_cfg


	def writeCfgToLdap(self, ldap_data, postfix_cfg):

		mod_name = self.class_name+'.writeCfgToLdap'

		if len(postfix_cfg) == 0:

			self.logger.debug(msg_str['id0101'])
			return

		ldap_data_new = ldap_data.copy()
		ldap_data_new.update(postfix_cfg)
		val, msg = self.gosa_ldap.modDN(self.dn, ldap_data, ldap_data_new)
		self.logger.debug(msg)


	def getClient(self, ldap_data):

		mod_name = self.class_name+'.getClient'

		if self.host_cfg['host']:
			self.logger.debug(msg_str['id0117'].format(self.host_cfg['host']))

		else:
			self.logger.debug(msg_str['id0118'])

			try:
				self.host_cfg['host'] = ldap_data['ipHostNumber'][0]
				self.logger.debug(msg_str['id0108'].format(self.host_cfg['host']))

			except KeyError:
				self.logger.error(msg_str['id0111'])

		if self.host_cfg['host']:

			if self.host_cfg['host'].lower() in ('127.0.0.1', 'localhost'):
				# using local client
				cmd_client = LocalClient(self.logger)
				self.logger.debug(msg_str['id0109'])
				value = 0

			else:
				# using ssh client
				cmd_client = SSHClient(self.logger)
				self.logger.debug(msg_str['id0110'])

				# connecting to host
				value = cmd_client.Connect(
						host = self.host_cfg['host'],
						username = self.host_cfg['username'],
						password = self.host_cfg['password'],
						keyfile = self.host_cfg['keyfile'],
						port = self.host_cfg['port'],
						hkeysave = self.module_cfg['hkeysave'],
						hkeypol = self.module_cfg['hkeypol'],
						)
		else:
			value = 254

		if value != 0:
			self.exit(value, msg_str['id0112'])
			#self.logger.debug(msg_str['id0112'].format(value))

		return cmd_client

	# mv to postfix_s_class.py
	# cmd_str id0103 id0104
	# msg_str id0127 id0128 id0129 id0130 id0131 id0132 id0133 id0138
	def daemonAction(self, daemon_cmd):

		mod_name = self.class_name+'.daemonAction'

		action_lst = {
			'start' : (0, 'start'),
			'stop' : (1, 'stop'),
			'reload' : (2, 'reload'),
			'restart' : (3, 'stop', 'start'),
			'status' : (4, 'status'),
			}

		daemon_status = {
			0 : 'started',
			1 : 'stoped',
			2 : 'reloaded',
			}

		# get daemon status
		self.logger.debug(msg_str['id0127'].format(daemon_cmd))
		value, std_out, std_err = self.cmd_client.runCmd(cmd_str['id0103'])
		self.logger.debug(msg_str['id0128'].format(daemon_status[value]))

		if daemon_cmd == 'status':
			return value

		elif daemon_cmd == 'reload' and value == 1:
			self.logger.debug(msg_str['id0129'].format(daemon_status[value], daemon_status[0]))
			return 1

		# perform each action from the right list in action_lst
		for action in action_lst[daemon_cmd][1:]:

			# skip loop if daemon status isn't right
			action_val = action_lst[action][0]
			if value == action_val:
				self.logger.debug(msg_str['id0129'].format(daemon_status[value], daemon_status[action_val^1]))
				continue

			# retry action 10 times in 1 sec. intervall
			for counter in range(0, 10):

				# start, stop ... daemon
				self.logger.debug(msg_str['id0130'].format(action))
				value, std_out, std_err = self.cmd_client.runCmd(cmd_str['id0104'].format(action))

				# exit method in case of errors
				if value != 0:
					self.logger.error(msg_str['id0131'].format(action))
					return 1

				elif daemon_cmd == 'reload':
					self.logger.debug(msg_str['id0132'].format(daemon_status[action_val]))
					return 0

				# test daemon status again
				value, std_out, std_err = self.cmd_client.runCmd(cmd_str['id0103'])
				if value == action_val:
					# break loop, if start / stop has been successfully
					self.logger.debug(msg_str['id0132'].format(daemon_status[action_val]))
					break
				else:
					# sleep a sec. bevore next try
					self.logger.debug(msg_str['id0133'].format(action, counter))
					time.sleep(1)

		if value == action_val:
			return 0
		else:
			self.logger.debug(msg_str['id0138'].format(daemon_cmd))
			return 1
			

	def execScheduledAct(self, ldap_data):

		mod_name = self.class_name+'.daemonAction'

		# exit if no action flag can be found in ldap
		try:
			daemon_cmd = ldap_data['goMailServerStatus'][0]
			self.logger.debug(msg_str['id0126'])

		except KeyError:
			self.logger.debug(msg_str['id0125'])
			return 0

		if self.daemonAction(daemon_cmd.lower()) == 0:

			# remove action flag from ldap, if status is as expected
			self.logger.debug(msg_str['id0134'].format(daemon_cmd.lower()))
			val, msg = self.gosa_ldap.modDNRaw(self.dn, [(1, 'goMailServerStatus', [daemon_cmd]),])
			self.logger.debug(msg)
			return 0
		else:
			# do nothing with action flag
			self.logger.debug(msg_str['id0135'])
			return 1


	def create(self):

		mod_name = self.class_name+'.create'
		self.logger.debug(msg_str['id0123'].format(mod_name))

		# gather all data we need
		self.ldap_data = self.getLdapData()
		self.cmd_client = self.getClient(self.ldap_data)
		self.pf_data = self.getPostConf(self.postfix_opts)

		# transform postfix config from postconf to ldap and write to ldap
		self.postfix_cfg = self.postfixToLdap(self.pf_data, self.host_cfg['skip_va_dom'])
		self.writeCfgToLdap(self.ldap_data, self.postfix_cfg)


	def modify(self):

		mod_name = self.class_name+'.modify'
		self.logger.debug(msg_str['id0123'].format(mod_name))

		# gather all data we need
		self.ldap_data = self.getLdapData()
		self.cmd_client = self.getClient(self.ldap_data)
		self.pf_data = self.getPostConf(self.postfix_opts)

		# transform postfix config from ldap to postconf
		self.postfix_cfg = self.ldapToPostfix(self.ldap_data, self.host_cfg['skip_va_dom'])
		return

		# sort out which optins actually changed in ldap
		for opt in self.postfix_cfg.keys():
			if self.postfix_cfg[opt] == self.pf_data[opt]:
				self.logger.debug(msg_str['id0136'].format(opt))
				del(self.postfix_cfg[opt])
			else:
				self.logger.debug(msg_str['id0137'].format(opt))

		# write new values to postconf
		self.writeCfgToPostfix(self.postfix_cfg)

		# execute possible daemon commands
		self.execScheduledAct(self.ldap_data)


	def remove(self):

		mod_name = self.class_name+'.remove'
		self.logger.debug(msg_str['id0123'].format(mod_name))
		self.logger.debug(msg_str['id0102'].format(mod_name))

