# The Admin4 Project
# (c) 2013-2022 Andreas Pflug
#
# Licensed under the Apache License, 
# see LICENSE.TXT for conditions of usage


import adm
import logger
from wh import xlt
try:
  import ldap.schema
except:
  ldap=None
  print("python3-ldap not present")
from . import AttrVal


class LdapServer:
  """
  LdapServer()

  openldap interface
  http://www.python-ldap.org/doc/html/ldap.html
  """
  def __init__(self, node):
    self.lastError=None
    self.wasConnected=False
    self.ldap=None
    self.node=node
    self.subschema=None
    self.base=None

    ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_ALLOW)
    ldap.set_option(ldap.OPT_REFERRALS,0)


    if node.settings['security'] == "ssl":
      protocol="ldaps"
    else:
      protocol="ldap"
    uri="%s://%s:%d" % (protocol, node.address, node.settings['port'])

    try:
      spot="Connect"
      self.ldap=ldap.ldapobject.SimpleLDAPObject(uri)

      if node.settings['security'] == "tls":
        spot="StartTLS"
        self.ldap.start_tls_s()

      ldap.timeout=node.timeout
  
      spot=xlt("OptReferrals")
      self.ldap.set_option(ldap.OPT_REFERRALS,0)
      
      user=node.settings['user']
      spot=xlt("Bind")
      if user:
        self.ldap.simple_bind_s(user, node.password)
      else:
        self.ldap.simple_bind_s("", "")
    except Exception as e:
      self.ldap=None
      self.lastError = str(e)
      raise adm.ConnectionException(self.node, spot, self.lastError)

    try:
      result=self.ldap.search_s("", ldap.SCOPE_BASE, "(objectClass=*)", ['*','+'] )
      self.decodeResult(result)
      self.base=result[0][1]

      try:
        subschemaDN=self.base['subschemaSubentry'][0]
#        result=self.ldap.search_s(subschemaDN, ldap.SCOPE_BASE, "(objectClass=*)", ['*','+'] )
        result=self.ldap.search_s(subschemaDN, ldap.SCOPE_BASE, "(objectClass=*)",  ['ldapSyntaxes', 'attributeTypes', 'objectclasses', 'matchingRules', 'matchingRuleUse'] )
        self.decodeResult(result)
        self.subschema=ldap.schema.SubSchema(result[0][1])
      except Exception as e:
        logger.debug("Didn't get subschema: %s", str(e))
        pass
    except Exception as e:
      logger.debug("Didn't get config: %s", str(e))
      pass
  #  adm.StopWaiting(frame)

  def decodeResult(self, result):
    for _dn, row in result:
      for key, val in row.items():
        row[key] = list(map(lambda x: x.decode(), val))
        
  def execute(self, proc, *args, **kargs):
    rc=None
    frame=adm.StartWaiting()
    try:
      rc=proc(*args, **kargs)
    except Exception as e:
      d=str(e)
      adm.StopWaiting(frame, d)
      raise e

    adm.StopWaiting(frame)
    return rc

  def raiseException(self, e, what):
    if isinstance(e.args[0], dict):
      info=e.args[0].get('info')
      if not info:
        info=e.args[0].get('desc')
      if not info:
        info="Exception: %s" % str(e)
    else:
      info="%s failed. %s - %s" % (what, type(e).__name__, str(e))
    fr=adm.GetCurrentFrame()
    if fr:
      adm.StopWaiting(fr, "LDAP Error: %s" % info)
    raise adm.ServerException(self.node, info)


  def _chkLst(self, lst):
    if lst == None:
      lst=[]
    elif isinstance(lst, dict):
      lst=AttrVal.CreateList(lst)
    elif not isinstance(lst, list):
      lst=[lst]
    return lst
  
  def _addMod(self, mods, attr, code=-1):
    value=list(map(lambda x:x.encode(), attr.value))
    if code >= 0:
      mods.append( (code, attr.name, value) )
    else:
      mods.append( (attr.name, value) )
      
  def Modify(self, dn, chgList, addList=None, delList=None):
    chgList=self._chkLst(chgList)
    addList=self._chkLst(addList)
    delList=self._chkLst(delList)
    logger.querylog("Modify %s: Chg %s, Add %s, Del %s" % (dn, list(map(str, chgList)), list(map(str, addList)), list(map(str, delList))))

    mods=[]
    for attr in delList:
      self._addMod(mods, attr, ldap.MOD_DELETE)
    for attr in addList:
      self._addMod(mods, attr, ldap.MOD_ADD)
    for attr in chgList:
      self._addMod(mods, attr, ldap.MOD_REPLACE)

    try:
      self.execute(self.ldap.modify_s, dn, mods)
      return True
    except Exception as e:
      self.raiseException(e, "Modify")


  def Delete(self, dn):
    logger.querylog("Delete %s" % dn)
    try:
      self.execute(self.ldap.delete_s, dn)
      return True
    except Exception as e:
      self.raiseException(e, "Delete")


  def Add(self, dn, addList):
    mods=[]
    addList=self._chkLst(addList)
    logger.querylog("Add %s: %s" % (dn, list(map(str, addList))))
    for attr in addList:
      self._addMod(mods, attr)

    try:
      self.execute(self.ldap.add_s, dn, mods)
      return True
    except Exception as e:
      self.raiseException(e, "Add")

  def Rename(self, dn, newRdn, newParentDn=None):
    logger.querylog("Rename %s to %s %s" % (dn, newRdn, newParentDn))
    if newParentDn:
      newParentDn=newParentDn
    try:
      self.execute(self.ldap.rename_s, dn, newRdn, newParentDn)
      return True
    except Exception as e:
      self.raiseException(e, "Rename")

  def SetPassword(self, dn, passwd):
    try:
      self.execute(self.ldap.passwd_s, dn, None, passwd)
      return True
    except Exception as e:
      self.raiseException(e, "SetPassword")


  def HasFailed(self):
    return self.ldap == None
  
  
  def GetSchema(self, typ, name):
    if not self.subschema:
      return None
    return self.subschema.get_obj(typ, name)

  def GetOid(self, typ, name):
    if not self.subschema:
      return None
    a=self.subschema.get_obj(typ, name)
    if a:
      return a.oid
    return None

  def _search(self, base, sfilter, scope, attrs):
    """
    _search(base, filter, scope)

    result:
    (dn, valueDict) utf-encoded str
    """

    def _searchAsync(base, scope, sfilter, attrs):
      msgid=self.ldap.search(base, scope, sfilter, list(map(str, attrs)))
      _number, result=self.ldap.result(msgid, 1, self.ldap.timeout)
      self.decodeResult(result)
      return result
    
    err=None
    try:
      result=self.execute(_searchAsync, base, scope, sfilter, attrs)
    except (ldap.NO_SUCH_OBJECT,ldap.NO_SUCH_ATTRIBUTE, ldap.INSUFFICIENT_ACCESS) as e:
      result=None
      err=type(e).__name__
      
    logger.querylog("%s %s base=%s, scope=%s" % (sfilter, str(attrs), base, ['BASE','ONE','SUB'][scope]), None, err)
    return result
  
  def Search(self, base, sfilter, attrs=["*"], scope=ldap.SCOPE_SUBTREE):
    if not isinstance(attrs, list):
      attrs=attrs.split()
    return self._search(base, sfilter, scope, attrs)

  def SearchSub(self, base, sfilter="(objectClass=*)", attrs=["*"]):
    return self.Search(base, sfilter, attrs, ldap.SCOPE_SUBTREE)

  def SearchOne(self, base, sfilter="(objectClass=*)", attrs=["*"]):
    return self.Search(base, sfilter, attrs, ldap.SCOPE_ONELEVEL)
  
  def SearchBase(self, base, sfilter="(objectClass=*)", attrs=["*"]):
    return self.Search(base, sfilter, attrs, ldap.SCOPE_BASE)
  

