#!/usr/bin/python

# Copyright (C) 2006-2008 Red Hat, Inc.
#
# This program is free software; you can redistribute
# it and/or modify it under the terms of version 2 of the
# GNU General Public License as published by the
# Free Software Foundation.

import sys, os, pwd
from select import select
from stat import S_ISREG
from time import time
import types
import xml
import xml.dom

sys.path.extend((
	'/usr/lib/luci/zope/lib/python',
	'/usr/lib/luci/zope/lib/python/Products',
	'/usr/lib64/luci/zope/lib/python',
	'/usr/lib64/luci/zope/lib/python/Products',
	'/usr/lib64/luci/zope/lib64/python',
	'/usr/lib64/luci/zope/lib64/python/Products',
	'/usr/lib64/zope/lib64/python',
	'/usr/lib64/zope/lib/python',
	'/usr/lib/zope/lib/python',
	'/usr/lib64/zope/lib/python/Products',
	'/usr/lib64/zope/lib64/python/Products',
	'/usr/lib/zope/lib/python/Products'
))

from Products import __path__
for pdir in [
	'/usr/lib/luci/zope/lib/python/Products',
	'/usr/lib64/luci/zope/lib/python/Products',
	'/usr/lib64/luci/zope/lib64/python/Products',
	'/usr/lib64/zope/lib/python/Products',
	'/usr/lib64/zope/lib64/python/Products',
	'/usr/lib/zope/lib/python/Products']:

	if os.path.isdir(pdir):
		__path__.append(pdir)

LUCI_ADMIN_DEBUG		= False

LUCI_USER				= 'luci'
LUCI_GROUP				= 'luci'

LUCI_HOME_DIR			= '/var/lib/luci'
LUCI_DB_PATH			= '%s/var/Data.fs' % LUCI_HOME_DIR
LUCI_CERT_DIR			= '%s/var/certs/' % LUCI_HOME_DIR
LUCI_PEERS_DIR			= '%speers/' % LUCI_CERT_DIR
LUCI_BACKUP_DIR			= '%s/var' % LUCI_HOME_DIR
LUCI_BACKUP_PATH		= '%s/luci_backup.xml' % LUCI_BACKUP_DIR
LUCI_ADMIN_SET_PATH		= '%s/.default_password_has_been_reset' % LUCI_HOME_DIR

SSL_PRIVKEY_NAME		= 'privkey.pem'
SSL_PUBKEY_NAME			= 'cacert.pem'
SSL_HTTPS_PRIVKEY_NAME	= 'https.key.pem'
SSL_HTTPS_PUBKEY_NAME	= 'https.pem'
SSL_KEYCONFIG_NAME		= 'cacert.config'

SSL_PRIVKEY_PATH		= '%s%s' % (LUCI_CERT_DIR, SSL_PRIVKEY_NAME)
SSL_PUBKEY_PATH			= '%s%s' % (LUCI_CERT_DIR, SSL_PUBKEY_NAME)
SSL_HTTPS_PRIVKEY_PATH	= '%s%s' % (LUCI_CERT_DIR, SSL_HTTPS_PRIVKEY_NAME)
SSL_HTTPS_PUBKEY_PATH	= '%s%s' % (LUCI_CERT_DIR, SSL_HTTPS_PUBKEY_NAME)
SSL_KEYCONFIG_PATH		= '%s%s' % (LUCI_CERT_DIR, SSL_KEYCONFIG_NAME)

err = sys.stderr

# only root should run this
if os.getuid() != 0:
	err.write('Only the \'root\' user can run %s\n' % sys.argv[0])
	err.write('Try again with root privileges.\n')
	sys.exit(2)

ssl_key_data = [
	{	'id': SSL_PRIVKEY_PATH,
		'name': SSL_PRIVKEY_NAME,
		'type': 'private',
		'mode': 0600
	},{	'id' : SSL_HTTPS_PRIVKEY_PATH,
		'name': SSL_HTTPS_PRIVKEY_NAME,
		'type': 'private',
		'mode': 0600
	},{	'id' : SSL_PUBKEY_PATH,
		'name': SSL_PUBKEY_NAME,
		'type': 'public',
		'mode': 0644
	},{	'id' : SSL_HTTPS_PUBKEY_PATH,
		'name': SSL_HTTPS_PUBKEY_NAME,
		'type': 'public',
		'mode': 0644
	},{	'id' : SSL_KEYCONFIG_PATH,
		'name': SSL_KEYCONFIG_NAME,
		'type': 'config',
		'mode': 0644
	}
]

for name in os.listdir(LUCI_PEERS_DIR):
	cert_path = '%s%s' % (LUCI_PEERS_DIR, name)
	if S_ISREG(os.stat(cert_path).st_mode):
		ssl_key_data.append({
			'id': cert_path,
			'name': cert_path.lstrip(LUCI_CERT_DIR),
			'type': 'public',
			'mode': 0644
		})

if '--debug' in sys.argv or '--verbose' in sys.argv:
	LUCI_ADMIN_DEBUG = True
	try:
		del sys.argv[sys.argv.index('--debug')]
	except:
		pass

	try:
		del sys.argv[sys.argv.index('--verbose')]
	except:
		pass

