#!/usr/bin/python2.6
# @package      hubzero-openldap
# @file         hzldap
# @author       David R. Benham <dbenham@purdue.edu>
# @copyright    Copyright (c) 2012-2013 HUBzero Foundation, LLC.
# @license      http://www.gnu.org/licenses/lgpl-3.0.html LGPLv3
#
# Copyright (c) 2012-2013 HUBzero Foundation, LLC.
#
# This file is part of: The HUBzero(R) Platform for Scientific Collaboration
#
# The HUBzero(R) Platform for Scientific Collaboration (HUBzero) is free
# software: you can redistribute it and/or modify it under the terms of
# the GNU Lesser General Public License as published by the Free Software
# Foundation, either version 3 of the License, or (at your option) any
# later version.
#
# HUBzero is distributed in the hope that it will be useful,

# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
# HUBzero is a registered trademark of HUBzero Foundation, LLC.
#
import argparse
import base64
import ConfigParser
import hashlib
import hubzero.data.db
import hubzero.utilities.misc
import hubzero.utilities.user
import hubzero.config.passwords
import hubzero.config.webconfig
import ldap
import ldap.modlist
import os
import re
import subprocess
import sys
import traceback


def _write_nslcd_conf(suffix, searchAcctDN, searchAcctPW, adminAcctDN):
	
	fileText = """

# /etc/nslcd.conf
# nslcd configuration file. See nslcd.conf(5)
# for details.

# The user and group nslcd should run as.
uid nslcd
gid nslcd

# The location at which the LDAP server(s) should be reachable.
uri ldap://localhost/

# The search base that will be used for all queries.
base %suffix%

# The LDAP protocol version to use.
ldap_version 3

# The DN to bind with for normal lookups.
binddn %searchAcctDN%
bindpw %searchAcctPW%

# The DN used for password modifications by root.
#rootpwmoddn %adminAcctDN% 

# SSL options
#ssl off
#tls_reqcert demand
#tls_cacertfile /etc/ssl/certs/ca-bundle.crt

# The search scope.
#scope sub

# initial server connection time limit (default 30 s is too long)
bind_timelimit 1

# Search timelimit.
timelimit 5

# Reconnect policy.
reconnect_sleeptime 0

# The search scope.
#scope sub

base passwd ou=users,%suffix%
base shadow ou=users,%suffix%
base group ou=groups,%suffix%
scope passwd one
scope group one
scope shadow one
filter shadow (&(objectClass=posixAccount)(host=web))
pam_authz_search (&(objectClass=posixAccount)(uid=$username)(host=web))

"""

	filename = "/etc/nslcd.conf"

	fileText = fileText.replace("%suffix%", suffix)
	fileText = fileText.replace("%searchAcctDN%", searchAcctDN)
	fileText = fileText.replace("%searchAcctPW%", searchAcctPW)
	fileText = fileText.replace("%adminAcctDN%", adminAcctDN)
	
	f1 = open(filename, 'w')
	f1.write(fileText)
	f1.close();
	
	hubzero.utilities.misc.exShellCommand(['chmod', '0640', filename])
	hubzero.utilities.misc.exShellCommand(['chown', 'root.nslcd', filename])


def _write_nsswitch_conf():
	
	fileText = """# /etc/nsswitch.conf
#
# Example configuration of GNU Name Service Switch functionality.
# If you have the `glibc-doc-reference' and `info' packages installed, try:
# `info libc "Name Service Switch"' for information about this file.

passwd:         compat ldap
group:          compat ldap
shadow:         compat ldap

hosts:          files dns
networks:       files

protocols:      db files
services:       db files
ethers:         db files
rpc:            db files

netgroup:       nis	
"""

	filename = "/etc/nsswitch.conf"

	f1 = open(filename, 'w')
	f1.write(fileText)
	f1.close();
	
	hubzero.utilities.misc.exShellCommand(['chmod', '0644', filename])
	hubzero.utilities.misc.exShellCommand(['chown', 'root.root', filename])




