/*
  Copyright (C) 2000-2004 SKYRIX Software AG

  This file is part of OpenGroupware.org.

  OGo is free software; you can redistribute it and/or modify it under
  the terms of the GNU Lesser General Public License as published by the
  Free Software Foundation; either version 2, or (at your option) any
  later version.

  OGo is distributed in the hope that it will be useful, but WITHOUT ANY
  WARRANTY; without even the implied warranty of MERCHANTABILITY or
  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
  License for more details.

  You should have received a copy of the GNU Lesser General Public
  License along with OGo; see the file COPYING.  If not, write to the
  Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
  02111-1307, USA.
*/
// $Id$

#include "NGLdapConnection.h"
#include "NGLdapSearchResultEnumerator.h"
#include "NGLdapEntry.h"
#include "NGLdapAttribute.h"
#include "NGLdapModification.h"
#include "EOQualifier+LDAP.h"
#include "common.h"
#include <ldap.h>

static BOOL     LDAPDebugEnabled        = NO;
static BOOL     LDAPInitialBindSpecific = NO;
static NSString *LDAPInitialBindDN = @"" ;
static NSString *LDAPInitialBindPW = @"" ;

/* this is required by SuSE EMail Server III */
#define ISOLATIN1_CREDENTIALS 1

@interface NGLdapConnection(Privates)
- (BOOL)_reinit;
@end

@implementation NGLdapConnection

static void freeMods(LDAPMod **mods) {
  LDAPMod  *buf;
  unsigned i;
  
  if (mods == NULL)
    return;

  buf = mods[0];
  for (i = 0; mods[i] != NULL; i++) {
    struct berval **values;
    char *type;

    if ((values = buf[i].mod_bvalues) != NULL) {
      unsigned j;
      
      for (j = 0; values[j] != NULL; j++)
	free(values[j]);
      
      free(values);
    }
    
    if ((type = buf[i].mod_type) != NULL)
      free(type);
  }
  
  if (buf)  free(buf);
  if (mods) free(mods);
}

+ (void)initialize {
  NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
  static BOOL didInit = NO;
  if (didInit) return;
  didInit = YES;
  
  LDAPDebugEnabled        = [ud boolForKey:@"LDAPDebugEnabled"];
  LDAPInitialBindSpecific = [ud boolForKey:@"LDAPInitialBindSpecific"];
  LDAPInitialBindDN       = [[ud stringForKey:@"LDAPInitialBindDN"] copy];
  LDAPInitialBindPW       = [[ud stringForKey:@"LDAPInitialBindPW"] copy];
}

- (BOOL)_reinit {
  if (self->handle) {
    ldap_unbind(self->handle);
    self->handle = NULL;
  }
  
  self->handle = ldap_init((char *)[self->hostName cString], self->port);

  if (self->handle == NULL)
    return NO;

  return YES;
}

- (id)initWithHostName:(NSString *)_hostName port:(int)_port {
  self->hostName = [_hostName copy];
  self->port     = (_port != 0) ? _port : 389;

  if (![self _reinit]) {
    [self release];
    return nil;
  }
  
  [self setCacheTimeout:120.0];
  [self setCacheMaxMemoryUsage:16000];
  
  return self;
}
- (id)initWithHostName:(NSString *)_hostName {
  return [self initWithHostName:_hostName port:0];
}

- (void)dealloc {
  if (self->handle) {
    if ([self isBound])
      [self unbind];
    else {
      // call unbind to free resources
      int err;
      err = ldap_unbind(self->handle);
      self->handle = NULL;
    }

    // free handle
  }
  [self->hostName release];
  [super dealloc];
}

/* settings */

- (NSString *)hostName {
  return self->hostName;
}
- (int)port {
  return self->port;
}

/* internals */

- (void *)ldapHandle {
  return self->handle;
}

/* errors */

