/* MAPIStoreDBMessage.m - this file is part of SOGo * * Copyright (C) 2011-2012 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 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. */ #import #import #import #import #import #import #import #import "MAPIStoreContext.h" #import "MAPIStorePropertySelectors.h" #import "SOGoMAPIDBMessage.h" #import "MAPIStoreDBFolder.h" #import "MAPIStoreDBMessage.h" #import "MAPIStoreTypes.h" #import "NSData+MAPIStore.h" #import "NSObject+MAPIStore.h" #import "NSString+MAPIStore.h" #undef DEBUG #include #include @implementation MAPIStoreDBMessage + (enum mapistore_error) getAvailableProperties: (struct SPropTagArray **) propertiesP inMemCtx: (TALLOC_CTX *) memCtx { struct SPropTagArray *properties; NSUInteger count; enum MAPITAGS faiProperties[] = { 0x68330048, /* PR_VIEW_CLSID */ 0x68350102, /* PR_VIEW_STATE */ 0x683c0102, 0x683d0040, 0x683e0102, 0x683f0102, /* PR_VIEW_VIEWTYPE_KEY */ 0x68410003, 0x68420102, 0x68450102, 0x68460003, 0x7006001F, /* PR_VD_NAME_W */ 0x70030003, /* PR_VD_FLAGS */ 0x70070003 /* PR_VD_VERSION */ }; size_t faiSize = sizeof(faiProperties) / sizeof(enum MAPITAGS); properties = talloc_zero (memCtx, struct SPropTagArray); properties->cValues = MAPIStoreSupportedPropertiesCount + faiSize; properties->aulPropTag = talloc_array (properties, enum MAPITAGS, MAPIStoreSupportedPropertiesCount + faiSize); for (count = 0; count < MAPIStoreSupportedPropertiesCount; count++) properties->aulPropTag[count] = MAPIStoreSupportedProperties[count]; /* FIXME (hack): append a few undocumented properties that can be added to FAI messages */ for (count = 0; count < faiSize; count++) properties->aulPropTag[MAPIStoreSupportedPropertiesCount+count] = faiProperties[count]; *propertiesP = properties; return MAPISTORE_SUCCESS; } - (enum mapistore_error) getAvailableProperties: (struct SPropTagArray **) propertiesP inMemCtx: (TALLOC_CTX *) memCtx { return [[self class] getAvailableProperties: propertiesP inMemCtx: memCtx]; } - (id) initWithSOGoObject: (id) newSOGoObject inContainer: (MAPIStoreObject *) newContainer { if ((self = [super initWithSOGoObject: newSOGoObject inContainer: newContainer])) { [properties release]; properties = [newSOGoObject properties]; [properties retain]; } return self; } - (NSString *) description { id key, value; NSEnumerator *propEnumerator; NSMutableString *description; description = [NSMutableString stringWithFormat: @"%@ %@. Properties: {", NSStringFromClass ([self class]), [self url]]; propEnumerator = [properties keyEnumerator]; while ((key = [propEnumerator nextObject])) { uint32_t proptag = 0; if ([key isKindOfClass: [NSString class]] && [(NSString *)key intValue] > 0) proptag = [(NSString *)key intValue]; else if ([key isKindOfClass: [NSNumber class]]) proptag = [key unsignedLongValue]; if (proptag > 0) { const char *propTagName = get_proptag_name ([key unsignedLongValue]); NSString *propName; if (propTagName) propName = [NSString stringWithCString: propTagName encoding: NSUTF8StringEncoding]; else propName = [NSString stringWithFormat: @"0x%.4x", [key unsignedLongValue]]; [description appendFormat: @"'%@': ", propName]; } else [description appendFormat: @"'%@': ", key]; value = [properties objectForKey: key]; [description appendFormat: @"%@ (%@), ", value, NSStringFromClass ([value class])]; } [description appendString: @"}\n"]; return description; } - (uint64_t) objectVersion { /* Return the global counter from CN structure. See [MS-OXCFXICS] Section 2.2.2.1 */ NSNumber *versionNbr, *cn; uint64_t objectVersion; [(SOGoMAPIDBMessage *) sogoObject reloadIfNeeded]; versionNbr = [properties objectForKey: @"version_number"]; if (versionNbr) objectVersion = exchange_globcnt ([versionNbr unsignedLongLongValue]); else { /* Old version which stored the CN structure not useful for searching */ cn = [properties objectForKey: @"version"]; if (cn) objectVersion = (([cn unsignedLongLongValue] >> 16) & 0x0000ffffffffffffLL); else objectVersion = ULLONG_MAX; } return objectVersion; } - (void) _updatePredecessorChangeList { BOOL updated; enum mapistore_error rc; NSData *currentChangeList, *changeKey; NSMutableArray *changeKeys; NSMutableData *newChangeList; NSUInteger count, len; struct SizedXid *changes; struct SPropValue property; struct SRow aRow; struct XID *currentChangeKey; TALLOC_CTX *localMemCtx; uint32_t nChanges; localMemCtx = talloc_new (NULL); if (!localMemCtx) { [self errorWithFormat: @"No more memory"]; return; } changeKey = [self getReplicaKeyFromGlobCnt: [self objectVersion]]; currentChangeList = [properties objectForKey: MAPIPropertyKey (PidTagPredecessorChangeList)]; if (!currentChangeList) { /* Create a new PredecessorChangeList */ len = [changeKey length]; newChangeList = [NSMutableData dataWithCapacity: len + 1]; [newChangeList appendUInt8: len]; [newChangeList appendData: changeKey]; } else { /* Update current predecessor change list with new change key */ changes = [currentChangeList asSizedXidArrayInMemCtx: localMemCtx with: &nChanges]; updated = NO; currentChangeKey = [changeKey asXIDInMemCtx: localMemCtx]; for (count = 0; count < nChanges && !updated; count++) { if (GUID_equal(&changes[count].XID.NameSpaceGuid, ¤tChangeKey->NameSpaceGuid)) { NSData *globCnt, *oldGlobCnt; oldGlobCnt = [NSData dataWithBytes: changes[count].XID.LocalId.data length: changes[count].XID.LocalId.length]; globCnt = [NSData dataWithBytes: currentChangeKey->LocalId.data length: currentChangeKey->LocalId.length]; if ([globCnt compare: oldGlobCnt] == NSOrderedDescending) { if ([globCnt length] != [oldGlobCnt length]) { [self errorWithFormat: @"Cannot compare globcnt with different length: %@ and %@", globCnt, oldGlobCnt]; abort(); } memcpy (changes[count].XID.LocalId.data, currentChangeKey->LocalId.data, currentChangeKey->LocalId.length); updated = YES; } } } /* Serialise it */ changeKeys = [NSMutableArray array]; if (!updated) [changeKeys addObject: changeKey]; for (count = 0; count < nChanges; count++) { changeKey = [NSData dataWithXID: &changes[count].XID]; [changeKeys addObject: changeKey]; } [changeKeys sortUsingFunction: MAPIChangeKeyGUIDCompare context: localMemCtx]; newChangeList = [NSMutableData data]; len = [changeKeys count]; for (count = 0; count < len; count++) { changeKey = [changeKeys objectAtIndex: count]; [newChangeList appendUInt8: [changeKey length]]; [newChangeList appendData: changeKey]; } } if ([newChangeList length] > 0) { property.ulPropTag = PidTagPredecessorChangeList; property.value.bin = *[newChangeList asBinaryInMemCtx: localMemCtx]; aRow.cValues = 1; aRow.lpProps = &property; rc = [self addPropertiesFromRow: &aRow]; if (rc != MAPISTORE_SUCCESS) [self errorWithFormat: @"Impossible to add a new predecessor change list: %d", rc]; } talloc_free (localMemCtx); } // // FIXME: how this can happen? // // We might get there if for some reasons, all classes weren't able // to tell us the message class. // - (int) getPidTagMessageClass: (void **) data inMemCtx: (TALLOC_CTX *) memCtx { *data = [@"IPM.Note" asUnicodeInMemCtx: memCtx]; [self logWithFormat: @"METHOD '%s' - unable to determine message class. Falling back to IPM.Note", __FUNCTION__]; return MAPISTORE_SUCCESS; } - (int) getProperties: (struct mapistore_property_data *) data withTags: (enum MAPITAGS *) tags andCount: (uint16_t) columnCount inMemCtx: (TALLOC_CTX *) memCtx { [sogoObject reloadIfNeeded]; return [super getProperties: data withTags: tags andCount: columnCount inMemCtx: memCtx]; } - (int) getProperty: (void **) data withTag: (enum MAPITAGS) propTag inMemCtx: (TALLOC_CTX *) memCtx { id value; int rc; 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; } - (void) addProperties: (NSDictionary *) newNewProperties { [sogoObject reloadIfNeeded]; [super addProperties: newNewProperties]; } - (void) save: (TALLOC_CTX *) memCtx { uint64_t newVersion; if ([attachmentKeys count] > 0) [properties setObject: attachmentParts forKey: @"attachments"]; newVersion = [[self context] getNewChangeNumber]; newVersion = exchange_globcnt ((newVersion >> 16) & 0x0000ffffffffffffLL); [properties setObject: [NSNumber numberWithUnsignedLongLong: newVersion] forKey: @"version_number"]; /* Remove old version */ [properties removeObjectForKey: @"version"]; /* Update PredecessorChangeList accordingly */ [self _updatePredecessorChangeList]; // [self logWithFormat: @"Saving %@", [self description]]; // [self logWithFormat: @"%d props in dict", [properties count]]; [sogoObject save]; } - (BOOL) _messageIsFreeBusy { NSString *msgClass; /* This is a HACK until we figure out how to determine a message position in the mailbox hierarchy.... (missing: folderid and role) */ msgClass = [properties objectForKey: MAPIPropertyKey (PR_MESSAGE_CLASS_UNICODE)]; return [msgClass isEqualToString: @"IPM.Microsoft.ScheduleData.FreeBusy"]; } /* TODO: differentiate between the "Own" and "All" cases */ - (BOOL) subscriberCanReadMessage { return [(MAPIStoreFolder *) container subscriberCanReadMessages]; // || [self _messageIsFreeBusy]); } - (BOOL) subscriberCanModifyMessage { return ((isNew && [(MAPIStoreFolder *) container subscriberCanCreateMessages]) || (!isNew && [(MAPIStoreFolder *) container subscriberCanModifyMessages])); // || [self _messageIsFreeBusy]); } - (NSDate *) creationTime { return [sogoObject creationDate]; } - (NSDate *) lastModificationTime { return [sogoObject lastModified]; } - (enum mapistore_error) setReadFlag: (uint8_t) flag { /* Modify PidTagMessageFlags from SetMessageReadFlag and SyncImportReadStateChanges ROPs */ NSNumber *flags; uint32_t newFlag; flags = [properties objectForKey: MAPIPropertyKey (PR_MESSAGE_FLAGS)]; if (flags) { newFlag = [flags unsignedLongValue]; if (flag & SUPPRESS_RECEIPT) newFlag |= MSGFLAG_READ; if (flag & CLEAR_RN_PENDING) newFlag &= ~MSGFLAG_RN_PENDING; if (flag & CLEAR_READ_FLAG) newFlag &= ~MSGFLAG_READ; if (flag & CLEAR_NRN_PENDING) newFlag &= ~MSGFLAG_NRN_PENDING; } else { newFlag = MSGFLAG_READ; if (flag & CLEAR_READ_FLAG) newFlag = 0x0; } [properties setObject: [NSNumber numberWithUnsignedLong: newFlag] forKey: MAPIPropertyKey (PR_MESSAGE_FLAGS)]; return MAPISTORE_SUCCESS; } @end