/* SOGoToolManageEAS.m - this file is part of SOGo * * Copyright (C) 2014-2017 Inverse inc. * * 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 "SOGoTool.h" typedef enum { ManageEASUnknown = -1, ManageEASListDevices = 0, ManageEASListFolders = 2, ManageEASResetDevice = 3, ManageEASResetFolder = 4, ManageEASVCard = 5, ManageEASVEvent = 6, } SOGoManageEASCommand; @interface SOGoToolManageEAS : SOGoTool @end @implementation SOGoToolManageEAS NSURL *folderTableURL; + (NSString *) command { return @"manage-eas"; } + (NSString *) description { return @"manage EAS folders"; } - (void) _setOrUnsetSyncRequest: (BOOL) set collections: (NSArray *) collections context: (WOContext *) theContext { SOGoCacheGCSObject *o; NSNumber *processIdentifier; NSString *key; NSArray *a; int i; processIdentifier = [NSNumber numberWithInt: [[NSProcessInfo processInfo] processIdentifier]]; o = [SOGoCacheGCSObject objectWithName: [[[collections objectAtIndex: 0] componentsSeparatedByString: @"+"] objectAtIndex: 0] inContainer: nil useCache: NO]; [o setObjectType: ActiveSyncGlobalCacheObject]; [o setTableUrl: folderTableURL]; [o setContext: theContext]; [o reloadIfNeeded]; if (set) { [[o properties] setObject: [NSNumber numberWithUnsignedInt: [[NSCalendarDate date] timeIntervalSince1970]] forKey: @"SyncRequest"]; for (i = 0; i < [collections count]; i++) { a = [[collections objectAtIndex: i] componentsSeparatedByString: @"+"]; key = [NSString stringWithFormat: @"SyncRequest+%@", [a objectAtIndex: 1]]; [[o properties] setObject: processIdentifier forKey: key]; } } else { [[o properties] removeObjectForKey: @"SyncRequest"]; for (i = 0; i < [collections count]; i++) { a = [[collections objectAtIndex: i] componentsSeparatedByString: @"+"]; key = [NSString stringWithFormat: @"SyncRequest+%@", [a objectAtIndex: 1]]; [[o properties] removeObjectForKey: key]; } } [o save]; } - (void) usage { fprintf (stderr, "manage-eas listdevices|listfolders|resetdevice|resetfolder|mergevcard|mergevevent user \n\n" " listdevices list the devices belonging to user\n" " listfolders list all folders of deviceId for user\n" " resetdevice force deviceId of user to resync everything\n" " resetfolder force folderId of user to resync everything\n" " mergevcard merge/un-merge all addressbooks into one for deviceId of user\n" " mergevevent merge/un-merge all calendars into one for deviceId of user\n" " user the user of whom to reset the whole device or a single folder\n" " Examples:\n" " sogo-tool manage-eas listdevices janedoe\n" " sogo-tool manage-eas listfolders janedoe androidc316986417\n" " sogo-tool manage-eas resetdevice janedoe androidc316986417\n" " sogo-tool manage-eas resetfolder janedow androidc316986417+folderlala-dada-sasa_7a13_1a2386e0_e\n" " sogo-tool manage-eas mergevcard janedow androidc316986417 YES\n" " sogo-tool manage-eas mergevevent janedow androidc316986417 YES\n") ; } - (SOGoManageEASCommand) _cmdFromString: (NSString *) theString { if ([theString length] > 2) { if ([theString caseInsensitiveCompare: @"listdevices"] == NSOrderedSame) return ManageEASListDevices; else if ([theString caseInsensitiveCompare: @"listfolders"] == NSOrderedSame) return ManageEASListFolders; else if ([theString caseInsensitiveCompare: @"resetdevice"] == NSOrderedSame) return ManageEASResetDevice; else if ([theString caseInsensitiveCompare: @"resetfolder"] == NSOrderedSame) return ManageEASResetFolder; else if ([theString caseInsensitiveCompare: @"mergevcard"] == NSOrderedSame) return ManageEASVCard; else if ([theString caseInsensitiveCompare: @"mergevevent"] == NSOrderedSame) return ManageEASVEvent; } return ManageEASUnknown; } - (BOOL) run { NSString *urlString, *deviceId, *folderId, *userId; NSMutableString *ocFSTableName; SOGoCacheGCSObject *oc, *foc; NSMutableArray *parts; NSArray *entries; id cacheEntry; WOContext *localContext; SOGoManageEASCommand cmd; int i, max; BOOL rc; max = [sanitizedArguments count]; rc = NO; if (max > 1) { SOGoUser *user; cmd = [self _cmdFromString: [sanitizedArguments objectAtIndex: 0]]; userId = [sanitizedArguments objectAtIndex: 1]; user = [SOGoUser userWithLogin: userId]; if (![user loginInDomain]) return NO; localContext = [WOContext context]; [localContext setActiveUser: user]; urlString = [[user domainDefaults] folderInfoURL]; parts = [[urlString componentsSeparatedByString: @"/"] mutableCopy]; [parts autorelease]; if ([parts count] == 5) { /* If "OCSFolderInfoURL" is properly configured, we must have 5 parts in this url. We strip the '-' character in case we have this in the domain part - like foo@bar-zot.com */ ocFSTableName = [NSMutableString stringWithFormat: @"sogo_cache_folder_%@", [[user login] asCSSIdentifier]]; [ocFSTableName replaceOccurrencesOfString: @"-" withString: @"_" options: 0 range: NSMakeRange(0, [ocFSTableName length])]; [parts replaceObjectAtIndex: 4 withObject: ocFSTableName]; folderTableURL = [NSURL URLWithString: [parts componentsJoinedByString: @"/"]]; [folderTableURL retain]; } switch (cmd) { case ManageEASListDevices: oc = [SOGoCacheGCSObject objectWithName: @"0" inContainer: nil]; [oc setObjectType: ActiveSyncGlobalCacheObject]; [oc setContext: localContext]; [oc setTableUrl: folderTableURL]; entries = [oc cacheEntriesForDeviceId: nil newerThanVersion: -1]; for (i = 0; i < [entries count]; i++) { cacheEntry = [entries objectAtIndex: i]; fprintf(stdout,"%s\n", [[cacheEntry substringFromIndex: 1] UTF8String]); } rc = YES; break; case ManageEASListFolders: if (max > 2) { /* value specified on command line */ deviceId = [sanitizedArguments objectAtIndex: 2]; oc = [SOGoCacheGCSObject objectWithName: @"0" inContainer: nil]; [oc setObjectType: ActiveSyncFolderCacheObject]; [oc setContext: localContext]; [oc setTableUrl: folderTableURL]; entries = [oc cacheEntriesForDeviceId: deviceId newerThanVersion: -1]; for (i = 0; i < [entries count]; i++) { cacheEntry = [entries objectAtIndex: i]; fprintf(stdout,"Folder Key: %s\n", [[cacheEntry substringFromIndex: 1] UTF8String]); foc = [SOGoCacheGCSObject objectWithName: [cacheEntry substringFromIndex: 1] inContainer: nil]; [foc setObjectType: ActiveSyncFolderCacheObject]; [foc setContext: localContext]; [foc setTableUrl: folderTableURL]; [foc reloadIfNeeded]; fprintf(stdout, " Folder Name: %s\n\n", [[[foc properties] objectForKey: @"displayName"] UTF8String]); if ([[foc properties] objectForKey: @"MergedFolder"]) fprintf(stdout, " MergedFolder = YES\n\n"); if (verbose) fprintf(stdout, " metadata Name: %s\n\n", [[[foc properties] description] UTF8String]); } rc = YES; } else { fprintf(stderr, "\nERROR: deviceId not specified\n\n"); } break; case ManageEASResetDevice: if (max > 2) { /* value specified on command line */ deviceId = [sanitizedArguments objectAtIndex: 2]; oc = [SOGoCacheGCSObject objectWithName: deviceId inContainer: nil]; [oc setObjectType: ActiveSyncGlobalCacheObject]; [oc setContext: localContext]; [oc setTableUrl: folderTableURL]; [oc reloadIfNeeded]; if ([oc isNew]) { fprintf(stderr, "ERROR: Device with ID '%s' not found\n", [deviceId UTF8String]); return rc; } NSMutableString *sql; sql = [NSMutableString stringWithFormat: @"DELETE FROM %@ WHERE c_path like '/%@%'", [oc tableName], deviceId]; [oc performBatchSQLQueries: [NSArray arrayWithObject: sql]]; rc = YES; } else { fprintf(stderr, "\nERROR: deviceId not specified\n\n"); } break; case ManageEASResetFolder: if (max > 2) { /* value specified on command line */ folderId = [sanitizedArguments objectAtIndex: 2]; oc = [SOGoCacheGCSObject objectWithName: folderId inContainer: nil]; [oc setObjectType: ActiveSyncFolderCacheObject]; [oc setContext: localContext]; [oc setTableUrl: folderTableURL]; [oc reloadIfNeeded]; if ([oc isNew]) { fprintf(stderr, "ERROR: Folder with ID \"%s\" not found\n", [folderId UTF8String]); return rc; } if ((![folderId hasSuffix: @"/personal"]) && [[oc properties] objectForKey: @"MergedFolder"]) { fprintf(stderr, "ERROR: MergedFolder = true; only personal folder can be reset"); return rc; } else { [self _setOrUnsetSyncRequest: YES collections: [NSArray arrayWithObject: folderId] context: localContext]; [[oc properties] removeObjectForKey: @"SyncKey"]; [[oc properties] removeObjectForKey: @"SyncCache"]; [[oc properties] removeObjectForKey: @"DateCache"]; [[oc properties] removeObjectForKey: @"UidCache"]; [[oc properties] removeObjectForKey: @"MoreAvailable"]; [[oc properties] removeObjectForKey: @"BodyPreferenceType"]; [[oc properties] removeObjectForKey: @"SupportedElements"]; [[oc properties] removeObjectForKey: @"SuccessfulMoveItemsOps"]; [[oc properties] removeObjectForKey: @"InitialLoadSequence"]; [[oc properties] removeObjectForKey: @"MergedFoldersSyncKeys"]; [[oc properties] removeObjectForKey: @"CleanoutDate"]; [oc save]; rc = YES; } } else { fprintf(stderr, "\nERROR: folderId not specified\n\n"); } break; case ManageEASVCard: case ManageEASVEvent: if (max > 3) { NSString *folderType; if (cmd == ManageEASVCard) folderType = @"vcard"; else folderType = @"vevent"; /* value specified on command line */ deviceId = [sanitizedArguments objectAtIndex: 2]; oc = [SOGoCacheGCSObject objectWithName: @"0" inContainer: nil]; [oc setObjectType: ActiveSyncFolderCacheObject]; [oc setContext: localContext]; [oc setTableUrl: folderTableURL]; entries = [oc cacheEntriesForDeviceId: deviceId newerThanVersion: -1]; vtodo: for (i = 0; i < [entries count]; i++) { cacheEntry = [entries objectAtIndex: i]; if ([[cacheEntry substringFromIndex: 1] hasPrefix: [NSString stringWithFormat: @"%@+%@/", deviceId, folderType]]) { fprintf(stdout,"Folder Key: %s\n", [[cacheEntry substringFromIndex: 1] UTF8String]); foc = [SOGoCacheGCSObject objectWithName: [cacheEntry substringFromIndex: 1] inContainer: nil]; [foc setObjectType: ActiveSyncFolderCacheObject]; [foc setContext: localContext]; [foc setTableUrl: folderTableURL]; [foc reloadIfNeeded]; if ([foc isNew]) continue; [self _setOrUnsetSyncRequest: YES collections: [NSArray arrayWithObject: [cacheEntry substringFromIndex: 1]] context: localContext]; if (![[cacheEntry substringFromIndex: 1] hasPrefix: [NSString stringWithFormat: @"%@+%@/personal", deviceId, folderType]] && [[sanitizedArguments objectAtIndex: 3] isEqualToString: @"NO"] && [[[foc properties] objectForKey: @"MergedFolder"] isEqualToString: @"2"]) { [foc destroy]; continue; } else if ([[sanitizedArguments objectAtIndex: 3] isEqualToString: @"NO"]) { [[foc properties] removeObjectForKey: @"SyncKey"]; [[foc properties] removeObjectForKey: @"SyncCache"]; [[foc properties] removeObjectForKey: @"DateCache"]; [[foc properties] removeObjectForKey: @"UidCache"]; [[foc properties] removeObjectForKey: @"MoreAvailable"]; [[foc properties] removeObjectForKey: @"BodyPreferenceType"]; [[foc properties] removeObjectForKey: @"SupportedElements"]; [[foc properties] removeObjectForKey: @"SuccessfulMoveItemsOps"]; [[foc properties] removeObjectForKey: @"InitialLoadSequence"]; [[foc properties] removeObjectForKey: @"FirstIdInCache"]; [[foc properties] removeObjectForKey: @"LastIdInCache"]; [[foc properties] removeObjectForKey: @"MergedFoldersSyncKeys"]; [[foc properties] removeObjectForKey: @"CleanoutDate"]; [[foc properties] removeObjectForKey: @"MergedFolder"]; } else if ([[sanitizedArguments objectAtIndex: 3] isEqualToString: @"YES"] && ![[foc properties] objectForKey: @"MergedFolder"]) { if (![[cacheEntry substringFromIndex: 1] hasPrefix: [NSString stringWithFormat: @"%@+%@/personal", deviceId, folderType]]) { [[foc properties] removeObjectForKey: @"SyncKey"]; [[foc properties] removeObjectForKey: @"SyncCache"]; [[foc properties] removeObjectForKey: @"DateCache"]; [[foc properties] removeObjectForKey: @"UidCache"]; [[foc properties] removeObjectForKey: @"MoreAvailable"]; [[foc properties] removeObjectForKey: @"BodyPreferenceType"]; [[foc properties] removeObjectForKey: @"SupportedElements"]; [[foc properties] removeObjectForKey: @"SuccessfulMoveItemsOps"]; [[foc properties] removeObjectForKey: @"InitialLoadSequence"]; [[foc properties] removeObjectForKey: @"FirstIdInCache"]; [[foc properties] removeObjectForKey: @"LastIdInCache"]; [[foc properties] removeObjectForKey: @"MergedFoldersSyncKeys"]; [[foc properties] removeObjectForKey: @"CleanoutDate"]; } [[foc properties] setObject: @"1" forKey: @"MergedFolder"]; } [foc save]; } } if (cmd == ManageEASVEvent && [folderType isEqualToString: @"vevent"]) { folderType = @"vtodo"; goto vtodo; } rc = YES; } else { fprintf(stderr, "\nERROR: folderId not specified\n\n"); } break; case ManageEASUnknown: break; } } if (!rc) { [self usage]; } return rc; } @end