#!/usr/bin/python

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

import sys, os, stat, select, string, pwd
from sys import stderr, argv
import types
import xml
import xml.dom
from xml.dom import minidom
	
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 i 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(i):
		__path__.append(i)

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

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):
	path = LUCI_PEERS_DIR + name
	if stat.S_ISREG(os.stat(path).st_mode):
		ssl_key_data.append({'id'   : path, 
				     'name' : 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 \"' + LUCI_USER + '\" user.\n'
		sys.stderr.write(msg)
		raise msg
	

def set_default_passwd_reset_flag():
	# set flag marking admin password has been set
	uid, gid = get_luci_uid_gid()
	open(LUCI_ADMIN_SET_PATH, 'w').write('True')
	os.chown(LUCI_ADMIN_SET_PATH, uid, gid)
	os.chmod(LUCI_ADMIN_SET_PATH, 0640)
	return True

def get_default_passwd_reset_flag():
	return open(LUCI_ADMIN_SET_PATH, 'r').read(16).strip() == 'True'


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 'Passwords 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 i in [ '.tmp', '.old', '.index', '.lock' ]:
			try:
				os.chown(LUCI_DB_PATH + i, uid, gid)
				os.chmod(LUCI_DB_PATH + i, 0600)
			except: pass
	except:
		sys.stderr.write('Unable to change ownership of the Luci database back to user \"' + LUCI_USER + '\"\n')
		return -1

def set_zope_passwd(user, passwd):
	sys.stderr = null
	import ZODB
	from ZODB.FileStorage import FileStorage
	from ZODB.DB import DB
	import OFS
	from OFS.Application import AppInitializer
	import OFS.Folder
	import AccessControl
	import AccessControl.User
	from AccessControl.AuthEncoding import SSHADigestScheme
	from AccessControl.SecurityManagement import newSecurityManager
	import transaction
	import Products.CMFCore
	import Products.CMFCore.MemberDataTool
	import App.ImageFile
	import Products.PluggableAuthService.plugins.ZODBUserManager
	import BTrees.OOBTree
	# 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 \"' + 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 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
	import ZODB
	from ZODB.FileStorage import FileStorage
	from ZODB.DB import DB
	import OFS
	from OFS.Application import AppInitializer
	import OFS.Folder
	import AccessControl
	import AccessControl.User
	from AccessControl.AuthEncoding import SSHADigestScheme
	from AccessControl.SecurityManagement import newSecurityManager
	import transaction
	import Products.CMFCore
	import Products.CMFCore.MemberDataTool
	import App.ImageFile
	import Products.PluggableAuthService.plugins.ZODBUserManager
	import BTrees.OOBTree
	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 = '__luci__:system'

		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 = '__luci__:cluster'
		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 = '__luci__:csystem:' + id
				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 @dict
def dataToXML(doc, dict, tltag):
	node = doc.createElement(tltag)
	for i in dict:
		if isinstance(dict[i], types.DictType):
			if i[-4:] == 'List':
				tagname = i
			else:
				tagname = tltag[:-4]
			temp = dataToXML(doc, dict[i], tagname)
			node.appendChild(temp)
		elif isinstance(dict[i], types.StringType) or isinstance(dict[i], types.IntType):
			node.setAttribute(i, str(dict[i]))
		elif isinstance(dict[i], types.ListType):
			if len(dict[i]) < 1:
				continue
			temp = doc.createElement(i)
			for x in 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
	import ZODB
	from ZODB.FileStorage import FileStorage
	from ZODB.DB import DB
	import OFS
	from OFS.Application import AppInitializer
	import OFS.Folder
	import AccessControl
	import AccessControl.User
	from AccessControl.AuthEncoding import SSHADigestScheme
	from AccessControl.SecurityManagement import newSecurityManager
	import transaction
	import Products.CMFCore
	import Products.CMFCore.MemberDataTool
	from CMFPlone.utils import getToolByName
	import App.ImageFile
	import Products.PluggableAuthService.plugins.ZODBUserManager
	import BTrees.OOBTree
	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 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 = {
		'userList': users,
		'systemList': systems,
		'clusterList': clusters
	}

	doc = xml.dom.minidom.Document()
	luciData = doc.createElement('luci')
	doc.appendChild(luciData)
	dataNode = dataToXML(doc, backup, '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, command + " can not be run"

    (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.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'
	password = read_passwd('Enter password: ', 'Confirm password: ')
	print '\nPlease wait...'
	if not set_zope_passwd('admin', 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):
	password = None
	if '--random' in argv:
		print 'Resetting the admin user\'s password to some random value\n'
		try:
			rand = open('/dev/urandom', 'r')
			password = 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'
		password = read_passwd('Enter new password: ', 'Confirm password: ')
		
	print '\nPlease wait...'
	if not set_zope_passwd('admin', password):
		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 not doc:
		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.
		stat = 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.stderr('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)
    
    # only root should run this
    if os.getuid() != 0:
        sys.stderr.write('Only \'root\' can run ' + argv[0] + '\n')
        sys.stderr.write('Try again with root privileges.\n')
        sys.exit(2)

    # test if luci installation is OK
    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)
