#!/usr/bin/python
# @package      hubzero-openldap
# @file         hzldap
# @copyright    Copyright (c) 2012-2020 The Regents of the University of California.
# @license      http://opensource.org/licenses/MIT MIT
#
# Copyright (c) 2012-2020 The Regents of the University of California.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
# HUBzero is a registered trademark of The Regents of the University of California.
#
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
import socket
import grp
from distutils.version import LooseVersion, StrictVersion

hubname = hubzero.config.webconfig.getDefaultSite()
docroot = hubzero.config.hubzerositeconfig.getHubzeroConfigOption(hubname, "DocumentRoot")

version = hubzero.config.webconfig.getCMSversion(docroot)

if StrictVersion(version) >= StrictVersion("1.2.0"):
    hubzero.utilities.misc.JOOMLA_25 = True
else:
    hubzero.utilities.misc.JOOMLA_25 = False

global_hubnname = hubname
global_documentroot = docroot
global_adminuserdn = hubzero.config.hubzerositeconfig.getHubzeroConfigOption(hubname, "ldap.adminuserdn")
global_searchuserdn = hubzero.config.hubzerositeconfig.getHubzeroConfigOption(hubname, "ldap.searchuserdn")
global_syncuserdn = hubzero.config.hubzerositeconfig.getHubzeroConfigOption(hubname, "ldap.syncuserdn")
global_basedn = hubzero.config.hubzerositeconfig.getHubzeroConfigOption(hubname, "ldap.basedn")
global_uri = hubzero.config.hubzerositeconfig.getHubzeroConfigOption(hubname, "ldap.uri")

def service(service, action = 'start'):

    if os.path.exists('/sbin/service'):
        serviceCmd = '/sbin/service'
    elif os.path.exists('/usr/sbin/service'):
        serviceCmd = '/usr/sbin/service'

    try:
        print serviceCmd + ' ' + service + " " + action 
        rc, procStdOut, procStdErr = hubzero.utilities.misc.exShellCommand([serviceCmd, service, action])
    except:
        print procStdErr
        print procStdOut
        print serviceCmd + ' ' + service + " " + action + " failed"
        return -1

    if rc : print procStdErr

    return rc


def replace(path, regexp, replace = None):

    if replace == None or replace == False:
        replace = ''

    if os.path.exists(path):
        with open(path, 'r+') as f:
            ftxt = f.read()
            txt = ftxt.split("\n")

            for i in range(0,len(txt)):
                txt[i] = re.sub(regexp, replace, txt[i])

            txt = '\n'.join(txt)

            if txt != ftxt:
                f.seek(0)
                f.write(txt)
                f.truncate()
                return 1

            return 0


def lineinfile(path, line, regexp = None, insertbefore = None, insertafter = None, create = False, ifexists = False, state = True):
    """search a file for a line, and ensure that it is present or absent.

    Keyword arguments:
    path            -- the file to modify
    line            -- the line to insert/replace into the file
    regexp          -- the regular expression to look for in the file
    insertbefore    -- the line will be inserted before the last match of specified regular expression
    insertafter     -- the line will be inserted after the last match of specified regular expression
    create          -- if true the file will be created if it does not already exist
    state           -- whether the line should be there (true) or not (false)
    ifexists        -- whether to run only if file exists

    Returns:
    0 - No change required
    1 - Changes made
    """

    if insertbefore != None and insertafter != None:
        raise ValueError('invalid argument combination: insertbefore and insertafter')

    if (insertbefore != None or insertafter != None) and not state:
        raise ValueError('invalid argument combination: insertbefore or insertafter with state == false')

    if (not os.path.exists(path)) and create:
        with open(path, 'w+') as f:
            if line != None and state:
                f.write(line)
        return 1

    if (not os.path.exists(path)) and ifexists:
        return 0

    with open(path, 'r+') as f:
        ftxt = f.read()
        txt = ftxt.split("\n")
        regexp_match = -1
        insertafter_match = -1
        insertbefore_match = -1
        line_match = -1

        for i in range(0,len(txt)):
            if regexp == None and txt[i] == line and state:
                return 0

            if txt[i] == line:
                line_match = i
            if (regexp != None and re.match(regexp, txt[i])):
                regexp_match = i
            if (insertafter != None and insertafter  != 'EOF' and re.match(insertafter,  txt[i])):
                insertafter_match = i
            if (insertbefore != None and insertbefore != 'BOF' and re.match(insertbefore, txt[i])):
                insertbefore_match= i

        if state:
            if (regexp_match != -1 and insertafter == None and insertbefore == None):
                txt[regexp_match] = line
            elif (regexp_match != -1):
                return 0
            elif (insertbefore == 'BOF'):
                txt.insert(0, line)
            elif (insertafter == 'EOF'):
                txt.append(line)
            elif (insertafter_match != -1):
                txt.insert(insertafter_match + 1, line)
            elif (insertbefore_match != -1):
                txt.insert(insertbefore_match + 1, line)
            elif (insertafter != None):
                txt.append(line)
            elif (insertbefore != None):
                txt.append(line)
            else:
                txt.append(line)
        else:
            if regexp_match != -1:
                txt.pop(regexp_match)
            elif line_match != -1:
                txt.pop(line_match)

        txt = '\n'.join(txt)

        if txt != ftxt:
            f.seek(0)
            f.write(txt)
            f.truncate()
            return 1

        return 0


def _configure_hubzero_conf(args):
    if not os.path.exists("/etc/hubzero.conf"):
        print("Unable to configure hubzero.conf. /etc/hubzero.conf does not exist")
        return 1

    if global_basedn == None or global_basedn == '':
        print("Unable to configure hubzero.conf. basedn not defined")
        return 2

    if global_uri == None or global_uri == '':
        print("Unable to configure hubzero.conf. uri not defined")
        return 3

    configure_hubzero_conf(global_basedn, global_uri)

