#!/usr/bin/python

# Copyright (C) 2006-2007 Red Hat, Inc.

import sys, os, pwd
from select import select
from stat import S_ISREG
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_INIT_DEBUG = 0

LUCI_USER  = 'luci'
LUCI_GROUP = 'luci'

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

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       = LUCI_CERT_DIR + SSL_PRIVKEY_NAME
SSL_PUBKEY_PATH        = LUCI_CERT_DIR + SSL_PUBKEY_NAME
SSL_HTTPS_PRIVKEY_PATH = LUCI_CERT_DIR + SSL_HTTPS_PRIVKEY_NAME
SSL_HTTPS_PUBKEY_PATH  = LUCI_CERT_DIR + SSL_HTTPS_PUBKEY_NAME
SSL_KEYCONFIG_PATH     = LUCI_CERT_DIR + SSL_KEYCONFIG_NAME

# only root should run this
if os.getuid() != 0:
	sys.stderr.write('Only the \'root\' user can run %s\n' % sys.argv[0])
	sys.stderr.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 = 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})

#null = file(os.devnull, 'rwb+', 0)   - available on python 2.4 and above!!!
null = file('/dev/null', 'rwb+', 0)
orig_stderr = sys.stderr

if LUCI_INIT_DEBUG:
	verbose = sys.stderr
else:
	verbose = null



def get_luci_uid_gid():
	try:
		luci = pwd.getpwnam(LUCI_USER)[2:4]
		if not luci:
			raise
		if len(luci) != 2:
			raise
		return luci
	except:
		msg = 'Cannot find the "%s" user.\n' % LUCI_USER
		sys.stderr.write(msg)
		raise Exception, msg


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

	try:
		uid, gid = get_luci_uid_gid()
	except:
		sys.stderr.write('Unable to find the luci user\'s UID\n')
		return False

	try:
		open(LUCI_ADMIN_SET_PATH, 'w').write('True')
	except IOError, e:
		if e[0] != 2:
			sys.stderr.write('Unable to open "%s" for writing: %s\n' \
				% (LUCI_ADMIN_SET_PATH, e[1]))
			return False
	except Exception, e:
		sys.stderr.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:
		return False
	return False