if LUCI_ADMIN_DEBUG is True:
	verbose = sys.stderr
else:
	verbose = file('/dev/null', 'rwb+', 0)

def get_luci_uid_gid():
	luci = pwd.getpwnam(LUCI_USER)[2:4]
	if not luci:
		raise Exception, 'The user "%s" does not exist' % LUCI_USER

	if len(luci) != 2:
		raise Exception, 'Unable to determine the UID and GID of %s' % LUCI_USER
	return luci

def set_default_passwd_reset_flag():
	# set flag marking admin password has been set

	try:
		uid, gid = get_luci_uid_gid()
	except Exception, e:
		err.write('Unable to find the luci user\'s UID and GID: %s\n' % str(e))
		return False

	try:
		open(LUCI_ADMIN_SET_PATH, 'w').write('True')
	except IOError, e:
		if e[0] != 2:
			err.write('Unable to open "%s" for writing: %s\n' \
				% (LUCI_ADMIN_SET_PATH, e[1]))
			return False
	except Exception, e:
		err.write('Unable to open "%s" for writing: %s\n' \
			% (LUCI_ADMIN_SET_PATH, str(e)))
		return False

	os.chown(LUCI_ADMIN_SET_PATH, uid, gid)
	os.chmod(LUCI_ADMIN_SET_PATH, 0640)
	return True

def get_default_passwd_reset_flag():
	try:
		return open(LUCI_ADMIN_SET_PATH, 'r').read(16).strip() == 'True'
	except Exception, e:
		verbose.write('Error reading %s: %s\n' % (LUCI_ADMIN_SET_PATH, str(e)))
		return False
	return False

def read_passwd(prompt, confirm_prompt):
	from getpass import getpass

	while True:
		s1 = getpass(prompt)
		if len(s1) < 6:
			err.write('Password has to be at least 6 characters long\n')
			continue

		if ' ' in s1 or '\t' in s1:
			err.write('Spaces are not allowed in passwords\n')
			continue

		s2 = getpass(confirm_prompt)
		if s1 != s2:
			err.write('Password mismatch, try again\n')
			continue
		return s1

def restore_luci_db_fsattr():
	uid, gid = -1, -1

	try:
		uid, gid = get_luci_uid_gid()
	except Exception, e:
		err.write('Unable to find the luci user\'s UID and GID: %s\n' % str(e))
		return -1

	try:
		os.chown(LUCI_DB_PATH, uid, gid)
		os.chmod(LUCI_DB_PATH, 0600)

		for fext in [ '.tmp', '.old', '.index', '.lock' ]:
			cur_file = '%s%s' % (LUCI_DB_PATH, fext)
			try:
				os.chown(cur_file, uid, gid)
				os.chmod(cur_file, 0600)
			except Exception, e:
				verbose.write('Error: %s: %s\n' % (cur_file, str(e)))
	except Exception, e:
		err.write('Unable to change the ownership of the luci database back to user "%s": %s\n' % (LUCI_USER, str(e)))
		return -1

def set_zope_passwd(user, passwd):
	sys.stderr = verbose

	from ZODB.FileStorage import FileStorage
	from ZODB.DB import DB
	from OFS.Application import AppInitializer
	import AccessControl
	import AccessControl.User
	from AccessControl.AuthEncoding import SSHADigestScheme
	from AccessControl.SecurityManagement import newSecurityManager
	import transaction
	import App.ImageFile

	# Zope wants to open a www/ok.gif and images/error.gif
	# when you initialize the application object. This keeps
	# the AppInitializer(app).initialize() call below from failing.
	App.ImageFile.__init__ = lambda x, y: None

	sys.stderr = err
	try:
		fs = FileStorage(LUCI_DB_PATH)
		db = DB(fs)
		conn = db.open()
	except IOError, e:
		if e[0] == 11:
			err.write('It appears that luci is running. Please stop luci before attempting to reset passwords.\n')
			return -1
		else:
			err.write('Unable to open the luci database "%s": %s\n' \
				% (LUCI_DB_PATH, str(e)))
			return -1
	except Exception, e:
		err.write('Unable to open the luci database "%s": %s\n' \
			% (LUCI_DB_PATH, str(e)))
		return -1

	try:
		sys.stderr = verbose
		tempuser = AccessControl.User.UnrestrictedUser('admin', '',
					('manage','Manager', 'Owner', 'View', 'Authenticated'), [])

		newSecurityManager(None, tempuser)

		app = conn.root()['Application']
		AppInitializer(app).initialize()
		sys.stderr = err
	except Exception, e:
		sys.stderr = err
		err.write('An error occurred while setting the password for user "%s": %s\n' % (user, str(e)))
		return -1

	ret = -1
	try:
		pwd_scheme = SSHADigestScheme
		pwd_hash = '{SSHA}%s' % pwd_scheme.encrypt(SSHADigestScheme(), passwd)
		acl_users = app.acl_users.users

		if len(acl_users):
			acl_users._user_passwords[user] = pwd_hash
			transaction.commit()
			ret = 0
		else:
			raise Exception, 'no admin user account exists'
	except Exception, e:
		sys.stderr = err
		err.write('Unable to set the password for user "%s": %s\n' \
			% (user, str(e)))

	conn.close()
	db.pack()
	db.close()
	fs.close()

	if restore_luci_db_fsattr():
		return -1

	if user == 'admin' and ret == 0:
		set_default_passwd_reset_flag()

	return ret