def getLDAPSiteSuffix():
	
	rc, procOutput, procErr = exShellCommand(['slapcat', '-b', 'cn=config', '-a', '(objectClass=olcDatabaseConfig)'])
	fullSlapcatOutput = procOutput
	
	# get the olcSuffix
	matchObj = re.search(r'olcSuffix: (.*)$' , fullSlapcatOutput, re.M)
	if matchObj:
		suffix = matchObj.group(1)
	else:
		raise Exception("cannot find olcSuffix")

	return suffix


def getAlphaNumericPW():
	validPW = False
	while not validPW:
		print "Enter user password and press return"
		pw = raw_input("Enter an password")
	
		p = re.compile("[a-zA-Z0-9]*")
		m = p.match(pw)
	
		if not m:
			print "Password invalid - must be alphanumeric"
		else:
			validPW = True

	return pw

def exShellCommand(argArray, processInput=''):
	""" Wrapper to run and return output of a shell command """
	
	try:
		proc = subprocess.Popen(argArray,
			                    shell=False,
			                    stdin=subprocess.PIPE,
			                    stdout=subprocess.PIPE,
			                    stderr=subprocess.PIPE)
	
		procStdOut, procStdErr = proc.communicate(processInput)
		rc = proc.returncode

	except Exception, ex:	
		raise Exception('exShellCommand error ' + str(argArray) + ' ' + str(ex) + "\n" + traceback.format_exc())

	return rc, procStdOut, procStdErr

 
def removeOlcAccessEntry(dbnum, dbtype):
	"""Remove any previous olcAccess entires for specified database"""
	
	ldifData = ("dn: olcDatabase={%s}" + dbtype + ",cn=config\n"
	            "changetype: modify\n"
	            "delete: olcAccess\n"
              "-\n"
              "delete: olcTLSCertificateFile\n"
              "-\n"
              "delete: olcTLSCertificateKeyFile")

	ldifData = ldifData % dbnum

	rc, procOutput, procError = exShellCommand(['ldapmodify', '-Y', 'EXTERNAL', '-H', 'ldapi:///'], ldifData)
	if rc: 
		print procOutput.rstrip() 
		print procError.rstrip()


def change_olcSuffix(dbnum, dbtype, newSuffix):
	"""Modify the olcSuffix for the specified DB number"""
	
	ldifData = ("dn: olcDatabase={%s}" + dbtype + ",cn=config\n"
	            "changetype: modify\n"
	            "replace: olcSuffix\n"
	            "olcSuffix: %s\n"
	            "-\n"
	            "replace: olcRootDN\n"
	            "olcRootDN: cn=Manager,%s")

	ldifData = ldifData % (dbnum, newSuffix, newSuffix)

	rc, procOutput, procError = exShellCommand(['ldapmodify', '-Y', 'EXTERNAL', '-H', 'ldapi:///'], ldifData)
	if rc: 
		print procOutput 
		print procError
	else: 
		print procOutput


def remove_olcRootPW(dbnum, dbtype):
	"""Remove the olcRootPW entry for the specified DB number"""
	
	ldifData = ("dn: olcDatabase={%s}" + dbtype + ",cn=config\n"
	            "changetype: modify\n"
	            "delete: olcRootPW")

	ldifData = ldifData % (dbnum)
	
	rc, procOutput, procError = exShellCommand(['ldapmodify', '-Y', 'EXTERNAL', '-H', 'ldapi:///'], ldifData)
	if rc: 
		print procOutput 
		print procError
	else: 
		print procOutput


def getDBNumber():
	
	rc, procOutput, procErr = exShellCommand(['slapcat', '-b', 'cn=config', '-a', '(objectClass=olcDatabaseConfig)'])
		
	# find the number of the min olcDatabase entries that has the format bdb 
	minbdbDBNumber = 999
	for m in re.finditer("olcDatabase=\{(\d+)\}bdb", procOutput):
		if int(m.group(1)) < minbdbDBNumber:
			minbdbDBNumber = int(m.group(1))

	# find the number of the min olcDatabase entries that has the format hdb 
	minhdbDBNumber = 999
	for m in re.finditer("olcDatabase=\{(-?\d+)\}hdb", procOutput):
		if int(m.group(1)) < minhdbDBNumber:
			minhdbDBNumber = int(m.group(1))

	# This way, if all databses use hdb or all use bdb, or even if there is a mix
	# we take the lowest (first) one
	if minbdbDBNumber <	minhdbDBNumber:
		return minbdbDBNumber, 'bdb'
	else:
		return minhdbDBNumber, 'hdb'