def configure_hubzero_conf(suffix, uri):

    if not os.path.exists("/etc/hubzero.conf"):
        print("Unable to configure hubzero.conf. /etc/hubzero.conf does not exist")
        return 1
    
    adminuserDN = hubzero.config.hubzerositeconfig.getHubzeroConfigOption(hubname, 'ldap.adminuserDN')

    if adminuserDN == None or adminuserDN == '':

        adminuserDN = hubzero.config.hubzerositeconfig.getHubzeroConfigOption('ldap', 'adminuserDN')

        if adminuserDN != None and adminuserDN != '':
            adminuserDN = "cn=admin" + suffix

    hubzero.config.hubzerositeconfig.setHubzeroConfigOption(hubname, 'ldap.adminuserDN', adminuserDN)

    searchuserDN = hubzero.config.hubzerositeconfig.getHubzeroConfigOption(hubname, 'ldap.searchuserDN')

    if searchuserDN == None or searchuserDN == '':

        searchuserDN = hubzero.config.hubzerositeconfig.getHubzeroConfigOption('ldap', 'searchuserDN')

        if searchuserDN == None or searchuserDN == '':
            searchuserDN = "cn=search" + suffix

    hubzero.config.hubzerositeconfig.setHubzeroConfigOption(hubname, 'ldap.searchuserDN', searchuserDN)
    
    syncuserDN = hubzero.config.hubzerositeconfig.getHubzeroConfigOption(hubname, 'ldap.syncuserDN')

    if syncuserDN == None or syncuserDN == '':

        syncuserDN = hubzero.config.hubzerositeconfig.getHubzeroConfigOption('ldap', 'syncuserDN')

        if syncuserDN == None or syncuserDN == '':
            syncuserDN = "cn=sync" + suffix

    hubzero.config.hubzerositeconfig.setHubzeroConfigOption(hubname, 'ldap.syncuserDN', syncuserDN)
    
    baseDN = hubzero.config.hubzerositeconfig.getHubzeroConfigOption(hubname, 'ldap.baseDN')

    if baseDN == None or baseDN == '':

        baseDN = hubzero.config.hubzerositeconfig.getHubzeroConfigOption('ldap', 'baseDN')

        if baseDN == None or baseDN == '':
            baseDN = suffix

    hubzero.config.hubzerositeconfig.setHubzeroConfigOption(hubname, 'ldap.baseDN', baseDN)

    uri = hubzero.config.hubzerositeconfig.getHubzeroConfigOption(hubname, 'ldap.uri')

    if uri == None or uri == '':

        uri = hubzero.config.hubzerositeconfig.getHubzeroConfigOption('ldap', 'uri')

        if uri == None or uri == '':
            uri = "ldap://" + socket.getaddrinfo(socket.gethostname(), 0, 0, 0, 0, socket.AI_CANONNAME)[0][3]

    hubzero.config.hubzerositeconfig.setHubzeroConfigOption(hubname, 'ldap.uri', uri)

    # /etc/hubzero.conf is world readable, do not store secrets here
    hubzero.config.hubzerositeconfig.removeHubzeroConfigOption('ldap', 'adminuserPW') 
    hubzero.config.hubzerositeconfig.removeHubzeroConfigOption('ldap', 'searchuserPW') 
    hubzero.config.hubzerositeconfig.removeHubzeroConfigOption('ldap', 'syncuserPW') 
    
    # hubzero.config.hubzerositeconfig.removeHubzeroConfigOption('ldap', 'adminuserDN') 
    # hubzero.config.hubzerositeconfig.removeHubzeroConfigOption('ldap', 'searchuserDN') 
    # hubzero.config.hubzerositeconfig.removeHubzeroConfigOption('ldap', 'basedn') 
    # hubzero.config.hubzerositeconfig.removeHubzeroConfigOption('ldap', 'uri') 

    # # update the jos_components table record for the com_system component
    # 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)

    

def _configure_ldap_conf(args):
    configure_ldap_conf(suffix,uri)

def configure_ldap_conf():

    if not os.path.exists('/etc/openldap/ldap.conf'):
        print("Unable to configure ldap.conf, /etc/openldap.conf does not exist")
        return 1

    lineinfile(path = '/etc/openldap/ldap.conf', regexp = r'^#*\s*base\s+.*$', line='base ' + suffix)
    lineinfile(path = '/etc/openldap/ldap.conf', regexp = r'^#*\s*uri\s+.*$',  line='base ' + uri)
        


def _configure_pam_ldap_conf(args):
    configure_pam_ldap_conf()

def configure_pam_ldap_conf():

    if not os.path.exists('/etc/pam_ldap.conf'):
        print("Unable to configure pam_ldap.conf, /etc/pam_ldap.conf does not exist")
        return 1

    lineinfile(path = '/etc/pam_ldap.conf', regexp = r'^#*\s*base\s+.*$',            line='base ' + suffix)
    lineinfile(path = '/etc/pam_ldap.conf', regexp = r'^#*\s*uri\s+.*$',             line='base ' + uri)
    lineinfile(path = '/etc/pam_ldap.conf', regexp = r'^#*\s*binddn\s+.*$',          line='base ' + uri)
    lineinfile(path = '/etc/pam_ldap.conf', regexp = r'^#*\s*bindpw\s+.*$',          line='base ' + uri)
    lineinfile(path = '/etc/pam_ldap.conf', regexp = r'^#*\s*nss_base_passwd\s+.*$', line='nss_base_passwd ou=users,?one')
    lineinfile(path = '/etc/pam_ldap.conf', regexp = r'^#*\s*nss_base_shadow\s+.*$', line='nss_base_shadow ou=users,?one?objectClass=shadowAccount')
    lineinfile(path = '/etc/pam_ldap.conf', regexp = r'^#*\s*nss_base_group\s+.*$',  line='nss_base_group ou=groups,' + suffix + 'sub')
    


def _configure_sssd_conf(args):

    secretsConfig = ConfigParser.ConfigParser()
    secretsConfig.optionxform = str

    if os.path.exists("/etc/hubzero.secrets"):
        f1 = open("/etc/hubzero.secrets", "r")
        secretsConfig.readfp(f1)
        f1.close()

    suffix = hubzero.config.webconfig.getComponentParam("com_system", "ldap_basedn")
    adminAcctPW = secretsConfig.get("DEFAULT", "LDAP-ADMINPW")

    configure_sssd_conf(suffix, adminAcctPW)