def luci_restore_certs(certList):
	if not certList or len(certList) < 1:
		err.write('Your backup file contains no certificate data. Please check that your backup file is not corrupt.\n')
		return -1

	certList = certList[0].getElementsByTagName('certificate')
	if not certList or len(certList) < 1:
		err.write('Your backup file contains no certificate data. Please check that your backup file is not corrupt.\n')
		return -1

	uid, gid = -1, -1
	try:
		uid, gid = get_luci_uid_gid()
	except Exception, e:
		err.write('Unable to find the luci user\'s UID and GID: %s\n' \
			% str(e))
		return -1

	for c in certList:
		path = c.getAttribute('name')
		if not path:
			err.write('Missing "name" field for certificate.\n')
			return -1
		path = '%s%s' % (LUCI_CERT_DIR, str(path))

		mode = c.getAttribute('mode')
		if not mode:
			mode = 0600
		else:
			mode = int(mode, 8)

		data = c.firstChild
		if not data or not data.wholeText:
			err.write('"%s" contains no certificate data.' % path)
			return -1

		# Because .prettyprint() was called to write the backup..
		data = data.wholeText.strip()
		if len(data) < 1:
			err.write('"%s" contains no certificate data.' % path)
			return -1
		data = str(data)

		try:
			f = file(path, 'wb+')
		except Exception, e:
			err.write('Unable to create "%s" for writing: %s\n' \
				% (path, str(e)))
			return -1

		os.chmod(path, mode)
		f.write('%s\n' % data)
		os.chown(path, uid, gid)
		f.close()
	return None


