2 Copyright (C) 2000-2005 SKYRIX Software AG
4 This file is part of SOPE.
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
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.
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
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"
31 static BOOL LDAPDebugEnabled = NO;
32 static BOOL LDAPInitialBindSpecific = NO;
33 static NSString *LDAPInitialBindDN = @"" ;
34 static NSString *LDAPInitialBindPW = @"" ;
36 /* this is required by SuSE EMail Server III */
37 #define ISOLATIN1_CREDENTIALS 1
39 @interface NGLdapConnection(Privates)
43 @implementation NGLdapConnection
45 static void freeMods(LDAPMod **mods) {
53 for (i = 0; mods[i] != NULL; i++) {
54 struct berval **values;
57 if ((values = buf[i].mod_bvalues) != NULL) {
60 for (j = 0; values[j] != NULL; j++)
66 if ((type = buf[i].mod_type) != NULL)
75 NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
76 static BOOL didInit = NO;
80 LDAPDebugEnabled = [ud boolForKey:@"LDAPDebugEnabled"];
81 LDAPInitialBindSpecific = [ud boolForKey:@"LDAPInitialBindSpecific"];
82 LDAPInitialBindDN = [[ud stringForKey:@"LDAPInitialBindDN"] copy];
83 LDAPInitialBindPW = [[ud stringForKey:@"LDAPInitialBindPW"] copy];
88 ldap_unbind(self->handle);
92 self->handle = ldap_init((char *)[self->hostName cString], self->port);
94 if (self->handle == NULL)
100 - (id)initWithHostName:(NSString *)_hostName port:(int)_port {
101 self->hostName = [_hostName copy];
102 self->port = (_port != 0) ? _port : 389;
104 if (![self _reinit]) {
109 [self setCacheTimeout:120.0];
110 [self setCacheMaxMemoryUsage:16000];
114 - (id)initWithHostName:(NSString *)_hostName {
115 return [self initWithHostName:_hostName port:0];
123 // call unbind to free resources
125 err = ldap_unbind(self->handle);
131 [self->hostName release];
137 - (NSString *)hostName {
138 return self->hostName;
146 - (void *)ldapHandle {
152 - (NSException *)_exceptionForErrorCode:(int)_err
153 operation:(NSString *)_operation
154 userInfo:(NSDictionary *)_ui
157 NSString *name, *reason;
159 name = @"LDAPException";
165 case LDAP_INAPPROPRIATE_AUTH:
166 reason = @"inappropriate authorization";
169 case LDAP_INVALID_CREDENTIALS:
170 reason = @"invalid credentials";
173 case LDAP_INSUFFICIENT_ACCESS:
174 reason = @"insufficient access";
177 case LDAP_SERVER_DOWN:
178 reason = @"the server is down";
182 reason = @"the operation timed out";
185 case LDAP_AUTH_UNKNOWN:
186 reason = @"authorization unknown";
189 case LDAP_NOT_ALLOWED_ON_NONLEAF:
190 reason = @"operation not allowed on non-leaf record";
194 reason = [NSString stringWithFormat:
195 @"operation %@ failed with code 0x%X",
200 e = [NSException exceptionWithName:name
210 return self->flags.isBound ? YES : NO;
214 if (self->flags.isBound) {
217 err = ldap_unbind(self->handle);
218 self->flags.isBound = 0;
223 - (BOOL)bindWithMethod:(NSString *)_method
224 binddn:(NSString *)_login credentials:(NSString *)_cred
226 int ldap_version3 = LDAP_VERSION3 ;
230 if (self->handle == NULL)
233 if ((_method == nil) || ([_method isEqualToString:@"simple"])) {
234 method = LDAP_AUTH_SIMPLE;
236 else if ([_method isEqualToString:@"krbv41"]) {
237 method = LDAP_AUTH_KRBV41;
239 else if ([_method isEqualToString:@"krbv42"]) {
240 method = LDAP_AUTH_KRBV42;
246 l = (char *)[_login UTF8String];
247 #if ISOLATIN1_CREDENTIALS
248 p = (char *)[_cred cString];
250 p = (char *)[_cred UTF8String];
252 err = (method == LDAP_AUTH_SIMPLE)
253 ? ldap_simple_bind_s(self->handle, l, p)
254 : ldap_bind_s(self->handle, l, p, method);
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;
263 [[self _exceptionForErrorCode:err
265 userInfo:[NSDictionary dictionaryWithObject:_login ? _login : @"<nil>"
272 /* running queries */
274 - (NSEnumerator *)_searchAtBaseDN:(NSString *)_base
275 qualifier:(EOQualifier *)_q
276 attributes:(NSArray *)_attributes
282 NGLdapSearchResultEnumerator *e;
284 if (self->handle == NULL)
287 if ((filter = [_q ldapFilterString]) == nil)
288 filter = @"(objectclass=*)";
293 acount = [_attributes count];
294 attrs = calloc(acount + 1, sizeof(char *));
296 for (i = 0; i < acount; i++)
297 attrs[i] = (char *)[[_attributes objectAtIndex:i] UTF8String];
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]);
307 msgid = ldap_search(self->handle,
308 (char *)[_base UTF8String],
310 (char *)[filter UTF8String],
314 /* free attributes */
316 free(attrs); attrs = NULL;
324 e = [[NGLdapSearchResultEnumerator alloc]
325 initWithConnection:self messageID:msgid];
327 return [e autorelease];
330 - (NSEnumerator *)flatSearchAtBaseDN:(NSString *)_base
331 qualifier:(EOQualifier *)_q
332 attributes:(NSArray *)_attributes
334 return [self _searchAtBaseDN:_base
336 attributes:_attributes
337 scope:LDAP_SCOPE_ONELEVEL];
340 - (NSEnumerator *)deepSearchAtBaseDN:(NSString *)_base
341 qualifier:(EOQualifier *)_q
342 attributes:(NSArray *)_attributes
344 return [self _searchAtBaseDN:_base
346 attributes:_attributes
347 scope:LDAP_SCOPE_SUBTREE];
350 - (NSEnumerator *)baseSearchAtBaseDN:(NSString *)_base
351 qualifier:(EOQualifier *)_q
352 attributes:(NSArray *)_attributes
354 return [self _searchAtBaseDN:_base
356 attributes:_attributes
357 scope:LDAP_SCOPE_BASE];
360 - (NGLdapEntry *)entryAtDN:(NSString *)_dn attributes:(NSArray *)_attrs {
364 e = [self _searchAtBaseDN:_dn
367 scope:LDAP_SCOPE_BASE];
369 entry = [e nextObject];
371 if ([e nextObject]) {
372 NSLog(@"more than one search results in base search !!!");
373 /* consume all entries */
374 while ([e nextObject])
383 - (void)setCacheTimeout:(NSTimeInterval)_to {
384 if (self->cacheTimeout != _to) {
385 self->cacheTimeout = _to;
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__);
392 ldap_disable_cache(self->handle);
393 ldap_enable_cache(self->handle, _to, [self cacheMaxMemoryUsage]);
398 - (NSTimeInterval)cacheTimeout {
399 return self->cacheTimeout;
402 - (void)setCacheMaxMemoryUsage:(long)_maxMem {
403 if (self->cacheMaxMemory != _maxMem) {
404 self->cacheMaxMemory = _maxMem;
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__);
411 ldap_disable_cache(self->handle);
412 ldap_enable_cache(self->handle, [self cacheTimeout], _maxMem);
417 - (long)cacheMaxMemoryUsage {
418 return self->cacheMaxMemory;
421 - (void)setUseCache:(BOOL)_flag {
423 #if LDAP_API_VERSION > 2000
424 NSLog(@"WARNING(%s): setting cache-usage unsupported on the client "
425 @"library version!", __PRETTY_FUNCTION__);
427 ldap_enable_cache(self->handle,
428 [self cacheTimeout], [self cacheMaxMemoryUsage]);
430 self->isCacheEnabled = YES;
433 #if LDAP_API_VERSION > 2000
434 NSLog(@"WARNING(%s): setting cache-usage unsupported on the client "
435 @"library version!", __PRETTY_FUNCTION__);
437 ldap_disable_cache(self->handle);
439 self->isCacheEnabled = NO;
442 - (BOOL)doesUseCache {
443 return self->isCacheEnabled;
447 #if !(LDAP_API_VERSION > 2000)
448 ldap_flush_cache(self->handle);
451 - (void)destroyCache {
452 #if !(LDAP_API_VERSION > 2000)
453 ldap_destroy_cache(self->handle);
455 self->isCacheEnabled = NO;
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]);
467 - (BOOL)addEntry:(NGLdapEntry *)_entry {
477 /* construct attributes */
481 NGLdapAttribute *attribute;
483 count = [_entry count];
485 attrBuf = calloc(count, sizeof(LDAPMod));
486 NSAssert(attrBuf, @"couldn't allocate attribute buffer");
488 attrs = calloc(count + 1, sizeof(LDAPMod *));
489 NSAssert(attrs, @"couldn't allocate attribute ptr buffer");
491 e = [[[_entry attributes] allValues] objectEnumerator];
492 for (i = 0; (attribute = [e nextObject]) && (i < count); i++) {
493 unsigned valCount, j;
494 struct berval **values;
500 key = [attribute attributeName];
502 valCount = [attribute count];
503 values = calloc(valCount + 1, sizeof(struct berval *));
505 ve = [attribute valueEnumerator];
506 for (j = 0; (v = [ve nextObject]) && (j < valCount); j++) {
509 bv = malloc(sizeof(struct berval));
511 bv->bv_len = [v length];
512 bv->bv_val = (void *)[v bytes];
515 values[valCount] = NULL;
517 /* TODO: use UTF-8, UNICODE */
518 attrName = malloc([key cStringLength] + 1);
519 [key getCString:attrName];
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]);
529 /* start operation */
531 msgid = ldap_add(self->handle, (char *)[[_entry dn] UTF8String], attrs);
533 /* deconstruct attributes */
539 /* check operation return value */
542 [[self _exceptionForErrorCode:
543 0 /* was in v1: ((LDAP *)self->handle)->ld_errno */
545 userInfo:[NSDictionary dictionaryWithObject:_entry forKey:@"entry"]]
553 res = ldap_result(self->handle, msgid, 0, NULL /* timeout */, &msg);
559 err = ldap_result2error(self->handle, msg, 1 /* free msg */);
560 [[self _exceptionForErrorCode:err
562 userInfo:[NSDictionary dictionaryWithObject:_entry forKey:@"entry"]]
568 if (msg) ldap_msgfree(msg);
575 - (BOOL)compareAttribute:(NSString *)_attr ofEntryWithDN:(NSString *)_dn
583 res = ldap_compare_s(self->handle,
584 (char *)[_dn UTF8String],
585 (char *)[_attr UTF8String],
586 (char *)[[_value stringValue] UTF8String]);
588 if (res == LDAP_COMPARE_TRUE)
590 if (res == LDAP_COMPARE_FALSE)
593 [[self _exceptionForErrorCode:res
595 userInfo:[NSDictionary dictionaryWithObject:_dn forKey:@"dn"]]
601 - (BOOL)removeEntryWithDN:(NSString *)_dn {
607 res = ldap_delete_s(self->handle, (char *)[_dn UTF8String]);
609 if (res == LDAP_SUCCESS)
612 [[self _exceptionForErrorCode:res
614 userInfo:[NSDictionary dictionaryWithObject:_dn forKey:@"dn"]]
620 - (BOOL)modifyEntryWithDN:(NSString *)_dn changes:(NSArray *)_mods {
629 if ((count = [_mods count]) == 0)
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");
639 for (i = 0; i < count; i++) {
640 NGLdapModification *mod;
641 NGLdapAttribute *attr;
647 struct berval **values;
650 mod = [_mods objectAtIndex:i];
651 mods[i] = &(modBuf[i]);
653 switch ([mod operation]) {
654 case NGLdapAddAttribute:
655 modBuf[i].mod_op = LDAP_MOD_ADD;
657 case NGLdapDeleteAttribute:
658 modBuf[i].mod_op = LDAP_MOD_DELETE;
660 case NGLdapReplaceAttribute:
661 modBuf[i].mod_op = LDAP_MOD_REPLACE;
664 modBuf[i].mod_op |= LDAP_MOD_BVALUES;
666 attr = [mod attribute];
667 attrName = [attr attributeName];
668 /* TODO: use UTF-8, UNICODE */
669 attrLen = [attrName cStringLength];
671 modBuf[i].mod_type = malloc(attrLen + 1);
672 [attrName getCString:modBuf[i].mod_type];
674 valCount = [attr count];
675 values = calloc(valCount + 1, sizeof(struct berval *));
677 e = [attr valueEnumerator];
678 for (j = 0; (value = [e nextObject]) && (j < valCount); j++) {
681 bv = malloc(sizeof(struct berval));
682 bv->bv_len = [value length];
683 bv->bv_val = (void *)[value bytes];
686 values[valCount] = NULL;
688 modBuf[i].mod_bvalues = values;
694 res = ldap_modify_s(self->handle, (char *)[_dn UTF8String], mods);
696 /* free structures */
705 [[self _exceptionForErrorCode:
706 0 /* was in v1: ((LDAP *)self->handle)->ld_errno */
708 userInfo:[NSDictionary dictionaryWithObject:_dn forKey:@"dn"]]
717 - (NGLdapEntry *)schemaEntry {
720 if ((e = [self entryAtDN:@"cn=schema" attributes:nil]))
726 - (NGLdapEntry *)rootDSE {
729 if ((e = [self entryAtDN:@"" attributes:nil]))
735 - (NGLdapEntry *)configEntry {
738 if ((e = [self entryAtDN:@"cn=config" attributes:nil]))
744 - (NSArray *)namingContexts {
746 NSEnumerator *values;
750 if ((e = [self rootDSE])) {
752 return [[e attributeWithName:@"namingcontexts"] allStringValues];
755 if ((e = [self configEntry]) == nil)
760 values = [[e attributeWithName:@"database"] stringValueEnumerator];
761 ma = [NSMutableArray arrayWithCapacity:4];
763 while ((value = [values nextObject])) {
766 r = [value rangeOfString:@":"];
768 /* couldn't parse value */
771 value = [value substringFromIndex:(r.location + r.length)];
772 [ma addObject:value];
779 - (NSString *)description {
782 s = [NSMutableString stringWithCapacity:100];
783 [s appendFormat:@"<0x%08X[%@]:", self, NSStringFromClass([self class])];
786 [s appendString:@" bound"];
788 if ([self doesUseCache]) {
789 [s appendFormat:@" cache[to=%.2fs,mem=%i]",
790 [self cacheTimeout], [self cacheMaxMemoryUsage]];
793 [s appendString:@">"];
798 @end /* NGLdapConnection */
800 @implementation NGLdapConnection(PlainPasswordCheck)
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";
812 - (NSString *)dnForLogin:(NSString *)_login baseDN:(NSString *)_baseDN {
821 int ldap_search_result ;
823 if (LDAPDebugEnabled)
824 [self logWithFormat:@"dn for login '%@' on %@", _login, _baseDN];
826 if (self->handle == NULL) {
827 if (![self _reinit]) {
828 NSLog(@"%s: _reinit failed...:", __PRETTY_FUNCTION__);
832 if (![self isBound]) {
834 if (LDAPDebugEnabled)
835 [self logWithFormat:@" attempt to do a simple, anonymous bind .."];
838 if (LDAPInitialBindSpecific)
839 didBind = [self bindWithMethod:@"simple" binddn:LDAPInitialBindDN credentials:LDAPInitialBindPW];
841 didBind = [self bindWithMethod:@"simple" binddn:@"" credentials:@""];
848 if (LDAPDebugEnabled) [self logWithFormat:@" bind failed !"];
852 if (LDAPDebugEnabled) [self logWithFormat:@" bound."];
854 filter = [NSString stringWithFormat:@"(%@=%@)",
855 [[self class] uidAttributeName],
857 if (LDAPDebugEnabled) [self logWithFormat:@" search: '%@'", filter];
859 /* we only check the DN anyway .. */
860 attrs[0] = "objectclass";
863 ldap_search_result = ldap_search_s(self->handle,
864 (char *)[_baseDN UTF8String],
866 (char *)[filter UTF8String],
869 if ((ldap_search_result != LDAP_SUCCESS) && (ldap_search_result != LDAP_PARTIAL_RESULTS)) {
874 if (LDAPDebugEnabled) {
875 [self logWithFormat:@" search failed"];
881 If the entry count is not equal to one, either the UID was not unique or
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];
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 !"];
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 !"];
908 strDN = [[[NSString alloc] initWithUTF8String:dn] autorelease];
911 fprintf(stderr, "Got exception %s while NSUTF8StringEncoding, "
912 "use defaultCStringEncoding",
913 [[localException description] cString]);
919 if (LDAPDebugEnabled) {
920 [self debugWithFormat:
921 @"could not convert DN to UTF-8 string, try cString .."];
923 strDN = [[[NSString alloc] initWithCString:dn] autorelease];
928 ldap_msgfree(result);
931 if (LDAPDebugEnabled) {
932 [self logWithFormat:@" return DN %@", strDN];
937 - (BOOL)checkPassword:(NSString *)_pwd ofLogin:(NSString *)_login
938 atBaseDN:(NSString *)_baseDN
943 if (LDAPDebugEnabled)
944 [self logWithFormat:@"check pwd of login '%@' on %@", _login, _baseDN];
946 if ([_pwd length] == 0) {
947 if (LDAPDebugEnabled) [self logWithFormat:@" no password provided."];
951 if (self->handle == NULL) {
952 if (![self _reinit]) {
953 NSLog(@"%s: _reinit failed...:", __PRETTY_FUNCTION__);
956 strDN = [self dnForLogin:_login baseDN:_baseDN];
958 if (LDAPDebugEnabled) {
959 [self logWithFormat:@" attempting to bind login %@ DN: %@ %s!",
961 [_pwd length] > 0 ? "(with password) " : "(empty password) "];
965 if (LDAPDebugEnabled) {
966 [self logWithFormat:@" missing dn for login %@ atBaseDN %@",
972 Now bind as the DN with the password supplied earlier...
973 Successful bind means the password was correct, otherwise the
979 /* Note: beware: do _not_ use empty passwords! (unauthenticated binds) */
980 didBind = [self bindWithMethod:@"simple" binddn:strDN credentials:_pwd];
987 /* invalid login or password */
988 if (LDAPDebugEnabled)
989 [self logWithFormat:@" could not simple bind DN '%@' !", strDN];
995 if (LDAPDebugEnabled) [self logWithFormat:@" bound successfully !"];
999 + (BOOL)checkPassword:(NSString *)_pwd ofLogin:(NSString *)_login
1000 atBaseDN:(NSString *)_baseDN
1001 onHost:(NSString *)_hostName port:(int)_port
1003 NGLdapConnection *ldap;
1005 if (LDAPDebugEnabled) {
1006 NSLog(@"LDAP: check pwd of login '%@' on %@,%i,%@ ...",
1007 _login, _hostName, _port, _baseDN);
1009 if ([_pwd length] == 0) {
1010 if (LDAPDebugEnabled) [self logWithFormat:@" no password provided."];
1014 if ((ldap = [[self alloc] initWithHostName:_hostName port:_port]) == nil) {
1015 if (LDAPDebugEnabled)
1016 NSLog(@"LDAP: got no connection to %@,%i ...", _hostName, _port);
1019 ldap = [ldap autorelease];
1020 if (LDAPDebugEnabled)
1021 NSLog(@"LDAP: use connection: %@", ldap);
1023 return [ldap checkPassword:_pwd ofLogin:_login atBaseDN:_baseDN];
1026 @end /* NGLdapConnection(PlainPasswordCheck) */