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