def luci_restore(argv):
	sys.stderr = verbose

	from ZODB.FileStorage import FileStorage
	from ZODB.DB import DB
	from OFS.Application import AppInitializer
	import AccessControl
	import AccessControl.User
	from AccessControl.SecurityManagement import newSecurityManager
	import transaction
	import App.ImageFile
	from DateTime import DateTime

	App.ImageFile.__init__ = lambda x, y: None
	sys.stderr = err

	if len(argv) > 0:
		dbfn = argv[0]
	else:
		dbfn = LUCI_DB_PATH

	if len(argv) > 1:
		backupfn = argv[1]
	else:
		backupfn = LUCI_BACKUP_PATH

	try:
		fs = FileStorage(dbfn)
		db = DB(fs)
		db.pack()
		conn = db.open()
	except IOError, e:
		if e[0] == 11:
			err.write('It appears that luci is running. Please stop luci before attempting to restore your installation.\n')
			return -1
		else:
			err.write('Unable to open the luci database "%s": %s\n' \
				% (dbfn, str(e)))
			return -1
	except Exception, e:
		err.write('Unable to open the luci database "%s": %s\n' \
			% (dbfn, str(e)))
		return -1

	try:
		node = xml.dom.minidom.parse(backupfn)
	except Exception, e:
		err.write('Unable to open the luci backup file "%s": %s\n' \
			% (backupfn, str(e)))
		return -1

	node = node.getElementsByTagName('luci')
	if not node or len(node) < 1:
		err.write('Backup file is missing the "luci" XML tag\n')
		return -1

	node = node[0].getElementsByTagName('backupData')
	if not node or len(node) < 1:
		err.write('Backup file is missing the "backupData" XML tag\n')
		return -1
	node = node[0]

	try:
		sys.stderr = verbose
		tempuser = AccessControl.User.UnrestrictedUser('admin', '',
					('manage','Manager', 'Owner', 'View', 'Authenticated'), [])

		newSecurityManager(None, tempuser)

		app = conn.root()['Application']
		AppInitializer(app).initialize()
		sys.stderr = err
	except Exception, e:
		sys.stderr = err
		err.write('An error occurred while initializing the luci installation for restoration from backup: %s\n' % str(e))
		return -1

	try:
		acl_users = app.acl_users.users
		portal_mem = app.luci.portal_membership
		portal_reg = app.luci.portal_registration
		if not (acl_users and len(acl_users) and portal_mem and portal_reg):
			raise Exception, 'no admin user account exists'
	except Exception, e:
		err.write('Your luci installation appears to be corrupt: %s\n' % str(e))
		return -1

	userList = node.getElementsByTagName('userList')
	if not userList or len(userList) < 1:
		err.write('Your backup file contains no users. At the very least, the admin user must exist. Please check that your backup file is not corrupt.\n')
		return -1

	userList = userList[0].getElementsByTagName('user')
	if not userList or len(userList) < 1:
		err.write('Your backup file contains no users. At the very least, the admin user must exist. Please check that your backup file is not corrupt.\n')
		return -1

	for u in userList:
		id = u.getAttribute('id')
		if not id:
			transaction.abort()
			err.write('Missing ID for user\n')
			return -1
		id = str(id)

		passwd = u.getAttribute('passwd')
		if not passwd:
			transaction.abort()
			err.write('Missing password for user "%s"\n' % id)
			return -1
		passwd = str(passwd)

		if id == 'admin':
			try:
				acl_users._user_passwords['admin'] = passwd
			except Exception, e:
				transaction.abort()
				err.write('Unable to restore admin password: %s\n' \
					% str(e))
				return -1
		else:
			email = u.getAttribute('email')
			if not email:
				email = '%s@luci.example.org' % id
			else:
				email = str(email)

			props = {
				'username': id,
				'roles': [ 'Member' ],
				'domains': [],
				'email': email,
				'must_change_password': False
			}

			login_time = u.getAttribute('login_time')
			if login_time:
				props['login_time'] = DateTime(str(login_time))

			last_login_time = u.getAttribute('last_login_time')
			if last_login_time:
				props['last_login_time'] = DateTime(str(last_login_time))

			must_change_passwd = u.getAttribute('must_change_password')
			if must_change_passwd:
				must_change_passwd = str(must_change_passwd)
				if must_change_passwd == 'True' or '1':
					props['must_change_password'] = True

			portal_reg.addMember(id, passwd, props)

			member = portal_mem.getMemberById(id)
			if not member:
				transaction.abort()
				err.write('An error occurred while restoring the user "%s"\n' \
					% id)
				return -1

			try:
				aclu = app.luci.acl_users.source_users
				if aclu and len(aclu):
					aclu._user_passwords[id] = passwd
				else:
					raise Exception, 'unable to set password for %s' % id
			except Exception, e:
				transaction.abort()
				err.write('An error occurred while restoring the password for user "%s": %s\n' % (id, str(e)))
				return -1
			verbose.write('Added user "%s"\n' % id)
	transaction.commit()

	try:
		x = app.luci.systems.storage
		if not x:
			raise Exception, 'no storage directory'
	except Exception, e:
		transaction.abort()
		err.write('Cannot find the luci storage systems directory. Your luci installation may be corrupt.\n')
		return -1

	systemList = node.getElementsByTagName('systemList')
	if not systemList or len(systemList) < 1:
		verbose.write('No storage systems to add\n')
	else:
		systemList = systemList[0].getElementsByTagName('system')
		if len(systemList) < 1:
			verbose.write('No storage systems to add\n')

	for s in systemList:
		id = s.getAttribute('id')
		if not id:
			transaction.abort()
			err.write('Missing ID for storage system. Your backup may be corrupt.\n')
			return -1
		id = str(id)
		try:
			title = str(s.getAttribute('title'))
		except:
			title = ''

		x.manage_addFolder(id, title)
		try:
			new_system = app.luci.systems.storage.get(id)

			if not new_system:
				raise Exception, 'unable to add system %s' % id

			new_system.manage_acquiredPermissions([])
			new_system.manage_role('View',
				[ 'Access contents information', 'View' ])
		except Exception, e:
			transaction.abort()
			err.write('An error occurred while restoring storage system "%s": %s\n' % (id, str(e)))
			return -1

		userPerms = s.getElementsByTagName('permList')
		if not userPerms or len(userPerms) < 1:
			verbose.write('Added storage system "%s"\n' % id)
			continue

		userPerms = userPerms[0].getElementsByTagName('ref')
		for i in userPerms:
			newuser = i.getAttribute('name')
			if not newuser:
				continue

			try:
				new_system.manage_setLocalRoles(newuser, [ 'View' ])
				verbose.write('Added view permission to storage system "%s" for "%s"\n' % (id, newuser))
			except Exception, e:
				err.write('An error occurred while restoring permission for system "%s" for user "%s": %s\n' % (id, newuser, str(e)))

		verbose.write('Added storage system "%s"\n' % id)
		transaction.commit()

	try:
		x = app.luci.systems.cluster
		if not x:
			raise
	except:
		transaction.abort()
		err.write('Cannot find the luci cluster directory. Your luci installation may be corrupt.\n')
		return -1

	clusterList = node.getElementsByTagName('clusterList')
	if not clusterList or len(clusterList) < 1:
		verbose.write('No clusters to add\n')
	else:
		clusterList = clusterList[0].getElementsByTagName('cluster')
		if len(clusterList) < 1:
			verbose.write('No clusters to add\n')

	for c in clusterList:
		id = c.getAttribute('id')
		if not id:
			transaction.abort()
			err.write('Cluster element is missing id\n')
			return -1
		id = str(id)

		title = c.getAttribute('title')
		if not title:
			title = ''
		else:
			title = str(title)

		try:
			x.manage_addFolder(id, title)
			new_cluster = app.luci.systems.cluster.get(id)

			if not new_cluster:
				raise Exception, 'unable to add cluster %s' % id

			new_cluster.manage_acquiredPermissions([])
			new_cluster.manage_role('View',
				[ 'Access contents information', 'View' ])
		except Exception, e:
			transaction.abort()
			err.write('An error occurred while restoring the cluster "%s": %s\n' % (id, str(e)))
			return -1

		viewperm = list()

		userPerms = c.getElementsByTagName('permList')
		if userPerms and len(userPerms) > 0:
			userPerms = userPerms[0].getElementsByTagName('ref')
			for i in userPerms:
				newuser = i.getAttribute('name')
				if not newuser:
					continue
				newuser = str(newuser)

				try:
					new_cluster.manage_setLocalRoles(newuser, [ 'View' ])
					verbose.write('Added view permission to cluster "%s" for "%s"\n' % (id, newuser))
				except Exception, e:
					err.write('An error occurred while restoring permission for cluster "%s" for user "%s"\n' % (id, newuser))
				viewperm.append(newuser)

		clusterSystems = c.getElementsByTagName('csystemList')
		if not clusterSystems or len(clusterSystems) < 1:
			verbose.write('Cluster "%s" has no nodes\n' % id)
		else:
			clusterSystems = clusterSystems[0].getElementsByTagName('csystem')
			for i in clusterSystems:
				newsys = i.getAttribute('id')
				if not newsys:
					transaction.abort()
					err.write('Missing node name for cluster "%s"\n' % id)
					return -1

				newsys = str(newsys)
				stitle = i.getAttribute('title')
				if not stitle:
					stitle = ''
				else:
					stitle = str(stitle)

				try:
					new_cluster.manage_addFolder(newsys, stitle)
					newcs = app.luci.systems.cluster.get(id).get(newsys)
					if not newcs:
						raise Exception, 'unable to add node %s to cluster %s' \
								% (newsys, id)

					newcs.manage_acquiredPermissions([])
					newcs.manage_role('View',
						[ 'Access contents information', 'View' ])
				except Exception, e:
					transaction.abort()
					err.write('An error occurred while restoring node "%s" for cluster "%s": %s\n' % (newsys, id, str(e)))
					return -1
				transaction.commit()

				try:
					for i in viewperm:
						newcs.manage_setLocalRoles(i, [ 'View' ])
						verbose.write('Added view permission to node "%s" in cluster "%s" for user "%s"\n' % (newsys, id, i))
				except Exception, e:
					transaction.abort()
					err.write('An error occurred while restoring view permission to node "%s" in cluster "%s" for user "%s"\n' % (newsys, id, i))
					return -1

				verbose.write('Added node "%s" to cluster "%s"\n' \
					% (newsys, id))

		verbose.write('Added cluster "%s"\n' % id)
		transaction.commit()

	transaction.commit()
	conn.close()
	db.pack()
	db.close()
	fs.close()

	certList = node.getElementsByTagName('certificateList')
	if not certList or len(certList) < 1:
		err.write('No certificate data was found.\n')
		return -1

	if luci_restore_certs(certList):
		err.write('An error occurred while restoring certificate data.\n')
		return -1

	return 0