9a7c50318fb8249084486dc7a1b4d693f4988011
<mask token>


class LdapServer:
    <mask token>

    def __init__(self, node):
        self.lastError = None
        self.wasConnected = False
        self.ldap = None
        self.node = node
        self.subschema = None
        self.base = None
        ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_ALLOW)
        ldap.set_option(ldap.OPT_REFERRALS, 0)
        if node.settings['security'] == 'ssl':
            protocol = 'ldaps'
        else:
            protocol = 'ldap'
        uri = '%s://%s:%d' % (protocol, node.address, node.settings['port'])
        try:
            spot = 'Connect'
            self.ldap = ldap.ldapobject.SimpleLDAPObject(uri)
            if node.settings['security'] == 'tls':
                spot = 'StartTLS'
                self.ldap.start_tls_s()
            ldap.timeout = node.timeout
            spot = xlt('OptReferrals')
            self.ldap.set_option(ldap.OPT_REFERRALS, 0)
            user = node.settings['user']
            spot = xlt('Bind')
            if user:
                self.ldap.simple_bind_s(user, node.password)
            else:
                self.ldap.simple_bind_s('', '')
        except Exception as e:
            self.ldap = None
            self.lastError = str(e)
            raise adm.ConnectionException(self.node, spot, self.lastError)
        try:
            result = self.ldap.search_s('', ldap.SCOPE_BASE,
                '(objectClass=*)', ['*', '+'])
            self.decodeResult(result)
            self.base = result[0][1]
            try:
                subschemaDN = self.base['subschemaSubentry'][0]
                result = self.ldap.search_s(subschemaDN, ldap.SCOPE_BASE,
                    '(objectClass=*)', ['ldapSyntaxes', 'attributeTypes',
                    'objectclasses', 'matchingRules', 'matchingRuleUse'])
                self.decodeResult(result)
                self.subschema = ldap.schema.SubSchema(result[0][1])
            except Exception as e:
                logger.debug("Didn't get subschema: %s", str(e))
                pass
        except Exception as e:
            logger.debug("Didn't get config: %s", str(e))
            pass
    <mask token>

    def execute(self, proc, *args, **kargs):
        rc = None
        frame = adm.StartWaiting()
        try:
            rc = proc(*args, **kargs)
        except Exception as e:
            d = str(e)
            adm.StopWaiting(frame, d)
            raise e
        adm.StopWaiting(frame)
        return rc

    def raiseException(self, e, what):
        if isinstance(e.args[0], dict):
            info = e.args[0].get('info')
            if not info:
                info = e.args[0].get('desc')
            if not info:
                info = 'Exception: %s' % str(e)
        else:
            info = '%s failed. %s - %s' % (what, type(e).__name__, str(e))
        fr = adm.GetCurrentFrame()
        if fr:
            adm.StopWaiting(fr, 'LDAP Error: %s' % info)
        raise adm.ServerException(self.node, info)

    def _chkLst(self, lst):
        if lst == None:
            lst = []
        elif isinstance(lst, dict):
            lst = AttrVal.CreateList(lst)
        elif not isinstance(lst, list):
            lst = [lst]
        return lst
    <mask token>
    <mask token>
    <mask token>
    <mask token>

    def Rename(self, dn, newRdn, newParentDn=None):
        logger.querylog('Rename %s to %s %s' % (dn, newRdn, newParentDn))
        if newParentDn:
            newParentDn = newParentDn
        try:
            self.execute(self.ldap.rename_s, dn, newRdn, newParentDn)
            return True
        except Exception as e:
            self.raiseException(e, 'Rename')
    <mask token>

    def HasFailed(self):
        return self.ldap == None
    <mask token>
    <mask token>
    <mask token>

    def Search(self, base, sfilter, attrs=['*'], scope=ldap.SCOPE_SUBTREE):
        if not isinstance(attrs, list):
            attrs = attrs.split()
        return self._search(base, sfilter, scope, attrs)
    <mask token>

    def SearchOne(self, base, sfilter='(objectClass=*)', attrs=['*']):
        return self.Search(base, sfilter, attrs, ldap.SCOPE_ONELEVEL)

    def SearchBase(self, base, sfilter='(objectClass=*)', attrs=['*']):
        return self.Search(base, sfilter, attrs, ldap.SCOPE_BASE)