def addOlcAccessEntries(dbnum, dbtype, suffix):

	# our access rules	
	ldifData = ('dn: olcDatabase={%1}' + dbtype + ',cn=config\n'
		            'changetype: modify\n'
                'delete: olcAccess\n'
                '-\n'
		            'add: olcAccess\n'
		            'olcAccess: {0}to dn.one="%2"\n'
		            '  filter=(objectClass=simpleSecurityObject) attrs=userPassword\n'
		            '  by dn="cn=admin,%2" write\n'
		            '  by dn="cn=syncuser,%2" read\n'
		            '  by anonymous auth\n'
		            '  by * none\n'
		            'olcAccess: {1}to dn.one="ou=users,%2"\n'
		            '  filter=(objectClass=posixAccount) attrs=userPassword\n'
		            '  by dn="cn=admin,%2" write\n'
		            '  by dn="cn=syncuser,%2" read\n'
		            '  by anonymous auth\n'
		            '  by * none\n'
		            'olcAccess: {2}to dn.one="ou=users,%2"\n'
		            '  filter=(objectClass=posixAccount)\n'
		            '  attrs=entry,objectClass,cn,gecos,gidNumber,homeDirectory,loginShell,uid,uidNumber,host\n'
		            '  by dn="cn=admin,%2" write\n'
		            '  by dn="cn=syncuser,%2" read\n'
		            '  by dn="cn=search,%2" read\n'
		            '  by self read\n'
		            '  by * none\n'
		            'olcAccess: {3}to dn.one="ou=users,%2"\n'
		            '  filter=(&(objectClass=posixAccount)(objectClass=shadowAccount))\n'
		            '  attrs=shadowExpire,shadowFlag,shadowInactive,shadowLastChange,shadowMax,shadowMin,shadowWarning\n'
		            '  by dn="cn=admin,%2" write\n'
		            '  by dn="cn=syncuser,%2" read\n'
		            '  by dn="cn=search,%2" read\n'
		            '  by * none\n'
		            'olcAccess: {4}to dn.one="ou=users,%2"\n'
		            '  filter=(objectClass=posixAccount)\n'
		            '  by dn="cn=admin,%2" write\n'
		            '  by dn="cn=update,%2" read\n'
		            '  by * none\n'
		            'olcAccess: {5}to dn.one="ou=groups,%2"\n'
		            '  filter=(objectClass=posixGroup)\n'
		            '  attrs=entry,objectClass,cn,gidNumber,memberUid,userPassword\n'
		            '  by dn="cn=admin,%2" write\n'
		            '  by dn="cn=syncuser,%2" read\n'
		            '  by dn="cn=search,%2" read\n'
		            '  by * none\n'
		            'olcAccess: {6}to dn.one="ou=groups,%2"\n'
		            '  filter=(objectClass=posixGroup)\n'
		            '  by dn="cn=admin,%2" write\n'
		            '  by dn="cn=syncuser,%2" read\n'
		            '  by * none\n'
		            'olcAccess: {7}to dn.subtree="%2"\n'
		            '  by dn="cn=admin,%2" write\n'
		            '  by dn="cn=syncuser,%2" read\n'
		            '  by dn="cn=search,%2" search\n'
		            '  by * none')
	
	ldifData =  ldifData.replace('%1', str(dbnum))
	ldifData =  ldifData.replace('%2', suffix)
	
	#print "olcAccess" + ldifData
	
	rc, procOutput, procError = exShellCommand(['ldapmodify', '-Y', 'EXTERNAL', '-H', 'ldapi:///'], ldifData)
	if rc: 
		print procError
		print procOutput 



def _init_ldap(args):
	
