/* MAPIStoreFolder.m - this file is part of SOGo * * Copyright (C) 2011-2014 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 3, 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. */ /* TODO: main key arrays must be initialized */ #import #import #import #import #import #import #import #import #import #import #import #import #import #import "MAPIStoreActiveTables.h" #import "MAPIStoreContext.h" #import "MAPIStoreFAIMessage.h" #import "MAPIStoreFAIMessageTable.h" #import "MAPIStoreFolder.h" #import "MAPIStoreFolderTable.h" #import "MAPIStoreMapping.h" #import "MAPIStoreMessage.h" #import "MAPIStorePermissionsTable.h" #import "MAPIStoreSamDBUtils.h" #import "MAPIStoreTypes.h" #import "MAPIStoreUserContext.h" #import "NSData+MAPIStore.h" #import "NSDate+MAPIStore.h" #import "NSString+MAPIStore.h" #import "NSObject+MAPIStore.h" #import #import "SOGoMAPIDBMessage.h" #import "SOGoCacheGCSObject+MAPIStore.h" #import #include #undef DEBUG #include #include #include #include Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMessageTableK, MAPIStoreFolderTableK; @implementation MAPIStoreFolder + (void) initialize { NSExceptionK = [NSException class]; MAPIStoreFAIMessageK = [MAPIStoreFAIMessage class]; MAPIStoreMessageTableK = [MAPIStoreMessageTable class]; MAPIStoreFAIMessageTableK = [MAPIStoreFAIMessageTable class]; MAPIStoreFolderTableK = [MAPIStoreFolderTable class]; } - (id) init { if ((self = [super init])) { // messageKeys = nil; // faiMessageKeys = nil; // folderKeys = nil; dbFolder = nil; context = nil; // propsFolder = nil; // propsMessage = nil; } return self; } - (void) setupAuxiliaryObjects { NSURL *folderURL; NSMutableString *pathPrefix; NSString *path, *folderName; NSArray *parts; NSUInteger lastPartIdx; MAPIStoreUserContext *userContext; parts = 0; lastPartIdx = 0; folderURL = [NSURL URLWithString: [self url]]; /* note: -[NSURL path] returns an unescaped representation */ path = [folderURL path]; path = [path substringFromIndex: 1]; if ([path length] > 0) { parts = [path componentsSeparatedByString: @"/"]; lastPartIdx = [parts count] - 1; if ([path hasSuffix: @"/"]) lastPartIdx--; folderName = [parts objectAtIndex: lastPartIdx]; } else folderName = [folderURL host]; userContext = [self userContext]; [userContext activate]; [userContext ensureFolderTableExists]; ASSIGN (dbFolder, [SOGoCacheGCSFolder objectWithName: folderName inContainer: [container dbFolder]]); [dbFolder setTableUrl: [userContext folderTableURL]]; if (!container && [path length] > 0) { pathPrefix = [NSMutableString stringWithCapacity: 64]; [pathPrefix appendFormat: @"/%@", [folderURL host]]; parts = [parts subarrayWithRange: NSMakeRange (0, lastPartIdx)]; if ([parts count] > 0) [pathPrefix appendFormat: @"/%@", [parts componentsJoinedByString: @"/"]]; [dbFolder setPathPrefix: pathPrefix]; } [dbFolder reloadIfNeeded]; /* propsMessage and self share the same properties dictionary */ // ASSIGN (propsMessage, // [SOGoMAPIDBMessage objectWithName: @"properties.plist" // inContainer: dbFolder]); // [propsMessage setObjectType: MAPIInternalCacheObject]; // [propsMessage reloadIfNeeded]; [properties release]; properties = [dbFolder properties]; [properties retain]; [self setupVersionsMessage]; } - (id) initWithSOGoObject: (id) newSOGoObject inContainer: (MAPIStoreObject *) newContainer { /* The instantiation of auxiliary folders is postponed when newContainer is nil since there is no way to deduce the parent url. When setContext: is invoked, it becomes possible again. */ if ((self = [super initWithSOGoObject: newSOGoObject inContainer: newContainer]) && newContainer) { [self setupAuxiliaryObjects]; } return self; } - (void) setContext: (MAPIStoreContext *) newContext { ASSIGN (context, newContext); if (newContext) [self setupAuxiliaryObjects]; } - (MAPIStoreContext *) context { if (!context) [self setContext: (MAPIStoreContext *) [container context]]; return context; } - (void) dealloc { //[self logWithFormat: @"METHOD '%s' (%d)", __FUNCTION__, __LINE__]; // [messageKeys release]; // [faiMessageKeys release]; // [folderKeys release]; // [propsMessage release]; [dbFolder release]; [context release]; [super dealloc]; } - (SOGoCacheGCSFolder *) dbFolder { return dbFolder; } /* backend interface */ // - (SOGoMAPIDBMessage *) propertiesMessage // { // return propsMessage; // } - (uint64_t) objectVersion { NSNumber *value; uint64_t cn; value = [properties objectForKey: MAPIPropertyKey (PidTagChangeNumber)]; if (value) cn = [value unsignedLongLongValue]; else { [self logWithFormat: @"no value for PidTagChangeNumber, adding one now"]; cn = [[self context] getNewChangeNumber]; value = [NSNumber numberWithUnsignedLongLong: cn]; [properties setObject: value forKey: MAPIPropertyKey (PidTagChangeNumber)]; [dbFolder save]; } return cn >> 16; } - (id) lookupFolder: (NSString *) folderKey { MAPIStoreFolder *childFolder = nil; SOGoFolder *sogoFolder; if ([[self folderKeys] containsObject: folderKey]) { [[self userContext] activate]; sogoFolder = [sogoObject lookupName: folderKey inContext: nil acquire: NO]; if (sogoFolder && ![sogoFolder isKindOfClass: NSExceptionK]) childFolder = [isa mapiStoreObjectWithSOGoObject: sogoFolder inContainer: self]; } return childFolder; } - (id) lookupFolderByURL: (NSString *) childURL { MAPIStoreObject *foundObject; NSString *key, *slashLessURL; if ([childURL hasSuffix: @"/"]) slashLessURL = [childURL substringToIndex: [childURL length] - 1]; else slashLessURL = childURL; key = [self childKeyFromURL: slashLessURL]; if (key) foundObject = [self lookupFolder: key]; else foundObject = nil; return foundObject; } - (id) lookupMessage: (NSString *) messageKey { MAPIStoreObject *childMessage = nil; Class messageClass; SOGoObject *msgObject; if (messageKey) { [[self userContext] activate]; msgObject = [sogoObject lookupName: messageKey inContext: nil acquire: NO]; /* If the lookup in the indexing table works, but the IMAP does not have the message, then the message does not exist in this folder */ if (msgObject && [msgObject isKindOfClass: [SOGoMailObject class]] && ! [(SOGoMailObject *)msgObject doesMailExist]) return nil; if (msgObject && ![msgObject isKindOfClass: NSExceptionK]) { [msgObject setContext: [[self userContext] woContext]]; messageClass = [msgObject mapistoreMessageClass]; childMessage = [messageClass mapiStoreObjectWithSOGoObject: msgObject inContainer: self]; } } return childMessage; } - (id) lookupFAIMessage: (NSString *) messageKey { MAPIStoreObject *childMessage = nil; SOGoObject *msgObject; if (messageKey) { [[self userContext] activate]; if ([[self faiMessageKeys] containsObject: messageKey]) { msgObject = [dbFolder lookupName: messageKey inContext: nil acquire: NO]; childMessage = [MAPIStoreFAIMessageK mapiStoreObjectWithSOGoObject: msgObject inContainer: self]; } } return childMessage; } - (NSString *) childKeyFromURL: (NSString *) childURL { NSString *baseURL, *subURL, *key = nil; NSArray *parts; NSUInteger partsCount; baseURL = [self url]; if (![baseURL hasSuffix: @"/"]) baseURL = [NSString stringWithFormat: @"%@/", baseURL]; if ([childURL hasPrefix: baseURL]) { subURL = [childURL substringFromIndex: [baseURL length]]; if ([subURL length] > 0) { parts = [subURL componentsSeparatedByString: @"/"]; partsCount = [parts count]; if (partsCount == 1) { key = [[parts objectAtIndex: 0] stringByReplacingPercentEscapesUsingEncoding: NSUTF8StringEncoding]; } } } return key; } - (id) lookupMessageByURL: (NSString *) childURL { MAPIStoreObject *foundObject; NSString *key; key = [self childKeyFromURL: childURL]; if (key) { foundObject = [self lookupFAIMessage: key]; if (!foundObject) foundObject = [self lookupMessage: key]; } else foundObject = nil; return foundObject; } - (int) openFolder: (MAPIStoreFolder **) childFolderPtr withFID: (uint64_t) fid { int rc = MAPISTORE_ERR_NOT_FOUND; MAPIStoreFolder *childFolder; MAPIStoreMapping *mapping; NSString *childURL; //[self logWithFormat: @"METHOD '%s' (%d)", __FUNCTION__, __LINE__]; mapping = [self mapping]; childURL = [mapping urlFromID: fid]; if (childURL) { childFolder = [self lookupFolderByURL: childURL]; if (childFolder) { *childFolderPtr = childFolder; rc = MAPISTORE_SUCCESS; } } return rc; } - (int) createFolder: (MAPIStoreFolder **) childFolderPtr withRow: (struct SRow *) aRow andFID: (uint64_t) fid { BOOL mapped; enum mapistore_error rc = MAPISTORE_SUCCESS; MAPIStoreMapping *mapping; NSString *baseURL, *childURL, *folderKey; MAPIStoreFolder *childFolder; SOGoUser *ownerUser; //[self logWithFormat: @"METHOD '%s' (%d)", __FUNCTION__, __LINE__]; ownerUser = [[self userContext] sogoUser]; if ([[context activeUser] isEqual: ownerUser] || [self subscriberCanCreateSubFolders]) { mapping = [self mapping]; childURL = [mapping urlFromID: fid]; if (childURL) rc = MAPISTORE_ERR_EXIST; else { rc = [self createFolder: aRow withFID: fid andKey: &folderKey]; if (rc == MAPISTORE_SUCCESS) { [self cleanupCaches]; baseURL = [self url]; if (![baseURL hasSuffix: @"/"]) baseURL = [NSString stringWithFormat: @"%@/", baseURL]; childURL = [NSString stringWithFormat: @"%@%@/", baseURL, [folderKey stringByAddingPercentEscapesUsingEncoding: NSUTF8StringEncoding]]; mapped = [mapping registerURL: childURL withID: fid]; if (!mapped) /* Enforce the creation if the backend does know the fid */ [mapping updateURL: childURL withID: fid]; childFolder = [self lookupFolder: folderKey]; if (childFolder) { [childFolder addPropertiesFromRow: aRow]; *childFolderPtr = childFolder; } else [NSException raise: @"MAPIStoreIOException" format: @"unable to fetch created folder"]; } } } else rc = MAPISTORE_ERR_DENIED; return rc; } - (int) deleteFolder { // TODO: raise exception in case underlying delete fails? // [propsMessage delete]; [dbFolder delete]; [self cleanupCaches]; return MAPISTORE_SUCCESS; } - (int) getChildCount: (uint32_t *) rowCount ofTableType: (enum mapistore_table_type) tableType { NSArray *keys; int rc = MAPISTORE_SUCCESS; //[self logWithFormat: @"METHOD '%s' (%d) -- tableType: %d", //__FUNCTION__, __LINE__, tableType]; if (tableType == MAPISTORE_MESSAGE_TABLE) keys = [self messageKeys]; else if (tableType == MAPISTORE_FOLDER_TABLE) keys = [self folderKeys]; else if (tableType == MAPISTORE_FAI_TABLE) keys = [self faiMessageKeys]; else { keys = nil; rc = MAPISTORE_ERR_NOT_FOUND; } *rowCount = [keys count]; return rc; } - (int) openMessage: (MAPIStoreMessage **) messagePtr withMID: (uint64_t) mid forWriting: (BOOL) readWrite inMemCtx: (TALLOC_CTX *) memCtx; { NSString *messageURL; MAPIStoreMapping *mapping; MAPIStoreMessage *message; SOGoUser *ownerUser; int rc = MAPISTORE_ERR_NOT_FOUND; mapping = [self mapping]; messageURL = [mapping urlFromID: mid]; if (messageURL) { message = [self lookupMessageByURL: messageURL]; if (message) { ownerUser = [[self userContext] sogoUser]; if ([[context activeUser] isEqual: ownerUser] || (readWrite && [message subscriberCanModifyMessage]) || (!readWrite && [message subscriberCanReadMessage])) { *messagePtr = message; rc = MAPISTORE_SUCCESS; } else rc = MAPISTORE_ERR_DENIED; } else { /* Unregistering from indexing table as the backend says the object was not found */ [mapping unregisterURLWithID: mid]; } } return rc; } - (int) createMessage: (MAPIStoreMessage **) messagePtr withMID: (uint64_t) mid isAssociated: (BOOL) isAssociated { enum mapistore_error rc; MAPIStoreMessage *message; NSString *baseURL, *childURL; MAPIStoreMapping *mapping; SOGoUser *ownerUser; //[self logWithFormat: @"METHOD '%s' -- mid: 0x%.16llx associated: %d", // __FUNCTION__, mid, isAssociated]; context = [self context]; ownerUser = [[self userContext] sogoUser]; [[self userContext] activate]; if ([[context activeUser] isEqual: ownerUser] || (!isAssociated && [self subscriberCanCreateMessages])) { mapping = [self mapping]; if ([mapping urlFromID: mid]) rc = MAPISTORE_ERR_EXIST; else { message = [self createMessage: isAssociated]; if (message) { baseURL = [self url]; if (![baseURL hasSuffix: @"/"]) baseURL = [NSString stringWithFormat: @"%@/", baseURL]; childURL = [NSString stringWithFormat: @"%@%@", baseURL, [message nameInContainer]]; [mapping registerURL: childURL withID: mid]; *messagePtr = message; rc = MAPISTORE_SUCCESS; } else rc = MAPISTORE_ERROR; } } else rc = MAPISTORE_ERR_DENIED; return rc; } - (int) deleteMessageWithMID: (uint64_t) mid andFlags: (uint8_t) flags { NSString *childURL; MAPIStoreMapping *mapping; MAPIStoreMessage *message; NSArray *activeTables; NSUInteger count, max; id msgObject; SOGoUser *ownerUser; int rc; /* flags that control the behaviour of the operation (MAPISTORE_SOFT_DELETE or MAPISTORE_PERMANENT_DELETE) */ [self logWithFormat: @"-deleteMessageWithMID: mid: 0x%.16llx flags: %d", mid, flags]; mapping = [self mapping]; childURL = [mapping urlFromID: mid]; if (childURL) { message = [self lookupMessageByURL: childURL]; if (message) { ownerUser = [[self userContext] sogoUser]; if ([[context activeUser] isEqual: ownerUser] || (![message isKindOfClass: MAPIStoreFAIMessageK] && [self subscriberCanDeleteMessages])) { /* we ensure the table caches are loaded so that old and new state can be compared */ activeTables = ([message isKindOfClass: MAPIStoreFAIMessageK] ? [self activeFAIMessageTables] : [self activeMessageTables]); max = [activeTables count]; for (count = 0; count < max; count++) [[activeTables objectAtIndex: count] restrictedChildKeys]; msgObject = [message sogoObject]; if (([msgObject respondsToSelector: @selector (prepareDelete)] && [msgObject prepareDelete]) || [msgObject delete]) { rc = MAPISTORE_ERROR; [self logWithFormat: @"ERROR deleting object at URL: %@", childURL]; } else { [self logWithFormat: @"successfully deleted object at URL: %@", childURL]; /* Ensure we are respecting flags parameter */ [mapping unregisterURLWithID: mid andFlags: flags]; [self cleanupCaches]; rc = MAPISTORE_SUCCESS; } } else rc = MAPISTORE_ERR_DENIED; } else rc = MAPISTORE_ERR_NOT_FOUND; } else rc = MAPISTORE_ERR_NOT_FOUND; return rc; } // private method - (int) _moveCopyMessageWithMID: (uint64_t) srcMid fromFolder: (MAPIStoreFolder *) sourceFolder withMID: (uint64_t) targetMid andChangeKey: (struct Binary_r *) targetChangeKey andPredecessorChangeList: (struct Binary_r *) targetPredecessorChangeList wantCopy: (uint8_t) wantCopy inMemCtx: (TALLOC_CTX *) memCtx { int rc; MAPIStoreMessage *sourceMsg, *destMsg; //TALLOC_CTX *memCtx; struct SRow aRow; struct SPropValue property; uint8_t deleteFlags; [self logWithFormat: @"-moveCopyMessageWithMID: 0x%.16llx .. withMID: 0x%.16llx .. wantCopy: %d", srcMid, targetMid, wantCopy]; //memCtx = talloc_zero (NULL, TALLOC_CTX); rc = [sourceFolder openMessage: &sourceMsg withMID: srcMid forWriting: NO inMemCtx: memCtx]; if (rc != MAPISTORE_SUCCESS) goto end; rc = [self createMessage: &destMsg withMID: targetMid isAssociated: [sourceMsg isKindOfClass: MAPIStoreFAIMessageK]]; if (rc != MAPISTORE_SUCCESS) goto end; [sourceMsg copyToMessage: destMsg inMemCtx: memCtx]; if (targetPredecessorChangeList) { property.ulPropTag = PidTagPredecessorChangeList; property.value.bin = *targetPredecessorChangeList; aRow.cValues = 1; aRow.lpProps = &property; rc = [destMsg addPropertiesFromRow: &aRow]; if (rc != MAPISTORE_SUCCESS) { [self errorWithFormat: @"Cannot add PredecessorChangeList on move"]; goto end; } } [destMsg save: memCtx]; if (!wantCopy) /* We want to keep mid for restoring/shared data to work if mids are different. */ deleteFlags = (srcMid == targetMid) ? MAPISTORE_PERMANENT_DELETE : MAPISTORE_SOFT_DELETE; rc = [sourceFolder deleteMessageWithMID: srcMid andFlags: deleteFlags]; end: //talloc_free (memCtx); return rc; } - (int) moveCopyMessagesWithMIDs: (uint64_t *) srcMids andCount: (uint32_t) midCount fromFolder: (MAPIStoreFolder *) sourceFolder withMIDs: (uint64_t *) targetMids andChangeKeys: (struct Binary_r **) targetChangeKeys andPredecessorChangeLists: (struct Binary_r **) targetPredecessorChangeLists wantCopy: (uint8_t) wantCopy inMemCtx: (TALLOC_CTX *) memCtx { int rc = MAPISTORE_SUCCESS; NSUInteger count; NSMutableArray *oldMessageURLs; NSString *oldMessageURL; MAPIStoreMapping *mapping; SOGoUser *ownerUser; struct Binary_r *targetChangeKey, *targetPredecessorChangeList; //TALLOC_CTX *memCtx; //memCtx = talloc_zero (NULL, TALLOC_CTX); ownerUser = [[self userContext] sogoUser]; if (wantCopy || [[context activeUser] isEqual: ownerUser]) { if ([sourceFolder isKindOfClass: isa] || [self isKindOfClass: [sourceFolder class]]) [self logWithFormat: @"%s: this class could probably implement" @" a specialized/optimized version", __FUNCTION__]; oldMessageURLs = [NSMutableArray arrayWithCapacity: midCount]; mapping = [self mapping]; for (count = 0; rc == MAPISTORE_SUCCESS && count < midCount; count++) { oldMessageURL = [mapping urlFromID: srcMids[count]]; if (oldMessageURL) { [oldMessageURLs addObject: oldMessageURL]; if (targetChangeKeys && targetPredecessorChangeList) { targetChangeKey = targetChangeKeys[count]; targetPredecessorChangeList = targetPredecessorChangeLists[count]; } else { targetChangeKey = NULL; targetPredecessorChangeList = NULL; } rc = [self _moveCopyMessageWithMID: srcMids[count] fromFolder: sourceFolder withMID: targetMids[count] andChangeKey: targetChangeKey andPredecessorChangeList: targetPredecessorChangeList wantCopy: wantCopy inMemCtx: memCtx]; } else rc = MAPISTORE_ERR_NOT_FOUND; } /* Notifications */ if (rc == MAPISTORE_SUCCESS) { // We cleanup cache of our source and destination folders [self cleanupCaches]; [sourceFolder cleanupCaches]; } } else rc = MAPISTORE_ERR_DENIED; //talloc_free (memCtx); return rc; } - (enum mapistore_error) moveCopyToFolder: (MAPIStoreFolder *) targetFolder withNewName: (NSString *) newFolderName isMove: (BOOL) isMove isRecursive: (BOOL) isRecursive inMemCtx: (TALLOC_CTX *) memCtx { enum mapistore_error rc; NSAutoreleasePool *pool; struct SRow folderRow; struct SPropValue nameProperty; MAPIStoreFolder *subFolder, *newFolder; NSArray *children; MAPIStoreMapping *mapping; MAPIStoreMessage *message, *targetMessage; NSUInteger count, max; NSString *childKey; uint64_t fmid; //TALLOC_CTX *memCtx; //memCtx = talloc_zero (NULL, TALLOC_CTX); /* TODO: one possible issue with this algorithm is that moved messages will lack a version number and will all be assigned a new one, even though they have not changed. This also means that they will be transferred again to the client during a sync operation. */ if ([targetFolder supportsSubFolders]) { mapping = [self mapping]; if (!newFolderName) newFolderName = [sogoObject displayName]; nameProperty.ulPropTag = PidTagDisplayName; nameProperty.value.lpszW = [newFolderName UTF8String]; folderRow.lpProps = &nameProperty; folderRow.cValues = 1; rc = [targetFolder createFolder: &folderRow withFID: [self objectId] andKey: &childKey]; if (rc == MAPISTORE_SUCCESS) { newFolder = [targetFolder lookupFolder: childKey]; [self copyPropertiesToObject: newFolder inMemCtx: memCtx]; pool = [NSAutoreleasePool new]; children = [self messageKeys]; max = [children count]; for (count = 0; count < max; count++) { childKey = [children objectAtIndex: count]; message = [self lookupMessage: childKey]; targetMessage = [newFolder createMessage: NO]; [targetMessage setIsNew: YES]; [message copyToMessage: targetMessage inMemCtx: memCtx]; if (isMove) { fmid = [mapping idFromURL: [message url]]; [self deleteMessageWithMID: fmid andFlags: MAPISTORE_PERMANENT_DELETE]; [mapping registerURL: [targetMessage url] withID: fmid]; } [targetMessage save: memCtx]; } [pool release]; pool = [NSAutoreleasePool new]; children = [self faiMessageKeys]; max = [children count]; for (count = 0; count < max; count++) { childKey = [children objectAtIndex: count]; message = [self lookupFAIMessage: childKey]; targetMessage = [newFolder createMessage: YES]; [targetMessage setIsNew: YES]; [message copyToMessage: targetMessage inMemCtx: memCtx]; if (isMove) { fmid = [mapping idFromURL: [message url]]; [self deleteMessageWithMID: fmid andFlags: MAPISTORE_PERMANENT_DELETE]; [mapping registerURL: [targetMessage url] withID: fmid]; } [targetMessage save: memCtx]; } [pool release]; if (isRecursive) { pool = [NSAutoreleasePool new]; children = [self folderKeys]; max = [children count]; for (count = 0; count < max; count++) { childKey = [children objectAtIndex: count]; subFolder = [self lookupFolder: childKey]; [subFolder moveCopyToFolder: newFolder withNewName: nil isMove: isMove isRecursive: isRecursive inMemCtx: memCtx]; } [pool release]; } if (isMove) [self deleteFolder]; [targetFolder cleanupCaches]; } [self cleanupCaches]; /* We perform the mapping operations at the end as objectId is required to be available until the caches are cleaned up */ if (isMove && rc == MAPISTORE_SUCCESS) { fmid = [mapping idFromURL: [self url]]; [mapping unregisterURLWithID: fmid]; [mapping registerURL: [newFolder url] withID: fmid]; } } else rc = MAPISTORE_ERR_DENIED; //talloc_free (memCtx); return rc; } - (SOGoFolder *) aclFolder { [self subclassResponsibility: _cmd]; return nil; } - (void) _modifyPermissionEntryForUser: (NSString *) user withRoles: (NSArray *) roles isAddition: (BOOL) isAddition withACLFolder: (SOGoFolder *) aclFolder { if (user) { if (isAddition) [aclFolder addUserInAcls: user]; [aclFolder setRoles: roles forUser: user]; } else [self logWithFormat: @"user is nil, keeping intended entry intact"]; } - (void) setupVersionsMessage { } - (void) ensureIDsForChildKeys: (NSArray *) keys { NSMutableArray *missingURLs; MAPIStoreMapping *mapping; NSUInteger count, max; NSString *baseURL, *URL, *key; NSArray *newIDs; uint64_t idNbr; bool softDeleted; baseURL = [self url]; mapping = [self mapping]; max = [keys count]; missingURLs = [NSMutableArray arrayWithCapacity: max]; for (count = 0; count < max; count++) { key = [keys objectAtIndex: count]; URL = [NSString stringWithFormat: @"%@%@", baseURL, key]; idNbr = [mapping idFromURL: URL isSoftDeleted: &softDeleted]; if (idNbr == NSNotFound && !softDeleted) [missingURLs addObject: URL]; } max = [missingURLs count]; newIDs = [[self context] getNewFMIDs: max]; [mapping registerURLs: missingURLs withIDs: newIDs]; } - (int) getDeletedFMIDs: (struct UI8Array_r **) fmidsPtr andCN: (uint64_t *) cnPtr fromChangeNumber: (uint64_t) changeNum inTableType: (enum mapistore_table_type) tableType inMemCtx: (TALLOC_CTX *) memCtx { int rc; NSString *baseURL, *format, *url; NSArray *keys; NSNumber *cnNbr; NSUInteger count, max; MAPIStoreMapping *mapping; struct UI8Array_r *fmids; uint64_t fmid; bool softDeleted; keys = [self getDeletedKeysFromChangeNumber: changeNum andCN: &cnNbr inTableType: tableType]; if (keys) { mapping = [self mapping]; max = [keys count]; fmids = talloc_zero (memCtx, struct UI8Array_r); fmids->cValues = 0; fmids->lpui8 = talloc_array (fmids, uint64_t, max); *fmidsPtr = fmids; if (max > 0) *cnPtr = [cnNbr unsignedLongLongValue]; baseURL = [self url]; if ([baseURL hasSuffix: @"/"]) format = @"%@%@"; else format = @"%@/%@"; for (count = 0; count < max; count++) { url = [NSString stringWithFormat: format, baseURL, [keys objectAtIndex: count]]; fmid = [mapping idFromURL: url isSoftDeleted: &softDeleted]; if (fmid != NSNotFound) /* if no fmid is returned, then the object "never existed" in the OpenChange databases. Soft-deleted messages are returned back */ { fmids->lpui8[fmids->cValues] = fmid; fmids->cValues++; } } rc = MAPISTORE_SUCCESS; } else rc = MAPISTORE_ERR_NOT_FOUND; return rc; } - (int) getTable: (MAPIStoreTable **) tablePtr andRowCount: (uint32_t *) countPtr tableType: (enum mapistore_table_type) tableType andHandleId: (uint32_t) handleId { int rc = MAPISTORE_SUCCESS; MAPIStoreTable *table; SOGoUser *ownerUser; if (tableType == MAPISTORE_MESSAGE_TABLE) table = [self messageTable]; else if (tableType == MAPISTORE_FAI_TABLE) table = [self faiMessageTable]; else if (tableType == MAPISTORE_FOLDER_TABLE) table = [self folderTable]; else if (tableType == MAPISTORE_PERMISSIONS_TABLE) { ownerUser = [[self userContext] sogoUser]; if ([[context activeUser] isEqual: ownerUser]) table = [self permissionsTable]; else rc = MAPISTORE_ERR_DENIED; } else { table = nil; [NSException raise: @"MAPIStoreIOException" format: @"unsupported table type: %d", tableType]; } if (rc == MAPISTORE_SUCCESS) { if (table) { [table setHandleId: handleId]; *tablePtr = table; *countPtr = [[table childKeys] count]; } else rc = MAPISTORE_ERR_NOT_FOUND; } return rc; } - (void) addProperties: (NSDictionary *) newProperties { static enum MAPITAGS bannedProps[] = { PR_MID, PR_FID, PR_PARENT_FID, PR_SOURCE_KEY, PR_PARENT_SOURCE_KEY, PR_CHANGE_KEY, PidTagChangeNumber, 0x00000000 }; enum MAPITAGS *currentProp; NSMutableDictionary *propsCopy; uint64_t cn; /* TODO: this should no longer be required once mapistore v2 API is in place, when we can then do this from -dealloc below */ [dbFolder reloadIfNeeded]; propsCopy = [newProperties mutableCopy]; [propsCopy autorelease]; currentProp = bannedProps; while (*currentProp) { [propsCopy removeObjectForKey: MAPIPropertyKey (*currentProp)]; currentProp++; } [properties addEntriesFromDictionary: propsCopy]; /* Update change number after setting the properties */ cn = [[self context] getNewChangeNumber]; [properties setObject: [NSNumber numberWithUnsignedLongLong: cn] forKey: MAPIPropertyKey (PidTagChangeNumber)]; [dbFolder save]; } - (NSArray *) messageKeys { return [self messageKeysMatchingQualifier: nil andSortOrderings: nil]; // if (!messageKeys) // { // messageKeys = [self messageKeysMatchingQualifier: nil // andSortOrderings: nil]; // [messageKeys retain]; // } // return messageKeys; } - (MAPIStoreFAIMessageTable *) faiMessageTable { return [MAPIStoreFAIMessageTable tableForContainer: self]; } - (NSArray *) faiMessageKeysMatchingQualifier: (EOQualifier *) qualifier andSortOrderings: (NSArray *) sortOrderings { [[self userContext] activate]; return [dbFolder childKeysOfType: MAPIFAICacheObject includeDeleted: NO matchingQualifier: qualifier andSortOrderings: sortOrderings]; } - (NSArray *) faiMessageKeys { return [self faiMessageKeysMatchingQualifier: nil andSortOrderings: nil]; // if (!faiMessageKeys) // { // faiMessageKeys = [self faiMessageKeysMatchingQualifier: nil // andSortOrderings: nil]; // [faiMessageKeys retain]; // } // return faiMessageKeys; } - (MAPIStoreFolderTable *) folderTable { return [MAPIStoreFolderTable tableForContainer: self]; } - (NSArray *) folderKeys { return [self folderKeysMatchingQualifier: nil andSortOrderings: nil]; // if (!folderKeys) // { // folderKeys = [self folderKeysMatchingQualifier: nil // andSortOrderings: nil]; // [folderKeys retain]; // } // return folderKeys; } - (NSArray *) folderKeysMatchingQualifier: (EOQualifier *) qualifier andSortOrderings: (NSArray *) sortOrderings { if (qualifier) [self errorWithFormat: @"qualifier is not used for folders"]; if (sortOrderings) [self errorWithFormat: @"sort orderings are not used for folders"]; return [sogoObject toManyRelationshipKeys]; } - (NSArray *) activeMessageTables { return [[MAPIStoreActiveTables activeTables] activeTablesForFMID: [self objectId] andType: MAPISTORE_MESSAGE_TABLE]; } - (NSArray *) activeFAIMessageTables { return [[MAPIStoreActiveTables activeTables] activeTablesForFMID: [self objectId] andType: MAPISTORE_FAI_TABLE]; } - (void) _cleanupTableCaches: (enum mapistore_table_type) tableType { NSArray *tables; NSUInteger count, max; tables = [[MAPIStoreActiveTables activeTables] activeTablesForFMID: [self objectId] andType: tableType]; max = [tables count]; for (count = 0; count < max; count++) [[tables objectAtIndex: count] cleanupCaches]; } - (void) cleanupCaches { [self _cleanupTableCaches: MAPISTORE_MESSAGE_TABLE]; [self _cleanupTableCaches: MAPISTORE_FAI_TABLE]; [self _cleanupTableCaches: MAPISTORE_FOLDER_TABLE]; // [faiMessageKeys release]; // faiMessageKeys = nil; // [messageKeys release]; // messageKeys = nil; // [folderKeys release]; // folderKeys = nil; } - (int) getPidTagParentFolderId: (void **) data inMemCtx: (TALLOC_CTX *) memCtx { *data = MAPILongLongValue (memCtx, [container objectId]); return MAPISTORE_SUCCESS; } - (int) getPidTagFolderId: (void **) data inMemCtx: (TALLOC_CTX *) memCtx { *data = MAPILongLongValue (memCtx, [self objectId]); return MAPISTORE_SUCCESS; } /* Possible values are: 0x00000001 Modify 0x00000002 Read 0x00000004 Delete 0x00000008 Create Hierarchy Table 0x00000010 Create Contents Table 0x00000020 Create Associated Contents Table */ - (int) getPidTagAccess: (void **) data inMemCtx: (TALLOC_CTX *) memCtx { uint32_t access = 0; SOGoUser *ownerUser; BOOL userIsOwner; ownerUser = [[self userContext] sogoUser]; userIsOwner = [[context activeUser] isEqual: ownerUser]; if (userIsOwner || [self subscriberCanModifyMessages]) access |= 0x01; if (userIsOwner || [self subscriberCanReadMessages]) access |= 0x02; if (userIsOwner || [self subscriberCanDeleteMessages]) access |= 0x04; if ((userIsOwner || [self subscriberCanCreateSubFolders]) && [self supportsSubFolders]) access |= 0x08; if (userIsOwner || [self subscriberCanCreateMessages]) access |= 0x10; if (userIsOwner) access |= 0x20; *data = MAPILongValue (memCtx, access); return MAPISTORE_SUCCESS; } - (int) getPidTagRights: (void **) data inMemCtx: (TALLOC_CTX *) memCtx { uint32_t rights = 0; SOGoUser *ownerUser; BOOL userIsOwner; ownerUser = [[self userContext] sogoUser]; userIsOwner = [[context activeUser] isEqual: ownerUser]; if (userIsOwner || [self subscriberCanReadMessages]) rights |= RightsReadItems; if (userIsOwner || [self subscriberCanCreateMessages]) rights |= RightsCreateItems; if (userIsOwner || [self subscriberCanModifyMessages]) rights |= RightsEditOwn | RightsEditAll; if (userIsOwner || [self subscriberCanDeleteMessages]) rights |= RightsDeleteOwn | RightsDeleteAll; if ((userIsOwner || [self subscriberCanCreateSubFolders]) && [self supportsSubFolders]) rights |= RightsCreateSubfolders; if (userIsOwner) rights |= RightsFolderOwner | RightsFolderContact; *data = MAPILongValue (memCtx, rights); return MAPISTORE_SUCCESS; } - (int) getPidTagAccessControlListData: (void **) data inMemCtx: (TALLOC_CTX *) memCtx { *data = [[NSData data] asBinaryInMemCtx: memCtx]; return MAPISTORE_SUCCESS; } - (int) getPidTagAttributeHidden: (void **) data inMemCtx: (TALLOC_CTX *) memCtx { return [self getNo: data inMemCtx: memCtx]; } - (int) getPidTagAttributeSystem: (void **) data inMemCtx: (TALLOC_CTX *) memCtx { return [self getNo: data inMemCtx: memCtx]; } - (int) getPidTagAttributeReadOnly: (void **) data inMemCtx: (TALLOC_CTX *) memCtx { return [self getNo: data inMemCtx: memCtx]; } - (int) getPidTagSubfolders: (void **) data inMemCtx: (TALLOC_CTX *) memCtx { *data = MAPIBoolValue (memCtx, [self supportsSubFolders] && [[self folderKeys] count] > 0); return MAPISTORE_SUCCESS; } - (int) getPidTagFolderChildCount: (void **) data inMemCtx: (TALLOC_CTX *) memCtx { *data = MAPILongValue (memCtx, [[self folderKeys] count]); return MAPISTORE_SUCCESS; } - (int) getPidTagContentCount: (void **) data inMemCtx: (TALLOC_CTX *) memCtx { *data = MAPILongValue (memCtx, [[self messageKeys] count]); return MAPISTORE_SUCCESS; } - (int) getPidTagContentUnreadCount: (void **) data inMemCtx: (TALLOC_CTX *) memCtx { *data = MAPILongValue (memCtx, 0); return MAPISTORE_SUCCESS; } - (int) getPidTagAssociatedContentCount: (void **) data inMemCtx: (TALLOC_CTX *) memCtx { *data = MAPILongValue (memCtx, [[self faiMessageKeys] count]); return MAPISTORE_SUCCESS; } - (int) getPidTagDeletedCountTotal: (void **) data inMemCtx: (TALLOC_CTX *) memCtx { /* TODO */ *data = MAPILongValue (memCtx, 0); return MAPISTORE_SUCCESS; } - (int) getPidTagLocalCommitTimeMax: (void **) data inMemCtx: (TALLOC_CTX *) memCtx { int rc = MAPISTORE_SUCCESS; NSDate *date; date = [self lastMessageModificationTime]; if (date) *data = [date asFileTimeInMemCtx: memCtx]; else rc = MAPISTORE_ERR_NOT_FOUND; return rc; } - (int) getPidTagDefaultPostMessageClass: (void **) data inMemCtx: (TALLOC_CTX *) memCtx { *data = [@"IPM.Note" asUnicodeInMemCtx: memCtx]; return MAPISTORE_SUCCESS; } - (int) getProperties: (struct mapistore_property_data *) data withTags: (enum MAPITAGS *) tags andCount: (uint16_t) columnCount inMemCtx: (TALLOC_CTX *) memCtx { [dbFolder reloadIfNeeded]; return [super getProperties: data withTags: tags andCount: columnCount inMemCtx: memCtx]; } - (int) getProperty: (void **) data withTag: (enum MAPITAGS) propTag inMemCtx: (TALLOC_CTX *) memCtx { int rc; id value; value = [properties objectForKey: MAPIPropertyKey (propTag)]; if (value) rc = [value getValue: data forTag: propTag inMemCtx: memCtx]; else rc = [super getProperty: data withTag: propTag inMemCtx: memCtx]; return rc; } - (MAPIStoreMessage *) _createAssociatedMessage { MAPIStoreMessage *newMessage; SOGoMAPIDBMessage *dbObject; NSString *newKey; newKey = [NSString stringWithFormat: @"%@.plist", [SOGoObject globallyUniqueObjectId]]; dbObject = [SOGoMAPIDBMessage objectWithName: newKey inContainer: dbFolder]; [dbObject setObjectType: MAPIFAICacheObject]; [dbObject setIsNew: YES]; newMessage = [MAPIStoreFAIMessageK mapiStoreObjectWithSOGoObject: dbObject inContainer: self]; return newMessage; } - (MAPIStoreMessage *) createMessage: (BOOL) isAssociated { MAPIStoreMessage *newMessage; WOContext *woContext; [[self userContext] activate]; if (isAssociated) newMessage = [self _createAssociatedMessage]; else newMessage = [self createMessage]; /* FIXME: this is ugly as the specifics of message creation should all be delegated to subclasses */ if ([newMessage respondsToSelector: @selector (setIsNew:)]) [newMessage setIsNew: YES]; woContext = [[self userContext] woContext]; /* FIXME: this is ugly too as the specifics of message creation should all be delegated to subclasses */ if ([newMessage respondsToSelector: @selector (sogoObject:)]) [[newMessage sogoObject] setContext: woContext]; return newMessage; } - (enum mapistore_error) createFolder: (struct SRow *) aRow withFID: (uint64_t) newFID andKey: (NSString **) newKeyP { [self errorWithFormat: @"new folders cannot be created in this context"]; return MAPISTORE_ERR_DENIED; } /* helpers */ - (NSString *) url { NSString *url; if (container) url = [NSString stringWithFormat: @"%@/", [super url]]; else { url = [[context url] absoluteString]; if (![url hasSuffix: @"/"]) url = [NSString stringWithFormat: @"%@/", url]; } return url; } - (MAPIStorePermissionsTable *) permissionsTable { return [MAPIStorePermissionsTable tableForContainer: self]; } - (NSArray *) permissionEntries { NSMutableArray *permissionEntries; MAPIStorePermissionEntry *entry; NSArray *aclUsers; uint64_t memberId, regularMemberId = 1; NSUInteger count, max; NSString *username, *defaultUserId; SOGoFolder *aclFolder; aclFolder = [self aclFolder]; defaultUserId = [aclFolder defaultUserID]; aclUsers = [aclFolder aclUsers]; max = [aclUsers count]; permissionEntries = [NSMutableArray arrayWithCapacity: max]; for (count = 0; count < max; count++) { username = [aclUsers objectAtIndex: count]; if (![username hasPrefix: @"@"]) { if ([username isEqualToString: defaultUserId]) memberId = 0; else if ([username isEqualToString: @"anonymous"]) memberId = ULLONG_MAX; else { memberId = regularMemberId; regularMemberId++; } entry = [MAPIStorePermissionEntry entryWithUserId: username andMemberId: memberId forFolder: self]; [permissionEntries addObject: entry]; } } return permissionEntries; } - (NSArray *) rolesForExchangeRights: (uint32_t) rights { [self subclassResponsibility: _cmd]; return nil; } - (uint32_t) exchangeRightsForRoles: (NSArray *) roles { [self subclassResponsibility: _cmd]; return 0; } - (NSString *) _usernameFromEntryId: (struct SBinary_short *) bin { struct Binary_r bin32; struct AddressBookEntryId *entryId; NSString *username; if (bin && bin->cb) { bin32.cb = bin->cb; bin32.lpb = bin->lpb; entryId = get_AddressBookEntryId (NULL, &bin32); if (entryId) { username = MAPIStoreSamDBUserAttribute ([[self context] connectionInfo], @"legacyExchangeDN", [NSString stringWithUTF8String: entryId->X500DN], @"sAMAccountName"); } else username = nil; talloc_free (entryId); } else username = nil; return username; } - (NSString *) _usernameFromMemberId: (uint64_t) memberId inEntries: (NSArray *) entries { NSString *username = nil; NSUInteger count, max; MAPIStorePermissionEntry *entry; if (memberId == 0) username = [[self aclFolder] defaultUserID]; else if (memberId == ULLONG_MAX) username = @"anonymous"; else { max = [entries count]; for (count = 0; !username && count < max; count++) { entry = [entries objectAtIndex: count]; if ([entry memberId] == memberId) username = [entry userId]; } } return username; } - (void) _emptyACL { NSUInteger count, max; NSArray *users; SOGoFolder *aclFolder; aclFolder = [self aclFolder]; users = [[aclFolder aclUsers] copy]; max = [users count]; for (count = 0; count < max; count++) [aclFolder removeUserFromAcls: [users objectAtIndex: count]]; [users release]; } - (int) modifyPermissions: (struct PermissionData *) permissions withCount: (uint16_t) pcount andFlags: (int8_t) flags { NSUInteger count, propCount; struct PermissionData *currentPermission; struct mapi_SPropValue *mapiValue; NSString *permissionUser; NSArray *entries; NSArray *permissionRoles; BOOL reset, isAdd = NO, isDelete = NO, isModify = NO; SOGoFolder *aclFolder; aclFolder = [self aclFolder]; reset = ((flags & ModifyPerms_ReplaceRows) != 0); if (reset) [self _emptyACL]; entries = [self permissionEntries]; for (count = 0; count < pcount; count++) { currentPermission = permissions + count; permissionUser = nil; permissionRoles = nil; if (currentPermission->PermissionDataFlags == ROW_ADD) isAdd = YES; else if (currentPermission->PermissionDataFlags == ROW_MODIFY) isModify = YES; else isDelete = YES; for (propCount = 0; propCount < currentPermission->lpProps.cValues; propCount++) { mapiValue = currentPermission->lpProps.lpProps + propCount; switch (mapiValue->ulPropTag) { case PR_ENTRYID: if (isAdd) permissionUser = [self _usernameFromEntryId: &mapiValue->value.bin]; break; case PR_MEMBER_ID: if (isModify || isDelete) permissionUser = [self _usernameFromMemberId: mapiValue->value.d inEntries: entries]; break; case PR_MEMBER_RIGHTS: if (isAdd || isModify) permissionRoles = [self rolesForExchangeRights: mapiValue->value.l]; break; default: if (mapiValue->ulPropTag != PR_MEMBER_NAME) [self warnWithFormat: @"unhandled permission property: %.8x", mapiValue->ulPropTag]; } } if (reset) { if (isAdd) [self _modifyPermissionEntryForUser: permissionUser withRoles: permissionRoles isAddition: YES withACLFolder: aclFolder]; } else { if (isAdd || currentPermission->PermissionDataFlags == ROW_MODIFY) [self _modifyPermissionEntryForUser: permissionUser withRoles: permissionRoles isAddition: isAdd withACLFolder: aclFolder]; else if (currentPermission->PermissionDataFlags == ROW_REMOVE) [aclFolder removeUserFromAcls: permissionUser]; else [self errorWithFormat: @"unhandled permission action flag: %d", currentPermission->PermissionDataFlags]; } } return MAPISTORE_SUCCESS; } - (enum mapistore_error) preloadMessageBodiesWithMIDs: (const struct UI8Array_r *) mids ofTableType: (enum mapistore_table_type) tableType { uint32_t count; NSMutableArray *messageKeys; MAPIStoreMapping *mapping; NSString *messageURL, *messageKey; messageKeys = [NSMutableArray arrayWithCapacity: mids->cValues]; mapping = [self mapping]; for (count = 0; count < mids->cValues; count++) { messageURL = [mapping urlFromID: mids->lpui8[count]]; if (messageURL) { messageKey = [self childKeyFromURL: messageURL]; if (messageKey) [messageKeys addObject: messageKey]; } } return [self preloadMessageBodiesWithKeys: messageKeys ofTableType: tableType]; } - (enum mapistore_error) preloadMessageBodiesWithKeys: (NSArray *) keys ofTableType: (enum mapistore_table_type) tableType { return MAPISTORE_SUCCESS; } - (uint64_t) objectId { uint64_t objectId; NSString *folderKey; if (container) { folderKey = [NSString stringWithFormat: @"%@/", [sogoObject nameInContainer]]; objectId = [container idForObjectWithKey: folderKey]; } else objectId = [self idForObjectWithKey: nil]; return objectId; } - (uint64_t) idForObjectWithKey: (NSString *) childKey { return [[self context] idForObjectWithKey: childKey inFolderURL: [self url]]; } - (MAPIStoreFolder *) rootContainer { /* Return the oldest ancestor, which does not have container. If there is not container, it returns itself. */ if (container) return [container rootContainer]; else return self; } - (NSDate *) creationTime { return [dbFolder creationDate]; } - (NSDate *) lastModificationTime { return [dbFolder lastModified]; } /* subclasses */ - (MAPIStoreMessageTable *) messageTable { return nil; } - (NSArray *) messageKeysMatchingQualifier: (EOQualifier *) qualifier andSortOrderings: (NSArray *) sortOrderings { [self subclassResponsibility: _cmd]; return nil; } - (NSArray *) getDeletedKeysFromChangeNumber: (uint64_t) changeNum andCN: (NSNumber **) cnNbrs inTableType: (enum mapistore_table_type) tableType { return nil; } - (MAPIStoreMessage *) createMessage { [self logWithFormat: @"ignored method: %s", __PRETTY_FUNCTION__]; return nil; } - (NSCalendarDate *) lastMessageModificationTime { [self subclassResponsibility: _cmd]; return nil; } - (BOOL) subscriberCanCreateMessages { return NO; } - (BOOL) subscriberCanModifyMessages { return NO; } - (BOOL) subscriberCanReadMessages { return NO; } - (BOOL) subscriberCanDeleteMessages { return NO; } - (BOOL) subscriberCanCreateSubFolders { return NO; } - (BOOL) supportsSubFolders { return NO; } @end