<mask token>


class LdapServer:
    <mask token>

    def __init__(self, node):
        self.lastError = None
        self.wasConnected = False
        self.ldap = None
        self.node = node
        self.subschema = None
        self.base = None
        ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_ALLOW)
        ldap.set_option(ldap.OPT_REFERRALS, 0)
        if node.settings['security'] == 'ssl':
            protocol = 'ldaps'
        else:
            protocol = 'ldap'
        uri = '%s://%s:%d' % (protocol, node.address, node.settings['port'])
        try:
            spot = 'Connect'
            self.ldap = ldap.ldapobject.SimpleLDAPObject(uri)
            if node.settings['security'] == 'tls':
                spot = 'StartTLS'
                self.ldap.start_tls_s()
            ldap.timeout = node.timeout
            spot = xlt('OptReferrals')
            self.ldap.set_option(ldap.OPT_REFERRALS, 0)
            user = node.settings['user']
            spot = xlt('Bind')
            if user:
                self.ldap.simple_bind_s(user, node.password)
            else:
                self.ldap.simple_bind_s('', '')
        except Exception as e:
            self.ldap = None
            self.lastError = str(e)
            raise adm.ConnectionException(self.node, spot, self.lastError)
        try:
            result = self.ldap.search_s('', ldap.SCOPE_BASE,
                '(objectClass=*)', ['*', '+'])
            self.decodeResult(result)
            self.base = result[0][1]
            try:
                subschemaDN = self.base['subschemaSubentry'][0]
                result = self.ldap.search_s(subschemaDN, ldap.SCOPE_BASE,
                    '(objectClass=*)', ['ldapSyntaxes', 'attributeTypes',
                    'objectclasses', 'matchingRules', 'matchingRuleUse'])
                self.decodeResult(result)
                self.subschema = ldap.schema.SubSchema(result[0][1])
            except Exception as e:
                logger.debug("Didn't get subschema: %s", str(e))
                pass
        except Exception as e:
            logger.debug("Didn't get config: %s", str(e))
            pass
    <mask token>

    def execute(self, proc, *args, **kargs):
        rc = None
        frame = adm.StartWaiting()
        try:
            rc = proc(*args, **kargs)
        except Exception as e:
            d = str(e)
            adm.StopWaiting(frame, d)
            raise e
        adm.StopWaiting(frame)
        return rc

    def raiseException(self, e, what):
        if isinstance(e.args[0], dict):
            info = e.args[0].get('info')
            if not info:
                info = e.args[0].get('desc')
            if not info:
                info = 'Exception: %s' % str(e)
        else:
            info = '%s failed. %s - %s' % (what, type(e).__name__, str(e))
        fr = adm.GetCurrentFrame()
        if fr:
            adm.StopWaiting(fr, 'LDAP Error: %s' % info)
        raise adm.ServerException(self.node, info)

    def _chkLst(self, lst):
        if lst == None:
            lst = []
        elif isinstance(lst, dict):
            lst = AttrVal.CreateList(lst)
        elif not isinstance(lst, list):
            lst = [lst]
        return lst

    def _addMod(self, mods, attr, code=-1):
        value = list(map(lambda x: x.encode(), attr.value))
        if code >= 0:
            mods.append((code, attr.name, value))
        else:
            mods.append((attr.name, value))
    <mask token>

    def Delete(self, dn):
        logger.querylog('Delete %s' % dn)
        try:
            self.execute(self.ldap.delete_s, dn)
            return True
        except Exception as e:
            self.raiseException(e, 'Delete')
    <mask token>

    def Rename(self, dn, newRdn, newParentDn=None):
        logger.querylog('Rename %s to %s %s' % (dn, newRdn, newParentDn))
        if newParentDn:
            newParentDn = newParentDn
        try:
            self.execute(self.ldap.rename_s, dn, newRdn, newParentDn)
            return True
        except Exception as e:
            self.raiseException(e, 'Rename')

    def SetPassword(self, dn, passwd):
        try:
            self.execute(self.ldap.passwd_s, dn, None, passwd)
            return True
        except Exception as e:
            self.raiseException(e, 'SetPassword')

    def HasFailed(self):
        return self.ldap == None

    def GetSchema(self, typ, name):
        if not self.subschema:
            return None
        return self.subschema.get_obj(typ, name)
    <mask token>

    def _search(self, base, sfilter, scope, attrs):
        """
    _search(base, filter, scope)

    result:
    (dn, valueDict) utf-encoded str
    """

        def _searchAsync(base, scope, sfilter, attrs):
            msgid = self.ldap.search(base, scope, sfilter, list(map(str,
                attrs)))
            _number, result = self.ldap.result(msgid, 1, self.ldap.timeout)
            self.decodeResult(result)
            return result
        err = None
        try:
            result = self.execute(_searchAsync, base, scope, sfilter, attrs)
        except (ldap.NO_SUCH_OBJECT, ldap.NO_SUCH_ATTRIBUTE, ldap.
            INSUFFICIENT_ACCESS) as e:
            result = None
            err = type(e).__name__
        logger.querylog('%s %s base=%s, scope=%s' % (sfilter, str(attrs),
            base, ['BASE', 'ONE', 'SUB'][scope]), None, err)
        return result

    def Search(self, base, sfilter, attrs=['*'], scope=ldap.SCOPE_SUBTREE):
        if not isinstance(attrs, list):
            attrs = attrs.split()
        return self._search(base, sfilter, scope, attrs)

    def SearchSub(self, base, sfilter='(objectClass=*)', attrs=['*']):
        return self.Search(base, sfilter, attrs, ldap.SCOPE_SUBTREE)

    def SearchOne(self, base, sfilter='(objectClass=*)', attrs=['*']):
        return self.Search(base, sfilter, attrs, ldap.SCOPE_ONELEVEL)

    def SearchBase(self, base, sfilter='(objectClass=*)', attrs=['*']):
        return self.Search(base, sfilter, attrs, ldap.SCOPE_BASE)