- (NSException *)_exceptionForErrorCode:(int)_err
  operation:(NSString *)_operation
  userInfo:(NSDictionary *)_ui
{
  NSException *e;
  NSString *name, *reason;

  name = @"LDAPException";

  switch (_err) {
    case LDAP_SUCCESS:
      return nil;

    case LDAP_INAPPROPRIATE_AUTH:
      reason = @"inappropriate authorization";
      break;

    case LDAP_INVALID_CREDENTIALS:
      reason = @"invalid credentials";
      break;
      
    case LDAP_INSUFFICIENT_ACCESS:
      reason = @"insufficient access";
      break;

    case LDAP_SERVER_DOWN:
      reason = @"the server is down";
      break;

    case LDAP_TIMEOUT:
      reason = @"the operation timed out";
      break;

    case LDAP_AUTH_UNKNOWN:
      reason = @"authorization unknown";
      break;
      
    case LDAP_NOT_ALLOWED_ON_NONLEAF:
      reason = @"operation not allowed on non-leaf record";
      break;
      
    default:
      reason = [NSString stringWithFormat:
                           @"operation %@ failed with code 0x%X",
                           _operation, _err];
      break;
  }

  e = [NSException exceptionWithName:name
                   reason:reason
                   userInfo:_ui];
  
  return e;
}

/* binding */

- (BOOL)isBound {
  return self->flags.isBound ? YES : NO;
}

- (void)unbind {
  if (self->flags.isBound) {
    int err;
    
    err = ldap_unbind(self->handle);
    self->flags.isBound = 0;
    self->handle = NULL;
  }
}

- (BOOL)bindWithMethod:(NSString *)_method
  binddn:(NSString *)_login credentials:(NSString *)_cred
{
  int ldap_version3 = LDAP_VERSION3 ;
  int method, err;
  const char *l, *p;

  if (self->handle == NULL)
    [self _reinit];
  
  if ((_method == nil) || ([_method isEqualToString:@"simple"])) {
    method = LDAP_AUTH_SIMPLE;
  }
  else if ([_method isEqualToString:@"krbv41"]) {
    method = LDAP_AUTH_KRBV41;
  }
  else if ([_method isEqualToString:@"krbv42"]) {
    method = LDAP_AUTH_KRBV42;
  }
  else
    /* unknown method */
    return NO;

  l = (char *)[_login UTF8String];
#if ISOLATIN1_CREDENTIALS
  p = (char *)[_cred  cString];
#else
  p = (char *)[_cred  UTF8String];
#endif
  err = (method == LDAP_AUTH_SIMPLE)
    ? ldap_simple_bind_s(self->handle, l, p)
    : ldap_bind_s(self->handle, l, p, method);
  
  if (err == LDAP_SUCCESS) {
    ldap_set_option(self->handle, LDAP_OPT_PROTOCOL_VERSION, &ldap_version3) ;
    ldap_set_option(self->handle, LDAP_OPT_REFERRALS, LDAP_OPT_OFF) ;
    self->flags.isBound = YES;
    return YES;
  }

  [[self _exceptionForErrorCode:err
         operation:@"bind"
         userInfo:[NSDictionary dictionaryWithObject:_login ? _login : @"<nil>"
                                forKey:@"login"]]
         raise];
  
  return NO;
}

/* running queries */

- (NSEnumerator *)_searchAtBaseDN:(NSString *)_base
  qualifier:(EOQualifier *)_q
  attributes:(NSArray *)_attributes
  scope:(int)_scope
{
  NSString *filter;
  int      msgid;
  char     **attrs;
  NGLdapSearchResultEnumerator *e;

  if (self->handle == NULL)
    [self _reinit];

  if ((filter = [_q ldapFilterString]) == nil)
    filter = @"(objectclass=*)";

  if (_attributes) {
    unsigned i, acount;

    acount = [_attributes count];
    attrs = calloc(acount + 1, sizeof(char *));
    
    for (i = 0; i < acount; i++)
      attrs[i] = (char *)[[_attributes objectAtIndex:i] UTF8String];
  }
  else
    attrs = NULL;

  if (LDAPDebugEnabled) 
    printf("%s: search with at base %s filter %s for attrs %s\n",
           __PRETTY_FUNCTION__, [_base cString], [filter cString],
           [[_attributes description] cString]);

  msgid = ldap_search(self->handle,
                      (char *)[_base UTF8String],
                      _scope,
                      (char *)[filter UTF8String],                      
                      attrs,
                      0);

  /* free attributes */
  if (attrs) {
    free(attrs); attrs = NULL;
  }
  
  if (msgid == -1) {
    /* trouble */
    return nil;
  }
  
  e = [[NGLdapSearchResultEnumerator alloc]
                                     initWithConnection:self messageID:msgid];

  return [e autorelease];
}