# prompt for passwords or create from scratch

	if not args.promptforadminpw:
		adminUserpw = hubzero.config.passwords.generateAlphaNumPassword(10)
	else:
		adminUserpw = getAlphaNumericPW()
	
	if not args.promptforsearchpw:
		searchUserpw = hubzero.config.passwords.generateAlphaNumPassword(10)
	else:
		searchUserpw = getAlphaNumericPW()
	
	if not args.promptforsyncuserpw:
		syncUserpw = hubzero.config.passwords.generateAlphaNumPassword(10)
	else:
		syncUserpw = getAlphaNumericPW()
	

	print "getting current info via slapcat"
	rc, procOutput, procErr = exShellCommand(['slapcat', '-b', 'cn=config', '-a', '(objectClass=olcDatabaseConfig)'])
	
	# Well use this later
	fullSlapcatOutput = procOutput
	
	# find the number of the max olcDatabase entries
	maxDBNumber, dbtype = getDBNumber()
	
	# find the oldDbDirectory for the max db 
	rc, procOutput, procErr = exShellCommand(['slapcat', '-b', 'cn=config', '-a', '(&(objectClass=olcDatabaseConfig)(olcDatabase={' + str(maxDBNumber) + '}' + dbtype + '))'])
	
	matchObj = re.search(r'olcDbDirectory: (.*)$' , procOutput, re.M)
	if matchObj:
		print 'found olcDbDirectory for for olcDatabase={' + str(maxDBNumber) + '}' + dbtype + ': ' + matchObj.group(1)
		configFileDir = matchObj.group(1)
	else:
		print 'Error, cannot find an entry for olcDbDirectory for (olcDatabase={' + str(maxDBNumber) + '}' + dbtype + ')'
		return(1)
	
	# See if this file exists for this database
	configFileName = configFileDir + '/DB_CONFIG'
	print "checking for " + configFileName

	if not os.path.exists(configFileName):
		print configFileDir + "/DB_CONFIG file not present"
	
		# add the data to ldap
		ldifData = ("dn: olcDatabase={"+ str(maxDBNumber) +"}" + dbtype + ",cn=config\n"
		"changetype: modify\n"
		"add: olcDbConfig\n"
		"olcDbConfig: set_cachesize 0 268435456 1\n"
		"olcDbConfig: set_lg_regionmax 262144\n"
		"olcDbConfig: set_lg_bsize 2097152")
	
		rc, procOutput, procError = exShellCommand(['ldapmodify', '-Y', 'EXTERNAL', '-H', 'ldapi:///'], ldifData)
		if rc: 
			print procOutput 
			print procError
		else: 
			print procOutput

		# restarting the server should generate the file we were looking for from the attribute mods we did above
		rc, procOutput, procError = exShellCommand(['service', 'slapd', 'restart'])
	else:
		print configFileDir + '/DB_CONFIG' " exists, no need to create"

	
	# See if our ldap db is empty
	rc, procOutput, procErr = exShellCommand(['slapcat', '-n', str(maxDBNumber)])
	#if procOutput:
	#	print "Current DB does not appear to be empty"
	#	print procOutput
	#	exit(1)

	# get the olcSuffix
	matchObj = re.search(r'olcSuffix: (.*)$' , fullSlapcatOutput, re.M)
	if matchObj:
		print "found olcSuffix: " + matchObj.group(1)
		suffix = matchObj.group(1)
	else:
		print "Error, cannot find olcSuffix"
		exit(1)	

	ldapManagerUserDN = "cn=Manager," + suffix

	# Since the database was empty, we're assuming it's ok to clear out any existing rules
	print "Removing olcAccess entires."
	removeOlcAccessEntry(maxDBNumber, dbtype)
	print "Adding olcAccess entires."
	addOlcAccessEntries(maxDBNumber, dbtype, suffix)


	# if olcRootPW doesn't exist, make it
	if fullSlapcatOutput.find('olcRootPW:') == -1:

		print "olcRootPW already exists, adding it"
		
		# create a root user for the new database to allow remote connections
		ldifData = ("dn: olcDatabase={%s}" + dbtype + ",cn=config\n"
		            "changetype: modify\n"
		            "replace: olcRootDN\n"
		            "olcRootDN: %s\n"
		            "-\n"
		            "add: olcRootPW\n"
		            "olcRootPW: %s")

	else:

		print "olcRootPW already exists, changing it"

		# create a root user for the new database to allow remote connections
		ldifData = ("dn: olcDatabase={%s}" + dbtype + ",cn=config\n"
	                "changetype: modify\n"
	                "replace: olcRootDN\n"
	                "olcRootDN: %s\n"
	                "-\n"
	                "replace: olcRootPW\n"
	                "olcRootPW: %s")
		
	ldapPWHash = "{MD5}" + base64.encodestring(hashlib.md5(str(adminUserpw)).digest())
	ldifData = ldifData % (maxDBNumber, ldapManagerUserDN, ldapPWHash)

	rc, procOutput, procError = exShellCommand(['ldapmodify', '-Y', 'EXTERNAL', '-H', 'ldapi:///'], ldifData)
	if rc: 
		print procError
		print procOutput 


	# Switch over to native ldap calls for the remainder of the setup using the above user and pw we just setup
	print "attempting bind to localhost"

	l = ldap.open("localhost")
	l.simple_bind_s(ldapManagerUserDN, adminUserpw)


	# create top level entry if it's not already there
	dn = suffix
	resultid = l.search(suffix, ldap.SCOPE_SUBTREE, '(&(objectclass=top)(objectclass=dcObject))', None)
	result_type, result_data = l.result(resultid, 0)
	if not result_data == []:
		print "top level entry already exists"
	else:
		attrs = {}
		attrs['objectclass'] = ['top', 'dcObject', 'organization']
		attrs['o'] = suffix
		ldif = ldap.modlist.addModlist(attrs)
		print "adding root container"
		print "top level entry ldif attributes: " 
		print ldif
		l.add_s(dn, ldif)
	

	# create admin
	dn = "cn=admin," + suffix
	resultid = l.search(suffix, ldap.SCOPE_SUBTREE, '(&(&(objectclass=organizationalRole)(objectclass=simpleSecurityObject))(cn=admin))', None)
	result_type, result_data = l.result(resultid, 0)
	if not result_data == []:
		print "admin user already exists, deleting and readding"
		l.delete_s(dn)
		
	attrs = {}
	attrs['objectclass'] = ['organizationalRole', 'simpleSecurityObject']
	attrs['cn'] = 'admin'
	attrs['userPassword'] = ldapPWHash = "{MD5}" + base64.encodestring(hashlib.md5(str(adminUserpw)).digest())
	ldif = ldap.modlist.addModlist(attrs)
	print "adding admin user"
	l.add_s(dn, ldif)


	# create syncuser
	dn = "cn=syncuser," + suffix
	resultid = l.search(suffix, ldap.SCOPE_SUBTREE, '(&(&(objectclass=organizationalRole)(objectclass=simpleSecurityObject))(cn=syncuser))', None)
	result_type, result_data = l.result(resultid, 0)
	
	if not result_data == []:
		print "syncuser user already exists, deleting and readding"
		l.delete_s(dn)
	
	attrs = {}
	attrs['objectclass'] = ['organizationalRole', 'simpleSecurityObject']
	attrs['cn'] = 'syncuser'
	attrs['userPassword'] = ldapPWHash = "{MD5}" + base64.encodestring(hashlib.md5(str(syncUserpw)).digest())
	ldif = ldap.modlist.addModlist(attrs)
	print "adding sync user"
	l.add_s(dn, ldif)


	# create search user
	dn = "cn=search," + suffix
	resultid = l.search(suffix, ldap.SCOPE_SUBTREE, '(&(&(objectclass=organizationalRole)(objectclass=simpleSecurityObject))(cn=search))', None)
	result_type, result_data = l.result(resultid, 0)
	if not result_data == []:
		print "search user already exists, deleting and readding"
		l.delete_s(dn)
	
	attrs = {}
	attrs['objectclass'] = ['organizationalRole', 'simpleSecurityObject']
	attrs['cn'] = 'search'
	attrs['userPassword'] = ldapPWHash = "{MD5}" + base64.encodestring(hashlib.md5(str(searchUserpw)).digest())
	ldif = ldap.modlist.addModlist(attrs)
	print "adding search user"
	l.add_s(dn, ldif)


	# add users ou
	dn = "ou=users," + suffix
	resultid = l.search(suffix, ldap.SCOPE_SUBTREE, '(&(objectclass=organizationalUnit)(ou=users))', None)
	result_type, result_data = l.result(resultid, 0)
	if not result_data == []:
		print "ou=users already exists"
	else:
		attrs = {}
		attrs['objectclass'] = ['organizationalUnit']
		attrs['ou'] = 'users'
		attrs['description'] = 'Users container'
		ldif = ldap.modlist.addModlist(attrs)
		print "adding users root ou container"
		l.add_s(dn, ldif)


	# add groups ou
	dn = "ou=groups," + suffix
	resultid = l.search(suffix, ldap.SCOPE_SUBTREE, '(&(objectclass=organizationalUnit)(ou=groups))', None)
	result_type, result_data = l.result(resultid, 0)
	if not result_data == []:
		print "ou=groups already exists"
	else:
		attrs = {}
		attrs['objectclass'] = ['organizationalUnit']
		attrs['ou'] = 'groups'
		attrs['description'] = 'Groups container'
		ldif = ldap.modlist.addModlist(attrs)
		print "adding groups root ou container"
		l.add_s(dn, ldif)
	
	
	print "unbinding from ldap " + suffix
	l.unbind();


	# update the jos_components table record for the com_system component to add the ldap config data 
	hubzero.config.webconfig.addComponentParam("com_system", "ldap_managerdn" , "cn=admin," + suffix)
	hubzero.config.webconfig.addComponentParam("com_system", "ldap_managerpw" , adminUserpw)
	hubzero.config.webconfig.addComponentParam("com_system", "ldap_basedn" , suffix)

	hubzero.config.webconfig.addComponentParam("com_system", "ldap_searchdn" , "cn=search," + suffix)
	hubzero.config.webconfig.addComponentParam("com_system", "ldap_searchpw" , searchUserpw)


	# write the nslcd.conf file
	print "writing out /etc/nslcd.conf"
	_write_nslcd_conf(suffix, "cn=search," + suffix, searchUserpw, "cn=admin," + suffix)
	
	# write the /etc/nsswitch.conf file
	print "writing out /etc/nsswitch.conf"
	_write_nsswitch_conf()

	# restart nslcd
	print "restarting nslcd"
	if os.path.exists('/etc/init.d/nslcd'):
		rc, procOutput, procError = exShellCommand(['/etc/init.d/nslcd', 'restart'])
		print procOutput.strip()
		if rc: 
			print procError


	# add hubrepo password to hubzero.secrets file, if file is not there, not sure what to do, just skip for now
	hubzeroSecretsFilename = "/etc/hubzero.secrets"
	if os.path.exists(hubzeroSecretsFilename):
		secretsConfig = ConfigParser.ConfigParser()
		secretsConfig.optionxform = str
		f1 = open(hubzeroSecretsFilename, "r")
		secretsConfig.readfp(f1)
		f1.close()
		
		secretsConfig.set("DEFAULT", "LDAP-ADMINPW", adminUserpw)
		secretsConfig.set("DEFAULT", "LDAP-SEARCHPW", searchUserpw)
		secretsConfig.set("DEFAULT", "LDAP-SYNCPW", syncUserpw)

		f2 = open(hubzeroSecretsFilename, "w")
		secretsConfig.write(f2)
		f2.close()


	print "\nldap default setup complete"
	print "ldap admin user pw: " + adminUserpw
	print "ldap search user pw: " + searchUserpw
	print "ldap sync user pw: " + syncUserpw
	
	print "ldap config written to jos_components com_system component params field on database: " + hubzero.config.webconfig.getDefaultSite()

	return(0)
	