# This function's ability to work is dependent
# upon the structure of @obj_dict
def dataToXML(doc, obj_dict, tltag):
	node = doc.createElement(tltag)
	for i in obj_dict:
		if isinstance(obj_dict[i], types.DictType):
			if i[-4:] == 'List':
				tagname = i
			else:
				tagname = tltag[:-4]
			temp = dataToXML(doc, obj_dict[i], tagname)
			node.appendChild(temp)
		elif isinstance(obj_dict[i], types.StringType) or isinstance(obj_dict[i], types.IntType):
			node.setAttribute(i, str(obj_dict[i]))
		elif isinstance(obj_dict[i], types.ListType):
			if len(obj_dict[i]) < 1:
				continue
			temp = doc.createElement(i)
			for x in obj_dict[i]:
				t = doc.createElement('ref')
				t.setAttribute('name', x)
				temp.appendChild(t.cloneNode(True))
			node.appendChild(temp.cloneNode(True))
	return node.cloneNode(True)

def luci_backup(argv):
	sys.stderr = verbose

	from ZODB.FileStorage import FileStorage
	from ZODB.DB import DB
	from OFS.Application import AppInitializer
	import AccessControl
	import AccessControl.User
	from AccessControl.SecurityManagement import newSecurityManager
	import transaction
	from CMFPlone.utils import getToolByName
	import App.ImageFile

	App.ImageFile.__init__ = lambda x, y: None
	sys.stderr = err

	if len(argv) > 0:
		dbfn = argv[0]
	else:
		dbfn = LUCI_DB_PATH

	try:
		fs = FileStorage(dbfn)
		db = DB(fs)
		db.pack()
		conn = db.open()
	except IOError, e:
		if e[0] == 11:
			err.write('It appears that luci is running. Please stop luci before attempting to backup your installation.\n')
			return -1
		else:
			err.write('Unable to open the luci database "%s": %s\n' \
				% (dbfn, str(e)))
			return -1
	except Exception, e:
		err.write('Unable to open the luci database "%s": %s\n' \
			% (dbfn, str(e)))
		return -1

	try:
		sys.stderr = verbose
		tempuser = AccessControl.User.UnrestrictedUser('admin', '',
					('manage','Manager', 'Owner', 'View', 'Authenticated'), [])

		newSecurityManager(None, tempuser)

		app = conn.root()['Application']
		AppInitializer(app).initialize()
		sys.stderr = err
	except Exception, e:
		sys.stderr = err 
		err.write('An error occurred while initializing the luci installation for restoration from backup: %s\n' % str(e))
		return -1

	app.luci.portal_memberdata.pruneMemberDataContents()
	transaction.commit()

	try:
		acl_users = app.acl_users.users
		if not (acl_users and len(acl_users)):
			raise Exception, 'no admin user account exists'
	except Exception, e:
		err.write('Your luci installation appears to be corrupt: %s\n' % str(e))
		return -1

	users = {}
	systems = {}
	clusters = {}

	try:
		acl_users = app.acl_users.users
		if len(acl_users) < 1:
			raise Exception, 'no admin user account exists'

		users['admin'] = {
			'id': 'admin',
			'name': 'admin',
			'passwd': app.acl_users.users._user_passwords['admin']
		}
	except Exception, e:
		err.write('Unable to find the admin user account: %s\n' % str(e))
		return -1

	acl_users = app.luci.acl_users.source_users
	if acl_users and len(acl_users):
		for i in app.luci.acl_users.source_users._user_passwords.items():
			try:
				users[i[0]] = {
					'id': i[0],
					'name': i[0],
					'passwd': i[1]
				}
			except Exception, e:
				try:
					err.write('An error occurred while saving details for user "%s": %s\n' % (i[0], str(e)))
				except:
					err.write('An error occurred while saving user information.\n')
				return -1

	try:
		membertool = getToolByName(app.luci, 'portal_membership')
		if not membertool:
			raise Exception, 'unable to find user list'

		for mem in membertool.listMembers():
			try:
				for i in [ 'login_time', 'last_login_time', 'must_change_password', 'email' ]:
					prop = mem.getProperty(i)
					if prop != '':
						users[mem.id][i] = str(prop)
			except Exception, e1:
				verbose.write('Error retrieving member properties: %s\n' \
					% str(e1))
				continue
	except Exception, e:
		verbose.write('Error: %s\n' % str(e))

	try:
		storagedir = app.luci.systems.storage
		clusterdir = app.luci.systems.cluster
	except:
		err.write('Your luci installation appears to be corrupt.')
		return -1

	if storagedir and len(storagedir):
		for i in storagedir.objectItems():
			systems[i[0]] = { 'id': i[0] }
			if hasattr(i[1], 'title'):
				systems[i[0]]['title'] = getattr(i[1], 'title')
			else:
				systems[i[0]]['title'] = '__luci__:system'

			if hasattr(i[1], '__ac_local_roles__'):
				roles = getattr(i[1], '__ac_local_roles__')
				if roles:
					systems[i[0]]['permList'] = map(lambda x: x[0], filter(lambda x: len(x) > 1 and 'View' in x[1], roles.items()))
			else:
				systems[i[0]]['permList'] = {}

	if clusterdir and len(clusterdir):
		for i in clusterdir.objectItems():
			cluster_name = i[0]
			clusters[cluster_name] = { 'id': cluster_name, 'csystemList': {} }
			if hasattr(i[1], 'title'):
				clusters[cluster_name]['title'] = getattr(i[1], 'title')
			else:
				clusters[cluster_name]['title'] = '__luci__:cluster'

			if hasattr(i[1], '__ac_local_roles__'):
				roles = getattr(i[1], '__ac_local_roles__')
				if roles:
					clusters[cluster_name]['permList'] = map(lambda x: x[0], filter(lambda x: len(x) > 1 and 'View' in x[1], roles.items()))
			else:
				clusters[cluster_name]['permList'] = {}

			for csystem in i[1].objectItems():
				csystem_hash = { 'id': csystem[0] }

				if hasattr(csystem[1], 'title'):
					csystem_hash['title'] = getattr(csystem[1], 'title')
				else:
					csystem_hash['title'] = '__luci__:csystem:%s' % cluster_name
				clusters[cluster_name]['csystemList'][csystem[0]] = csystem_hash

	transaction.commit()
	conn.close()
	db.pack()
	db.close()
	fs.close()

	backup_data = {
		'userList': users,
		'systemList': systems,
		'clusterList': clusters
	}

	doc = xml.dom.minidom.Document()
	luciData = doc.createElement('luci')
	doc.appendChild(luciData)
	dataNode = dataToXML(doc, backup_data, 'backupData')

	certList = doc.createElement('certificateList')
	for i in ssl_key_data:
		try:
			certfile = file(i['id'], 'rb')
			output = certfile.read()
			certfile.close()

			if len(output) < 1:
				raise Exception, '%s contains no data' % i['id']
		except Exception, e:
			err.write('Unable to read certificate data from "%s": %s\n' \
				% (i['id'], str(e)))

			# An error backing up anything other than the config
			# is fatal.
			if i['type'] != 'config':
				return None

		certNode = doc.createElement('certificate')
		certNode.setAttribute('id', i['id'])
		certNode.setAttribute('name', i['name'])
		certNode.setAttribute('type', i['type'])
		certNode.setAttribute('mode', str(oct(i['mode'])))
		textNode = doc.createTextNode('\n%s' % output)
		certNode.appendChild(textNode)
		certList.appendChild(certNode)

	dataNode.appendChild(certList.cloneNode(True))
	luciData.appendChild(dataNode)

	return doc


