0f432b654f
PidTag*EntryId properties were not being generated (which contain the email address and so on). Functionality on Outlook clients like "Reply All" were not working because of this (probably a lot more stuff related with email addresses). With multidomain support enabled outlook clients will use full email address (e.g. user@domain.com) as login. This change is needed because we were performing ldap queries on samdb using (sAMAccountName=UIDFieldName), being UIDFieldName the parameter configured in sogo.conf for that source. In multidomain environment this field could be `sAMAccountName` but it could not. Actually the more logical scenario will be to use `uid` field here (which will be just `user`, without the `@domain.com` part). SOGoUserManager will return `sAMAccountName` if the contact has it (in Outlook environment that means always) so it can (and must) be used to query samdb in MAPIStoreSamDBUtils properly. TL;DR: use sAMAccoutName instead of uid to query samdb
1161 lines
36 KiB
Objective-C
1161 lines
36 KiB
Objective-C
/* MAPIStoreMailVolatileMessage.m - this file is part of SOGo
|
|
*
|
|
* Copyright (C) 2011-2012 Inverse inc
|
|
*
|
|
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
|
|
*
|
|
* This file is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 3, or (at your option)
|
|
* any later version.
|
|
*
|
|
* This file is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; see the file COPYING. If not, write to
|
|
* the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
|
* Boston, MA 02111-1307, USA.
|
|
*/
|
|
|
|
/* TODO:
|
|
- calendar invitations
|
|
- merge some code in a common module with SOGoDraftObject
|
|
*/
|
|
|
|
#import <Foundation/NSArray.h>
|
|
#import <Foundation/NSData.h>
|
|
#import <Foundation/NSDictionary.h>
|
|
#import <Foundation/NSScanner.h>
|
|
#import <Foundation/NSString.h>
|
|
#import <Foundation/NSValue.h>
|
|
#import <NGExtensions/NGBase64Coding.h>
|
|
#import <NGExtensions/NGHashMap.h>
|
|
#import <NGExtensions/NSObject+Logs.h>
|
|
#import <NGExtensions/NSString+Encoding.h>
|
|
#import <NGMime/NGMimeBodyPart.h>
|
|
#import <NGMime/NGMimeMultipartBody.h>
|
|
#import <NGMail/NGMimeMessage.h>
|
|
#import <NGMail/NGMimeMessageGenerator.h>
|
|
#import <NGImap4/NGImap4Client.h>
|
|
#import <NGImap4/NGImap4Connection.h>
|
|
#import <SOGo/NSArray+Utilities.h>
|
|
#import <SOGo/NSCalendarDate+SOGo.h>
|
|
#import <SOGo/NSString+Utilities.h>
|
|
#import <SOGo/SOGoCacheObject.h>
|
|
#import <SOGo/SOGoDomainDefaults.h>
|
|
#import <SOGo/SOGoMailer.h>
|
|
#import <SOGo/SOGoUser.h>
|
|
#import <SOGo/SOGoUserManager.h>
|
|
#import <Mailer/SOGoMailFolder.h>
|
|
#import <Mailer/NSString+Mail.h>
|
|
|
|
#import "Codepages.h"
|
|
#import "MAPIStoreAttachment.h"
|
|
#import "MAPIStoreAttachmentTable.h"
|
|
#import "MAPIStoreContext.h"
|
|
#import "MAPIStoreMailFolder.h"
|
|
#import "MAPIStoreMIME.h"
|
|
#import "MAPIStoreMapping.h"
|
|
#import "MAPIStoreSamDBUtils.h"
|
|
#import "MAPIStoreTypes.h"
|
|
#import "MAPIStoreUserContext.h"
|
|
#import "NSData+MAPIStore.h"
|
|
#import "NSObject+MAPIStore.h"
|
|
#import "NSString+MAPIStore.h"
|
|
|
|
#import "MAPIStoreMailVolatileMessage.h"
|
|
|
|
#undef DEBUG
|
|
#include <mapistore/mapistore.h>
|
|
#include <mapistore/mapistore_errors.h>
|
|
#include <mapistore/mapistore_nameid.h>
|
|
|
|
static Class NSNumberK = Nil;
|
|
|
|
static NSString *recTypes[] = { @"orig", @"to", @"cc", @"bcc" };
|
|
|
|
//
|
|
// Useful extension that comes from Pantomime which is also
|
|
// released under the LGPL. We should eventually merge
|
|
// this with the same category found in SOPE's NGSmtpClient.m
|
|
// or simply drop sope-mime in favor of Pantomime
|
|
//
|
|
@interface NSMutableData (DataCleanupExtension)
|
|
|
|
- (NSRange) rangeOfCString: (const char *) theCString;
|
|
- (NSRange) rangeOfCString: (const char *) theCString
|
|
options: (unsigned int) theOptions
|
|
range: (NSRange) theRange;
|
|
@end
|
|
|
|
@implementation NSMutableData (DataCleanupExtension)
|
|
|
|
- (NSRange) rangeOfCString: (const char *) theCString
|
|
{
|
|
return [self rangeOfCString: theCString
|
|
options: 0
|
|
range: NSMakeRange(0,[self length])];
|
|
}
|
|
|
|
-(NSRange) rangeOfCString: (const char *) theCString
|
|
options: (unsigned int) theOptions
|
|
range: (NSRange) theRange
|
|
{
|
|
const char *b, *bytes;
|
|
int i, len, slen;
|
|
|
|
if (!theCString)
|
|
{
|
|
return NSMakeRange(NSNotFound,0);
|
|
}
|
|
|
|
bytes = [self bytes];
|
|
len = [self length];
|
|
slen = strlen(theCString);
|
|
|
|
b = bytes;
|
|
|
|
if (len > theRange.location + theRange.length)
|
|
{
|
|
len = theRange.location + theRange.length;
|
|
}
|
|
|
|
if (theOptions == NSCaseInsensitiveSearch)
|
|
{
|
|
i = theRange.location;
|
|
b += i;
|
|
|
|
for (; i <= len-slen; i++, b++)
|
|
{
|
|
if (!strncasecmp(theCString,b,slen))
|
|
{
|
|
return NSMakeRange(i,slen);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
i = theRange.location;
|
|
b += i;
|
|
|
|
for (; i <= len-slen; i++, b++)
|
|
{
|
|
if (!memcmp(theCString,b,slen))
|
|
{
|
|
return NSMakeRange(i,slen);
|
|
}
|
|
}
|
|
}
|
|
|
|
return NSMakeRange(NSNotFound,0);
|
|
}
|
|
|
|
@end
|
|
|
|
@interface MAPIStoreAttachment (MAPIStoreMIME)
|
|
|
|
- (BOOL) hasContentId;
|
|
- (NGMimeBodyPart *) asMIMEBodyPart;
|
|
|
|
@end
|
|
|
|
@implementation MAPIStoreAttachment (MAPIStoreMIME)
|
|
|
|
- (BOOL) hasContentId
|
|
{
|
|
return ([properties
|
|
objectForKey: MAPIPropertyKey (PR_ATTACH_CONTENT_ID_UNICODE)]
|
|
!= nil);
|
|
}
|
|
|
|
- (NGMimeBodyPart *) asMIMEBodyPart
|
|
{
|
|
NGMimeBodyPart *bodyPart = nil;
|
|
NSString *filename, *mimeType, *baseDisposition, *contentType,
|
|
*contentDisposition, *contentId;
|
|
NSData *content;
|
|
struct mapistore_connection_info *connInfo;
|
|
SOGoDomainDefaults *dd;
|
|
SOGoUser *activeUser;
|
|
NGMutableHashMap *map;
|
|
|
|
content = [properties objectForKey: MAPIPropertyKey (PR_ATTACH_DATA_BIN)];
|
|
if (content)
|
|
{
|
|
filename
|
|
= [properties
|
|
objectForKey: MAPIPropertyKey (PR_ATTACH_LONG_FILENAME_UNICODE)];
|
|
if (![filename length])
|
|
filename
|
|
= [properties
|
|
objectForKey: MAPIPropertyKey (PR_ATTACH_FILENAME_UNICODE)];
|
|
|
|
mimeType = [properties
|
|
objectForKey: MAPIPropertyKey (PR_ATTACH_MIME_TAG_UNICODE)];
|
|
if (!mimeType && [filename length])
|
|
mimeType = [[MAPIStoreMIME sharedMAPIStoreMIME]
|
|
mimeTypeForExtension: [filename pathExtension]];
|
|
if (!mimeType)
|
|
mimeType = @"application/octet-stream";
|
|
|
|
if ([mimeType hasPrefix: @"text/"])
|
|
{
|
|
connInfo = [[self context] connectionInfo];
|
|
activeUser = [SOGoUser userWithLogin: [NSString stringWithUTF8String: connInfo->username]];
|
|
dd = [activeUser domainDefaults];
|
|
baseDisposition = ([dd mailAttachTextDocumentsInline]
|
|
? @"inline" : @"attachment");
|
|
}
|
|
else if ([mimeType hasPrefix: @"image/"] || [mimeType hasPrefix: @"message"])
|
|
baseDisposition = @"inline";
|
|
else
|
|
baseDisposition = @"attachment";
|
|
|
|
if ([filename length] > 0)
|
|
{
|
|
contentType = [NSString stringWithFormat: @"%@; name=\"%@\"",
|
|
mimeType, filename];
|
|
contentDisposition = [NSString stringWithFormat: @"%@; filename=\"%@\"",
|
|
baseDisposition, filename];
|
|
}
|
|
else
|
|
{
|
|
contentType = mimeType;
|
|
contentDisposition = baseDisposition;
|
|
}
|
|
|
|
map = [[NGMutableHashMap alloc] initWithCapacity: 16];
|
|
[map addObject: contentType forKey: @"content-type"];
|
|
[map addObject: contentDisposition forKey: @"content-disposition"];
|
|
contentId = [properties
|
|
objectForKey: MAPIPropertyKey (PR_ATTACH_CONTENT_ID_UNICODE)];
|
|
if (contentId)
|
|
[map setObject: [NSString stringWithFormat: @"<%@>", contentId]
|
|
forKey: @"content-id"];
|
|
bodyPart = [NGMimeBodyPart bodyPartWithHeader: map];
|
|
[bodyPart setBody: content];
|
|
[map release];
|
|
}
|
|
else
|
|
[self errorWithFormat: @"no content for attachment"];
|
|
|
|
return bodyPart;
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation MAPIStoreMailVolatileMessage
|
|
|
|
+ (void) initialize
|
|
{
|
|
NSNumberK = [NSNumber class];
|
|
}
|
|
|
|
- (id) initWithSOGoObject: (id) newSOGoObject
|
|
inContainer: (MAPIStoreObject *) newContainer
|
|
{
|
|
if ((self = [super initWithSOGoObject: newSOGoObject
|
|
inContainer: newContainer]))
|
|
{
|
|
ASSIGN (properties, [sogoObject properties]);
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
- (void) addProperties: (NSDictionary *) newProperties
|
|
{
|
|
[super addProperties: newProperties];
|
|
[sogoObject adjustLastModified];
|
|
}
|
|
|
|
- (BOOL) canGetProperty: (enum MAPITAGS) propTag
|
|
{
|
|
return ([super canGetProperty: propTag]
|
|
|| [properties objectForKey: MAPIPropertyKey (propTag)] != nil);
|
|
}
|
|
|
|
- (uint64_t) objectVersion
|
|
{
|
|
NSNumber *version;
|
|
|
|
version = [properties objectForKey: @"version"];
|
|
|
|
return (version
|
|
? exchange_globcnt ([version unsignedLongLongValue])
|
|
: ULLONG_MAX);
|
|
}
|
|
|
|
- (int) getPidTagMessageClass: (void **) data inMemCtx: (TALLOC_CTX *) memCtx
|
|
{
|
|
*data = [@"IPM.Note" asUnicodeInMemCtx: memCtx];
|
|
|
|
return MAPISTORE_SUCCESS;
|
|
}
|
|
|
|
- (int) getPidTagChangeKey: (void **) data inMemCtx: (TALLOC_CTX *) memCtx
|
|
{
|
|
NSData *changeKey;
|
|
int rc;
|
|
|
|
changeKey = [properties objectForKey: MAPIPropertyKey (PR_CHANGE_KEY)];
|
|
if (changeKey)
|
|
{
|
|
*data = [changeKey asBinaryInMemCtx: memCtx];
|
|
rc = MAPISTORE_SUCCESS;
|
|
}
|
|
else
|
|
rc = [super getPidTagChangeKey: data inMemCtx: memCtx];
|
|
|
|
return rc;
|
|
}
|
|
|
|
- (NSDate *) creationTime
|
|
{
|
|
return [sogoObject creationDate];
|
|
}
|
|
|
|
- (NSDate *) lastModificationTime
|
|
{
|
|
return [sogoObject lastModified];
|
|
}
|
|
|
|
- (id) lookupAttachment: (NSString *) childKey
|
|
{
|
|
return [attachmentParts objectForKey: childKey];
|
|
}
|
|
|
|
- (void) getMessageData: (struct mapistore_message **) dataPtr
|
|
inMemCtx: (TALLOC_CTX *) memCtx
|
|
{
|
|
NSArray *recipients;
|
|
NSUInteger count, max, recipientsMax, p, current;
|
|
NSString *username, *cn, *email;
|
|
NSData *entryId;
|
|
NSDictionary *allRecipients, *dict, *contactInfos;
|
|
SOGoUserManager *mgr;
|
|
struct ldb_context *samCtx;
|
|
struct mapistore_message *msgData;
|
|
struct mapistore_message_recipient *recipient;
|
|
enum ulRecipClass type;
|
|
|
|
samCtx = [[self context] connectionInfo]->sam_ctx;
|
|
|
|
// [super getMessageData: &msgData inMemCtx: memCtx];
|
|
|
|
msgData = talloc_zero (memCtx, struct mapistore_message);
|
|
|
|
allRecipients = [properties objectForKey: @"recipients"];
|
|
msgData->columns = set_SPropTagArray (msgData, 9,
|
|
PR_OBJECT_TYPE,
|
|
PR_DISPLAY_TYPE,
|
|
PR_7BIT_DISPLAY_NAME_UNICODE,
|
|
PR_SMTP_ADDRESS_UNICODE,
|
|
PR_SEND_INTERNET_ENCODING,
|
|
PR_RECIPIENT_DISPLAY_NAME_UNICODE,
|
|
PR_RECIPIENT_FLAGS,
|
|
PR_RECIPIENT_ENTRYID,
|
|
PR_RECIPIENT_TRACKSTATUS);
|
|
|
|
/* Retrieve recipients from the message */
|
|
max = ([[allRecipients objectForKey: @"orig"] count]
|
|
+ [[allRecipients objectForKey: @"to"] count]
|
|
+ [[allRecipients objectForKey: @"cc"] count]
|
|
+ [[allRecipients objectForKey: @"bcc"] count]);
|
|
|
|
mgr = [SOGoUserManager sharedUserManager];
|
|
msgData->recipients_count = max;
|
|
msgData->recipients = talloc_array (msgData, struct mapistore_message_recipient, max);
|
|
current = 0;
|
|
for (type = MAPI_ORIG; type <= MAPI_BCC; type++)
|
|
{
|
|
recipients = [allRecipients objectForKey: recTypes[type]];
|
|
recipientsMax = [recipients count];
|
|
for (count = 0; count < recipientsMax; count++)
|
|
{
|
|
recipient = msgData->recipients + current;
|
|
recipient->type = type;
|
|
|
|
dict = [recipients objectAtIndex: count];
|
|
cn = [dict objectForKey: @"fullName"];
|
|
email = [dict objectForKey: @"email"];
|
|
|
|
contactInfos = [mgr contactInfosForUserWithUIDorEmail: email];
|
|
if (contactInfos)
|
|
{
|
|
username = [contactInfos objectForKey: @"sAMAccountName"];
|
|
recipient->username = [username asUnicodeInMemCtx: msgData];
|
|
entryId = MAPIStoreInternalEntryId (samCtx, username);
|
|
}
|
|
else
|
|
{
|
|
recipient->username = NULL;
|
|
entryId = MAPIStoreExternalEntryId (cn, email);
|
|
}
|
|
|
|
/* properties */
|
|
p = 0;
|
|
recipient->data = talloc_array (msgData, void *, msgData->columns->cValues);
|
|
memset (recipient->data, 0, msgData->columns->cValues * sizeof (void *));
|
|
|
|
// PR_OBJECT_TYPE = MAPI_MAILUSER (see MAPI_OBJTYPE)
|
|
recipient->data[p] = MAPILongValue (msgData, MAPI_MAILUSER);
|
|
p++;
|
|
|
|
// PR_DISPLAY_TYPE = DT_MAILUSER (see MS-NSPI)
|
|
recipient->data[p] = MAPILongValue (msgData, 0);
|
|
p++;
|
|
|
|
// PR_7BIT_DISPLAY_NAME_UNICODE
|
|
recipient->data[p] = [cn asUnicodeInMemCtx: msgData];
|
|
p++;
|
|
|
|
// PR_SMTP_ADDRESS_UNICODE
|
|
recipient->data[p] = [email asUnicodeInMemCtx: msgData];
|
|
p++;
|
|
|
|
// PR_SEND_INTERNET_ENCODING = 0x00060000 (plain text, see OXCMAIL)
|
|
recipient->data[p] = MAPILongValue (msgData, 0x00060000);
|
|
p++;
|
|
|
|
// PR_RECIPIENT_DISPLAY_NAME_UNICODE
|
|
recipient->data[p] = [cn asUnicodeInMemCtx: msgData];
|
|
p++;
|
|
|
|
// PR_RECIPIENT_FLAGS
|
|
recipient->data[p] = MAPILongValue (msgData, 0x01);
|
|
p++;
|
|
|
|
// PR_RECIPIENT_ENTRYID
|
|
recipient->data[p] = [entryId asBinaryInMemCtx: msgData];
|
|
p++;
|
|
|
|
// PR_RECIPIENT_TRACKSTATUS
|
|
recipient->data[p] = MAPILongValue (msgData, 0x00);
|
|
p++;
|
|
|
|
current++;
|
|
}
|
|
}
|
|
*dataPtr = msgData;
|
|
}
|
|
|
|
static inline NSString *
|
|
MakeRecipientString (NSDictionary *recipient)
|
|
{
|
|
NSString *fullName, *email, *fullEmail;
|
|
|
|
fullName = [recipient objectForKey: @"fullName"];
|
|
email = [recipient objectForKey: @"email"];
|
|
if ([email length] > 0)
|
|
{
|
|
if ([fullName length] > 0)
|
|
fullEmail = [NSString stringWithFormat: @"%@ <%@>", fullName, email];
|
|
else
|
|
fullEmail = email;
|
|
}
|
|
else
|
|
{
|
|
NSLog (@"recipient not generated from record: %@", recipient);
|
|
fullEmail = nil;
|
|
}
|
|
|
|
return fullEmail;
|
|
}
|
|
|
|
static inline NSArray *
|
|
MakeRecipientsList (NSArray *recipients)
|
|
{
|
|
NSMutableArray *list;
|
|
NSUInteger count, max;
|
|
NSString *recipient;
|
|
|
|
max = [recipients count];
|
|
list = [NSMutableArray arrayWithCapacity: max];
|
|
for (count = 0; count < max; count++)
|
|
{
|
|
recipient = MakeRecipientString ([recipients objectAtIndex: count]);
|
|
if (recipient)
|
|
[list addObject: recipient];
|
|
}
|
|
|
|
return list;
|
|
}
|
|
|
|
static NSString *
|
|
QuoteSpecials (NSString *address)
|
|
{
|
|
NSString *result, *part, *s2;
|
|
int i, len;
|
|
|
|
// We want to correctly send mails to recipients such as :
|
|
// foo.bar
|
|
// foo (bar) <foo@zot.com>
|
|
// bar, foo <foo@zot.com>
|
|
if ([address indexOf: '('] >= 0 || [address indexOf: ')'] >= 0
|
|
|| [address indexOf: '<'] >= 0 || [address indexOf: '>'] >= 0
|
|
|| [address indexOf: '@'] >= 0 || [address indexOf: ','] >= 0
|
|
|| [address indexOf: ';'] >= 0 || [address indexOf: ':'] >= 0
|
|
|| [address indexOf: '\\'] >= 0 || [address indexOf: '"'] >= 0
|
|
|| [address indexOf: '.'] >= 0
|
|
|| [address indexOf: '['] >= 0 || [address indexOf: ']'] >= 0)
|
|
{
|
|
// We search for the first instance of < from the end
|
|
// and we quote what was before if we need to
|
|
len = [address length];
|
|
i = -1;
|
|
while (len--)
|
|
if ([address characterAtIndex: len] == '<')
|
|
{
|
|
i = len;
|
|
break;
|
|
}
|
|
|
|
if (i > 0)
|
|
{
|
|
part = [address substringToIndex: i - 1];
|
|
s2 = [[part stringByReplacingString: @"\\" withString: @"\\\\"]
|
|
stringByReplacingString: @"\"" withString: @"\\\""];
|
|
result = [NSString stringWithFormat: @"\"%@\" %@", s2, [address substringFromIndex: i]];
|
|
}
|
|
else
|
|
{
|
|
s2 = [[address stringByReplacingString: @"\\" withString: @"\\\\"]
|
|
stringByReplacingString: @"\"" withString: @"\\\""];
|
|
result = [NSString stringWithFormat: @"\"%@\"", s2];
|
|
}
|
|
}
|
|
else
|
|
result = address;
|
|
|
|
return result;
|
|
}
|
|
|
|
static inline void
|
|
FillMessageHeadersFromSharingProperties (NGMutableHashMap *headers, NSDictionary *mailProperties)
|
|
{
|
|
/* Store the *important* properties related with a sharing object as
|
|
MIME eXtension headers. See [MS-OXSHARE] Section 2.2 for details
|
|
about the properties */
|
|
|
|
id value;
|
|
NSNumber *sharingFlavourNum = nil;
|
|
|
|
value = [mailProperties objectForKey: MAPIPropertyKey (PidLidSharingCapabilities)];
|
|
if (value)
|
|
[headers setObject: value
|
|
forKey: @"X-MS-Sharing-Capabilities"];
|
|
|
|
value = [mailProperties objectForKey: MAPIPropertyKey (PidLidSharingFlavor)];
|
|
if (value)
|
|
sharingFlavourNum = (NSNumber *)value;
|
|
else
|
|
{
|
|
value = [mailProperties objectForKey: MAPIPropertyKey (PidNameXSharingFlavor)];
|
|
if (value)
|
|
{
|
|
/* Transform the hex string to unsigned int */
|
|
NSScanner *scanner;
|
|
unsigned int sharingFlavour;
|
|
scanner = [NSScanner scannerWithString:value];
|
|
if ([scanner scanHexInt:&sharingFlavour])
|
|
sharingFlavourNum =[NSNumber numberWithUnsignedInt: sharingFlavour];
|
|
}
|
|
}
|
|
if (sharingFlavourNum)
|
|
{
|
|
if ([sharingFlavourNum unsignedIntegerValue] == 0x5100)
|
|
{
|
|
/* 0x5100 sharing flavour is not in standard but it seems to
|
|
be a denial of request + invitation message so we store
|
|
deny sharing flavour */
|
|
sharingFlavourNum = [NSNumber numberWithUnsignedInt: SHARING_DENY_REQUEST];
|
|
}
|
|
[headers setObject: sharingFlavourNum
|
|
forKey: @"X-MS-Sharing-Flavor"];
|
|
}
|
|
|
|
value = [mailProperties objectForKey: MAPIPropertyKey (PidLidSharingInitiatorEntryId)];
|
|
if (value)
|
|
[headers setObject: [[value stringByEncodingBase64] stringByReplacingOccurrencesOfString: @"\n"
|
|
withString: @""]
|
|
forKey: @"X-MS-Sharing-InitiatorEntryId"];
|
|
|
|
value = [mailProperties objectForKey: MAPIPropertyKey (PidLidSharingInitiatorName)];
|
|
if (value)
|
|
[headers setObject: value
|
|
forKey: @"X-MS-Sharing-InitiatorName"];
|
|
|
|
value = [mailProperties objectForKey: MAPIPropertyKey (PidLidSharingInitiatorSmtp)];
|
|
if (value)
|
|
[headers setObject: value
|
|
forKey: @"X-MS-Sharing-InitiatorSmtp"];
|
|
|
|
value = [mailProperties objectForKey: MAPIPropertyKey (PidLidSharingLocalType)];
|
|
if (value)
|
|
[headers setObject: value
|
|
forKey: @"X-MS-Sharing-LocalType"];
|
|
|
|
value = [mailProperties objectForKey: MAPIPropertyKey (PidLidSharingProviderName)];
|
|
if (value)
|
|
[headers setObject: value
|
|
forKey: @"X-MS-Sharing-ProviderName"];
|
|
|
|
value = [mailProperties objectForKey: MAPIPropertyKey (PidLidSharingRemoteName)];
|
|
if (value)
|
|
[headers setObject: value
|
|
forKey: @"X-MS-Sharing-RemoteName"];
|
|
|
|
value = [mailProperties objectForKey: MAPIPropertyKey (PidLidSharingRemoteStoreUid)];
|
|
if (value)
|
|
[headers setObject: value
|
|
forKey: @"X-MS-Sharing-RemoteStoreUid"];
|
|
|
|
value = [mailProperties objectForKey: MAPIPropertyKey (PidLidSharingRemoteUid)];
|
|
if (value)
|
|
[headers setObject: value
|
|
forKey: @"X-MS-Sharing-RemoteUid"];
|
|
|
|
value = [mailProperties objectForKey: MAPIPropertyKey (PidLidSharingResponseTime)];
|
|
if (value)
|
|
[headers setObject: value
|
|
forKey: @"X-MS-Sharing-ResponseTime"];
|
|
|
|
value = [mailProperties objectForKey: MAPIPropertyKey (PidLidSharingResponseType)];
|
|
if (value)
|
|
[headers setObject: value
|
|
forKey: @"X-MS-Sharing-ResponseType"];
|
|
|
|
}
|
|
|
|
static inline void
|
|
FillMessageHeadersFromProperties (NGMutableHashMap *headers,
|
|
NSDictionary *mailProperties, BOOL withBcc,
|
|
struct mapistore_connection_info *connInfo)
|
|
{
|
|
BOOL fromResolved = NO;
|
|
NSData *senderEntryId;
|
|
NSMutableString *subject;
|
|
NSString *from, *recId, *messageId, *subjectData, *recipientsStr, *msgClass;
|
|
NSArray *list;
|
|
NSCalendarDate *date;
|
|
NSDictionary *recipients;
|
|
enum ulRecipClass type, bccLimit;
|
|
SOGoUser *activeUser;
|
|
NSNumber *priority;
|
|
|
|
activeUser
|
|
= [SOGoUser
|
|
userWithLogin: [NSString stringWithUTF8String: connInfo->username]];
|
|
|
|
from = [NSString stringWithFormat: @"%@ <%@>",
|
|
[activeUser cn], [[activeUser allEmails] objectAtIndex: 0]];
|
|
[headers setObject: QuoteSpecials (from) forKey: @"from"];
|
|
|
|
/* save the recipients */
|
|
recipients = [mailProperties objectForKey: @"recipients"];
|
|
if (recipients)
|
|
{
|
|
if (withBcc)
|
|
bccLimit = MAPI_BCC;
|
|
else
|
|
bccLimit = MAPI_CC;
|
|
|
|
for (type = MAPI_TO; type <= bccLimit; type++)
|
|
{
|
|
recId = recTypes[type];
|
|
list = MakeRecipientsList ([recipients objectForKey: recId]);
|
|
[headers setObjects: list forKey: recId];
|
|
}
|
|
|
|
list = MakeRecipientsList ([recipients objectForKey: @"orig"]);
|
|
if ([list count])
|
|
{
|
|
[headers setObjects: list forKey: @"from"];
|
|
fromResolved = YES;
|
|
}
|
|
}
|
|
|
|
if (!fromResolved)
|
|
{
|
|
NSLog (@"Message without an orig from, try to guess it from PidTagSenderEntryId");
|
|
senderEntryId = [mailProperties objectForKey: MAPIPropertyKey (PR_SENDER_ENTRYID)];
|
|
if (senderEntryId)
|
|
{
|
|
struct Binary_r bin32;
|
|
struct AddressBookEntryId *addrBookEntryId;
|
|
NSString *username;
|
|
NSMutableDictionary *fromRecipient;
|
|
|
|
fromRecipient = [NSMutableDictionary dictionaryWithCapacity: 2];
|
|
|
|
bin32.cb = [senderEntryId length];
|
|
bin32.lpb = (uint8_t *) [senderEntryId bytes];
|
|
addrBookEntryId = get_AddressBookEntryId (connInfo->sam_ctx, &bin32);
|
|
if (addrBookEntryId && [[NSString stringWithGUID: &addrBookEntryId->ProviderUID]
|
|
hasSuffix: @"08002b2fe182"])
|
|
{
|
|
/* TODO: better way to distinguish local and other ones */
|
|
username = MAPIStoreSamDBUserAttribute (connInfo->sam_ctx, @"legacyExchangeDN",
|
|
[NSString stringWithUTF8String: addrBookEntryId->X500DN], @"sAMAccountName");
|
|
if (username)
|
|
{
|
|
SOGoUser *fromUser;
|
|
|
|
fromUser = [SOGoUser userWithLogin: [username lowercaseString]];
|
|
[fromRecipient setObject: [fromUser cn] forKey: @"fullName"];
|
|
[fromRecipient setObject: [[fromUser allEmails] objectAtIndex: 0]
|
|
forKey: @"email"];
|
|
}
|
|
else
|
|
[fromRecipient setObject: [NSString stringWithUTF8String: addrBookEntryId->X500DN]
|
|
forKey: @"email"];
|
|
|
|
}
|
|
else
|
|
{
|
|
/* Try with One-Off EntryId */
|
|
struct OneOffEntryId *oneOffEntryId;
|
|
|
|
oneOffEntryId = get_OneOffEntryId (connInfo->sam_ctx, &bin32);
|
|
if (oneOffEntryId && [[NSString stringWithGUID: &oneOffEntryId->ProviderUID]
|
|
hasSuffix: @"00dd010f5402"])
|
|
{
|
|
[fromRecipient setObject: [NSString stringWithUTF8String: oneOffEntryId->DisplayName.lpszW]
|
|
forKey: @"fullName"];
|
|
[fromRecipient setObject: [NSString stringWithUTF8String: oneOffEntryId->EmailAddress.lpszW]
|
|
forKey: @"email"];
|
|
}
|
|
talloc_free (oneOffEntryId);
|
|
}
|
|
talloc_free (addrBookEntryId);
|
|
|
|
if ([[fromRecipient allKeys] count] > 0)
|
|
{
|
|
list = MakeRecipientsList ([NSArray arrayWithObjects: fromRecipient, nil]);
|
|
if ([list count])
|
|
[headers setObjects: list forKey: @"from"];
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
if (!recipients)
|
|
{
|
|
NSLog (@"Message without recipients."
|
|
@"Guessing recipients from PidTagOriginalDisplayTo and PidTagOriginalCc");
|
|
recipientsStr = [mailProperties objectForKey: MAPIPropertyKey (PidTagOriginalDisplayTo)];
|
|
if (recipientsStr)
|
|
{
|
|
list = [recipientsStr componentsSeparatedByString:@", "];
|
|
if ([list count])
|
|
[headers setObjects: list forKey: @"to"];
|
|
}
|
|
|
|
recipientsStr = [mailProperties objectForKey: MAPIPropertyKey (PidTagOriginalDisplayCc)];
|
|
if (recipientsStr)
|
|
{
|
|
list = [recipientsStr componentsSeparatedByString:@", "];
|
|
if ([list count])
|
|
[headers setObjects: list forKey: @"cc"];
|
|
}
|
|
}
|
|
|
|
subject = [NSMutableString stringWithCapacity: 128];
|
|
subjectData = [mailProperties objectForKey: MAPIPropertyKey (PR_SUBJECT_PREFIX_UNICODE)];
|
|
if (subjectData)
|
|
[subject appendString: subjectData];
|
|
subjectData = [mailProperties objectForKey: MAPIPropertyKey (PR_NORMALIZED_SUBJECT_UNICODE)];
|
|
if (subjectData)
|
|
[subject appendString: subjectData];
|
|
[headers setObject: [subject asQPSubjectString: @"utf-8"] forKey: @"subject"];
|
|
|
|
messageId = [mailProperties objectForKey: MAPIPropertyKey (PR_INTERNET_MESSAGE_ID_UNICODE)];
|
|
if ([messageId length])
|
|
[headers setObject: messageId forKey: @"message-id"];
|
|
|
|
date = [mailProperties objectForKey: MAPIPropertyKey (PR_CLIENT_SUBMIT_TIME)];
|
|
if (date)
|
|
{
|
|
[headers addObject: [date rfc822DateString] forKey: @"date"];
|
|
}
|
|
[headers addObject: @"1.0" forKey: @"MIME-Version"];
|
|
|
|
priority = [mailProperties objectForKey: MAPIPropertyKey (PidTagImportance)];
|
|
|
|
if ([priority intValue] == 2)
|
|
{
|
|
[headers addObject: @"1 (Highest)" forKey: @"X-Priority"];
|
|
}
|
|
else if ([priority intValue] == 1)
|
|
{
|
|
[headers removeAllObjectsForKey: @"X-Priority"];
|
|
}
|
|
else
|
|
{
|
|
[headers addObject: @"5 (Lowest)" forKey: @"X-Priority"];
|
|
}
|
|
|
|
msgClass = [mailProperties objectForKey: MAPIPropertyKey (PidTagMessageClass)];
|
|
if ([msgClass isEqualToString: @"IPM.Sharing"])
|
|
{
|
|
FillMessageHeadersFromSharingProperties (headers, mailProperties);
|
|
}
|
|
|
|
}
|
|
|
|
static NSArray *
|
|
MakeAttachmentParts (NSDictionary *attachmentParts, BOOL withContentId)
|
|
{
|
|
NSMutableArray *attachmentMimeParts;
|
|
NSArray *keys;
|
|
MAPIStoreAttachment *attachment;
|
|
NSUInteger count, max;
|
|
NGMimeBodyPart *mimePart;
|
|
|
|
keys = [attachmentParts allKeys];
|
|
max = [keys count];
|
|
attachmentMimeParts = [NSMutableArray arrayWithCapacity: max];
|
|
for (count = 0; count < max; count++)
|
|
{
|
|
attachment = [attachmentParts
|
|
objectForKey: [keys objectAtIndex: count]];
|
|
if ([attachment hasContentId] == withContentId)
|
|
{
|
|
mimePart = [attachment asMIMEBodyPart];
|
|
if (mimePart)
|
|
[attachmentMimeParts addObject: mimePart];
|
|
}
|
|
}
|
|
|
|
return attachmentMimeParts;
|
|
}
|
|
|
|
static inline id
|
|
MakeTextPlainBody (NSDictionary *mailProperties, NSString **contentType)
|
|
{
|
|
id textPlainBody;
|
|
|
|
textPlainBody = [[mailProperties
|
|
objectForKey: MAPIPropertyKey (PR_BODY_UNICODE)]
|
|
dataUsingEncoding: NSUTF8StringEncoding];
|
|
*contentType = @"text/plain; charset=utf-8";
|
|
|
|
return textPlainBody;
|
|
}
|
|
|
|
static inline id
|
|
MakeTextHtmlBody (NSDictionary *mailProperties, NSDictionary *attachmentParts,
|
|
NSString **contentType)
|
|
{
|
|
id textHtmlBody;
|
|
NSData *htmlBody;
|
|
NSString *charset, *htmlContentType;
|
|
NSArray *parts;
|
|
NSNumber *codePage;
|
|
NGMimeBodyPart *htmlBodyPart;
|
|
NGMutableHashMap *headers;
|
|
NSUInteger count, max;
|
|
|
|
htmlBody = [mailProperties objectForKey: MAPIPropertyKey (PR_HTML)];
|
|
if (htmlBody)
|
|
{
|
|
/* charset */
|
|
codePage = [mailProperties objectForKey: MAPIPropertyKey (PR_INTERNET_CPID)];
|
|
charset = [Codepages getNameFromCodepage: codePage];
|
|
if (!charset)
|
|
charset = @"utf-8";
|
|
htmlContentType = [NSString stringWithFormat: @"text/html; charset=%@",
|
|
charset];
|
|
|
|
parts = MakeAttachmentParts (attachmentParts, YES);
|
|
max = [parts count];
|
|
if (max > 0)
|
|
{
|
|
textHtmlBody = [NGMimeMultipartBody new];
|
|
[textHtmlBody autorelease];
|
|
|
|
headers = [[NGMutableHashMap alloc] initWithCapacity: 1];
|
|
[headers setObject: htmlContentType forKey: @"content-type"];
|
|
htmlBodyPart = [NGMimeBodyPart bodyPartWithHeader: headers];
|
|
[htmlBodyPart setBody: htmlBody];
|
|
[headers release];
|
|
[textHtmlBody addBodyPart: htmlBodyPart];
|
|
|
|
for (count = 0; count < max; count++)
|
|
[textHtmlBody addBodyPart: [parts objectAtIndex: count]];
|
|
|
|
*contentType = @"multipart/related";
|
|
}
|
|
else
|
|
{
|
|
textHtmlBody = htmlBody;
|
|
*contentType = htmlContentType;
|
|
}
|
|
}
|
|
else
|
|
textHtmlBody = nil;
|
|
|
|
return textHtmlBody;
|
|
}
|
|
|
|
static inline id
|
|
MakeTextPartBody (NSDictionary *mailProperties, NSDictionary *attachmentParts,
|
|
NSString **contentType)
|
|
{
|
|
id textBody, textPlainBody, textHtmlBody;
|
|
NSString *textPlainContentType, *textHtmlContentType;
|
|
NGMutableHashMap *headers;
|
|
NGMimeBodyPart *bodyPart;
|
|
|
|
textPlainBody = MakeTextPlainBody (mailProperties, &textPlainContentType);
|
|
textHtmlBody = MakeTextHtmlBody (mailProperties, attachmentParts, &textHtmlContentType);
|
|
if (textPlainBody)
|
|
{
|
|
if (textHtmlBody)
|
|
{
|
|
textBody = [NGMimeMultipartBody new];
|
|
[textBody autorelease];
|
|
|
|
headers = [[NGMutableHashMap alloc] initWithCapacity: 1];
|
|
[headers setObject: textHtmlContentType forKey: @"content-type"];
|
|
bodyPart = [NGMimeBodyPart bodyPartWithHeader: headers];
|
|
[bodyPart setBody: textHtmlBody];
|
|
[headers release];
|
|
[textBody addBodyPart: bodyPart];
|
|
|
|
headers = [[NGMutableHashMap alloc] initWithCapacity: 1];
|
|
[headers setObject: textPlainContentType forKey: @"content-type"];
|
|
bodyPart = [NGMimeBodyPart bodyPartWithHeader: headers];
|
|
[bodyPart setBody: textPlainBody];
|
|
[headers release];
|
|
[textBody addBodyPart: bodyPart];
|
|
|
|
*contentType = @"multipart/alternative";
|
|
}
|
|
else
|
|
{
|
|
textBody = textPlainBody;
|
|
*contentType = textPlainContentType;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
textBody = textHtmlBody;
|
|
*contentType = textHtmlContentType;
|
|
}
|
|
|
|
return textBody;
|
|
}
|
|
|
|
// static id
|
|
// MakeMessageBody (NSDictionary *mailProperties, NSDictionary *attachmentParts,
|
|
// NSString **contentType)
|
|
static id
|
|
MakeMessageBody (NSDictionary *mailProperties, NSDictionary *attachmentParts, NSString **contentType)
|
|
{
|
|
id messageBody, textBody;
|
|
NSString *textContentType;
|
|
NSArray *parts;
|
|
NGMimeBodyPart *textBodyPart;
|
|
NGMutableHashMap *headers;
|
|
NSUInteger count, max;
|
|
|
|
textBody = MakeTextPartBody (mailProperties, attachmentParts,
|
|
&textContentType);
|
|
|
|
parts = MakeAttachmentParts (attachmentParts, NO);
|
|
max = [parts count];
|
|
if (max > 0)
|
|
{
|
|
messageBody = [NGMimeMultipartBody new];
|
|
[messageBody autorelease];
|
|
|
|
if (textBody)
|
|
{
|
|
headers = [[NGMutableHashMap alloc] initWithCapacity: 1];
|
|
[headers setObject: textContentType forKey: @"content-type"];
|
|
textBodyPart = [NGMimeBodyPart bodyPartWithHeader: headers];
|
|
[textBodyPart setBody: textBody];
|
|
[headers release];
|
|
[messageBody addBodyPart: textBodyPart];
|
|
}
|
|
|
|
for (count = 0; count < max; count++)
|
|
[messageBody addBodyPart: [parts objectAtIndex: count]];
|
|
|
|
*contentType = @"multipart/mixed";
|
|
}
|
|
else
|
|
{
|
|
messageBody = textBody;
|
|
*contentType = textContentType;
|
|
}
|
|
|
|
return messageBody;
|
|
}
|
|
|
|
- (NGMimeMessage *) _generateMessageWithBcc: (BOOL) withBcc
|
|
{
|
|
NSString *contentType;
|
|
NGMimeMessage *message;
|
|
NGMutableHashMap *headers;
|
|
id messageBody;
|
|
|
|
headers = [[NGMutableHashMap alloc] initWithCapacity: 16];
|
|
FillMessageHeadersFromProperties (headers, properties, withBcc,
|
|
[[self context] connectionInfo]);
|
|
message = [[NGMimeMessage alloc] initWithHeader: headers];
|
|
[message autorelease];
|
|
[headers release];
|
|
|
|
messageBody = MakeMessageBody (properties, attachmentParts, &contentType);
|
|
if (messageBody)
|
|
{
|
|
[message setHeader: contentType forKey: @"content-type"];
|
|
[message setBody: messageBody];
|
|
}
|
|
|
|
return message;
|
|
}
|
|
|
|
- (NSData *) _generateMailDataWithBcc: (BOOL) withBcc
|
|
{
|
|
NGMimeMessage *message;
|
|
NGMimeMessageGenerator *generator;
|
|
NSData *messageData;
|
|
|
|
/* mime message generation */
|
|
generator = [NGMimeMessageGenerator new];
|
|
message = [self _generateMessageWithBcc: withBcc];
|
|
messageData = [generator generateMimeFromPart: message];
|
|
[generator release];
|
|
|
|
// [messageData writeToFile: @"/tmp/mimegen.eml" atomically: NO];
|
|
|
|
return messageData;
|
|
}
|
|
|
|
- (int) submitWithFlags: (enum SubmitFlags) flags
|
|
{
|
|
NSDictionary *recipients;
|
|
NSData *messageData;
|
|
NSMutableArray *recipientEmails;
|
|
NSArray *list;
|
|
NSString *recId, *from, *msgClass;
|
|
enum ulRecipClass type;
|
|
SOGoUser *activeUser;
|
|
SOGoDomainDefaults *dd;
|
|
NSException *error;
|
|
WOContext *woContext;
|
|
id <SOGoAuthenticator> authenticator;
|
|
|
|
msgClass = [properties objectForKey: MAPIPropertyKey (PidTagMessageClass)];
|
|
if ([msgClass isEqualToString: @"IPM.Note"] || [msgClass isEqualToString: @"IPM.Sharing"]) /* we skip invitation replies */
|
|
{
|
|
/* send mail */
|
|
|
|
messageData = [self _generateMailDataWithBcc: NO];
|
|
|
|
recipientEmails = [NSMutableArray arrayWithCapacity: 32];
|
|
recipients = [properties objectForKey: @"recipients"];
|
|
for (type = MAPI_ORIG; type <= MAPI_BCC; type++)
|
|
{
|
|
recId = recTypes[type];
|
|
list = [recipients objectForKey: recId];
|
|
[recipientEmails
|
|
addObjectsFromArray: [list objectsForKey: @"email"
|
|
notFoundMarker: nil]];
|
|
}
|
|
|
|
activeUser = [[self context] activeUser];
|
|
|
|
[self logWithFormat: @"recipients: %@", recipientEmails];
|
|
dd = [activeUser domainDefaults];
|
|
from = [[activeUser allEmails] objectAtIndex: 0];
|
|
|
|
woContext = [[self userContext] woContext];
|
|
authenticator = [sogoObject authenticatorInContext: woContext];
|
|
error = [[SOGoMailer mailerWithDomainDefaults: dd]
|
|
sendMailData: messageData
|
|
toRecipients: recipientEmails
|
|
sender: from
|
|
withAuthenticator: authenticator
|
|
inContext: woContext];
|
|
if (error)
|
|
[self logWithFormat: @"an error occurred: '%@'", error];
|
|
|
|
// mapping = [self mapping];
|
|
// [mapping unregisterURLWithID: [self objectId]];
|
|
// [self setIsNew: NO];
|
|
// [properties removeAllObjects];
|
|
[(MAPIStoreMailFolder *) [self container] cleanupCaches];
|
|
}
|
|
else
|
|
[self logWithFormat: @"skipping submit of message with class '%@'",
|
|
msgClass];
|
|
|
|
return MAPISTORE_SUCCESS;
|
|
}
|
|
|
|
- (void) save: (TALLOC_CTX *) memCtx
|
|
{
|
|
NSString *folderName, *flag, *newIdString, *messageKey;
|
|
NSData *changeKey, *messageData;
|
|
NGImap4Connection *connection;
|
|
NGImap4Client *client;
|
|
SOGoMailFolder *containerFolder;
|
|
NSDictionary *result, *responseResult;
|
|
MAPIStoreMapping *mapping;
|
|
uint64_t mid;
|
|
|
|
messageData = [self _generateMailDataWithBcc: YES];
|
|
|
|
/* appending to imap folder */
|
|
containerFolder = [container sogoObject];
|
|
connection = [containerFolder imap4Connection];
|
|
client = [connection client];
|
|
folderName = [connection imap4FolderNameForURL: [containerFolder imap4URL]];
|
|
result = [client append: messageData toFolder: folderName
|
|
withFlags: [NSArray arrayWithObjects: @"seen", nil]];
|
|
if ([[result objectForKey: @"result"] boolValue])
|
|
{
|
|
/* we reregister the new message URL with the id mapper */
|
|
responseResult = [[result objectForKey: @"RawResponse"]
|
|
objectForKey: @"ResponseResult"];
|
|
flag = [responseResult objectForKey: @"flag"];
|
|
|
|
newIdString = [[flag componentsSeparatedByString: @" "]
|
|
objectAtIndex: 2];
|
|
mapping = [self mapping];
|
|
mid = [self objectId];
|
|
[mapping unregisterURLWithID: mid];
|
|
// [sogoObject setNameInContainer: ];
|
|
|
|
messageKey = [NSString stringWithFormat: @"%@.eml", newIdString];
|
|
[sogoObject setNameInContainer: messageKey];
|
|
[mapping registerURL: [self url] withID: mid];
|
|
|
|
/* synchronise the cache and update the change key with the one provided
|
|
by the client. Before doing this, lets issue a unselect/select combo
|
|
because of timing issues with Dovecot in obtaining the latest modseq.
|
|
Sometimes, Dovecot doesn't return the newly appended UID if we do
|
|
a "UID SORT (DATE) UTF-8 (MODSEQ XYZ) (NOT DELETED)" command (where
|
|
XYZ is the HIGHESTMODSEQ+1) immediately after IMAP APPEND */
|
|
[client unselect];
|
|
[client select: folderName];
|
|
|
|
[(MAPIStoreMailFolder *) container synchroniseCache];
|
|
changeKey = [properties objectForKey: MAPIPropertyKey (PR_CHANGE_KEY)];
|
|
if (changeKey)
|
|
[(MAPIStoreMailFolder *) container
|
|
setChangeKey: changeKey
|
|
forMessageWithKey: messageKey];
|
|
}
|
|
}
|
|
|
|
@end
|