(feat) initial S/MIME support for EAS (#3327)

pull/110/head
Ludovic Marcotte 2015-10-15 15:31:46 -04:00
parent ddf97ebb13
commit 020fa78848
6 changed files with 247 additions and 36 deletions

View File

@ -1 +1,4 @@
# compilation settings ifeq ($(HAS_LIBRARY_ssl),yes)
ADDITIONAL_CPPFLAGS += -DHAVE_OPENSSL=1
BUNDLE_LIBS += -lcrypto
endif

View File

@ -1407,6 +1407,11 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// By default, send MIME mails. See #3146 for details. // By default, send MIME mails. See #3146 for details.
if (!bodyPreferenceType) if (!bodyPreferenceType)
bodyPreferenceType = @"4"; bodyPreferenceType = @"4";
mimeSupport = [[folderMetadata objectForKey: @"FolderOptions"] objectForKey: @"MIMESupport"];
if (!mimeSupport)
mimeSupport = @"1";
} }
else else
{ {
@ -1437,6 +1442,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
} }
[context setObject: bodyPreferenceType forKey: @"BodyPreferenceType"]; [context setObject: bodyPreferenceType forKey: @"BodyPreferenceType"];
[context setObject: mimeSupport forKey: @"MIMESupport"];
// //
// We process the commands from the request // We process the commands from the request

View File