<mask token>


class LdapServer:
    <mask token>

    def __init__(self, node):
        self.lastError = None
        self.wasConnected = False
        self.ldap = None
        self.node = node
        self.subschema = None
        self.base = None
        ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_ALLOW)
        ldap.set_option(ldap.OPT_REFERRALS, 0)
        if node.settings['security'] == 'ssl':
            protocol = 'ldaps'
        else:
            protocol = 'ldap'
        uri = '%s://%s:%d' % (protocol, node.address, node.settings['port'])
        try:
            spot = 'Connect'
            self.ldap = ldap.ldapobject.SimpleLDAPObject(uri)
            if node.settings['security'] == 'tls':
                spot = 'StartTLS'
                self.ldap.start_tls_s()
            ldap.timeout = node.timeout
            spot = xlt('OptReferrals')
            self.ldap.set_option(ldap.OPT_REFERRALS, 0)
            user = node.settings['user']
            spot = xlt('Bind')
            if user:
                self.ldap.simple_bind_s(user, node.password)
            else:
                self.ldap.simple_bind_s('', '')
        except Exception as e:
            self.ldap = None
            self.lastError = str(e)
            raise adm.ConnectionException(self.node, spot, self.lastError)
        try:
            result = self.ldap.search_s('', ldap.SCOPE_BASE,
                '(objectClass=*)', ['*', '+'])
            self.decodeResult(result)
            self.base = result[0][1]
            try:
                subschemaDN = self.base['subschemaSubentry'][0]
                result = self.ldap.search_s(subschemaDN, ldap.SCOPE_BASE,
                    '(objectClass=*)', ['ldapSyntaxes', 'attributeTypes',
                    'objectclasses', 'matchingRules', 'matchingRuleUse'])
                self.decodeResult(result)
                self.subschema = ldap.schema.SubSchema(result[0][1])
            except Exception as e:
                logger.debug("Didn't get subschema: %s", str(e))
                pass
        except Exception as e:
            logger.debug("Didn't get config: %s", str(e))
            pass
    <mask token>

    def execute(self, proc, *args, **kargs):
        rc = None
        frame = adm.StartWaiting()
        try:
            rc = proc(*args, **kargs)
        except Exception as e:
            d = str(e)
            adm.StopWaiting(frame, d)
            raise e
        adm.StopWaiting(frame)
        return rc

    def raiseException(self, e, what):
        if isinstance(e.args[0], dict):
            info = e.args[0].get('info')
            if not info:
                info = e.args[0].get('desc')
            if not info:
                info = 'Exception: %s' % str(e)
        else:
            info = '%s failed. %s - %s' % (what, type(e).__name__, str(e))
        fr = adm.GetCurrentFrame()
        if fr:
            adm.StopWaiting(fr, 'LDAP Error: %s' % info)
        raise adm.ServerException(self.node, info)

    def _chkLst(self, lst):
        if lst == None:
            lst = []
        elif isinstance(lst, dict):
            lst = AttrVal.CreateList(lst)
        elif not isinstance(lst, list):
            lst = [lst]
        return lst

    def _addMod(self, mods, attr, code=-1):
        value = list(map(lambda x: x.encode(), attr.value))
        if code >= 0:
            mods.append((code, attr.name, value))
        else:
            mods.append((attr.name, value))
    <mask token>

    def Delete(self, dn):
        logger.querylog('Delete %s' % dn)
        try:
            self.execute(self.ldap.delete_s, dn)
            return True
        except Exception as e:
            self.raiseException(e, 'Delete')
    <mask token>

    def Rename(self, dn, newRdn, newParentDn=None):
        logger.querylog('Rename %s to %s %s' % (dn, newRdn, newParentDn))
        if newParentDn:
            newParentDn = newParentDn
        try:
            self.execute(self.ldap.rename_s, dn, newRdn, newParentDn)
            return True
        except Exception as e:
            self.raiseException(e, 'Rename')

    def SetPassword(self, dn, passwd):
        try:
            self.execute(self.ldap.passwd_s, dn, None, passwd)
            return True
        except Exception as e:
            self.raiseException(e, 'SetPassword')

    def HasFailed(self):
        return self.ldap == None

    def GetSchema(self, typ, name):
        if not self.subschema:
            return None
        return self.subschema.get_obj(typ, name)

    def GetOid(self, typ, name):
        if not self.subschema:
            return None
        a = self.subschema.get_obj(typ, name)
        if a:
            return a.oid
        return None

    def _search(self, base, sfilter, scope, attrs):
        """
    _search(base, filter, scope)

    result:
    (dn, valueDict) utf-encoded str
    """

        def _searchAsync(base, scope, sfilter, attrs):
            msgid = self.ldap.search(base, scope, sfilter, list(map(str,
                attrs)))
            _number, result = self.ldap.result(msgid, 1, self.ldap.timeout)
            self.decodeResult(result)
            return result
        err = None
        try:
            result = self.execute(_searchAsync, base, scope, sfilter, attrs)
        except (ldap.NO_SUCH_OBJECT, ldap.NO_SUCH_ATTRIBUTE, ldap.
            INSUFFICIENT_ACCESS) as e:
            result = None
            err = type(e).__name__
        logger.querylog('%s %s base=%s, scope=%s' % (sfilter, str(attrs),
            base, ['BASE', 'ONE', 'SUB'][scope]), None, err)
        return result

    def Search(self, base, sfilter, attrs=['*'], scope=ldap.SCOPE_SUBTREE):
        if not isinstance(attrs, list):
            attrs = attrs.split()
        return self._search(base, sfilter, scope, attrs)

    def SearchSub(self, base, sfilter='(objectClass=*)', attrs=['*']):
        return self.Search(base, sfilter, attrs, ldap.SCOPE_SUBTREE)

    def SearchOne(self, base, sfilter='(objectClass=*)', attrs=['*']):
        return self.Search(base, sfilter, attrs, ldap.SCOPE_ONELEVEL)

    def SearchBase(self, base, sfilter='(objectClass=*)', attrs=['*']):
        return self.Search(base, sfilter, attrs, ldap.SCOPE_BASE)