def configure_sssd_conf(suffix, adminAcctPW):

        sssdConfConfig = ConfigParser.ConfigParser()
        sssdConfConfig.optionxform = str

        if os.path.exists("/etc/sssd/sssd.conf"):
            f1 = open("/etc/sssd/sssd.conf", "r")
            sssdConfConfig.readfp(f1)
            f1.close()

        if not sssdConfConfig.has_section("domain/default"):
            sssdConfConfig.add_section("domain/default")

        sssdConfConfig.set("domain/default", "autofs_provider", 'ldap')
        sssdConfConfig.set("domain/default", "cache_credentials", 'True')
        sssdConfConfig.set("domain/default", "id_provider", 'ldap')
        sssdConfConfig.set("domain/default", "auth_provider", 'ldap')
        sssdConfConfig.set("domain/default", "chpass_provider", 'ldap')
        sssdConfConfig.set("domain/default", "ldap_tls_cacertdir", '/etc/openldap/cacerts')

        if not sssdConfConfig.has_section("sssd"):
            sssdConfConfig.add_section("sssd")

        sssdConfConfig.set("sssd", "services", 'nss, pam, autofs')
        sssdConfConfig.set("sssd", "domains", 'default, ldap')
        sssdConfConfig.set("sssd", "config_file_version", '2')

        if not sssdConfConfig.has_section("nss"):
            sssdConfConfig.add_section("nss")

        sssdConfConfig.set("nss", "homedir_substring", '/home')
        sssdConfConfig.set("nss", "filter_groups", 'root')
        sssdConfConfig.set("nss", "filter_users", 'root')

        if not sssdConfConfig.has_section("domain/LDAP"):
            sssdConfConfig.add_section("domain/LDAP")

        sssdConfConfig.set("domain/LDAP", "enumerate", 'true')
        sssdConfConfig.set("domain/LDAP", "tls_reqcert", 'never')
        sssdConfConfig.set("domain/LDAP", "access_provider", 'ldap')
        sssdConfConfig.set("domain/LDAP", "ldap_user_search_base", "ou=users," + suffix)
        sssdConfConfig.set("domain/LDAP", "ldap_group_search_base", "ou=groups," + suffix)
        sssdConfConfig.set("domain/LDAP", "ldap_default_bind_dn", "cn=admin," + suffix)
        sssdConfConfig.set("domain/LDAP", "ldap_default_authtok_type", 'password')
        sssdConfConfig.set("domain/LDAP", "ldap_default_authtok", adminAcctPW)
        sssdConfConfig.set("domain/LDAP", "ldap_id_use_start_tls", 'False')
        sssdConfConfig.set("domain/LDAP", "ldap_auth_disable_tls_never_use_in_production", 'true')
        sssdConfConfig.set("domain/LDAP", "id_provider", 'ldap')
        sssdConfConfig.set("domain/LDAP", "ldap_uri", 'ldap://localhost')
        sssdConfConfig.set("domain/LDAP", "ldap_access_filter", 'host=web')

        f2 = os.open("/etc/sssd/sssd.conf", os.O_RDWR | os.O_CREAT, 0600)
        os.fchmod(f2, 0600)

        f3 = os.fdopen(f2,"w")
        sssdConfConfig.write(f3)
        f3.close()


def _configure_nslcd_conf(args):

    secretsConfig = ConfigParser.ConfigParser()
    secretsConfig.optionxform = str

    if os.path.exists("/etc/hubzero.secrets"):
        f1 = open("/etc/hubzero.secrets", "r")
        secretsConfig.readfp(f1)
        f1.close()

    suffix = hubzero.config.webconfig.getComponentParam("com_system", "ldap_basedn")
    searchAcctDN = "cn=search," + suffix
    searchAcctPW = secretsConfig.get("DEFAULT", "LDAP-SEARCHPW")
    adminAcctDN = "cn=admin,." + suffix
    configure_nslcd_conf(suffix, searchAcctDN, searchAcctPW, adminAcctDN)
    
def configure_nslcd_conf(suffix, searchAcctDN, searchAcctPW, adminAcctDN):

    if os.path.exists('/etc/nslcd.conf'):
        lineinfile(path = '/etc/nslcd.conf', regexp = r'^#*\s*uid\s+.*$',                 line='uid nslcd')
        lineinfile(path = '/etc/nslcd.conf', regexp = r'^#*\s*gid\s+.*$',                 line='gid ldap')
        lineinfile(path = '/etc/nslcd.conf', regexp = r'^#*\s*uri\s+.*$',                 line='uri ldap://localhost')
        lineinfile(path = '/etc/nslcd.conf', regexp = r'^#*\s*base\s+.*$',                line='base ' + suffix)
        lineinfile(path = '/etc/nslcd.conf', regexp = r'^#*\s*ldap_version\s+.*$',        line='ldap_version 3')
        lineinfile(path = '/etc/nslcd.conf', regexp = r'^#*\s*binddn\s+.*$',              line='binddn ' + searchAcctDN)
        lineinfile(path = '/etc/nslcd.conf', regexp = r'^#*\s*bindpw\s+.*$',              line='bindpw ' + searchAcctPW)
        lineinfile(path = '/etc/nslcd.conf', regexp = r'^#*\s*rootpwmoddn\s+.*$',         line='#rootpwmoddn ' + adminAcctDN)
        lineinfile(path = '/etc/nslcd.conf', regexp = r'^#*\s*ssl\s+.*$',                 line='#ssl off')
        lineinfile(path = '/etc/nslcd.conf', regexp = r'^#*\s*tls_reqcert\s+.*$',         line='#tls_reqcert demand')
        lineinfile(path = '/etc/nslcd.conf', regexp = r'^#*\s*tls_cacertfile\s+.*$',      line='#tls_cacertfile = /etc/ssl/certs/ca-bundle.crt')
        lineinfile(path = '/etc/nslcd.conf', regexp = r'^#*\s*scope\s+sub\s*$',           line='#scope sub')
        lineinfile(path = '/etc/nslcd.conf', regexp = r'^#*\s*bind_timelimit\s+.*$',      line='bind_timelimit 1')
        lineinfile(path = '/etc/nslcd.conf', regexp = r'^#*\s*timelimit\s+.*$',           line='timelimit 5')
        lineinfile(path = '/etc/nslcd.conf', regexp = r'^#*\s*reconnect_sleeptime\s+.*$', line='reconnect_sleeptime 0')
        lineinfile(path = '/etc/nslcd.conf', regexp = r'^\s*base passwd\s+.*$',           line='base passwd ou=users,' + suffix)
        lineinfile(path = '/etc/nslcd.conf', regexp = r'^\s*base shadow\s+.*$',           line='base shadow ou=users,' + suffix)
        lineinfile(path = '/etc/nslcd.conf', regexp = r'^\s*base group\s+.*$',            line='base group ou=groups,' + suffix)
        lineinfile(path = '/etc/nslcd.conf', regexp = r'^#*\s*scope passwd\s+.*$',        line='scope passwd one')
        lineinfile(path = '/etc/nslcd.conf', regexp = r'^#*\s*scope group\s+.*$',         line='scope group one')
        lineinfile(path = '/etc/nslcd.conf', regexp = r'^#*\s*scope shadow\s+.*$',        line='scope shadow one')
        lineinfile(path = '/etc/nslcd.conf', regexp = r'^\s*filter shadow\s+.*$',         line='filter shadow (&(objectClass=posixAccount)(host=web)))')
        lineinfile(path = '/etc/nslcd.conf', regexp = r'^\s*pam_authz_search\s+.*$',      line='pam_authz_search (&(objectClass=posixAccount)(uid=$username)(host=web))')
    else:

        fileText = """

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

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

# 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))

"""

        fileText = fileText.replace("%suffix%", suffix)
        fileText = fileText.replace("%searchAcctDN%", searchAcctDN)
        fileText = fileText.replace("%searchAcctPW%", searchAcctPW)
        fileText = fileText.replace("%adminAcctDN%", adminAcctDN)
    
        f1 = open('/etc/nslcd.conf', 'w')
        f1.write(fileText)
        f1.close();
    
    os.chmod('/etc/nslcd.conf', 0600)
    os.chown('/etc/nslcd.conf', 0, 0)
    

