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