def _delete_ldap_user(args):
	hubzero.utilities.user._delete_ldap_user(args.username)

def _add_ldap_user(args):
	
	username = args.username
	suffix = getLDAPSiteSuffix()
	
	if not args.promptforpw:
		pw = hubzero.config.passwords.generateAlphaNumPassword(10)
	else:
		pw = getAlphaNumericPW()	

	hubzero.utilities.user._add_ldap_user(username, pw, args.uidnumber, 'none')
	
	print "new user pw: " + pw


def _delete_ldap_group(args):
	pass


def _add_ldap_group(groupName, groupDescription, gidNumber):
	pass

def _sync_ldap_users(args):
	dbPW = hubzero.config.webconfig.getWebConfigDBPassword()
	dbUserName = hubzero.config.webconfig.getWebConfigDBUsername()
	dbName = hubzero.config.webconfig.getWebConfigDBName()
	db =  hubzero.data.db.MySQLConnection("localhost", dbName, dbUserName, dbPW)

	# grab all users from db 
	sql = "SELECT `username`, `name`, `userPassword`, `uidNumber`, `homeDirectory`, `loginShell`, `gid`, gidNumber from jos_xprofiles"

	for user in db.query(sql, None):
		hubzero.utilities.user.add_ldap_user(user.username, 
		                                     user.name, 
		                                     user.userPassword, 
		                                     user.uidNumber, 
		                                     user.homeDirectory, 
		                                     user.loginShell, 
		                                     user.gid, 
		                                     user.gidNumber)
	
	if args.addgroupmemberships:
		# get all user group memberships
		sql = "SELECT `p`.`username`, `g`.`cn` FROM jos_xgroups_members m JOIN jos_xprofiles p on (p.uidNumber = m.uidNumber) JOIN jos_xgroups g on (g.gidNumber = m.gidNumber)"

		for userGroupMembership in db.query(sql, None):
			print "adding " + userGroupMembership.username + " to " + userGroupMembership.cn
			hubzero.utilities.group.addusertogroup_ldap(userGroupMembership.username, userGroupMembership.cn)