def exec_cmd(command, argv, searchPath = 0, root = '/', stdin = 0, catchfd = 1, catcherrfd = 2, closefd = -1):
	if not os.access ('%s%s' % (root, command), os.X_OK):
		raise RuntimeError, '%s is not executable' % command

	(read, write) = os.pipe()
	(read_err, write_err) = os.pipe()

	childpid = os.fork()
	if (not childpid):
		# child
		if (root and root != '/'):
			os.chroot (root)
		if isinstance(catchfd, tuple):
			for fd in catchfd:
				os.dup2(write, fd)
		else:
			os.dup2(write, catchfd)
		os.close(write)
		os.close(read)

		if isinstance(catcherrfd, tuple):
			for fd in catcherrfd:
				os.dup2(write_err, fd)
		else:
			os.dup2(write_err, catcherrfd)
		os.close(write_err)
		os.close(read_err)

		if closefd != -1:
			os.close(closefd)

		if stdin:
			os.dup2(stdin, 0)
			os.close(stdin)

		if (searchPath):
			os.execvp(command, argv)
		else:
			os.execv(command, argv)
		# will never come here

	os.close(write)
	os.close(write_err)

	rc = ""
	rc_err = ""
	in_list = [read, read_err]
	while len(in_list) != 0:
		i, o, e = select(in_list, [], [], 0.1)
		for fd in i:
			if fd == read:
				s = os.read(read, 4096)
				if s == '':
					in_list.remove(read)
				rc = '%s%s' % (rc, s)
			if fd == read_err:
				s = os.read(read_err, 4096)
				if s == '':
					in_list.remove(read_err)
				rc_err = '%s%s' % (rc_err, s)

	os.close(read)
	os.close(read_err)

	status = -1
	try:
		(pid, status) = os.waitpid(childpid, 0)
	except OSError, (errno, msg):
		err.write('%s waitpid: %s\n' % (__name__,  msg))

	if os.WIFEXITED(status):
		status = os.WEXITSTATUS(status)
	else:
		status = -1

	return (rc, rc_err, status)