- (NSEnumerator *)flatSearchAtBaseDN:(NSString *)_base
  qualifier:(EOQualifier *)_q
  attributes:(NSArray *)_attributes
{
  return [self _searchAtBaseDN:_base
               qualifier:_q
               attributes:_attributes
               scope:LDAP_SCOPE_ONELEVEL];
}

- (NSEnumerator *)deepSearchAtBaseDN:(NSString *)_base
  qualifier:(EOQualifier *)_q
  attributes:(NSArray *)_attributes
{
  return [self _searchAtBaseDN:_base
               qualifier:_q
               attributes:_attributes
               scope:LDAP_SCOPE_SUBTREE];
}

- (NSEnumerator *)baseSearchAtBaseDN:(NSString *)_base
  qualifier:(EOQualifier *)_q
  attributes:(NSArray *)_attributes
{
  return [self _searchAtBaseDN:_base
               qualifier:_q
               attributes:_attributes
               scope:LDAP_SCOPE_BASE];
}

- (NGLdapEntry *)entryAtDN:(NSString *)_dn attributes:(NSArray *)_attrs {
  NSEnumerator *e;
  NGLdapEntry  *entry;
  
  e = [self _searchAtBaseDN:_dn
            qualifier:nil
            attributes:_attrs
            scope:LDAP_SCOPE_BASE];
  
  entry = [e nextObject];
  
  if ([e nextObject]) {
    NSLog(@"more than one search results in base search !!!");
    /* consume all entries */
    while ([e nextObject])
      ;
  }
  
  return entry;
}

/* cache */

- (void)setCacheTimeout:(NSTimeInterval)_to {
  if (self->cacheTimeout != _to) {
    self->cacheTimeout = _to;

    if (self->isCacheEnabled) {
#if LDAP_API_VERSION > 2000
      NSLog(@"WARNING(%s): setting cache-timeout unsupported on the client "
	    @"library version!", __PRETTY_FUNCTION__);
#else
      ldap_disable_cache(self->handle);
      ldap_enable_cache(self->handle, _to, [self cacheMaxMemoryUsage]);
#endif
    }
  }
}
- (NSTimeInterval)cacheTimeout {
  return self->cacheTimeout;
}

- (void)setCacheMaxMemoryUsage:(long)_maxMem {
  if (self->cacheMaxMemory != _maxMem) {
    self->cacheMaxMemory = _maxMem;

    if (self->isCacheEnabled) {
#if LDAP_API_VERSION > 2000
      NSLog(@"WARNING(%s): setting maxmem usage unsupported on the client "
	    @"library version!", __PRETTY_FUNCTION__);
#else
      ldap_disable_cache(self->handle);
      ldap_enable_cache(self->handle, [self cacheTimeout], _maxMem);
#endif
    }
  }
}
- (long)cacheMaxMemoryUsage {
  return self->cacheMaxMemory;
}

- (void)setUseCache:(BOOL)_flag {
  if (_flag) {
#if LDAP_API_VERSION > 2000
      NSLog(@"WARNING(%s): setting cache-usage unsupported on the client "
	    @"library version!", __PRETTY_FUNCTION__);
#else
    ldap_enable_cache(self->handle,
                      [self cacheTimeout], [self cacheMaxMemoryUsage]);
#endif
    self->isCacheEnabled = YES;
  }
  else {
#if LDAP_API_VERSION > 2000
      NSLog(@"WARNING(%s): setting cache-usage unsupported on the client "
	    @"library version!", __PRETTY_FUNCTION__);
#else
    ldap_disable_cache(self->handle);
#endif
    self->isCacheEnabled = NO;
  }
}
- (BOOL)doesUseCache {
  return self->isCacheEnabled;
}

- (void)flushCache {
#if !(LDAP_API_VERSION > 2000)
  ldap_flush_cache(self->handle);
#endif
}
- (void)destroyCache {
#if !(LDAP_API_VERSION > 2000)
  ldap_destroy_cache(self->handle);
#endif
  self->isCacheEnabled = NO;
}