def _sync_ldap_groups(args):
	dbPW = hubzero.config.webconfig.getWebConfigDBPassword()
	dbUserName = hubzero.config.webconfig.getWebConfigDBUsername()
	dbName = hubzero.config.webconfig.getWebConfigDBName()
	db =  hubzero.data.db.MySQLConnection("localhost", dbName, dbUserName, dbPW)

	# grab all groups from db 
	sql = "SELECT `cn`, `description`, gidNumber from jos_xgroups"
	
	for group in db.query(sql, None):
		hubzero.utilities.group.add_ldap_group(group.cn, group.description, group.gidNumber)


def _delete_ldap_users(args): 
	
	# by only deleting users in db, any users that exist only in ldap will be preserved
	if args.preserveorphans:
		dbPW = hubzero.config.webconfig.getWebConfigDBPassword()
		dbUserName = hubzero.config.webconfig.getWebConfigDBUsername()
		dbName = hubzero.config.webconfig.getWebConfigDBName()
		db =  hubzero.data.db.MySQLConnection("localhost", dbName, dbUserName, dbPW)
	
		# grab all users from db 
		sql = "SELECT `username`, `name`, `userPassword`, `uidNumber`, `homeDirectory`, `loginShell`, `gid`, gidNumber from jos_xprofiles"
		
		for user in db.query(sql, None):
			print "deleting " + user.username
			try:
				hubzero.utilities.user.delete_ldap_user(user.username)
			except ldap.NO_SUCH_OBJECT:
				print " user not found"

	else:
		# grab all users from ldap
		hubLDAPAcctMgrDN = hubzero.config.webconfig.getComponentParam("com_system", "ldap_managerdn")
		hubLDAPAcctMgrPW = hubzero.config.webconfig.getComponentParam("com_system", "ldap_managerpw")
		hubLDAPBaseDN = hubzero.config.webconfig.getComponentParam("com_system", "ldap_basedn")
		siteSuffix = hubzero.utilities.hzldap.getLDAPSiteSuffix()
		l = ldap.open("localhost")
		l.simple_bind_s(hubLDAPAcctMgrDN, hubLDAPAcctMgrPW)

		try:
			r = l.search_s("ou=users," + siteSuffix, ldap.SCOPE_SUBTREE, '(objectClass=account)', ['uid'])

			for dn,entry in r:
				uid = str(entry['uid'][0])
				hubzero.utilities.user.delete_ldap_user(uid)

		except ldap.NO_SUCH_OBJECT:
			print "No users in ou=users"
	