@ -30,6 +30,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "SOGoActiveSyncDispatcher.h" #include "SOGoActiveSyncDispatcher.h"
#import <Foundation/NSArray.h> #import <Foundation/NSArray.h>
#import <Foundation/NSData.h>
#import <Foundation/NSAutoreleasePool.h> #import <Foundation/NSAutoreleasePool.h>
#import <Foundation/NSCalendarDate.h> #import <Foundation/NSCalendarDate.h>
#import <Foundation/NSLocale.h> #import <Foundation/NSLocale.h>
@ -135,6 +136,12 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include <unistd.h> #include <unistd.h>
#ifdef HAVE_OPENSSL
#include <openssl/bio.h>
#include <openssl/err.h>
#include <openssl/x509.h>
#endif
@interface SOGoActiveSyncDispatcher (Sync) @interface SOGoActiveSyncDispatcher (Sync)
- (NSMutableDictionary *) _folderMetadataForKey: (NSString *) theFolderKey; - (NSMutableDictionary *) _folderMetadataForKey: (NSString *) theFolderKey;
@ -1262,7 +1269,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- (void) processItemOperations: (id <DOMElement>) theDocumentElement - (void) processItemOperations: (id <DOMElement>) theDocumentElement
inResponse: (WOResponse *) theResponse inResponse: (WOResponse *) theResponse
{ {
NSString *fileReference, *realCollectionId, *serverId, *bodyPreferenceType, *collectionId; NSString *fileReference, *realCollectionId, *serverId, *bodyPreferenceType, *mimeSupport, *collectionId;
NSMutableString *s; NSMutableString *s;
NSArray *fetchRequests; NSArray *fetchRequests;
id aFetch; id aFetch;
@ -1375,6 +1382,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
serverId = [[(id)[theDocumentElement getElementsByTagName: @"ServerId"] lastObject] textValue]; serverId = [[(id)[theDocumentElement getElementsByTagName: @"ServerId"] lastObject] textValue];
bodyPreferenceType = [[(id)[[(id)[theDocumentElement getElementsByTagName: @"BodyPreference"] lastObject] getElementsByTagName: @"Type"] lastObject] textValue]; bodyPreferenceType = [[(id)[[(id)[theDocumentElement getElementsByTagName: @"BodyPreference"] lastObject] getElementsByTagName: @"Type"] lastObject] textValue];
[context setObject: bodyPreferenceType forKey: @"BodyPreferenceType"]; [context setObject: bodyPreferenceType forKey: @"BodyPreferenceType"];
mimeSupport = [[(id)[theDocumentElement getElementsByTagName: @"MIMESupport"] lastObject] textValue];
[context setObject: mimeSupport forKey: @"MIMESupport"];
currentCollection = [self collectionFromId: realCollectionId type: folderType]; currentCollection = [self collectionFromId: realCollectionId type: folderType];
@ -2102,6 +2111,123 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[theResponse setContent: d]; [theResponse setContent: d];
} }
//
//
//
#ifdef HAVE_OPENSSL
- (unsigned int) validateCert: (NSString *) theCert
{
NSData *d;
const unsigned char *data;
X509_STORE_CTX *ctx;
X509_LOOKUP *lookup;
X509_STORE *store;
X509 *cert;
BOOL success;
size_t len;
int rc;
success = NO;
d = [theCert dataByDecodingBase64];
data = (unsigned char *)[d bytes];
len = [d length];
cert = d2i_X509(NULL, &data, len);
if (!cert)
{
[self logWithFormat: @"EAS - validateCert failed for device %@: d2i_X509 failed", [context objectForKey: @"DeviceId"]];
return 17;
}
store = X509_STORE_new();
OpenSSL_add_all_algorithms();
if (store)
{
lookup = X509_STORE_add_lookup(store, X509_LOOKUP_file());
if (lookup)
{
X509_LOOKUP_load_file(lookup, NULL, X509_FILETYPE_DEFAULT);
lookup = X509_STORE_add_lookup(store, X509_LOOKUP_hash_dir());
if (lookup)
{
X509_LOOKUP_add_dir(lookup, NULL, X509_FILETYPE_DEFAULT);
ERR_clear_error();
success = YES;
}
}
}
if (!success)
{
if (store)
{
X509_STORE_free(store);
store = NULL;
}
}
ctx = X509_STORE_CTX_new();
if (!ctx)
{
[self logWithFormat: @"EAS - validateCert failed for device %@: X509_STORE_CTX_new failed", [context objectForKey: @"DeviceId"]];
return 17;
}
if (X509_STORE_CTX_init(ctx, store, cert, NULL) != 1)
{
[self logWithFormat: @"EAS - validateCert failed for device %@: X509_STORE_CTX_init failed", [context objectForKey: @"DeviceId"]];
X509_STORE_CTX_free(ctx);
return 17;
}
rc = X509_verify_cert(ctx);
X509_STORE_CTX_free(ctx);
X509_free(cert);
if (rc)
{
return 1;
}
else
{
[self logWithFormat: @"EAS - validateCert failed for device %@: err=%d", [context objectForKey: @"DeviceId"], X509_STORE_CTX_get_error(ctx)];
return 17;
}
}
#else
- (unsigned int) validateCert: (NSString *) theCert
{
return 17;
}
#endif
- (void) processValidateCert: (id <DOMElement>) theDocumentElement
inResponse: (WOResponse *) theResponse
{
NSMutableString *s;
NSString *cert;
NSData *d;
cert = [[(id)[theDocumentElement getElementsByTagName: @"Certificate"] lastObject] textValue];
s = [NSMutableString string];
[s appendString: @"<?xml version=\"1.0\" encoding=\"utf-8\"?>"];
[s appendString: @"<!DOCTYPE ActiveSync PUBLIC \"-//MICROSOFT//DTD ActiveSync//EN\" \"http://www.microsoft.com/\">"];
[s appendString: @"<ValidateCert xmlns=\"ValidateCert:\">"];
[s appendString: @"<Status>1</Status><Certificate>"];
[s appendFormat: @"<Status>%d</Status>", [self validateCert: cert]];
[s appendString: @"</Certificate></ValidateCert>"];
d = [[s dataUsingEncoding: NSUTF8StringEncoding] xml2wbxml];
[theResponse setContent: d];
}
// //
// <?xml version="1.0"?> // <?xml version="1.0"?>
// <!DOCTYPE ActiveSync PUBLIC "-//MICROSOFT//DTD ActiveSync//EN" "http://www.microsoft.com/"> // <!DOCTYPE ActiveSync PUBLIC "-//MICROSOFT//DTD ActiveSync//EN" "http://www.microsoft.com/">
@ -2418,38 +2544,84 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
NGMimeMessageParser *parser; NGMimeMessageParser *parser;
NGMimeMessage *message; NGMimeMessage *message;
NSException *error; NSException *error;
NSData *data; NSMutableData *data;
NGMutableHashMap *map; NSData *new_from_header;
NGMimeMessage *messageToSend;
NGMimeMessageGenerator *generator;
NSDictionary *identity; NSDictionary *identity;
NSString *fullName, *email; NSString *fullName, *email;
const char *bytes;
int i, len;
BOOL found_header;
// We get the mail's data // We get the mail's data
data = [[[[(id)[theDocumentElement getElementsByTagName: @"MIME"] lastObject] textValue] stringByDecodingBase64] dataUsingEncoding: NSUTF8StringEncoding]; data = [NSMutableData dataWithData: [[[[(id)[theDocumentElement getElementsByTagName: @"MIME"] lastObject] textValue] stringByDecodingBase64] dataUsingEncoding: NSUTF8StringEncoding]];
// We extract the recipients // We extract the recipients
parser = [[NGMimeMessageParser alloc] init]; parser = [[NGMimeMessageParser alloc] init];
message = [parser parsePartFromData: data]; message = [parser parsePartFromData: data];
RELEASE(parser); RELEASE(parser);
map = [NGHashMap hashMapWithDictionary: [message headers]];
identity = [[context activeUser] primaryIdentity]; identity = [[context activeUser] primaryIdentity];
fullName = [identity objectForKey: @"fullName"]; fullName = [identity objectForKey: @"fullName"];
email = [identity objectForKey: @"email"]; email = [identity objectForKey: @"email"];
if ([fullName length]) if ([fullName length])
[map setObject: [NSString stringWithFormat: @"%@ <%@>", fullName, email] forKey: @"from"]; new_from_header = [[NSString stringWithFormat: @"From: %@ <%@>\r\n", fullName, email] dataUsingEncoding:NSUTF8StringEncoding];
else else
[map setObject: email forKey: @"from"]; new_from_header = [[NSString stringWithFormat: @"From: %@\r\n", email] dataUsingEncoding:NSUTF8StringEncoding];
messageToSend = [[[NGMimeMessage alloc] initWithHeader: map] autorelease]; bytes = [data bytes];
len = [data length];
i = 0;
found_header = NO;
[messageToSend setBody: [message body]]; // Search for the from-header
while (i < len)
{
if (i == 0 &&
(*bytes == 'f' || *bytes == 'F') &&
(*(bytes+1) == 'r' || *(bytes+1) == 'R') &&
(*(bytes+2) == 'o' || *(bytes+2) == 'O') &&
(*(bytes+3) == 'm' || *(bytes+3) == 'M') &&
(*(bytes+4) == ':'))
{
found_header = YES;
break;
}
generator = [[[NGMimeMessageGenerator alloc] init] autorelease]; if (((*bytes == '\r') && (*(bytes+1) == '\n')) &&
data = [generator generateMimeFromPart: messageToSend]; (*(bytes+2) == 'f' || *(bytes+2) == 'F') &&
(*(bytes+3) == 'r' || *(bytes+3) == 'R') &&
(*(bytes+4) == 'o' || *(bytes+4) == 'O') &&
(*(bytes+5) == 'm' || *(bytes+5) == 'M') &&
(*(bytes+6) == ':'))
{
found_header = YES;
i = i + 2; // \r\n
break;
}
bytes++;
i++;
}
// Update/Add the From header in the MIMEBody of the SendMail request.
// Any other way to modify the mail body would break s/mime emails.
if (found_header)
{
// Change the From header
[data replaceBytesInRange: NSMakeRange(i, [[message headerForKey: @"from"] length]+8) // start of the From header found - length of the parsed from-header-value + 8 (From:+\r\n+1)
withBytes: [new_from_header bytes]
length: [new_from_header length]];
}
else
{
// Add a From header
[data replaceBytesInRange: NSMakeRange(0, 0)
withBytes: [new_from_header bytes]
length: [new_from_header length]];
}
error = [self _sendMail: data error = [self _sendMail: data
recipients: [message allRecipients] recipients: [message allRecipients]

View File

@ -476,14 +476,16 @@ struct GlobalObjectId {
// //
// //
- (NSData *) _preferredBodyDataUsingType: (int) theType - (NSData *) _preferredBodyDataUsingType: (int) theType
mimeSupport: (int) theMimeSupport
nativeType: (int *) theNativeType nativeType: (int *) theNativeType
{ {
NSString *type, *subtype, *encoding; NSString *type, *subtype, *encoding;
NSData *d; NSData *d;
BOOL isSMIME;
type = [[[self bodyStructure] valueForKey: @"type"] lowercaseString]; type = [[[self bodyStructure] valueForKey: @"type"] lowercaseString];
subtype = [[[self bodyStructure] valueForKey: @"subtype"] lowercaseString]; subtype = [[[self bodyStructure] valueForKey: @"subtype"] lowercaseString];
isSMIME = NO;
d = nil; d = nil;
// We determine the native type // We determine the native type
@ -494,8 +496,14 @@ struct GlobalObjectId {
else if ([type isEqualToString: @"multipart"]) else if ([type isEqualToString: @"multipart"])
*theNativeType = 4; *theNativeType = 4;
if (([subtype isEqualToString: @"signed"] || [subtype isEqualToString: @"pkcs7-mime"] ) && theMimeSupport > 0)
{
*theNativeType = 4;
isSMIME = YES;
}
// We get the right part based on the preference // We get the right part based on the preference
if (theType == 1 || theType == 2) if ((theType == 1 || theType == 2) && !isSMIME)
{ {
if ([type isEqualToString: @"text"] && ![subtype isEqualToString: @"calendar"]) if ([type isEqualToString: @"text"] && ![subtype isEqualToString: @"calendar"])
{ {
@ -536,12 +544,12 @@ struct GlobalObjectId {
d = [self _preferredBodyDataInMultipartUsingType: theType nativeTypeFound: theNativeType]; d = [self _preferredBodyDataInMultipartUsingType: theType nativeTypeFound: theNativeType];
} }
} }
else if (theType == 4) else if (theType == 4 || isSMIME)
{ {
// We sanitize the content *ONLY* for Outlook clients and if the content-transfer-encoding is 8bit. 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. // with quoted-printable/base64 encoded text parts. It just doesn't decode them.
encoding = [[self lookupInfoForBodyPart: @""] objectForKey: @"encoding"]; encoding = [[self lookupInfoForBodyPart: @""] objectForKey: @"encoding"];
if (encoding && ([encoding caseInsensitiveCompare: @"8bit"] == NSOrderedSame)) if ((encoding && ([encoding caseInsensitiveCompare: @"8bit"] == NSOrderedSame)) && !isSMIME)
d = [self _sanitizedMIMEMessage]; d = [self _sanitizedMIMEMessage];
else else
d = [self content]; d = [self content];
@ -656,16 +664,18 @@ struct GlobalObjectId {
{ {
NSData *d, *globalObjId; NSData *d, *globalObjId;
NSArray *attachmentKeys; NSArray *attachmentKeys;
iCalCalendar *calendar;
NSString *p, *subtype;
NSMutableString *s; NSMutableString *s;
uint32_t v;
NSString *p;
id value; id value;
iCalCalendar *calendar; int preferredBodyType, mimeSupport, nativeBodyType;
uint32_t v;
int preferredBodyType, nativeBodyType; subtype = [[[self bodyStructure] valueForKey: @"subtype"] lowercaseString];
preferredBodyType = [[context objectForKey: @"BodyPreferenceType"] intValue];
mimeSupport = [[context objectForKey: @"MIMESupport"] intValue];
s = [NSMutableString string]; s = [NSMutableString string];
@ -862,6 +872,11 @@ struct GlobalObjectId {
else else
{ {
// MesssageClass and ContentClass // MesssageClass and ContentClass
if ([subtype isEqualToString: @"signed"])
[s appendFormat: @"<MessageClass xmlns=\"Email:\">%@</MessageClass>", @"IPM.Note.SMIME.MultipartSigned"];
else if ([subtype isEqualToString: @"pkcs7-mime"])
[s appendFormat: @"<MessageClass xmlns=\"Email:\">%@</MessageClass>", @"IPM.Note.SMIME"];
else
[s appendFormat: @"<MessageClass xmlns=\"Email:\">%@</MessageClass>", @"IPM.Note"]; [s appendFormat: @"<MessageClass xmlns=\"Email:\">%@</MessageClass>", @"IPM.Note"];
[s appendFormat: @"<ContentClass xmlns=\"Email:\">%@</ContentClass>", @"urn:content-classes:message"]; [s appendFormat: @"<ContentClass xmlns=\"Email:\">%@</ContentClass>", @"urn:content-classes:message"];
} }
@ -876,10 +891,8 @@ struct GlobalObjectId {
[s appendFormat: @"<InternetCPID xmlns=\"Email:\">%@</InternetCPID>", @"65001"]; [s appendFormat: @"<InternetCPID xmlns=\"Email:\">%@</InternetCPID>", @"65001"];
// Body - namespace 17 // Body - namespace 17
preferredBodyType = [[context objectForKey: @"BodyPreferenceType"] intValue];
nativeBodyType = 1; nativeBodyType = 1;
d = [self _preferredBodyDataUsingType: preferredBodyType nativeType: &nativeBodyType]; d = [self _preferredBodyDataUsingType: preferredBodyType mimeSupport: mimeSupport nativeType: &nativeBodyType];
if (calendar && !d) if (calendar && !d)
{ {
@ -981,9 +994,12 @@ struct GlobalObjectId {
{ {
[s appendString: @"<Body xmlns=\"AirSyncBase:\">"]; [s appendString: @"<Body xmlns=\"AirSyncBase:\">"];
// Set the correct type if client requested text/html but we got text/plain // Set the correct type if client requested text/html but we got text/plain.
// For s/mime mails type is always 4 if mimeSupport is 1 or 2.
if (preferredBodyType == 2 && nativeBodyType == 1) if (preferredBodyType == 2 && nativeBodyType == 1)
[s appendString: @"<Type>1</Type>"]; [s appendString: @"<Type>1</Type>"];
else if (([subtype isEqualToString: @"signed"] || [subtype isEqualToString: @"pkcs7-mime"] ) && mimeSupport > 0)
[s appendString: @"<Type>4</Type>"];
else else
[s appendFormat: @"<Type>%d</Type>", preferredBodyType]; [s appendFormat: @"<Type>%d</Type>", preferredBodyType];
@ -1001,7 +1017,8 @@ struct GlobalObjectId {
// Attachments -namespace 16 // Attachments -namespace 16
attachmentKeys = [self fetchFileAttachmentKeys]; attachmentKeys = [self fetchFileAttachmentKeys];
if ([attachmentKeys count])
if ([attachmentKeys count] && !([subtype isEqualToString: @"signed"]))
{ {
int i; int i;

1
NEWS
View File

@ -2,6 +2,7 @@
------------------ ------------------
New features New features
- initial S/MIME support for EAS (#3327)
Enhancements Enhancements
- we no longer always entirely rewrite messages for Outlook 2013 when using EAS - we no longer always entirely rewrite messages for Outlook 2013 when using EAS

View File

@ -389,6 +389,10 @@ static BOOL debugSoParts = NO;
[[[info valueForKey: @"subtype"] lowercaseString] isEqualToString: @"calendar"]) [[[info valueForKey: @"subtype"] lowercaseString] isEqualToString: @"calendar"])
return info; return info;
if ([[[info valueForKey: @"type"] lowercaseString] isEqualToString: @"application"] &&
[[[info valueForKey: @"subtype"] lowercaseString] isEqualToString: @"pkcs7-mime"])
return info;
/* /*
For each path component, eg 1,1,3 For each path component, eg 1,1,3
@ -810,7 +814,7 @@ static BOOL debugSoParts = NO;
NSMutableDictionary *currentPart; NSMutableDictionary *currentPart;
NSString *newPath; NSString *newPath;
NSArray *subparts; NSArray *subparts;
NSString *type; NSString *type, *subtype;
NSUInteger i; NSUInteger i;
type = [[part objectForKey: @"type"] lowercaseString]; type = [[part objectForKey: @"type"] lowercaseString];
@ -833,7 +837,15 @@ static BOOL debugSoParts = NO;
else else
{ {
if (!path) if (!path)
{
path = @"1"; path = @"1";
// We set the path to 0 in case of a smime mail if not provided.
subtype = [[part objectForKey: @"subtype"] lowercaseString];
if ([subtype isEqualToString: @"pkcs7-mime"])
path = @"0";
}
[self _fetchFileAttachmentKey: part [self _fetchFileAttachmentKey: part
intoArray: keys intoArray: keys
withPath: path withPath: path