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