diff --git a/ActiveSync/NGMimeMessage+ActiveSync.m b/ActiveSync/NGMimeMessage+ActiveSync.m index 85fec63cd..f3c11cd96 100644 --- a/ActiveSync/NGMimeMessage+ActiveSync.m +++ b/ActiveSync/NGMimeMessage+ActiveSync.m @@ -1,6 +1,6 @@ /* -Copyright (c) 2014, Inverse inc. +Copyright (c) 2015, Inverse inc. All rights reserved. Redistribution and use in source and binary forms, with or without @@ -40,35 +40,38 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. @implementation NGMimeMessage (ActiveSync) -- (NSArray *) allRecipients +- (void) _addRecipients: (NSEnumerator *) enumerator + toArray: (NSMutableArray *) recipients { - NSEnumerator *enumerator, *addressList; - NSMutableArray *recipients; NGMailAddressParser *parser; + NSEnumerator *addressList; NGMailAddress *address; NSString *s; + while ((s = [enumerator nextObject])) + { + parser = [NGMailAddressParser mailAddressParserWithString: s]; + addressList = [[parser parseAddressList] objectEnumerator]; + + while ((address = [addressList nextObject])) + [recipients addObject: [address address]]; + } +} + +- (NSArray *) allRecipients +{ + NSMutableArray *recipients; + recipients = [NSMutableArray array]; - enumerator = [[self headersForKey: @"to"] objectEnumerator]; - while ((s = [enumerator nextObject])) - { - parser = [NGMailAddressParser mailAddressParserWithString: s]; - addressList = [[parser parseAddressList] objectEnumerator]; - - while ((address = [addressList nextObject])) - [recipients addObject: [address address]]; - } + [self _addRecipients: [[self headersForKey: @"to"] objectEnumerator] + toArray: recipients]; - enumerator = [[self headersForKey: @"cc"] objectEnumerator]; - while ((s = [enumerator nextObject])) - { - parser = [NGMailAddressParser mailAddressParserWithString: s]; - addressList = [[parser parseAddressList] objectEnumerator]; - - while ((address = [addressList nextObject])) - [recipients addObject: [address address]]; - } + [self _addRecipients: [[self headersForKey: @"cc"] objectEnumerator] + toArray: recipients]; + + [self _addRecipients: [[self headersForKey: @"bcc"] objectEnumerator] + toArray: recipients]; return recipients; } diff --git a/ActiveSync/NSString+ActiveSync.h b/ActiveSync/NSString+ActiveSync.h index 172d71eb1..4aad558a2 100644 --- a/ActiveSync/NSString+ActiveSync.h +++ b/ActiveSync/NSString+ActiveSync.h @@ -49,6 +49,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - (NSString *) deviceType; - (NSString *) attachmentName; - (NSString *) command; +- (NSString *) collectionid; +- (NSString *) itemid; - (NSData *) convertHexStringToBytes; @end diff --git a/ActiveSync/NSString+ActiveSync.m b/ActiveSync/NSString+ActiveSync.m index 01f593352..6aa054d10 100644 --- a/ActiveSync/NSString+ActiveSync.m +++ b/ActiveSync/NSString+ActiveSync.m @@ -298,6 +298,31 @@ static NSArray *easCommandParameters = nil; return s; } +- (NSString *) itemid +{ + NSString *s; + + s = [self _valueForParameter: @"ITEMID="]; + + if (!s) + s = @"Unknown"; + + return s; +} + +- (NSString *) collectionid +{ + NSString *s; + + s = [self _valueForParameter: @"COLLECTIONID="]; + + if (!s) + s = @"Unknown"; + + return s; +} + + // // FIXME: combine with our OpenChange code. // diff --git a/ActiveSync/SOGoActiveSyncDispatcher+Sync.m b/ActiveSync/SOGoActiveSyncDispatcher+Sync.m index 2c353f736..cdd4cd9d1 100644 --- a/ActiveSync/SOGoActiveSyncDispatcher+Sync.m +++ b/ActiveSync/SOGoActiveSyncDispatcher+Sync.m @@ -540,6 +540,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. { NSMutableDictionary *folderMetadata, *dateCache, *syncCache; + NSString *davCollectionTagToStore; NSAutoreleasePool *pool; NSMutableString *s; @@ -618,6 +619,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. more_available = NO; + davCollectionTagToStore = [theCollection davCollectionTag]; + switch (theFolderType) { // Handle all the GCS components @@ -788,7 +791,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. else { [folderMetadata removeObjectForKey: @"MoreAvailable"]; - [folderMetadata setObject: [theCollection davCollectionTag] forKey: @"SyncKey"]; + [folderMetadata setObject: davCollectionTagToStore forKey: @"SyncKey"]; } [self _setFolderMetadata: folderMetadata @@ -963,7 +966,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. else { [folderMetadata removeObjectForKey: @"MoreAvailable"]; - [folderMetadata setObject: [theCollection davCollectionTag] forKey: @"SyncKey"]; + [folderMetadata setObject: davCollectionTagToStore forKey: @"SyncKey"]; } [self _setFolderMetadata: folderMetadata forKey: [self _getNameInCache: theCollection withType: theFolderType]]; @@ -1211,7 +1214,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. { // Make sure that client is updated with the right syncKey. - This keeps vtodo's and vevent's syncKey in sync. syncKeyInCache = [folderMetadata objectForKey: @"SyncKey"]; - if (syncKeyInCache && !([davCollectionTag isEqualToString:syncKeyInCache])) + if (syncKeyInCache && !([davCollectionTag isEqualToString:syncKeyInCache]) && ![davCollectionTag isEqualToString: @"-1"]) { davCollectionTag = syncKeyInCache; *changeDetected = YES; @@ -1431,8 +1434,9 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. } } - // Only send a response if there are changes otherwise send an empty response. - if (changeDetected) + + // Only send a response if there are changes or MS-ASProtocolVersion is either 2.5 or 12.0 oterwise send an empty response. + if (changeDetected || [[[context request] headerForKey: @"MS-ASProtocolVersion"] isEqualToString: @"2.5"] || [[[context request] headerForKey: @"MS-ASProtocolVersion"] isEqualToString: @"12.0"]) { // We always return the last generated response. // If we only return , diff --git a/ActiveSync/SOGoActiveSyncDispatcher.m b/ActiveSync/SOGoActiveSyncDispatcher.m index ff9321f92..fc73a7b5e 100644 --- a/ActiveSync/SOGoActiveSyncDispatcher.m +++ b/ActiveSync/SOGoActiveSyncDispatcher.m @@ -1127,6 +1127,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. id currentCollection; NSMutableString *s; NSData *d; + NSArray *allCollections; + int j; SOGoMicrosoftActiveSyncFolderType folderType; int status, count; @@ -1135,66 +1137,72 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. status = 1; count = 0; - collectionId = [[(id)[theDocumentElement getElementsByTagName: @"CollectionId"] lastObject] textValue]; - realCollectionId = [collectionId realCollectionIdWithFolderType: &folderType]; - - if (folderType == ActiveSyncMailFolder) - nameInCache = [NSString stringWithFormat: @"folder%@", realCollectionId]; - else - nameInCache = collectionId; - - realCollectionId = [self globallyUniqueIDToIMAPFolderName: realCollectionId type: folderType]; - - currentCollection = [self collectionFromId: realCollectionId type: folderType]; - - // - // For IMAP, we simply build a request like this: - // - // . UID SORT (SUBJECT) UTF-8 SINCE 1-Jan-2014 NOT DELETED - // * SORT 124576 124577 124579 124578 - // . OK Completed (4 msgs in 0.000 secs) - // - if (folderType == ActiveSyncMailFolder) - { - EOQualifier *notDeletedQualifier, *sinceDateQualifier; - EOAndQualifier *qualifier; - NSCalendarDate *filter; - NSArray *uids; - - filter = [NSCalendarDate dateFromFilterType: [[(id)[theDocumentElement getElementsByTagName: @"FilterType"] lastObject] textValue]]; - - notDeletedQualifier = [EOQualifier qualifierWithQualifierFormat: - @"(not (flags = %@))", - @"deleted"]; - sinceDateQualifier = [EOQualifier qualifierWithQualifierFormat: - @"(DATE >= %@)", filter]; - - qualifier = [[EOAndQualifier alloc] initWithQualifiers: notDeletedQualifier, sinceDateQualifier, - nil]; - AUTORELEASE(qualifier); - - uids = [currentCollection fetchUIDsMatchingQualifier: qualifier - sortOrdering: @"REVERSE ARRIVAL" - threaded: NO]; - count = [uids count]; - - // Add the number of UIDs expected to "soft delete" - count += [self _softDeleteCountWithFilter: filter collectionId: nameInCache]; - - } - else - { - count = [[currentCollection toOneRelationshipKeys] count]; - } - [s appendString: @""]; [s appendString: @""]; - [s appendFormat: @"%d", status]; + [s appendString: @""]; + + allCollections = (id)[theDocumentElement getElementsByTagName: @"Collection"]; + + for (j = 0; j < [allCollections count]; j++) + { + collectionId = [[(id)[[allCollections objectAtIndex: j] getElementsByTagName: @"CollectionId"] lastObject] textValue]; + realCollectionId = [collectionId realCollectionIdWithFolderType: &folderType]; + + if (folderType == ActiveSyncMailFolder) + nameInCache = [NSString stringWithFormat: @"folder%@", realCollectionId]; + else + nameInCache = collectionId; + + realCollectionId = [self globallyUniqueIDToIMAPFolderName: realCollectionId type: folderType]; + + currentCollection = [self collectionFromId: realCollectionId type: folderType]; - [s appendFormat: @"%@", collectionId]; - [s appendFormat: @"%d", count]; + // + // For IMAP, we simply build a request like this: + // + // . UID SORT (SUBJECT) UTF-8 SINCE 1-Jan-2014 NOT DELETED + // * SORT 124576 124577 124579 124578 + // . OK Completed (4 msgs in 0.000 secs) + // + if (folderType == ActiveSyncMailFolder) + { + NSCalendarDate *filter; + NSString *syncKey; + NSArray *allMessages; + + filter = [NSCalendarDate dateFromFilterType: [[(id)[[allCollections objectAtIndex: j] getElementsByTagName: @"FilterType"] lastObject] textValue]]; + syncKey = [[(id)[[allCollections objectAtIndex: j] getElementsByTagName: @"SyncKey"] lastObject] textValue]; + + allMessages = [currentCollection syncTokenFieldsWithProperties: nil matchingSyncToken: syncKey fromDate: filter]; + + count = [allMessages count]; + + // Add the number of UIDs expected to "soft delete" + count += [self _softDeleteCountWithFilter: filter collectionId: nameInCache]; + } + else + { + count = [[currentCollection toOneRelationshipKeys] count]; + } + + + [s appendString: @""]; + [s appendFormat: @"%d", status]; + + if (folderType == ActiveSyncMailFolder) + [s appendString: @"Email"]; + else if (folderType == ActiveSyncContactFolder) + [s appendString: @"Contacts"]; + else if (folderType == ActiveSyncEventFolder) + [s appendString: @"Calendar"]; + else if (folderType == ActiveSyncTaskFolder) + [s appendString: @"Tasks"]; + + [s appendFormat: @"%@",collectionId]; + [s appendFormat: @"%d", count]; + } - [s appendString: @""]; + [s appendString: @""]; d = [[s dataUsingEncoding: NSUTF8StringEncoding] xml2wbxml]; @@ -1929,6 +1937,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. [s appendString: @""]; [s appendString: @""]; [s appendString: @""]; + [s appendString: @"1"]; [s appendString: @""]; d = [[s dataUsingEncoding: NSUTF8StringEncoding] xml2wbxml]; @@ -2254,7 +2263,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. error = [self _sendMail: data recipients: [message allRecipients] - saveInSentItems: ([(id)[theDocumentElement getElementsByTagName: @"SaveInSentItems"] count] ? YES : NO)]; + saveInSentItems: ([(id)[theDocumentElement getElementsByTagName: @"SaveInSentItems"] count] ? YES : NO)]; if (error) { @@ -2345,7 +2354,21 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. id value; folderId = [[(id)[theDocumentElement getElementsByTagName: @"FolderId"] lastObject] textValue]; + + // if folderId is not there try to get it from URL + if (!folderId) + { + folderId = [[[context request] uri] collectionid]; + } + itemId = [[(id)[theDocumentElement getElementsByTagName: @"ItemId"] lastObject] textValue]; + + // if itemId is not there try to get it from URL + if (!itemId) + { + itemId = [[[context request] uri] itemid]; + } + realCollectionId = [folderId realCollectionIdWithFolderType: &folderType]; realCollectionId = [self globallyUniqueIDToIMAPFolderName: realCollectionId type: folderType]; @@ -2543,7 +2566,9 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // If the MS-ASProtocolVersion header is set to "12.1", the body of the SendMail request is // is a "message/rfc822" payload - otherwise, it's a WBXML blob. // - if ([cmdName caseInsensitiveCompare: @"SendMail"] == NSOrderedSame && + if (([cmdName caseInsensitiveCompare: @"SendMail"] == NSOrderedSame || + [cmdName caseInsensitiveCompare: @"SmartReply"] == NSOrderedSame || + [cmdName caseInsensitiveCompare: @"SmartForward"] == NSOrderedSame) && [[theRequest headerForKey: @"content-type"] caseInsensitiveCompare: @"message/rfc822"] == NSOrderedSame) { NSString *s, *xml; @@ -2561,7 +2586,9 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. s = [theRequest contentAsString]; } - xml = [NSString stringWithFormat: @"%@", [s stringByEncodingBase64]]; + xml = [NSString stringWithFormat: @"<%@ xmlns=\"ComposeMail:\">%@", cmdName, [s stringByEncodingBase64], cmdName]; + + d = [xml dataUsingEncoding: NSASCIIStringEncoding]; } diff --git a/ActiveSync/SOGoMailObject+ActiveSync.m b/ActiveSync/SOGoMailObject+ActiveSync.m index 6cc06e3d0..45c865c84 100644 --- a/ActiveSync/SOGoMailObject+ActiveSync.m +++ b/ActiveSync/SOGoMailObject+ActiveSync.m @@ -47,6 +47,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #import #import #import +#import #import #import @@ -57,6 +58,10 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #import #import +#import + +#import + #include "iCalTimeZone+ActiveSync.h" #include "NSData+ActiveSync.h" #include "NSDate+ActiveSync.h" @@ -204,6 +209,7 @@ struct GlobalObjectId { // // - (NSData *) _preferredBodyDataInMultipartUsingType: (int) theType + nativeTypeFound: (int *) theNativeTypeFound { NSString *encoding, *key, *plainKey, *htmlKey, *type, *subtype; NSDictionary *textParts, *part; @@ -222,6 +228,10 @@ struct GlobalObjectId { type = [part valueForKey: @"type"]; subtype = [part valueForKey: @"subtype"]; + // Don't select an attachment as body + if ([[[part valueForKey: @"disposition"] valueForKey: @"type"] isEqualToString: @"attachment"]) + continue; + if ([type isEqualToString: @"text"] && [subtype isEqualToString: @"html"]) htmlKey = key; else if ([type isEqualToString: @"text"] && [subtype isEqualToString: @"plain"]) @@ -229,11 +239,22 @@ struct GlobalObjectId { } key = nil; + *theNativeTypeFound = 1; - if (theType == 2) - key = htmlKey; - else if (theType == 1) + if (theType == 2 && htmlKey) + { + key = htmlKey; + *theNativeTypeFound = 2; + } + else if (theType == 1 && plainKey) key = plainKey; + else if (theType == 2 && plainKey) + key = plainKey; + else if (theType == 1 && htmlKey) + { + key = htmlKey; + *theNativeTypeFound = 2; + } if (key) { @@ -251,9 +272,17 @@ struct GlobalObjectId { charset = [[[self lookupInfoForBodyPart: key] objectForKey: @"parameterList"] objectForKey: @"charset"]; if (![charset length]) - charset = @"us-ascii"; + charset = @"utf-8"; s = [NSString stringWithData: d usingEncodingNamed: charset]; + + // We fallback to ISO-8859-1 string encoding + if (!s) + s = [[[NSString alloc] initWithData: d encoding: NSISOLatin1StringEncoding] autorelease]; + + if (theType == 1 && *theNativeTypeFound == 2) + s = [s htmlToText]; + d = [s dataUsingEncoding: NSUTF8StringEncoding]; } @@ -298,7 +327,7 @@ struct GlobalObjectId { [[[thePart contentType] type] isEqualToString: @"text"] && ([[[thePart contentType] subType] isEqualToString: @"plain"] || [[[thePart contentType] subType] isEqualToString: @"html"])) { - // We make sure everything is encoded in UTF-8 + // We make sure everything is encoded in UTF-8. NGMimeType *mimeType; NSString *s; @@ -309,9 +338,13 @@ struct GlobalObjectId { charset = [[thePart contentType] valueOfParameter: @"charset"]; if (![charset length]) - charset = @"us-ascii"; + charset = @"utf-8"; - s = [NSString stringWithData: body usingEncodingNamed: charset]; + s = [NSString stringWithData: body usingEncodingNamed: charset]; + + // We fallback to ISO-8859-1 string encoding. We avoid #3103. + if (!s) + s = [[[NSString alloc] initWithData: body encoding: NSISOLatin1StringEncoding] autorelease]; } else { @@ -414,7 +447,7 @@ struct GlobalObjectId { charset = [[[self lookupInfoForBodyPart: @""] objectForKey: @"parameterList"] objectForKey: @"charset"]; if (![charset length]) - charset = @"us-ascii"; + charset = @"utf-8"; d = [[self fetchPlainTextParts] objectForKey: @""]; @@ -428,25 +461,30 @@ struct GlobalObjectId { d = [d dataByDecodingQuotedPrintableTransferEncoding]; s = [NSString stringWithData: d usingEncodingNamed: charset]; + + // We fallback to ISO-8859-1 string encoding. We avoid #3103. + if (!s) + s = [[[NSString alloc] initWithData: d encoding: NSISOLatin1StringEncoding] autorelease]; // Check if we must convert html->plain if (theType == 1 && [subtype isEqualToString: @"html"]) { s = [s htmlToText]; } - + d = [s dataUsingEncoding: NSUTF8StringEncoding]; } else if ([type isEqualToString: @"multipart"]) { - d = [self _preferredBodyDataInMultipartUsingType: theType]; + d = [self _preferredBodyDataInMultipartUsingType: theType nativeTypeFound: theNativeType]; } } else if (theType == 4) { - // We sanitize the content *ONLY* for Outlook clients. Outlook has strange issues + // We sanitize the content *ONLY* for Outlook clients and if the content-transfer-encoding is 8bit. Outlook has strange issues // with quoted-printable/base64 encoded text parts. It just doesn't decode them. - if ([[context objectForKey: @"DeviceType"] isEqualToString: @"WindowsOutlook15"]) + encoding = [[self lookupInfoForBodyPart: @""] objectForKey: @"encoding"]; + if ([[context objectForKey: @"DeviceType"] isEqualToString: @"WindowsOutlook15"] || ([encoding caseInsensitiveCompare: @"8bit"] == NSOrderedSame)) d = [self _sanitizedMIMEMessage]; else d = [self content]; @@ -512,6 +550,10 @@ struct GlobalObjectId { NSData *d, *globalObjId; NSArray *attachmentKeys; NSMutableString *s; + + uint32_t v; + NSString *p; + id value; iCalCalendar *calendar; @@ -552,8 +594,25 @@ struct GlobalObjectId { if (value) [s appendFormat: @"%@", [value activeSyncRepresentationInContext: context]]; - // Importance - FIXME - [s appendFormat: @"%@", @"1"]; + // Importance + v = 0x1; + p = [[self mailHeaders] objectForKey: @"x-priority"]; + + if (p) + { + if ([p hasPrefix: @"1"]) v = 0x2; + else if ([p hasPrefix: @"2"]) v = 0x2; + else if ([p hasPrefix: @"4"]) v = 0x0; + else if ([p hasPrefix: @"5"]) v = 0x0; + } + else + { + p = [[self mailHeaders] objectForKey: @"importance"]; + if ([p hasPrefix: @"High"]) v = 0x2; + else if ([p hasPrefix: @"Low"]) v = 0x0; + } + + [s appendFormat: @"%d", v]; // Read [s appendFormat: @"%d", ([self read] ? 1 : 0)]; @@ -728,17 +787,31 @@ struct GlobalObjectId { len = [content length]; - [s appendString: @""]; - [s appendFormat: @"%d", preferredBodyType]; - [s appendFormat: @"%d", truncated]; - [s appendFormat: @""]; - - if (!truncated) + if ([[[context request] headerForKey: @"MS-ASProtocolVersion"] isEqualToString: @"2.5"]) { - [s appendFormat: @"%@", content]; - [s appendFormat: @"%d", len]; + [s appendFormat: @"%@", content]; + [s appendFormat: @"%d", truncated]; } - [s appendString: @""]; + else + { + [s appendString: @""]; + + // Set the correct type if client requested text/html but we got text/plain + if (preferredBodyType == 2 && nativeBodyType == 1) + [s appendString: @"1"]; + else + [s appendFormat: @"%d", preferredBodyType]; + + [s appendFormat: @"%d", truncated]; + [s appendFormat: @""]; + + if (!truncated) + { + [s appendFormat: @"%@", content]; + [s appendFormat: @"%d", len]; + } + [s appendString: @""]; + } } // Attachments -namespace 16 @@ -746,9 +819,12 @@ struct GlobalObjectId { if ([attachmentKeys count]) { int i; - - [s appendString: @""]; - + + if ([[[context request] headerForKey: @"MS-ASProtocolVersion"] isEqualToString: @"2.5"]) + [s appendString: @""]; + else + [s appendString: @""]; + for (i = 0; i < [attachmentKeys count]; i++) { value = [attachmentKeys objectAtIndex: i]; @@ -759,11 +835,22 @@ struct GlobalObjectId { // FileReference must be a unique identifier across the whole store. We use the following structure: // mail// // mail/INBOX/2 - [s appendFormat: @"mail/%@/%@/%@", [[[self container] relativeImap4Name] stringByEscapingURL], [self nameInContainer], [value objectForKey: @"path"]]; + if ([[[context request] headerForKey: @"MS-ASProtocolVersion"] isEqualToString: @"2.5"]) + [s appendFormat: @"mail/%@/%@/%@", [[[self container] relativeImap4Name] stringByEscapingURL], [self nameInContainer], [value objectForKey: @"path"]]; + else + [s appendFormat: @"mail/%@/%@/%@", [[[self container] relativeImap4Name] stringByEscapingURL], [self nameInContainer], [value objectForKey: @"path"]]; - [s appendFormat: @"%d", 1]; // See: http://msdn.microsoft.com/en-us/library/ee160322(v=exchg.80).aspx - [s appendFormat: @"%d", [[value objectForKey: @"size"] intValue]]; - //[s appendFormat: @"%d", 1]; + if ([[[context request] headerForKey: @"MS-ASProtocolVersion"] isEqualToString: @"2.5"]) + { + [s appendFormat: @"%d", 1]; + [s appendFormat: @"%d", [[value objectForKey: @"size"] intValue]]; + } + else + { + [s appendFormat: @"%d", 1]; // See: http://msdn.microsoft.com/en-us/library/ee160322(v=exchg.80).aspx + [s appendFormat: @"%d", [[value objectForKey: @"size"] intValue]]; + //[s appendFormat: @"%d", 1]; + } [s appendString: @""]; } @@ -774,6 +861,27 @@ struct GlobalObjectId { [s appendString: @""]; [s appendFormat: @"%d", ([self flagged] ? 2 : 0)]; [s appendString: @""]; + + + // Categroies/Labels + NSEnumerator *categories; + categories = [[[self fetchCoreInfos] objectForKey: @"flags"] objectEnumerator]; + + if (categories) + { + NSString *currentFlag; + NSDictionary *v; + + v = [[[context activeUser] userDefaults] mailLabelsColors]; + + [s appendFormat: @""]; + while ((currentFlag = [categories nextObject])) + { + if ([[v objectForKey: currentFlag] objectAtIndex:0]) + [s appendFormat: @"%@", [[[v objectForKey: currentFlag] objectAtIndex:0] activeSyncRepresentationInContext: context]]; + } + [s appendFormat: @""]; + } // FIXME - support these in the future //[s appendString: @"foobar"]; @@ -836,6 +944,65 @@ struct GlobalObjectId { else [self removeFlags: @"seen"];; } + + if ((o = [theValues objectForKey: @"Categories"])) + { + NSEnumerator *categories; + NSString *currentFlag; + NSDictionary *v; + + v = [[[context activeUser] userDefaults] mailLabelsColors]; + + // add categories/labels sent from client + if ([o isKindOfClass: [NSArray class]]) + { + NSEnumerator *enumerator; + NSMutableArray *labels; + NSEnumerator *flags; + id key; + + labels = [NSMutableArray array]; + + enumerator = [v keyEnumerator]; + flags = [o objectEnumerator]; + + while ((currentFlag = [flags nextObject])) + { + while ((key = [enumerator nextObject])) + { + if (([currentFlag isEqualToString:[[v objectForKey:key] objectAtIndex:0]])) + { + [labels addObject: key]; + break; + } + } + } + + [self addFlags: [labels componentsJoinedByString: @" "]]; + } + + categories = [[[self fetchCoreInfos] objectForKey: @"flags"] objectEnumerator]; + + // remove all categories/labels from server which were not it the list sent from client + if (categories) + { + while ((currentFlag = [categories nextObject])) + { + // only deal with lables and don't touch flags like seen and flagged + if (([v objectForKey: currentFlag])) + { + if (![o isKindOfClass: [NSArray class]]) + { + [self removeFlags: currentFlag]; + } + else if (([o indexOfObject: [[v objectForKey:currentFlag] objectAtIndex:0]] == NSNotFound)) + { + [self removeFlags: currentFlag]; + } + } + } + } + } } @end diff --git a/ActiveSync/iCalEvent+ActiveSync.m b/ActiveSync/iCalEvent+ActiveSync.m index d1a4b6f8a..586bf5798 100644 --- a/ActiveSync/iCalEvent+ActiveSync.m +++ b/ActiveSync/iCalEvent+ActiveSync.m @@ -40,6 +40,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #import #import #import +#import #import #import @@ -274,11 +275,20 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // It is very important here to NOT set 0 in the response, // otherwise it'll prevent WP8 phones from sync'ing. See #3028 for details. o = [o activeSyncRepresentationInContext: context]; - [s appendString: @""]; - [s appendFormat: @"%d", 1]; - [s appendFormat: @"%d", [o length]]; - [s appendFormat: @"%@", o]; - [s appendString: @""]; + + if ([[[context request] headerForKey: @"MS-ASProtocolVersion"] isEqualToString: @"2.5"]) + { + [s appendFormat: @"%@", o]; + [s appendString: @"0"]; + } + else + { + [s appendString: @""]; + [s appendFormat: @"%d", 1]; + [s appendFormat: @"%d", [o length]]; + [s appendFormat: @"%@", o]; + [s appendString: @""]; + } } [s appendFormat: @"%d", 1]; @@ -394,9 +404,17 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. } // FIXME: merge with iCalToDo - if ((o = [[theValues objectForKey: @"Body"] objectForKey: @"Data"])) - [self setComment: o]; - + if ([[[context request] headerForKey: @"MS-ASProtocolVersion"] isEqualToString: @"2.5"]) + { + if ((o = [theValues objectForKey: @"Body"])) + [self setComment: o]; + } + else + { + if ((o = [[theValues objectForKey: @"Body"] objectForKey: @"Data"])) + [self setComment: o]; + } + if ((o = [theValues objectForKey: @"Location"])) [self setLocation: o]; diff --git a/ActiveSync/iCalToDo+ActiveSync.m b/ActiveSync/iCalToDo+ActiveSync.m index 5f237a31b..e9417752c 100644 --- a/ActiveSync/iCalToDo+ActiveSync.m +++ b/ActiveSync/iCalToDo+ActiveSync.m @@ -38,6 +38,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #import #import #import +#import #import #import @@ -49,6 +50,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "NSDate+ActiveSync.h" #include "NSString+ActiveSync.h" + @implementation iCalToDo (ActiveSync) - (NSString *) activeSyncRepresentationInContext: (WOContext *) context @@ -62,11 +64,10 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. s = [NSMutableString string]; // Complete - o = [self completed]; - [s appendFormat: @"%d", (o ? 1 : 0)]; + [s appendFormat: @"%d", [[self status] isEqualToString: @"COMPLETED"] ? 1 : 0]; // DateCompleted - if (o) + if ((o = [self completed])) [s appendFormat: @"%@", [o activeSyncRepresentationInContext: context]]; // Start date @@ -97,6 +98,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // Reminder - FIXME [s appendFormat: @"%d", 0]; + // Sensitivity if ([[self accessClass] isEqualToString: @"PRIVATE"]) v = 2; else if ([[self accessClass] isEqualToString: @"CONFIDENTIAL"]) @@ -104,6 +106,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. else v = 0; + [s appendFormat: @"%d", v]; + categories = [self categories]; if ([categories count]) @@ -126,11 +130,20 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // It is very important here to NOT set 0 in the response, // otherwise it'll prevent WP8 phones from sync'ing. See #3028 for details. o = [o activeSyncRepresentationInContext: context]; - [s appendString: @""]; - [s appendFormat: @"%d", 1]; - [s appendFormat: @"%d", [o length]]; - [s appendFormat: @"%@", o]; - [s appendString: @""]; + + if ([[[context request] headerForKey: @"MS-ASProtocolVersion"] isEqualToString: @"2.5"]) + { + [s appendFormat: @"%@", o]; + [s appendString: @"0"]; + } + else + { + [s appendString: @""]; + [s appendFormat: @"%d", 1]; + [s appendFormat: @"%d", [o length]]; + [s appendFormat: @"%@", o]; + [s appendString: @""]; + } } return s; @@ -152,9 +165,16 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. [self setSummary: o]; // FIXME: merge with iCalEvent - if ((o = [[theValues objectForKey: @"Body"] objectForKey: @"Data"])) - [self setComment: o]; - + if ([[[context request] headerForKey: @"MS-ASProtocolVersion"] isEqualToString: @"2.5"]) + { + if ((o = [theValues objectForKey: @"Body"])) + [self setComment: o]; + } + else + { + if ((o = [[theValues objectForKey: @"Body"] objectForKey: @"Data"])) + [self setComment: o]; + } if ([[theValues objectForKey: @"Complete"] intValue] && ((o = [theValues objectForKey: @"DateCompleted"])) ) @@ -166,6 +186,11 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. [completed setDate: o]; [self setStatus: @"COMPLETED"]; } + else + { + [self setStatus: @"IN-PROCESS"]; + [self setCompleted: nil]; + } if ((o = [theValues objectForKey: @"DueDate"])) { @@ -177,6 +202,16 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. [due setDateTime: o]; } + if ((o = [theValues objectForKey: @"StartDate"])) + { + iCalDateTime *due; + + o = [o calendarDate]; + due = (iCalDateTime *) [self uniqueChildWithTag: @"dtstart"]; + [due setTimeZone: tz]; + [due setDateTime: o]; + } + // 2 == high, 1 == normal, 0 == low if ((o = [theValues objectForKey: @"Importance"])) { diff --git a/ChangeLog b/ChangeLog index ab90551ac..8c90ec341 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,592 @@ +commit f11bcd3373f39967b870f8d8f3358759a6221b62 +Author: Ludovic Marcotte +Date: Thu Mar 26 13:54:17 2015 -0400 + + Avoid using stringByReplacing... since old runtimes don't support it + +M UI/MailerUI/UIxMailEditor.m +M UI/Scheduler/UIxCalMainView.m + +commit b933f53fc2580bd5b9913f7afebcf6b2d9bedc53 +Author: Ludovic Marcotte +Date: Tue Mar 24 10:56:26 2015 -0400 + + Changes for the 2.2.17 release + +M ChangeLog +M Documentation/docinfo.xml +M Documentation/includes/global-attributes.asciidoc +M NEWS +M Version + +commit 097d5c2333515092f03ace5e64fc6824682ba974 +Author: Ludovic Marcotte +Date: Tue Mar 24 08:40:13 2015 -0400 + + now possible to configure objectClass names for LDAP groups using GroupObjectClasses (#1499) + +M Documentation/SOGoInstallationGuide.asciidoc +M NEWS +M SoObjects/SOGo/LDAPSource.h +M SoObjects/SOGo/LDAPSource.m +M SoObjects/SOGo/SOGoGroup.m +M SoObjects/SOGo/SOGoSource.h + +commit d75fb0722a0ced5bb937e5b754e95cd0261df7a7 +Author: Ludovic Marcotte +Date: Tue Mar 24 08:34:29 2015 -0400 + + Code cleanups + +M UI/Scheduler/UIxAttendeesEditor.h +M UI/Scheduler/UIxAttendeesEditor.m +M UI/Scheduler/UIxComponentEditor.h +M UI/Scheduler/UIxComponentEditor.m + +commit 26d876e67a32e6c2dc36a3a92449aa0b6638ec41 +Author: Francis Lachapelle +Date: Mon Mar 23 16:28:32 2015 -0400 + + Improve event attendees editor + + - Avoid repetitive AJAX queries; + - Improve handling of daylight changes. + +M UI/WebServerResources/JavascriptAPIExtensions.js +M UI/WebServerResources/UIxAttendeesEditor.js + +commit cc3652b59b40bcb420d806412277509ac247d85d +Author: Ludovic Marcotte +Date: Mon Mar 23 17:23:29 2015 -0400 + + improvements to EAS SyncKey handling to avoid missing mails (#3048, #3058) + +M ActiveSync/SOGoActiveSyncDispatcher+Sync.m +M NEWS + +commit 9d7bbbc312cbe8ccb535e24308be111a3a2eb710 +Author: Ludovic Marcotte +Date: Mon Mar 23 14:55:14 2015 -0400 + + enable by default HTML mails support using EAS on Windows and BB phones + +M ActiveSync/SOGoActiveSyncDispatcher.m +M NEWS + +commit d2915add6d69a8fd05d15bb8c2ed979fe6ad9026 +Author: Ludovic Marcotte +Date: Mon Mar 23 14:08:31 2015 -0400 + + now favor login@domain as the default email address if multiple mail: fields are specified + +M NEWS +M SoObjects/SOGo/SOGoUser.h +M SoObjects/SOGo/SOGoUser.m + +commit 67139ce3e3f69160a683d8c11bee439be202a84e +Author: Ludovic Marcotte +Date: Mon Mar 23 11:35:45 2015 -0400 + + improved GetItemEstimate to count all vasnished/deleted mails too + +M ActiveSync/SOGoActiveSyncDispatcher.m +M NEWS + +commit 9eb138b867ebd0a51b9a649891422bac70406510 +Author: Ludovic Marcotte +Date: Mon Mar 23 10:54:36 2015 -0400 + + prevent potential freebusy lookup crashes during timezone changes with repetitive events + +M NEWS +M UI/MainUI/SOGoUserHomePage.m + +commit baf3d1f986d48d4fa37a076ccf555d75f4d5dbda +Author: Ludovic Marcotte +Date: Fri Mar 20 09:10:49 2015 -0400 + + immediately delete mails from EAS clients when they are marked as deleted on the IMAP server + +M NEWS +M SoObjects/Mailer/SOGoMailFolder.m + +commit ecd336fa5c6513ee8533dae05e5a328c81527a38 +Author: Ludovic Marcotte +Date: Fri Mar 20 08:51:35 2015 -0400 + + Fix compilation warning and error + +M ActiveSync/NGMimeMessage+ActiveSync.m +M ActiveSync/SOGoMailObject+ActiveSync.m + +commit afd73289d1ebb13c643f7a8290eac7da43b69330 +Author: Ludovic Marcotte +Date: Fri Mar 20 08:45:42 2015 -0400 + + properly fallback over EAS to UTF-8 and then Latin1 for messages w/o charset (#3103) + +M ActiveSync/SOGoMailObject+ActiveSync.m +M NEWS + +commit bff21ab1ff136ada7d93cc529a87da9dd254fa60 +Author: Ludovic Marcotte +Date: Thu Mar 19 16:08:33 2015 -0400 + + support for mail prority using EAS + +M ActiveSync/SOGoMailObject+ActiveSync.m +M NEWS + +commit 6d99226b1390d2dce1f7dfc6a3bab5143aad6364 +Author: Ludovic Marcotte +Date: Thu Mar 19 14:53:01 2015 -0400 + + Always add DisplayName no matter EAS client version + +M ActiveSync/SOGoMailObject+ActiveSync.m + +commit 69974807b5740f245376f8e19f71559997bcae44 +Author: Francis Lachapelle +Date: Thu Mar 19 08:32:37 2015 -0400 + + Fix Language-Region tags in Web interface + + Fixes #3121 + +M NEWS +M UI/MailerUI/UIxMailEditor.m +M UI/Scheduler/UIxCalMainView.m + +commit cdb766e2eff3b6e420da48b3f396814edff96b4f +Author: Ludovic Marcotte +Date: Wed Mar 18 10:15:33 2015 -0400 + + Documented NGMimeBuildMimeTempDirectory + +M Documentation/SOGoInstallationGuide.asciidoc + +commit e25a7e3d8d0a5f11a65a6fa337a637b3e1f44432 +Author: Ludovic Marcotte +Date: Wed Mar 18 09:36:35 2015 -0400 + + Fix for bug #3138 + +M ActiveSync/NGMimeMessage+ActiveSync.m +M ActiveSync/SOGoActiveSyncDispatcher.m +M NEWS + +commit 1d5ae27c996f63488653d3a8638b33320540741c +Author: Francis Lachapelle +Date: Tue Mar 17 15:31:35 2015 -0400 + + Fix issues with freebusy (Web, busyOffHours) + +M NEWS +M SoObjects/Appointments/SOGoFreeBusyObject.m +M UI/WebServerResources/UIxAttendeesEditor.js + +commit 1bdd5dfc9085e1f08f839b8179a7ccb37b038e5c +Author: Jens Erat +Date: Fri Mar 13 14:06:58 2015 +0100 + + Remove obsolete archive string from translations + +M UI/MailerUI/Arabic.lproj/Localizable.strings +M UI/MailerUI/Danish.lproj/Localizable.strings +M UI/MailerUI/Icelandic.lproj/Localizable.strings +M UI/MailerUI/Italian.lproj/Localizable.strings +M UI/MailerUI/NorwegianNynorsk.lproj/Localizable.strings +M UI/MailerUI/Swedish.lproj/Localizable.strings +M UI/MailerUI/Ukrainian.lproj/Localizable.strings +M UI/MailerUI/Welsh.lproj/Localizable.strings + +commit 37fbd75d552873575f11853e4478d6dce9640201 +Author: Jens Erat +Date: Fri Mar 13 11:41:07 2015 +0100 + + Rename "Archive" to "Export" in Webmail, ctd. + + Fixes #2758 also for non-Inbox folders + +M UI/Templates/MailerUI/UIxMailMainFrame.wox + +commit f1fda8bd0617d5e6b924bb1ae57fb7840bbcc13f +Author: Francis Lachapelle +Date: Tue Mar 10 13:27:17 2015 -0400 + + Return a single phone number in JSON contacts list + +M SoObjects/Contacts/SOGoContactSourceFolder.m + +commit 517f4b0f49e2545f930408b922dabc1a4a9006eb +Author: Francis Lachapelle +Date: Mon Mar 9 14:04:21 2015 -0400 + + Fix possible JS exception in Contacts module + +M NEWS + +commit cbba0719bb58229d9be8d3bb5af5fba23da3ae14 +Author: Francis Lachapelle +Date: Wed Mar 4 10:05:12 2015 -0500 + + Fix exception in SOGoUserManager + +M SoObjects/SOGo/SOGoUserManager.m + +commit 8e0849029ea93bfc9e1f9e2cb4ff2e0e5b6a4308 +Author: Francis Lachapelle +Date: Tue Mar 3 21:18:46 2015 -0500 + + Fix contacts lookup by UID + + When looking for a specific contact UID, we no longer match a pattern + that could return multiple results. We search for the exact UID only. + +M NEWS +M SoObjects/Appointments/SOGoFreeBusyObject.m +M SoObjects/SOGo/SOGoUserManager.h +M SoObjects/SOGo/SOGoUserManager.m + +commit 61923225136db72ceb89294d504b65a7f62e7a7f +Author: Francis Lachapelle +Date: Tue Mar 3 20:59:32 2015 -0500 + + Fix some gcc warnings + +M SoObjects/SOGo/SOGoCASSession.m +M SoObjects/SOGo/SOGoUser.m +M SoObjects/SOGo/SOGoUserManager.m + +commit df59b73a221467523c18c56493c13c86003d7271 +Author: Ludovic Marcotte +Date: Thu Feb 26 17:56:42 2015 -0500 + + use correct mail attachment elements for EAS 2.5 clients - patch from tfu + +M ActiveSync/SOGoMailObject+ActiveSync.m +M NEWS + +commit a5672fda5210e6149bd893683c7ee4338f0c3cf9 +Author: Ludovic Marcotte +Date: Thu Feb 26 17:55:36 2015 -0500 + + do not use syncKey from cache when davCollectionTag = -1 - patch from tfu + +M ActiveSync/SOGoActiveSyncDispatcher+Sync.m +M NEWS + +commit b593ddc6436e0d4aff2ef24e6ff9b3cda0c0c396 +Author: Ludovic Marcotte +Date: Thu Feb 26 17:53:58 2015 -0500 + + SmartReply improvements for missing body attributes - patch from tfu + +M ActiveSync/NSString+ActiveSync.h +M ActiveSync/NSString+ActiveSync.m +M ActiveSync/SOGoActiveSyncDispatcher.m +M NEWS + +commit 38d61f720f0c9352619fa2cb214e81734ed05868 +Author: Ludovic Marcotte +Date: Thu Feb 26 17:52:37 2015 -0500 + + use the correct body element for events for EAS 2.5 clients - patch from tfu + +M ActiveSync/iCalEvent+ActiveSync.m +M NEWS + +commit 733e334726824be7ab3a41ca7d4b885550a18f9a +Author: Ludovic Marcotte +Date: Thu Feb 26 17:51:02 2015 -0500 + + fixed tasks disappearing issue with RoadSync - patch from tfu + +M ActiveSync/iCalToDo+ActiveSync.m +M NEWS + +commit 22888ba5ac2cdc8d52d4c8eba9a94936376b7520 +Author: Ludovic Marcotte +Date: Thu Feb 26 17:49:26 2015 -0500 + + use the correct mail body element for EAS 2.5 clients - patch from tfu + +M ActiveSync/SOGoMailObject+ActiveSync.m +M NEWS + +commit 33c485d2f6d58443c0c67297b87d8cc3d67d1398 +Author: Ludovic Marcotte +Date: Thu Feb 26 17:48:06 2015 -0500 + + fixed empty sync responses for EAS 2.5 and 12.0 clients - patch from tfu + +M ActiveSync/SOGoActiveSyncDispatcher+Sync.m +M NEWS + +commit 9160e34134c1c24f5f51cd6c9081723f0963e7c5 +Author: Ludovic Marcotte +Date: Thu Feb 26 17:46:34 2015 -0500 + + multiple collections support for GetItemEstimate - patch from tfu + +M ActiveSync/SOGoActiveSyncDispatcher.m +M NEWS + +commit 5ceddc213ec08ccb5b44b07aae47921a19b36fd8 +Author: Ludovic Marcotte +Date: Thu Feb 26 16:00:03 2015 -0500 + + Fix for bug #3116 + +M ActiveSync/SOGoMailObject+ActiveSync.m +M NEWS + +commit b97002565e3d21b417f99b704641abee1052642c +Author: Ludovic Marcotte +Date: Wed Feb 25 08:42:18 2015 -0500 + + Updated NEWS file regarding previous commit + +M NEWS + +commit 8d21bc3e36014d63db0e37bf7121d81c8200b5e1 +Author: Jesús García Sáez +Date: Wed Feb 25 12:01:31 2015 +0100 + + Fix change password with DomainBasedUID + + When users use full domain to login (SOGoEnableDomainBasedUID) the + user attributes in the cache were not being properly updated because + in this case the key is `uid@domain` instead of just `uid`. + +M SoObjects/SOGo/SOGoUserManager.m + +commit b26ea30b1b794e983ea272b5de713b0205fda0d8 +Author: Ludovic Marcotte +Date: Thu Feb 12 11:06:57 2015 -0500 + + Update ChangeLog + +M ChangeLog + +commit 72b210cd2ebccc8e9cdc13c23abb76b7f455b780 +Author: Ludovic Marcotte +Date: Thu Feb 12 10:29:46 2015 -0500 + + Fixed typo + +M NEWS + +commit a80d75d09cd0b6f310455e33847ae62433e258ca +Author: Ludovic Marcotte +Date: Thu Feb 12 10:27:10 2015 -0500 + + Preparation for the release + +M Documentation/docinfo.xml +M Documentation/includes/global-attributes.asciidoc +M NEWS +M Version + +commit aae76dc4e6ce1909a1aeaee9500ec65e8e67aec8 +Author: Ludovic Marcotte +Date: Thu Feb 12 09:57:10 2015 -0500 + + Limit curl request for webcalendars to 60 seconds + +M SoObjects/Appointments/SOGoWebAppointmentFolder.m + +commit c20ca0e33dd3e25ba41f7b2a2cd70a1c39d1e125 +Author: Francis Lachapelle +Date: Thu Feb 12 09:29:40 2015 -0500 + + Fix exception in set operation of sogo-tool + +M NEWS +M Tools/SOGoToolUserPreferences.m + +commit 601a9ca5598490f839938884f20d0a366388ed4b +Author: Francis Lachapelle +Date: Thu Feb 12 09:09:18 2015 -0500 + + Update translations + +M NEWS +M UI/PreferencesUI/Czech.lproj/Localizable.strings +M UI/PreferencesUI/Finnish.lproj/Localizable.strings +M UI/PreferencesUI/French.lproj/Localizable.strings +M UI/PreferencesUI/German.lproj/Localizable.strings +M UI/PreferencesUI/Hungarian.lproj/Localizable.strings + +commit d7c283f7511c7753f2cf0fc5af0e2eaea826b66f +Author: Ludovic Marcotte +Date: Thu Feb 12 08:49:17 2015 -0500 + + improved fetching of text parts over EAS + +M ActiveSync/SOGoMailObject+ActiveSync.m +M NEWS + +commit f3325b8b0d59a730716090b8f5e1319ce3edfd70 +Author: Ludovic Marcotte +Date: Wed Feb 11 18:13:36 2015 -0500 + + Updated NEWS file wrt fixes + +M NEWS + +commit b4ea4d7e9fd90520675452bdcbf70155d6b87eef +Author: Ludovic Marcotte +Date: Wed Feb 11 14:39:57 2015 -0500 + + Improvement over previous commit + +M UI/Templates/PreferencesUI/UIxPreferences.wox +M UI/WebServerResources/UIxPreferences.js + +commit 9447437d6fd9da2701312363ae92427d2404749a +Author: Ludovic Marcotte +Date: Wed Feb 11 14:33:32 2015 -0500 + + Added missing strings from previous commit + +M UI/PreferencesUI/English.lproj/Localizable.strings + +commit 322f72626a3253f9d290cfb6d83ff8f5fac2a4e4 +Author: Ludovic Marcotte +Date: Wed Feb 11 14:30:40 2015 -0500 + + now possible to limit automatic forwards to internal/external domains + +M Documentation/SOGoInstallationGuide.asciidoc +M NEWS +M SoObjects/SOGo/SOGoDomainDefaults.h +M SoObjects/SOGo/SOGoDomainDefaults.m +M UI/PreferencesUI/UIxPreferences.m +M UI/Templates/PreferencesUI/UIxPreferences.wox +M UI/WebServerResources/UIxPreferences.js + +commit 55ae4cb8c0390f2007ea08896e1adcb722e72c10 +Author: Ludovic Marcotte +Date: Wed Feb 11 12:59:04 2015 -0500 + + First pass at EAS' best practices for SOGo + +M Documentation/SOGoInstallationGuide.asciidoc +M NEWS + +commit e5d01428ad074d45ed8719b3aaac5d09537faba4 +Author: Ludovic Marcotte +Date: Wed Feb 11 11:31:35 2015 -0500 + + now possible for SOGo to change the sambaNTPassword/sambaLMPassword + +M Documentation/SOGoInstallationGuide.asciidoc +M NEWS +M SoObjects/SOGo/GNUmakefile +M SoObjects/SOGo/LDAPSource.h +M SoObjects/SOGo/LDAPSource.m +M SoObjects/SOGo/NSData+Crypto.h +M SoObjects/SOGo/NSData+Crypto.m +M SoObjects/SOGo/NSString+Crypto.h +M SoObjects/SOGo/NSString+Crypto.m +A SoObjects/SOGo/lmhash.c +A SoObjects/SOGo/lmhash.h +M Tools/SOGoTool.h +M Tools/SOGoTool.m + +commit 82178fd6ceb5c4cb9acb10a4be06c022c895de7a +Author: Ludovic Marcotte +Date: Wed Feb 11 08:31:32 2015 -0500 + + Initial patch from tfu for bug #3055 + +M ActiveSync/SOGoMailObject+ActiveSync.m + +commit 6cf1ec998e33990e77725b9c84bb3ba7ec34dcbc +Author: Ludovic Marcotte +Date: Tue Feb 10 07:09:31 2015 -0500 + + Updated NEWS file for the #3095 bug fix in SOPE + +M NEWS + +commit 6c9b1a494fa1f0a44b118f6c4a399e958796704d +Author: Ludovic Marcotte +Date: Mon Feb 9 16:12:59 2015 -0500 + + Fix for bug #2332 + +M NEWS +M SoObjects/SOGo/SOGoSieveManager.m +M UI/PreferencesUI/English.lproj/Localizable.strings +M UI/PreferencesUI/UIxPreferences.m +M UI/Templates/PreferencesUI/UIxPreferences.wox + +commit 98e4c8ca55b0424111bd7c98060f689316008572 +Author: Ludovic Marcotte +Date: Mon Feb 9 09:10:55 2015 -0500 + + Fixed doc regarder domain vs. system settings + +M Documentation/SOGoInstallationGuide.asciidoc + +commit 3731819e1565a6198237218dff078047628b1316 +Author: Ludovic Marcotte +Date: Thu Feb 5 16:21:27 2015 -0500 + + Added patch from tfu for #2995 + +M ActiveSync/SOGoMailObject+ActiveSync.m +M Documentation/SOGoInstallationGuide.asciidoc +M NEWS + +commit eca22caefc921fa713c2b83e24831f52b397eca7 +Author: Ludovic Marcotte +Date: Thu Feb 5 11:14:53 2015 -0500 + + Fix for bug #3078 + +M NEWS +M UI/MailerUI/UIxMailFolderActions.m + +commit 923bcf8ae291f574feb611d80c8b5a861dcb663a +Author: Ludovic Marcotte +Date: Thu Feb 5 09:29:29 2015 -0500 + + Applied patch for #3092 + +M SOPE/GDLContentStore/GCSChannelManager.m + +commit 0b44fa81f4c1ec9c5b812d799bf76399ce240b5b +Author: Francis Lachapelle +Date: Tue Feb 3 13:20:57 2015 -0500 + + Update NEWS file + +M NEWS + +commit 776b17ff202c45ff63bfc5144dd0ccc37a2f3e58 +Author: Francis Lachapelle +Date: Tue Feb 3 12:06:28 2015 -0500 + + Fix mail editor when not called from mail module + + Fixes #3088 + +M UI/WebServerResources/UIxMailEditor.js + +commit 2aa528dacca6c61d194bd2603cf0ba0d5a436647 +Author: Ludovic Marcotte +Date: Fri Jan 30 11:08:43 2015 -0500 + + Preparation for the release + +M ChangeLog +M Documentation/docinfo.xml +M Documentation/includes/global-attributes.asciidoc +M Version + commit d1d398091961f5d497b67313e098a8a5624089f4 Author: Francis Lachapelle Date: Fri Jan 30 11:03:38 2015 -0500 diff --git a/Documentation/SOGoInstallationGuide.asciidoc b/Documentation/SOGoInstallationGuide.asciidoc index e88affe42..e74cf48b0 100644 --- a/Documentation/SOGoInstallationGuide.asciidoc +++ b/Documentation/SOGoInstallationGuide.asciidoc @@ -1003,6 +1003,14 @@ this feature, every LDAP requests will fail. Note that some LDAP servers require LDAP/SSL for password policies to work. This is the case for example with 389 Directory Server. +|updateSambaNTLMPasswords +|If set to `YES`, SOGo will automatically update the sambaNTPassword +and sambaLMPassword attributes when changing passwords. The attributes +must be called sambaNTPassword and sambaLMPassword. You must also make +sure the correct ACL is set in your LDAP server to allow users to change +their own sambaNTPassword and sambaLMPassword password attributes. +Defaults to `NO` when unset. + |isAddressBook |If set to `YES`, this LDAP source is used as a shared address book (with read-only access). Note that if set to `NO`, autocompletion will @@ -1040,6 +1048,11 @@ of supported attributes. user addressbooks (see _abOU_ below), this list of object classes will be applied to new records as they are created. +|GroupObjectClasses +|A list (array) of names identifying groups within the LDAP source. If not +set, SOGo will use `group`, `groupofnames`, `groupofuniquenames` +and `posixgroup`. + |modifiers |A list (array) of usernames that are authorized to perform modifications to the address book defined by this LDAP source. @@ -1329,27 +1342,27 @@ The following table describes the parameters that were set: [cols="3,47,50a"] |======================================================================= -|D |SOGoProfileURL +|S |SOGoProfileURL |Parameter used to set the database URL so that SOGo can retrieve user profiles. For MySQL, set the database URL to something like: `mysql://sogo:sogo@localhost:3306/sogo/sogo_user_profile`. -|D |OCSFolderInfoURL +|S |OCSFolderInfoURL |Parameter used to set the database URL so that SOGo can retrieve the location of user folders (address books and calendars). For Oracle, set the database URL to something like: `oracle://sogo:sogo@localhost:1526/sogo/sogo_folder_info`. -|D |OCSSessionsFolderURL +|S |OCSSessionsFolderURL |Parameter used to set the database URL so that SOGo can store and retrieve secured user sessions information. For PostgreSQL, the database URL could be set to something like: `postgresql://sogo:sogo@localhost:5432/sogo/sogo_sessions_folder`. -|D |OCSEMailAlarmsFolderURL +|S |OCSEMailAlarmsFolderURL |Parameter used to set the database URL for email-based alarms (that can be set on events and tasks). This parameter is relevant only if _SOGoEnableEMailAlarms_ is set to `YES`. For PostgreSQL, the database @@ -1681,6 +1694,13 @@ cronjob `sogo-tmpwatch`. Defaults to `/var/spool/sogo`. +|S |NGMimeBuildMimeTempDirectory +|Parameter used to set the path where temporary files will be stored +by SOPE when dealing with MIME messages. + +Defaults to `/tmp`. + + |S |NGImap4DisableIMAP4Pooling |Disables IMAP pooling when set to `YES`. Enable pooling by setting to `NO` or using a caching proxy like imapproxy. @@ -1784,6 +1804,12 @@ host. Defaults to `NO` when unset. +|D |SOGoForwardConstraints +|Parameter used to set constraints on possible addresses used when +automatically forwarding mails. When set to `0` (default), no constraint +is enforced. When set to `1`, only internal domains can be used. When +set to `2`, only external domains can be used. + |D |SOGoSieveScriptsEnabled |Parameter used to activate the edition from the preferences windows of server-side mail filters. Requires Sieve script support on the IMAP @@ -2346,8 +2372,8 @@ easier. These GUI utilities can make use of templates to create and pre-configure typical user accounts or any standardized LDAP record, along with the correct object classes, fields and default values. -Microsoft ActiveSync --------------------- +Microsoft Enterprise ActiveSync +------------------------------- SOGo supports the Microsoft ActiveSync protocol. @@ -2421,6 +2447,10 @@ Please be aware of the following limitations: solution is to configure Outlook to use a LDAP server (over SSL) with authentication. Outlook 2013 also does not seem to support multiple address books over ActiveSync. +* To successfully synchronize Outlook email categories, a corresponding +mail label (Preferences->Mail Options) has to be created manually in SOGo +for each label defined in Outlook. The name in SOGo and in Outlook must be +identical. * Make sure you do not use a self-signed certificate. While this will work, Outlook will work intermittently as it will raise popups for certificate validation, sometimes in background, preventing the user to @@ -2457,6 +2487,83 @@ and send an email to iplicreq@microsoft.com Inverse inc. provides this software for free, but is not responsible for anything related to its usage. +Microsoft Enterprise ActiveSync Tuning +-------------------------------------- + +First of all, it is important to know that most EAS devices will keep +HTTP connections open to SOGo (and thus, Apache) for a long time. This +is required for "push" to work properly. Connections can stay open for +up to one hour, or 3600 seconds. + +The first parameter to check is related to Apache's proxying to +SOGo: + +---- +ProxyPass /Microsoft-Server-ActiveSync \ + http://127.0.0.1:20000/SOGo/Microsoft-Server-ActiveSync \ + retry=60 connectiontimeout=5 timeout=360 +---- + +The above line sets a timeout for up to 360 seconds, or 6 minutes. If +you want to let EAS clients keep their HTTP connections open for up +to an hour, you must change the timeout parameter and set it to 3600. + +If you change this value, the WOWatchDogRequestTimeout parameter must be changed +accordingly in SOGo's configuration file (/etc/sogo/sogo.conf). By default, +a SOGo child process is allowed to handle a request that can take up +to 10 minutes before it gets killed by its parent process. When using +EAS "push", the client expects to keep its connection open for up to one +hour - so the WOWatchDogRequestTimeout, which is set in minutes, +must be adjusted accordingly. + +EAS clients will keep HTTP connections open for a long time +during these two EAS commands: Ping and Sync. By default, SOGo will prevent +EAS clients from keeping connections for a long time. This is to avoid the +situation where all SOGo child processes would be monopolized by EAS clients - +rendering the SOGo web interface or DAV interface unavailable. The +default SOGo behavior is thus similar to disable EAS push entirely. + +Two SOGo configuration parameters are available to modify this behavior: +SOGoMaximumPingInterval (set by default to 10 seconds) and +SOGoMaximumSyncInterval (set by default to 30 seconds). If you want +connection to stay open for up to one hour, you should set these +slightly under 3600 seconds (say 3540 - or 59 minutes). During a +long-lived HTTP connection, the SOGo child process will perform +internal polling to detect changes and return them to the EAS client +if any changes are found. The parameter used to control this +is SOGoInternalSyncInterval. By default, polling is done every 10 +seconds. This might generate too much load on large-scale system. + +The last configuration parameter to adjust is WOWorkersCount - which sets the +number of SOGo child process that will be used to handle requests. +You should have at least one child per EAS device configured to use +"push". You must also have more children than you have EAS devices +configured to use "push" - in order to handle normal SOGo requests to +its Web or DAV interfaces. + +Here are some usage examples for EAS devices using "push". In all +cases, the Apache timeout is set to 3600 and the +WOWatchDogRequestTimeout parameter is set to 60. + +Example 1 - 100 users, 10 EAS devices: + +---- +WOWorkersCount = 15; +SOGoMaximumPingInterval = 3540; +SOGoMaximumSyncInterval = 3540; +SOGoInternalSyncInterval = 30; +---- + +Example 2 - 1000 users, 100 EAS devices: + +---- +WOWorkersCount = 120; +SOGoMaximumPingInterval = 3540; +SOGoMaximumSyncInterval = 3540; +SOGoInternalSyncInterval = 60; +---- + + Using SOGo ---------- diff --git a/Documentation/docinfo.xml b/Documentation/docinfo.xml index 00fc54948..5a83383db 100644 --- a/Documentation/docinfo.xml +++ b/Documentation/docinfo.xml @@ -1,7 +1,7 @@ -Version 2.2.15 - January 2015 -for version 2.2.15 -2015-01-30 +Version 2.2.17a - March 2015 +for version 2.2.17a +2015-03-26 Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.2 or any later version published by the Free Software Foundation; with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. A copy of the license is included in the section entitled "GNU Free Documentation License". diff --git a/Documentation/includes/global-attributes.asciidoc b/Documentation/includes/global-attributes.asciidoc index 0cbd771ae..815d94e72 100644 --- a/Documentation/includes/global-attributes.asciidoc +++ b/Documentation/includes/global-attributes.asciidoc @@ -13,6 +13,6 @@ // TODO have the build system take care of this -:release_version: 2.2.15 +:release_version: 2.2.17a // vim: set syntax=asciidoc tabstop=2 shiftwidth=2 expandtab: diff --git a/NEWS b/NEWS index 38c6e8f3e..1644f8b71 100644 --- a/NEWS +++ b/NEWS @@ -20,6 +20,65 @@ Bug fixes - Fix exception modifications import in recurrence series - Fix server side crash parsing rtf emails with images (with word97 format) +2.2.17a (2014-03-15) +-------------------- + +Bug fixes + - avoid calling -stringByReplacingOccurrencesOfString:... for old GNUstep runtime + +2.2.17 (2015-03-24) +------------------- + +Enhancements + - support for mail prority using EAS + - immediately delete mails from EAS clients when they are marked as deleted on the IMAP server + - now favor login@domain as the default email address if multiple mail: fields are specified + - enable by default HTML mails support using EAS on Windows and BB phones + - now possible to configure objectClass names for LDAP groups using GroupObjectClasses (#1499) + +Bug fixes + - fixed login issue after password change (#2601) + - fixed potential encoding issue using EAS and 8-bit mails (#3116) + - multiple collections support for GetItemEstimate using EAS + - fixed empty sync responses for EAS 2.5 and 12.0 clients + - use the correct mail body element for EAS 2.5 clients + - fixed tasks disappearing issue with RoadSync + - use the correct body element for events for EAS 2.5 clients + - SmartReply improvements for missing body attributes + - do not use syncKey from cache when davCollectionTag = -1 + - use correct mail attachment elements for EAS 2.5 clients + - fixed contacts lookup by UID in freebusy + - reduced telephone number to a single value in JSON response of contacts list + - fixed freebusy data when 'busy off hours' is enabled and period starts during the weekend + - fixed fetching of freebusy data from the Web interface + - fixed EAS handling of Bcc in emails (#3138) + - fixed Language-Region tags in Web interface (#3121) + - properly fallback over EAS to UTF-8 and then Latin1 for messages w/o charset (#3103) + - prevent potential freebusy lookup crashes during timezone changes with repetitive events + - improved GetItemEstimate to count all vasnished/deleted mails too + - improvements to EAS SyncKey handling to avoid missing mails (#3048, #3058) + +2.2.16 (2015-02-12) +------------------- + +New features + - now possible for SOGo to change the sambaNTPassword/sambaLMPassword + - now possible to limit automatic forwards to internal/external domains + +Enhancements + - added support for email categories using EAS (#2995) + - now possible to always send vacation messages (#2332) + - added EAS best practices to the documentation + - improved fetching of text parts over EAS + - updated Czech, Finnish, French, German and Hungarian translations + +Bug fixes + - (regression) fixed sending a message when mail module is not active (#3088) + - mail labels with blanks are not handled correctly (#3078) + - fixed BlackBerry issues sending multiple mails over EAS (#3095) + - fixed plain/text mails showing on one line on Android/EAS (#3055) + - fixed exception in sogo-tool when parsing arguments of a set operation + 2.2.15-zentyal2 (2015-03-16) ---------------------------- diff --git a/SOPE/GDLContentStore/GCSChannelManager.m b/SOPE/GDLContentStore/GCSChannelManager.m index b21b230a6..d04d70446 100644 --- a/SOPE/GDLContentStore/GCSChannelManager.m +++ b/SOPE/GDLContentStore/GCSChannelManager.m @@ -240,7 +240,7 @@ static NSTimeInterval ChannelCollectionTimer = 5 * 60; handle = currentHandle; else if (debugPools) [self logWithFormat: @"DBPOOL: cannot use handle (%@ vs %@) ", - [_url absoluteString], [handle->url absoluteString]]; + [_url absoluteString], [currentHandle->url absoluteString]]; return handle; } diff --git a/SoObjects/Appointments/SOGoFreeBusyObject.m b/SoObjects/Appointments/SOGoFreeBusyObject.m index 6624a2cfe..1c6e55662 100644 --- a/SoObjects/Appointments/SOGoFreeBusyObject.m +++ b/SoObjects/Appointments/SOGoFreeBusyObject.m @@ -62,17 +62,14 @@ SOGoUserManager *um; NSString *domain; NSDictionary *contactInfos; - NSArray *contacts; um = [SOGoUserManager sharedUserManager]; contactInfos = [um contactInfosForUserWithUIDorEmail: uid]; if (contactInfos == nil) { + // Search among global addressbooks domain = [[context activeUser] domain]; - [um fetchContactsMatching: uid inDomain: domain]; - contacts = [um fetchContactsMatching: uid inDomain: domain]; - if ([contacts count] == 1) - contactInfos = [contacts lastObject]; + contactInfos = [um fetchContactWithUID: uid inDomain: domain]; } /* iCal.app compatibility: @@ -279,7 +276,6 @@ if ([uid length]) { SOGoUserManager *um; - NSArray *contacts; NSString *domain, *email; NSDictionary *contact; MSExchangeFreeBusy *exchangeFreeBusy; @@ -287,10 +283,9 @@ um = [SOGoUserManager sharedUserManager]; domain = [[context activeUser] domain]; - contacts = [um fetchContactsMatching: uid inDomain: domain]; - if ([contacts count] == 1) + contact = [um fetchContactWithUID: uid inDomain: domain]; + if (contact) { - contact = [contacts lastObject]; email = [contact valueForKey: @"c_email"]; source = [contact objectForKey: @"source"]; if ([email length] @@ -386,8 +381,7 @@ ([currentStartDate compare: startDate] == NSOrderedAscending)? startDate : currentStartDate, @"startDate", currentEndDate, @"endDate", nil]]; - if (!firstRange - && currentEndDate != endDate + if (currentEndDate != endDate && ([currentEndDate dayOfWeek] == 6 || [currentEndDate dayOfWeek] == 0)) { // Fill weekend days diff --git a/SoObjects/Appointments/SOGoWebAppointmentFolder.m b/SoObjects/Appointments/SOGoWebAppointmentFolder.m index 3bd6c4206..cb4c7543c 100644 --- a/SoObjects/Appointments/SOGoWebAppointmentFolder.m +++ b/SoObjects/Appointments/SOGoWebAppointmentFolder.m @@ -1,6 +1,6 @@ /* SOGoWebAppointmentFolder.m - this file is part of SOGo * - * Copyright (C) 2009-2014 Inverse inc. + * Copyright (C) 2009-2015 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 @@ -137,6 +137,7 @@ size_t curl_body_function(void *ptr, size_t size, size_t nmemb, void *buffer) curl_easy_setopt (curl, CURLOPT_URL, [location UTF8String]); curl_easy_setopt (curl, CURLOPT_SSL_VERIFYPEER, 0L); curl_easy_setopt (curl, CURLOPT_SSL_VERIFYHOST, 0L); + curl_easy_setopt (curl, CURLOPT_TIMEOUT, 60L); authInfos = [self _loadAuthData]; if (authInfos) diff --git a/SoObjects/Contacts/SOGoContactSourceFolder.m b/SoObjects/Contacts/SOGoContactSourceFolder.m index 19e726a96..e385d9c09 100644 --- a/SoObjects/Contacts/SOGoContactSourceFolder.m +++ b/SoObjects/Contacts/SOGoContactSourceFolder.m @@ -267,6 +267,13 @@ data = [oldRecord objectForKey: @"homephone"]; if (![data length]) data = @""; + else if ([data isKindOfClass: [NSArray class]]) + { + if ([data count] > 0) + data = [data objectAtIndex: 0]; + else + data = @""; + } [newRecord setObject: data forKey: @"c_telephonenumber"]; // Custom attribute for group-lookups. See LDAPSource.m where diff --git a/SoObjects/Mailer/SOGoMailFolder.m b/SoObjects/Mailer/SOGoMailFolder.m index d83ca78a6..c65c6c43c 100644 --- a/SoObjects/Mailer/SOGoMailFolder.m +++ b/SoObjects/Mailer/SOGoMailFolder.m @@ -2129,13 +2129,13 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data) value: nextModseq]; searchQualifier = [[EOAndQualifier alloc] initWithQualifiers: - kvQualifier, [self _nonDeletedQualifier], nil]; + kvQualifier, nil]; [kvQualifier release]; [searchQualifier autorelease]; } else { - searchQualifier = [self _nonDeletedQualifier]; + searchQualifier = nil; } if (theStartDate) @@ -2144,7 +2144,7 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data) sinceDateQualifier = [EOQualifier qualifierWithQualifierFormat: @"(DATE >= %@)", theStartDate]; - searchQualifier = [[EOAndQualifier alloc] initWithQualifiers: searchQualifier, sinceDateQualifier, + searchQualifier = [[EOAndQualifier alloc] initWithQualifiers: sinceDateQualifier, searchQualifier, nil]; [searchQualifier autorelease]; } @@ -2155,7 +2155,7 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data) sortOrdering: nil]; fetchResults = [(NSDictionary *)[self fetchUIDs: uids - parts: [NSArray arrayWithObject: @"modseq"]] + parts: [NSArray arrayWithObjects: @"modseq", @"flags", nil]] objectForKey: @"fetch"]; /* NOTE: we sort items manually because Cyrus does not properly sort @@ -2166,7 +2166,7 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data) for (i = 0; i < [fetchResults count]; i++) { - d = [NSDictionary dictionaryWithObject: [[fetchResults objectAtIndex: i] objectForKey: @"modseq"] + d = [NSDictionary dictionaryWithObject: ([[[fetchResults objectAtIndex: i] objectForKey: @"flags"] containsObject: @"deleted"]) ? [NSNull null] : [[fetchResults objectAtIndex: i] objectForKey: @"modseq"] forKey: [[[fetchResults objectAtIndex: i] objectForKey: @"uid"] stringValue]]; [allTokens addObject: d]; } diff --git a/SoObjects/SOGo/GNUmakefile b/SoObjects/SOGo/GNUmakefile index 70b458001..4e506d166 100644 --- a/SoObjects/SOGo/GNUmakefile +++ b/SoObjects/SOGo/GNUmakefile @@ -77,11 +77,10 @@ SOGo_HEADER_FILES = \ WORequest+SOGo.h \ WOResourceManager+SOGo.h \ WOResponse+SOGo.h \ - WOContext+SOGo.h \ + WOContext+SOGo.h \ \ SOGoCredentialsFile.h -# daemon tool all:: @touch SOGoBuild.m @@ -156,10 +155,11 @@ SOGo_OBJC_FILES = \ WORequest+SOGo.m \ WOResourceManager+SOGo.m \ WOResponse+SOGo.m \ - WOContext+SOGo.m \ + WOContext+SOGo.m \ \ SOGoCredentialsFile.m +SOGo_C_FILES = lmhash.c SOGo_RESOURCE_FILES = \ SOGoDefaults.plist \ diff --git a/SoObjects/SOGo/LDAPSource.h b/SoObjects/SOGo/LDAPSource.h index 982c39543..3877ec4ee 100644 --- a/SoObjects/SOGo/LDAPSource.h +++ b/SoObjects/SOGo/LDAPSource.h @@ -1,6 +1,6 @@ /* LDAPSource.h - this file is part of SOGo * - * Copyright (C) 2007-2014 Inverse inc. + * Copyright (C) 2007-2015 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 @@ -72,12 +72,14 @@ NSDictionary *contactMapping; NSArray *contactObjectClasses; + NSArray *groupObjectClasses; NSDictionary *modulesConstraints; NSMutableArray *searchAttributes; BOOL passwordPolicy; + BOOL updateSambaNTLMPasswords; /* resources handling */ NSString *kindField; @@ -105,6 +107,7 @@ UIDField: (NSString *) newUIDField mailFields: (NSArray *) newMailFields searchFields: (NSArray *) newSearchFields +groupObjectClasses: (NSArray *) newGroupObjectClasses IMAPHostField: (NSString *) newIMAPHostField IMAPLoginField: (NSString *) newIMAPLoginField SieveHostField: (NSString *) newSieveHostField diff --git a/SoObjects/SOGo/LDAPSource.m b/SoObjects/SOGo/LDAPSource.m index daeddb740..598e56ffe 100644 --- a/SoObjects/SOGo/LDAPSource.m +++ b/SoObjects/SOGo/LDAPSource.m @@ -1,6 +1,6 @@ /* LDAPSource.m - this file is part of SOGo * - * Copyright (C) 2007-2014 Inverse inc. + * Copyright (C) 2007-2015 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 @@ -50,7 +50,6 @@ static Class NSStringK; stringByReplacingString: @"'" withString: @"\\'"] \ stringByReplacingString: @"%" withString: @"%%"] - @implementation LDAPSource + (void) initialize @@ -102,6 +101,8 @@ static Class NSStringK; contactMapping = nil; searchFields = [NSArray arrayWithObjects: @"sn", @"displayname", @"telephonenumber", nil]; [searchFields retain]; + groupObjectClasses = [NSArray arrayWithObjects: @"group", @"groupofnames", @"groupofuniquenames", @"posixgroup", nil]; + [groupObjectClasses retain]; IMAPHostField = nil; IMAPLoginField = nil; SieveHostField = nil; @@ -113,6 +114,7 @@ static Class NSStringK; searchAttributes = nil; passwordPolicy = NO; + updateSambaNTLMPasswords = NO; kindField = nil; multipleBookingsField = nil; @@ -144,6 +146,7 @@ static Class NSStringK; [contactMapping release]; [mailFields release]; [searchFields release]; + [groupObjectClasses release]; [IMAPHostField release]; [IMAPLoginField release]; [SieveHostField release]; @@ -189,6 +192,7 @@ static Class NSStringK; UIDField: [udSource objectForKey: @"UIDFieldName"] mailFields: [udSource objectForKey: @"MailFieldNames"] searchFields: [udSource objectForKey: @"SearchFieldNames"] + groupObjectClasses: [udSource objectForKey: @"GroupObjectClasses"] IMAPHostField: [udSource objectForKey: @"IMAPHostFieldName"] IMAPLoginField: [udSource objectForKey: @"IMAPLoginFieldName"] SieveHostField: [udSource objectForKey: @"SieveHostFieldName"] @@ -245,6 +249,9 @@ static Class NSStringK; if ([udSource objectForKey: @"passwordPolicy"]) passwordPolicy = [[udSource objectForKey: @"passwordPolicy"] boolValue]; + if ([udSource objectForKey: @"updateSambaNTLMPasswords"]) + updateSambaNTLMPasswords = [[udSource objectForKey: @"updateSambaNTLMPasswords"] boolValue]; + ASSIGN(MSExchangeHostname, [udSource objectForKey: @"MSExchangeHostname"]); } @@ -307,6 +314,7 @@ static Class NSStringK; UIDField: (NSString *) newUIDField mailFields: (NSArray *) newMailFields searchFields: (NSArray *) newSearchFields +groupObjectClasses: (NSArray *) newGroupObjectClasses IMAPHostField: (NSString *) newIMAPHostField IMAPLoginField: (NSString *) newIMAPLoginField SieveHostField: (NSString *) newSieveHostField @@ -331,6 +339,8 @@ static Class NSStringK; ASSIGN(mailFields, newMailFields); if (newSearchFields) ASSIGN(searchFields, newSearchFields); + if (newGroupObjectClasses) + ASSIGN(groupObjectClasses, newGroupObjectClasses); if (newBindFields) { // Before SOGo v1.2.0, bindFields was a comma-separated list @@ -598,6 +608,40 @@ static Class NSStringK; return [NSString stringWithFormat: @"{%@}%@", _userPasswordAlgorithm, pass]; } +- (BOOL) _ldapModifyAttribute: (NSString *) theAttribute + withValue: (NSString *) theValue + userDN: (NSString *) theUserDN + password: (NSString *) theUserPassword + connection: (NGLdapConnection *) bindConnection +{ + NGLdapModification *mod; + NGLdapAttribute *attr; + NSArray *changes; + + BOOL didChange; + + attr = [[NGLdapAttribute alloc] initWithAttributeName: theAttribute]; + [attr addStringValue: theValue]; + + mod = [NGLdapModification replaceModification: attr]; + + changes = [NSArray arrayWithObject: mod]; + + if ([bindConnection bindWithMethod: @"simple" + binddn: theUserDN + credentials: theUserPassword]) + { + didChange = [bindConnection modifyEntryWithDN: theUserDN + changes: changes]; + } + else + didChange = NO; + + RELEASE(attr); + + return didChange; +} + // // // @@ -651,12 +695,8 @@ static Class NSStringK; { // We don't use a password policy - we simply use // a modify-op to change the password - NGLdapModification *mod; - NGLdapAttribute *attr; - NSArray *changes; NSString* encryptedPass; - - attr = [[NGLdapAttribute alloc] initWithAttributeName: @"userPassword"]; + if ([_userPasswordAlgorithm isEqualToString: @"none"]) { encryptedPass = newPassword; @@ -665,24 +705,33 @@ static Class NSStringK; { encryptedPass = [self _encryptPassword: newPassword]; } - if(encryptedPass != nil) + + if (encryptedPass != nil) { - [attr addStringValue: encryptedPass]; - mod = [NGLdapModification replaceModification: attr]; - changes = [NSArray arrayWithObject: mod]; *perr = PolicyNoError; - - if ([bindConnection bindWithMethod: @"simple" - binddn: userDN - credentials: oldPassword]) - { - didChange = [bindConnection modifyEntryWithDN: userDN - changes: changes]; - } - else - didChange = NO; + didChange = [self _ldapModifyAttribute: @"userPassword" + withValue: encryptedPass + userDN: userDN + password: oldPassword + connection: bindConnection]; } } + + // We must check if we must update the Samba NT/LM password hashes + if (didChange && updateSambaNTLMPasswords) + { + [self _ldapModifyAttribute: @"sambaNTPassword" + withValue: [newPassword asNTHash] + userDN: userDN + password: newPassword + connection: bindConnection]; + + [self _ldapModifyAttribute: @"sambaLMPassword" + withValue: [newPassword asLMHash] + userDN: userDN + password: newPassword + connection: bindConnection]; + } } } } @@ -989,6 +1038,8 @@ static Class NSStringK; NSString *value; static NSArray *resourceKinds = nil; NSMutableArray *classes; + NSEnumerator *gclasses; + NSString *gclass; id o; if (!resourceKinds) @@ -1017,24 +1068,27 @@ static Class NSStringK; if (classes) { - // We check if our entry is a group. If so, we set the - // 'isGroup' custom attribute. - if ([classes containsObject: @"group"] || - [classes containsObject: @"groupofnames"] || - [classes containsObject: @"groupofuniquenames"] || - [classes containsObject: @"posixgroup"]) - { - [ldifRecord setObject: [NSNumber numberWithInt: 1] - forKey: @"isGroup"]; - } // We check if our entry is a resource. We also support // determining resources based on the KindFieldName attribute // value - see below. - else if ([classes containsObject: @"calendarresource"]) + if ([classes containsObject: @"calendarresource"]) { [ldifRecord setObject: [NSNumber numberWithInt: 1] forKey: @"isResource"]; } + else + { + // We check if our entry is a group. If so, we set the + // 'isGroup' custom attribute. + gclasses = [groupObjectClasses objectEnumerator]; + while (gclass = [gclasses nextObject]) + if ([classes containsObject: [gclass lowercaseString]]) + { + [ldifRecord setObject: [NSNumber numberWithInt: 1] + forKey: @"isGroup"]; + break; + } + } } // We check if that entry corresponds to a resource. For this, @@ -1326,6 +1380,11 @@ static Class NSStringK; return modifiers; } +- (NSArray *) groupObjectClasses +{ + return groupObjectClasses; +} + static NSArray * _convertRecordToLDAPAttributes (LDAPSourceSchema *schema, NSDictionary *ldifRecord) { @@ -1641,6 +1700,7 @@ _makeLDAPChanges (NGLdapConnection *ldapConnection, UIDField: @"cn" mailFields: nil searchFields: nil + groupObjectClasses: nil IMAPHostField: nil IMAPLoginField: nil SieveHostField: nil diff --git a/SoObjects/SOGo/NSData+Crypto.h b/SoObjects/SOGo/NSData+Crypto.h index 547e2d0b6..da69a40ba 100644 --- a/SoObjects/SOGo/NSData+Crypto.h +++ b/SoObjects/SOGo/NSData+Crypto.h @@ -1,7 +1,7 @@ /* NSData+Crypto.h - this file is part of SOGo * * Copyright (C) 2012 Nicolas Höft - * Copyright (C) 2012 Inverse inc. + * Copyright (C) 2012-2015 Inverse inc. * * Author: Nicolas Höft * Inverse inc. @@ -32,8 +32,10 @@ @interface NSData (SOGoCryptoExtension) - (NSData *) asCryptedPassUsingScheme: (NSString *) passwordScheme - withSalt: (NSData *) theSalt; + withSalt: (NSData *) theSalt; +- (NSData *) asLM; +- (NSData *) asMD4; - (NSData *) asMD5; - (NSData *) asSMD5UsingSalt: (NSData *) theSalt; - (NSData *) asSHA1; diff --git a/SoObjects/SOGo/NSData+Crypto.m b/SoObjects/SOGo/NSData+Crypto.m index 5c670b445..d869f9b68 100644 --- a/SoObjects/SOGo/NSData+Crypto.m +++ b/SoObjects/SOGo/NSData+Crypto.m @@ -1,7 +1,7 @@ /* NSData+Crypto.m - this file is part of SOGo * * Copyright (C) 2012 Nicolas Höft - * Copyright (C) 2012 Inverse inc. + * Copyright (C) 2012-2015 Inverse inc. * Copyright (C) 2012 Jeroen Dekkers * * Author: Nicolas Höft @@ -39,12 +39,14 @@ #include #include #include +#define MD4_DIGEST_LENGTH 16 #define MD5_DIGEST_LENGTH 16 #define SHA_DIGEST_LENGTH 20 #define SHA256_DIGEST_LENGTH 32 #define SHA512_DIGEST_LENGTH 64 #elif defined(HAVE_OPENSSL) #include +#include #include #include #else @@ -145,7 +147,7 @@ static void _nettle_md5_compress(uint32_t *digest, const uint8_t *input); * @return Pseudo-random binary data with length theLength or nil, if an error occured */ + (NSData *) generateSaltForLength: (unsigned int) theLength - withBase64: (BOOL) doBase64 + withBase64: (BOOL) doBase64 { char *buf; int fd; @@ -177,7 +179,7 @@ static void _nettle_md5_compress(uint32_t *digest, const uint8_t *input); * @return Binary data from the encryption by the specified scheme. On error the funciton returns nil. */ - (NSData *) asCryptedPassUsingScheme: (NSString *) passwordScheme - withSalt: (NSData *) theSalt + withSalt: (NSData *) theSalt { if ([passwordScheme caseInsensitiveCompare: @"none"] == NSOrderedSame || [passwordScheme caseInsensitiveCompare: @"plain"] == NSOrderedSame || @@ -193,6 +195,10 @@ static void _nettle_md5_compress(uint32_t *digest, const uint8_t *input); { return [self asMD5CryptUsingSalt: theSalt]; } + else if ([passwordScheme caseInsensitiveCompare: @"md4"] == NSOrderedSame) + { + return [self asMD4]; + } else if ([passwordScheme caseInsensitiveCompare: @"md5"] == NSOrderedSame || [passwordScheme caseInsensitiveCompare: @"plain-md5"] == NSOrderedSame || [passwordScheme caseInsensitiveCompare: @"ldap-md5"] == NSOrderedSame) @@ -235,6 +241,49 @@ static void _nettle_md5_compress(uint32_t *digest, const uint8_t *input); return nil; } +- (NSData *) asLM +{ + NSData *out; + + unsigned char buf[14]; + unsigned char *o; + unsigned int len; + + memset(buf, 0, 14); + len = ([self length] >= 14 ? 14 : [self length]); + [self getBytes: buf length: len]; + + o = malloc(16*sizeof(unsigned char)); + + auth_LMhash(o, buf, len); + + out = [NSData dataWithBytes: o length: 16]; + free(o); + + return out; +} + +/** + * Hash the data with MD4. Uses openssl functions to generate it. + * + * @return Binary data from MD4 hashing. On error the funciton returns nil. + */ +- (NSData *) asMD4 +{ + unsigned char md4[MD4_DIGEST_LENGTH]; + memset(md4, 0, MD4_DIGEST_LENGTH); + +#if defined(HAVE_GNUTLS) + if (!check_gnutls_init()) + return nil; + + md4_buffer([self bytes], [self length], md4); +#elif defined(HAVE_OPENSSL) + MD4([self bytes], [self length], md4); +#endif + + return [NSData dataWithBytes: md4 length: MD4_DIGEST_LENGTH]; +} /** * Hash the data with MD5. Uses openssl functions to generate it diff --git a/SoObjects/SOGo/NSString+Crypto.h b/SoObjects/SOGo/NSString+Crypto.h index 100bfaced..ba4e23cbe 100644 --- a/SoObjects/SOGo/NSString+Crypto.h +++ b/SoObjects/SOGo/NSString+Crypto.h @@ -1,7 +1,7 @@ /* NSString+Crypto.h - this file is part of SOGo * * Copyright (C) 2012 Nicolas Höft - * Copyright (C) 2012 Inverse inc. + * Copyright (C) 2012-2015 Inverse inc. * * Author: Nicolas Höft * Inverse inc. @@ -39,13 +39,12 @@ typedef enum { @interface NSString (SOGoCryptoExtension) - - (BOOL) isEqualToCrypted: (NSString *) cryptedPassword - withDefaultScheme: (NSString *) theScheme; + withDefaultScheme: (NSString *) theScheme; - (NSString *) asCryptedPassUsingScheme: (NSString *) passwordScheme withSalt: (NSData *) theSalt - andEncoding: (keyEncoding) encoding; + andEncoding: (keyEncoding) encoding; // this method uses the default encoding (base64, plain, hex) // and generates a salt when necessary @@ -56,6 +55,9 @@ typedef enum { - (NSString *) asSHA1String; - (NSString *) asMD5String; +- (NSString *) asNTHash; +- (NSString *) asLMHash; + + (NSArray *) getDefaultEncodingForScheme: (NSString *) passwordScheme; @end diff --git a/SoObjects/SOGo/NSString+Crypto.m b/SoObjects/SOGo/NSString+Crypto.m index 7a720d442..bd0fa17ba 100644 --- a/SoObjects/SOGo/NSString+Crypto.m +++ b/SoObjects/SOGo/NSString+Crypto.m @@ -1,7 +1,7 @@ /* NSString+Crypto.m - this file is part of SOGo * * Copyright (C) 2012 Nicolas Höft - * Copyright (C) 2012 Inverse inc. + * Copyright (C) 2012-2015 Inverse inc. * * Author: Nicolas Höft * Inverse inc. @@ -29,6 +29,8 @@ #import "NSData+Crypto.h" #import +#include "lmhash.h" + @implementation NSString (SOGoCryptoExtension) /** @@ -90,7 +92,8 @@ encodingAndScheme = [NSString getDefaultEncodingForScheme: scheme]; pass = [self substringWithRange: range]; - // return array with [scheme, password, encoding] + + // Returns an array with [scheme, password, encoding] return [NSArray arrayWithObjects: [encodingAndScheme objectAtIndex: 1], pass, [encodingAndScheme objectAtIndex: 0], nil]; } @@ -103,7 +106,7 @@ * @return YES if the passwords are identical using this encryption scheme */ - (BOOL) isEqualToCrypted: (NSString *) cryptedPassword - withDefaultScheme: (NSString *) theScheme + withDefaultScheme: (NSString *) theScheme { NSArray *passInfo; NSString *selfCrypted; @@ -193,7 +196,7 @@ */ - (NSString *) asCryptedPassUsingScheme: (NSString *) passwordScheme withSalt: (NSData *) theSalt - andEncoding: (keyEncoding) userEncoding + andEncoding: (keyEncoding) userEncoding { keyEncoding dataEncoding; NSData* cryptedData; @@ -229,7 +232,7 @@ { // base64 encoding NSString *s = [[NSString alloc] initWithData: [cryptedData dataByEncodingBase64WithLineLength: 1024] - encoding: NSASCIIStringEncoding]; + encoding: NSASCIIStringEncoding]; return [s autorelease]; } @@ -277,7 +280,8 @@ } // in order to keep backwards-compatibility, hex encoding is used for sha1 here - if ([passwordScheme caseInsensitiveCompare: @"md5"] == NSOrderedSame || + if ([passwordScheme caseInsensitiveCompare: @"md4"] == NSOrderedSame || + [passwordScheme caseInsensitiveCompare: @"md5"] == NSOrderedSame || [passwordScheme caseInsensitiveCompare: @"plain-md5"] == NSOrderedSame || [passwordScheme caseInsensitiveCompare: @"sha"] == NSOrderedSame || [passwordScheme caseInsensitiveCompare: @"cram-md5"] == NSOrderedSame) @@ -294,6 +298,7 @@ { encoding = encBase64; } + return [NSArray arrayWithObjects: [NSNumber numberWithInt: encoding], trueScheme, nil]; } @@ -306,7 +311,7 @@ { NSData *cryptData; cryptData = [self dataUsingEncoding: NSUTF8StringEncoding]; - return [NSData encodeDataAsHexString: [cryptData asSHA1] ]; + return [NSData encodeDataAsHexString: [cryptData asSHA1]]; } /** @@ -318,7 +323,36 @@ { NSData *cryptData; cryptData = [self dataUsingEncoding: NSUTF8StringEncoding]; - return [NSData encodeDataAsHexString: [cryptData asMD5] ]; + return [NSData encodeDataAsHexString: [cryptData asMD5]]; +} + +/** + * Encrypts the data using the NT-hash password scheme. + * + * @return If successful, NT-hash encrypted and with hex encoded string + */ +- (NSString *) asNTHash +{ + NSData *d; + + d = [self dataUsingEncoding: NSUTF16LittleEndianStringEncoding]; + + return [[NSData encodeDataAsHexString: [d asMD4]] uppercaseString]; +} + +/** + * Encrypts the data using the LM-hash password scheme. + * + * @return If successful, LM-hash encrypted and with hex encoded string + */ +- (NSString *) asLMHash +{ + NSData *d; + + // See http://en.wikipedia.org/wiki/LM_hash#Algorithm + d = [[self uppercaseString] dataUsingEncoding: NSWindowsCP1252StringEncoding]; + + return [[NSData encodeDataAsHexString: [d asLM]] uppercaseString]; } @end diff --git a/SoObjects/SOGo/SOGoCASSession.m b/SoObjects/SOGo/SOGoCASSession.m index 8c77df29b..6eac8b2c5 100644 --- a/SoObjects/SOGo/SOGoCASSession.m +++ b/SoObjects/SOGo/SOGoCASSession.m @@ -113,7 +113,6 @@ + (void) handleLogoutRequest: (NSString *) logoutRequest { CASLogoutRequest *rq; - SOGoCache *cache; NSBundle *bundle; NSString *mapFile, *sessionIndex; diff --git a/SoObjects/SOGo/SOGoDomainDefaults.h b/SoObjects/SOGo/SOGoDomainDefaults.h index 4743917af..fbc665852 100644 --- a/SoObjects/SOGo/SOGoDomainDefaults.h +++ b/SoObjects/SOGo/SOGoDomainDefaults.h @@ -1,6 +1,6 @@ /* SOGoDomainDefaults.h - this file is part of SOGo * - * Copyright (C) 2009-2014 Inverse inc. + * Copyright (C) 2009-2015 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 @@ -49,6 +49,7 @@ - (BOOL) forceExternalLoginWithEmail; - (BOOL) sieveScriptsEnabled; - (BOOL) forwardEnabled; +- (int) forwardConstraints; - (BOOL) vacationEnabled; - (NSString *) mailingMechanism; - (NSString *) smtpServer; diff --git a/SoObjects/SOGo/SOGoDomainDefaults.m b/SoObjects/SOGo/SOGoDomainDefaults.m index 35a078b3f..ff02b6a96 100644 --- a/SoObjects/SOGo/SOGoDomainDefaults.m +++ b/SoObjects/SOGo/SOGoDomainDefaults.m @@ -1,6 +1,6 @@ /* SOGoDomainDefaults.m - this file is part of SOGo * - * Copyright (C) 2009-2014 Inverse inc. + * Copyright (C) 2009-2015 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 @@ -201,6 +201,15 @@ return [self boolForKey: @"SOGoForwardEnabled"]; } +- (int) forwardConstraints +{ + unsigned int v; + + v = [self integerForKey: @"SOGoForwardConstraints"]; + + return (v > 2 ? 0 : v); +} + - (BOOL) vacationEnabled { return [self boolForKey: @"SOGoVacationEnabled"]; diff --git a/SoObjects/SOGo/SOGoGroup.m b/SoObjects/SOGo/SOGoGroup.m index 648b97ae4..cacdd7770 100644 --- a/SoObjects/SOGo/SOGoGroup.m +++ b/SoObjects/SOGo/SOGoGroup.m @@ -137,6 +137,8 @@ NGLdapEntry *entry; NSObject *source; id o; + NSEnumerator *gclasses; + NSString *gclass; int i; @@ -194,12 +196,11 @@ } } - // Found a group, let's return it. - if ([classes containsObject: @"group"] || - [classes containsObject: @"groupofnames"] || - [classes containsObject: @"groupofuniquenames"] || - [classes containsObject: @"posixgroup"]) - { + gclasses = [[source groupObjectClasses] objectEnumerator]; + while (gclass = [gclasses nextObject]) + if ([classes containsObject: gclass]) + { + // Found a group, let's return it. o = [[self alloc] initWithIdentifier: theValue domain: domain source: source diff --git a/SoObjects/SOGo/SOGoSieveManager.m b/SoObjects/SOGo/SOGoSieveManager.m index 2f949ad20..f2f5a7b41 100644 --- a/SoObjects/SOGo/SOGoSieveManager.m +++ b/SoObjects/SOGo/SOGoSieveManager.m @@ -835,13 +835,16 @@ static NSString *sieveScriptName = @"sogo"; if (values && [[values objectForKey: @"enabled"] boolValue]) { + NSMutableString *vacation_script; NSArray *addresses; NSString *text; - BOOL ignore; + + BOOL ignore, alwaysSend; int days, i; - + days = [[values objectForKey: @"daysBetweenResponse"] intValue]; addresses = [values objectForKey: @"autoReplyEmailAddresses"]; + alwaysSend = [[values objectForKey: @"alwaysSend"] boolValue]; ignore = [[values objectForKey: @"ignoreLists"] boolValue]; text = [values objectForKey: @"autoReplyText"]; b = YES; @@ -849,28 +852,38 @@ static NSString *sieveScriptName = @"sogo"; if (days == 0) days = 7; + vacation_script = [NSMutableString string]; + [req addObjectUniquely: @"vacation"]; // Skip mailing lists if (ignore) - [script appendString: @"if allof ( not exists [\"list-help\", \"list-unsubscribe\", \"list-subscribe\", \"list-owner\", \"list-post\", \"list-archive\", \"list-id\", \"Mailing-List\"], not header :comparator \"i;ascii-casemap\" :is \"Precedence\" [\"list\", \"bulk\", \"junk\"], not header :comparator \"i;ascii-casemap\" :matches \"To\" \"Multiple recipients of*\" ) {"]; + [vacation_script appendString: @"if allof ( not exists [\"list-help\", \"list-unsubscribe\", \"list-subscribe\", \"list-owner\", \"list-post\", \"list-archive\", \"list-id\", \"Mailing-List\"], not header :comparator \"i;ascii-casemap\" :is \"Precedence\" [\"list\", \"bulk\", \"junk\"], not header :comparator \"i;ascii-casemap\" :matches \"To\" \"Multiple recipients of*\" ) {"]; - [script appendFormat: @"vacation :days %d :addresses [", days]; + [vacation_script appendFormat: @"vacation :days %d :addresses [", days]; for (i = 0; i < [addresses count]; i++) { - [script appendFormat: @"\"%@\"", [addresses objectAtIndex: i]]; + [vacation_script appendFormat: @"\"%@\"", [addresses objectAtIndex: i]]; if (i == [addresses count]-1) - [script appendString: @"] "]; + [vacation_script appendString: @"] "]; else - [script appendString: @", "]; + [vacation_script appendString: @", "]; } - [script appendFormat: @"text:\r\n%@\r\n.\r\n;\r\n", text]; + [vacation_script appendFormat: @"text:\r\n%@\r\n.\r\n;\r\n", text]; if (ignore) - [script appendString: @"}\r\n"]; + [vacation_script appendString: @"}\r\n"]; + + // + // See http://sogo.nu/bugs/view.php?id=2332 for details + // + if (alwaysSend) + [script insertString: vacation_script atIndex: 0]; + else + [script appendString: vacation_script]; } diff --git a/SoObjects/SOGo/SOGoSource.h b/SoObjects/SOGo/SOGoSource.h index 2415f408c..b55a5a14a 100644 --- a/SoObjects/SOGo/SOGoSource.h +++ b/SoObjects/SOGo/SOGoSource.h @@ -110,6 +110,7 @@ - (NSString *) baseDN; - (NSString *) MSExchangeHostname; +- (NSArray *) groupObjectClasses; @end #endif /* SOGOSOURCE_H */ diff --git a/SoObjects/SOGo/SOGoUser.h b/SoObjects/SOGo/SOGoUser.h index fad66e4e8..5702f10cc 100644 --- a/SoObjects/SOGo/SOGoUser.h +++ b/SoObjects/SOGo/SOGoUser.h @@ -1,5 +1,5 @@ /* - Copyright (C) 2006-2013 Inverse inc. + Copyright (C) 2006-2015 Inverse inc. Copyright (C) 2005 SKYRIX Software AG This file is part of SOGo. diff --git a/SoObjects/SOGo/SOGoUser.m b/SoObjects/SOGo/SOGoUser.m index da0ed8c6c..91d3b1b5a 100644 --- a/SoObjects/SOGo/SOGoUser.m +++ b/SoObjects/SOGo/SOGoUser.m @@ -37,6 +37,7 @@ #import #import +#import #import "NSArray+Utilities.h" #import "SOGoCache.h" @@ -234,7 +235,6 @@ [currentPassword release]; [cn release]; [loginInDomain release]; - //[language release]; [super dealloc]; } @@ -577,13 +577,13 @@ - (void) _appendSystemMailAccount { NSString *fullName, *replyTo, *imapLogin, *imapServer, *cImapServer, *signature, - *encryption, *scheme, *action, *query, *customEmail, *sieveServer; + *encryption, *scheme, *action, *query, *customEmail, *defaultEmail, *sieveServer; NSMutableDictionary *mailAccount, *identity, *mailboxes, *receipts; NSNumber *port; NSMutableArray *identities; NSArray *mails; NSURL *url, *cUrl; - unsigned int count, max; + unsigned int count, max, default_identity; NSInteger defaultPort; [self userDefaults]; @@ -661,8 +661,9 @@ [mailAccount setObject: sieveServer forKey: @"sieveServerName"]; } - - /* identities */ + // Identities + defaultEmail = [NSString stringWithFormat: @"%@@%@", [self loginInDomain], [self domain]]; + default_identity = 0; identities = [NSMutableArray new]; mails = [self allEmails]; [mailAccount setObject: [mails objectAtIndex: 0] forKey: @"name"]; @@ -702,6 +703,10 @@ if (signature) [identity setObject: signature forKey: @"signature"]; [identities addObject: identity]; + + if ([[identity objectForKey: @"email"] caseInsensitiveCompare: defaultEmail] == NSOrderedSame) + default_identity = [identities count]-1; + [identity release]; } } @@ -723,10 +728,14 @@ if (signature) [identity setObject: signature forKey: @"signature"]; [identities addObject: identity]; + + if ([[identity objectForKey: @"email"] caseInsensitiveCompare: defaultEmail] == NSOrderedSame) + default_identity = [identities count]-1; + [identity release]; } - [[identities objectAtIndex: 0] setObject: [NSNumber numberWithBool: YES] - forKey: @"isDefault"]; + [[identities objectAtIndex: default_identity] setObject: [NSNumber numberWithBool: YES] + forKey: @"isDefault"]; [mailAccount setObject: identities forKey: @"identities"]; [identities release]; diff --git a/SoObjects/SOGo/SOGoUserManager.h b/SoObjects/SOGo/SOGoUserManager.h index 237347053..d30821108 100644 --- a/SoObjects/SOGo/SOGoUserManager.h +++ b/SoObjects/SOGo/SOGoUserManager.h @@ -65,10 +65,13 @@ - (NSDictionary *) contactInfosForUserWithUIDorEmail: (NSString *) uid; - (NSDictionary *) contactInfosForUserWithUIDorEmail: (NSString *) uid inDomain: (NSString *) domain; +- (NSDictionary *) fetchContactWithUID: (NSString *) uid + inDomain: (NSString *) domain; - (NSArray *) fetchContactsMatching: (NSString *) match inDomain: (NSString *) domain; - (NSArray *) fetchUsersMatching: (NSString *) filter inDomain: (NSString *) domain; +- (NSArray *) _compactAndCompleteContacts: (NSEnumerator *) contacts; - (NSString *) getCNForUID: (NSString *) uid; - (NSString *) getEmailForUID: (NSString *) uid; diff --git a/SoObjects/SOGo/SOGoUserManager.m b/SoObjects/SOGo/SOGoUserManager.m index 4d6a5b911..99e9995b2 100644 --- a/SoObjects/SOGo/SOGoUserManager.m +++ b/SoObjects/SOGo/SOGoUserManager.m @@ -18,6 +18,7 @@ * Boston, MA 02111-1307, USA. */ #import +#import #import #import #import @@ -914,6 +915,37 @@ static Class NSNullK; return currentUser; } +/** + * Fetch the contact information identified by the specified UID in the global addressbooks. + */ +- (NSDictionary *) fetchContactWithUID: (NSString *) uid + inDomain: (NSString *) domain +{ + NSDictionary *contact; + NSMutableArray *contacts; + NSEnumerator *sources; + NSString *sourceID; + id currentSource; + + contacts = [NSMutableArray array]; + contact = nil; + sources = [[self addressBookSourceIDsInDomain: domain] objectEnumerator]; + while ((sourceID = [sources nextObject])) + { + currentSource = [_sources objectForKey: sourceID]; + contact = [currentSource lookupContactEntry: uid]; + if (contact) + [contacts addObject: contact]; + } + + if ([contacts count]) + contact = [[self _compactAndCompleteContacts: [contacts objectEnumerator]] lastObject]; + else + contact = nil; + + return contact; +} + - (NSArray *) _compactAndCompleteContacts: (NSEnumerator *) contacts { NSMutableDictionary *compactContacts, *returnContact; diff --git a/SoObjects/SOGo/lmhash.c b/SoObjects/SOGo/lmhash.c new file mode 100644 index 000000000..070ba1e25 --- /dev/null +++ b/SoObjects/SOGo/lmhash.c @@ -0,0 +1,742 @@ +#include "lmhash.h" + +#include +#include + +/* ========================================================================== ** + * + * LMhash.h + * + * Copyright: + * Copyright (C) 2004 by Christopher R. Hertel + * + * Email: crh@ubiqx.mn.org + * + * $Id: LMhash.h,v 0.1 2004/05/30 02:26:31 crh Exp $ + * + * -------------------------------------------------------------------------- ** + * + * Description: + * + * Implemention of the LAN Manager hash (LM hash) and LM response + * algorithms. + * + * -------------------------------------------------------------------------- ** + * + * License: + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * -------------------------------------------------------------------------- ** + * + * Notes: + * + * This module implements the LM hash. The NT hash is simply the MD4() of + * the password, so we don't need a separate implementation of that. This + * module also implements the LM response, which can be combined with the + * NT hash to produce the NTLM response. + * + * This implementation was created based on the description in my own book. + * The book description was, in turn, written after studying many existing + * examples in various documentation. Jeremy Allison and Andrew Tridgell + * deserve lots of credit for having figured out the secrets of Lan Manager + * authentication many years ago. + * + * See: + * Implementing CIFS - the Common Internet File System + * by your truly. ISBN 0-13-047116-X, Prentice Hall PTR., August 2003 + * Section 15.3, in particular. + * (Online at: http://ubiqx.org/cifs/SMB.html#SMB.8.3) + * + * ========================================================================== ** + */ + + +/* Initial permutation map. + * In the first step of DES, the bits of the initial plaintext are rearranged + * according to the map given below. This map and those like it are read by + * the Permute() function (below) which uses the maps as a guide when moving + * bits from one place to another. + * + * Note that the values here are all one less than those shown in Schneier. + * That's because C likes to start counting from 0, not 1. + * + * According to Schneier (Ch12, pg 271), the purpose of the initial + * permutation was to make it easier to load plaintext and ciphertext into + * a DES ecryption chip. I have no idea why that would be the case. + */ +static const uint8_t InitialPermuteMap[64] = + { + 57, 49, 41, 33, 25, 17, 9, 1, + 59, 51, 43, 35, 27, 19, 11, 3, + 61, 53, 45, 37, 29, 21, 13, 5, + 63, 55, 47, 39, 31, 23, 15, 7, + 56, 48, 40, 32, 24, 16, 8, 0, + 58, 50, 42, 34, 26, 18, 10, 2, + 60, 52, 44, 36, 28, 20, 12, 4, + 62, 54, 46, 38, 30, 22, 14, 6 + }; + + +/* Key permutation map. + * Like the input data and encryption result, the key is permuted before + * the algorithm really gets going. The original algorithm called for an + * eight-byte key in which each byte contained a parity bit. During the + * key permutiation, the parity bits were discarded. The DES algorithm, + * as used with SMB, does not make use of the parity bits. Instead, SMB + * passes 7-byte keys to DES. For DES implementations that expect parity, + * the parity bits must be added. In this case, however, we're just going + * to start with a 7-byte (56 bit) key. KeyPermuteMap, below, is adjusted + * accordingly and, of course, each entry in the map is reduced by 1 with + * respect to the documented values because C likes to start counting from + * 0, not 1. + */ +static const uint8_t KeyPermuteMap[56] = + { + 49, 42, 35, 28, 21, 14, 7, 0, + 50, 43, 36, 29, 22, 15, 8, 1, + 51, 44, 37, 30, 23, 16, 9, 2, + 52, 45, 38, 31, 55, 48, 41, 34, + 27, 20, 13, 6, 54, 47, 40, 33, + 26, 19, 12, 5, 53, 46, 39, 32, + 25, 18, 11, 4, 24, 17, 10, 3, + }; + + +/* Key rotation table. + * At the start of each round of encryption, the key is split and each + * 28-bit half is rotated left. The number of bits of rotation per round + * is given in the table below. + */ +static const uint8_t KeyRotation[16] = + { 1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1 }; + + +/* Key compression table. + * This table is used to select 48 of the 56 bits of the key. + * The left and right halves of the source text are each 32 bits, + * but they are expanded to 48 bits and the results are XOR'd + * against the compressed (48-bit) key. + */ +static const uint8_t KeyCompression[48] = + { + 13, 16, 10, 23, 0, 4, 2, 27, + 14, 5, 20, 9, 22, 18, 11, 3, + 25, 7, 15, 6, 26, 19, 12, 1, + 40, 51, 30, 36, 46, 54, 29, 39, + 50, 44, 32, 47, 43, 48, 38, 55, + 33, 52, 45, 41, 49, 35, 28, 31 + }; + + +/* Data expansion table. + * This table is used after the data block (64-bits) has been split + * into two 32-bit (4-byte) halves (generally denoted L and R). + * Each 32-bit half is "expanded", using this table, to a 48 bit + * data block, which is then XOR'd with the 48 bit subkey for the + * round. + */ +static const uint8_t DataExpansion[48] = + { + 31, 0, 1, 2, 3, 4, 3, 4, + 5, 6, 7, 8, 7, 8, 9, 10, + 11, 12, 11, 12, 13, 14, 15, 16, + 15, 16, 17, 18, 19, 20, 19, 20, + 21, 22, 23, 24, 23, 24, 25, 26, + 27, 28, 27, 28, 29, 30, 31, 0 + }; + + +/* The (in)famous S-boxes. + * These are used to perform substitutions. + * Six bits worth of input will return four bits of output. + * The four bit values are stored in these tables. Each table has + * 64 entries...and 6 bits provides a number between 0 and 63. + * There are eight S-boxes, one per 6 bits of a 48-bit value. + * Thus, 48 bits are reduced to 32 bits. Obviously, this step + * follows the DataExpansion step. + * + * Note that the literature generally shows this as 8 arrays each + * with four rows and 16 colums. There is a complex formula for + * mapping the 6 bit input values to the correct row and column. + * I've pre-computed that mapping, and the tables below provide + * direct 6-bit input to 4-bit output. See pp 274-274 in Schneier. + */ +static const uint8_t SBox[8][64] = + { + { /* S0 */ + 14, 0, 4, 15, 13, 7, 1, 4, 2, 14, 15, 2, 11, 13, 8, 1, + 3, 10, 10, 6, 6, 12, 12, 11, 5, 9, 9, 5, 0, 3, 7, 8, + 4, 15, 1, 12, 14, 8, 8, 2, 13, 4, 6, 9, 2, 1, 11, 7, + 15, 5, 12, 11, 9, 3, 7, 14, 3, 10, 10, 0, 5, 6, 0, 13 + }, + { /* S1 */ + 15, 3, 1, 13, 8, 4, 14, 7, 6, 15, 11, 2, 3, 8, 4, 14, + 9, 12, 7, 0, 2, 1, 13, 10, 12, 6, 0, 9, 5, 11, 10, 5, + 0, 13, 14, 8, 7, 10, 11, 1, 10, 3, 4, 15, 13, 4, 1, 2, + 5, 11, 8, 6, 12, 7, 6, 12, 9, 0, 3, 5, 2, 14, 15, 9 + }, + { /* S2 */ + 10, 13, 0, 7, 9, 0, 14, 9, 6, 3, 3, 4, 15, 6, 5, 10, + 1, 2, 13, 8, 12, 5, 7, 14, 11, 12, 4, 11, 2, 15, 8, 1, + 13, 1, 6, 10, 4, 13, 9, 0, 8, 6, 15, 9, 3, 8, 0, 7, + 11, 4, 1, 15, 2, 14, 12, 3, 5, 11, 10, 5, 14, 2, 7, 12 + }, + { /* S3 */ + 7, 13, 13, 8, 14, 11, 3, 5, 0, 6, 6, 15, 9, 0, 10, 3, + 1, 4, 2, 7, 8, 2, 5, 12, 11, 1, 12, 10, 4, 14, 15, 9, + 10, 3, 6, 15, 9, 0, 0, 6, 12, 10, 11, 1, 7, 13, 13, 8, + 15, 9, 1, 4, 3, 5, 14, 11, 5, 12, 2, 7, 8, 2, 4, 14 + }, + { /* S4 */ + 2, 14, 12, 11, 4, 2, 1, 12, 7, 4, 10, 7, 11, 13, 6, 1, + 8, 5, 5, 0, 3, 15, 15, 10, 13, 3, 0, 9, 14, 8, 9, 6, + 4, 11, 2, 8, 1, 12, 11, 7, 10, 1, 13, 14, 7, 2, 8, 13, + 15, 6, 9, 15, 12, 0, 5, 9, 6, 10, 3, 4, 0, 5, 14, 3 + }, + { /* S5 */ + 12, 10, 1, 15, 10, 4, 15, 2, 9, 7, 2, 12, 6, 9, 8, 5, + 0, 6, 13, 1, 3, 13, 4, 14, 14, 0, 7, 11, 5, 3, 11, 8, + 9, 4, 14, 3, 15, 2, 5, 12, 2, 9, 8, 5, 12, 15, 3, 10, + 7, 11, 0, 14, 4, 1, 10, 7, 1, 6, 13, 0, 11, 8, 6, 13 + }, + { /* S6 */ + 4, 13, 11, 0, 2, 11, 14, 7, 15, 4, 0, 9, 8, 1, 13, 10, + 3, 14, 12, 3, 9, 5, 7, 12, 5, 2, 10, 15, 6, 8, 1, 6, + 1, 6, 4, 11, 11, 13, 13, 8, 12, 1, 3, 4, 7, 10, 14, 7, + 10, 9, 15, 5, 6, 0, 8, 15, 0, 14, 5, 2, 9, 3, 2, 12 + }, + { /* S7 */ + 13, 1, 2, 15, 8, 13, 4, 8, 6, 10, 15, 3, 11, 7, 1, 4, + 10, 12, 9, 5, 3, 6, 14, 11, 5, 0, 0, 14, 12, 9, 7, 2, + 7, 2, 11, 1, 4, 14, 1, 7, 9, 4, 12, 10, 14, 8, 2, 13, + 0, 15, 6, 12, 10, 9, 13, 0, 15, 3, 3, 5, 5, 6, 8, 11 + } + }; + + +/* P-Box permutation. + * This permutation is applied to the result of the S-Box Substitutions. + * It's a straight-forward re-arrangement of the bits. + */ +static const uint8_t PBox[32] = + { + 15, 6, 19, 20, 28, 11, 27, 16, + 0, 14, 22, 25, 4, 17, 30, 9, + 1, 7, 23, 13, 31, 26, 2, 8, + 18, 12, 29, 5, 21, 10, 3, 24 + }; + + +/* Final permutation map. + * This is supposed to be the inverse of the Initial Permutation, + * but there's been a bit of fiddling done. + * As always, the values given are one less than those in the literature + * (because C starts counting from 0, not 1). In addition, the penultimate + * step in DES is to swap the left and right hand sides of the ciphertext. + * The inverse of the Initial Permutation is then applied to produce the + * final result. + * To save a step, the map below does the left/right swap as well as the + * inverse permutation. + */ +static const uint8_t FinalPermuteMap[64] = + { + 7, 39, 15, 47, 23, 55, 31, 63, + 6, 38, 14, 46, 22, 54, 30, 62, + 5, 37, 13, 45, 21, 53, 29, 61, + 4, 36, 12, 44, 20, 52, 28, 60, + 3, 35, 11, 43, 19, 51, 27, 59, + 2, 34, 10, 42, 18, 50, 26, 58, + 1, 33, 9, 41, 17, 49, 25, 57, + 0, 32, 8, 40, 16, 48, 24, 56 + }; + + +/* -------------------------------------------------------------------------- ** + * Macros: + * + * CLRBIT( STR, IDX ) + * Input: STR - (uchar *) pointer to an array of 8-bit bytes. + * IDX - (int) bitwise index of a bit within the STR array + * that is to be cleared (that is, given a value of 0). + * Notes: This macro clears a bit within an array of bits (which is + * built within an array of bytes). + * - The macro converts to an assignment of the form A &= B. + * - The string of bytes is viewed as an array of bits, read from + * highest order bit first. The highest order bit of a byte + * would, therefore, be bit 0 (within that byte). + * + * SETBIT( STR, IDX ) + * Input: STR - (uchar *) pointer to an array of 8-bit bytes. + * IDX - (int) bitwise index of a bit within the STR array + * that is to be set (that is, given a value of 1). + * Notes: This macro sets a bit within an array of bits (which is + * built within an array of bytes). + * - The macro converts to an assignment of the form A |= B. + * - The string of bytes is viewed as an array of bits, read from + * highest order bit first. The highest order bit of a byte + * would, therefore, be bit 0 (within that byte). + * + * GETBIT( STR, IDX ) + * Input: STR - (uchar *) pointer to an array of 8-bit bytes. + * IDX - (int) bit-wise index of a bit within the STR array + * that is to be read. + * Output: True (1) if the indexed bit was set, else false (0). + * + * -------------------------------------------------------------------------- ** + */ + +#define CLRBIT( STR, IDX ) ( (STR)[(IDX)/8] &= ~(0x01 << (7 - ((IDX)%8))) ) + +#define SETBIT( STR, IDX ) ( (STR)[(IDX)/8] |= (0x01 << (7 - ((IDX)%8))) ) + +#define GETBIT( STR, IDX ) (( ((STR)[(IDX)/8]) >> (7 - ((IDX)%8)) ) & 0x01) + + +/* -------------------------------------------------------------------------- ** + * Static Functions: + */ + +static void Permute( uchar *dst, + const uchar *src, + const uint8_t *map, + const int mapsize ) + /* ------------------------------------------------------------------------ ** + * Performs a DES permutation, which re-arranges the bits in an array of + * bytes. + * + * Input: dst - Destination into which to put the re-arranged bits. + * src - Source from which to read the bits. + * map - Permutation map. + * mapsize - Number of bytes represented by the . This also + * represents the number of bytes to be copied to . + * + * Output: none. + * + * Notes: and must not point to the same location. + * + * - No checks are done to ensure that there is enough room + * in , or that the bit numbers in do not exceed + * the bits available in . A good reason to make this + * function static (private). + * + * - The value is in bytes. All permutations in DES + * use tables that are a multiple of 8 bits, so there is no + * need to handle partial bytes. (Yes, I know that there + * are some machines out there that still use bytes of a size + * other than 8 bits. For our purposes we'll stick with 8-bit + * bytes.) + * + * ------------------------------------------------------------------------ ** + */ + { + int bitcount; + int i; + + /* Clear all bits in the destination. + */ + for( i = 0; i < mapsize; i++ ) + dst[i] = 0; + + /* Set destination bit if the mapped source bit it set. */ + bitcount = mapsize * 8; + for( i = 0; i < bitcount; i++ ) + { + if( GETBIT( src, map[i] ) ) + SETBIT( dst, i ); + } + } /* Permute */ + + +static void KeyShift( uchar *key, const int numbits ) + /* ------------------------------------------------------------------------ ** + * Split the 56-bit key in half & left rotate each half by bits. + * + * Input: key - The 56-bit key to be split-rotated. + * numbits - The number of bits by which to rotate the key. + * + * Output: none. + * + * Notes: There are probably several better ways to implement this. + * + * ------------------------------------------------------------------------ ** + */ + { + int i; + uchar keep = key[0]; /* Copy the highest order bits of the key. */ + + /* Repeat the shift process times. + */ + for( i = 0; i < numbits; i++ ) + { + int j; + + /* Shift the entire thing, byte by byte. + */ + for( j = 0; j < 7; j++ ) + { + if( j && (key[j] & 0x80) ) /* If the top bit of this byte is set. */ + key[j-1] |= 0x01; /* ...shift it to last byte's low bit. */ + key[j] <<= 1; /* Then left-shift the whole byte. */ + } + + /* Now move the high-order bits of each 28-bit half-key to their + * correct locations. + * Bit 27 is the lowest order bit of the first half-key. + * Before the shift, it was the highest order bit of the 2nd half-key. + */ + if( GETBIT( key, 27 ) ) /* If bit 27 is set... */ + { + CLRBIT( key, 27 ); /* ...clear bit 27. */ + SETBIT( key, 55 ); /* ...set lowest order bit of 2nd half-key. */ + } + + /* We kept the highest order bit of the first half-key in . + * If it's set, copy it to bit 27. + */ + if( keep & 0x80 ) + SETBIT( key, 27 ); + + /* Rotate the byte too, in case is 2 and there's + * a second round coming. + */ + keep <<= 1; + } + } /* KeyShift */ + + +static void sbox( uchar *dst, const uchar *src ) + /* ------------------------------------------------------------------------ ** + * Perform S-Box substitutions. + * + * Input: dst - Destination byte array into which the S-Box substituted + * bitmap will be written. + * src - Source byte array. + * + * Output: none. + * + * Notes: It's really not possible (for me, anyway) to understand how + * this works without reading one or more detailed explanations. + * Quick overview, though: + * + * After the DataExpansion step (in which a 32-bit bit array is + * expanded to a 48-bit bit array) the expanded data block is + * XOR'd with 48-bits worth of key. That 48 bits then needs to + * be condensed back into 32 bits. + * + * The S-Box substitution handles the data reduction by breaking + * the 48-bit value into eight 6-bit values. For each of these + * 6-bit values there is a table (an S-Box table). The table + * contains 64 possible values. Conveniently, a 6-bit integer + * can represent a value between 0 and 63. + * + * So, if you think of the 48-bit bit array as an array of 6-bit + * integers, you use S-Box table 0 with the 0th 6-bit value. + * Table 1 is used with the 6-bit value #1, and so on until #7. + * Within each table, the correct substitution is found based + * simply on the value of the 6-bit integer. + * + * Well, the original algorithm (and most documentation) don't + * make it so simple. There's a complex formula for mapping + * the 6-bit values to the correct substitution. Fortunately, + * those lookups can be precomputed (and have been for this + * implementation). See pp 274-274 in Schneier. + * + * Oh, and the substitute values are all 4-bit values, so each + * 6-bits gets reduced to 4-bits resulting in a 32-bit bit array. + * + * ------------------------------------------------------------------------ ** + */ + { + int i; + + /* Clear the destination array. + */ + for( i = 0; i < 4; i++ ) + dst[i] = 0; + + /* For each set of six input bits... + */ + for( i = 0; i < 8; i++ ) + { + int j; + int Snum; + int bitnum; + + /* Extract the 6-bit integer from the source. + * This will be the lookup key within the SBox[i] array. + */ + for( Snum = j = 0, bitnum = (i * 6); j < 6; j++, bitnum++ ) + { + Snum <<= 1; + Snum |= GETBIT( src, bitnum ); + } + + /* Find the correct value in the correct SBox[] + * and copy it into the destination. + * Left shift the nibble four bytes for even values of . + */ + if( 0 == (i%2) ) + dst[i/2] |= ((SBox[i][Snum]) << 4); + else + dst[i/2] |= SBox[i][Snum]; + } + } /* sbox */ + + +static void xor( uchar *dst, const uchar *a, const uchar *b, const int count ) + /* ------------------------------------------------------------------------ ** + * Perform an XOR operation on two byte arrays. + * + * Input: dst - Destination array to which the result will be written. + * a - The first string of bytes. + * b - The second string of bytes. + * count - Number of bytes to XOR against one another. + * + * Output: none. + * + * Notes: This function operates on whole byte chunks. There's no need + * to XOR partial bytes so no need to write code to handle it. + * + * - This function essentially implements dst = a ^ b; for byte + * arrays. + * + * - may safely point to the same location as or . + * + * ------------------------------------------------------------------------ ** + */ + { + int i; + + for( i = 0; i < count; i++ ) + dst[i] = a[i] ^ b[i]; + } /* xor */ + + +/* -------------------------------------------------------------------------- ** + * Functions: + */ + +uchar *auth_DESkey8to7( uchar *dst, const uchar *key ) + /* ------------------------------------------------------------------------ ** + * Compress an 8-byte DES key to its 7-byte form. + * + * Input: dst - Pointer to a memory location (minimum 7 bytes) to accept + * the compressed key. + * key - Pointer to an 8-byte DES key. See the notes below. + * + * Output: A pointer to the compressed key (same as ) or NULL if + * either or were NULL. + * + * Notes: There are no checks done to ensure that and point + * to sufficient space. Please be carefull. + * + * The two pointers, and may point to the same + * memory location. Internally, a temporary buffer is used and + * the results are copied back to . + * + * The DES algorithm uses 8 byte keys by definition. The first + * step in the algorithm, however, involves removing every eigth + * bit to produce a 56-bit key (seven bytes). SMB authentication + * skips this step and uses 7-byte keys. The + * algorithm in this module expects 7-byte keys. This function + * is used to convert an 8-byte DES key into a 7-byte SMB DES key. + * + * ------------------------------------------------------------------------ ** + */ + { + int i; + uchar tmp[7]; + static const uint8_t map8to7[56] = + { + 0, 1, 2, 3, 4, 5, 6, + 8, 9, 10, 11, 12, 13, 14, + 16, 17, 18, 19, 20, 21, 22, + 24, 25, 26, 27, 28, 29, 30, + 32, 33, 34, 35, 36, 37, 38, + 40, 41, 42, 43, 44, 45, 46, + 48, 49, 50, 51, 52, 53, 54, + 56, 57, 58, 59, 60, 61, 62 + }; + + if( (NULL == dst) || (NULL == key) ) + return( NULL ); + + Permute( tmp, key, map8to7, 7 ); + for( i = 0; i < 7; i++ ) + dst[i] = tmp[i]; + + return( dst ); + } /* auth_DESkey8to7 */ + + +uchar *auth_DEShash( uchar *dst, const uchar *key, const uchar *src ) + /* ------------------------------------------------------------------------ ** + * DES encryption of the input data using the input key. + * + * Input: dst - Destination buffer. It *must* be at least eight bytes + * in length, to receive the encrypted result. + * key - Encryption key. Exactly seven bytes will be used. + * If your key is shorter, ensure that you pad it to seven + * bytes. + * src - Source data to be encrypted. Exactly eight bytes will + * be used. If your source data is shorter, ensure that + * you pad it to eight bytes. + * + * Output: A pointer to the encrpyted data (same as ). + * + * Notes: In SMB, the DES function is used as a hashing function rather + * than an encryption/decryption tool. When used for generating + * the LM hash the input is the known value "KGS!@#$%" and + * the key is derived from the password entered by the user. + * When used to generate the LM or NTLM response, the is + * derived from the LM or NTLM hash, and the challenge is used + * as the input. + * See: http://ubiqx.org/cifs/SMB.html#SMB.8.3 + * + * - This function is called "DEShash" rather than just "DES" + * because it is only used for creating LM hashes and the + * LM/NTLM responses. For all practical purposes, however, it + * is a full DES encryption implementation. + * + * - This DES implementation does not need to be fast, nor is a + * DES decryption function needed. The goal is to keep the + * code small, simple, and well documented. + * + * - The input values are copied and refiddled within the module + * and the result is not written to until the very last + * step, so it's okay if points to the same memory as + * or . + * + * ------------------------------------------------------------------------ ** + */ + { + int i; /* Loop counter. */ + uchar K[7]; /* Holds the key, as we manipulate it. */ + uchar D[8]; /* The data block, as we manipulate it. */ + + /* Create the permutations of the key and the source. + */ + Permute( K, key, KeyPermuteMap, 7 ); + Permute( D, src, InitialPermuteMap, 8 ); + + /* DES encryption proceeds in 16 rounds. + * The stuff inside the loop is known in the literature as "function f". + */ + for( i = 0; i < 16; i++ ) + { + int j; + uchar *L = D; /* The left 4 bytes (half) of the data block. */ + uchar *R = &(D[4]); /* The right half of the ciphertext block. */ + uchar Rexp[6]; /* Expanded right half. */ + uchar Rn[4]; /* New value of R, as we manipulate it. */ + uchar SubK[6]; /* The 48-bit subkey. */ + + /* Generate the subkey for this round. + */ + KeyShift( K, KeyRotation[i] ); + Permute( SubK, K, KeyCompression, 6 ); + + /* Expand the right half (R) of the data block to 48 bytes, + * then XOR the result with the Subkey for this round. + */ + Permute( Rexp, R, DataExpansion, 6 ); + xor( Rexp, Rexp, SubK, 6 ); + + /* S-Box substitutions, P-Box permutation, and final XOR. + * The S-Box substitutions return a 32-bit value, which is then + * run through the 32-bit to 32-bit P-Box permutation. The P-Box + * result is then XOR'd with the left-hand half of the key. + * (Rexp is used as a temporary variable between the P-Box & XOR). + */ + sbox( Rn, Rexp ); + Permute( Rexp, Rn, PBox, 4 ); + xor( Rn, L, Rexp, 4 ); + + /* The previous R becomes the new L, + * and Rn is moved into R ready for the next round. + */ + for( j = 0; j < 4; j++ ) + { + L[j] = R[j]; + R[j] = Rn[j]; + } + } + + /* The encryption is complete. + * Now reverse-permute the ciphertext to produce the final result. + * We actually combine two steps here. The penultimate step is to + * swap the positions of L and R in the result of the 16 rounds, + * after which the reverse of the Initial Permutation is applied. + * To save a step, the FinalPermuteMap applies both the L/R swap + * and the inverse of the Initial Permutation. + */ + Permute( dst, D, FinalPermuteMap, 8 ); + return( dst ); + } /* auth_DEShash */ + + +static const uchar SMB_LMhash_Magic[8] = + { 'K', 'G', 'S', '!', '@', '#', '$', '%' }; + +uchar *auth_LMhash( uchar *dst, const uchar *pwd, const int pwdlen ) + /* ------------------------------------------------------------------------ ** + * Generate an LM Hash from the input password. + * + * Input: dst - Pointer to a location to which to write the LM Hash. + * Requires 16 bytes minimum. + * pwd - Source password. Should be in OEM charset (extended + * ASCII) format in all upper-case, but this + * implementation doesn't really care. See the notes + * below. + * pwdlen - Length, in bytes, of the password. Normally, this + * will be strlen( pwd ). + * + * Output: Pointer to the resulting LM hash (same as ). + * + * Notes: This function does not convert the input password to upper + * case. The upper-case conversion should be done before the + * password gets this far. DOS codepage handling and such + * should be taken into consideration. Rather than attempt to + * work out all those details here, the function assumes that + * the password is in the correct form before it reaches this + * point. + * + * ------------------------------------------------------------------------ ** + */ + { + int i, + max14; + uint8_t tmp_pwd[14] = { 0,0,0,0,0,0,0,0,0,0,0,0,0,0 }; + + /* Copy at most 14 bytes of into . + * If the password is less than 14 bytes long + * the rest will be nul padded. + */ + max14 = pwdlen > 14 ? 14 : pwdlen; + for( i = 0; i < max14; i++ ) + tmp_pwd[i] = pwd[i]; + + /* The password is split into two 7-byte keys, each of which + * are used to DES-encrypt the magic string. The results are + * concatonated to produce the 16-byte LM Hash. + */ + (void)auth_DEShash( dst, tmp_pwd, SMB_LMhash_Magic ); + (void)auth_DEShash( &dst[8], &tmp_pwd[7], SMB_LMhash_Magic ); + + /* Return a pointer to the result. + */ + return( dst ); + } /* auth_LMhash */ diff --git a/SoObjects/SOGo/lmhash.h b/SoObjects/SOGo/lmhash.h new file mode 100644 index 000000000..cfcece2a4 --- /dev/null +++ b/SoObjects/SOGo/lmhash.h @@ -0,0 +1,95 @@ +#ifndef AUTH_LMHASH_H +#define AUTH_LMHASH_H + +typedef unsigned char uchar; + +/* ========================================================================== ** + * + * LMhash.h + * + * Copyright: + * Copyright (C) 2004 by Christopher R. Hertel + * + * Email: crh@ubiqx.mn.org + * + * $Id: LMhash.h,v 0.1 2004/05/30 02:26:31 crh Exp $ + * + * -------------------------------------------------------------------------- ** + * + * Description: + * + * Implemention of the LAN Manager hash (LM hash) and LM response + * algorithms. + * + * -------------------------------------------------------------------------- ** + * + * License: + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * -------------------------------------------------------------------------- ** + * + * Notes: + * + * This module implements the LM hash. The NT hash is simply the MD4() of + * the password, so we don't need a separate implementation of that. This + * module also implements the LM response, which can be combined with the + * NT hash to produce the NTLM response. + * + * This implementation was created based on the description in my own book. + * The book description was, in turn, written after studying many existing + * examples in various documentation. Jeremy Allison and Andrew Tridgell + * deserve lots of credit for having figured out the secrets of Lan Manager + * authentication many years ago. + * + * See: + * Implementing CIFS - the Common Internet File System + * by your truly. ISBN 0-13-047116-X, Prentice Hall PTR., August 2003 + * Section 15.3, in particular. + * (Online at: http://ubiqx.org/cifs/SMB.html#SMB.8.3) + * + * ========================================================================== ** + */ + +/* -------------------------------------------------------------------------- ** + * Functions: + */ + +uchar *auth_LMhash( uchar *dst, const uchar *pwd, const int pwdlen ); + /* ------------------------------------------------------------------------ ** + * Generate an LM Hash from the input password. + * + * Input: dst - Pointer to a location to which to write the LM Hash. + * Requires 16 bytes minimum. + * pwd - Source password. Should be in OEM charset (extended + * ASCII) format in all upper-case, but this + * implementation doesn't really care. See the notes + * below. + * pwdlen - Length, in bytes, of the password. Normally, this + * will be strlen( pwd ). + * + * Output: Pointer to the resulting LM hash (same as ). + * + * Notes: This function does not convert the input password to upper + * case. The upper-case conversion should be done before the + * password gets this far. DOS codepage handling and such + * should be taken into consideration. Rather than attempt to + * work out all those details here, the function assumes that + * the password is in the correct form before it reaches this + * point. + * + * ------------------------------------------------------------------------ ** + */ +#endif diff --git a/Tools/SOGoTool.h b/Tools/SOGoTool.h index bbc4de098..8eb67d417 100644 --- a/Tools/SOGoTool.h +++ b/Tools/SOGoTool.h @@ -1,8 +1,6 @@ /* SOGoTool.h - this file is part of SOGo * - * Copyright (C) 2009-2011 Inverse inc. - * - * Author: Wolfgang Sourdeau + * Copyright (C) 2009-2015 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 diff --git a/Tools/SOGoTool.m b/Tools/SOGoTool.m index 99cbc2b3b..6bd230c3d 100644 --- a/Tools/SOGoTool.m +++ b/Tools/SOGoTool.m @@ -1,8 +1,6 @@ /* SOGoTool.m - this file is part of SOGo * - * Copyright (C) 2009-2011 Inverse inc. - * - * Author: Wolfgang Sourdeau + * Copyright (C) 2009-2015 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 diff --git a/Tools/SOGoToolUserPreferences.m b/Tools/SOGoToolUserPreferences.m index a6255d6f0..4f981cb4f 100644 --- a/Tools/SOGoToolUserPreferences.m +++ b/Tools/SOGoToolUserPreferences.m @@ -254,7 +254,7 @@ typedef enum // // to achieve what we want. // - if (o && [o count] == 1) + if (o && [o isKindOfClass: [NSDictionary class]] && [o count] == 1) { [source setObject: [[o allValues] lastObject] forKey: key]; } diff --git a/UI/MailerUI/Arabic.lproj/Localizable.strings b/UI/MailerUI/Arabic.lproj/Localizable.strings index deb1bc17e..490cea964 100644 --- a/UI/MailerUI/Arabic.lproj/Localizable.strings +++ b/UI/MailerUI/Arabic.lproj/Localizable.strings @@ -79,7 +79,6 @@ "Remove this folder" = "مسح المجلد"; "Erase mails from this folder" = "مسح الرسائل من هذا المجلد"; "Expunge this folder" = "محو هذا المجلد"; -"Archive This Folder" = "أرشفة هذا المجلد"; "Modify the acl of this folder" = "تعديل قائمة الصلاحيات لهذا المجلد"; "Saved Messages.zip" = "حفظ Messages.zip"; diff --git a/UI/MailerUI/Danish.lproj/Localizable.strings b/UI/MailerUI/Danish.lproj/Localizable.strings index 7a60b99c7..e668f8cae 100644 --- a/UI/MailerUI/Danish.lproj/Localizable.strings +++ b/UI/MailerUI/Danish.lproj/Localizable.strings @@ -79,7 +79,6 @@ "Remove this folder" = "Fjern denne mappe"; "Erase mails from this folder" = "Slet beskeder fra denne mappe"; "Expunge this folder" = "Slet denne mappe"; -"Archive This Folder" = "Gem denne mappe"; "Modify the acl of this folder" = "Rediger ACL i denne mappe"; "Saved Messages.zip" = "Gemte beskeder.zip"; diff --git a/UI/MailerUI/Icelandic.lproj/Localizable.strings b/UI/MailerUI/Icelandic.lproj/Localizable.strings index 21cf027cf..375f4e6e7 100644 --- a/UI/MailerUI/Icelandic.lproj/Localizable.strings +++ b/UI/MailerUI/Icelandic.lproj/Localizable.strings @@ -76,7 +76,6 @@ "Remove this folder" = "Fjarlægja þessa möppu"; "Erase mails from this folder" = "Eyða bréfum úr þessari möppu"; "Expunge this folder" = "Afmá allt í þessari möppu"; -"Archive This Folder" = "Setja þessa möppu í geymslu"; "Modify the acl of this folder" = "Breyta aðgangsstýringum fyrir þessa möppu"; "Saved Messages.zip" = "vistud_skilabod.zip"; diff --git a/UI/MailerUI/Italian.lproj/Localizable.strings b/UI/MailerUI/Italian.lproj/Localizable.strings index 79ae3f99a..0bf331060 100644 --- a/UI/MailerUI/Italian.lproj/Localizable.strings +++ b/UI/MailerUI/Italian.lproj/Localizable.strings @@ -79,7 +79,6 @@ "Remove this folder" = "Rimuovi questa cartella"; "Erase mails from this folder" = "Elimina emails da questa cartella"; "Expunge this folder" = "Pulisci questa cartella"; -"Archive This Folder" = "Archivia questa cartella"; "Modify the acl of this folder" = "Modifica i permessi per questa cartella"; "Saved Messages.zip" = "Salvato Messages.zip"; diff --git a/UI/MailerUI/NorwegianNynorsk.lproj/Localizable.strings b/UI/MailerUI/NorwegianNynorsk.lproj/Localizable.strings index 6cd8f6e8f..776cc23e6 100644 --- a/UI/MailerUI/NorwegianNynorsk.lproj/Localizable.strings +++ b/UI/MailerUI/NorwegianNynorsk.lproj/Localizable.strings @@ -76,7 +76,6 @@ "Remove this folder" = "Ta bort mappen"; "Erase mails from this folder" = "Slett meldinger fra mappen"; "Expunge this folder" = "Fjern mappen"; -"Archive This Folder" = "Arkiver denne mappen"; "Modify the acl of this folder" = "Endre adgangsrettigheter til mappen"; "Saved Messages.zip" = "Lagrete Meldinger.zip"; diff --git a/UI/MailerUI/Swedish.lproj/Localizable.strings b/UI/MailerUI/Swedish.lproj/Localizable.strings index 207ab4c15..ee18ce90f 100644 --- a/UI/MailerUI/Swedish.lproj/Localizable.strings +++ b/UI/MailerUI/Swedish.lproj/Localizable.strings @@ -76,7 +76,6 @@ "Remove this folder" = "Ta bort mappen"; "Erase mails from this folder" = "Radera meddelanden från mappen"; "Expunge this folder" = "Radera mappen"; -"Archive This Folder" = "Arkivera mappen"; "Modify the acl of this folder" = "Ändra åtkomsträttigheter på mappen"; "Saved Messages.zip" = "Sparat Messages.zip"; diff --git a/UI/MailerUI/UIxMailEditor.m b/UI/MailerUI/UIxMailEditor.m index 80c29f512..a9bf3a65c 100644 --- a/UI/MailerUI/UIxMailEditor.m +++ b/UI/MailerUI/UIxMailEditor.m @@ -1,6 +1,6 @@ /* Copyright (C) 2004-2005 SKYRIX Software AG - Copyright (C) 2008-2014 Inverse inc. + Copyright (C) 2008-2015 Inverse inc. This file is part of SOGo. @@ -230,7 +230,15 @@ static NSArray *infoKeys = nil; - (NSString *) localeCode { // WARNING : NSLocaleCode is not defined in - return [locale objectForKey: @"NSLocaleCode"]; + // Region subtag must be separated by a dash + NSMutableString *s = [NSMutableString stringWithString: [locale objectForKey: @"NSLocaleCode"]]; + + [s replaceOccurrencesOfString: @"_" + withString: @"-" + options: 0 + range: NSMakeRange(0, [s length])]; + + return s; } - (void) setFrom: (NSString *) newFrom diff --git a/UI/MailerUI/UIxMailFolderActions.m b/UI/MailerUI/UIxMailFolderActions.m index e04bf50bc..4515eac4f 100644 --- a/UI/MailerUI/UIxMailFolderActions.m +++ b/UI/MailerUI/UIxMailFolderActions.m @@ -712,19 +712,26 @@ WORequest *request; SOGoMailFolder *co; NSException *error; - NSArray *msgUIDs, *flags; + NSArray *msgUIDs; + NSMutableArray *flags; NSString *operation; NSDictionary *content, *result; BOOL addOrRemove; NGImap4Client *client; + int i; + request = [context request]; content = [[request contentAsString] objectFromJSONString]; - flags = [NSArray arrayWithObject:[content objectForKey:@"flags"]]; + flags = [NSMutableArray arrayWithObject:[content objectForKey:@"flags"]]; msgUIDs = [NSArray arrayWithArray:[content objectForKey:@"msgUIDs"]]; operation = [content objectForKey:@"operation"]; addOrRemove = ([operation isEqualToString:@"add"]? YES: NO); + // We unescape our flags + for (i = [flags count]-1; i >= 0; i--) + [flags replaceObjectAtIndex: i withObject: [[flags objectAtIndex: i] fromCSSIdentifier]]; + co = [self clientObject]; client = [[co imap4Connection] client]; [[co imap4Connection] selectFolder: [co imap4URL]]; diff --git a/UI/MailerUI/Ukrainian.lproj/Localizable.strings b/UI/MailerUI/Ukrainian.lproj/Localizable.strings index f9a64ebac..d7ae77b28 100644 --- a/UI/MailerUI/Ukrainian.lproj/Localizable.strings +++ b/UI/MailerUI/Ukrainian.lproj/Localizable.strings @@ -79,7 +79,6 @@ "Remove this folder" = "Вилучати цю теку"; "Erase mails from this folder" = "Вилучати повідомлення з цієї теки"; "Expunge this folder" = "Позначати листи як вилучені"; -"Archive This Folder" = "Архівувати цю теку"; "Modify the acl of this folder" = "Керувати правами доступу до цієї теки"; "Saved Messages.zip" = "Збережено Messages.zip"; diff --git a/UI/MailerUI/Welsh.lproj/Localizable.strings b/UI/MailerUI/Welsh.lproj/Localizable.strings index 87fc7fec3..8a4d93d7e 100644 --- a/UI/MailerUI/Welsh.lproj/Localizable.strings +++ b/UI/MailerUI/Welsh.lproj/Localizable.strings @@ -76,7 +76,6 @@ "Remove this folder" = "Dileu'r ffolder hwn"; "Erase mails from this folder" = "Dileu ebost o'r ffolder hwn"; "Expunge this folder" = "Dileu'r ffolder hwn"; -"Archive This Folder" = "Archive This Folder"; "Modify the acl of this folder" = "Newid acl y ffolder"; "Saved Messages.zip" = "Saved Messages.zip"; diff --git a/UI/MainUI/SOGoUserHomePage.m b/UI/MainUI/SOGoUserHomePage.m index 2a8415d70..c6223e656 100644 --- a/UI/MainUI/SOGoUserHomePage.m +++ b/UI/MainUI/SOGoUserHomePage.m @@ -155,6 +155,7 @@ delta = [[currentDate timeZoneDetail] timeZoneSecondsFromGMT] - [[startDate timeZoneDetail] timeZoneSecondsFromGMT]; startInterval += (delta/60/15); + startInterval = (startInterval < -4 ? -4 : startInterval); currentDate = [record objectForKey: @"endDate"]; if ([currentDate earlierDate: endDate] == endDate) @@ -165,6 +166,8 @@ delta = [[currentDate timeZoneDetail] timeZoneSecondsFromGMT] - [[startDate timeZoneDetail] timeZoneSecondsFromGMT]; endInterval += (delta/60/15); + endInterval = (endInterval < 0 ? 0 : endInterval); + endInterval = (endInterval > itemCount+4 ? itemCount+4 : endInterval); // Update bit string representation // If the user is a resource with restristed amount of bookings, keep the sum of overlapping events @@ -230,7 +233,7 @@ intervals = interval / intervalSeconds + 8; // Build a bit string representation of the freebusy data for the period - freeBusyItems = NSZoneCalloc (NULL, intervals, sizeof (int)); + freeBusyItems = calloc(intervals, sizeof (unsigned int)); [self _fillFreeBusyItems: (freeBusyItems+4) count: (intervals-4) withRecords: [fb fetchFreeBusyInfosFrom: start to: end forContact: uid] @@ -243,7 +246,7 @@ { [freeBusy addObject: [NSString stringWithFormat: @"%d", *(freeBusyItems + count)]]; } - NSZoneFree (NULL, freeBusyItems); + free(freeBusyItems); // Return a NSString representation return [freeBusy componentsJoinedByString: @","]; diff --git a/UI/PreferencesUI/Czech.lproj/Localizable.strings b/UI/PreferencesUI/Czech.lproj/Localizable.strings index 891be5277..8d362aef4 100644 --- a/UI/PreferencesUI/Czech.lproj/Localizable.strings +++ b/UI/PreferencesUI/Czech.lproj/Localizable.strings @@ -29,6 +29,7 @@ "Days between responses :" = "Počet dnů mezi odpověďmi :"; "Do not send responses to mailing lists" = "Neposílat odpovědi do konferencí"; "Disable auto reply on" = "Automatické odpovědi vypnout dne"; +"Always send vacation message response" = "Vždy posílat zprávu v nepřítomnosti"; "Please specify your message and your email addresses for which you want to enable auto reply." = "Prosím zadejte text zprávy a své emailové adresy, pro které chcete zasílání v nepřítomnosti použít."; "Your vacation message must not end with a single dot on a line." = "Vaše zpráva v nepřítomnosti nesmí končit samotnou tečkou na řádce."; @@ -40,6 +41,9 @@ "Keep a copy" = "Ponechat kopii"; "Please specify an address to which you want to forward your messages." = "Prosím zadejte adresu, na kterou chcete své zprávy přeposílat."; +"You are not allowed to forward your messages to an external email address." = "Nemáte povoleno přeposílání Vašich zpráv na externí emailovou adresu."; +"You are not allowed to forward your messages to an internal email address." = "Nemáte povoleno přeposílání Vašich zpráv na interní emailovou adresu."; + /* d & t */ "Current Time Zone :" = "Současné časové pásmo :"; diff --git a/UI/PreferencesUI/English.lproj/Localizable.strings b/UI/PreferencesUI/English.lproj/Localizable.strings index 538bd3d9b..c0885ec35 100644 --- a/UI/PreferencesUI/English.lproj/Localizable.strings +++ b/UI/PreferencesUI/English.lproj/Localizable.strings @@ -29,6 +29,7 @@ "Days between responses :" = "Days between responses:"; "Do not send responses to mailing lists" = "Do not send responses to mailing lists"; "Disable auto reply on" = "Disable auto reply on"; +"Always send vacation message response" = "Always send vacation message response"; "Please specify your message and your email addresses for which you want to enable auto reply." = "Please specify your message and your email addresses for which you want to enable auto reply."; "Your vacation message must not end with a single dot on a line." = "Your vacation message must not end with a single dot on a line."; @@ -40,6 +41,9 @@ "Keep a copy" = "Keep a copy"; "Please specify an address to which you want to forward your messages." = "Please specify an address to which you want to forward your messages."; +"You are not allowed to forward your messages to an external email address." = "You are not allowed to forward your messages to an external email address."; +"You are not allowed to forward your messages to an internal email address." = "You are not allowed to forward your messages to an internal email address."; + /* d & t */ "Current Time Zone :" = "Current Time Zone:"; diff --git a/UI/PreferencesUI/Finnish.lproj/Localizable.strings b/UI/PreferencesUI/Finnish.lproj/Localizable.strings index 3df3e7718..912e0b11b 100644 --- a/UI/PreferencesUI/Finnish.lproj/Localizable.strings +++ b/UI/PreferencesUI/Finnish.lproj/Localizable.strings @@ -29,6 +29,7 @@ "Days between responses :" = "Päivät vastausten välillä:"; "Do not send responses to mailing lists" = "Älä lähetä vastauksia postituslistoille"; "Disable auto reply on" = "Poista automaattivastaus käytöstä"; +"Always send vacation message response" = "Lähetä lomaviestivastaus aina"; "Please specify your message and your email addresses for which you want to enable auto reply." = "Ole hyvä ja määritä viesti sekä sähköpostiosoitteet joille haluat aktivoida automaattivastauksen."; "Your vacation message must not end with a single dot on a line." = "Lomaviestisi ei saa päättyä yksittäiseen pisteeseen viimeisellä rivillä."; @@ -40,6 +41,9 @@ "Keep a copy" = "Pidä kopio"; "Please specify an address to which you want to forward your messages." = "Ole hyvä ja määritä sähköpostiosoite johon haluat edelleenlähettää viestisi."; +"You are not allowed to forward your messages to an external email address." = "Sinulla ei ole lupaa välittää viestejä ulkoiseen sähköpostiosoitteeseen."; +"You are not allowed to forward your messages to an internal email address." = "Sinulla ei ole lupaa välittää viestejä sisäiseen sähköpostiosoitteeseen."; + /* d & t */ "Current Time Zone :" = "Nykyinen aikavyöhyke:"; diff --git a/UI/PreferencesUI/French.lproj/Localizable.strings b/UI/PreferencesUI/French.lproj/Localizable.strings index 4caec8c78..56f72a0ad 100644 --- a/UI/PreferencesUI/French.lproj/Localizable.strings +++ b/UI/PreferencesUI/French.lproj/Localizable.strings @@ -29,6 +29,7 @@ "Days between responses :" = "Nombre de jours entre réponses :"; "Do not send responses to mailing lists" = "Ne pas envoyer les réponses aux listes de diffusion"; "Disable auto reply on" = "Désactiver la réponse automatique le"; +"Always send vacation message response" = "Toujours envoyer le message de vacances prolongées"; "Please specify your message and your email addresses for which you want to enable auto reply." = "Veuillez définir un message et vos adresses pour lesquelles pour désirez activer une réponse automatique."; "Your vacation message must not end with a single dot on a line." = "Le message de vacances ne doit pas se terminer par une ligne ne contenant qu'un point."; @@ -40,6 +41,9 @@ "Keep a copy" = "Garder une copie"; "Please specify an address to which you want to forward your messages." = "Veuillez définir une adresse à laquelle vous désirez transférer automatiquement vos nouveaux messages."; +"You are not allowed to forward your messages to an external email address." = "Il est interdit de renvoyer vos messages vers une adresse externe."; +"You are not allowed to forward your messages to an internal email address." = "Il est interdit de renvoyer vos messages vers une adresse interne."; + /* d & t */ "Current Time Zone :" = "Fuseau horaire en cours :"; diff --git a/UI/PreferencesUI/German.lproj/Localizable.strings b/UI/PreferencesUI/German.lproj/Localizable.strings index 9b66de389..473fa7706 100644 --- a/UI/PreferencesUI/German.lproj/Localizable.strings +++ b/UI/PreferencesUI/German.lproj/Localizable.strings @@ -29,6 +29,7 @@ "Days between responses :" = "Tage zwischen automatischen Abwesenheitsnachrichten:"; "Do not send responses to mailing lists" = "Keine automatischen Abwesenheitsnachrichten an Mailinglisten senden"; "Disable auto reply on" = "Automatische Abwesenheitsnachricht abschalten am"; +"Always send vacation message response" = "Immer Abwesenheitsmeldung senden"; "Please specify your message and your email addresses for which you want to enable auto reply." = "Bitte ihren Nachrichtentext und ihre E-Mail-Adressen angeben, für die automatische Abwesenheitsnachrichten aktiviert werden sollen."; "Your vacation message must not end with a single dot on a line." = "Ihre Abwesenheitsnachricht darf nicht mit einem alleinstehenden Punkt in einer Zeile enden."; @@ -40,6 +41,9 @@ "Keep a copy" = "Eine Kopie behalten"; "Please specify an address to which you want to forward your messages." = "Bitte Adresse angeben, an die Ihre Nachrichten weitergeleitet werden sollen."; +"You are not allowed to forward your messages to an external email address." = "Es ist Ihnen nicht erlaubt, Ihre Nachrichten an externe E-Mail-Adressen weiterzuleiten."; +"You are not allowed to forward your messages to an internal email address." = "Es ist Ihnen nicht erlaubt, Ihre Nachrichten an interne E-Mail-Adressen weiterzuleiten."; + /* d & t */ "Current Time Zone :" = "Aktuelle Zeitzone:"; diff --git a/UI/PreferencesUI/Hungarian.lproj/Localizable.strings b/UI/PreferencesUI/Hungarian.lproj/Localizable.strings index 9532bf3b3..b90ff2297 100644 --- a/UI/PreferencesUI/Hungarian.lproj/Localizable.strings +++ b/UI/PreferencesUI/Hungarian.lproj/Localizable.strings @@ -29,6 +29,7 @@ "Days between responses :" = "Válaszok közötti napok száma:"; "Do not send responses to mailing lists" = "Levelező listákra válasz mellőzése"; "Disable auto reply on" = "Automatikus válasz tiltása"; +"Always send vacation message response" = "Mindig küldjön távollét üzenet választ"; "Please specify your message and your email addresses for which you want to enable auto reply." = "Kérem adja meg az üzenetét valamint a címeket, amelyekre engedélyezi az automatikus válasz küldését."; "Your vacation message must not end with a single dot on a line." = "A távollét üzenet utolsó sora nem tartalmazhat egy egyedülálló . karatert."; @@ -40,6 +41,9 @@ "Keep a copy" = "Másolat megtartása"; "Please specify an address to which you want to forward your messages." = "Kérem adjon meg egy címet, amelyre a leveleit továbbítani kívánja."; +"You are not allowed to forward your messages to an external email address." = "Ön számára nem endélyezett az üzenetek továbbítása külső email címre."; +"You are not allowed to forward your messages to an internal email address." = "Ön számára nem endélyezett az üzenetek továbbítása belső email címre."; + /* d & t */ "Current Time Zone :" = "Időzóna :"; diff --git a/UI/PreferencesUI/UIxPreferences.m b/UI/PreferencesUI/UIxPreferences.m index a43a8e744..6e54bc043 100644 --- a/UI/PreferencesUI/UIxPreferences.m +++ b/UI/PreferencesUI/UIxPreferences.m @@ -1,6 +1,6 @@ /* UIxPreferences.m - this file is part of SOGo * - * Copyright (C) 2007-2014 Inverse inc. + * Copyright (C) 2007-2015 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 @@ -1127,6 +1127,30 @@ static NSArray *reminderValues = nil; return ignore; } +// +// See http://sogo.nu/bugs/view.php?id=2332 for details +// +- (void) setAlwaysSend: (BOOL) ignoreLists +{ + [vacationOptions setObject: [NSNumber numberWithBool: ignoreLists] + forKey: @"alwaysSend"]; +} + +- (BOOL) alwaysSend +{ + NSNumber *obj; + BOOL ignore; + + obj = [vacationOptions objectForKey: @"alwaysSend"]; + + if (obj == nil) + ignore = NO; // defaults to NO + else + ignore = [obj boolValue]; + + return ignore; +} + - (BOOL) enableVacationEndDate { return [[vacationOptions objectForKey: @"endDateEnabled"] boolValue]; @@ -1214,6 +1238,15 @@ static NSArray *reminderValues = nil; return [[forwardOptions objectForKey: @"keepCopy"] boolValue]; } +- (NSString *) forwardConstraints +{ + SOGoDomainDefaults *dd; + + dd = [[context activeUser] domainDefaults]; + + return [NSString stringWithFormat: @"%d", [dd forwardConstraints]]; +} + /* main */ - (NSArray *) availableModules diff --git a/UI/Scheduler/UIxAttendeesEditor.h b/UI/Scheduler/UIxAttendeesEditor.h index 530c33c51..4e08ccfa1 100644 --- a/UI/Scheduler/UIxAttendeesEditor.h +++ b/UI/Scheduler/UIxAttendeesEditor.h @@ -1,8 +1,6 @@ /* UIxAttendeesEditor.h - this file is part of SOGo * - * Copyright (C) 2007-2012 Inverse inc. - * - * Author: Wolfgang Sourdeau + * Copyright (C) 2007-2015 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 diff --git a/UI/Scheduler/UIxAttendeesEditor.m b/UI/Scheduler/UIxAttendeesEditor.m index 40379d48e..ae7de6700 100644 --- a/UI/Scheduler/UIxAttendeesEditor.m +++ b/UI/Scheduler/UIxAttendeesEditor.m @@ -1,8 +1,6 @@ /* UIxAttendeesEditor.m - this file is part of SOGo * - * Copyright (C) 2007-2012 Inverse inc. - * - * Author: Wolfgang Sourdeau + * Copyright (C) 2007-2015 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 diff --git a/UI/Scheduler/UIxCalMainView.m b/UI/Scheduler/UIxCalMainView.m index f9fd125eb..e13a05738 100644 --- a/UI/Scheduler/UIxCalMainView.m +++ b/UI/Scheduler/UIxCalMainView.m @@ -1,7 +1,6 @@ /* UIxCalMainView.m - this file is part of SOGo * - * Copyright (C) 2006-2014 Inverse inc. - * + * Copyright (C) 2006-2015 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 @@ -82,7 +81,15 @@ - (NSString *) localeCode { // WARNING : NSLocaleCode is not defined in - return [locale objectForKey: @"NSLocaleCode"]; + // Region subtag must be separated by a dash + NSMutableString *s = [NSMutableString stringWithString: [locale objectForKey: @"NSLocaleCode"]]; + + [s replaceOccurrencesOfString: @"_" + withString: @"-" + options: 0 + range: NSMakeRange(0, [s length])]; + + return s; } - (NSArray *) monthMenuItems diff --git a/UI/Scheduler/UIxComponentEditor.h b/UI/Scheduler/UIxComponentEditor.h index 201065c11..b660dc436 100644 --- a/UI/Scheduler/UIxComponentEditor.h +++ b/UI/Scheduler/UIxComponentEditor.h @@ -1,6 +1,6 @@ /* UIxComponentEditor.h - this file is part of SOGo * - * Copyright (C) 2006-2014 Inverse inc. + * Copyright (C) 2006-2015 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 diff --git a/UI/Scheduler/UIxComponentEditor.m b/UI/Scheduler/UIxComponentEditor.m index c1d666e63..a7581e57c 100644 --- a/UI/Scheduler/UIxComponentEditor.m +++ b/UI/Scheduler/UIxComponentEditor.m @@ -176,7 +176,6 @@ iRANGE(2); [self setIsCycleEndNever]; componentOwner = @""; organizer = nil; - //organizerIdentity = nil; organizerProfile = nil; ownerAsAttendee = nil; attendee = nil; @@ -213,7 +212,6 @@ iRANGE(2); [title release]; [location release]; [organizer release]; - //[organizerIdentity release]; [organizerProfile release]; [ownerAsAttendee release]; [comment release]; @@ -820,25 +818,27 @@ iRANGE(2); uid = email; [profile setObject: name - forKey: @"name"]; + forKey: @"name"]; [profile setObject: email - forKey: @"email"]; + forKey: @"email"]; if (partstat == nil || ![partstat length]) partstat = @"accepted"; + [profile setObject: partstat - forKey: @"partstat"]; + forKey: @"partstat"]; if (role == nil || ![role length]) role = @"chair"; + [profile setObject: role - forKey: @"role"]; + forKey: @"role"]; organizerProfile = [NSDictionary dictionaryWithObject: profile forKey: uid]; [organizerProfile retain]; } - + return organizerProfile; } @@ -897,30 +897,6 @@ iRANGE(2); return [[[[self organizerProfile] allValues] lastObject] jsonRepresentation]; } -// - (BOOL) canBeOrganizer -// { -// NSString *owner; -// SOGoObject *co; -// SOGoUser *currentUser; -// BOOL hasOrganizer; -// SoSecurityManager *sm; - -// co = [self clientObject]; -// owner = [co ownerInContext: context]; -// currentUser = [context activeUser]; - -// hasOrganizer = ([[organizer value: 0] length] > 0); - -// sm = [SoSecurityManager sharedSecurityManager]; - -// return ([co isNew] -// || (([owner isEqualToString: [currentUser login]] -// || ![sm validatePermission: SOGoCalendarPerm_ModifyComponent -// onObject: co -// inContext: context]) -// && (!hasOrganizer || [component userIsOrganizer: currentUser]))); -// } - - (BOOL) hasOrganizer { // We check if there's an organizer and if it's not ourself @@ -948,51 +924,8 @@ iRANGE(2); } return NO; - - //return ([[organizer value: 0] length] && ![self canBeOrganizer]); } -//- (void) setOrganizerIdentity: (NSDictionary *) newOrganizerIdentity -//{ -// ASSIGN (organizerIdentity, newOrganizerIdentity); -//} - -// - (NSDictionary *) organizerIdentity -// { -// NSArray *allIdentities; -// NSEnumerator *identities; -// NSDictionary *currentIdentity; -// NSString *orgEmail; - -// orgEmail = [organizer rfc822Email]; -// if (!organizerIdentity) -// { -// if ([orgEmail length]) -// { -// allIdentities = [[context activeUser] allIdentities]; -// identities = [allIdentities objectEnumerator]; -// while (!organizerIdentity -// && ((currentIdentity = [identities nextObject]))) -// if ([[currentIdentity objectForKey: @"email"] -// caseInsensitiveCompare: orgEmail] -// == NSOrderedSame) -// ASSIGN (organizerIdentity, currentIdentity); -// } -// } - -// return organizerIdentity; -// } - -//- (NSArray *) organizerList -//{ -// return [[context activeUser] allIdentities]; -//} - -//- (NSString *) itemOrganizerText -//{ -// return [item keysWithFormat: @"%{fullName} <%{email}>"]; -//} - - (BOOL) hasAttendees { return ([[component attendees] count] > 0); diff --git a/UI/Templates/MailerUI/UIxMailMainFrame.wox b/UI/Templates/MailerUI/UIxMailMainFrame.wox index aa924a35d..4aaf37b3a 100644 --- a/UI/Templates/MailerUI/UIxMailMainFrame.wox +++ b/UI/Templates/MailerUI/UIxMailMainFrame.wox @@ -85,7 +85,7 @@
  • -
  • +
  • @@ -103,7 +103,7 @@
  • -
  • +
  • diff --git a/UI/Templates/PreferencesUI/UIxPreferences.wox b/UI/Templates/PreferencesUI/UIxPreferences.wox index a89c8a204..6459c874d 100644 --- a/UI/Templates/PreferencesUI/UIxPreferences.wox +++ b/UI/Templates/PreferencesUI/UIxPreferences.wox @@ -600,7 +600,7 @@

    - +
    +
    + diff --git a/UI/WebServerResources/JavascriptAPIExtensions.js b/UI/WebServerResources/JavascriptAPIExtensions.js index d71b92a10..190a8e99b 100644 --- a/UI/WebServerResources/JavascriptAPIExtensions.js +++ b/UI/WebServerResources/JavascriptAPIExtensions.js @@ -218,7 +218,7 @@ Date.prototype.stringWithSeparator = function(separator) { Date.prototype.addDays = function(nbrDays) { var milliSeconds = this.getTime(); - milliSeconds += 86400000 * nbrDays; + milliSeconds += 86400000 * Math.round(nbrDays); this.setTime(milliSeconds); }; diff --git a/UI/WebServerResources/UIxAttendeesEditor.js b/UI/WebServerResources/UIxAttendeesEditor.js index af2fc0d5c..7ca8e2874 100644 --- a/UI/WebServerResources/UIxAttendeesEditor.js +++ b/UI/WebServerResources/UIxAttendeesEditor.js @@ -896,9 +896,8 @@ _freeBusyCacheEntry.prototype = { if (this.entries.length > offset) { var adjustedEd = ed.beginOfDay(); var nbrDays = adjustedSd.deltaDays(adjustedEd) + 1; - var nbrQu = nbrDays * 96; - var offsetEnd = offset + nbrQu; - if (this.entries.length >= offsetEnd) { + var offsetEnd = offset + (nbrDays * 96); + if (Math.round(this.entries.length/96) >= nbrDays) { entries = this.entries.slice(offset, offsetEnd); } } @@ -912,15 +911,19 @@ _freeBusyCacheEntry.prototype = { var adjustedSd = sd.beginOfDay(); var adjustedEd = ed.beginOfDay(); - var nbrDays = adjustedSd.deltaDays(adjustedEd) + 1; + var start = adjustedSd.clone(); + start.addDays(-7); + var end = adjustedEd.clone(); + end.addDays(7); if (this.startDate) { fetchDates = []; if (adjustedSd.getTime() < this.startDate.getTime()) { - var start = adjustedSd.clone(); - start.addDays(-7); - var end = this.startDate.beginOfDay(); - end.addDays(-1); + // Period extends to before current start + if (end.getTime() >= this.startDate.getTime()) { + end = this.startDate.beginOfDay(); + end.addDays(-1); + } fetchDates.push({ start: start, end: end }); } @@ -928,16 +931,16 @@ _freeBusyCacheEntry.prototype = { var nextDate = this.startDate.clone(); nextDate.addDays(currentNbrDays); if (adjustedEd.getTime() >= nextDate.getTime()) { - var end = nextDate.clone(); - end.addDays(7); - fetchDates.push({ start: nextDate, end: end }); + // Period extends to after current end + if (start.getTime() <= nextDate.getTime()) { + start = nextDate.beginOfDay(); + start.addDays(1); + } + fetchDates.push({ start: start, end: end }); } } else { - var start = adjustedSd.clone(); - start.addDays(-7); - var end = adjustedEd.clone(); - end.addDays(7); + // Initial range fetchDates = [ { start: start, end: end } ]; } @@ -945,18 +948,27 @@ _freeBusyCacheEntry.prototype = { }, integrateEntries: function fBCE_integrateEntries(entries, start, end) { + var days, merged = false; if (this.startDate) { if (start.getTime() < this.startDate) { - var days = start.deltaDays(this.startDate); + days = start.deltaDays(this.startDate); if (entries.length == (days * 96)) { + // New period is just before previous period this.startDate = start; this.entries = entries.concat(this.entries); + merged = true; } } else { - this.entries = this.entries.concat(entries); + days = this.startDate.deltaDays(start); + if (Math.round(this.entries.length/96) == days) { + // New period is just after previous period + this.entries = this.entries.concat(entries); + merged = true; + } } - } else { + } + if (!merged) { this.startDate = start; this.entries = entries; } @@ -1193,7 +1205,7 @@ function drawFbData(input, slots) { else { log("inconsistency between freebusy results and" + " the number of cells"); - log(" expecting: " + tds.length + " received: " + slots.length); + log(" expecting: " + (tds.length * 4) + " received: " + slots.length); } } @@ -1674,10 +1686,10 @@ function initTimeWidgets(widgets) { function onAdjustTime(event) { var endDate = window.getEndDate(); var startDate = window.getStartDate(); + var delta = 0; if (this.id.startsWith("start")) { // Start date was changed - var delta = window.getShadowStartDate().valueOf() - - startDate.valueOf(); + delta = window.getShadowStartDate().valueOf() - startDate.valueOf(); var newEndDate = new Date(endDate.valueOf() - delta); window.setEndDate(newEndDate); window.timeWidgets['end']['date'].updateShadowValue(); @@ -1687,20 +1699,27 @@ function onAdjustTime(event) { } else { // End date was changed - var delta = endDate.valueOf() - startDate.valueOf(); - if (delta < 0) { - showAlertDialog(labels.validate_endbeforestart); - var oldEndDate = window.getShadowEndDate(); - window.setEndDate(oldEndDate); - - window.timeWidgets['end']['date'].updateShadowValue(); - window.timeWidgets['end']['time'].updateShadowValue(); - window.timeWidgets['end']['time'].onChange(); // method from SOGoTimePicker + delta = window.getShadowEndDate().valueOf() - endDate.valueOf(); + if (delta != 0) { + var startEndDelta = endDate.valueOf() - startDate.valueOf(); + if (startEndDelta < 0) { + showAlertDialog(labels.validate_endbeforestart); + var oldEndDate = window.getShadowEndDate(); + window.setEndDate(oldEndDate); + window.timeWidgets['end']['date'].updateShadowValue(); + window.timeWidgets['end']['time'].updateShadowValue(); + window.timeWidgets['end']['time'].onChange(); // method from SOGoTimePicker + } + else { + window.timeWidgets['end']['date'].updateShadowValue(); + window.timeWidgets['end']['time'].updateShadowValue(); + } } } - // Specific function for the attendees editor - onTimeDateWidgetChange(); + if ($("freeBusyHeader").getElementsByTagName("th").length == 0 || delta != 0) + // Update freebusy data + onTimeDateWidgetChange(); } function _getDate(which) { @@ -1739,6 +1758,7 @@ function getShadowEndDate() { function _setDate(which, newDate) { window.timeWidgets[which]['date'].setInputAsDate(newDate); + jQuery(window.timeWidgets[which]['date']).closest('.date').datepicker('update'); if (!isAllDay) { window.timeWidgets[which]['time'].value = newDate.getDisplayHoursString(); if (window.timeWidgets[which]['time'].onChange) window.timeWidgets[which]['time'].onChange(); // method from SOGoTimePicker diff --git a/UI/WebServerResources/UIxMailEditor.js b/UI/WebServerResources/UIxMailEditor.js index 10c232fd7..60e10dfa9 100644 --- a/UI/WebServerResources/UIxMailEditor.js +++ b/UI/WebServerResources/UIxMailEditor.js @@ -14,7 +14,7 @@ var MailEditor = { var autoSaveTimer; function refreshDraftsFolder() { - if (window.opener && window.opener.open && !window.opener.closed) { + if (window.opener && window.opener.getUnseenCountForFolder) { var nodes = window.opener.$("mailboxTree").select("DIV[datatype=draft]"); window.opener.getUnseenCountForFolder(nodes[0].readAttribute("dataname")); } diff --git a/UI/WebServerResources/UIxPreferences.js b/UI/WebServerResources/UIxPreferences.js index 7c2d12652..8b1f59f9d 100644 --- a/UI/WebServerResources/UIxPreferences.js +++ b/UI/WebServerResources/UIxPreferences.js @@ -70,11 +70,42 @@ function savePreferences(sender) { if ($("enableForward") && $("enableForward").checked) { var addresses = $("forwardAddress").value.split(","); + + // We check if all addresses are valid for (var i = 0; i < addresses.length && sendForm; i++) if (!emailRE.test(addresses[i].strip())) { showAlertDialog(_("Please specify an address to which you want to forward your messages.")); sendForm = false; } + + // We check if we can only to internal/external addresses. + var constraints = parseInt(forwardConstraints); + + if (constraints > 0) { + // We first extract the list of 'known domains' to SOGo + var defaultAddresses = $("defaultEmailAddresses").value.split(/, */); + var domains = new Array(); + + defaultAddresses.each(function(adr) { + var domain = adr.split("@")[1]; + if (domain) { + domains.push(domain.toLowerCase()); + } + }); + + // We check if we're allowed or not to forward based on the domain defaults + for (var i = 0; i < addresses.length && sendForm; i++) { + var domain = addresses[i].split("@")[1].toLowerCase(); + if (domains.indexOf(domain) < 0 && constraints == 1) { + showAlertDialog(_("You are not allowed to forward your messages to an external email address.")); + sendForm = false; + } + else if (domains.indexOf(domain) >= 0 && constraints == 2) { + showAlertDialog(_("You are not allowed to forward your messages to an internal email address.")); + sendForm = false; + } + } + } } if (typeof sieveCapabilities != "undefined") { diff --git a/Version b/Version index 13e56d03b..e7b91ec62 100644 --- a/Version +++ b/Version @@ -4,4 +4,4 @@ MAJOR_VERSION=2 MINOR_VERSION=2 -SUBMINOR_VERSION=15 +SUBMINOR_VERSION=17a