def _delete_ldap_groups(args):
	
	print args.preserveorphans
	
	# by only deleting groups in db, any ldap only groups will be untouched
	if args.preserveorphans:
		dbPW = hubzero.config.webconfig.getWebConfigDBPassword()
		dbUserName = hubzero.config.webconfig.getWebConfigDBUsername()
		dbName = hubzero.config.webconfig.getWebConfigDBName()
		db =  hubzero.data.db.MySQLConnection("localhost", dbName, dbUserName, dbPW)
	
		# grab all users groups db 
		sql = "SELECT `cn`, `gidNumber` from jos_xgroups"
		
		for group in query(sql, None):
			print "deleting " + group.cn

			try:
				hubzero.utilities.group.del_ldap_group(group[0])
			except ldap.NO_SUCH_OBJECT:
				print " group not found"

	else:
		# grab all groups from ldap
		hubLDAPAcctMgrDN = hubzero.config.webconfig.getComponentParam("com_system", "ldap_managerdn")
		hubLDAPAcctMgrPW = hubzero.config.webconfig.getComponentParam("com_system", "ldap_managerpw")
		hubLDAPBaseDN = hubzero.config.webconfig.getComponentParam("com_system", "ldap_basedn")
		siteSuffix = hubzero.utilities.hzldap.getLDAPSiteSuffix()
		
		l = ldap.open("localhost")
		l.simple_bind_s(hubLDAPAcctMgrDN, hubLDAPAcctMgrPW)
		
		try:
			r = l.search_s("ou=groups," + siteSuffix, ldap.SCOPE_SUBTREE, '(objectClass=posixGroup)', ['uid'])
	
			for dn,entry in r:
				uid = str(entry['uid'][0])
				hubzero.utilities.user.delete_ldap_user(uid)

		except ldap.NO_SUCH_OBJECT:
			print "No groups in ou=groups"
		