- (void)cacheForgetEntryWithDN:(NSString *)_dn {
  if (_dn == nil) return;
#if !(LDAP_API_VERSION > 2000)
  ldap_uncache_entry(self->handle, (char *)[_dn UTF8String]);
#endif
}

/* modifications */

- (BOOL)addEntry:(NGLdapEntry *)_entry {
  int         msgid, res;
  LDAPMod     **attrs;
  LDAPMessage *msg;
  LDAPMod     *attrBuf;
  unsigned    count;
  
  attrs   = NULL;
  attrBuf = NULL;
  
  /* construct attributes */
  {
    unsigned        i;
    NSEnumerator    *e;
    NGLdapAttribute *attribute;
    
    count = [_entry count];
    
    attrBuf = calloc(count, sizeof(LDAPMod));
    NSAssert(attrBuf, @"couldn't allocate attribute buffer");

    attrs = calloc(count + 1, sizeof(LDAPMod *));
    NSAssert(attrs, @"couldn't allocate attribute ptr buffer");

    e = [[[_entry attributes] allValues] objectEnumerator];
    for (i = 0; (attribute = [e nextObject]) && (i < count); i++) {
      unsigned      valCount, j;
      struct berval **values;
      NSEnumerator  *ve;
      NSData        *v;
      char          *attrName;
      NSString      *key;

      key = [attribute attributeName];
      
      valCount = [attribute count];
      values = calloc(valCount + 1, sizeof(struct berval *));

      ve = [attribute valueEnumerator];
      for (j = 0; (v = [ve nextObject]) && (j < valCount); j++) {
        struct berval *bv;

        bv = malloc(sizeof(struct berval));
        
        bv->bv_len = [v length];
        bv->bv_val = (void *)[v bytes];
        values[j] = bv;
      }
      values[valCount] = NULL;

      /* TODO: use UTF-8, UNICODE */
      attrName = malloc([key cStringLength] + 1);
      [key getCString:attrName];
      
      attrBuf[i].mod_op      = LDAP_MOD_BVALUES;
      attrBuf[i].mod_type    = attrName;
      attrBuf[i].mod_bvalues = values;
      attrs[i] = &(attrBuf[i]);
    }
    attrs[count] = NULL;
  }
  
  /* start operation */

  msgid = ldap_add(self->handle, (char *)[[_entry dn] UTF8String], attrs);

  /* deconstruct attributes */

  freeMods(attrs);
  attrs   = NULL;
  attrBuf = NULL;

  /* check operation return value */
  
  if (msgid == -1) {
    [[self _exceptionForErrorCode:
	     0 /* was in v1: ((LDAP *)self->handle)->ld_errno */
           operation:@"add"
           userInfo:[NSDictionary dictionaryWithObject:_entry forKey:@"entry"]]
           raise];
    return NO;
  }
  
  /* process result */
  
  msg = NULL;
  res = ldap_result(self->handle, msgid, 0, NULL /* timeout */, &msg);

  if (res == -1) {
    /* error */
    int err;

    err = ldap_result2error(self->handle, msg, 1 /* free msg */);
    [[self _exceptionForErrorCode:err
           operation:@"add"
           userInfo:[NSDictionary dictionaryWithObject:_entry forKey:@"entry"]]
           raise];
    
    return NO;
  }
  
  if (msg) ldap_msgfree(msg);
  
  return YES;
}

/* comparing */

- (BOOL)compareAttribute:(NSString *)_attr ofEntryWithDN:(NSString *)_dn
  withValue:(id)_value
{
  int res;
  
  if (_dn == nil)
    return NO;

  res = ldap_compare_s(self->handle,
                       (char *)[_dn UTF8String],
                       (char *)[_attr UTF8String],
                       (char *)[[_value stringValue] UTF8String]);
  
  if (res == LDAP_COMPARE_TRUE)
    return YES;
  if (res == LDAP_COMPARE_FALSE)
    return NO;

  [[self _exceptionForErrorCode:res
         operation:@"compare"
         userInfo:[NSDictionary dictionaryWithObject:_dn forKey:@"dn"]]
         raise];
  
  return NO;
}