def luci_initialized():
	# existence of privkey.pem file and
	# admin password (not the one Data.fs comes with)
	# mean that luci has been initialized
	b1 = get_default_passwd_reset_flag()
	b2 = os.access(SSL_PRIVKEY_PATH, os.F_OK)
	return b1 and b2

def generate_ssl_certs():
	command = '/bin/rm'
	args = [ command, '-f', SSL_PRIVKEY_PATH, SSL_PUBKEY_PATH ]
	exec_cmd(command, args)

	# /usr/bin/openssl genrsa -out /var/lib/luci/var/certs/privkey.pem 2048 > /dev/null 2>&1
	command = '/usr/bin/openssl'
	args = [ command, 'genrsa', '-out', SSL_PRIVKEY_PATH, '2048' ]
	exec_cmd(command, args)

	# /usr/bin/openssl req -new -x509 -key /var/lib/luci/var/certs/privkey.pem -out /var/lib/luci/var/certs/cacert.pem -days 1825 -config /var/lib/luci/var/certs/cacert.config
	command = '/usr/bin/openssl'
	args = [ command, 'req', '-new', '-x509', '-key', SSL_PRIVKEY_PATH, '-out', SSL_PUBKEY_PATH, '-days', '1825', '-set_serial', str(int(time())), '-config', SSL_KEYCONFIG_PATH ]
	exec_cmd(command, args)

	# take ownership and restrict access
	try:
		uid, gid = get_luci_uid_gid()
		os.chown(SSL_PRIVKEY_PATH, uid, gid)
		os.chown(SSL_PUBKEY_PATH, uid, gid)
		os.chmod(SSL_PRIVKEY_PATH, 0600)
		os.chmod(SSL_PUBKEY_PATH, 0644)
	except Exception, e:
		err.write('Error generating SSL certificates: %s\n' % str(e))
		command = '/bin/rm'
		args = [ command, '-f', SSL_PRIVKEY_PATH, SSL_PUBKEY_PATH ]
		exec_cmd(command, args)
		return False

	return True

def restart_message():
	print '\n\nYou must restart the luci server for changes to take effect.\n'
	print 'Run "service luci restart" to do so\n'

def init(argv):
	if luci_initialized():
		err.write('luci site has been already initialized.\n')
		err.write('If you want to reset admin password, execute\n')
		err.write('\t%s password\n' % argv[0])
		sys.exit(1)

	print 'Initializing the luci server\n'
	print '\nCreating the \'admin\' user\n'

	new_password = read_passwd('Enter password: ', 'Confirm password: ')

	print '\nPlease wait...'

	if not set_zope_passwd('admin', new_password):
		restore_luci_db_fsattr()
		print 'The admin password has been successfully set.'
	else:
		err.write('Unable to set the admin user\'s password.\n')
		sys.exit(1)

	print 'Generating SSL certificates... '
	if generate_ssl_certs() == False:
		sys.exit(1)

	print 'The luci server has been successfully initialized'
	restart_message()