<mask token>


class LdapServer:
    <mask token>

    def __init__(self, node):
        self.lastError = None
        self.wasConnected = False
        self.ldap = None
        self.node = node
        self.subschema = None
        self.base = None
        ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_ALLOW)
        ldap.set_option(ldap.OPT_REFERRALS, 0)
        if node.settings['security'] == 'ssl':
            protocol = 'ldaps'
        else:
            protocol = 'ldap'
        uri = '%s://%s:%d' % (protocol, node.address, node.settings['port'])
        try:
            spot = 'Connect'
            self.ldap = ldap.ldapobject.SimpleLDAPObject(uri)
            if node.settings['security'] == 'tls':
                spot = 'StartTLS'
                self.ldap.start_tls_s()
            ldap.timeout = node.timeout
            spot = xlt('OptReferrals')
            self.ldap.set_option(ldap.OPT_REFERRALS, 0)
            user = node.settings['user']
            spot = xlt('Bind')
            if user:
                self.ldap.simple_bind_s(user, node.password)
            else:
                self.ldap.simple_bind_s('', '')
        except Exception as e:
            self.ldap = None
            self.lastError = str(e)
            raise adm.ConnectionException(self.node, spot, self.lastError)
        try:
            result = self.ldap.search_s('', ldap.SCOPE_BASE,
                '(objectClass=*)', ['*', '+'])
            self.decodeResult(result)
            self.base = result[0][1]
            try:
                subschemaDN = self.base['subschemaSubentry'][0]
                result = self.ldap.search_s(subschemaDN, ldap.SCOPE_BASE,
                    '(objectClass=*)', ['ldapSyntaxes', 'attributeTypes',
                    'objectclasses', 'matchingRules', 'matchingRuleUse'])
                self.decodeResult(result)
                self.subschema = ldap.schema.SubSchema(result[0][1])
            except Exception as e:
                logger.debug("Didn't get subschema: %s", str(e))
                pass
        except Exception as e:
            logger.debug("Didn't get config: %s", str(e))
            pass

    def decodeResult(self, result):
        for _dn, row in result:
            for key, val in row.items():
                row[key] = list(map(lambda x: x.decode(), val))

    def execute(self, proc, *args, **kargs):
        rc = None
        frame = adm.StartWaiting()
        try:
            rc = proc(*args, **kargs)
        except Exception as e:
            d = str(e)
            adm.StopWaiting(frame, d)
            raise e
        adm.StopWaiting(frame)
        return rc

    def raiseException(self, e, what):
        if isinstance(e.args[0], dict):
            info = e.args[0].get('info')
            if not info:
                info = e.args[0].get('desc')
            if not info:
                info = 'Exception: %s' % str(e)
        else:
            info = '%s failed. %s - %s' % (what, type(e).__name__, str(e))
        fr = adm.GetCurrentFrame()
        if fr:
            adm.StopWaiting(fr, 'LDAP Error: %s' % info)
        raise adm.ServerException(self.node, info)

    def _chkLst(self, lst):
        if lst == None:
            lst = []
        elif isinstance(lst, dict):
            lst = AttrVal.CreateList(lst)
        elif not isinstance(lst, list):
            lst = [lst]
        return lst

    def _addMod(self, mods, attr, code=-1):
        value = list(map(lambda x: x.encode(), attr.value))
        if code >= 0:
            mods.append((code, attr.name, value))
        else:
            mods.append((attr.name, value))

    def Modify(self, dn, chgList, addList=None, delList=None):
        chgList = self._chkLst(chgList)
        addList = self._chkLst(addList)
        delList = self._chkLst(delList)
        logger.querylog('Modify %s: Chg %s, Add %s, Del %s' % (dn, list(map
            (str, chgList)), list(map(str, addList)), list(map(str, delList))))
        mods = []
        for attr in delList:
            self._addMod(mods, attr, ldap.MOD_DELETE)
        for attr in addList:
            self._addMod(mods, attr, ldap.MOD_ADD)
        for attr in chgList:
            self._addMod(mods, attr, ldap.MOD_REPLACE)
        try:
            self.execute(self.ldap.modify_s, dn, mods)
            return True
        except Exception as e:
            self.raiseException(e, 'Modify')

    def Delete(self, dn):
        logger.querylog('Delete %s' % dn)
        try:
            self.execute(self.ldap.delete_s, dn)
            return True
        except Exception as e:
            self.raiseException(e, 'Delete')

    def Add(self, dn, addList):
        mods = []
        addList = self._chkLst(addList)
        logger.querylog('Add %s: %s' % (dn, list(map(str, addList))))
        for attr in addList:
            self._addMod(mods, attr)
        try:
            self.execute(self.ldap.add_s, dn, mods)
            return True
        except Exception as e:
            self.raiseException(e, 'Add')

    def Rename(self, dn, newRdn, newParentDn=None):
        logger.querylog('Rename %s to %s %s' % (dn, newRdn, newParentDn))
        if newParentDn:
            newParentDn = newParentDn
        try:
            self.execute(self.ldap.rename_s, dn, newRdn, newParentDn)
            return True
        except Exception as e:
            self.raiseException(e, 'Rename')

    def SetPassword(self, dn, passwd):
        try:
            self.execute(self.ldap.passwd_s, dn, None, passwd)
            return True
        except Exception as e:
            self.raiseException(e, 'SetPassword')

    def HasFailed(self):
        return self.ldap == None

    def GetSchema(self, typ, name):
        if not self.subschema:
            return None
        return self.subschema.get_obj(typ, name)

    def GetOid(self, typ, name):
        if not self.subschema:
            return None
        a = self.subschema.get_obj(typ, name)
        if a:
            return a.oid
        return None

    def _search(self, base, sfilter, scope, attrs):
        """
    _search(base, filter, scope)

    result:
    (dn, valueDict) utf-encoded str
    """

        def _searchAsync(base, scope, sfilter, attrs):
            msgid = self.ldap.search(base, scope, sfilter, list(map(str,
                attrs)))
            _number, result = self.ldap.result(msgid, 1, self.ldap.timeout)
            self.decodeResult(result)
            return result
        err = None
        try:
            result = self.execute(_searchAsync, base, scope, sfilter, attrs)
        except (ldap.NO_SUCH_OBJECT, ldap.NO_SUCH_ATTRIBUTE, ldap.
            INSUFFICIENT_ACCESS) as e:
            result = None
            err = type(e).__name__
        logger.querylog('%s %s base=%s, scope=%s' % (sfilter, str(attrs),
            base, ['BASE', 'ONE', 'SUB'][scope]), None, err)
        return result

    def Search(self, base, sfilter, attrs=['*'], scope=ldap.SCOPE_SUBTREE):
        if not isinstance(attrs, list):
            attrs = attrs.split()
        return self._search(base, sfilter, scope, attrs)

    def SearchSub(self, base, sfilter='(objectClass=*)', attrs=['*']):
        return self.Search(base, sfilter, attrs, ldap.SCOPE_SUBTREE)

    def SearchOne(self, base, sfilter='(objectClass=*)', attrs=['*']):
        return self.Search(base, sfilter, attrs, ldap.SCOPE_ONELEVEL)

    def SearchBase(self, base, sfilter='(objectClass=*)', attrs=['*']):
        return self.Search(base, sfilter, attrs, ldap.SCOPE_BASE)