- (BOOL)removeEntryWithDN:(NSString *)_dn {
  int res;

  if (_dn == nil)
    return YES;

  res = ldap_delete_s(self->handle, (char *)[_dn UTF8String]);

  if (res == LDAP_SUCCESS)
    return YES;

  [[self _exceptionForErrorCode:res
         operation:@"delete"
         userInfo:[NSDictionary dictionaryWithObject:_dn forKey:@"dn"]]
         raise];
  
  return NO;
}

- (BOOL)modifyEntryWithDN:(NSString *)_dn changes:(NSArray *)_mods {
  int      res;
  LDAPMod  **mods;
  LDAPMod  *modBuf;
  unsigned i, count;

  if (_dn == nil)
    return NO;

  if ((count = [_mods count]) == 0)
    return YES;

  /* construct mods */

  mods   = calloc(count + 1, sizeof(LDAPMod *));
  modBuf = calloc(count, sizeof(LDAPMod));
  NSAssert(mods,   @"couldn't allocate modification array");
  NSAssert(modBuf, @"couldn't allocate modification buffer");

  for (i = 0; i < count; i++) {
    NGLdapModification *mod;
    NGLdapAttribute    *attr;
    NSString           *attrName;
    unsigned           attrLen;
    unsigned           valCount;
    NSEnumerator       *e;
    NSData             *value;
    struct berval      **values;
    unsigned           j;

    mod = [_mods objectAtIndex:i];
    mods[i] = &(modBuf[i]);

    switch ([mod operation]) {
      case NGLdapAddAttribute:
        modBuf[i].mod_op = LDAP_MOD_ADD;
        break;
      case NGLdapDeleteAttribute:
        modBuf[i].mod_op = LDAP_MOD_DELETE;
        break;
      case NGLdapReplaceAttribute:
        modBuf[i].mod_op = LDAP_MOD_REPLACE;
        break;
    }
    modBuf[i].mod_op |= LDAP_MOD_BVALUES;

    attr     = [mod      attribute];
    attrName = [attr     attributeName];
    /* TODO: use UTF-8, UNICODE */
    attrLen  = [attrName cStringLength];

    modBuf[i].mod_type = malloc(attrLen + 1);
    [attrName getCString:modBuf[i].mod_type];

    valCount = [attr count];
    values = calloc(valCount + 1, sizeof(struct berval *));
    
    e = [attr valueEnumerator];
    for (j = 0; (value = [e nextObject]) && (j < valCount); j++) {
      struct berval *bv;

      bv = malloc(sizeof(struct berval));
      bv->bv_len = [value length];
      bv->bv_val = (void *)[value bytes];
      values[j] = bv;
    }
    values[valCount] = NULL;

    modBuf[i].mod_bvalues = values;
  }
  mods[count] = NULL;

  /* run modify */

  res = ldap_modify_s(self->handle, (char *)[_dn UTF8String], mods);

  /* free structures */

  freeMods(mods);
  mods   = NULL;
  modBuf = NULL;

  /* check result */
  
  if (res == -1) {
    [[self _exceptionForErrorCode:
	     0 /* was in v1: ((LDAP *)self->handle)->ld_errno */
           operation:@"modify"
           userInfo:[NSDictionary dictionaryWithObject:_dn forKey:@"dn"]]
           raise];
    return NO;
  }
  return YES;
}

/* root DSE */

- (NGLdapEntry *)schemaEntry {
  NGLdapEntry *e;
  
  if ((e = [self entryAtDN:@"cn=schema" attributes:nil]))
    return e;
  
  return nil;
}

- (NGLdapEntry *)rootDSE {
  NGLdapEntry *e;
  
  if ((e = [self entryAtDN:@"" attributes:nil]))
    return e;
  
  return nil;
}

- (NGLdapEntry *)configEntry {
  NGLdapEntry *e;
  
  if ((e = [self entryAtDN:@"cn=config" attributes:nil]))
    return e;
  
  return nil;
}

