]> err.no Git - sope/blob - sope-ldap/NGLdap/NGLdapConnection.m
fixed copyrights for 2005
[sope] / sope-ldap / NGLdap / NGLdapConnection.m
1 /*
2   Copyright (C) 2000-2005 SKYRIX Software AG
3
4   This file is part of SOPE.
5
6   SOPE is free software; you can redistribute it and/or modify it under
7   the terms of the GNU Lesser General Public License as published by the
8   Free Software Foundation; either version 2, or (at your option) any
9   later version.
10
11   SOPE is distributed in the hope that it will be useful, but WITHOUT ANY
12   WARRANTY; without even the implied warranty of MERCHANTABILITY or
13   FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
14   License for more details.
15
16   You should have received a copy of the GNU Lesser General Public
17   License along with SOPE; see the file COPYING.  If not, write to the
18   Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
19   02111-1307, USA.
20 */
21
22 #include "NGLdapConnection.h"
23 #include "NGLdapSearchResultEnumerator.h"
24 #include "NGLdapEntry.h"
25 #include "NGLdapAttribute.h"
26 #include "NGLdapModification.h"
27 #include "EOQualifier+LDAP.h"
28 #include "common.h"
29 #include <ldap.h>
30
31 static BOOL     LDAPDebugEnabled        = NO;
32 static BOOL     LDAPInitialBindSpecific = NO;
33 static NSString *LDAPInitialBindDN = @"" ;
34 static NSString *LDAPInitialBindPW = @"" ;
35
36 /* this is required by SuSE EMail Server III */
37 #define ISOLATIN1_CREDENTIALS 1
38
39 @interface NGLdapConnection(Privates)
40 - (BOOL)_reinit;
41 @end
42
43 @implementation NGLdapConnection
44
45 static void freeMods(LDAPMod **mods) {
46   LDAPMod  *buf;
47   unsigned i;
48   
49   if (mods == NULL)
50     return;
51
52   buf = mods[0];
53   for (i = 0; mods[i] != NULL; i++) {
54     struct berval **values;
55     char *type;
56
57     if ((values = buf[i].mod_bvalues) != NULL) {
58       unsigned j;
59       
60       for (j = 0; values[j] != NULL; j++)
61         free(values[j]);
62       
63       free(values);
64     }
65     
66     if ((type = buf[i].mod_type) != NULL)
67       free(type);
68   }
69   
70   if (buf)  free(buf);
71   if (mods) free(mods);
72 }
73
74 + (void)initialize {
75   NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
76   static BOOL didInit = NO;
77   if (didInit) return;
78   didInit = YES;
79   
80   LDAPDebugEnabled        = [ud boolForKey:@"LDAPDebugEnabled"];
81   LDAPInitialBindSpecific = [ud boolForKey:@"LDAPInitialBindSpecific"];
82   LDAPInitialBindDN       = [[ud stringForKey:@"LDAPInitialBindDN"] copy];
83   LDAPInitialBindPW       = [[ud stringForKey:@"LDAPInitialBindPW"] copy];
84 }
85
86 - (BOOL)_reinit {
87   if (self->handle) {
88     ldap_unbind(self->handle);
89     self->handle = NULL;
90   }
91   
92   self->handle = ldap_init((char *)[self->hostName cString], self->port);
93
94   if (self->handle == NULL)
95     return NO;
96
97   return YES;
98 }
99
100 - (id)initWithHostName:(NSString *)_hostName port:(int)_port {
101   self->hostName = [_hostName copy];
102   self->port     = (_port != 0) ? _port : 389;
103
104   if (![self _reinit]) {
105     [self release];
106     return nil;
107   }
108   
109   [self setCacheTimeout:120.0];
110   [self setCacheMaxMemoryUsage:16000];
111   
112   return self;
113 }
114 - (id)initWithHostName:(NSString *)_hostName {
115   return [self initWithHostName:_hostName port:0];
116 }
117
118 - (void)dealloc {
119   if (self->handle) {
120     if ([self isBound])
121       [self unbind];
122     else {
123       // call unbind to free resources
124       int err;
125       err = ldap_unbind(self->handle);
126       self->handle = NULL;
127     }
128
129     // free handle
130   }
131   [self->hostName release];
132   [super dealloc];
133 }
134
135 /* settings */
136
137 - (NSString *)hostName {
138   return self->hostName;
139 }
140 - (int)port {
141   return self->port;
142 }
143
144 /* internals */
145
146 - (void *)ldapHandle {
147   return self->handle;
148 }
149
150 /* errors */
151
152 - (NSException *)_exceptionForErrorCode:(int)_err
153   operation:(NSString *)_operation
154   userInfo:(NSDictionary *)_ui
155 {
156   NSException *e;
157   NSString *name, *reason;
158
159   name = @"LDAPException";
160
161   switch (_err) {
162     case LDAP_SUCCESS:
163       return nil;
164
165     case LDAP_INAPPROPRIATE_AUTH:
166       reason = @"inappropriate authorization";
167       break;
168
169     case LDAP_INVALID_CREDENTIALS:
170       reason = @"invalid credentials";
171       break;
172       
173     case LDAP_INSUFFICIENT_ACCESS:
174       reason = @"insufficient access";
175       break;
176
177     case LDAP_SERVER_DOWN:
178       reason = @"the server is down";
179       break;
180
181     case LDAP_TIMEOUT:
182       reason = @"the operation timed out";
183       break;
184
185     case LDAP_AUTH_UNKNOWN:
186       reason = @"authorization unknown";
187       break;
188       
189     case LDAP_NOT_ALLOWED_ON_NONLEAF:
190       reason = @"operation not allowed on non-leaf record";
191       break;
192       
193     default:
194       reason = [NSString stringWithFormat:
195                            @"operation %@ failed with code 0x%X",
196                            _operation, _err];
197       break;
198   }
199
200   e = [NSException exceptionWithName:name
201                    reason:reason
202                    userInfo:_ui];
203   
204   return e;
205 }
206
207 /* binding */
208
209 - (BOOL)isBound {
210   return self->flags.isBound ? YES : NO;
211 }
212
213 - (void)unbind {
214   if (self->flags.isBound) {
215     int err;
216     
217     err = ldap_unbind(self->handle);
218     self->flags.isBound = 0;
219     self->handle = NULL;
220   }
221 }
222
223 - (BOOL)bindWithMethod:(NSString *)_method
224   binddn:(NSString *)_login credentials:(NSString *)_cred
225 {
226   int ldap_version3 = LDAP_VERSION3 ;
227   int method, err;
228   const char *l, *p;
229
230   if (self->handle == NULL)
231     [self _reinit];
232   
233   if ((_method == nil) || ([_method isEqualToString:@"simple"])) {
234     method = LDAP_AUTH_SIMPLE;
235   }
236   else if ([_method isEqualToString:@"krbv41"]) {
237     method = LDAP_AUTH_KRBV41;
238   }
239   else if ([_method isEqualToString:@"krbv42"]) {
240     method = LDAP_AUTH_KRBV42;
241   }
242   else
243     /* unknown method */
244     return NO;
245
246   l = (char *)[_login UTF8String];
247 #if ISOLATIN1_CREDENTIALS
248   p = (char *)[_cred  cString];
249 #else
250   p = (char *)[_cred  UTF8String];
251 #endif
252   err = (method == LDAP_AUTH_SIMPLE)
253     ? ldap_simple_bind_s(self->handle, l, p)
254     : ldap_bind_s(self->handle, l, p, method);
255   
256   if (err == LDAP_SUCCESS) {
257     ldap_set_option(self->handle, LDAP_OPT_PROTOCOL_VERSION, &ldap_version3) ;
258     ldap_set_option(self->handle, LDAP_OPT_REFERRALS, LDAP_OPT_OFF) ;
259     self->flags.isBound = YES;
260     return YES;
261   }
262
263   [[self _exceptionForErrorCode:err
264          operation:@"bind"
265          userInfo:[NSDictionary dictionaryWithObject:_login ? _login : @"<nil>"
266                                 forKey:@"login"]]
267          raise];
268   
269   return NO;
270 }
271
272 /* running queries */
273
274 - (NSEnumerator *)_searchAtBaseDN:(NSString *)_base
275   qualifier:(EOQualifier *)_q
276   attributes:(NSArray *)_attributes
277   scope:(int)_scope
278 {
279   NSString *filter;
280   int      msgid;
281   char     **attrs;
282   NGLdapSearchResultEnumerator *e;
283
284   if (self->handle == NULL)
285     [self _reinit];
286
287   if ((filter = [_q ldapFilterString]) == nil)
288     filter = @"(objectclass=*)";
289
290   if (_attributes) {
291     unsigned i, acount;
292
293     acount = [_attributes count];
294     attrs = calloc(acount + 1, sizeof(char *));
295     
296     for (i = 0; i < acount; i++)
297       attrs[i] = (char *)[[_attributes objectAtIndex:i] UTF8String];
298   }
299   else
300     attrs = NULL;
301
302   if (LDAPDebugEnabled) 
303     printf("%s: search with at base %s filter %s for attrs %s\n",
304            __PRETTY_FUNCTION__, [_base cString], [filter cString],
305            [[_attributes description] cString]);
306
307   msgid = ldap_search(self->handle,
308                       (char *)[_base UTF8String],
309                       _scope,
310                       (char *)[filter UTF8String],                      
311                       attrs,
312                       0);
313
314   /* free attributes */
315   if (attrs) {
316     free(attrs); attrs = NULL;
317   }
318   
319   if (msgid == -1) {
320     /* trouble */
321     return nil;
322   }
323   
324   e = [[NGLdapSearchResultEnumerator alloc]
325                                      initWithConnection:self messageID:msgid];
326
327   return [e autorelease];
328 }
329
330 - (NSEnumerator *)flatSearchAtBaseDN:(NSString *)_base
331   qualifier:(EOQualifier *)_q
332   attributes:(NSArray *)_attributes
333 {
334   return [self _searchAtBaseDN:_base
335                qualifier:_q
336                attributes:_attributes
337                scope:LDAP_SCOPE_ONELEVEL];
338 }
339
340 - (NSEnumerator *)deepSearchAtBaseDN:(NSString *)_base
341   qualifier:(EOQualifier *)_q
342   attributes:(NSArray *)_attributes
343 {
344   return [self _searchAtBaseDN:_base
345                qualifier:_q
346                attributes:_attributes
347                scope:LDAP_SCOPE_SUBTREE];
348 }
349
350 - (NSEnumerator *)baseSearchAtBaseDN:(NSString *)_base
351   qualifier:(EOQualifier *)_q
352   attributes:(NSArray *)_attributes
353 {
354   return [self _searchAtBaseDN:_base
355                qualifier:_q
356                attributes:_attributes
357                scope:LDAP_SCOPE_BASE];
358 }
359
360 - (NGLdapEntry *)entryAtDN:(NSString *)_dn attributes:(NSArray *)_attrs {
361   NSEnumerator *e;
362   NGLdapEntry  *entry;
363   
364   e = [self _searchAtBaseDN:_dn
365             qualifier:nil
366             attributes:_attrs
367             scope:LDAP_SCOPE_BASE];
368   
369   entry = [e nextObject];
370   
371   if ([e nextObject]) {
372     NSLog(@"more than one search results in base search !!!");
373     /* consume all entries */
374     while ([e nextObject])
375       ;
376   }
377   
378   return entry;
379 }
380
381 /* cache */
382
383 - (void)setCacheTimeout:(NSTimeInterval)_to {
384   if (self->cacheTimeout != _to) {
385     self->cacheTimeout = _to;
386
387     if (self->isCacheEnabled) {
388 #if LDAP_API_VERSION > 2000
389       NSLog(@"WARNING(%s): setting cache-timeout unsupported on the client "
390             @"library version!", __PRETTY_FUNCTION__);
391 #else
392       ldap_disable_cache(self->handle);
393       ldap_enable_cache(self->handle, _to, [self cacheMaxMemoryUsage]);
394 #endif
395     }
396   }
397 }
398 - (NSTimeInterval)cacheTimeout {
399   return self->cacheTimeout;
400 }
401
402 - (void)setCacheMaxMemoryUsage:(long)_maxMem {
403   if (self->cacheMaxMemory != _maxMem) {
404     self->cacheMaxMemory = _maxMem;
405
406     if (self->isCacheEnabled) {
407 #if LDAP_API_VERSION > 2000
408       NSLog(@"WARNING(%s): setting maxmem usage unsupported on the client "
409             @"library version!", __PRETTY_FUNCTION__);
410 #else
411       ldap_disable_cache(self->handle);
412       ldap_enable_cache(self->handle, [self cacheTimeout], _maxMem);
413 #endif
414     }
415   }
416 }
417 - (long)cacheMaxMemoryUsage {
418   return self->cacheMaxMemory;
419 }
420
421 - (void)setUseCache:(BOOL)_flag {
422   if (_flag) {
423 #if LDAP_API_VERSION > 2000
424       NSLog(@"WARNING(%s): setting cache-usage unsupported on the client "
425             @"library version!", __PRETTY_FUNCTION__);
426 #else
427     ldap_enable_cache(self->handle,
428                       [self cacheTimeout], [self cacheMaxMemoryUsage]);
429 #endif
430     self->isCacheEnabled = YES;
431   }
432   else {
433 #if LDAP_API_VERSION > 2000
434       NSLog(@"WARNING(%s): setting cache-usage unsupported on the client "
435             @"library version!", __PRETTY_FUNCTION__);
436 #else
437     ldap_disable_cache(self->handle);
438 #endif
439     self->isCacheEnabled = NO;
440   }
441 }
442 - (BOOL)doesUseCache {
443   return self->isCacheEnabled;
444 }
445
446 - (void)flushCache {
447 #if !(LDAP_API_VERSION > 2000)
448   ldap_flush_cache(self->handle);
449 #endif
450 }
451 - (void)destroyCache {
452 #if !(LDAP_API_VERSION > 2000)
453   ldap_destroy_cache(self->handle);
454 #endif
455   self->isCacheEnabled = NO;
456 }
457
458 - (void)cacheForgetEntryWithDN:(NSString *)_dn {
459   if (_dn == nil) return;
460 #if !(LDAP_API_VERSION > 2000)
461   ldap_uncache_entry(self->handle, (char *)[_dn UTF8String]);
462 #endif
463 }
464
465 /* modifications */
466
467 - (BOOL)addEntry:(NGLdapEntry *)_entry {
468   int         msgid, res;
469   LDAPMod     **attrs;
470   LDAPMessage *msg;
471   LDAPMod     *attrBuf;
472   unsigned    count;
473   
474   attrs   = NULL;
475   attrBuf = NULL;
476   
477   /* construct attributes */
478   {
479     unsigned        i;
480     NSEnumerator    *e;
481     NGLdapAttribute *attribute;
482     
483     count = [_entry count];
484     
485     attrBuf = calloc(count, sizeof(LDAPMod));
486     NSAssert(attrBuf, @"couldn't allocate attribute buffer");
487
488     attrs = calloc(count + 1, sizeof(LDAPMod *));
489     NSAssert(attrs, @"couldn't allocate attribute ptr buffer");
490
491     e = [[[_entry attributes] allValues] objectEnumerator];
492     for (i = 0; (attribute = [e nextObject]) && (i < count); i++) {
493       unsigned      valCount, j;
494       struct berval **values;
495       NSEnumerator  *ve;
496       NSData        *v;
497       char          *attrName;
498       NSString      *key;
499
500       key = [attribute attributeName];
501       
502       valCount = [attribute count];
503       values = calloc(valCount + 1, sizeof(struct berval *));
504
505       ve = [attribute valueEnumerator];
506       for (j = 0; (v = [ve nextObject]) && (j < valCount); j++) {
507         struct berval *bv;
508
509         bv = malloc(sizeof(struct berval));
510         
511         bv->bv_len = [v length];
512         bv->bv_val = (void *)[v bytes];
513         values[j] = bv;
514       }
515       values[valCount] = NULL;
516
517       /* TODO: use UTF-8, UNICODE */
518       attrName = malloc([key cStringLength] + 1);
519       [key getCString:attrName];
520       
521       attrBuf[i].mod_op      = LDAP_MOD_BVALUES;
522       attrBuf[i].mod_type    = attrName;
523       attrBuf[i].mod_bvalues = values;
524       attrs[i] = &(attrBuf[i]);
525     }
526     attrs[count] = NULL;
527   }
528   
529   /* start operation */
530
531   msgid = ldap_add(self->handle, (char *)[[_entry dn] UTF8String], attrs);
532
533   /* deconstruct attributes */
534
535   freeMods(attrs);
536   attrs   = NULL;
537   attrBuf = NULL;
538
539   /* check operation return value */
540   
541   if (msgid == -1) {
542     [[self _exceptionForErrorCode:
543              0 /* was in v1: ((LDAP *)self->handle)->ld_errno */
544            operation:@"add"
545            userInfo:[NSDictionary dictionaryWithObject:_entry forKey:@"entry"]]
546            raise];
547     return NO;
548   }
549   
550   /* process result */
551   
552   msg = NULL;
553   res = ldap_result(self->handle, msgid, 0, NULL /* timeout */, &msg);
554
555   if (res == -1) {
556     /* error */
557     int err;
558
559     err = ldap_result2error(self->handle, msg, 1 /* free msg */);
560     [[self _exceptionForErrorCode:err
561            operation:@"add"
562            userInfo:[NSDictionary dictionaryWithObject:_entry forKey:@"entry"]]
563            raise];
564     
565     return NO;
566   }
567   
568   if (msg) ldap_msgfree(msg);
569   
570   return YES;
571 }
572
573 /* comparing */
574
575 - (BOOL)compareAttribute:(NSString *)_attr ofEntryWithDN:(NSString *)_dn
576   withValue:(id)_value
577 {
578   int res;
579   
580   if (_dn == nil)
581     return NO;
582
583   res = ldap_compare_s(self->handle,
584                        (char *)[_dn UTF8String],
585                        (char *)[_attr UTF8String],
586                        (char *)[[_value stringValue] UTF8String]);
587   
588   if (res == LDAP_COMPARE_TRUE)
589     return YES;
590   if (res == LDAP_COMPARE_FALSE)
591     return NO;
592
593   [[self _exceptionForErrorCode:res
594          operation:@"compare"
595          userInfo:[NSDictionary dictionaryWithObject:_dn forKey:@"dn"]]
596          raise];
597   
598   return NO;
599 }
600
601 - (BOOL)removeEntryWithDN:(NSString *)_dn {
602   int res;
603
604   if (_dn == nil)
605     return YES;
606
607   res = ldap_delete_s(self->handle, (char *)[_dn UTF8String]);
608
609   if (res == LDAP_SUCCESS)
610     return YES;
611
612   [[self _exceptionForErrorCode:res
613          operation:@"delete"
614          userInfo:[NSDictionary dictionaryWithObject:_dn forKey:@"dn"]]
615          raise];
616   
617   return NO;
618 }
619
620 - (BOOL)modifyEntryWithDN:(NSString *)_dn changes:(NSArray *)_mods {
621   int      res;
622   LDAPMod  **mods;
623   LDAPMod  *modBuf;
624   unsigned i, count;
625
626   if (_dn == nil)
627     return NO;
628
629   if ((count = [_mods count]) == 0)
630     return YES;
631
632   /* construct mods */
633
634   mods   = calloc(count + 1, sizeof(LDAPMod *));
635   modBuf = calloc(count, sizeof(LDAPMod));
636   NSAssert(mods,   @"couldn't allocate modification array");
637   NSAssert(modBuf, @"couldn't allocate modification buffer");
638
639   for (i = 0; i < count; i++) {
640     NGLdapModification *mod;
641     NGLdapAttribute    *attr;
642     NSString           *attrName;
643     unsigned           attrLen;
644     unsigned           valCount;
645     NSEnumerator       *e;
646     NSData             *value;
647     struct berval      **values;
648     unsigned           j;
649
650     mod = [_mods objectAtIndex:i];
651     mods[i] = &(modBuf[i]);
652
653     switch ([mod operation]) {
654       case NGLdapAddAttribute:
655         modBuf[i].mod_op = LDAP_MOD_ADD;
656         break;
657       case NGLdapDeleteAttribute:
658         modBuf[i].mod_op = LDAP_MOD_DELETE;
659         break;
660       case NGLdapReplaceAttribute:
661         modBuf[i].mod_op = LDAP_MOD_REPLACE;
662         break;
663     }
664     modBuf[i].mod_op |= LDAP_MOD_BVALUES;
665
666     attr     = [mod      attribute];
667     attrName = [attr     attributeName];
668     /* TODO: use UTF-8, UNICODE */
669     attrLen  = [attrName cStringLength];
670
671     modBuf[i].mod_type = malloc(attrLen + 1);
672     [attrName getCString:modBuf[i].mod_type];
673
674     valCount = [attr count];
675     values = calloc(valCount + 1, sizeof(struct berval *));
676     
677     e = [attr valueEnumerator];
678     for (j = 0; (value = [e nextObject]) && (j < valCount); j++) {
679       struct berval *bv;
680
681       bv = malloc(sizeof(struct berval));
682       bv->bv_len = [value length];
683       bv->bv_val = (void *)[value bytes];
684       values[j] = bv;
685     }
686     values[valCount] = NULL;
687
688     modBuf[i].mod_bvalues = values;
689   }
690   mods[count] = NULL;
691
692   /* run modify */
693
694   res = ldap_modify_s(self->handle, (char *)[_dn UTF8String], mods);
695
696   /* free structures */
697
698   freeMods(mods);
699   mods   = NULL;
700   modBuf = NULL;
701
702   /* check result */
703   
704   if (res == -1) {
705     [[self _exceptionForErrorCode:
706              0 /* was in v1: ((LDAP *)self->handle)->ld_errno */
707            operation:@"modify"
708            userInfo:[NSDictionary dictionaryWithObject:_dn forKey:@"dn"]]
709            raise];
710     return NO;
711   }
712   return YES;
713 }
714
715 /* root DSE */
716
717 - (NGLdapEntry *)schemaEntry {
718   NGLdapEntry *e;
719   
720   if ((e = [self entryAtDN:@"cn=schema" attributes:nil]))
721     return e;
722   
723   return nil;
724 }
725
726 - (NGLdapEntry *)rootDSE {
727   NGLdapEntry *e;
728   
729   if ((e = [self entryAtDN:@"" attributes:nil]))
730     return e;
731   
732   return nil;
733 }
734
735 - (NGLdapEntry *)configEntry {
736   NGLdapEntry *e;
737   
738   if ((e = [self entryAtDN:@"cn=config" attributes:nil]))
739     return e;
740   
741   return nil;
742 }
743
744 - (NSArray *)namingContexts {
745   NGLdapEntry    *e;
746   NSEnumerator   *values;
747   NSString       *value;
748   NSMutableArray *ma;
749   
750   if ((e = [self rootDSE])) {
751     /* LDAP v3 */
752     return [[e attributeWithName:@"namingcontexts"] allStringValues];
753   }
754   
755   if ((e = [self configEntry]) == nil)
756     return nil;
757   
758   /* OpenLDAP */
759     
760   values = [[e attributeWithName:@"database"] stringValueEnumerator];
761   ma     = [NSMutableArray arrayWithCapacity:4];
762
763   while ((value = [values nextObject])) {
764     NSRange r;
765       
766     r = [value rangeOfString:@":"];
767     if (r.length == 0)
768       /* couldn't parse value */
769       continue;
770       
771     value = [value substringFromIndex:(r.location + r.length)];
772     [ma addObject:value];
773   }
774   return ma;
775 }
776
777 /* description */
778
779 - (NSString *)description {
780   NSMutableString *s;
781
782   s = [NSMutableString stringWithCapacity:100];
783   [s appendFormat:@"<0x%08X[%@]:", self, NSStringFromClass([self class])];
784   
785   if ([self isBound])
786     [s appendString:@" bound"];
787   
788   if ([self doesUseCache]) {
789     [s appendFormat:@" cache[to=%.2fs,mem=%i]",
790          [self cacheTimeout], [self cacheMaxMemoryUsage]];
791   }
792   
793   [s appendString:@">"];
794
795   return s;
796 }
797
798 @end /* NGLdapConnection */
799
800 @implementation NGLdapConnection(PlainPasswordCheck)
801
802 + (NSString *)uidAttributeName {
803   static NSString *uidAttr = nil;
804   if (uidAttr == nil) {
805     uidAttr = [[[NSUserDefaults standardUserDefaults] 
806                  stringForKey:@"LDAPLoginAttributeName"] copy];
807     if ([uidAttr length] == 0) uidAttr = @"uid";
808   }
809   return uidAttr;
810 }
811
812 - (NSString *)dnForLogin:(NSString *)_login baseDN:(NSString *)_baseDN {
813   NSString    *filter;
814   char        *attrs[2];
815   LDAPMessage *result;
816   LDAPMessage *entry;
817   char        *dn;
818   BOOL        didBind = NO;
819   int         matchCount;
820   NSString    *strDN;
821   int         ldap_search_result ;
822
823   if (LDAPDebugEnabled)
824     [self logWithFormat:@"dn for login '%@' on %@", _login, _baseDN];
825   
826   if (self->handle == NULL) {
827     if (![self _reinit]) {
828       NSLog(@"%s: _reinit failed...:", __PRETTY_FUNCTION__);
829       return nil;
830     }
831   }
832   if (![self isBound]) {
833     didBind = NO;
834     if (LDAPDebugEnabled)
835       [self logWithFormat:@"  attempt to do a simple, anonymous bind .."];
836     
837     NS_DURING
838       if (LDAPInitialBindSpecific)
839         didBind = [self bindWithMethod:@"simple" binddn:LDAPInitialBindDN credentials:LDAPInitialBindPW];
840       else
841         didBind = [self bindWithMethod:@"simple" binddn:@"" credentials:@""];
842     NS_HANDLER
843       didBind = NO;
844     NS_ENDHANDLER;
845
846     if (!didBind) {
847       /* couldn't bind */
848       if (LDAPDebugEnabled) [self logWithFormat:@"  bind failed !"];
849       return nil;
850     }
851     didBind = YES;
852     if (LDAPDebugEnabled) [self logWithFormat:@"  bound."];
853   }
854   filter = [NSString stringWithFormat:@"(%@=%@)",
855                        [[self class] uidAttributeName],
856                        _login];
857   if (LDAPDebugEnabled) [self logWithFormat:@"  search: '%@'", filter];
858   
859   /* we only check the DN anyway .. */
860   attrs[0] = "objectclass";
861   attrs[1] = NULL;
862   
863   ldap_search_result = ldap_search_s(self->handle,
864                     (char *)[_baseDN UTF8String],
865                     LDAP_SCOPE_SUBTREE,
866                     (char *)[filter UTF8String],
867                     attrs, 1,
868                     &result) ;
869   if ((ldap_search_result != LDAP_SUCCESS) && (ldap_search_result != LDAP_PARTIAL_RESULTS)) {
870     /* search failed */
871     if (didBind)
872       [self unbind];
873
874     if (LDAPDebugEnabled) {
875       [self logWithFormat:@"  search failed"];
876     }
877     return nil;
878   }
879
880   /*
881     If the entry count is not equal to one, either the UID was not unique or
882     there was no match
883   */
884   if (((matchCount = ldap_count_entries(self->handle, result))) != 1) {
885     if (didBind) [self unbind];
886     if (LDAPDebugEnabled)
887       [self logWithFormat:@"  failed: %i matches", matchCount];
888     return nil;
889   }
890   
891   /* get first entry */
892   if ((entry = ldap_first_entry(self->handle, result)) == NULL) {
893     if (didBind) [self unbind];
894     if (LDAPDebugEnabled) 
895       [self logWithFormat:@"  could not retrieve first entry !"];
896     return nil;
897   }
898
899   /* get DN of first entry */
900   if ((dn = ldap_get_dn(self->handle, entry)) == NULL) {
901     /* couldn't get DN */
902     if (didBind) [self unbind];
903     if (LDAPDebugEnabled) [self logWithFormat:@"  got no DN for entry !"];
904     return nil;
905   }
906   strDN = nil;
907   NS_DURING {
908     strDN = [[[NSString alloc] initWithUTF8String:dn] autorelease];
909   }
910   NS_HANDLER {
911     fprintf(stderr, "Got exception %s while NSUTF8StringEncoding, "
912             "use defaultCStringEncoding",
913             [[localException description] cString]);
914     strDN = nil;
915   }
916   NS_ENDHANDLER;
917
918   if (strDN == nil) {
919     if (LDAPDebugEnabled) {
920       [self debugWithFormat:
921             @"could not convert DN to UTF-8 string, try cString .."];
922     }
923     strDN = [[[NSString alloc] initWithCString:dn] autorelease];
924   }
925   free(dn); dn = NULL;
926
927   if (result) {
928     ldap_msgfree(result);
929   }
930   [self unbind];
931   if (LDAPDebugEnabled) {
932     [self logWithFormat:@"   return DN %@", strDN];
933   }
934   return strDN;
935 }
936
937 - (BOOL)checkPassword:(NSString *)_pwd ofLogin:(NSString *)_login
938   atBaseDN:(NSString *)_baseDN
939 {
940   BOOL        didBind;
941   NSString    *strDN; 
942
943   if (LDAPDebugEnabled)
944     [self logWithFormat:@"check pwd of login '%@' on %@", _login, _baseDN];
945   
946   if (self->handle == NULL) {
947     if (![self _reinit]) {
948       NSLog(@"%s: _reinit failed...:", __PRETTY_FUNCTION__);
949     }
950   }
951   strDN = [self dnForLogin:_login baseDN:_baseDN];
952
953   if (LDAPDebugEnabled) {
954     [self logWithFormat:@"  attempting to bind login %@ DN: %@ %s!",
955           _login, strDN,
956           [_pwd length] > 0 ? "(with password) " : "(empty password) "];
957   }
958   
959   if (!strDN) {
960     if (LDAPDebugEnabled) {
961       [self logWithFormat:@"  missing dn for login %@ atBaseDN %@",
962             _login, _baseDN];
963     }
964     return NO;
965   }
966   /*
967     Now bind as the DN with the password supplied earlier...
968     Successful bind means the password was correct, otherwise the
969     password is invalid.
970   */
971
972   didBind = NO;
973   NS_DURING
974     didBind = [self bindWithMethod:@"simple" binddn:strDN credentials:_pwd];
975   NS_HANDLER
976     didBind = NO;
977   NS_ENDHANDLER;
978   
979   if (!didBind) {
980     /* invalid login or password */
981     if (LDAPDebugEnabled) 
982       [self logWithFormat:@"  could not simple bind DN '%@' !", strDN];
983     
984     [self unbind];
985     return NO;
986   }
987   [self unbind];
988   if (LDAPDebugEnabled) [self logWithFormat:@"  bound successfully !"];
989   return YES;
990 }
991
992 + (BOOL)checkPassword:(NSString *)_pwd ofLogin:(NSString *)_login
993   atBaseDN:(NSString *)_baseDN
994   onHost:(NSString *)_hostName port:(int)_port
995 {
996   NGLdapConnection *ldap;
997   
998   if (LDAPDebugEnabled) {
999     NSLog(@"LDAP: check pwd of login '%@' on %@,%i,%@ ...",
1000           _login, _hostName, _port, _baseDN);
1001   }
1002   
1003   if ((ldap = [[self alloc] initWithHostName:_hostName port:_port]) == nil) {
1004     if (LDAPDebugEnabled)
1005       NSLog(@"LDAP:   got no connection to %@,%i ...", _hostName, _port);
1006     return NO;
1007   }
1008   ldap = [ldap autorelease];
1009   if (LDAPDebugEnabled)
1010     NSLog(@"LDAP:   use connection: %@", ldap);
1011   
1012   return [ldap checkPassword:_pwd ofLogin:_login atBaseDN:_baseDN];
1013 }
1014
1015 @end /* NGLdapConnection(PlainPasswordCheck) */