# #######################################################################
# Main

# main parser
parser = argparse.ArgumentParser(prog="hzldap")
subparsers = parser.add_subparsers()

parser_init = subparsers.add_parser('init', help='initialize hub ldap')
parser_init.add_argument("--promptforadminpw", help="Prompt for password", action="store_true", default=False)
parser_init.add_argument("--promptforsearchpw", help="Prompt for password", action="store_true", default=False)
parser_init.add_argument("--promptforsyncuserpw", help="Prompt for password", action="store_true", default=False)
parser_init.set_defaults(func=_init_ldap)

parser_adduser = subparsers.add_parser('adduser', help='add ldap user')
parser_adduser.add_argument("username", help="username")
parser_adduser.add_argument("--promptforpw", help='prompt for a user password, if not provided, one will be created and output and output to screen', action="store_true", default=False)
parser_adduser.add_argument("--uidnumber", help="Hub UserIDNumber", default="99999999")
parser_adduser.set_defaults(func=_add_ldap_user)

parser_deleteuser = subparsers.add_parser('deleteuser', help='delete ldap user')
parser_deleteuser.add_argument("username", help="username")
parser_deleteuser.set_defaults(func=_delete_ldap_user)

parser_addgroup = subparsers.add_parser('addgroup', help='add ldap group')
parser_addgroup.add_argument("groupname", help="groupname")
parser_addgroup.set_defaults(func=_add_ldap_group)

parser_deletegroup = subparsers.add_parser('deletegroup', help='delete ldap group')
parser_deletegroup.add_argument("groupname", help="groupname")
parser_deletegroup.set_defaults(func=_delete_ldap_group)

parser_syncusers = subparsers.add_parser('syncusers', help='add all users from db into ldap')
parser_syncusers.add_argument("--addgroupmemberships", help='add newly created users group memberships', action="store_true", default=False)
parser_syncusers.set_defaults(func=_sync_ldap_users)

parser_syncgroups = subparsers.add_parser('syncgroups', help='add all groups from db into ldap')
parser_syncgroups.set_defaults(func=_sync_ldap_groups)

parser_deleteusers = subparsers.add_parser('deleteusers', help='delete all users in ldap')
parser_deleteusers.add_argument("--preserveorphans", help='leave any user in ldap that is not in the database', action="store_true", default=False)
parser_deleteusers.set_defaults(func=_delete_ldap_users)

parser_deletegroups = subparsers.add_parser('deletegroups', help='delete all groups from ldap')
parser_deletegroups.add_argument("--preserveorphans", help='leave any group in ldap that is not in the database', action="store_true", default=False)
parser_deletegroups.set_defaults(func=_delete_ldap_groups)


args =  parser.parse_args()
rc = args.func(args)

exit(rc)