- (NSArray *)namingContexts {
  NGLdapEntry    *e;
  NSEnumerator   *values;
  NSString       *value;
  NSMutableArray *ma;
  
  if ((e = [self rootDSE])) {
    /* LDAP v3 */
    return [[e attributeWithName:@"namingcontexts"] allStringValues];
  }
  
  if ((e = [self configEntry]) == nil)
    return nil;
  
  /* OpenLDAP */
    
  values = [[e attributeWithName:@"database"] stringValueEnumerator];
  ma     = [NSMutableArray arrayWithCapacity:4];

  while ((value = [values nextObject])) {
    NSRange r;
      
    r = [value rangeOfString:@":"];
    if (r.length == 0)
      /* couldn't parse value */
      continue;
      
    value = [value substringFromIndex:(r.location + r.length)];
    [ma addObject:value];
  }
  return ma;
}

/* description */

- (NSString *)description {
  NSMutableString *s;

  s = [NSMutableString stringWithCapacity:100];
  [s appendFormat:@"<0x%08X[%@]:", self, NSStringFromClass([self class])];
  
  if ([self isBound])
    [s appendString:@" bound"];
  
  if ([self doesUseCache]) {
    [s appendFormat:@" cache[to=%.2fs,mem=%i]",
         [self cacheTimeout], [self cacheMaxMemoryUsage]];
  }
  
  [s appendString:@">"];

  return s;
}

@end /* NGLdapConnection */

@implementation NGLdapConnection(PlainPasswordCheck)

+ (NSString *)uidAttributeName {
  static NSString *uidAttr = nil;
  if (uidAttr == nil) {
    uidAttr = [[[NSUserDefaults standardUserDefaults] 
                 stringForKey:@"LDAPLoginAttributeName"] copy];
    if ([uidAttr length] == 0) uidAttr = @"uid";
  }
  return uidAttr;
}

- (NSString *)dnForLogin:(NSString *)_login baseDN:(NSString *)_baseDN {
  NSString    *filter;
  char        *attrs[2];
  LDAPMessage *result;
  LDAPMessage *entry;
  char        *dn;
  BOOL        didBind = NO;
  int         matchCount;
  NSString    *strDN;
  int         ldap_search_result ;

  if (LDAPDebugEnabled)
    [self logWithFormat:@"dn for login '%@' on %@", _login, _baseDN];
  
  if (self->handle == NULL) {
    if (![self _reinit]) {
      NSLog(@"%s: _reinit failed...:", __PRETTY_FUNCTION__);
      return nil;
    }
  }
  if (![self isBound]) {
    didBind = NO;
    if (LDAPDebugEnabled)
      [self logWithFormat:@"  attempt to do a simple, anonymous bind .."];
    
    NS_DURING
      if (LDAPInitialBindSpecific)
        didBind = [self bindWithMethod:@"simple" binddn:LDAPInitialBindDN credentials:LDAPInitialBindPW];
      else
        didBind = [self bindWithMethod:@"simple" binddn:@"" credentials:@""];
    NS_HANDLER
      didBind = NO;
    NS_ENDHANDLER;

    if (!didBind) {
      /* couldn't bind */
      if (LDAPDebugEnabled) [self logWithFormat:@"  bind failed !"];
      return nil;
    }
    didBind = YES;
    if (LDAPDebugEnabled) [self logWithFormat:@"  bound."];
  }
  filter = [NSString stringWithFormat:@"(%@=%@)",
                       [[self class] uidAttributeName],
                       _login];
  if (LDAPDebugEnabled) [self logWithFormat:@"  search: '%@'", filter];
  
  /* we only check the DN anyway .. */
  attrs[0] = "objectclass";
  attrs[1] = NULL;
  
  ldap_search_result = ldap_search_s(self->handle,
                    (char *)[_baseDN UTF8String],
                    LDAP_SCOPE_SUBTREE,
                    (char *)[filter UTF8String],
                    attrs, 1,
                    &result) ;
  if ((ldap_search_result != LDAP_SUCCESS) && (ldap_search_result != LDAP_PARTIAL_RESULTS)) {
    /* search failed */
    if (didBind)
      [self unbind];

    if (LDAPDebugEnabled) {
      [self logWithFormat:@"  search failed"];
    }
    return nil;
  }

  /*
    If the entry count is not equal to one, either the UID was not unique or
    there was no match
  */
  if (((matchCount = ldap_count_entries(self->handle, result))) != 1) {
    if (didBind) [self unbind];
    if (LDAPDebugEnabled)
      [self logWithFormat:@"  failed: %i matches", matchCount];
    return nil;
  }
  
  /* get first entry */
  if ((entry = ldap_first_entry(self->handle, result)) == NULL) {
    if (didBind) [self unbind];
    if (LDAPDebugEnabled) 
      [self logWithFormat:@"  could not retrieve first entry !"];
    return nil;
  }

  /* get DN of first entry */
  if ((dn = ldap_get_dn(self->handle, entry)) == NULL) {
    /* couldn't get DN */
    if (didBind) [self unbind];
    if (LDAPDebugEnabled) [self logWithFormat:@"  got no DN for entry !"];
    return nil;
  }
  strDN = nil;
  NS_DURING {
    strDN = [[[NSString alloc] initWithUTF8String:dn] autorelease];
  }
  NS_HANDLER {
    fprintf(stderr, "Got exception %s while NSUTF8StringEncoding, "
            "use defaultCStringEncoding",
            [[localException description] cString]);
    strDN = nil;
  }
  NS_ENDHANDLER;

  if (strDN == nil) {
    if (LDAPDebugEnabled) {
      [self debugWithFormat:
            @"could not convert DN to UTF-8 string, try cString .."];
    }
    strDN = [[[NSString alloc] initWithCString:dn] autorelease];
  }
  free(dn); dn = NULL;

  if (result) {
    ldap_msgfree(result);
  }
  [self unbind];
  if (LDAPDebugEnabled) {
    [self logWithFormat:@"   return DN %@", strDN];
  }
  return strDN;
}

