1/* 2 Copyright (C) 2000-2005 SKYRIX Software AG 3 4 This file is part of SOPE. 5 6 SOPE is free software; you can redistribute it and/or modify it under 7 the terms of the GNU Lesser General Public License as published by the 8 Free Software Foundation; either version 2, or (at your option) any 9 later version. 10 11 SOPE is distributed in the hope that it will be useful, but WITHOUT ANY 12 WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 14 License for more details. 15 16 You should have received a copy of the GNU Lesser General Public 17 License along with SOPE; see the file COPYING. If not, write to the 18 Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA 19 02111-1307, USA. 20*/ 21 22#include "NGLdapFileManager.h" 23#include "NGLdapConnection.h" 24#include "NGLdapEntry.h" 25#include "NGLdapAttribute.h" 26#include "NGLdapURL.h" 27#include "NSString+DN.h" 28#import <NGExtensions/NGFileFolderInfoDataSource.h> 29#include "common.h" 30 31@implementation NGLdapFileManager 32 33static NSString *LDAPObjectClassKey = @"objectclass"; 34static NSArray *objectClassAttrs = nil; 35static NSArray *fileInfoAttrs = nil; 36 37+ (void)_initCache { 38 if (objectClassAttrs == nil) { 39 objectClassAttrs = 40 [[NSArray alloc] initWithObjects:&LDAPObjectClassKey count:1]; 41 } 42 if (fileInfoAttrs == nil) { 43 fileInfoAttrs = 44 [[NSArray alloc] initWithObjects: 45 @"objectclass", 46 @"createTimestamp", 47 @"modifyTimestamp", 48 @"creatorsName", 49 @"modifiersName", 50 nil]; 51 } 52} 53 54- (id)initWithLdapConnection:(NGLdapConnection *)_con { // designated initializer 55 if (_con == nil) { 56 [self release]; 57 return nil; 58 } 59 60 [[self class] _initCache]; 61 62 if ((self = [super init])) { 63 self->connection = [_con retain]; 64 } 65 return self; 66} 67 68- (id)initWithHostName:(NSString *)_host port:(int)_port 69 bindDN:(NSString *)_login credentials:(NSString *)_pwd 70 rootDN:(NSString *)_rootDN 71{ 72 NGLdapConnection *ldap; 73 74 ldap = [[NGLdapConnection alloc] initWithHostName:_host port:_port?_port:389]; 75 if (ldap == nil) { 76 [self release]; 77 return nil; 78 } 79 ldap = [ldap autorelease]; 80 81 if (![ldap bindWithMethod:@"simple" binddn:_login credentials:_pwd]) { 82 NSLog(@"couldn't bind as DN '%@' with %@", _login, ldap); 83 [self release]; 84 return nil; 85 } 86 87 if ((self = [self initWithLdapConnection:ldap])) { 88 if (_rootDN == nil) { 89 /* check cn=config as available in OpenLDAP */ 90 NSArray *nctxs; 91 92 if ((nctxs = [self->connection namingContexts])) { 93 if ([nctxs count] > 1) 94 NSLog(@"WARNING: more than one naming context handled by server !"); 95 if ([nctxs isNotEmpty]) 96 _rootDN = [[nctxs objectAtIndex:0] lowercaseString]; 97 } 98 } 99 100 if (_rootDN) { 101 ASSIGNCOPY(self->rootDN, _rootDN); 102 ASSIGNCOPY(self->currentDN, _rootDN); 103 self->currentPath = @"/"; 104 } 105 } 106 return self; 107} 108 109- (id)initWithURLString:(NSString *)_url { 110 NGLdapURL *url; 111 112 if ((url = [NGLdapURL ldapURLWithString:_url]) == nil) { 113 /* couldn't parse URL */ 114 [self release]; 115 return nil; 116 } 117 118 return [self initWithHostName:[url hostName] port:[url port] 119 bindDN:nil credentials:nil 120 rootDN:[url baseDN]]; 121} 122- (id)initWithURL:(id)_url { 123 return (![_url isKindOfClass:[NSURL class]]) 124 ? [self initWithURLString:[_url stringValue]] 125 : [self initWithURLString:[_url absoluteString]]; 126} 127 128- (void)dealloc { 129 [self->connection release]; 130 [self->rootDN release]; 131 [self->currentDN release]; 132 [self->currentPath release]; 133 [super dealloc]; 134} 135 136/* internals */ 137 138- (NSString *)_rdnForPathComponent:(NSString *)_pathComponent { 139 return _pathComponent; 140} 141- (NSString *)_pathComponentForRDN:(NSString *)_rdn { 142 return _rdn; 143} 144 145- (NSString *)pathForDN:(NSString *)_dn { 146 NSEnumerator *dnComponents; 147 NSString *path; 148 NSString *rdn; 149 150 if (_dn == nil) return nil; 151 _dn = [_dn lowercaseString]; 152 153 if (![_dn hasSuffix:self->rootDN]) { 154 /* DN is not rooted in this hierachy */ 155 return nil; 156 } 157 158 /* cut of root */ 159 _dn = [_dn substringToIndex:([_dn length] - [self->rootDN length])]; 160 161 path = @"/"; 162 dnComponents = [[_dn dnComponents] reverseObjectEnumerator]; 163 while ((rdn = [dnComponents nextObject])) { 164 NSString *pathComponent; 165 166 pathComponent = [self _pathComponentForRDN:rdn]; 167 168 path = [path stringByAppendingPathComponent:pathComponent]; 169 } 170 return path; 171} 172 173- (NGLdapConnection *)ldapConnection { 174 return self->connection; 175} 176- (NSString *)dnForPath:(NSString *)_path { 177 NSString *dn = nil; 178 NSArray *pathComponents; 179 unsigned i, count; 180 181 if (![_path isAbsolutePath]) 182 _path = [[self currentDirectoryPath] stringByAppendingPathComponent:_path]; 183 184 if (![_path isNotEmpty]) return nil; 185 186 NSAssert1([_path isAbsolutePath], 187 @"path %@ is not an absolute path (after append to cwd) !", _path); 188 NSAssert(self->rootDN, @"missing root DN !"); 189 190 pathComponents = [_path pathComponents]; 191 for (i = 0, count = [pathComponents count]; i < count; i++) { 192 NSString *pathComponent; 193 NSString *rdn; 194 195 pathComponent = [pathComponents objectAtIndex:i]; 196 197 if ([pathComponent isEqualToString:@"."]) 198 continue; 199 if (![pathComponent isNotEmpty]) 200 continue; 201 202 if ([pathComponent isEqualToString:@"/"]) { 203 dn = self->rootDN; 204 continue; 205 } 206 207 if ([pathComponent isEqualToString:@".."]) { 208 dn = [dn stringByDeletingLastDNComponent]; 209 continue; 210 } 211 212 rdn = [self _rdnForPathComponent:pathComponent]; 213 dn = [dn stringByAppendingDNComponent:rdn]; 214 } 215 216 return [dn lowercaseString]; 217} 218 219/* accessors */ 220 221- (BOOL)changeCurrentDirectoryPath:(NSString *)_path { 222 NSString *dn; 223 NSString *path; 224 225 if (![_path isNotEmpty]) 226 return NO; 227 228 if ((dn = [self dnForPath:_path]) == nil) 229 return NO; 230 231 if ((path = [self pathForDN:dn]) == nil) 232 return NO; 233 234 ASSIGNCOPY(self->currentDN, dn); 235 ASSIGNCOPY(self->currentPath, path); 236 return YES; 237} 238 239- (NSString *)currentDirectoryPath { 240 return self->currentPath; 241} 242 243 244- (NSArray *)directoryContentsAtPath:(NSString *)_path { 245 NSString *dn; 246 NSEnumerator *e; 247 NSMutableArray *rdns; 248 NGLdapEntry *entry; 249 250 if ((dn = [self dnForPath:_path]) == nil) 251 return nil; 252 253 e = [self->connection flatSearchAtBaseDN:dn 254 qualifier:nil 255 attributes:objectClassAttrs]; 256 if (e == nil) 257 return nil; 258 259 rdns = nil; 260 while ((entry = [e nextObject])) { 261 if (rdns == nil) 262 rdns = [NSMutableArray arrayWithCapacity:128]; 263 264 [rdns addObject:[entry rdn]]; 265 } 266 267 return [[rdns copy] autorelease]; 268} 269 270- (NSArray *)subpathsAtPath:(NSString *)_path { 271 NSString *dn; 272 NSEnumerator *e; 273 NSMutableArray *paths; 274 NGLdapEntry *entry; 275 276 if ((dn = [self dnForPath:_path]) == nil) 277 return nil; 278 279 _path = [self pathForDN:dn]; 280 281 e = [self->connection deepSearchAtBaseDN:dn 282 qualifier:nil 283 attributes:objectClassAttrs]; 284 if (e == nil) 285 return nil; 286 287 paths = nil; 288 while ((entry = [e nextObject])) { 289 NSString *path; 290 NSString *sdn; 291 292 sdn = [entry dn]; 293 294 if ((path = [self pathForDN:sdn]) == nil) { 295 NSLog(@"got no path for dn '%@' ..", sdn); 296 continue; 297 } 298 299 if ([path hasPrefix:_path]) 300 path = [path substringFromIndex:[_path length]]; 301 302 if (paths == nil) 303 paths = [NSMutableArray arrayWithCapacity:128]; 304 305 [paths addObject:path]; 306 } 307 308 return [[paths copy] autorelease]; 309} 310 311- (NSDictionary *)fileAttributesAtPath:(NSString *)_path traverseLink:(BOOL)_fl { 312 NSString *dn; 313 NGLdapEntry *entry; 314 NGLdapAttribute *attr; 315 id keys[10]; 316 id vals[10]; 317 short count; 318 319 if ((dn = [self dnForPath:_path]) == nil) 320 return nil; 321 322 entry = [self->connection entryAtDN:dn attributes:fileInfoAttrs]; 323 if (entry == nil) 324 return nil; 325 326 count = 0; 327 if ((attr = [entry attributeWithName:@"modifytimestamp"])) { 328 keys[count] = NSFileModificationDate; 329 vals[count] = [[attr stringValueAtIndex:0] ldapTimestamp]; 330 count++; 331 } 332 if ((attr = [entry attributeWithName:@"modifiersname"])) { 333 keys[count] = NSFileOwnerAccountName; 334 vals[count] = [[attr allStringValues] componentsJoinedByString:@","]; 335 count++; 336 } 337 if ((attr = [entry attributeWithName:@"creatorsname"])) { 338 keys[count] = @"NSFileCreatorAccountName"; 339 vals[count] = [[attr allStringValues] componentsJoinedByString:@","]; 340 count++; 341 } 342 if ((attr = [entry attributeWithName:@"createtimestamp"])) { 343 keys[count] = @"NSFileCreationDate"; 344 vals[count] = [[attr stringValueAtIndex:0] ldapTimestamp]; 345 count++; 346 } 347 if ((attr = [entry attributeWithName:@"objectclass"])) { 348 keys[count] = @"LDAPObjectClasses"; 349 vals[count] = [attr allStringValues]; 350 count++; 351 } 352 353 keys[count] = @"NSFileIdentifier"; 354 if ((vals[count] = [entry dn])) 355 count++; 356 357 keys[count] = NSFilePath; 358 if ((vals[count] = _path)) 359 count++; 360 361 keys[count] = NSFileName; 362 if ((vals[count] = [self _pathComponentForRDN:[dn lastDNComponent]])) 363 count++; 364 365 return [NSDictionary dictionaryWithObjects:vals forKeys:keys count:count]; 366} 367 368/* determine access */ 369 370- (BOOL)fileExistsAtPath:(NSString *)_path { 371 return [self fileExistsAtPath:_path isDirectory:NULL]; 372} 373- (BOOL)fileExistsAtPath:(NSString *)_path isDirectory:(BOOL *)_isDir { 374 NSString *dn; 375 NGLdapEntry *entry; 376 377 if ((dn = [self dnForPath:_path]) == nil) 378 return NO; 379 380 entry = [self->connection entryAtDN:dn attributes:objectClassAttrs]; 381 if (entry == nil) 382 return NO; 383 384 if (_isDir) { 385 NSEnumerator *e; 386 387 /* is-dir based on child-availablitiy */ 388 e = [self->connection flatSearchAtBaseDN:dn 389 qualifier:nil 390 attributes:objectClassAttrs]; 391 *_isDir = [e nextObject] ? YES : NO; 392 } 393 return YES; 394} 395 396- (BOOL)isReadableFileAtPath:(NSString *)_path { 397 return [self fileExistsAtPath:_path]; 398} 399- (BOOL)isWritableFileAtPath:(NSString *)_path { 400 return [self fileExistsAtPath:_path]; 401} 402- (BOOL)isExecutableFileAtPath:(NSString *)_path { 403 return NO; 404} 405- (BOOL)isDeletableFileAtPath:(NSString *)_path { 406 return [self fileExistsAtPath:_path]; 407} 408 409/* reading contents */ 410 411- (BOOL)contentsEqualAtPath:(NSString *)_path1 andPath:(NSString *)_path2 { 412 NSString *dn1, *dn2; 413 NGLdapEntry *e1, *e2; 414 415 if ((dn1 = [self dnForPath:_path1]) == nil) 416 return NO; 417 if ((dn2 = [self dnForPath:_path2]) == nil) 418 return NO; 419 420 if ([dn1 isEqualToString:dn2]) 421 /* same DN */ 422 return YES; 423 424 e1 = [self->connection entryAtDN:dn1 attributes:nil]; 425 e2 = [self->connection entryAtDN:dn2 attributes:nil]; 426 427 return [e1 isEqual:e2]; 428} 429- (NSData *)contentsAtPath:(NSString *)_path { 430 /* generate LDIF for record */ 431 NSString *dn; 432 NGLdapEntry *entry; 433 434 if ((dn = [self dnForPath:_path]) == nil) 435 return nil; 436 437 entry = [self->connection entryAtDN:dn attributes:nil]; 438 if (entry == nil) 439 return nil; 440 441 return [[entry ldif] dataUsingEncoding:NSUTF8StringEncoding]; 442} 443 444/* modifications */ 445 446- (NSDictionary *)_errDictForPath:(NSString *)_path toPath:(NSString *)_dest 447 dn:(NSString *)_dn reason:(NSString *)_reason 448{ 449 id keys[6]; 450 id values[6]; 451 short count; 452 453 count = 0; 454 455 if (_path) { 456 keys[count] = @"Path"; 457 values[count] = _path; 458 count++; 459 } 460 if (_dest) { 461 keys[count] = @"ToPath"; 462 values[count] = _dest; 463 count++; 464 } 465 if (_reason) { 466 keys[count] = @"Error"; 467 values[count] = _reason; 468 count++; 469 } 470 if (_dn) { 471 keys[count] = @"dn"; 472 values[count] = _dn; 473 count++; 474 keys[count] = @"ldap"; 475 values[count] = self->connection; 476 count++; 477 } 478 479 return [NSDictionary dictionaryWithObjects:values forKeys:keys count:count]; 480} 481 482- (BOOL)removeFileAtPath:(NSString *)_path handler:(id)_fhandler { 483 NSString *dn; 484 485 [_fhandler fileManager:(id)self willProcessPath:_path]; 486 487 if ((dn = [self dnForPath:_path]) == nil) { 488 if (_fhandler) { 489 NSDictionary *errDict; 490 491 errDict = [self _errDictForPath:_path toPath:nil dn:nil 492 reason:@"couldn't map path to LDAP dn"]; 493 494 if ([_fhandler fileManager:(id)self shouldProceedAfterError:errDict]) 495 return YES; 496 } 497 return NO; 498 } 499 500 /* should delete sub-entries first ... */ 501 502 /* delete entry */ 503 504 if (![self->connection removeEntryWithDN:dn]) { 505 if (_fhandler) { 506 NSDictionary *errDict; 507 508 errDict = [self _errDictForPath:_path toPath:nil dn:dn 509 reason:@"couldn't remove LDAP entry"]; 510 511 if ([_fhandler fileManager:(id)self shouldProceedAfterError:errDict]) 512 return YES; 513 } 514 return NO; 515 } 516 517 return YES; 518} 519 520- (BOOL)copyPath:(NSString *)_path toPath:(NSString *)_destination 521 handler:(id)_fhandler 522{ 523 NGLdapEntry *e; 524 NSString *fromDN, *toDN, *toRDN; 525 526 [_fhandler fileManager:(id)self willProcessPath:_path]; 527 528 if ((fromDN = [self dnForPath:_path]) == nil) { 529 if (_fhandler) { 530 NSDictionary *errDict; 531 532 errDict = [self _errDictForPath:_path toPath:_destination dn:nil 533 reason:@"couldn't map source path to LDAP dn"]; 534 535 if ([_fhandler fileManager:(id)self shouldProceedAfterError:errDict]) 536 return YES; 537 } 538 return NO; 539 } 540 541 /* 542 split destination. 'toDN' is the target 'directory', 'toRDN' the name of 543 the target 'file' 544 */ 545 toDN = [self dnForPath:_destination]; 546 toRDN = [toDN lastDNComponent]; 547 toDN = [toDN stringByDeletingLastDNComponent]; 548 549 if ((toDN == nil) || (toRDN == nil)) { 550 if (_fhandler) { 551 NSDictionary *errDict; 552 553 errDict = [self _errDictForPath:_path toPath:_destination dn:fromDN 554 reason:@"couldn't map destination path to LDAP dn"]; 555 556 if ([_fhandler fileManager:(id)self shouldProceedAfterError:errDict]) 557 return YES; 558 } 559 return NO; 560 } 561 562 /* process record */ 563 564 if ((e = [self->connection entryAtDN:fromDN attributes:nil]) == nil) { 565 if (_fhandler) { 566 NSDictionary *errDict; 567 568 errDict = [self _errDictForPath:_path toPath:_destination dn:fromDN 569 reason:@"couldn't load source LDAP record"]; 570 571 if ([_fhandler fileManager:(id)self shouldProceedAfterError:errDict]) 572 return YES; 573 } 574 return NO; 575 } 576 else { 577 /* create new record with the attributes of the old one */ 578 NGLdapEntry *newe; 579 NSArray *attrs; 580 581 attrs = [[e attributes] allValues]; 582 newe = [[NGLdapEntry alloc] initWithDN:toDN attributes:attrs]; 583 newe = [newe autorelease]; 584 585 /* insert record in target space */ 586 if (![self->connection addEntry:newe]) { 587 /* insert failed */ 588 589 if (_fhandler) { 590 NSDictionary *errDict; 591 592 errDict = [self _errDictForPath:_path toPath:_destination dn:toDN 593 reason:@"couldn't insert LDAP record in target dn"]; 594 595 if ([_fhandler fileManager:(id)self shouldProceedAfterError:errDict]) 596 return YES; 597 } 598 return NO; 599 } 600 } 601 602 /* should process children ? */ 603 604 return YES; 605} 606 607- (BOOL)movePath:(NSString *)_path toPath:(NSString *)_destination 608 handler:(id)_fhandler 609{ 610 /* needs to invoke a modrdn operation */ 611 [_fhandler fileManager:(id)self willProcessPath:_path]; 612 613 return NO; 614} 615 616- (BOOL)linkPath:(NSString *)_path toPath:(NSString *)_destination 617 handler:(id)_fhandler 618{ 619 /* LDAP doesn't support links .. */ 620 [_fhandler fileManager:(id)self willProcessPath:_path]; 621 622 return NO; 623} 624 625- (BOOL)createFileAtPath:(NSString *)path 626 contents:(NSData *)contents 627 attributes:(NSDictionary *)attributes 628{ 629 return NO; 630} 631 632/* description */ 633 634- (NSString *)description { 635 NSMutableString *ms; 636 637 ms = [NSMutableString stringWithCapacity:64]; 638 [ms appendFormat:@"<0x%p[%@]:", self, NSStringFromClass([self class])]; 639 640 if (self->rootDN) 641 [ms appendFormat:@" root=%@", self->rootDN]; 642 if (self->currentDN && ![self->currentDN isEqualToString:self->rootDN]) 643 [ms appendFormat:@" cwd=%@", self->currentDN]; 644 645 if (self->connection) 646 [ms appendFormat:@" ldap=%@", self->connection]; 647 648 [ms appendString:@">"]; 649 return ms; 650} 651 652@end /* NGLdapFileManager */ 653 654#include <NGLdap/NGLdapDataSource.h> 655#include <NGLdap/NGLdapGlobalID.h> 656 657@implementation NGLdapFileManager(ExtendedFileManager) 658 659/* feature check */ 660 661- (BOOL)supportsVersioningAtPath:(NSString *)_path { 662 return NO; 663} 664- (BOOL)supportsLockingAtPath:(NSString *)_path { 665 return NO; 666} 667- (BOOL)supportsFolderDataSourceAtPath:(NSString *)_path { 668 return YES; 669} 670 671/* writing */ 672 673- (BOOL)writeContents:(NSData *)_content atPath:(NSString *)_path { 674 /* should decode LDIF and store at path .. */ 675 return NO; 676} 677 678/* datasources (work on folders) */ 679 680- (EODataSource *)dataSourceAtPath:(NSString *)_path { 681 NGLdapDataSource *ds; 682 NSString *dn; 683 684 if ((dn = [self dnForPath:_path]) == nil) 685 /* couldn't get DN for specified path .. */ 686 return nil; 687 688 ds = [[NGLdapDataSource alloc] 689 initWithLdapConnection:self->connection 690 searchBase:dn]; 691 return [ds autorelease]; 692} 693- (EODataSource *)dataSource { 694 return [self dataSourceAtPath:[self currentDirectoryPath]]; 695} 696 697/* global-IDs */ 698 699- (EOGlobalID *)globalIDForPath:(NSString *)_path { 700 NSString *dn; 701 NGLdapGlobalID *gid; 702 703 if ((dn = [self dnForPath:_path]) == nil) 704 return nil; 705 706 gid = [[NGLdapGlobalID alloc] 707 initWithHost:[self->connection hostName] 708 port:[self->connection port] 709 dn:dn]; 710 return [gid autorelease]; 711} 712 713- (NSString *)pathForGlobalID:(EOGlobalID *)_gid { 714 NGLdapGlobalID *gid; 715 716 if (![_gid isKindOfClass:[NGLdapGlobalID class]]) 717 return nil; 718 719 gid = (NGLdapGlobalID *)_gid; 720 721 /* check whether host&port is correct */ 722 if (![[self->connection hostName] isEqualToString:[gid host]]) 723 return nil; 724 if (![self->connection port] == [gid port]) 725 return nil; 726 727 return [self pathForDN:[gid dn]]; 728} 729 730/* trash */ 731 732- (BOOL)supportsTrashFolderAtPath:(NSString *)_path { 733 return NO; 734} 735- (NSString *)trashFolderForPath:(NSString *)_path { 736 return nil; 737} 738 739@end /* NGLdapFileManager(ExtendedFileManager) */ 740