# The Admin4 Project
# (c) 2013-2022 Andreas Pflug
#
# Licensed under the Apache License, 
# see LICENSE.TXT for conditions of usage


import adm
import logger
from wh import xlt
try:
  import ldap.schema
except:
  ldap=None
  print("python3-ldap not present")
from . import AttrVal


class LdapServer:
  """
  LdapServer()

  openldap interface
  http://www.python-ldap.org/doc/html/ldap.html
  """
  def __init__(self, node):
    self.lastError=None
    self.wasConnected=False
    self.ldap=None
    self.node=node
    self.subschema=None
    self.base=None

    ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_ALLOW)
    ldap.set_option(ldap.OPT_REFERRALS,0)


    if node.settings['security'] == "ssl":
      protocol="ldaps"
    else:
      protocol="ldap"
    uri="%s://%s:%d" % (protocol, node.address, node.settings['port'])

    try:
      spot="Connect"
      self.ldap=ldap.ldapobject.SimpleLDAPObject(uri)

      if node.settings['security'] == "tls":
        spot="StartTLS"
        self.ldap.start_tls_s()

      ldap.timeout=node.timeout
  
      spot=xlt("OptReferrals")
      self.ldap.set_option(ldap.OPT_REFERRALS,0)
      
      user=node.settings['user']
      spot=xlt("Bind")
      if user:
        self.ldap.simple_bind_s(user, node.password)
      else:
        self.ldap.simple_bind_s("", "")
    except Exception as e:
      self.ldap=None
      self.lastError = str(e)
      raise adm.ConnectionException(self.node, spot, self.lastError)

    try:
      result=self.ldap.search_s("", ldap.SCOPE_BASE, "(objectClass=*)", ['*','+'] )
      self.decodeResult(result)
      self.base=result[0][1]

      try:
        subschemaDN=self.base['subschemaSubentry'][0]