- (BOOL)checkPassword:(NSString *)_pwd ofLogin:(NSString *)_login
  atBaseDN:(NSString *)_baseDN
{
  BOOL        didBind;
  NSString    *strDN; 

  if (LDAPDebugEnabled)
    [self logWithFormat:@"check pwd of login '%@' on %@", _login, _baseDN];
  
  if (self->handle == NULL) {
    if (![self _reinit]) {
      NSLog(@"%s: _reinit failed...:", __PRETTY_FUNCTION__);
    }
  }
  strDN = [self dnForLogin:_login baseDN:_baseDN];

  if (LDAPDebugEnabled) {
    [self logWithFormat:@"  attempting to bind login %@ DN: %@ %s!",
          _login, strDN,
          [_pwd length] > 0 ? "(with password) " : "(empty password) "];
  }
  
  if (!strDN) {
    if (LDAPDebugEnabled) {
      [self logWithFormat:@"  missing dn for login %@ atBaseDN %@",
            _login, _baseDN];
    }
    return NO;
  }
  /*
    Now bind as the DN with the password supplied earlier...
    Successful bind means the password was correct, otherwise the
    password is invalid.
  */

  didBind = NO;
  NS_DURING
    didBind = [self bindWithMethod:@"simple" binddn:strDN credentials:_pwd];
  NS_HANDLER
    didBind = NO;
  NS_ENDHANDLER;
  
  if (!didBind) {
    /* invalid login or password */
    if (LDAPDebugEnabled) 
      [self logWithFormat:@"  could not simple bind DN '%@' !", strDN];
    
    [self unbind];
    return NO;
  }
  [self unbind];
  if (LDAPDebugEnabled) [self logWithFormat:@"  bound successfully !"];
  return YES;
}

+ (BOOL)checkPassword:(NSString *)_pwd ofLogin:(NSString *)_login
  atBaseDN:(NSString *)_baseDN
  onHost:(NSString *)_hostName port:(int)_port
{
  NGLdapConnection *ldap;
  
  if (LDAPDebugEnabled) {
    NSLog(@"LDAP: check pwd of login '%@' on %@,%i,%@ ...",
          _login, _hostName, _port, _baseDN);
  }
  
  if ((ldap = [[self alloc] initWithHostName:_hostName port:_port]) == nil) {
    if (LDAPDebugEnabled)
      NSLog(@"LDAP:   got no connection to %@,%i ...", _hostName, _port);
    return NO;
  }
  ldap = [ldap autorelease];
  if (LDAPDebugEnabled)
    NSLog(@"LDAP:   use connection: %@", ldap);
  
  return [ldap checkPassword:_pwd ofLogin:_login atBaseDN:_baseDN];
}

@end /* NGLdapConnection(PlainPasswordCheck) */