def password(argv):
	passwd = None

	ret = exec_cmd('/sbin/service', [ 'service', 'luci', 'status' ])
	if ret[2] == 0:
		err.write('You must stop the luci server before attempting to set the admin password.\n')
		sys.exit(1)

	if '--random' in argv:
		print 'Setting the admin user password to a random string...\n'

		try:
			rand = open('/dev/urandom', 'r')
			passwd = rand.read(16)
			rand.close()
		except Exception, e:
			err.write('Unable to read from /dev/urandom: %s\n' % str(e))
			sys.exit(1)
	else:
		if not luci_initialized():
			err.write('The luci site has not been initialized.\n')
			err.write('To initialize it, execute\n')
			err.write('\t%s init\n' % argv[0])
			sys.exit(1)

		print 'Setting the admin user\'s password\n'
		passwd = read_passwd('Enter new password: ', 'Confirm password: ')

	print '\nPlease wait...'
	if not set_zope_passwd('admin', passwd):
		print 'The admin password has been successfully set.'
	else:
		sys.exit(1)

	restart_message()

def backup(argv):
	# If the site hasn't been initialized, there's nothing to
	# save, and luci_backup() will fail
	if not luci_initialized():
		print 'The luci site has not been initialized\n'
		print 'There is nothing to backup\n'
		sys.exit(0)

	ret = exec_cmd('/sbin/service', [ 'service', 'luci', 'status' ])
	if ret[2] == 0:
		err.write('You must stop the luci server before backing up the luci database.\n')
		sys.exit(1)

	print 'Backing up the luci server...'

	try:
		os.umask(077)
	except:
		pass

	doc = luci_backup(argv[2:])
	restore_luci_db_fsattr()
	if doc == -1:
		err.write('The luci backup failed. Exiting.\n')
		sys.exit(1)

	try:
		# The LUCI_BACKUP_DIR must not be world-writable
		# as the code below is obviously not safe against
		# races.
		os.stat(LUCI_BACKUP_PATH)
		trynum = 1

		while True:
			oldbackup = '%s/luci-backup-%d.xml' % (LUCI_BACKUP_DIR, trynum)
			if not os.path.exists(oldbackup):
				try:
					os.rename(LUCI_BACKUP_PATH, oldbackup)
				except Exception, e:
					err.write('Unable to rename the existing backup file "%s" to "%s": %s\n' % (LUCI_BACKUP_PATH, oldbackup, str(e)))
					err.write('The luci backup failed.\n')
				break
			trynum += 1
	except OSError, e:
		#if e[0] == 2:
		pass

	try:
		f = file(LUCI_BACKUP_PATH, 'wb+')
	except Exception, e:
		err.write('Unable to open "%s" to write the backup: %s\n' \
			% (LUCI_BACKUP_PATH, str(e)))
		err.write('The luci backup failed.\n')
		sys.exit(1)

	try:
		os.chmod(LUCI_BACKUP_PATH, 0600)
	except OSError, e:
		err.write('An error occurred while setting file system permissions for "%s": %s\n' % (LUCI_BACKUP_PATH, str(e)))
		err.write('Please ensure this file is not world-readable.\n')

	try:
		f.write(doc.toprettyxml())
		f.close()
	except Exception, e:
		err.write('The luci backup failed: %s\n' % str(e))
		sys.exit(1)

	print 'The luci backup was successful.\n'
	print 'The backup data is contained in the file "%s"\n' % LUCI_BACKUP_PATH

def restore(argv):
	ret = exec_cmd('/sbin/service', [ 'service', 'luci', 'status' ])
	if ret[2] == 0:
		err.write('You must stop the luci server before restoring the luci database from backup.\n')
		sys.exit(1)

	print 'Restoring the luci server...'

	try:
		os.umask(077)
	except:
		pass

	if luci_restore(argv[2:]):
		ret = False
		err.write('The luci restore failed. Try reinstalling luci, then restoring again.\n')
	else:
		set_default_passwd_reset_flag()
		ret = True
		print 'The luci restore was successful.'
		restart_message()

	if restore_luci_db_fsattr():
		return False

	return ret

def luci_help(argv):
	print 'Usage:'
	print '%s [init|backup|restore|password|help]\n' % argv[0]
	print '\tinit: initialize the luci server'
	print '\tpassword: reset the admin password'
	print '\t\t--random: set the admin password to a random value (disable account)'
	print '\tbackup: backup the luci database to an XML file'
	print '\trestore: restore luci database from a backup'
	print '\thelp: display this help message\n'

def test_luci_installation():
	# perform basic checks
	# TODO: do more tests

	# check if luci user and group are present on the system
	try:
		get_luci_uid_gid()
	except Exception, e:
		err.write('There is a problem with your luci installation!\n')
		err.write('The luci user\'s UID and GID could not be determined: %s\n' \
			% str(e))
		err.write('Reinstalling luci is recommended\n\n')
		sys.exit(3)

	return True

def main(argv):
	if len(argv) < 2:
		luci_help(argv)
		sys.exit(1)

	test_luci_installation()

	if 'init' in argv:
		init(argv)
	elif 'backup' in argv:
		backup(argv)
	elif 'restore' in argv:
		restore(argv)
	elif 'password' in argv:
		password(argv)
	elif 'help' in argv:
		luci_help(argv)
	else:
		err.write('Unknown command\n\n')
		luci_help(argv)
		sys.exit(1)

# If called from the command line
if __name__ == '__main__':
	main(sys.argv)