def read_passwd(prompt, confirm_prompt):
	from getpass import getpass
	while True:
		s1 = getpass(prompt)
		if len(s1) < 6:
			print 'Password has to be at least 6 characters long'
			continue
		if ' ' in s1:
			print 'Spaces are not allowed in passwords'
			continue
		s2 = getpass(confirm_prompt)
		if s1 != s2:
			print 'Password mismatch, try again'
			continue
		return s1



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

	try:
		uid, gid = get_luci_uid_gid()
	except:
		return -1

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

		for fext in [ '.tmp', '.old', '.index', '.lock' ]:
			try:
				os.chown('%s%s' % (LUCI_DB_PATH, fext), uid, gid)
				os.chmod('%s%s' % (LUCI_DB_PATH, fext), 0600)
			except:
				pass
	except Exception, e:
		sys.stderr.write('Unable to change 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 = null
	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 = orig_stderr

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

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

		newSecurityManager(None, tempuser)

		app = conn.root()['Application']
		AppInitializer(app).initialize()
		sys.stderr = orig_stderr
	except:
		sys.stderr = orig_stderr
		sys.stderr.write('An error occurred while setting the password for user \"' + user + '\"\n')
		return -1

	ret = -1
	try:
		pwd_scheme = SSHADigestScheme
		pwd_hash = '{SSHA}' + 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
	except:
		sys.stderr.write('Unable to set the password for user \"' + user + '\"\n')

	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:
		sys.stderr.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:
		sys.stderr.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:
		return -1

	for c in certList:
		path = c.getAttribute('name')
		if not path:
			sys.stderr.write('Missing \"name\" field for certificate.\n')
			return -1
		path = 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:
			sys.stderr.write('\"' + path + '\" has no certificate data.')
			return -1

		# Because .prettyprint() was called to write the backup..
		data = data.wholeText.strip()
		if len(data) < 1:
			sys.stderr.write('\"' + path + '\" has no certificate data.')
			return -1
		data = str(data)

		try:
			f = file(path, 'wb+')
		except:
			sys.stderr.write('Unable to create \" ' + path + '\" for writing.\n')
			return -1

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


def luci_restore(argv):
	sys.stderr = null
	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 = orig_stderr

	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:
			sys.stderr.write('It appears that Luci is running. Please stop Luci before attempting to restore your installation.\n')
			return -1
		else:
			sys.stderr.write('Unable to open the Luci database \"' + dbfn + '\":' + str(e) + '\n')
			return -1
	except Exception, e:
		sys.stderr.write('Unable to open the Luci database \"' + dbfn + '\":' + str(e) + '\n')
		return -1

	try:
		node = xml.dom.minidom.parse(backupfn)
	except:
		sys.stderr.write('Unable to open the Luci backup file \"'+ backupfn +'\"\n')
		return -1

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

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

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

		newSecurityManager(None, tempuser)

		app = conn.root()['Application']
		AppInitializer(app).initialize()
		sys.stderr = orig_stderr
	except:
		sys.stderr = orig_stderr
		sys.stderr.write('An error occurred while initializing the Luci installation for restoration from backup\n')
		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
	except:
		sys.stderr.write('Your Luci installation appears to be corrupt.\n')
		return -1

	userList = node.getElementsByTagName('userList')
	if not userList or len(userList) < 1:
		sys.stderr.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:
		sys.stderr.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()
			sys.stderr.write('Missing ID for user\n')
			return -1
		id = str(id)

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

		if id == 'admin':
			try:
				acl_users._user_passwords['admin'] = passwd
			except:
				transaction.abort()
				sys.stderr.write('Unable to restore admin password.')
				return -1
		else:
			email = u.getAttribute('email')
			if not email:
				email = id + '@luci.example.org'
			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()
				sys.stderr.write('An error occurred while restoring the user \"' + id + '\"\n')
				return -1

			try:
				aclu = app.luci.acl_users.source_users
				if aclu and len(aclu):
					aclu._user_passwords[id] = passwd
				else:
					raise
			except:
				transaction.abort()
				sys.stderr.write('An error occurred while restoring the password for user \"' + id + '\"\n')
				return -1
			verbose.write('Added user \"' + id + '\"\n')
	transaction.commit()

	try:
		x = app.luci.systems.storage
		if not x:
			raise
	except:
		transaction.abort()
		sys.stderr.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()
			sys.stderr.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
			new_system.manage_acquiredPermissions([])
			new_system.manage_role('View',
				['Access contents information', 'View'])
		except:
			transaction.abort()
			sys.stderr.write('An error occurred while restoring storage system \"' + id + '\"\n')
			return -1

		userPerms = s.getElementsByTagName('permList')
		if not userPerms or len(userPerms) < 1:
			verbose.write('Added storage system \"' + id + '\"\n')
			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 \"' + id + '\" for \"' + newuser + '\"\n')
			except:
				sys.stderr.write('An error occurred while restoring permission for storage system \"' + id + '\" for user \"' + newuser + '\"\n')

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

	try:
		x = app.luci.systems.cluster
		if not x:
			raise
	except:
		transaction.abort()
		sys.stderr.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()
			sys.stderr.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
			new_cluster.manage_acquiredPermissions([])
			new_cluster.manage_role('View',
				['Access contents information', 'View'])
		except:
			transaction.abort()
			sys.stderr.write('An error occurred while restoring the cluster \"' + id + '\"\n')
			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 \"' + id + '\" for \"' + newuser + '\"\n')
				except:
					sys.stderr.write('An error occurred while restoring permission for cluster \"' + id + '\" for user \"' + newuser + '\"\n')
				viewperm.append(newuser)

		clusterSystems = c.getElementsByTagName('csystemList')
		if not clusterSystems or len(clusterSystems) < 1:
			verbose.write('Cluster \"' + id + '\" has no storage systems\n')
		else:
			clusterSystems = clusterSystems[0].getElementsByTagName('csystem')
			for i in clusterSystems:
				newsys = i.getAttribute('id')
				if not newsys:
					transaction.abort()
					sys.stderr.write('Storage system missing name for cluster \"' + id + '\"\n')
					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
					newcs.manage_acquiredPermissions([])
					newcs.manage_role('View',
						['Access contents information', 'View'])
				except:
					transaction.abort()
					sys.stderr.write('An error occurred while restoring the storage system \"' + newsys + '\" for cluster \"' + id + '\"\n')
					return -1
				transaction.commit()

				try:
					for i in viewperm:
						newcs.manage_setLocalRoles(i, ['View'])
						verbose.write('Added view permission to cluster system \"' + newsys + '\" for \"' + i + '\"\n')
				except:
					transaction.abort()
					sys.stderr.write('An error occurred while restoring permissions for cluster system \"' + newsys + '\" in cluster \"' + id + '\" for user \"' + i + '\"\n')
					return -1

				verbose.write('Added storage system \"' + newsys + '\" for cluster \"' + id + '\"\n')

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

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

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

	if luci_restore_certs(certList):
		sys.stderr.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 = null
	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 = orig_stderr

	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:
			sys.stderr.write('It appears that Luci is running. Please stop Luci before attempting to backup your installation.\n')
			return -1
		else:
			sys.stderr.write('Unable to open the Luci database \"' + dbfn + '\":' + str(e) + '\n')
			return -1
	except Exception, e:
		sys.stderr.write('Unable to open the Luci database \"' + dbfn + '\":' + str(e) + '\n')
		return -1

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

		newSecurityManager(None, tempuser)

		app = conn.root()['Application']
		AppInitializer(app).initialize()
		sys.stderr = orig_stderr
	except:
		sys.stderr = orig_stderr
		sys.stderr.write('An error occurred while initializing the Luci installation for restoration from backup\n')
		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
	except:
		sys.stderr.write('Your Luci installation appears to be corrupt.\n')
		return -1

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

	try:
		acl_users = app.acl_users.users
		if len(acl_users) < 1:
			raise
		users['admin'] = {
			'id': 'admin',
			'name': 'admin',
			'passwd': app.acl_users.users._user_passwords['admin']
		}
	except:
		sys.stderr.write('Unable to find the admin user.\n')
		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:
				try:
					sys.stderr.write('An error occurred while saving details for user \"' + i[0] + '\"\n')
				except:
					sys.stderr.write('An error occurred while saving user information.')
				return -1

	try:
		membertool = getToolByName(app.luci, 'portal_membership')
		if not membertool:
			raise
		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:
				continue
	except:
		pass

	try:
		storagedir = app.luci.systems.storage
		clusterdir = app.luci.systems.cluster
	except:
		sys.stderr.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:' + 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
		except:
			sys.stderr.write('Unable to read \"' + i['id'] + '\"\n')
			# 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' + output)
		certNode.appendChild(textNode)
		certList.appendChild(certNode)

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

	return doc


def _execWithCaptureErrorStatus(command, argv, searchPath = 0, root = '/', stdin = 0, catchfd = 1, catcherrfd = 2, closefd = -1):
    if not os.access (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, 1000)
                if s == '':
                    in_list.remove(read)
                rc = rc + s
            if fd == read_err:
                s = os.read(read_err, 1000)
                if s == '':
                    in_list.remove(read_err)
                rc_err = rc_err + s

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

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

    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]
    _execWithCaptureErrorStatus(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']
    _execWithCaptureErrorStatus(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', '-config', SSL_KEYCONFIG_PATH]
    _execWithCaptureErrorStatus(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:
	    command = '/bin/rm'
	    args = [command, '-f', SSL_PRIVKEY_PATH, SSL_PUBKEY_PATH]
	    _execWithCaptureErrorStatus(command, args)
	    return False

    return True


def restart_message():
    print
    print
    print 'Restart the Luci server for changes to take effect'
    print 'eg. service luci restart'
    print
    return





def init(argv):
	if luci_initialized():
		sys.stderr.write('Luci site has been already initialized.\n')
		sys.stderr.write('If you want to reset admin password, execute\n')
		sys.stderr.write('\t' + argv[0] + ' password\n')
		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:
		sys.stderr.write('Unable to set the admin user\'s password.\n')
		sys.exit(1)

	print 'Generating SSL certificates...'
	if generate_ssl_certs() == False:
		sys.stderr.write('failed. exiting ...\n')
		sys.exit(1)

	print 'Luci server has been successfully initialized'
	restart_message()

	return


def password(argv):
	passwd = None
	if '--random' in argv:
		print 'Resetting the admin user\'s password to some random value\n'
		try:
			rand = open('/dev/urandom', 'r')
			passwd = rand.read(16)
			rand.close()
		except:
			sys.stderr.write('Unable to read from /dev/urandom\n')
			sys.exit(1)
	else:
		if not luci_initialized():
			sys.stderr.write('The Luci site has not been initialized.\n')
			sys.stderr.write('To initialize it, execute\n')
			sys.stderr.write('\t' + argv[0] + ' init\n')
			sys.exit(1)

		print 'Resetting 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 reset.'
	else:
		sys.stderr.write('Unable to set the admin user\'s password.\n')
		sys.exit(1)

	restart_message()

	return


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 'Nothing to backup\n'
		sys.exit(0)

	print 'Backing up the Luci server...'

	try:
		os.umask(077)
	except: pass

	doc = luci_backup(argv[2:])
	restore_luci_db_fsattr()
	if doc == -1:
		sys.stderr.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
		basename = '/luci_backup-'

		while True:
			oldbackup = LUCI_BACKUP_DIR + basename + str(trynum) + '.xml'
			if not os.path.exists(oldbackup):
				try:
					os.rename(LUCI_BACKUP_PATH, oldbackup)
				except:
					sys.stderr.write('Unable to rename the existing backup file.\n')
					sys.stderr.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:
		sys.stderr.write('Unable to open \"' + LUCI_BACKUP_PATH + '\" to write backup.\n')
		sys.stderr.write('The Luci backup failed.\n')
		sys.exit(1)

	try:
		os.chmod(LUCI_BACKUP_PATH, 0600)
	except OSError, e:
		sys.stderr.write('An error occurred while making \"' + LUCI_BACKUP_PATH + '\" read-only: '  + e + '\n')
		sys.stderr.write('Please check that this file is not world-readable.\n')

	try:
		f.write(doc.toprettyxml())
		f.close()
	except:
		sys.stderr.write('The Luci backup failed.\n')
		sys.exit(1)

	print 'Luci backup was successful.\nThe backup data is contained in the file \"' + LUCI_BACKUP_PATH + '\"'


def restore(argv):
	print 'Restoring the Luci server...'

	try:
		os.umask(077)
	except:
		pass

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

	if restore_luci_db_fsattr():
		return False

	return ret


def luci_help(argv):
    print 'Usage:'
    print argv[0] + ' [init|backup|restore|password|help]'
    print
    print '\tinit: initialize Luci site'
    print '\tpassword: reset admin password'
    print '\t\t--random: reset admin password to random value (disable account)'
    print '\tbackup: backup Luci site to a file'
    print '\trestore: restore Luci site from backup'
    print '\thelp: this help message'
    print



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:
	   sys.stderr.write('There is a problem with luci installation!\n')
	   sys.stderr.write('Mising luci\'s system account and group')
	   sys.stderr.write('Recommended action: reinstall luci\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:
        sys.stderr.write('Unknown command\n\n')
        luci_help(argv)
        sys.exit(1)


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