#        result=self.ldap.search_s(subschemaDN, ldap.SCOPE_BASE, "(objectClass=*)", ['*','+'] )
        result=self.ldap.search_s(subschemaDN, ldap.SCOPE_BASE, "(objectClass=*)",  ['ldapSyntaxes', 'attributeTypes', 'objectclasses', 'matchingRules', 'matchingRuleUse'] )
        self.decodeResult(result)
        self.subschema=ldap.schema.SubSchema(result[0][1])
      except Exception as e:
        logger.debug("Didn't get subschema: %s", str(e))
        pass
    except Exception as e:
      logger.debug("Didn't get config: %s", str(e))
      pass
  #  adm.StopWaiting(frame)

  def decodeResult(self, result):
    for _dn, row in result:
      for key, val in row.items():
        row[key] = list(map(lambda x: x.decode(), val))
        
  def execute(self, proc, *args, **kargs):
    rc=None
    frame=adm.StartWaiting()
    try:
      rc=proc(*args, **kargs)
    except Exception as e:
      d=str(e)
      adm.StopWaiting(frame, d)
      raise e

    adm.StopWaiting(frame)
    return rc

  def raiseException(self, e, what):
    if isinstance(e.args[0], dict):
      info=e.args[0].get('info')
      if not info:
        info=e.args[0].get('desc')
      if not info:
        info="Exception: %s" % str(e)
    else:
      info="%s failed. %s - %s" % (what, type(e).__name__, str(e))
    fr=adm.GetCurrentFrame()
    if fr:
      adm.StopWaiting(fr, "LDAP Error: %s" % info)
    raise adm.ServerException(self.node, info)


  def _chkLst(self, lst):
    if lst == None:
      lst=[]
    elif isinstance(lst, dict):
      lst=AttrVal.CreateList(lst)
    elif not isinstance(lst, list):
      lst=[lst]
    return lst
  
  def _addMod(self, mods, attr, code=-1):
    value=list(map(lambda x:x.encode(), attr.value))
    if code >= 0:
      mods.append( (code, attr.name, value) )
    else:
      mods.append( (attr.name, value) )
      
  def Modify(self, dn, chgList, addList=None, delList=None):
    chgList=self._chkLst(chgList)
    addList=self._chkLst(addList)
    delList=self._chkLst(delList)
    logger.querylog("Modify %s: Chg %s, Add %s, Del %s" % (dn, list(map(str, chgList)), list(map(str, addList)), list(map(str, delList))))

    mods=[]
    for attr in delList:
      self._addMod(mods, attr, ldap.MOD_DELETE)
    for attr in addList:
      self._addMod(mods, attr, ldap.MOD_ADD)
    for attr in chgList:
      self._addMod(mods, attr, ldap.MOD_REPLACE)

    try:
      self.execute(self.ldap.modify_s, dn, mods)
      return True
    except Exception as e:
      self.raiseException(e, "Modify")


  def Delete(self, dn):
    logger.querylog("Delete %s" % dn)
    try:
      self.execute(self.ldap.delete_s, dn)
      return True
    except Exception as e:
      self.raiseException(e, "Delete")


  def Add(self, dn, addList):
    mods=[]
    addList=self._chkLst(addList)
    logger.querylog("Add %s: %s" % (dn, list(map(str, addList))))
    for attr in addList:
      self._addMod(mods, attr)

    try:
      self.execute(self.ldap.add_s, dn, mods)
      return True
    except Exception as e:
      self.raiseException(e, "Add")

  def Rename(self, dn, newRdn, newParentDn=None):
    logger.querylog("Rename %s to %s %s" % (dn, newRdn, newParentDn))
    if newParentDn:
      newParentDn=newParentDn
    try:
      self.execute(self.ldap.rename_s, dn, newRdn, newParentDn)
      return True
    except Exception as e:
      self.raiseException(e, "Rename")

  def SetPassword(self, dn, passwd):
    try:
      self.execute(self.ldap.passwd_s, dn, None, passwd)
      return True
    except Exception as e:
      self.raiseException(e, "SetPassword")


  def HasFailed(self):
    return self.ldap == None
  
  
  def GetSchema(self, typ, name):
    if not self.subschema:
      return None
    return self.subschema.get_obj(typ, name)

  def GetOid(self, typ, name):
    if not self.subschema:
      return None
    a=self.subschema.get_obj(typ, name)
    if a:
      return a.oid
    return None

  def _search(self, base, sfilter, scope, attrs):
    """
    _search(base, filter, scope)

    result:
    (dn, valueDict) utf-encoded str
    """

    def _searchAsync(base, scope, sfilter, attrs):
      msgid=self.ldap.search(base, scope, sfilter, list(map(str, attrs)))
      _number, result=self.ldap.result(msgid, 1, self.ldap.timeout)
      self.decodeResult(result)
      return result
    
    err=None
    try:
      result=self.execute(_searchAsync, base, scope, sfilter, attrs)
    except (ldap.NO_SUCH_OBJECT,ldap.NO_SUCH_ATTRIBUTE, ldap.INSUFFICIENT_ACCESS) as e:
      result=None
      err=type(e).__name__
      
    logger.querylog("%s %s base=%s, scope=%s" % (sfilter, str(attrs), base, ['BASE','ONE','SUB'][scope]), None, err)
    return result
  
  def Search(self, base, sfilter, attrs=["*"], scope=ldap.SCOPE_SUBTREE):
    if not isinstance(attrs, list):
      attrs=attrs.split()
    return self._search(base, sfilter, scope, attrs)

  def SearchSub(self, base, sfilter="(objectClass=*)", attrs=["*"]):
    return self.Search(base, sfilter, attrs, ldap.SCOPE_SUBTREE)

  def SearchOne(self, base, sfilter="(objectClass=*)", attrs=["*"]):
    return self.Search(base, sfilter, attrs, ldap.SCOPE_ONELEVEL)
  
  def SearchBase(self, base, sfilter="(objectClass=*)", attrs=["*"]):
    return self.Search(base, sfilter, attrs, ldap.SCOPE_BASE)
  
