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