2 Copyright (C) 2000-2004 SKYRIX Software AG
4 This file is part of OpenGroupware.org.
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
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.
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
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"
32 static BOOL LDAPDebugEnabled = NO;
33 static BOOL LDAPInitialBindSpecific = NO;
34 static NSString *LDAPInitialBindDN = @"" ;
35 static NSString *LDAPInitialBindPW = @"" ;
37 /* this is required by SuSE EMail Server III */
38 #define ISOLATIN1_CREDENTIALS 1
40 @interface NGLdapConnection(Privates)
44 @implementation NGLdapConnection
46 static void freeMods(LDAPMod **mods) {
54 for (i = 0; mods[i] != NULL; i++) {
55 struct berval **values;
58 if ((values = buf[i].mod_bvalues) != NULL) {
61 for (j = 0; values[j] != NULL; j++)
67 if ((type = buf[i].mod_type) != NULL)
76 NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
77 static BOOL didInit = NO;
81 LDAPDebugEnabled = [ud boolForKey:@"LDAPDebugEnabled"];
82 LDAPInitialBindSpecific = [ud boolForKey:@"LDAPInitialBindSpecific"];
83 LDAPInitialBindDN = [[ud stringForKey:@"LDAPInitialBindDN"] copy];
84 LDAPInitialBindPW = [[ud stringForKey:@"LDAPInitialBindPW"] copy];
89 ldap_unbind(self->handle);
93 self->handle = ldap_init((char *)[self->hostName cString], self->port);
95 if (self->handle == NULL)
101 - (id)initWithHostName:(NSString *)_hostName port:(int)_port {
102 self->hostName = [_hostName copy];
103 self->port = (_port != 0) ? _port : 389;
105 if (![self _reinit]) {
110 [self setCacheTimeout:120.0];
111 [self setCacheMaxMemoryUsage:16000];
115 - (id)initWithHostName:(NSString *)_hostName {
116 return [self initWithHostName:_hostName port:0];
124 // call unbind to free resources
126 err = ldap_unbind(self->handle);
132 [self->hostName release];
138 - (NSString *)hostName {
139 return self->hostName;
147 - (void *)ldapHandle {
153 - (NSException *)_exceptionForErrorCode:(int)_err
154 operation:(NSString *)_operation
155 userInfo:(NSDictionary *)_ui
158 NSString *name, *reason;
160 name = @"LDAPException";
166 case LDAP_INAPPROPRIATE_AUTH:
167 reason = @"inappropriate authorization";
170 case LDAP_INVALID_CREDENTIALS:
171 reason = @"invalid credentials";
174 case LDAP_INSUFFICIENT_ACCESS:
175 reason = @"insufficient access";
178 case LDAP_SERVER_DOWN:
179 reason = @"the server is down";
183 reason = @"the operation timed out";
186 case LDAP_AUTH_UNKNOWN:
187 reason = @"authorization unknown";
190 case LDAP_NOT_ALLOWED_ON_NONLEAF:
191 reason = @"operation not allowed on non-leaf record";
195 reason = [NSString stringWithFormat:
196 @"operation %@ failed with code 0x%X",
201 e = [NSException exceptionWithName:name
211 return self->flags.isBound ? YES : NO;
215 if (self->flags.isBound) {
218 err = ldap_unbind(self->handle);
219 self->flags.isBound = 0;
224 - (BOOL)bindWithMethod:(NSString *)_method
225 binddn:(NSString *)_login credentials:(NSString *)_cred
227 int ldap_version3 = LDAP_VERSION3 ;
231 if (self->handle == NULL)
234 if ((_method == nil) || ([_method isEqualToString:@"simple"])) {
235 method = LDAP_AUTH_SIMPLE;
237 else if ([_method isEqualToString:@"krbv41"]) {
238 method = LDAP_AUTH_KRBV41;
240 else if ([_method isEqualToString:@"krbv42"]) {
241 method = LDAP_AUTH_KRBV42;
247 l = (char *)[_login UTF8String];
248 #if ISOLATIN1_CREDENTIALS
249 p = (char *)[_cred cString];
251 p = (char *)[_cred UTF8String];
253 err = (method == LDAP_AUTH_SIMPLE)
254 ? ldap_simple_bind_s(self->handle, l, p)
255 : ldap_bind_s(self->handle, l, p, method);
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;
264 [[self _exceptionForErrorCode:err
266 userInfo:[NSDictionary dictionaryWithObject:_login ? _login : @"<nil>"
273 /* running queries */
275 - (NSEnumerator *)_searchAtBaseDN:(NSString *)_base
276 qualifier:(EOQualifier *)_q
277 attributes:(NSArray *)_attributes
283 NGLdapSearchResultEnumerator *e;
285 if (self->handle == NULL)
288 if ((filter = [_q ldapFilterString]) == nil)
289 filter = @"(objectclass=*)";
294 acount = [_attributes count];
295 attrs = calloc(acount + 1, sizeof(char *));
297 for (i = 0; i < acount; i++)
298 attrs[i] = (char *)[[_attributes objectAtIndex:i] UTF8String];
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]);
308 msgid = ldap_search(self->handle,
309 (char *)[_base UTF8String],
311 (char *)[filter UTF8String],
315 /* free attributes */
317 free(attrs); attrs = NULL;
325 e = [[NGLdapSearchResultEnumerator alloc]
326 initWithConnection:self messageID:msgid];
328 return [e autorelease];
331 - (NSEnumerator *)flatSearchAtBaseDN:(NSString *)_base
332 qualifier:(EOQualifier *)_q
333 attributes:(NSArray *)_attributes
335 return [self _searchAtBaseDN:_base
337 attributes:_attributes
338 scope:LDAP_SCOPE_ONELEVEL];
341 - (NSEnumerator *)deepSearchAtBaseDN:(NSString *)_base
342 qualifier:(EOQualifier *)_q
343 attributes:(NSArray *)_attributes
345 return [self _searchAtBaseDN:_base
347 attributes:_attributes
348 scope:LDAP_SCOPE_SUBTREE];
351 - (NSEnumerator *)baseSearchAtBaseDN:(NSString *)_base
352 qualifier:(EOQualifier *)_q
353 attributes:(NSArray *)_attributes
355 return [self _searchAtBaseDN:_base
357 attributes:_attributes
358 scope:LDAP_SCOPE_BASE];
361 - (NGLdapEntry *)entryAtDN:(NSString *)_dn attributes:(NSArray *)_attrs {
365 e = [self _searchAtBaseDN:_dn
368 scope:LDAP_SCOPE_BASE];
370 entry = [e nextObject];
372 if ([e nextObject]) {
373 NSLog(@"more than one search results in base search !!!");
374 /* consume all entries */
375 while ([e nextObject])
384 - (void)setCacheTimeout:(NSTimeInterval)_to {
385 if (self->cacheTimeout != _to) {
386 self->cacheTimeout = _to;
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__);
393 ldap_disable_cache(self->handle);
394 ldap_enable_cache(self->handle, _to, [self cacheMaxMemoryUsage]);
399 - (NSTimeInterval)cacheTimeout {
400 return self->cacheTimeout;
403 - (void)setCacheMaxMemoryUsage:(long)_maxMem {
404 if (self->cacheMaxMemory != _maxMem) {
405 self->cacheMaxMemory = _maxMem;
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__);
412 ldap_disable_cache(self->handle);
413 ldap_enable_cache(self->handle, [self cacheTimeout], _maxMem);
418 - (long)cacheMaxMemoryUsage {
419 return self->cacheMaxMemory;
422 - (void)setUseCache:(BOOL)_flag {
424 #if LDAP_API_VERSION > 2000
425 NSLog(@"WARNING(%s): setting cache-usage unsupported on the client "
426 @"library version!", __PRETTY_FUNCTION__);
428 ldap_enable_cache(self->handle,
429 [self cacheTimeout], [self cacheMaxMemoryUsage]);
431 self->isCacheEnabled = YES;
434 #if LDAP_API_VERSION > 2000
435 NSLog(@"WARNING(%s): setting cache-usage unsupported on the client "
436 @"library version!", __PRETTY_FUNCTION__);
438 ldap_disable_cache(self->handle);
440 self->isCacheEnabled = NO;
443 - (BOOL)doesUseCache {
444 return self->isCacheEnabled;
448 #if !(LDAP_API_VERSION > 2000)
449 ldap_flush_cache(self->handle);
452 - (void)destroyCache {
453 #if !(LDAP_API_VERSION > 2000)
454 ldap_destroy_cache(self->handle);
456 self->isCacheEnabled = NO;
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]);
468 - (BOOL)addEntry:(NGLdapEntry *)_entry {
478 /* construct attributes */
482 NGLdapAttribute *attribute;
484 count = [_entry count];
486 attrBuf = calloc(count, sizeof(LDAPMod));
487 NSAssert(attrBuf, @"couldn't allocate attribute buffer");
489 attrs = calloc(count + 1, sizeof(LDAPMod *));
490 NSAssert(attrs, @"couldn't allocate attribute ptr buffer");
492 e = [[[_entry attributes] allValues] objectEnumerator];
493 for (i = 0; (attribute = [e nextObject]) && (i < count); i++) {
494 unsigned valCount, j;
495 struct berval **values;
501 key = [attribute attributeName];
503 valCount = [attribute count];
504 values = calloc(valCount + 1, sizeof(struct berval *));
506 ve = [attribute valueEnumerator];
507 for (j = 0; (v = [ve nextObject]) && (j < valCount); j++) {
510 bv = malloc(sizeof(struct berval));
512 bv->bv_len = [v length];
513 bv->bv_val = (void *)[v bytes];
516 values[valCount] = NULL;
518 /* TODO: use UTF-8, UNICODE */
519 attrName = malloc([key cStringLength] + 1);
520 [key getCString:attrName];
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]);
530 /* start operation */
532 msgid = ldap_add(self->handle, (char *)[[_entry dn] UTF8String], attrs);
534 /* deconstruct attributes */
540 /* check operation return value */
543 [[self _exceptionForErrorCode:
544 0 /* was in v1: ((LDAP *)self->handle)->ld_errno */
546 userInfo:[NSDictionary dictionaryWithObject:_entry forKey:@"entry"]]
554 res = ldap_result(self->handle, msgid, 0, NULL /* timeout */, &msg);
560 err = ldap_result2error(self->handle, msg, 1 /* free msg */);
561 [[self _exceptionForErrorCode:err
563 userInfo:[NSDictionary dictionaryWithObject:_entry forKey:@"entry"]]
569 if (msg) ldap_msgfree(msg);
576 - (BOOL)compareAttribute:(NSString *)_attr ofEntryWithDN:(NSString *)_dn
584 res = ldap_compare_s(self->handle,
585 (char *)[_dn UTF8String],
586 (char *)[_attr UTF8String],
587 (char *)[[_value stringValue] UTF8String]);
589 if (res == LDAP_COMPARE_TRUE)
591 if (res == LDAP_COMPARE_FALSE)
594 [[self _exceptionForErrorCode:res
596 userInfo:[NSDictionary dictionaryWithObject:_dn forKey:@"dn"]]
602 - (BOOL)removeEntryWithDN:(NSString *)_dn {
608 res = ldap_delete_s(self->handle, (char *)[_dn UTF8String]);
610 if (res == LDAP_SUCCESS)
613 [[self _exceptionForErrorCode:res
615 userInfo:[NSDictionary dictionaryWithObject:_dn forKey:@"dn"]]
621 - (BOOL)modifyEntryWithDN:(NSString *)_dn changes:(NSArray *)_mods {
630 if ((count = [_mods count]) == 0)
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");
640 for (i = 0; i < count; i++) {
641 NGLdapModification *mod;
642 NGLdapAttribute *attr;
648 struct berval **values;
651 mod = [_mods objectAtIndex:i];
652 mods[i] = &(modBuf[i]);
654 switch ([mod operation]) {
655 case NGLdapAddAttribute:
656 modBuf[i].mod_op = LDAP_MOD_ADD;
658 case NGLdapDeleteAttribute:
659 modBuf[i].mod_op = LDAP_MOD_DELETE;
661 case NGLdapReplaceAttribute:
662 modBuf[i].mod_op = LDAP_MOD_REPLACE;
665 modBuf[i].mod_op |= LDAP_MOD_BVALUES;
667 attr = [mod attribute];
668 attrName = [attr attributeName];
669 /* TODO: use UTF-8, UNICODE */
670 attrLen = [attrName cStringLength];
672 modBuf[i].mod_type = malloc(attrLen + 1);
673 [attrName getCString:modBuf[i].mod_type];
675 valCount = [attr count];
676 values = calloc(valCount + 1, sizeof(struct berval *));
678 e = [attr valueEnumerator];
679 for (j = 0; (value = [e nextObject]) && (j < valCount); j++) {
682 bv = malloc(sizeof(struct berval));
683 bv->bv_len = [value length];
684 bv->bv_val = (void *)[value bytes];
687 values[valCount] = NULL;
689 modBuf[i].mod_bvalues = values;
695 res = ldap_modify_s(self->handle, (char *)[_dn UTF8String], mods);
697 /* free structures */
706 [[self _exceptionForErrorCode:
707 0 /* was in v1: ((LDAP *)self->handle)->ld_errno */
709 userInfo:[NSDictionary dictionaryWithObject:_dn forKey:@"dn"]]
718 - (NGLdapEntry *)schemaEntry {
721 if ((e = [self entryAtDN:@"cn=schema" attributes:nil]))
727 - (NGLdapEntry *)rootDSE {
730 if ((e = [self entryAtDN:@"" attributes:nil]))
736 - (NGLdapEntry *)configEntry {
739 if ((e = [self entryAtDN:@"cn=config" attributes:nil]))
745 - (NSArray *)namingContexts {
747 NSEnumerator *values;
751 if ((e = [self rootDSE])) {
753 return [[e attributeWithName:@"namingcontexts"] allStringValues];
756 if ((e = [self configEntry]) == nil)
761 values = [[e attributeWithName:@"database"] stringValueEnumerator];
762 ma = [NSMutableArray arrayWithCapacity:4];
764 while ((value = [values nextObject])) {
767 r = [value rangeOfString:@":"];
769 /* couldn't parse value */
772 value = [value substringFromIndex:(r.location + r.length)];
773 [ma addObject:value];
780 - (NSString *)description {
783 s = [NSMutableString stringWithCapacity:100];
784 [s appendFormat:@"<0x%08X[%@]:", self, NSStringFromClass([self class])];
787 [s appendString:@" bound"];
789 if ([self doesUseCache]) {
790 [s appendFormat:@" cache[to=%.2fs,mem=%i]",
791 [self cacheTimeout], [self cacheMaxMemoryUsage]];
794 [s appendString:@">"];
799 @end /* NGLdapConnection */
801 @implementation NGLdapConnection(PlainPasswordCheck)
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";
813 - (NSString *)dnForLogin:(NSString *)_login baseDN:(NSString *)_baseDN {
822 int ldap_search_result ;
824 if (LDAPDebugEnabled)
825 [self logWithFormat:@"dn for login '%@' on %@", _login, _baseDN];
827 if (self->handle == NULL) {
828 if (![self _reinit]) {
829 NSLog(@"%s: _reinit failed...:", __PRETTY_FUNCTION__);
833 if (![self isBound]) {
835 if (LDAPDebugEnabled)
836 [self logWithFormat:@" attempt to do a simple, anonymous bind .."];
839 if (LDAPInitialBindSpecific)
840 didBind = [self bindWithMethod:@"simple" binddn:LDAPInitialBindDN credentials:LDAPInitialBindPW];
842 didBind = [self bindWithMethod:@"simple" binddn:@"" credentials:@""];
849 if (LDAPDebugEnabled) [self logWithFormat:@" bind failed !"];
853 if (LDAPDebugEnabled) [self logWithFormat:@" bound."];
855 filter = [NSString stringWithFormat:@"(%@=%@)",
856 [[self class] uidAttributeName],
858 if (LDAPDebugEnabled) [self logWithFormat:@" search: '%@'", filter];
860 /* we only check the DN anyway .. */
861 attrs[0] = "objectclass";
864 ldap_search_result = ldap_search_s(self->handle,
865 (char *)[_baseDN UTF8String],
867 (char *)[filter UTF8String],
870 if ((ldap_search_result != LDAP_SUCCESS) && (ldap_search_result != LDAP_PARTIAL_RESULTS)) {
875 if (LDAPDebugEnabled) {
876 [self logWithFormat:@" search failed"];
882 If the entry count is not equal to one, either the UID was not unique or
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];
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 !"];
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 !"];
909 strDN = [[[NSString alloc] initWithUTF8String:dn] autorelease];
912 fprintf(stderr, "Got exception %s while NSUTF8StringEncoding, "
913 "use defaultCStringEncoding",
914 [[localException description] cString]);
920 if (LDAPDebugEnabled) {
921 [self debugWithFormat:
922 @"could not convert DN to UTF-8 string, try cString .."];
924 strDN = [[[NSString alloc] initWithCString:dn] autorelease];
929 ldap_msgfree(result);
932 if (LDAPDebugEnabled) {
933 [self logWithFormat:@" return DN %@", strDN];
938 - (BOOL)checkPassword:(NSString *)_pwd ofLogin:(NSString *)_login
939 atBaseDN:(NSString *)_baseDN
944 if (LDAPDebugEnabled)
945 [self logWithFormat:@"check pwd of login '%@' on %@", _login, _baseDN];
947 if (self->handle == NULL) {
948 if (![self _reinit]) {
949 NSLog(@"%s: _reinit failed...:", __PRETTY_FUNCTION__);
952 strDN = [self dnForLogin:_login baseDN:_baseDN];
954 if (LDAPDebugEnabled) {
955 [self logWithFormat:@" attempting to bind login %@ DN: %@ %s!",
957 [_pwd length] > 0 ? "(with password) " : "(empty password) "];
961 if (LDAPDebugEnabled) {
962 [self logWithFormat:@" missing dn for login %@ atBaseDN %@",
968 Now bind as the DN with the password supplied earlier...
969 Successful bind means the password was correct, otherwise the
975 didBind = [self bindWithMethod:@"simple" binddn:strDN credentials:_pwd];
981 /* invalid login or password */
982 if (LDAPDebugEnabled)
983 [self logWithFormat:@" could not simple bind DN '%@' !", strDN];
989 if (LDAPDebugEnabled) [self logWithFormat:@" bound successfully !"];
993 + (BOOL)checkPassword:(NSString *)_pwd ofLogin:(NSString *)_login
994 atBaseDN:(NSString *)_baseDN
995 onHost:(NSString *)_hostName port:(int)_port
997 NGLdapConnection *ldap;
999 if (LDAPDebugEnabled) {
1000 NSLog(@"LDAP: check pwd of login '%@' on %@,%i,%@ ...",
1001 _login, _hostName, _port, _baseDN);
1004 if ((ldap = [[self alloc] initWithHostName:_hostName port:_port]) == nil) {
1005 if (LDAPDebugEnabled)
1006 NSLog(@"LDAP: got no connection to %@,%i ...", _hostName, _port);
1009 ldap = [ldap autorelease];
1010 if (LDAPDebugEnabled)
1011 NSLog(@"LDAP: use connection: %@", ldap);
1013 return [ldap checkPassword:_pwd ofLogin:_login atBaseDN:_baseDN];
1016 @end /* NGLdapConnection(PlainPasswordCheck) */