1/* LDAPSource.m - this file is part of SOGo 2 * 3 * Copyright (C) 2007-2021 Inverse inc. 4 * 5 * This file is free software; you can redistribute it and/or modify 6 * it under the terms of the GNU General Public License as published by 7 * the Free Software Foundation; either version 2, or (at your option) 8 * any later version. 9 * 10 * This file is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program; see the file COPYING. If not, write to 17 * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, 18 * Boston, MA 02111-1307, USA. 19 */ 20 21 22 23#import <NGExtensions/NSObject+Logs.h> 24#import <EOControl/EOControl.h> 25#import <NGLdap/NGLdapConnection.h> 26#import <NGLdap/NGLdapAttribute.h> 27#import <NGLdap/NGLdapEntry.h> 28#import <NGLdap/NGLdapModification.h> 29#import <NGLdap/NSString+DN.h> 30 31#import "LDAPSourceSchema.h" 32#import "NSArray+Utilities.h" 33#import "NSString+Utilities.h" 34#import "NSString+Crypto.h" 35#import "SOGoCache.h" 36#import "SOGoSystemDefaults.h" 37#import "SOGoUser.h" 38#import "SOGoUserManager.h" 39 40#import "LDAPSource.h" 41 42static Class NSStringK; 43 44#define SafeLDAPCriteria(x) [[[x stringByReplacingString: @"\\" withString: @"\\\\"] \ 45 stringByReplacingString: @"'" withString: @"\\'"] \ 46 stringByReplacingString: @"%" withString: @"%%"] 47 48@implementation LDAPSource 49 50+ (void) initialize 51{ 52 NSStringK = [NSString class]; 53} 54 55// 56// 57// 58+ (id) sourceFromUDSource: (NSDictionary *) theSource 59 inDomain: (NSString *) theDomain 60{ 61 id source; 62 63 source = [[self alloc] initFromUDSource: theSource 64 inDomain: theDomain]; 65 [source autorelease]; 66 67 return source; 68} 69 70// 71// 72// 73- (id) init 74{ 75 if ((self = [super init])) 76 { 77 _sourceID = nil; 78 _displayName = nil; 79 80 _bindDN = nil; 81 _password = nil; 82 _sourceBindDN = nil; 83 _sourceBindPassword = nil; 84 _hostname = nil; 85 _port = 389; 86 _encryption = nil; 87 _domain = nil; 88 89 _baseDN = nil; 90 _pristineBaseDN = nil; 91 _schema = nil; 92 _IDField = @"cn"; /* the first part of a user DN */ 93 _CNField = @"cn"; 94 _UIDField = @"uid"; 95 _mailFields = [[NSArray arrayWithObject: @"mail"] retain]; 96 _contactMapping = nil; 97 // "mail" expands to all entries of MailFieldNames 98 // "name" expands to sn, displayname and cn 99 _searchFields = [[NSArray arrayWithObjects: @"name", @"mail", @"telephonenumber", nil] retain]; 100 _groupObjectClasses = [[NSArray arrayWithObjects: @"group", @"groupofnames", @"groupofuniquenames", @"posixgroup", nil] retain]; 101 _IMAPHostField = nil; 102 _IMAPLoginField = nil; 103 _SieveHostField = nil; 104 _bindFields = nil; 105 _scope = @"sub"; 106 _filter = nil; 107 _userPasswordAlgorithm = nil; 108 _listRequiresDot = YES; 109 110 _passwordPolicy = NO; 111 _updateSambaNTLMPasswords = NO; 112 _lookupFields = [NSArray arrayWithObject: @"*"]; 113 [_lookupFields retain]; 114 115 _kindField = nil; 116 _multipleBookingsField = nil; 117 118 _MSExchangeHostname = nil; 119 120 _modifiers = nil; 121 } 122 123 return self; 124} 125 126// 127// 128// 129- (void) dealloc 130{ 131 [_schema release]; 132 [_bindDN release]; 133 [_password release]; 134 [_sourceBindDN release]; 135 [_sourceBindPassword release]; 136 [_hostname release]; 137 [_encryption release]; 138 [_baseDN release]; 139 [_pristineBaseDN release]; 140 [_IDField release]; 141 [_CNField release]; 142 [_UIDField release]; 143 [_contactMapping release]; 144 [_mailFields release]; 145 [_searchFields release]; 146 [_groupObjectClasses release]; 147 [_IMAPHostField release]; 148 [_IMAPLoginField release]; 149 [_SieveHostField release]; 150 [_bindFields release]; 151 [_filter release]; 152 [_userPasswordAlgorithm release]; 153 [_sourceID release]; 154 [_modulesConstraints release]; 155 [_scope release]; 156 [_domain release]; 157 [_kindField release]; 158 [_multipleBookingsField release]; 159 [_MSExchangeHostname release]; 160 [_modifiers release]; 161 [_displayName release]; 162 [_lookupFields release]; 163 [super dealloc]; 164} 165 166// 167// 168// 169- (id) initFromUDSource: (NSDictionary *) udSource 170 inDomain: (NSString *) sourceDomain 171{ 172 SOGoDomainDefaults *dd; 173 NSNumber *udQueryLimit, *udQueryTimeout, *udGroupExpansionEnabled, *dotValue; 174 175 if ((self = [self init])) 176 { 177 [self setSourceID: [udSource objectForKey: @"id"]]; 178 [self setDisplayName: [udSource objectForKey: @"displayName"]]; 179 180 [self setBindDN: [udSource objectForKey: @"bindDN"] 181 password: [udSource objectForKey: @"bindPassword"] 182 hostname: [udSource objectForKey: @"hostname"] 183 port: [udSource objectForKey: @"port"] 184 encryption: [udSource objectForKey: @"encryption"] 185 bindAsCurrentUser: [udSource objectForKey: @"bindAsCurrentUser"]]; 186 187 [self setBaseDN: [udSource objectForKey: @"baseDN"] 188 IDField: [udSource objectForKey: @"IDFieldName"] 189 CNField: [udSource objectForKey: @"CNFieldName"] 190 UIDField: [udSource objectForKey: @"UIDFieldName"] 191 mailFields: [udSource objectForKey: @"MailFieldNames"] 192 searchFields: [udSource objectForKey: @"SearchFieldNames"] 193 groupObjectClasses: [udSource objectForKey: @"GroupObjectClasses"] 194 IMAPHostField: [udSource objectForKey: @"IMAPHostFieldName"] 195 IMAPLoginField: [udSource objectForKey: @"IMAPLoginFieldName"] 196 SieveHostField: [udSource objectForKey: @"SieveHostFieldName"] 197 bindFields: [udSource objectForKey: @"bindFields"] 198 lookupFields: [udSource objectForKey: @"lookupFields"] 199 kindField: [udSource objectForKey: @"KindFieldName"] 200 andMultipleBookingsField: [udSource objectForKey: @"MultipleBookingsFieldName"]]; 201 202 dotValue = [udSource objectForKey: @"listRequiresDot"]; 203 if (dotValue) 204 [self setListRequiresDot: [dotValue boolValue]]; 205 [self setContactMapping: [udSource objectForKey: @"mapping"] 206 andObjectClasses: [udSource objectForKey: @"objectClasses"]]; 207 208 [self setModifiers: [udSource objectForKey: @"modifiers"]]; 209 ASSIGN(_abOU, [udSource objectForKey: @"abOU"]); 210 211 if ([sourceDomain length]) 212 { 213 NSMutableString *s; 214 215 dd = [SOGoDomainDefaults defaultsForDomain: sourceDomain]; 216 ASSIGN(_domain, sourceDomain); 217 218 // We now look if we have a dynamic baseBN and if we have any value to swap 219 if ([_baseDN rangeOfString: @"%d"].location != NSNotFound) 220 { 221 s = [NSMutableString stringWithString: _baseDN]; 222 [s replaceOccurrencesOfString: @"%d" withString: _domain options: 0 range: NSMakeRange(0, [s length])]; 223 ASSIGN(_baseDN, s); 224 } 225 } 226 else 227 dd = [SOGoSystemDefaults sharedSystemDefaults]; 228 229 _contactInfoAttribute 230 = [udSource objectForKey: @"SOGoLDAPContactInfoAttribute"]; 231 if (!_contactInfoAttribute) 232 _contactInfoAttribute = [dd ldapContactInfoAttribute]; 233 [_contactInfoAttribute retain]; 234 235 udQueryLimit = [udSource objectForKey: @"SOGoLDAPQueryLimit"]; 236 if (udQueryLimit) 237 _queryLimit = [udQueryLimit intValue]; 238 else 239 _queryLimit = [dd ldapQueryLimit]; 240 241 udQueryTimeout = [udSource objectForKey: @"SOGoLDAPQueryTimeout"]; 242 if (udQueryTimeout) 243 _queryTimeout = [udQueryTimeout intValue]; 244 else 245 _queryTimeout = [dd ldapQueryTimeout]; 246 247 if ([[udSource allKeys] containsObject: @"SOGoLDAPGroupExpansionEnabled"]) 248 { 249 udGroupExpansionEnabled = [udSource objectForKey: @"SOGoLDAPGroupExpansionEnabled"]; 250 _groupExpansionEnabled = [udGroupExpansionEnabled boolValue]; 251 } 252 else 253 _groupExpansionEnabled = [dd ldapGroupExpansionEnabled]; 254 255 ASSIGN(_modulesConstraints, [udSource objectForKey: @"ModulesConstraints"]); 256 ASSIGN(_filter, [udSource objectForKey: @"filter"]); 257 ASSIGN(_userPasswordAlgorithm, [udSource objectForKey: @"userPasswordAlgorithm"]); 258 ASSIGN(_scope, ([udSource objectForKey: @"scope"] 259 ? [udSource objectForKey: @"scope"] 260 : (id)@"sub")); 261 262 if (!_userPasswordAlgorithm) 263 _userPasswordAlgorithm = @"none"; 264 265 if ([udSource objectForKey: @"passwordPolicy"]) 266 _passwordPolicy = [[udSource objectForKey: @"passwordPolicy"] boolValue]; 267 268 if ([udSource objectForKey: @"updateSambaNTLMPasswords"]) 269 _updateSambaNTLMPasswords = [[udSource objectForKey: @"updateSambaNTLMPasswords"] boolValue]; 270 271 ASSIGN(_MSExchangeHostname, [udSource objectForKey: @"MSExchangeHostname"]); 272 } 273 274 return self; 275} 276 277- (void) setBindDN: (NSString *) theDN 278{ 279 //NSLog(@"Setting bind DN to %@", theDN); 280 ASSIGN(_bindDN, theDN); 281} 282 283- (NSString *) bindDN 284{ 285 return _bindDN; 286} 287 288- (void) setBindPassword: (NSString *) thePassword 289{ 290 ASSIGN(_password, thePassword); 291} 292 293- (NSString *) bindPassword 294{ 295 return _password; 296} 297 298- (BOOL) bindAsCurrentUser 299{ 300 return _bindAsCurrentUser; 301} 302 303- (void) setBindDN: (NSString *) newBindDN 304 password: (NSString *) newBindPassword 305 hostname: (NSString *) newBindHostname 306 port: (NSString *) newBindPort 307 encryption: (NSString *) newEncryption 308 bindAsCurrentUser: (NSString *) bindAsCurrentUser 309{ 310 ASSIGN(_bindDN, newBindDN); 311 ASSIGN(_password, newBindPassword); 312 ASSIGN(_sourceBindDN, newBindDN); 313 ASSIGN(_sourceBindPassword, newBindPassword); 314 315 ASSIGN(_encryption, [newEncryption uppercaseString]); 316 if ([_encryption isEqualToString: @"SSL"]) 317 _port = 636; 318 ASSIGN(_hostname, newBindHostname); 319 if (newBindPort) 320 _port = [newBindPort intValue]; 321 _bindAsCurrentUser = [bindAsCurrentUser boolValue]; 322} 323 324// 325// 326// 327- (void) setBaseDN: (NSString *) newBaseDN 328 IDField: (NSString *) newIDField 329 CNField: (NSString *) newCNField 330 UIDField: (NSString *) newUIDField 331 mailFields: (NSArray *) newMailFields 332 searchFields: (NSArray *) newSearchFields 333groupObjectClasses: (NSArray *) newGroupObjectClasses 334 IMAPHostField: (NSString *) newIMAPHostField 335 IMAPLoginField: (NSString *) newIMAPLoginField 336 SieveHostField: (NSString *) newSieveHostField 337 bindFields: (id) newBindFields 338 lookupFields: (NSArray *) newLookupFields 339 kindField: (NSString *) newKindField 340 andMultipleBookingsField: (NSString *) newMultipleBookingsField 341{ 342 ASSIGN(_baseDN, [newBaseDN lowercaseString]); 343 ASSIGN(_pristineBaseDN, [newBaseDN lowercaseString]); 344 if (newIDField) 345 ASSIGN(_IDField, [newIDField lowercaseString]); 346 if (newCNField) 347 ASSIGN(_CNField, [newCNField lowercaseString]); 348 if (newUIDField) 349 ASSIGN(_UIDField, [newUIDField lowercaseString]); 350 if (newIMAPHostField) 351 ASSIGN(_IMAPHostField, [newIMAPHostField lowercaseString]); 352 if (newIMAPLoginField) 353 ASSIGN(_IMAPLoginField, [newIMAPLoginField lowercaseString]); 354 if (newSieveHostField) 355 ASSIGN(_SieveHostField, [newSieveHostField lowercaseString]); 356 if (newMailFields) 357 ASSIGN(_mailFields, newMailFields); 358 if (newSearchFields) 359 ASSIGN(_searchFields, newSearchFields); 360 if (newGroupObjectClasses) 361 ASSIGN(_groupObjectClasses, newGroupObjectClasses); 362 if (newBindFields) 363 { 364 // Before SOGo v1.2.0, bindFields was a comma-separated list 365 // of values. So it could be configured as: 366 // 367 // bindFields = foo; 368 // bindFields = "foo, bar, baz"; 369 // 370 // SOGo v1.2.0 and upwards redefined that parameter as an array 371 // so we would have instead: 372 // 373 // bindFields = (foo); 374 // bindFields = (foo, bar, baz); 375 // 376 // We check for the old format and we support it. 377 if ([newBindFields isKindOfClass: [NSArray class]]) 378 ASSIGN(_bindFields, newBindFields); 379 else 380 { 381 [self logWithFormat: @"WARNING: using old bindFields format - please update it"]; 382 ASSIGN(_bindFields, [newBindFields componentsSeparatedByString: @","]); 383 } 384 } 385 if (newLookupFields) 386 ASSIGN(_lookupFields, newLookupFields); 387 if (newKindField) 388 ASSIGN(_kindField, [newKindField lowercaseString]); 389 if (newMultipleBookingsField) 390 ASSIGN(_multipleBookingsField, [newMultipleBookingsField lowercaseString]); 391} 392 393- (void) setListRequiresDot: (BOOL) theBool 394{ 395 _listRequiresDot = theBool; 396} 397 398- (BOOL) listRequiresDot 399{ 400 return _listRequiresDot; 401} 402 403- (NSArray *) searchFields 404{ 405 return _searchFields; 406} 407 408- (void) setContactMapping: (NSDictionary *) theMapping 409 andObjectClasses: (NSArray *) theObjectClasses 410{ 411 ASSIGN(_contactMapping, theMapping); 412 ASSIGN(_contactObjectClasses, theObjectClasses); 413} 414 415// 416// 417// 418- (BOOL) _setupEncryption: (NGLdapConnection *) theConnection 419{ 420 BOOL rc; 421 422 if ([_encryption isEqualToString: @"SSL"]) 423 rc = [theConnection useSSL]; 424 else if ([_encryption isEqualToString: @"STARTTLS"]) 425 rc = [theConnection startTLS]; 426 else 427 { 428 [self errorWithFormat: 429 @"encryption scheme '%@' not supported:" 430 @" use 'SSL' or 'STARTTLS'", _encryption]; 431 rc = NO; 432 } 433 434 return rc; 435} 436 437// 438// 439// 440- (id) connection 441{ 442 NGLdapConnection *ldapConnection; 443 NSString *value, *key; 444 SOGoCache *cache; 445 446 NS_DURING 447 { 448 //NSLog(@"Creating NGLdapConnection instance for bindDN '%@'", _bindDN); 449 ldapConnection = [[NGLdapConnection alloc] initWithHostName: _hostname 450 port: _port]; 451 [ldapConnection autorelease]; 452 if (![_encryption length] || [self _setupEncryption: ldapConnection]) 453 { 454 [ldapConnection bindWithMethod: @"simple" 455 binddn: _bindDN 456 credentials: _password]; 457 if (_queryLimit > 0) 458 [ldapConnection setQuerySizeLimit: _queryLimit]; 459 if (_queryTimeout > 0) 460 [ldapConnection setQueryTimeLimit: _queryTimeout]; 461 if (!_schema) 462 { 463 _schema = [LDAPSourceSchema new]; 464 cache = [SOGoCache sharedCache]; 465 key = [NSString stringWithFormat: @"schema:%@", _sourceID]; 466 value = [cache valueForKey: key]; 467 468 if (value) 469 { 470 [_schema setSchema: (NSMutableDictionary *)[value objectFromJSONString]]; 471 } 472 else 473 { 474 // We go check in the LDAP directory 475 [_schema readSchemaFromConnection: ldapConnection]; 476 [cache setValue: [_schema jsonRepresentation] forKey: key]; 477 } 478 } 479 } 480 else 481 ldapConnection = nil; 482 } 483 NS_HANDLER 484 { 485 [self errorWithFormat: @"Could not bind to the LDAP server %@ (%d) " 486 @"using the bind DN: %@", _hostname, _port, _bindDN]; 487 [self errorWithFormat: @"%@", localException]; 488 ldapConnection = nil; 489 } 490 NS_ENDHANDLER; 491 492 return ldapConnection; 493} 494 495- (NGLdapConnection *) _ldapConnection 496{ 497 return (NGLdapConnection *)[self connection]; 498} 499 500- (NSString *) domain 501{ 502 return _domain; 503} 504 505/* user management */ 506- (EOQualifier *) _qualifierForBindFilter: (NSString *) theUID 507{ 508 NSMutableString *qs; 509 NSString *escapedUid; 510 NSEnumerator *fields; 511 NSString *currentField; 512 513 qs = [NSMutableString string]; 514 515 escapedUid = SafeLDAPCriteria(theUID); 516 517 fields = [_bindFields objectEnumerator]; 518 while ((currentField = [fields nextObject])) 519 [qs appendFormat: @" OR (%@='%@')", currentField, escapedUid]; 520 521 if (_filter && [_filter length]) 522 [qs appendFormat: @" AND %@", _filter]; 523 524 [qs deleteCharactersInRange: NSMakeRange(0, 4)]; 525 526 return [EOQualifier qualifierWithQualifierFormat: qs]; 527} 528 529- (NSString *) _fetchUserDNForLogin: (NSString *) theLogin 530{ 531 NGLdapConnection *ldapConnection; 532 EOQualifier *qualifier; 533 NSEnumerator *entries; 534 NSArray *attributes; 535 NSString *userDN; 536 537 ldapConnection = [self _ldapConnection]; 538 qualifier = [self _qualifierForBindFilter: theLogin]; 539 attributes = [NSArray arrayWithObject: @"dn"]; 540 541 if ([_scope caseInsensitiveCompare: @"BASE"] == NSOrderedSame) 542 entries = [ldapConnection baseSearchAtBaseDN: _baseDN 543 qualifier: qualifier 544 attributes: attributes]; 545 else if ([_scope caseInsensitiveCompare: @"ONE"] == NSOrderedSame) 546 entries = [ldapConnection flatSearchAtBaseDN: _baseDN 547 qualifier: qualifier 548 attributes: attributes]; 549 else 550 entries = [ldapConnection deepSearchAtBaseDN: _baseDN 551 qualifier: qualifier 552 attributes: attributes]; 553 554 userDN = [[entries nextObject] dn]; 555 556 return userDN; 557} 558 559// 560// 561// 562- (BOOL) checkLogin: (NSString *) _login 563 password: (NSString *) _pwd 564 perr: (SOGoPasswordPolicyError *) _perr 565 expire: (int *) _expire 566 grace: (int *) _grace 567{ 568 NGLdapConnection *bindConnection; 569 NSString *userDN; 570 BOOL didBind; 571 572 didBind = NO; 573 574 NS_DURING 575 if ([_login length] > 0 && [_pwd length] > 0) 576 { 577 // We check if SOGo admins have deviced a top-level SOGoUserSources with a dynamic base DN. 578 // This is a supported multi-domain configuration. We alter the baseDN in this case by extracting 579 // the domain from the login. 580 [self updateBaseDNFromLogin: _login]; 581 582 bindConnection = [[NGLdapConnection alloc] initWithHostName: _hostname 583 port: _port]; 584 if (![_encryption length] || [self _setupEncryption: bindConnection]) 585 { 586 if (_queryTimeout > 0) 587 [bindConnection setQueryTimeLimit: _queryTimeout]; 588 589 userDN = [[SOGoCache sharedCache] distinguishedNameForLogin: _login]; 590 591 if (!userDN) 592 { 593 if (_bindFields) 594 { 595 // We MUST always use the source's bindDN/password in 596 // order to lookup the user's DN. This is important since 597 // if we use bindAsCurrentUser, we could stay bound and 598 // lookup the user's DN (for an other user that is trying 599 // to log in) but not be able to do so due to ACLs in LDAP. 600 [self setBindDN: _sourceBindDN]; 601 [self setBindPassword: _sourceBindPassword]; 602 userDN = [self _fetchUserDNForLogin: _login]; 603 } 604 else 605 userDN = [NSString stringWithFormat: @"%@=%@,%@", 606 _IDField, [_login escapedForLDAPDN], _baseDN]; 607 } 608 609 if (userDN) 610 { 611 if (!_passwordPolicy) 612 didBind = [bindConnection bindWithMethod: @"simple" 613 binddn: userDN 614 credentials: _pwd]; 615 else 616 didBind = [bindConnection bindWithMethod: @"simple" 617 binddn: userDN 618 credentials: _pwd 619 perr: (void *)_perr 620 expire: _expire 621 grace: _grace]; 622 623 if (didBind) 624 // We cache the _login <-> userDN entry to speed up things 625 [[SOGoCache sharedCache] setDistinguishedName: userDN 626 forLogin: _login]; 627 } 628 } 629 } 630 NS_HANDLER 631 { 632 [self logWithFormat: @"%@", localException]; 633 } 634 NS_ENDHANDLER; 635 636 [bindConnection release]; 637 return didBind; 638} 639 640/** 641 * Encrypts a string using this source password algorithm. 642 * @param plainPassword the unencrypted password. 643 * @return a new encrypted string. 644 * @see _isPassword:equalTo: 645 */ 646- (NSString *) _encryptPassword: (NSString *) thePassword 647{ 648 NSString *pass; 649 pass = [thePassword asCryptedPassUsingScheme: _userPasswordAlgorithm 650 keyPath: nil]; 651 652 if (pass == nil) 653 { 654 [self errorWithFormat: @"Unsupported user-password algorithm: %@", _userPasswordAlgorithm]; 655 return nil; 656 } 657 658 return [NSString stringWithFormat: @"{%@}%@", _userPasswordAlgorithm, pass]; 659} 660 661- (BOOL) _ldapModifyAttribute: (NSString *) theAttribute 662 withValue: (NSString *) theValue 663 userDN: (NSString *) theUserDN 664 password: (NSString *) theUserPassword 665 connection: (NGLdapConnection *) bindConnection 666{ 667 NGLdapModification *mod; 668 NGLdapAttribute *attr; 669 NSArray *changes; 670 671 BOOL didChange; 672 673 attr = [[NGLdapAttribute alloc] initWithAttributeName: theAttribute]; 674 [attr addStringValue: theValue]; 675 676 mod = [NGLdapModification replaceModification: attr]; 677 678 changes = [NSArray arrayWithObject: mod]; 679 680 if ([bindConnection bindWithMethod: @"simple" 681 binddn: theUserDN 682 credentials: theUserPassword]) 683 { 684 didChange = [bindConnection modifyEntryWithDN: theUserDN 685 changes: changes]; 686 } 687 else 688 didChange = NO; 689 690 RELEASE(attr); 691 692 return didChange; 693} 694 695// 696// 697// 698- (BOOL) changePasswordForLogin: (NSString *) login 699 oldPassword: (NSString *) oldPassword 700 newPassword: (NSString *) newPassword 701 perr: (SOGoPasswordPolicyError *) perr 702 703{ 704 NGLdapConnection *bindConnection; 705 NSString *userDN; 706 BOOL didChange; 707 708 didChange = NO; 709 710 NS_DURING 711 if ([login length] > 0) 712 { 713 bindConnection = [[NGLdapConnection alloc] initWithHostName: _hostname 714 port: _port]; 715 if (![_encryption length] || [self _setupEncryption: bindConnection]) 716 { 717 if (_queryTimeout > 0) 718 [bindConnection setQueryTimeLimit: _queryTimeout]; 719 if (_bindFields) 720 userDN = [self _fetchUserDNForLogin: login]; 721 else 722 { 723 userDN = [NSString stringWithFormat: @"%@=%@,%@", 724 _IDField, [login escapedForLDAPDN], _baseDN]; 725 } 726 if (userDN) 727 { 728 if ([bindConnection isADCompatible]) 729 { 730 if ([bindConnection bindWithMethod: @"simple" 731 binddn: userDN 732 credentials: oldPassword]) 733 { 734 didChange = [bindConnection changeADPasswordAtDn: userDN 735 oldPassword: oldPassword 736 newPassword: newPassword]; 737 } 738 } 739 else if (_passwordPolicy) 740 { 741 if ([bindConnection bindWithMethod: @"simple" 742 binddn: _sourceBindDN 743 credentials: _sourceBindPassword]) 744 { 745 didChange = [bindConnection changePasswordAtDn: userDN 746 oldPassword: oldPassword 747 newPassword: newPassword 748 perr: (void *)perr]; 749 } 750 } 751 else 752 { 753 // We don't use a password policy - we simply use 754 // a modify-op to change the password 755 NSString* encryptedPass; 756 757 if ([_userPasswordAlgorithm isEqualToString: @"none"]) 758 { 759 encryptedPass = newPassword; 760 } 761 else 762 { 763 encryptedPass = [self _encryptPassword: newPassword]; 764 } 765 766 if (encryptedPass != nil) 767 { 768 if ([bindConnection bindWithMethod: @"simple" 769 binddn: userDN 770 credentials: oldPassword]) 771 { 772 didChange = [self _ldapModifyAttribute: @"userPassword" 773 withValue: encryptedPass 774 userDN: userDN 775 password: oldPassword 776 connection: bindConnection]; 777 if (didChange) 778 { 779 *perr = PolicyNoError; 780 } 781 } 782 } 783 } 784 785 // We must check if we must update the Samba NT/LM password hashes 786 if (didChange && _updateSambaNTLMPasswords) 787 { 788 [self _ldapModifyAttribute: @"sambaNTPassword" 789 withValue: [newPassword asNTHash] 790 userDN: userDN 791 password: newPassword 792 connection: bindConnection]; 793 794 [self _ldapModifyAttribute: @"sambaLMPassword" 795 withValue: [newPassword asLMHash] 796 userDN: userDN 797 password: newPassword 798 connection: bindConnection]; 799 } 800 } 801 } 802 } 803 NS_HANDLER 804 { 805 if ([[localException name] isEqual: @"LDAPException"] && 806 ([[[localException userInfo] objectForKey: @"error_code"] intValue] == LDAP_CONSTRAINT_VIOLATION)) 807 { 808 *perr = PolicyInsufficientPasswordQuality; 809 } 810 else 811 { 812 [self logWithFormat: @"%@", localException]; 813 } 814 } 815 NS_ENDHANDLER ; 816 817 [bindConnection release]; 818 return didChange; 819} 820 821 822/** 823 * Search for contacts matching some string. 824 * @param filter the string to search for 825 * @see fetchContactsMatching: 826 * @return a EOQualifier matching the filter 827 */ 828- (EOQualifier *) _qualifierForFilter: (NSString *) filter 829 onCriteria: (NSArray *) criteria 830{ 831 NSEnumerator *criteriaList; 832 NSMutableArray *fields; 833 NSString *fieldFormat, *currentCriteria, *searchFormat, *escapedFilter; 834 EOQualifier *qualifier; 835 NSMutableString *qs; 836 837 escapedFilter = SafeLDAPCriteria(filter); 838 qs = [NSMutableString string]; 839 840 if (([escapedFilter length] == 0 && !_listRequiresDot) || [escapedFilter isEqualToString: @"."]) 841 { 842 [qs appendFormat: @"(%@='*')", _CNField]; 843 } 844 else 845 { 846 fieldFormat = [NSString stringWithFormat: @"(%%@='*%@*')", escapedFilter]; 847 if (criteria) 848 criteriaList = [criteria objectEnumerator]; 849 else 850 criteriaList = [[self searchFields] objectEnumerator]; 851 852 fields = [NSMutableArray array]; 853 while (( currentCriteria = [criteriaList nextObject] )) 854 { 855 if ([currentCriteria isEqualToString: @"name"]) 856 { 857 [fields addObject: @"sn"]; 858 [fields addObject: @"displayname"]; 859 [fields addObject: @"cn"]; 860 } 861 else if ([currentCriteria isEqualToString: @"mail"]) 862 { 863 // Expand to all mail fields 864 [fields addObject: currentCriteria]; 865 [fields addObjectsFromArray: _mailFields]; 866 } 867 else if ([[self searchFields] containsObject: currentCriteria]) 868 [fields addObject: currentCriteria]; 869 } 870 871 searchFormat = [[[fields uniqueObjects] stringsWithFormat: fieldFormat] componentsJoinedByString: @" OR "]; 872 [qs appendString: searchFormat]; 873 } 874 875 if (_filter && [_filter length]) 876 [qs appendFormat: @" AND %@", _filter]; 877 878 if ([qs length]) 879 qualifier = [EOQualifier qualifierWithQualifierFormat: qs]; 880 else 881 qualifier = nil; 882 883 return qualifier; 884} 885 886- (EOQualifier *) _qualifierForUIDFilter: (NSString *) uid 887{ 888 NSString *mailFormat, *fieldFormat, *escapedUid, *currentField; 889 NSEnumerator *bindFieldsEnum; 890 NSMutableString *qs; 891 892 escapedUid = SafeLDAPCriteria(uid); 893 894 fieldFormat = [NSString stringWithFormat: @"(%%@='%@')", escapedUid]; 895 mailFormat = [[_mailFields stringsWithFormat: fieldFormat] 896 componentsJoinedByString: @" OR "]; 897 qs = [NSMutableString stringWithFormat: @"(%@='%@') OR %@", 898 _UIDField, escapedUid, mailFormat]; 899 if (_bindFields) 900 { 901 bindFieldsEnum = [_bindFields objectEnumerator]; 902 while ((currentField = [bindFieldsEnum nextObject])) 903 { 904 if ([currentField caseInsensitiveCompare: _UIDField] != NSOrderedSame 905 && ![_mailFields containsObject: currentField]) 906 [qs appendFormat: @" OR (%@='%@')", [currentField stringByTrimmingSpaces], escapedUid]; 907 } 908 } 909 910 if (_filter && [_filter length]) 911 [qs appendFormat: @" AND %@", _filter]; 912 913 return [EOQualifier qualifierWithQualifierFormat: qs]; 914} 915 916/* 917- (NSArray *) _constraintsFields 918{ 919 NSMutableArray *fields; 920 NSEnumerator *values; 921 NSDictionary *currentConstraint; 922 923 fields = [NSMutableArray array]; 924 values = [[modulesConstraints allValues] objectEnumerator]; 925 while ((currentConstraint = [values nextObject])) 926 [fields addObjectsFromArray: [currentConstraint allKeys]]; 927 928 return fields; 929} 930*/ 931 932/* This is required for SQL sources when DomainFieldName is enabled. 933 * For LDAP, simply discard the domain and call the original method */ 934- (NSArray *) allEntryIDsVisibleFromDomain: (NSString *) theDomain 935{ 936 return [self allEntryIDs]; 937} 938 939- (NSArray *) allEntryIDs 940{ 941 NSEnumerator *entries; 942 NGLdapEntry *currentEntry; 943 NGLdapConnection *ldapConnection; 944 EOQualifier *qualifier; 945 NSMutableString *qs; 946 NSString *value; 947 NSArray *attributes; 948 NSMutableArray *ids; 949 950 ids = [NSMutableArray array]; 951 952 ldapConnection = [self _ldapConnection]; 953 attributes = [NSArray arrayWithObject: _IDField]; 954 955 qs = [NSMutableString stringWithFormat: @"(%@='*')", _CNField]; 956 if ([_filter length]) 957 [qs appendFormat: @" AND %@", _filter]; 958 qualifier = [EOQualifier qualifierWithQualifierFormat: qs]; 959 960 if ([_scope caseInsensitiveCompare: @"BASE"] == NSOrderedSame) 961 entries = [ldapConnection baseSearchAtBaseDN: _baseDN 962 qualifier: qualifier 963 attributes: attributes]; 964 else if ([_scope caseInsensitiveCompare: @"ONE"] == NSOrderedSame) 965 entries = [ldapConnection flatSearchAtBaseDN: _baseDN 966 qualifier: qualifier 967 attributes: attributes]; 968 else 969 entries = [ldapConnection deepSearchAtBaseDN: _baseDN 970 qualifier: qualifier 971 attributes: attributes]; 972 973 while ((currentEntry = [entries nextObject])) 974 { 975 value = [[currentEntry attributeWithName: _IDField] 976 stringValueAtIndex: 0]; 977 if ([value length] > 0) 978 [ids addObject: value]; 979 } 980 981 return ids; 982} 983 984- (void) _fillEmailsOfEntry: (NGLdapEntry *) ldapEntry 985 intoLDIFRecord: (NSMutableDictionary *) ldifRecord 986{ 987 NSString *currentFieldName, *ldapValue; 988 NSEnumerator *emailFields; 989 NSMutableArray *emails; 990 NSArray *allValues; 991 992 emails = [[NSMutableArray alloc] init]; 993 emailFields = [_mailFields objectEnumerator]; 994 while ((currentFieldName = [emailFields nextObject])) 995 { 996 allValues = [[ldapEntry attributeWithName: currentFieldName] 997 allStringValues]; 998 999 // Special case handling for Microsoft Active Directory. proxyAddresses 1000 // is generally prefixed with smtp: - if we find this (or any value preceeding 1001 // the semi-colon), we strip it. See https://msdn.microsoft.com/en-us/library/ms679424(v=vs.85).aspx 1002 if ([currentFieldName caseInsensitiveCompare: @"proxyAddresses"] == NSOrderedSame) 1003 { 1004 NSRange r; 1005 int i; 1006 1007 for (i = 0; i < [allValues count]; i++) 1008 { 1009 ldapValue = [allValues objectAtIndex: i]; 1010 r = [ldapValue rangeOfString: @":"]; 1011 1012 if (r.length) 1013 { 1014 // We only keep "smtp" ones 1015 if ([[ldapValue lowercaseString] hasPrefix: @"smtp"]) 1016 [emails addObject: [ldapValue substringFromIndex: r.location+1]]; 1017 } 1018 else 1019 [emails addObject: ldapValue]; 1020 } 1021 } 1022 else 1023 [emails addObjectsFromArray: allValues]; 1024 } 1025 [ldifRecord setObject: emails forKey: @"c_emails"]; 1026 [emails release]; 1027 1028 if (_IMAPHostField) 1029 { 1030 ldapValue = [[ldapEntry attributeWithName: _IMAPHostField] stringValueAtIndex: 0]; 1031 if ([ldapValue length] > 0) 1032 [ldifRecord setObject: ldapValue forKey: @"c_imaphostname"]; 1033 } 1034 1035 if (_IMAPLoginField) 1036 { 1037 ldapValue = [[ldapEntry attributeWithName: _IMAPLoginField] stringValueAtIndex: 0]; 1038 if ([ldapValue length] > 0) 1039 [ldifRecord setObject: ldapValue forKey: @"c_imaplogin"]; 1040 } 1041 1042 if (_SieveHostField) 1043 { 1044 ldapValue = [[ldapEntry attributeWithName: _SieveHostField] stringValueAtIndex: 0]; 1045 if ([ldapValue length] > 0) 1046 [ldifRecord setObject: ldapValue forKey: @"c_sievehostname"]; 1047 } 1048} 1049 1050- (void) _fillConstraints: (NGLdapEntry *) ldapEntry 1051 forModule: (NSString *) module 1052 intoLDIFRecord: (NSMutableDictionary *) ldifRecord 1053{ 1054 NSDictionary *constraints; 1055 NSEnumerator *matches, *ldapValues; 1056 NSString *currentMatch, *currentValue, *ldapValue; 1057 BOOL result; 1058 1059 result = YES; 1060 1061 constraints = [_modulesConstraints objectForKey: module]; 1062 if (constraints) 1063 { 1064 matches = [[constraints allKeys] objectEnumerator]; 1065 while (result == YES && (currentMatch = [matches nextObject])) 1066 { 1067 ldapValues = [[[ldapEntry attributeWithName: currentMatch] allStringValues] objectEnumerator]; 1068 currentValue = [constraints objectForKey: currentMatch]; 1069 result = NO; 1070 1071 while (result == NO && (ldapValue = [ldapValues nextObject])) 1072 if ([ldapValue caseInsensitiveMatches: currentValue]) 1073 result = YES; 1074 } 1075 } 1076 1077 [ldifRecord setObject: [NSNumber numberWithBool: result] 1078 forKey: [NSString stringWithFormat: @"%@Access", module]]; 1079} 1080 1081/* conversion LDAP -> SOGo inetOrgPerson entry */ 1082- (void) applyContactMappingToResult: (NSMutableDictionary *) ldifRecord 1083{ 1084 NSArray *sourceFields; 1085 NSArray *keys; 1086 NSString *key, *field, *value; 1087 NSUInteger count, max, fieldCount, fieldMax; 1088 BOOL filled; 1089 1090 keys = [_contactMapping allKeys]; 1091 max = [keys count]; 1092 for (count = 0; count < max; count++) 1093 { 1094 key = [keys objectAtIndex: count]; 1095 sourceFields = [_contactMapping objectForKey: key]; 1096 if ([sourceFields isKindOfClass: NSStringK]) 1097 sourceFields = [NSArray arrayWithObject: sourceFields]; 1098 fieldMax = [sourceFields count]; 1099 filled = NO; 1100 for (fieldCount = 0; 1101 !filled && fieldCount < fieldMax; 1102 fieldCount++) 1103 { 1104 field = [[sourceFields objectAtIndex: fieldCount] lowercaseString]; 1105 value = [ldifRecord objectForKey: field]; 1106 if (value) 1107 { 1108 [ldifRecord setObject: value forKey: [key lowercaseString]]; 1109 filled = YES; 1110 } 1111 } 1112 } 1113} 1114 1115/* conversion SOGo inetOrgPerson entry -> LDAP */ 1116- (void) applyContactMappingToOutput: (NSMutableDictionary *) ldifRecord 1117{ 1118 NSArray *sourceFields; 1119 NSArray *keys; 1120 NSString *key, *lowerKey, *field, *value; 1121 NSUInteger count, max, fieldCount, fieldMax; 1122 1123 if (_contactObjectClasses) 1124 [ldifRecord setObject: _contactObjectClasses 1125 forKey: @"objectclass"]; 1126 1127 keys = [_contactMapping allKeys]; 1128 max = [keys count]; 1129 for (count = 0; count < max; count++) 1130 { 1131 key = [keys objectAtIndex: count]; 1132 lowerKey = [key lowercaseString]; 1133 value = [ldifRecord objectForKey: lowerKey]; 1134 if ([value length] > 0) 1135 { 1136 sourceFields = [_contactMapping objectForKey: key]; 1137 if ([sourceFields isKindOfClass: NSStringK]) 1138 sourceFields = [NSArray arrayWithObject: sourceFields]; 1139 1140 fieldMax = [sourceFields count]; 1141 for (fieldCount = 0; fieldCount < fieldMax; fieldCount++) 1142 { 1143 field = [[sourceFields objectAtIndex: fieldCount] 1144 lowercaseString]; 1145 [ldifRecord setObject: value forKey: field]; 1146 } 1147 } 1148 } 1149} 1150 1151- (NSDictionary *) _convertLDAPEntryToContact: (NGLdapEntry *) ldapEntry 1152{ 1153 NSMutableDictionary *ldifRecord; 1154 NSString *value; 1155 static NSArray *resourceKinds = nil; 1156 NSMutableArray *classes; 1157 NSEnumerator *gclasses; 1158 NSString *gclass; 1159 id o; 1160 1161 if (!resourceKinds) 1162 resourceKinds = [[NSArray alloc] initWithObjects: @"location", @"thing", 1163 @"group", nil]; 1164 1165 ldifRecord = [ldapEntry asDictionary]; 1166 [ldifRecord setObject: self forKey: @"source"]; 1167 [ldifRecord setObject: [ldapEntry dn] forKey: @"dn"]; 1168 1169 // We get our objectClass attribute values. We lowercase 1170 // everything for ease of search after. 1171 o = [ldapEntry objectClasses]; 1172 classes = nil; 1173 1174 if (o) 1175 { 1176 int i, c; 1177 1178 classes = [NSMutableArray arrayWithArray: o]; 1179 c = [classes count]; 1180 for (i = 0; i < c; i++) 1181 [classes replaceObjectAtIndex: i 1182 withObject: [[classes objectAtIndex: i] lowercaseString]]; 1183 } 1184 1185 if (classes) 1186 { 1187 // We check if our entry is a resource. We also support 1188 // determining resources based on the KindFieldName attribute 1189 // value - see below. 1190 if ([classes containsObject: @"calendarresource"]) 1191 { 1192 [ldifRecord setObject: [NSNumber numberWithInt: 1] 1193 forKey: @"isResource"]; 1194 } 1195 else 1196 { 1197 // We check if our entry is a group. If so, we set the 1198 // 'isGroup' custom attribute. 1199 gclasses = [_groupObjectClasses objectEnumerator]; 1200 while ((gclass = [gclasses nextObject])) 1201 if ([classes containsObject: [gclass lowercaseString]]) 1202 { 1203 [ldifRecord setObject: [NSNumber numberWithInt: 1] 1204 forKey: @"isGroup"]; 1205 break; 1206 } 1207 } 1208 } 1209 1210 // We check if that entry corresponds to a resource. For this, 1211 // kindField must be defined and it must hold one of those values 1212 // 1213 // location 1214 // thing 1215 // group 1216 // 1217 if ([_kindField length] > 0) 1218 { 1219 value = [ldifRecord objectForKey: [_kindField lowercaseString]]; 1220 if ([value isKindOfClass: NSStringK] 1221 && [resourceKinds containsObject: value]) 1222 [ldifRecord setObject: [NSNumber numberWithInt: 1] 1223 forKey: @"isResource"]; 1224 } 1225 1226 // We check for the number of simultanous bookings that is allowed. 1227 // A value of 0 means that there's no limit. 1228 if ([_multipleBookingsField length] > 0) 1229 { 1230 value = [ldifRecord objectForKey: [_multipleBookingsField lowercaseString]]; 1231 [ldifRecord setObject: [NSNumber numberWithInt: [value intValue]] 1232 forKey: @"numberOfSimultaneousBookings"]; 1233 } 1234 1235 value = [[ldapEntry attributeWithName: _IDField] stringValueAtIndex: 0]; 1236 if (!value) 1237 value = @""; 1238 [ldifRecord setObject: value forKey: @"c_name"]; 1239 value = [[ldapEntry attributeWithName: _UIDField] stringValueAtIndex: 0]; 1240 if (!value) 1241 value = @""; 1242// else 1243// { 1244// Eventually, we could check at this point if the entry is a group 1245// and prefix the UID with a "@" 1246// } 1247 [ldifRecord setObject: value forKey: @"c_uid"]; 1248 value = [[ldapEntry attributeWithName: _CNField] stringValueAtIndex: 0]; 1249 if (!value) 1250 value = @""; 1251 [ldifRecord setObject: value forKey: @"c_cn"]; 1252 /* if "displayName" is not set, we use CNField because it must exist */ 1253 if (![ldifRecord objectForKey: @"displayname"]) 1254 [ldifRecord setObject: value forKey: @"displayname"]; 1255 1256 if (_contactInfoAttribute) 1257 { 1258 value = [[ldapEntry attributeWithName: _contactInfoAttribute] 1259 stringValueAtIndex: 0]; 1260 if (!value) 1261 value = @""; 1262 } 1263 else 1264 value = @""; 1265 [ldifRecord setObject: value forKey: @"c_info"]; 1266 1267 if (_domain) 1268 value = _domain; 1269 else 1270 value = @""; 1271 [ldifRecord setObject: value forKey: @"c_domain"]; 1272 1273 [self _fillEmailsOfEntry: ldapEntry intoLDIFRecord: ldifRecord]; 1274 [self _fillConstraints: ldapEntry forModule: @"Calendar" 1275 intoLDIFRecord: (NSMutableDictionary *) ldifRecord]; 1276 [self _fillConstraints: ldapEntry forModule: @"Mail" 1277 intoLDIFRecord: (NSMutableDictionary *) ldifRecord]; 1278 [self _fillConstraints: ldapEntry forModule: @"ActiveSync" 1279 intoLDIFRecord: (NSMutableDictionary *) ldifRecord]; 1280 1281 if (_contactMapping) 1282 [self applyContactMappingToResult: ldifRecord]; 1283 1284 return ldifRecord; 1285} 1286 1287- (NSArray *) fetchContactsMatching: (NSString *) match 1288 withCriteria: (NSArray *) criteria 1289 inDomain: (NSString *) theDomain 1290{ 1291 NSAutoreleasePool *pool; 1292 NGLdapConnection *ldapConnection; 1293 NGLdapEntry *currentEntry; 1294 NSEnumerator *entries; 1295 NSMutableArray *contacts; 1296 EOQualifier *qualifier; 1297 unsigned int i; 1298 1299 contacts = [NSMutableArray array]; 1300 1301 if ([match length] > 0 || !_listRequiresDot) 1302 { 1303 ldapConnection = [self _ldapConnection]; 1304 qualifier = [self _qualifierForFilter: match onCriteria: criteria]; 1305 1306 if ([_scope caseInsensitiveCompare: @"BASE"] == NSOrderedSame) 1307 entries = [ldapConnection baseSearchAtBaseDN: _baseDN 1308 qualifier: qualifier 1309 attributes: _lookupFields]; 1310 else if ([_scope caseInsensitiveCompare: @"ONE"] == NSOrderedSame) 1311 entries = [ldapConnection flatSearchAtBaseDN: _baseDN 1312 qualifier: qualifier 1313 attributes: _lookupFields]; 1314 else /* we do it like before */ 1315 entries = [ldapConnection deepSearchAtBaseDN: _baseDN 1316 qualifier: qualifier 1317 attributes: _lookupFields]; 1318 1319 i = 0; 1320 pool = [NSAutoreleasePool new]; 1321 while ((currentEntry = [entries nextObject])) 1322 { 1323 [contacts addObject: 1324 [self _convertLDAPEntryToContact: currentEntry]]; 1325 i++; 1326 if (i % 10 == 0) 1327 { 1328 [pool release]; 1329 pool = [NSAutoreleasePool new]; 1330 } 1331 } 1332 [pool release]; 1333 } 1334 1335 return contacts; 1336} 1337 1338- (NGLdapEntry *) _lookupLDAPEntry: (EOQualifier *) theQualifier 1339 usingConnection: (id) connection 1340{ 1341 NGLdapConnection *ldapConnection; 1342 NSEnumerator *entries; 1343 1344 ldapConnection = (NGLdapConnection *)connection; 1345 1346 if ([_scope caseInsensitiveCompare: @"BASE"] == NSOrderedSame) 1347 entries = [ldapConnection baseSearchAtBaseDN: _baseDN 1348 qualifier: theQualifier 1349 attributes: _lookupFields]; 1350 else if ([_scope caseInsensitiveCompare: @"ONE"] == NSOrderedSame) 1351 entries = [ldapConnection flatSearchAtBaseDN: _baseDN 1352 qualifier: theQualifier 1353 attributes: _lookupFields]; 1354 else 1355 entries = [ldapConnection deepSearchAtBaseDN: _baseDN 1356 qualifier: theQualifier 1357 attributes: _lookupFields]; 1358 1359 return [entries nextObject]; 1360} 1361 1362- (NGLdapEntry *) _lookupLDAPEntry: (EOQualifier *) theQualifier 1363{ 1364 return [self _lookupLDAPEntry: theQualifier 1365 usingConnection: [self _ldapConnection]]; 1366} 1367 1368- (NSDictionary *) lookupContactEntry: (NSString *) theID 1369 inDomain: (NSString *) theDomain 1370 usingConnection: (id) connection 1371{ 1372 NGLdapEntry *ldapEntry; 1373 EOQualifier *qualifier; 1374 NSString *s; 1375 NSDictionary *ldifRecord; 1376 1377 ldifRecord = nil; 1378 1379 if ([theID length] > 0) 1380 { 1381 s = [NSString stringWithFormat: @"(%@='%@')", 1382 _IDField, SafeLDAPCriteria(theID)]; 1383 qualifier = [EOQualifier qualifierWithQualifierFormat: s]; 1384 ldapEntry = [self _lookupLDAPEntry: qualifier 1385 usingConnection: connection]; 1386 if (ldapEntry) 1387 ldifRecord = [self _convertLDAPEntryToContact: ldapEntry]; 1388 } 1389 1390 return ldifRecord; 1391} 1392 1393- (NSDictionary *) lookupContactEntry: (NSString *) theID 1394 inDomain: (NSString *) theDomain 1395{ 1396 return [self lookupContactEntry: theID 1397 inDomain: theDomain 1398 usingConnection: [self _ldapConnection]]; 1399} 1400 1401- (NSDictionary *) lookupContactEntryWithUIDorEmail: (NSString *) uid 1402 inDomain: (NSString *) theDomain 1403{ 1404 NGLdapEntry *ldapEntry; 1405 EOQualifier *qualifier; 1406 NSDictionary *ldifRecord; 1407 1408 ldifRecord = nil; 1409 1410 if ([uid length] > 0) 1411 { 1412 qualifier = [self _qualifierForUIDFilter: uid]; 1413 ldapEntry = [self _lookupLDAPEntry: qualifier]; 1414 if (ldapEntry) 1415 ldifRecord = [self _convertLDAPEntryToContact: ldapEntry]; 1416 } 1417 1418 return ldifRecord; 1419} 1420 1421- (NSString *) lookupLoginByDN: (NSString *) theDN 1422{ 1423 NGLdapConnection *ldapConnection; 1424 NGLdapEntry *entry; 1425 EOQualifier *qualifier; 1426 NSString *login; 1427 1428 login = nil; 1429 qualifier = nil; 1430 1431 ldapConnection = [self _ldapConnection]; 1432 1433 if (_filter) 1434 qualifier = [EOQualifier qualifierWithQualifierFormat: _filter]; 1435 1436 entry = [ldapConnection entryAtDN: theDN 1437 qualifier: qualifier 1438 attributes: [NSArray arrayWithObject: _UIDField]]; 1439 if (entry) 1440 login = [[entry attributeWithName: _UIDField] stringValueAtIndex: 0]; 1441 1442 return login; 1443} 1444 1445- (NSDictionary *) lookupContactEntryByDN: (NSString *) theDN 1446{ 1447 NGLdapConnection *ldapConnection; 1448 NGLdapEntry *ldapEntry; 1449 EOQualifier *qualifier; 1450 NSDictionary *ldifRecord; 1451 1452 ldifRecord = nil; 1453 qualifier = nil; 1454 1455 ldapConnection = [self _ldapConnection]; 1456 1457 if (_filter) 1458 qualifier = [EOQualifier qualifierWithQualifierFormat: _filter]; 1459 1460 ldapEntry = [ldapConnection entryAtDN: theDN 1461 qualifier: qualifier 1462 attributes: [NSArray arrayWithObject: @"*"]]; 1463 if (ldapEntry) 1464 ldifRecord = [self _convertLDAPEntryToContact: ldapEntry]; 1465 1466 return ldifRecord; 1467} 1468 1469- (NSString *) lookupDNByLogin: (NSString *) theLogin 1470{ 1471 return [[SOGoCache sharedCache] distinguishedNameForLogin: theLogin]; 1472} 1473 1474- (NGLdapEntry *) _lookupGroupEntryByAttributes: (NSArray *) theAttributes 1475 andValue: (NSString *) theValue 1476{ 1477 EOQualifier *qualifier; 1478 NGLdapEntry *ldapEntry; 1479 NSString *s; 1480 1481 if ([theValue length] > 0 && [theAttributes count] > 0) 1482 { 1483 if ([theAttributes count] == 1) 1484 { 1485 s = [NSString stringWithFormat: @"(%@='%@')", 1486 [theAttributes lastObject], SafeLDAPCriteria(theValue)]; 1487 1488 } 1489 else 1490 { 1491 NSString *fieldFormat; 1492 1493 fieldFormat = [NSString stringWithFormat: @"(%%@='%@')", SafeLDAPCriteria(theValue)]; 1494 s = [[theAttributes stringsWithFormat: fieldFormat] 1495 componentsJoinedByString: @" OR "]; 1496 } 1497 1498 qualifier = [EOQualifier qualifierWithQualifierFormat: s]; 1499 ldapEntry = [self _lookupLDAPEntry: qualifier]; 1500 } 1501 else 1502 ldapEntry = nil; 1503 1504 return ldapEntry; 1505} 1506 1507- (NGLdapEntry *) lookupGroupEntryByUID: (NSString *) theUID 1508 inDomain: (NSString *) theDomain 1509{ 1510 return [self _lookupGroupEntryByAttributes: [NSArray arrayWithObject: _UIDField] 1511 andValue: theUID]; 1512} 1513 1514- (NGLdapEntry *) lookupGroupEntryByEmail: (NSString *) theEmail 1515 inDomain: (NSString *) theDomain 1516{ 1517 return [self _lookupGroupEntryByAttributes: _mailFields 1518 andValue: theEmail]; 1519} 1520 1521- (void) setSourceID: (NSString *) newSourceID 1522{ 1523 ASSIGN(_sourceID, newSourceID); 1524} 1525 1526- (NSString *) sourceID 1527{ 1528 return _sourceID; 1529} 1530 1531- (void) setDisplayName: (NSString *) newDisplayName 1532{ 1533 ASSIGN(_displayName, newDisplayName); 1534} 1535 1536- (NSString *) displayName 1537{ 1538 return _displayName; 1539} 1540 1541- (NSString *) baseDN 1542{ 1543 return _baseDN; 1544} 1545 1546- (NSString *) MSExchangeHostname 1547{ 1548 return _MSExchangeHostname; 1549} 1550 1551- (void) setModifiers: (NSArray *) newModifiers 1552{ 1553 ASSIGN(_modifiers, newModifiers); 1554} 1555 1556- (NSArray *) modifiers 1557{ 1558 return _modifiers; 1559} 1560 1561- (NSArray *) groupObjectClasses 1562{ 1563 return _groupObjectClasses; 1564} 1565 1566- (BOOL) groupExpansionEnabled 1567{ 1568 return _groupExpansionEnabled; 1569} 1570 1571static NSArray * 1572_convertRecordToLDAPAttributes (LDAPSourceSchema *schema, NSDictionary *ldifRecord) 1573{ 1574 /* convert resulting record to NGLdapEntry: 1575 - strip non-existing object classes 1576 - ignore fields with empty values 1577 - ignore extra fields 1578 - use correct case for LDAP attribute matching classes */ 1579 NSMutableArray *validClasses, *validFields, *attributes; 1580 NGLdapAttribute *attribute; 1581 NSArray *classes, *fields, *values; 1582 NSString *objectClass, *field, *lowerField, *value; 1583 NSUInteger count, max, valueCount, valueMax; 1584 1585 classes = [ldifRecord objectForKey: @"objectclass"]; 1586 if ([classes isKindOfClass: NSStringK]) 1587 classes = [NSArray arrayWithObject: classes]; 1588 max = [classes count]; 1589 validClasses = [NSMutableArray array]; 1590 validFields = [NSMutableArray array]; 1591 for (count = 0; count < max; count++) 1592 { 1593 objectClass = [classes objectAtIndex: count]; 1594 fields = [schema fieldsForClass: objectClass]; 1595 if ([fields count] > 0) 1596 { 1597 [validClasses addObject: objectClass]; 1598 [validFields addObjectsFromArray: fields]; 1599 } 1600 } 1601 [validFields removeDoubles]; 1602 1603 attributes = [NSMutableArray new]; 1604 max = [validFields count]; 1605 for (count = 0; count < max; count++) 1606 { 1607 attribute = nil; 1608 field = [validFields objectAtIndex: count]; 1609 lowerField = [field lowercaseString]; 1610 if (![lowerField isEqualToString: @"dn"]) 1611 { 1612 if ([lowerField isEqualToString: @"objectclass"]) 1613 values = validClasses; 1614 else 1615 { 1616 values = [ldifRecord objectForKey: lowerField]; 1617 if ([values isKindOfClass: NSStringK]) 1618 values = [NSArray arrayWithObject: values]; 1619 } 1620 valueMax = [values count]; 1621 for (valueCount = 0; valueCount < valueMax; valueCount++) 1622 { 1623 value = [values objectAtIndex: valueCount]; 1624 if ([value length] > 0) 1625 { 1626 if (!attribute) 1627 { 1628 attribute = [[NGLdapAttribute alloc] 1629 initWithAttributeName: field]; 1630 [attributes addObject: attribute]; 1631 [attribute release]; 1632 } 1633 [attribute addStringValue: value]; 1634 } 1635 } 1636 } 1637 } 1638 1639 return attributes; 1640} 1641 1642- (NSException *) addContactEntry: (NSDictionary *) theEntry 1643 withID: (NSString *) theId 1644{ 1645 NGLdapConnection *ldapConnection; 1646 NSMutableDictionary *ldifRecord; 1647 NSString *dn, *cnValue; 1648 NGLdapEntry *newEntry; 1649 NSException *result; 1650 NSArray *attributes; 1651 1652 result = nil; 1653 1654 if ([theId length] > 0) 1655 { 1656 ldapConnection = [self _ldapConnection]; 1657 ldifRecord = [theEntry mutableCopy]; 1658 [ldifRecord autorelease]; 1659 [ldifRecord setObject: theId forKey: _UIDField]; 1660 1661 /* if CN is not set, we use aId because it must exist */ 1662 if (![ldifRecord objectForKey: _CNField]) 1663 { 1664 cnValue = [ldifRecord objectForKey: @"displayname"]; 1665 if ([cnValue length] == 0) 1666 cnValue = theId; 1667 [ldifRecord setObject: theId forKey: @"cn"]; 1668 } 1669 1670 [self applyContactMappingToOutput: ldifRecord]; 1671 1672 /* since the id might have changed due to the mapping above, we 1673 reload the record ID */ 1674 theId = [ldifRecord objectForKey: _UIDField]; 1675 dn = [NSString stringWithFormat: @"%@=%@,%@", _IDField, 1676 [theId escapedForLDAPDN], _baseDN]; 1677 attributes = _convertRecordToLDAPAttributes(_schema, ldifRecord); 1678 1679 newEntry = [[NGLdapEntry alloc] initWithDN: dn 1680 attributes: attributes]; 1681 [newEntry autorelease]; 1682 [attributes release]; 1683 NS_DURING 1684 { 1685 [ldapConnection addEntry: newEntry]; 1686 result = nil; 1687 } 1688 NS_HANDLER 1689 { 1690 result = localException; 1691 [result retain]; 1692 } 1693 NS_ENDHANDLER; 1694 [result autorelease]; 1695 } 1696 else 1697 [self errorWithFormat: @"no value for id field '%@'", _IDField]; 1698 1699 return result; 1700} 1701 1702static NSArray * 1703_makeLDAPChanges (NGLdapConnection *ldapConnection, 1704 NSString *dn, NSArray *attributes) 1705{ 1706 NSMutableArray *changes, *attributeNames, *origAttributeNames; 1707 NGLdapAttribute *attribute, *origAttribute; 1708 NSDictionary *origAttributes; 1709 NGLdapEntry *origEntry; 1710 NSString *name; 1711 1712 NSUInteger count, max; 1713 1714 /* additions and modifications */ 1715 origEntry = [ldapConnection entryAtDN: dn 1716 attributes: [NSArray arrayWithObject: @"*"]]; 1717 origAttributes = [origEntry attributes]; 1718 1719 max = [attributes count]; 1720 changes = [NSMutableArray arrayWithCapacity: max]; 1721 attributeNames = [NSMutableArray arrayWithCapacity: max]; 1722 for (count = 0; count < max; count++) 1723 { 1724 attribute = [attributes objectAtIndex: count]; 1725 name = [attribute attributeName]; 1726 [attributeNames addObject: name]; 1727 origAttribute = [origAttributes objectForKey: name]; 1728 if (origAttribute) 1729 { 1730 if (![origAttribute isEqual: attribute]) 1731 [changes 1732 addObject: [NGLdapModification replaceModification: attribute]]; 1733 } 1734 else 1735 [changes addObject: [NGLdapModification addModification: attribute]]; 1736 } 1737 1738 /* deletions */ 1739 origAttributeNames = [[origAttributes allKeys] mutableCopy]; 1740 [origAttributeNames autorelease]; 1741 [origAttributeNames removeObjectsInArray: attributeNames]; 1742 max = [origAttributeNames count]; 1743 for (count = 0; count < max; count++) 1744 { 1745 name = [origAttributeNames objectAtIndex: count]; 1746 origAttribute = [origAttributes objectForKey: name]; 1747 /* the attribute must only have string values, otherwise it will anyway 1748 be missing from the new record */ 1749 // allStrings = YES; 1750 // values = [origAttribute allValues]; 1751 // valueMax = [values count]; 1752 // for (valueCount = 0; allStrings && valueCount < valueMax; valueCount++) 1753 // if (![[values objectAtIndex: valueCount] isKindOfClass: NSStringK]) 1754 // allStrings = NO; 1755 // if (allStrings) 1756 [changes 1757 addObject: [NGLdapModification deleteModification: origAttribute]]; 1758 } 1759 1760 return changes; 1761} 1762 1763- (NSException *) updateContactEntry: (NSDictionary *) theEntry 1764{ 1765 NGLdapConnection *ldapConnection; 1766 NSMutableDictionary *ldifRecord; 1767 NSArray *attributes, *changes; 1768 NSException *result; 1769 NSString *dn; 1770 1771 dn = [theEntry objectForKey: @"dn"]; 1772 result = nil; 1773 1774 if ([dn length] > 0) 1775 { 1776 ldapConnection = [self _ldapConnection]; 1777 ldifRecord = [theEntry mutableCopy]; 1778 [ldifRecord autorelease]; 1779 [self applyContactMappingToOutput: ldifRecord]; 1780 attributes = _convertRecordToLDAPAttributes(_schema, ldifRecord); 1781 1782 changes = _makeLDAPChanges (ldapConnection, dn, attributes); 1783 1784 NS_DURING 1785 { 1786 [ldapConnection modifyEntryWithDN: dn 1787 changes: changes]; 1788 result = nil; 1789 } 1790 NS_HANDLER 1791 { 1792 result = localException; 1793 [result retain]; 1794 } 1795 NS_ENDHANDLER; 1796 [result autorelease]; 1797 } 1798 else 1799 [self errorWithFormat: @"expected dn for modified record"]; 1800 1801 return result; 1802} 1803 1804- (NSException *) removeContactEntryWithID: (NSString *) theId 1805{ 1806 NGLdapConnection *ldapConnection; 1807 NSException *result; 1808 NSString *dn; 1809 1810 ldapConnection = [self _ldapConnection]; 1811 dn = [NSString stringWithFormat: @"%@=%@,%@", _IDField, 1812 [theId escapedForLDAPDN], _baseDN]; 1813 NS_DURING 1814 { 1815 [ldapConnection removeEntryWithDN: dn]; 1816 result = nil; 1817 } 1818 NS_HANDLER 1819 { 1820 result = localException; 1821 [result retain]; 1822 } 1823 NS_ENDHANDLER; 1824 1825 [result autorelease]; 1826 1827 return result; 1828} 1829 1830/* user addressbooks */ 1831- (BOOL) hasUserAddressBooks 1832{ 1833 return ([_abOU length] > 0); 1834} 1835 1836- (NSArray *) addressBookSourcesForUser: (NSString *) theUser 1837{ 1838 NGLdapConnection *ldapConnection; 1839 NSMutableDictionary *entryRecord; 1840 NSArray *attributes, *modifier; 1841 NSMutableArray *sources; 1842 NSDictionary *sourceRec; 1843 NSEnumerator *entries; 1844 NGLdapEntry *entry; 1845 NSString *abBaseDN; 1846 LDAPSource *ab; 1847 1848 if ([self hasUserAddressBooks]) 1849 { 1850 /* list subentries */ 1851 sources = [NSMutableArray array]; 1852 1853 ldapConnection = [self _ldapConnection]; 1854 abBaseDN = [NSString stringWithFormat: @"ou=%@,%@=%@,%@", 1855 [_abOU escapedForLDAPDN], _IDField, 1856 [theUser escapedForLDAPDN], _baseDN]; 1857 1858 /* test ou=addressbooks entry */ 1859 attributes = [NSArray arrayWithObject: @"*"]; 1860 entries = [ldapConnection baseSearchAtBaseDN: abBaseDN 1861 qualifier: nil 1862 attributes: attributes]; 1863 entry = [entries nextObject]; 1864 if (entry) 1865 { 1866 attributes = [NSArray arrayWithObjects: @"ou", @"description", nil]; 1867 entries = [ldapConnection flatSearchAtBaseDN: abBaseDN 1868 qualifier: nil 1869 attributes: attributes]; 1870 modifier = [NSArray arrayWithObject: theUser]; 1871 while ((entry = [entries nextObject])) 1872 { 1873 sourceRec = [entry asDictionary]; 1874 ab = [LDAPSource new]; 1875 [ab setSourceID: [sourceRec objectForKey: @"ou"]]; 1876 [ab setDisplayName: [sourceRec objectForKey: @"description"]]; 1877 [ab setBindDN: _bindDN 1878 password: _password 1879 hostname: _hostname 1880 port: [NSString stringWithFormat: @"%d", _port] 1881 encryption: _encryption 1882 bindAsCurrentUser: [NSString stringWithFormat: @"%d", NO]]; 1883 [ab setBaseDN: [entry dn] 1884 IDField: @"cn" 1885 CNField: @"displayName" 1886 UIDField: @"cn" 1887 mailFields: nil 1888 searchFields: nil 1889 groupObjectClasses: nil 1890 IMAPHostField: nil 1891 IMAPLoginField: nil 1892 SieveHostField: nil 1893 bindFields: nil 1894 lookupFields: nil 1895 kindField: nil 1896 andMultipleBookingsField: nil]; 1897 [ab setListRequiresDot: NO]; 1898 [ab setModifiers: modifier]; 1899 [sources addObject: ab]; 1900 [ab release]; 1901 } 1902 } 1903 else 1904 { 1905 entryRecord = [NSMutableDictionary dictionary]; 1906 [entryRecord setObject: @"organizationalUnit" forKey: @"objectclass"]; 1907 [entryRecord setObject: @"addressbooks" forKey: @"ou"]; 1908 attributes = _convertRecordToLDAPAttributes(_schema, entryRecord); 1909 entry = [[NGLdapEntry alloc] initWithDN: abBaseDN 1910 attributes: attributes]; 1911 [entry autorelease]; 1912 [attributes release]; 1913 NS_DURING 1914 { 1915 [ldapConnection addEntry: entry]; 1916 } 1917 NS_HANDLER 1918 { 1919 [self errorWithFormat: @"failed to create ou=addressbooks" 1920 @" entry for user"]; 1921 } 1922 NS_ENDHANDLER; 1923 } 1924 } 1925 else 1926 sources = nil; 1927 1928 return sources; 1929} 1930 1931- (NSException *) addAddressBookSource: (NSString *) newId 1932 withDisplayName: (NSString *) newDisplayName 1933 forUser: (NSString *) user 1934{ 1935 NSException *result; 1936 NSString *abDN; 1937 NGLdapConnection *ldapConnection; 1938 NSArray *attributes; 1939 NGLdapEntry *entry; 1940 NSMutableDictionary *entryRecord; 1941 1942 if ([self hasUserAddressBooks]) 1943 { 1944 abDN = [NSString stringWithFormat: @"ou=%@,ou=%@,%@=%@,%@", 1945 [newId escapedForLDAPDN], [_abOU escapedForLDAPDN], 1946 _IDField, [user escapedForLDAPDN], _baseDN]; 1947 entryRecord = [NSMutableDictionary dictionary]; 1948 [entryRecord setObject: @"organizationalUnit" forKey: @"objectclass"]; 1949 [entryRecord setObject: newId forKey: @"ou"]; 1950 if ([newDisplayName length] > 0) 1951 [entryRecord setObject: newDisplayName forKey: @"description"]; 1952 ldapConnection = [self _ldapConnection]; 1953 attributes = _convertRecordToLDAPAttributes(_schema, entryRecord); 1954 entry = [[NGLdapEntry alloc] initWithDN: abDN 1955 attributes: attributes]; 1956 [entry autorelease]; 1957 [attributes release]; 1958 NS_DURING 1959 { 1960 [ldapConnection addEntry: entry]; 1961 result = nil; 1962 } 1963 NS_HANDLER 1964 { 1965 [self errorWithFormat: @"failed to create addressbook entry"]; 1966 result = localException; 1967 [result retain]; 1968 } 1969 NS_ENDHANDLER; 1970 [result autorelease]; 1971 } 1972 else 1973 result = [NSException exceptionWithName: @"LDAPSourceIOException" 1974 reason: @"user addressbooks" 1975 @" are not supported" 1976 userInfo: nil]; 1977 1978 return result; 1979} 1980 1981- (NSException *) renameAddressBookSource: (NSString *) newId 1982 withDisplayName: (NSString *) newDisplayName 1983 forUser: (NSString *) user 1984{ 1985 NGLdapConnection *ldapConnection; 1986 NSMutableDictionary *entryRecord; 1987 NSArray *attributes, *changes; 1988 NSException *result; 1989 NSString *abDN; 1990 1991 if ([self hasUserAddressBooks]) 1992 { 1993 abDN = [NSString stringWithFormat: @"ou=%@,ou=%@,%@=%@,%@", 1994 [newId escapedForLDAPDN], [_abOU escapedForLDAPDN], 1995 _IDField, [user escapedForLDAPDN], _baseDN]; 1996 entryRecord = [NSMutableDictionary dictionary]; 1997 [entryRecord setObject: @"organizationalUnit" forKey: @"objectclass"]; 1998 [entryRecord setObject: newId forKey: @"ou"]; 1999 if ([newDisplayName length] > 0) 2000 [entryRecord setObject: newDisplayName forKey: @"description"]; 2001 ldapConnection = [self _ldapConnection]; 2002 attributes = _convertRecordToLDAPAttributes(_schema, entryRecord); 2003 changes = _makeLDAPChanges (ldapConnection, abDN, attributes); 2004 [attributes release]; 2005 NS_DURING 2006 { 2007 [ldapConnection modifyEntryWithDN: abDN 2008 changes: changes]; 2009 result = nil; 2010 } 2011 NS_HANDLER 2012 { 2013 [self errorWithFormat: @"failed to rename addressbook entry"]; 2014 result = localException; 2015 [result retain]; 2016 } 2017 NS_ENDHANDLER; 2018 [result autorelease]; 2019 } 2020 else 2021 result = [NSException exceptionWithName: @"LDAPSourceIOException" 2022 reason: @"user addressbooks" 2023 @" are not supported" 2024 userInfo: nil]; 2025 2026 return result; 2027} 2028 2029- (NSException *) removeAddressBookSource: (NSString *) newId 2030 forUser: (NSString *) user 2031{ 2032 NGLdapConnection *ldapConnection; 2033 NSEnumerator *entries; 2034 NSException *result; 2035 NGLdapEntry *entry; 2036 NSString *abDN; 2037 2038 if ([self hasUserAddressBooks]) 2039 { 2040 abDN = [NSString stringWithFormat: @"ou=%@,ou=%@,%@=%@,%@", 2041 [newId escapedForLDAPDN], [_abOU escapedForLDAPDN], 2042 _IDField, [user escapedForLDAPDN], _baseDN]; 2043 ldapConnection = [self _ldapConnection]; 2044 NS_DURING 2045 { 2046 /* we must remove the ab sub=entries prior to the ab entry */ 2047 entries = [ldapConnection flatSearchAtBaseDN: abDN 2048 qualifier: nil 2049 attributes: nil]; 2050 while ((entry = [entries nextObject])) 2051 [ldapConnection removeEntryWithDN: [entry dn]]; 2052 [ldapConnection removeEntryWithDN: abDN]; 2053 result = nil; 2054 } 2055 NS_HANDLER 2056 { 2057 [self errorWithFormat: @"failed to remove addressbook entry"]; 2058 result = localException; 2059 [result retain]; 2060 } 2061 NS_ENDHANDLER; 2062 [result autorelease]; 2063 } 2064 else 2065 result = [NSException exceptionWithName: @"LDAPSourceIOException" 2066 reason: @"user addressbooks" 2067 @" are not supported" 2068 userInfo: nil]; 2069 2070 return result; 2071} 2072 2073- (void) updateBaseDNFromLogin: (NSString *) theLogin 2074{ 2075 NSMutableString *s; 2076 NSRange r; 2077 2078 r = [theLogin rangeOfString: @"@"]; 2079 if (r.location != NSNotFound && 2080 [_pristineBaseDN rangeOfString: @"%d"].location != NSNotFound) 2081 { 2082 s = [NSMutableString stringWithString: _pristineBaseDN]; 2083 [s replaceOccurrencesOfString: @"%d" withString: [theLogin substringFromIndex: r.location+1] options: 0 range: NSMakeRange(0, [s length])]; 2084 ASSIGN(_baseDN, s); 2085 } 2086} 2087 2088#define CHECK_CLASS(o) ({ \ 2089 if ([o isKindOfClass: [NSString class]]) \ 2090 o = [NSArray arrayWithObject: o]; \ 2091}) 2092 2093- (NSArray *) membersForGroupWithUID: (NSString *) uid 2094{ 2095 NSMutableArray *dns, *uids; 2096 NSString *dn, *login; 2097 SOGoUserManager *um; 2098 NSDictionary *d, *contactInfos; 2099 SOGoUser *user; 2100 NSArray *o, *subusers, *logins; 2101 NSAutoreleasePool *pool; 2102 int i, c; 2103 NGLdapEntry *entry; 2104 NSMutableArray *members = nil; 2105 2106 if ([uid hasPrefix: @"@"]) 2107 uid = [uid substringFromIndex: 1]; 2108 2109 entry = [self lookupGroupEntryByUID: uid inDomain: nil]; 2110 2111 if (entry) 2112 { 2113 members = [NSMutableArray new]; 2114 uids = [NSMutableArray array]; 2115 dns = [NSMutableArray array]; 2116 2117 // We check if it's a static group 2118 // Fetch "members" - we get DNs 2119 d = [entry asDictionary]; 2120 o = [d objectForKey: @"member"]; 2121 CHECK_CLASS(o); 2122 if (o) [dns addObjectsFromArray: o]; 2123 2124 // Fetch "uniqueMembers" - we get DNs 2125 o = [d objectForKey: @"uniquemember"]; 2126 CHECK_CLASS(o); 2127 if (o) [dns addObjectsFromArray: o]; 2128 2129 // Fetch "memberUid" - we get UID (like login names) 2130 o = [d objectForKey: @"memberuid"]; 2131 CHECK_CLASS(o); 2132 if (o) [uids addObjectsFromArray: o]; 2133 2134 c = [dns count] + [uids count]; 2135 2136 // We deal with a static group, let's add the members 2137 if (c) 2138 { 2139 um = [SOGoUserManager sharedUserManager]; 2140 2141 // We add members for whom we have their associated DN 2142 for (i = 0; i < [dns count]; i++) 2143 { 2144 pool = [NSAutoreleasePool new]; 2145 dn = [dns objectAtIndex: i]; 2146 login = [um getLoginForDN: [dn lowercaseString]]; 2147 user = [SOGoUser userWithLogin: login roles: nil]; 2148 if (user) 2149 { 2150 contactInfos = [self lookupContactEntryWithUIDorEmail: login inDomain: nil]; 2151 if ([contactInfos objectForKey: @"isGroup"]) 2152 { 2153 subusers = [self membersForGroupWithUID: login]; 2154 [members addObjectsFromArray: subusers]; 2155 } 2156 else 2157 { 2158 [members addObject: user]; 2159 } 2160 } 2161 [pool release]; 2162 } 2163 2164 // We add members for whom we have their associated login name 2165 for (i = 0; i < [uids count]; i++) 2166 { 2167 pool = [NSAutoreleasePool new]; 2168 login = [uids objectAtIndex: i]; 2169 user = [SOGoUser userWithLogin: login roles: nil]; 2170 if (user) 2171 { 2172 contactInfos = [self lookupContactEntryWithUIDorEmail: login inDomain: nil]; 2173 if ([contactInfos objectForKey: @"isGroup"]) 2174 { 2175 subusers = [self membersForGroupWithUID: login]; 2176 [members addObjectsFromArray: subusers]; 2177 } 2178 else 2179 { 2180 [members addObject: user]; 2181 } 2182 } 2183 [pool release]; 2184 } 2185 2186 // We are done fetching members, let's cache the members of the group 2187 // (ie., their UIDs) in memcached to speed up -groupWithUIDHasMemberWithUID. 2188 logins = [members resultsOfSelector: @selector (loginInDomain)]; 2189 [[SOGoCache sharedCache] setValue: [logins componentsJoinedByString: @","] 2190 forKey: [NSString stringWithFormat: @"%@+%@", uid, _domain]]; 2191 } 2192 else 2193 { 2194 // We deal with a dynamic group, let's search all users for whom 2195 // memberOf is equal to our group's DN. 2196 // We also need to look for labelelURI? 2197 } 2198 } 2199 2200 return members; 2201} 2202 2203// 2204// 2205// 2206- (BOOL) groupWithUIDHasMemberWithUID: (NSString *) uid 2207 memberUid: (NSString *) memberUid 2208{ 2209 2210 BOOL rc; 2211 NSString *key, *value;; 2212 NSArray *a; 2213 2214 rc = NO; 2215 2216 if ([uid hasPrefix: @"@"]) 2217 uid = [uid substringFromIndex: 1]; 2218 2219 key = [NSString stringWithFormat: @"%@+%@", uid, _domain]; 2220 value = [[SOGoCache sharedCache] valueForKey: key]; 2221 2222 // If the value isn't in memcached, that probably means -members was never called. 2223 // We call it only once here. 2224 if (!value) 2225 { 2226 [self membersForGroupWithUID: uid]; 2227 value = [[SOGoCache sharedCache] valueForKey: key]; 2228 } 2229 2230 a = [value componentsSeparatedByString: @","]; 2231 rc = [a containsObject: memberUid]; 2232 2233 return rc; 2234} 2235 2236@end 2237