From cd20a4bb9567b6941043c6da417339fc57fd4bc6 Mon Sep 17 00:00:00 2001 From: Wolfgang Sourdeau Date: Thu, 13 Aug 2009 18:34:54 +0000 Subject: [PATCH] Monotone-Parent: 1fb8ef38e6540891b9c5b8b0bdb02bde0768f4d9 Monotone-Revision: 30fad51e106d0d41d97df28ae6130ac987d1af5f Monotone-Author: wsourdeau@inverse.ca Monotone-Date: 2009-08-13T18:34:54 Monotone-Branch: ca.inverse.sogo --- ChangeLog | 12 ++ SOPE/sope-patchset-r1660.diff | 36 ++++ SoObjects/SOGo/LDAPSource.m | 3 + Tools/GNUmakefile | 5 +- Tools/README.backup | 18 ++ Tools/SOGoToolBackup.h | 36 ++++ Tools/SOGoToolBackup.m | 373 ++++++++++++++++++++++++++++++++++ 7 files changed, 482 insertions(+), 1 deletion(-) create mode 100644 Tools/README.backup create mode 100644 Tools/SOGoToolBackup.h create mode 100644 Tools/SOGoToolBackup.m diff --git a/ChangeLog b/ChangeLog index c76f406d4..7a6919a8b 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,17 @@ 2009-08-13 Wolfgang Sourdeau + * SoObjects/SOGo/LDAPSource.m: the objectClass are now all added + to the return user records. + + * Tools/NSDictionary+SOGoTool.m (-userRecordAsLDIFEntry): new + method that converts a user record as returned by LDAPSource to a + compliant LDIF entry. + + * Tools/NSDictionary+SOGoTool.[hm]: new category module. + + * Tools/SOGoToolBackup.m: new "sogo-tool" utility that backs up + user data following the file-format specified in README.backup + * Tools/SOGoToolRemoveDoubles.m: new "sogo-tool" utility method implementing the old "sogo-contacts-removedoubles" utility. diff --git a/SOPE/sope-patchset-r1660.diff b/SOPE/sope-patchset-r1660.diff index ffcd35c2b..5bca8fa0f 100644 --- a/SOPE/sope-patchset-r1660.diff +++ b/SOPE/sope-patchset-r1660.diff @@ -1,3 +1,39 @@ +Index: sope-ldap/NGLdap/NGLdapEntry.m +=================================================================== +--- sope-ldap/NGLdap/NGLdapEntry.m (revision 1660) ++++ sope-ldap/NGLdap/NGLdapEntry.m (working copy) +@@ -105,14 +105,16 @@ + - (NGLdapAttribute *)attributeWithName:(NSString *)_name { + NSEnumerator *e; + NGLdapAttribute *a; +- ++ NSString *upperName; ++ + if (_name == nil) + return nil; + ++ upperName = [_name uppercaseString]; + e = [self->attributes objectEnumerator]; + + while ((a = [e nextObject])) { +- if ([[a attributeName] isEqualToString:_name]) ++ if ([[[a attributeName] uppercaseString] isEqualToString:upperName]) + return a; + } + return nil; +Index: sope-ldap/NGLdap/ChangeLog +=================================================================== +--- sope-ldap/NGLdap/ChangeLog (revision 1660) ++++ sope-ldap/NGLdap/ChangeLog (working copy) +@@ -1,3 +1,8 @@ ++2009-08-13 Wolfgang Sourdeau ++ ++ * NGLdapEntry.m (-attributeWithName:): attribute names are now ++ accessed in a case-insensitive way. ++ + 2009-04-02 Wolfgang Sourdeau + + * NGLdapConnection.m (useSSL,startTLS): new method enabling Index: sope-mime/NGImap4/NGImap4Functions.m =================================================================== --- sope-mime/NGImap4/NGImap4Functions.m (revision 1660) diff --git a/SoObjects/SOGo/LDAPSource.m b/SoObjects/SOGo/LDAPSource.m index a44bd7a4d..9619e8743 100644 --- a/SoObjects/SOGo/LDAPSource.m +++ b/SoObjects/SOGo/LDAPSource.m @@ -660,6 +660,9 @@ static NSLock *lock; NSString *currentAttribute, *value; contactEntry = [NSMutableDictionary dictionary]; + [contactEntry setObject: [ldapEntry dn] forKey: @"dn"]; + [contactEntry setObject: [ldapEntry objectClasses] + forKey: @"objectClasses"]; attributes = [[self _searchAttributes] objectEnumerator]; while ((currentAttribute = [attributes nextObject])) { diff --git a/Tools/GNUmakefile b/Tools/GNUmakefile index e65588288..f344b8d02 100644 --- a/Tools/GNUmakefile +++ b/Tools/GNUmakefile @@ -10,8 +10,11 @@ $(SOGO_TOOL)_OBJC_FILES += \ sogo-tool.m \ \ SOGoTool.m \ + SOGoToolBackup.m \ SOGoToolCheckDoubles.m \ - SOGoToolRemoveDoubles.m + SOGoToolRemoveDoubles.m \ + \ + NSDictionary+SOGoTool.m TOOL_NAME = $(SOGO_TOOL) diff --git a/Tools/README.backup b/Tools/README.backup new file mode 100644 index 000000000..745edce69 --- /dev/null +++ b/Tools/README.backup @@ -0,0 +1,18 @@ +fileformat: +----------- +Property list: + +{ + ldif_record = "dn: xxxxx\nfield: xxxx\n..."; + preferences = ( settingsdict, defaultsdict ); + tables = { "c_path4" = { + displayname = "c_folder_name"; + records = { c_name = c_content1; + c_name2 = c_content2; + ... }; + acl = { userA = ( role1, role2, ...); + userB = ( role1, role2, ...); + ... }; + }, + ... }; +} diff --git a/Tools/SOGoToolBackup.h b/Tools/SOGoToolBackup.h new file mode 100644 index 000000000..bce547928 --- /dev/null +++ b/Tools/SOGoToolBackup.h @@ -0,0 +1,36 @@ +/* SOGoToolBackup.h - this file is part of SOGo + * + * Copyright (C) 2009 Inverse inc. + * + * Author: Wolfgang Sourdeau + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef SOGOTOOLBACKUP_H +#define SOGOTOOLBACKUP_H + +#import "SOGoTool.h" + +@interface SOGoToolBackup : SOGoTool +{ + NSString *directory; + NSArray *userIDs; +} + +@end + +#endif /* SOGOTOOLBACKUP_H */ diff --git a/Tools/SOGoToolBackup.m b/Tools/SOGoToolBackup.m new file mode 100644 index 000000000..5513dd2a4 --- /dev/null +++ b/Tools/SOGoToolBackup.m @@ -0,0 +1,373 @@ +/* SOGoToolBackup.m - this file is part of SOGo + * + * Copyright (C) 2009 Inverse inc. + * + * Author: Wolfgang Sourdeau + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#import +#import +#import +#import + +#import + +#import +#import +#import +#import + +#import +#import +#import +#import + +#import "NSDictionary+SOGoTool.h" +#import "SOGoToolBackup.h" + +@implementation SOGoToolBackup + ++ (NSString *) command +{ + return @"backup"; +} + ++ (NSString *) description +{ + return @"backup user folders"; +} + +- (id) init +{ + if ((self = [super init])) + { + directory = nil; + userIDs = nil; + } + + return self; +} + +- (void) dealloc +{ + [directory release]; + [userIDs release]; + [super dealloc]; +} + +- (void) usage +{ + fprintf (stderr, "backup folder user|ALL\n\n" + " folder the folder where backup files will be stored\n" + " user the user of whom to save the data\n"); +} + +- (BOOL) checkDirectory +{ + NSFileManager *fm; + BOOL exists, isDir, rc; + NSError *createError; + + fm = [NSFileManager defaultManager]; + exists = [fm fileExistsAtPath: directory isDirectory: &isDir]; + if (exists) + { + if (isDir) + rc = YES; + else + { + rc = NO; + NSLog (@"specified directory is a regular file"); + } + } + else + { + rc = [fm createDirectoryAtPath: directory + withIntermediateDirectories: YES + attributes: nil + error: &createError]; + if (!rc) + NSLog (@"an error occured during directory creation: %@", + createError); + } + + return rc; +} + +- (BOOL) fetchUserIDs: (NSString *) identifier +{ + BOOL rc; + LDAPUserManager *lm; + NSDictionary *infos; + NSString *userID; + NSArray *allUsers; + + lm = [LDAPUserManager sharedUserManager]; + if ([identifier isEqualToString: @"ALL"]) + { + rc = YES; + allUsers = [lm fetchUsersMatching: @"."]; + ASSIGN (userIDs, [allUsers objectsForKey: @"c_uid" + notFoundMarker: nil]); + } + else + { + infos = [lm contactInfosForUserWithUIDorEmail: identifier]; + userID = [infos objectForKey: @"c_uid"]; + if (userID) + { + rc = YES; + ASSIGN (userIDs, [NSArray arrayWithObject: userID]); + } + else + { + rc = NO; + NSLog (@"user '%@' not found", identifier); + } + } + + return rc; +} + +- (BOOL) parseArguments +{ + BOOL rc; + NSString *identifier; + + if ([arguments count] == 2) + { + ASSIGN (directory, [arguments objectAtIndex: 0]); + identifier = [arguments objectAtIndex: 1]; + rc = ([self checkDirectory] && [self fetchUserIDs: identifier]); + } + else + { + [self usage]; + rc = NO; + } + + return rc; +} + +- (NSString *) fetchFolderDisplayName: (NSString *) folder + withFM: (GCSFolderManager *) fm +{ + GCSChannelManager *cm; + EOAdaptorChannel *fc; + NSURL *folderLocation; + NSString *sql, *displayName; + NSArray *attrs; + NSDictionary *row; + + displayName = nil; + + cm = [fm channelManager]; + folderLocation = [fm folderInfoLocation]; + fc = [cm acquireOpenChannelForURL: folderLocation]; + if (fc) + { + sql + = [NSString stringWithFormat: (@"SELECT c_foldername FROM %@" + @" WHERE c_path = '%@'"), + [folderLocation gcsTableName], folder]; + [fc evaluateExpressionX: sql]; + attrs = [fc describeResults: NO]; + row = [fc fetchAttributes: attrs withZone: NULL]; + displayName = [row objectForKey: @"c_foldername"]; + [fc cancelFetch]; + [cm releaseChannel: fc]; + } + + if (!displayName) + displayName = @""; + + return displayName; +} + +- (NSDictionary *) fetchFolderACL: (GCSFolder *) gcsFolder +{ + NSMutableDictionary *acl; + NSEnumerator *aclRecords; + NSDictionary *currentRecord; + NSMutableArray *userRoles; + NSString *user, *folderPath; + + acl = [NSMutableDictionary dictionary]; + + folderPath = [gcsFolder path]; + aclRecords = [[gcsFolder fetchAclMatchingQualifier: nil] objectEnumerator]; + while ((currentRecord = [aclRecords nextObject])) + { + user = [currentRecord objectForKey: @"c_uid"]; + if ([folderPath hasSuffix: [currentRecord objectForKey: @"c_object"]]) + { + userRoles = [acl objectForKey: user]; + if (!userRoles) + { + userRoles = [NSMutableArray array]; + [acl setObject: userRoles forKey: user]; + } + [userRoles addObject: [currentRecord objectForKey: @"c_role"]]; + } + } + + return acl; +} + +- (BOOL) extractFolder: (NSString *) folder + withFM: (GCSFolderManager *) fm + intoRecord: (NSMutableDictionary *) folderRecord +{ + GCSFolder *gcsFolder; + NSArray *records; + static NSArray *fields = nil; + NSMutableDictionary *tableRecord; + + if (!fields) + { + fields = [NSArray arrayWithObjects: @"c_name", @"c_content", nil]; + [fields retain]; + } + + gcsFolder = [fm folderAtPath: folder]; + + tableRecord = [NSMutableDictionary dictionary]; + // [tableRecord setObject: + // forKey: @"displayname"]; + records = [gcsFolder fetchFields: fields + fetchSpecification: nil]; + [tableRecord setObject: records forKey: @"records"]; + [tableRecord setObject: [self fetchFolderDisplayName: folder + withFM: fm] + forKey: @"displayname"]; + [tableRecord setObject: [self fetchFolderACL: gcsFolder] + forKey: @"acl"]; + [folderRecord setObject: tableRecord forKey: folder]; + + return YES; +} + +- (BOOL) extractUserFolders: (NSString *) uid + intoRecord: (NSMutableDictionary *) userRecord +{ + GCSFolderManager *fm; + NSArray *folders; + NSMutableDictionary *tables; + int count, max; + NSString *basePath, *folder; + + fm = [GCSFolderManager defaultFolderManager]; + basePath = [NSString stringWithFormat: @"/Users/%@", uid]; + folders = [fm listSubFoldersAtPath: basePath recursive: YES]; + max = [folders count]; + tables = [NSMutableDictionary dictionaryWithCapacity: max]; + for (count = 0; count < max; count++) + { + folder = [NSString stringWithFormat: @"%@/%@", + basePath, [folders objectAtIndex: count]]; + NSLog (@"folder %d: %@", count, folder); + [self extractFolder: folder withFM: fm + intoRecord: tables]; + } + [userRecord setObject: tables forKey: @"tables"]; + + return YES; +} + +- (BOOL) extractUserLDIFRecord: (NSString *) uid + intoRecord: (NSMutableDictionary *) userRecord +{ + NSEnumerator *ldapSources; + NSString *sourceID; + LDAPSource *currentSource; + LDAPUserManager *lm; + NSDictionary *userEntry; + BOOL done; + + lm = [LDAPUserManager sharedUserManager]; + + done = NO; + ldapSources = [[lm authenticationSourceIDs] objectEnumerator]; + while (!done && (sourceID = [ldapSources nextObject])) + { + currentSource = [lm sourceWithID: sourceID]; + userEntry = [currentSource lookupContactEntry: uid]; + if (userEntry) + { + [userRecord setObject: [userEntry userRecordAsLDIFEntry] + forKey: @"ldif_record"]; + done = YES; + } + } + + return YES; +} + +- (BOOL) extractUserPreferences: (NSString *) uid + intoRecord: (NSMutableDictionary *) userRecord +{ + SOGoUser *sogoUser; + NSArray *preferences; + + sogoUser = [SOGoUser userWithLogin: uid roles: nil]; + preferences = [NSArray arrayWithObjects: + [sogoUser userDefaults], + [sogoUser userSettings], nil]; + [userRecord setObject: preferences forKey: @"preferences"]; + + return YES; +} + +- (BOOL) exportUser: (NSString *) uid +{ + NSMutableDictionary *userRecord; + NSString *exportPath; + + userRecord = [NSMutableDictionary dictionary]; + exportPath = [directory stringByAppendingPathComponent: uid]; + + return ([self extractUserFolders: uid + intoRecord: userRecord] + && [self extractUserLDIFRecord: uid + intoRecord: userRecord] + && [self extractUserPreferences: uid + intoRecord: userRecord] + && [userRecord writeToFile: exportPath + atomically: NO]); +} + +- (BOOL) proceed +{ + int count, max; + BOOL rc; + + rc = YES; + + max = [userIDs count]; + for (count = 0; rc && count < max; count++) + rc = [self exportUser: [userIDs objectAtIndex: count]]; + + return rc; +} + +- (BOOL) run +{ + return ([self parseArguments] && [self proceed]); +} + +@end