def _configure_nsswitch_conf(args):

    use_sss = false
    configure_nsswitch_conf(use_sss)

def configure_nsswitch_conf(use_sss = False):

    if use_sss:
        r1 = "files sss"
    else:
        r1 = "compat ldap"

    if os.path.exists('/etc/nsswitch.conf'):
        lineinfile(path = '/etc/nsswitch.conf', regexp = r'^\s*passwd\s*:+.*$', line='passwd: ' + r1)
        lineinfile(path = '/etc/nsswitch.conf', regexp = r'^\s*group\s*:+.*$',  line='group: ' + r1)
        lineinfile(path = '/etc/nsswitch.conf', regexp = r'^\s*shadow\s*:+.*$', line='shadow: ' + r1)
    else:
        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:         %r1%
group:          %r1%
shadow:         %r1%

hosts:          files dns
networks:       files

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

netgroup:       nis
"""

        fileText = fileText.replace("%r1%", r1)

        f1 = open("/etc/nsswitch.conf", 'w')
        f1.write(fileText)
        f1.close();
    
        os.chmod("/etc/nsswitch.conf", 0644)
        os.chown("/etc/nsswitch.conf", 0, 0)


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 removeConfigEntry(dbnum, dbtype, name):
    """Remove configuration entries"""
    
    print "removing '%s' from dn: olcDatabase={%d}%s,cn=config" % (name,dbnum,dbtype)

    ldifData = ("dn: olcDatabase={%s}" + dbtype + ",cn=config\n"
        "changetype: modify\n"
        "delete: %s\n")

    ldifData = ldifData % (dbnum, name)

    rc, procOutput, procError = hubzero.utilities.misc.exShellCommand(['ldapmodify', '-Y', 'EXTERNAL', '-H', 'ldapi:///'], ldifData)

    if rc and "No such attribute (16)" not in procError:
            print procOutput.rstrip() 
            print procError.rstrip()


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 = hubzero.utilities.misc.exShellCommand(['ldapmodify', '-Y', 'EXTERNAL', '-H', 'ldapi:///'], ldifData)

    if rc: 
        print procOutput.rstrip() 
        print procError.rstrip()


def olcVar(dbnum, dbtype, key, value):
    """Modify/Add/Delete olc variable"""

    print "Modifying " + key + " to [" + str(value) + "]"

    if (value == None or value == False or value == ''):
        ldifData = ("dn: olcDatabase={" + str(dbnum) + "}" + dbtype + ",cn=config\n"
            "changetype: modify\n"
            "delete: " + key + "\n")

        rc, procOutput, procError = hubzero.utilities.misc.exShellCommand(['ldapmodify', '-Y', 'EXTERNAL', '-H', 'ldapi:///'], ldifData)

        if (rc != 16) and rc:
            print procOutput
            print procError

        return rc

    ldifData = ("dn: olcDatabase={" + str(dbnum) + "}" + dbtype + ",cn=config\n"
        "changetype: modify\n"
        "replace: " + key + "\n"
        "" + key + ":" + value + "\n")

    rc, procOutput, procError = hubzero.utilities.misc.exShellCommand(['ldapmodify', '-Y', 'EXTERNAL', '-H', 'ldapi:///'], ldifData)

    if (rc == 0) and (rc != 16):
        return rc

    ldifData = ("dn: olcDatabase={" + str(dbnum) + "}" + dbtype + ",cn=config\n"
        "changetype: modify\n"
        "add: " + key + "\n"
        "" + key + ":" + value + "\n")

    rc, procOutput, procError = hubzero.utilities.misc.exShellCommand(['ldapmodify', '-Y', 'EXTERNAL', '-H', 'ldapi:///'], ldifData)

    if rc: 
        print procOutput 
        print procError

    return rc


def olcSuffix(dbnum, dbtype, suffix):
    """Modify the olcSuffix for the specified DB number"""

    olcVar(dbnum,dbtype,"olcSuffix",suffix)

def olcRootDN(dbnum, dbtype, dn):
    """Set the olcRootDN entry for the specified DB number"""

    olcVar(dbnum,dbtype,"olcRootDN", dn)

def olcRootPW(dbnum, dbtype, pw):
    """Set the olcRootPW entry for the specified DB number"""

    olcVar(dbnum,dbtype,"olcRootPW", pw)

def olcTLSCertificateFile(dbnum, dbtype, file):
    """ Set the olcTLSCertificateFile entry for the specified DB number"""
    olcVar(dbnum,dbtype,"olcTLSCertificateFile", file)

def olcTLSCertificateKeyFile(dbnum, dbtype, keyfile):
    """ Set the olcTLSCertificateKeyFile entry for the specified DB number"""
    olcVar(dbnum,dbtype,"olcTLSCertificateKeyFile", keyfile)

def olcAccess(dbnum, dbtype, access):
    """ Set the olcAccess entry for the specified DB number"""
    olcVar(dbnum,dbtype,"olcAccess", access)

def getDBNumber():
    
    rc, procOutput, procErr = hubzero.utilities.misc.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))

    # find the number of the min olcDatabase entries that has the format mdb
    minmdbDBNumber = 999
    for m in re.finditer("olcDatabase=\{(-?\d+)\}mdb", procOutput):
        if int(m.group(1)) < minmdbDBNumber:
            minmdbDBNumber = 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) and (minbdbDBNumber < minmdbDBNumber):
        return minbdbDBNumber, 'bdb'
    if (minhdbDBNumber < minbdbDBNumber) and (minhdbDBNumber < minmdbDBNumber):
        return minhdbDBNumber, 'hdb'
    if (minmdbDBNumber < minhdbDBNumber) and (minmdbDBNumber < minbdbDBNumber):
        return minmdbDBNumber, 'mdb'


def addCosineSchema():

    if os.path.exists('/etc/openldap/schema/cosine.ldif'):
        rc, procOutput, procError = hubzero.utilities.misc.exShellCommand(['ldapadd', '-Y', 'EXTERNAL', '-H', 'ldapi:///', '-f', '/etc/openldap/schema/cosine.ldif'])
        if rc:
            print procOutput.rstrip()
            print procError.rstrip()

def addNisSchema():

    if os.path.exists('/etc/openldap/schema/nis.ldif'):
        rc, procOutput, procError = hubzero.utilities.misc.exShellCommand(['ldapadd', '-Y', 'EXTERNAL', '-H', 'ldapi:///', '-f', '/etc/openldap/schema/nis.ldif'])
        if rc:
            print procOutput.rstrip()
            print procError.rstrip()

def addOlcAccessEntries(dbnum, dbtype, suffix):

    # our access rules    
    ldifData = ('dn: olcDatabase={%1}' + dbtype + ',cn=config\n'
                    'changetype: modify\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 = hubzero.utilities.misc.exShellCommand(['ldapmodify', '-Y', 'EXTERNAL', '-H', 'ldapi:///'], ldifData)
    if rc: 
        print procOutput.rstrip()
        print procError.rstrip()


def _reset_ldap(args):

    rc, fullSlapcatOutput, procErr = hubzero.utilities.misc.exShellCommand(['slapcat', '-b', 'cn=config'])
    
    # find the number of the max olcDatabase entries
    maxDBNumber, dbtype = getDBNumber()

    # get the olcSuffix
    suffix = None
    matchObj = re.search(r'olcSuffix: (.*)$' , fullSlapcatOutput, re.M)
    if matchObj:
        suffix = matchObj.group(1)

    print "olcSuffix=" + str(suffix)

    olcAccess(maxDBNumber,dbtype,None)
    olcTLSCertificateFile(maxDBNumber,dbtype,None)
    olcTLSCertificateKeyFile(maxDBNumber,dbtype,None)
    olcRootDN(maxDBNumber,dbtype,"gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth")
    olcRootPW(maxDBNumber,dbtype,None)

    print "Deleting tree " + suffix
    rc, procOutput, procError = hubzero.utilities.misc.exShellCommand(['ldapdelete', '-Y', 'EXTERNAL', '-H', 'ldapi:///', "-r", suffix])

    if rc != 32 and rc: 
        print procError
        print procOutput 

    olcSuffix(maxDBNumber,dbtype,"dc=my-domain,dc=com")

    return rc

def _init_ldap(args):

    if not os.path.exists('/var/lib/ldap/__db.001') and not os.path.exists('/var/lib/ldap/data.mdb'):
        service('slapd','restart')

    if not os.path.exists('/var/lib/ldap/__db.001') and not os.path.exists('/var/lib/ldap/data.mdb'):
        print "Uable to start OpenLDAP Server (slapd)"
        return 1

    hubzeroSecretsFilename = "/etc/hubzero.secrets"

    secretsConfig = ConfigParser.ConfigParser()
    secretsConfig.optionxform = str

    # read in existing data from hubzero.secrets
    if os.path.exists(hubzeroSecretsFilename):
        f1 = open(hubzeroSecretsFilename, "r")
        secretsConfig.readfp(f1)
        f1.close()

    # write our new password data
    adminUserPW = None
    try:
        adminUserPW = secretsConfig.get("DEFAULT", "LDAP-ADMINPW")
    except:
        pass

    searchUserPW = None
    try:
        searchUserPW = secretsConfig.get("DEFAULT", "LDAP-SEARCHPW")
    except:
        pass

    syncUserPW = None
    try:
        syncUserPW = secretsConfig.get("DEFAULT", "LDAP-SYNCPW")
    except:
        pass

    # prompt for passwords or create from scratch
    if (adminUserPW == '' or adminUserPW == None) and not args.promptforadminpw:
        adminUserPW = hubzero.config.passwords.generateAlphaNumPassword(10)
    elif  (adminUserPW == '' or adminUserPW == None):
        adminUserPW = getAlphaNumericPW()

    if (searchUserPW == '' or searchUserPW == None) and not args.promptforsearchpw:
        searchUserPW = hubzero.config.passwords.generateAlphaNumPassword(10)
    elif (searchUserPW == '' or searchUserPW == None):
        searchUserPW = getAlphaNumericPW()
    
    if (syncUserPW == '' or syncUserPW == None) and not args.promptforsyncuserpw:
        syncUserPW = hubzero.config.passwords.generateAlphaNumPassword(10)
    elif (syncUserPW == '' or syncUserPW == None):
        syncUserPW = getAlphaNumericPW()
    
    print "getting current info via slapcat"
    rc, fullSlapcatOutput, procErr = hubzero.utilities.misc.exShellCommand(['slapcat', '-b', 'cn=config'])
    
    # find the number of the max olcDatabase entries
    maxDBNumber, dbtype = getDBNumber()

    # find the oldDbDirectory for the max db 
    rc, procOutput, procErr = hubzero.utilities.misc.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)
    
    if dbtype != 'mdb':
        # See if this file exists for this database
        configFileName = configFileDir + '/DB_CONFIG'
        print "checking " + configFileName

        if not os.path.exists(configFileName):
            print configFileDir + "/DB_CONFIG file not present, using defaults"
    
            # 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 = hubzero.utilities.misc.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
            service('slapd','restart')
    
    # get the olcSuffix
    suffix = None
    matchObj = re.search(r'olcSuffix: (.*)$' , fullSlapcatOutput, re.M)
    if matchObj:
        print "found olcSuffix: " + matchObj.group(1)
        suffix = matchObj.group(1)

    if (args.suffix == None) and (suffix == None):
        newSuffix = "dc=hub"
    elif (args.suffix != None) and (args.suffix != suffix):
        newSuffix = args.suffix
    else:
        newSuffix = suffix

    # See if our ldap db is empty
    # If this is a debian system, just note that the slapd package postinst installs some initial ldap content.
    # So we allow two entries to be considered an "empty" database
    rc, procOutput, procErr = hubzero.utilities.misc.exShellCommand(['ldapsearch', '-Y', 'EXTERNAL', '-H', 'ldapi:///', '-b', suffix, '-z', '2'])
       
    emptyDB = (rc == 0 or rc == 32)

    if newSuffix != suffix:
        
        if not emptyDB:
            print "Unable to change olcSuffix to [" + newSuffix + "] as the current LDAP database is not empty"
            return 1

	# If this is a debian system, just note that the slapd package postinst installs some initial ldap content.
        # We inherit (but recreate) the cn=admin entry and orphan the top level entry 
 
        print "Changing olcSuffix to " + newSuffix
        olcSuffix(maxDBNumber, dbtype, newSuffix)
        suffix = newSuffix

    ldapManagerUserDN = "cn=Manager," + suffix

    # Since the database was empty, we're assuming it's ok to clear out any existing rules
    removeConfigEntry(maxDBNumber, dbtype, "olcAccess")
    removeConfigEntry(maxDBNumber, dbtype, "olcTLSCertificateFile")
    removeConfigEntry(maxDBNumber, dbtype, "olcTLSCertificateKeyFile")

    if fullSlapcatOutput.find('DN of manager') == -1:
        print "adding cosine schema"
        addCosineSchema()

    if fullSlapcatOutput.find('posixAccount') == -1:
        print "adding nis schema"
        addNisSchema()

    print "adding olcAccess entires"

    addOlcAccessEntries(maxDBNumber, dbtype, suffix)

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

        print "olcRootPW doesn't exist, 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 = hubzero.utilities.misc.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 ldap bind to localhost"

    l = ldap.initialize('ldap://localhost')
    print "ldapManagerUserDN=" + ldapManagerUserDN,
    print "adminUserPW=" + adminUserPW
    l.simple_bind_s(ldapManagerUserDN, adminUserPW)

    # create top level entry if it's not already there. For some reason this fails with an 
    # ldap.NO_SUCH_OBJECT error if it's not there, unlike other ldap searches that return 
    # an empty result set. This is why we have odd exception handling here and noplace else
    dn = suffix
    resultid = l.search(suffix, ldap.SCOPE_SUBTREE, '(&(objectclass=top)(objectclass=dcObject))', None)
    try:
        result_type, result_data = l.result(resultid, 0)
        if not result_data == []:
            print "top level entry already exists"

    except ldap.NO_SUCH_OBJECT:
        attrs = {}
        attrs['objectclass'] = ['top', 'dcObject', 'organization']
        attrs['o'] = suffix
        ldif = ldap.modlist.addModlist(attrs)
        print "adding root container"
        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 sync user
    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();


    ## Some debain/redhat differences from here on out

    ## RHEL
    if hubzero.utilities.misc.isRHEL():
        sssdConfFilename = "/etc/sssd/sssd.conf"
        sssdConfConfig = ConfigParser.ConfigParser()
        sssdConfConfig.optionxform = str

        if os.path.exists(sssdConfFilename):
            f1 = open(sssdConfFilename, "r")
            sssdConfConfig.readfp(f1)
            f1.close()

        if not sssdConfConfig.has_section("domain/default"):
            sssdConfConfig.add_section("domain/default")
        sssdConfConfig.set("domain/default", "autofs_provider", 'ldap')
        sssdConfConfig.set("domain/default", "cache_credentials", 'True')
        sssdConfConfig.set("domain/default", "id_provider", 'ldap')
        sssdConfConfig.set("domain/default", "auth_provider", 'ldap')
        sssdConfConfig.set("domain/default", "chpass_provider", 'ldap')
        sssdConfConfig.set("domain/default", "ldap_tls_cacertdir", '/etc/openldap/cacerts')
        if not sssdConfConfig.has_section("sssd"):
            sssdConfConfig.add_section("sssd")
        sssdConfConfig.set("sssd", "services", 'nss, pam, autofs')
        sssdConfConfig.set("sssd", "domains", 'default, ldap')
        sssdConfConfig.set("sssd", "config_file_version", '2')
        if not sssdConfConfig.has_section("nss"):
            sssdConfConfig.add_section("nss")
        sssdConfConfig.set("nss", "homedir_substring", '/home')
        sssdConfConfig.set("nss", "filter_groups", 'root')
        sssdConfConfig.set("nss", "filter_users", 'root')

        if not sssdConfConfig.has_section("domain/LDAP"):
            sssdConfConfig.add_section("domain/LDAP")
        sssdConfConfig.set("domain/LDAP", "enumerate", 'true')
        sssdConfConfig.set("domain/LDAP", "tls_reqcert", 'never')
        sssdConfConfig.set("domain/LDAP", "access_provider", 'ldap')
        sssdConfConfig.set("domain/LDAP", "ldap_user_search_base", "ou=users," + suffix)
        sssdConfConfig.set("domain/LDAP", "ldap_group_search_base", "ou=groups," + suffix)
        sssdConfConfig.set("domain/LDAP", "ldap_default_bind_dn", "cn=admin," + suffix)
        sssdConfConfig.set("domain/LDAP", "ldap_default_authtok_type", 'password')
        sssdConfConfig.set("domain/LDAP", "ldap_default_authtok", adminUserPW)
        sssdConfConfig.set("domain/LDAP", "ldap_id_use_start_tls", 'False')
        sssdConfConfig.set("domain/LDAP", "ldap_auth_disable_tls_never_use_in_production", 'true')
        sssdConfConfig.set("domain/LDAP", "id_provider", 'ldap')
        sssdConfConfig.set("domain/LDAP", "ldap_uri", 'ldap://localhost')
        sssdConfConfig.set("domain/LDAP", "ldap_access_filter", 'host=web')

        f2 = os.open(sssdConfFilename, os.O_RDWR | os.O_CREAT, 0600)
        os.fchmod(f2, 0600)
        f3 = os.fdopen(f2,"w")
        sssdConfConfig.write(f3)
        f3.close()

        # These aren't all strictly necessary, but are included for completeness of being equivalent to
        # authconfig call being replaced. They are also very fragile, a much more complex
        # and "intelligent" pam.d config file modification function would be needed to
        # improve this.
        #
        # @TODO: is broken_shadow needed?

        replace(path = '/etc/pam.d/password-auth-ac',         regexp = r'^\s*auth\s+(required|requisite)\s+.*pam_unix\.so.*$',           replace='auth        sufficient    pam_unix.so nullok try_first_pass');
        lineinfile(path = '/etc/pam.d/password-auth-ac',    insertafter = r'^\s*auth\s+.*pam_unix\.so.*$',           line='auth        sufficient    pam_sss.so use_first_pass');
        replace(path = '/etc/pam.d/password-auth-ac',   regexp =r'(^\s*auth\s*.*)(success\s*=\s*1)(.*pam_unix\.so\s+.*$)', replace='\\1success=2\\3')
            
        replace(path = '/etc/pam.d/system-auth-ac',           regexp = r'^\s*auth\s+(required|requisite)\s+.*pam_unix\.so.*$',           replace='auth        sufficient    pam_unix.so nullok try_first_pass');
        lineinfile(path = '/etc/pam.d/system-auth-ac',      insertafter = r'^\s*auth\s+.*pam_unix\.so.*$',           line='auth        sufficient    pam_sss.so use_first_pass');
        replace(path = '/etc/pam.d/system-auth-ac',   regexp =r'(^\s*auth\s*.*)(success\s*=\s*1)(.*pam_unix\.so\s+.*$)', replace='\\1success=2\\3')

        lineinfile(path = '/etc/pam.d/fingerprint-auth-ac',      regexp = r'^\s*account\s+.*pam_unix\.so.*$',        line='account     required      pam_unix.so broken_shadow');
        lineinfile(path = '/etc/pam.d/password-auth-ac',         regexp = r'^\s*account\s+.*pam_unix\.so.*$',        line='account     required      pam_unix.so broken_shadow');
        lineinfile(path = '/etc/pam.d/smartcard-auth-ac',        regexp = r'^\s*account\s+.*pam_unix\.so.*$',        line='account     required      pam_unix.so broken_shadow');
        lineinfile(path = '/etc/pam.d/system-auth-ac',           regexp = r'^\s*account\s+.*pam_unix\.so.*$',        line='account     required      pam_unix.so broken_shadow');

        lineinfile(path = '/etc/pam.d/smartcard-auth-ac',   insertafter = r'^\s*account\s+sufficient\s+pam_succeed', line='account     [default=bad success=ok user_unknown=ignore] pam_sss.so');
        lineinfile(path = '/etc/pam.d/password-auth-ac',    insertafter = r'^\s*account\s+sufficient\s+pam_succeed', line='account     [default=bad success=ok user_unknown=ignore] pam_sss.so');
        lineinfile(path = '/etc/pam.d/system-auth-ac',      insertafter = r'^\s*account\s+sufficient\s+pam_succeed', line='account     [default=bad success=ok user_unknown=ignore] pam_sss.so');
        lineinfile(path = '/etc/pam.d/fingerprint-auth-ac', insertafter = r'^\s*account\s+sufficient\s+pam_succeed', line='account     [default=bad success=ok user_unknown=ignore] pam_sss.so');
        lineinfile(path = '/etc/pam.d/password-auth-ac',    insertafter = r'^\s*password\s+.*pam_unix\.so.*$',       line='password    sufficient    pam_sss.so use_authtok');
        lineinfile(path = '/etc/pam.d/system-auth-ac',      insertafter = r'^\s*password\s+.*pam_unix\.so.*$',       line='password    sufficient    pam_sss.so use_authtok');
        lineinfile(path = '/etc/pam.d/fingerprint-auth-ac', insertafter = r'^\s*session\s+.*pam_unix\.so.*$',        line='session     optional      pam_sss.so');
        lineinfile(path = '/etc/pam.d/password-auth-ac',    insertafter = r'^\s*session\s+.*pam_unix\.so.*$',        line='session     optional      pam_sss.so');
        lineinfile(path = '/etc/pam.d/smartcard-auth-ac',   insertafter = r'^\s*session\s+.*pam_unix\.so.*$',        line='session     optional      pam_sss.so');
        lineinfile(path = '/etc/pam.d/system-auth-ac',      insertafter = r'^\s*session\s+.*pam_unix\.so.*$',        line='session     optional      pam_sss.so');

        lineinfile(path = '/etc/nsswitch.conf', regexp = r'\s*passwd:\s*.*$', line='passwd: files sss')
        lineinfile(path = '/etc/nsswitch.conf', regexp = r'\s*shadow:\s*.*$', line='shadow: files sss')
        lineinfile(path = '/etc/nsswitch.conf', regexp = r'\s*group:\s*.*$', line='group: files sss')
        lineinfile(path = '/etc/nsswitch.conf', regexp = r'\s*services:\s*.*$', line='services: files sss')
        lineinfile(path = '/etc/nsswitch.conf', regexp = r'\s*netgroup:\s*.*$', line='netgroup: files sss')
        lineinfile(path = '/etc/nsswitch.conf', regexp = r'\s*automount:\s*.*$', line='automount: files sss')

        service('slapd', 'restart')
        service('sssd', 'restart')

        if os.path.exists('/etc/init.d/nslcd') and os.path.exists('/sbin/chkconfig'):
            service('nslcd', 'stop')
            print "Setting nslcd to not autostart"
            rc, procOutput, procError = hubzero.utilities.misc.exShellCommand(['/sbin/chkconfig', 'nslcd', 'off'])
            #print procOutput.strip()
            if rc: 
                print procError

        if os.path.exists('/etc/init.d/slapd') and os.path.exists('/sbin/chkconfig'):
            print "Setting slapd for autostart"
            rc, procOutput, procError = hubzero.utilities.misc.exShellCommand(['/sbin/chkconfig', 'slapd', 'on'])
            #print procOutput.strip()
            if rc: 
                print procError

        if os.path.exists('/etc/init.d/sssd') and os.path.exists('/sbin/chkconfig'):
            print "Setting sssd for autostart"
            rc, procOutput, procError = hubzero.utilities.misc.exShellCommand(['/sbin/chkconfig', 'sssd', 'on'])
            #print procOutput.strip()
            if rc: 
                print procError


    ## Debian
    if hubzero.utilities.misc.isDebian():

        print "writing out /etc/nslcd.conf"
        _write_nslcd_conf(suffix, "cn=search," + suffix, searchUserPW, "cn=admin," + suffix)

        service('nslcd','stop')
        service('nslcd','start')
        service('nscd','stop')
        service('nscd','start')

        print "writing out /etc/nsswitch.conf"
        _write_nsswitch_conf()

    # Write ldap info to the /etc/hubzero.conf file 
    hubzero.config.hubzerositeconfig.setHubzeroConfigOption('ldap', 'adminuserDN', "cn=admin," + suffix)
    hubzero.config.hubzerositeconfig.setHubzeroConfigOption(hubname, 'ldap.adminuserDN', "cn=admin," + suffix)
    hubzero.config.hubzerositeconfig.setHubzeroConfigOption('ldap', 'searchuserDN', "cn=search," + suffix)
    hubzero.config.hubzerositeconfig.setHubzeroConfigOption(hubname, 'ldap.searchuserDN', "cn=search," + suffix)
    hubzero.config.hubzerositeconfig.setHubzeroConfigOption('ldap', 'syncuserDN', "cn=sync," + suffix)
    hubzero.config.hubzerositeconfig.setHubzeroConfigOption(hubname, 'ldap.syncuserDN', "cn=sync," + suffix)

    uri = "ldap://" + socket.getaddrinfo(socket.gethostname(), 0, 0, 0, 0, socket.AI_CANONNAME)[0][3]

    hubzero.config.hubzerositeconfig.setHubzeroConfigOption('ldap', 'basedn', suffix)
    hubzero.config.hubzerositeconfig.setHubzeroConfigOption(hubname, 'ldap.basedn', suffix)
    hubzero.config.hubzerositeconfig.setHubzeroConfigOption('ldap', 'uri', uri)
    hubzero.config.hubzerositeconfig.setHubzeroConfigOption(hubname, 'ldap.uri', uri)

    # /etc/hubzero.conf is world readable, do not store secrets here
    hubzero.config.hubzerositeconfig.removeHubzeroConfigOption('ldap', 'adminuserPW') 
    hubzero.config.hubzerositeconfig.removeHubzeroConfigOption('ldap', 'searchuserPW') 
    hubzero.config.hubzerositeconfig.removeHubzeroConfigOption('ldap', 'syncuserPW') 

    # update the jos_components table record for the com_system component
    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)

    # add hubrepo password to hubzero.secrets file, if file is not there, create it
    secretsConfig = ConfigParser.ConfigParser()
    secretsConfig.optionxform = str

    # read in existing data from hubzero.secrets
    if os.path.exists(hubzeroSecretsFilename):
        f1 = open(hubzeroSecretsFilename, "r")
        secretsConfig.readfp(f1)
        f1.close()

    # write our new password data
    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 ""
    print "==============================================="
    print "LDAP default setup complete"
    print "==============================================="
    print "LDAP admin user pw: " + adminUserPW
    print "LDAP search user pw: " + searchUserPW
    print "LDAP sync user pw: " + syncUserPW
    print ""
    print "LDAP config written to com_system component params field in database: " + hubzero.config.webconfig.getDefaultSite()
    print ""

    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 _sync_ldap_users(args):
    hubzero.utilities.user.syncusers("", args.purgeorphans, args.syncattributes)


def _sync_ldap_groups(args):
    hubzero.utilities.group.syncgroups("", args.purgeorphans, args.addgroupmemberships)


def _delete_ldap_groups(args):
    
    # by only deleting groups in db, any ldap only groups will be untouched
    if not args.deleteorphans:
        print "Orphaned groups in ldap without a corresponding group in the database will be preserved"

        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 db.query(sql, None):
            print "Deleting " + group.cn

            try:
                hubzero.utilities.group.del_ldap_group(group.cn)
            except ldap.NO_SUCH_OBJECT:
                print " Group not found"

    else:
        print "Orphaned groups in ldap without a corresponding group in the database will be DELETED"

        # 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 = getLDAPSiteSuffix()
        
        l = ldap.initialize('ldap://localhost')
        l.simple_bind_s(hubLDAPAcctMgrDN, hubLDAPAcctMgrPW)
        
        try:
            r = l.search_s("ou=groups," + siteSuffix, ldap.SCOPE_SUBTREE, '(objectClass=posixGroup)',   ['cn'])

        except ldap.NO_SUCH_OBJECT:
            print "No groups"
    
        for dn,entry in r:
            cn = str(entry['cn'][0])
            hubzero.utilities.group.delete_ldap_group(cn)


# #######################################################################
# 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("suffix", help="Suffix", nargs='?', default=None)
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_reset = subparsers.add_parser('reset', help='reset hub ldap')
parser_reset.set_defaults(func=_reset_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_syncusers = subparsers.add_parser('syncusers', help='add all users from db into ldap')
parser_syncusers.add_argument("--purgeorphans", help='remove any ldap user not in the jos_users table', action="store_true", default=True)
parser_syncusers.add_argument("--syncattributes", help='sync non required ldap user attributes', action="store_true", default=True)
parser_syncusers.set_defaults(func=_sync_ldap_users)

parser_syncgroups = subparsers.add_parser('syncgroups', help='add all groups from db into ldap')
parser_syncgroups.add_argument("--addgroupmemberships", help='add newly created users group memberships', action="store_true", default=True)
parser_syncgroups.add_argument("--purgeorphans", help='remove any ldap group not in the jos_xgroups table', action="store_true", default=True)
parser_syncgroups.set_defaults(func=_sync_ldap_groups)

parser_configure = subparsers.add_parser('configure', help='configure ldap component')
parser_configure_subparsers = parser_configure.add_subparsers()

parser_configure_nslcd = parser_configure_subparsers.add_parser('nslcd', help='configure /etc/nslcd.conf')
parser_configure_nslcd.set_defaults(func=_configure_nslcd_conf)
parser_configure_nsswitch = parser_configure_subparsers.add_parser('nsswitch', help='configure /etc/nsswitch.conf')
parser_configure_nsswitch.set_defaults(func=_configure_nsswitch_conf)
parser_configure_sssd = parser_configure_subparsers.add_parser('sssd', help='configure /etc/sssd.conf')
parser_configure_sssd.set_defaults(func=_configure_sssd_conf)
parser_configure_hubzero = parser_configure_subparsers.add_parser('hubzero', help='configure /etc/hubzero.conf')
parser_configure_hubzero.set_defaults(func=_configure_hubzero_conf)

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

exit(rc)
