diff --git a/ActiveSync/GNUmakefile.preamble b/ActiveSync/GNUmakefile.preamble index c302ad8be..545cdf46b 100644 --- a/ActiveSync/GNUmakefile.preamble +++ b/ActiveSync/GNUmakefile.preamble @@ -1 +1,4 @@ -# compilation settings +ifeq ($(HAS_LIBRARY_ssl),yes) +ADDITIONAL_CPPFLAGS += -DHAVE_OPENSSL=1 +BUNDLE_LIBS += -lcrypto +endif diff --git a/ActiveSync/SOGoActiveSyncDispatcher+Sync.m b/ActiveSync/SOGoActiveSyncDispatcher+Sync.m index 2683633d1..d2f6448d4 100644 --- a/ActiveSync/SOGoActiveSyncDispatcher+Sync.m +++ b/ActiveSync/SOGoActiveSyncDispatcher+Sync.m @@ -1407,6 +1407,11 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // By default, send MIME mails. See #3146 for details. if (!bodyPreferenceType) bodyPreferenceType = @"4"; + + mimeSupport = [[folderMetadata objectForKey: @"FolderOptions"] objectForKey: @"MIMESupport"]; + + if (!mimeSupport) + mimeSupport = @"1"; } else { @@ -1437,6 +1442,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. } [context setObject: bodyPreferenceType forKey: @"BodyPreferenceType"]; + [context setObject: mimeSupport forKey: @"MIMESupport"]; // // We process the commands from the request diff --git a/ActiveSync/SOGoActiveSyncDispatcher.m b/ActiveSync/SOGoActiveSyncDispatcher.m index 377ebd1c1..f08276c29 100644 --- a/ActiveSync/SOGoActiveSyncDispatcher.m +++ b/ActiveSync/SOGoActiveSyncDispatcher.m @@ -30,6 +30,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "SOGoActiveSyncDispatcher.h" #import +#import #import #import #import @@ -135,6 +136,12 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include +#ifdef HAVE_OPENSSL +#include +#include +#include +#endif + @interface SOGoActiveSyncDispatcher (Sync) - (NSMutableDictionary *) _folderMetadataForKey: (NSString *) theFolderKey; @@ -1262,7 +1269,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - (void) processItemOperations: (id ) theDocumentElement inResponse: (WOResponse *) theResponse { - NSString *fileReference, *realCollectionId, *serverId, *bodyPreferenceType, *collectionId; + NSString *fileReference, *realCollectionId, *serverId, *bodyPreferenceType, *mimeSupport, *collectionId; NSMutableString *s; NSArray *fetchRequests; id aFetch; @@ -1375,6 +1382,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. serverId = [[(id)[theDocumentElement getElementsByTagName: @"ServerId"] lastObject] textValue]; bodyPreferenceType = [[(id)[[(id)[theDocumentElement getElementsByTagName: @"BodyPreference"] lastObject] getElementsByTagName: @"Type"] lastObject] textValue]; [context setObject: bodyPreferenceType forKey: @"BodyPreferenceType"]; + mimeSupport = [[(id)[theDocumentElement getElementsByTagName: @"MIMESupport"] lastObject] textValue]; + [context setObject: mimeSupport forKey: @"MIMESupport"]; currentCollection = [self collectionFromId: realCollectionId type: folderType]; @@ -2102,6 +2111,123 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. [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 ) theDocumentElement + inResponse: (WOResponse *) theResponse +{ + NSMutableString *s; + NSString *cert; + NSData *d; + + cert = [[(id)[theDocumentElement getElementsByTagName: @"Certificate"] lastObject] textValue]; + + s = [NSMutableString string]; + [s appendString: @""]; + [s appendString: @""]; + [s appendString: @""]; + [s appendString: @"1"]; + [s appendFormat: @"%d", [self validateCert: cert]]; + [s appendString: @""]; + + d = [[s dataUsingEncoding: NSUTF8StringEncoding] xml2wbxml]; + + [theResponse setContent: d]; +} + + // // // @@ -2418,39 +2544,85 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. NGMimeMessageParser *parser; NGMimeMessage *message; NSException *error; - NSData *data; - NGMutableHashMap *map; - NGMimeMessage *messageToSend; - NGMimeMessageGenerator *generator; + NSMutableData *data; + NSData *new_from_header; NSDictionary *identity; NSString *fullName, *email; + + const char *bytes; + int i, len; + BOOL found_header; // 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 parser = [[NGMimeMessageParser alloc] init]; message = [parser parsePartFromData: data]; RELEASE(parser); - map = [NGHashMap hashMapWithDictionary: [message headers]]; - identity = [[context activeUser] primaryIdentity]; fullName = [identity objectForKey: @"fullName"]; email = [identity objectForKey: @"email"]; + if ([fullName length]) - [map setObject: [NSString stringWithFormat: @"%@ <%@>", fullName, email] forKey: @"from"]; + new_from_header = [[NSString stringWithFormat: @"From: %@ <%@>\r\n", fullName, email] dataUsingEncoding:NSUTF8StringEncoding]; 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; + } + + if (((*bytes == '\r') && (*(bytes+1) == '\n')) && + (*(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]]; + } - generator = [[[NGMimeMessageGenerator alloc] init] autorelease]; - data = [generator generateMimeFromPart: messageToSend]; - error = [self _sendMail: data recipients: [message allRecipients] saveInSentItems: ([(id)[theDocumentElement getElementsByTagName: @"SaveInSentItems"] count] ? YES : NO)]; diff --git a/ActiveSync/SOGoMailObject+ActiveSync.m b/ActiveSync/SOGoMailObject+ActiveSync.m index cfee21a81..763ed20f2 100644 --- a/ActiveSync/SOGoMailObject+ActiveSync.m +++ b/ActiveSync/SOGoMailObject+ActiveSync.m @@ -476,14 +476,16 @@ struct GlobalObjectId { // // - (NSData *) _preferredBodyDataUsingType: (int) theType + mimeSupport: (int) theMimeSupport nativeType: (int *) theNativeType { NSString *type, *subtype, *encoding; NSData *d; + BOOL isSMIME; type = [[[self bodyStructure] valueForKey: @"type"] lowercaseString]; subtype = [[[self bodyStructure] valueForKey: @"subtype"] lowercaseString]; - + isSMIME = NO; d = nil; // We determine the native type @@ -494,8 +496,14 @@ struct GlobalObjectId { else if ([type isEqualToString: @"multipart"]) *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 - if (theType == 1 || theType == 2) + if ((theType == 1 || theType == 2) && !isSMIME) { if ([type isEqualToString: @"text"] && ![subtype isEqualToString: @"calendar"]) { @@ -536,12 +544,12 @@ struct GlobalObjectId { 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 // with quoted-printable/base64 encoded text parts. It just doesn't decode them. encoding = [[self lookupInfoForBodyPart: @""] objectForKey: @"encoding"]; - if (encoding && ([encoding caseInsensitiveCompare: @"8bit"] == NSOrderedSame)) + if ((encoding && ([encoding caseInsensitiveCompare: @"8bit"] == NSOrderedSame)) && !isSMIME) d = [self _sanitizedMIMEMessage]; else d = [self content]; @@ -656,16 +664,18 @@ struct GlobalObjectId { { NSData *d, *globalObjId; NSArray *attachmentKeys; - NSMutableString *s; - - uint32_t v; - NSString *p; - - id value; - iCalCalendar *calendar; + NSString *p, *subtype; + NSMutableString *s; + id value; - int preferredBodyType, nativeBodyType; + int preferredBodyType, mimeSupport, nativeBodyType; + uint32_t v; + + subtype = [[[self bodyStructure] valueForKey: @"subtype"] lowercaseString]; + + preferredBodyType = [[context objectForKey: @"BodyPreferenceType"] intValue]; + mimeSupport = [[context objectForKey: @"MIMESupport"] intValue]; s = [NSMutableString string]; @@ -862,7 +872,12 @@ struct GlobalObjectId { else { // MesssageClass and ContentClass - [s appendFormat: @"%@", @"IPM.Note"]; + if ([subtype isEqualToString: @"signed"]) + [s appendFormat: @"%@", @"IPM.Note.SMIME.MultipartSigned"]; + else if ([subtype isEqualToString: @"pkcs7-mime"]) + [s appendFormat: @"%@", @"IPM.Note.SMIME"]; + else + [s appendFormat: @"%@", @"IPM.Note"]; [s appendFormat: @"%@", @"urn:content-classes:message"]; } @@ -876,10 +891,8 @@ struct GlobalObjectId { [s appendFormat: @"%@", @"65001"]; // Body - namespace 17 - preferredBodyType = [[context objectForKey: @"BodyPreferenceType"] intValue]; - nativeBodyType = 1; - d = [self _preferredBodyDataUsingType: preferredBodyType nativeType: &nativeBodyType]; + d = [self _preferredBodyDataUsingType: preferredBodyType mimeSupport: mimeSupport nativeType: &nativeBodyType]; if (calendar && !d) { @@ -981,9 +994,12 @@ struct GlobalObjectId { { [s appendString: @""]; - // 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) [s appendString: @"1"]; + else if (([subtype isEqualToString: @"signed"] || [subtype isEqualToString: @"pkcs7-mime"] ) && mimeSupport > 0) + [s appendString: @"4"]; else [s appendFormat: @"%d", preferredBodyType]; @@ -1001,7 +1017,8 @@ struct GlobalObjectId { // Attachments -namespace 16 attachmentKeys = [self fetchFileAttachmentKeys]; - if ([attachmentKeys count]) + + if ([attachmentKeys count] && !([subtype isEqualToString: @"signed"])) { int i; diff --git a/NEWS b/NEWS index c06fbc82e..2ad60b0da 100644 --- a/NEWS +++ b/NEWS @@ -2,6 +2,7 @@ ------------------ New features + - initial S/MIME support for EAS (#3327) Enhancements - we no longer always entirely rewrite messages for Outlook 2013 when using EAS diff --git a/SoObjects/Mailer/SOGoMailObject.m b/SoObjects/Mailer/SOGoMailObject.m index 3975dd82c..7e695bcc2 100644 --- a/SoObjects/Mailer/SOGoMailObject.m +++ b/SoObjects/Mailer/SOGoMailObject.m @@ -389,6 +389,10 @@ static BOOL debugSoParts = NO; [[[info valueForKey: @"subtype"] lowercaseString] isEqualToString: @"calendar"]) 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 @@ -810,7 +814,7 @@ static BOOL debugSoParts = NO; NSMutableDictionary *currentPart; NSString *newPath; NSArray *subparts; - NSString *type; + NSString *type, *subtype; NSUInteger i; type = [[part objectForKey: @"type"] lowercaseString]; @@ -833,7 +837,15 @@ static BOOL debugSoParts = NO; else { 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 intoArray: keys withPath: path