diff --git a/SoObjects/Mailer/SOGoDraftObject.h b/SoObjects/Mailer/SOGoDraftObject.h index 2d3bd9610..d17d093a9 100644 --- a/SoObjects/Mailer/SOGoDraftObject.h +++ b/SoObjects/Mailer/SOGoDraftObject.h @@ -61,6 +61,11 @@ NSString *sourceFlag; NSString *sourceFolder; BOOL isHTML; + BOOL sign; + BOOL encrypt; + + // Used during S/MIME encryption + NSMutableDictionary *certificates; } /* contents */ @@ -79,6 +84,13 @@ - (void) setIsHTML: (BOOL) aBool; - (BOOL) isHTML; +- (void) setSign: (BOOL) aBool; +- (BOOL) sign; + +- (void) setEncrypt: (BOOL) aBool; +- (BOOL) encrypt; + + /* for replies and forwards */ - (NSString *) inReplyTo; - (void) setInReplyTo: (NSString *) newInReplyTo; @@ -105,8 +117,7 @@ /* NGMime representations */ -- (NGMimeMessage *) mimeMessage; -- (NSData *) mimeMessageAsData; +- (NSData *) mimeMessageForRecipient: (NSString *) theRecipient; /* operations */ - (NSArray *) allRecipients; diff --git a/SoObjects/Mailer/SOGoDraftObject.m b/SoObjects/Mailer/SOGoDraftObject.m index 29e2b782e..581220243 100644 --- a/SoObjects/Mailer/SOGoDraftObject.m +++ b/SoObjects/Mailer/SOGoDraftObject.m @@ -41,6 +41,7 @@ #import #import #import +#import #import #import #import @@ -65,6 +66,7 @@ #import #import "NSData+Mail.h" +#import "NSData+SMIME.h" #import "NSString+Mail.h" #import "SOGoDraftsFolder.h" #import "SOGoMailAccount.h" @@ -120,13 +122,16 @@ static NSString *userAgent = nil; { sourceIMAP4ID = -1; IMAP4ID = -1; - headers = [NSMutableDictionary new]; + headers = [[NSMutableDictionary alloc] init]; + certificates = [[NSMutableDictionary alloc] init]; text = @""; path = nil; sourceURL = nil; sourceFlag = nil; inReplyTo = nil; isHTML = NO; + sign = NO; + encrypt = NO; } return self; @@ -135,6 +140,7 @@ static NSString *userAgent = nil; - (void) dealloc { [headers release]; + [certificates release]; [text release]; [path release]; [sourceURL release]; @@ -317,6 +323,25 @@ static NSString *userAgent = nil; return isHTML; } +- (void) setSign: (BOOL) aBool +{ + sign = aBool; +} +- (BOOL) sign +{ + return sign; +} + +- (void) setEncrypt: (BOOL) aBool +{ + encrypt = aBool; +} + +- (BOOL) encrypt +{ + return encrypt; +} + - (NSString *) inReplyTo { return inReplyTo; @@ -400,7 +425,7 @@ static NSString *userAgent = nil; [infos setObject: sourceFolder forKey: @"sourceFolder"]; } - if ([infos writeToFile: [self infoPath] atomically:YES]) + if ([infos writeToFile: [self infoPath] atomically: YES]) error = nil; else { @@ -536,7 +561,7 @@ static NSString *userAgent = nil; id result; error = nil; - message = [self mimeMessageAsData]; + message = [self mimeMessageForRecipient: nil]; if (!message) { @@ -730,7 +755,7 @@ static NSString *userAgent = nil; /* CC processing if we reply-to-all: - we add all 'to' and 'cc' fields */ if (_replyToAll) { - to = [NSMutableArray new]; + to = [[NSMutableArray alloc] init]; [addrs setArray: [_envelope to]]; [self _purgeRecipients: allRecipients @@ -1006,7 +1031,7 @@ static NSString *userAgent = nil; if ([mimeType length] > 0) { pmime = [self pathToAttachmentWithName: [NSString stringWithFormat: @".%@.mime", name]]; - if (![[mimeType dataUsingEncoding: NSUTF8StringEncoding] writeToFile: pmime atomically: YES]) + if (![[mimeType dataUsingEncoding: NSUTF8StringEncoding] writeToFile: pmime atomically: YES]) { [[NSFileManager defaultManager] removeFileAtPath: p handler: nil]; return [NSException exceptionWithHTTPStatus: 500 /* Server Error */ @@ -1089,7 +1114,7 @@ static NSString *userAgent = nil; NGMimeMessage *message; id body; - message = [[[NGMimeMessage alloc] initWithHeader:map] autorelease]; + message = [[[NGMimeMessage alloc] initWithHeader: map] autorelease]; if (!isHTML) { @@ -1256,8 +1281,8 @@ static NSString *userAgent = nil; content = [content dataByEncodingBase64]; [map setObject: @"base64" forKey: @"content-transfer-encoding"]; } - [map setObject:[NSNumber numberWithInt:[content length]] - forKey: @"content-length"]; + [map setObject: [NSNumber numberWithInt: [content length]] + forKey: @"content-length"]; /* Note: the -init method will create a temporary file! */ body = [[NGMimeFileData alloc] initWithBytes:[content bytes] @@ -1365,6 +1390,7 @@ static NSString *userAgent = nil; - (NGMimeMessage *) mimeMultiPartMessageWithHeaderMap: (NGMutableHashMap *) map extractedBodyParts: (NSArray *) extractedBodyParts andBodyParts: (NSArray *) _bodyParts + bodyOnly: (BOOL) _bodyOnly { NGMimeMessage *message; NGMimeMultipartBody *mBody; @@ -1578,6 +1604,7 @@ static NSString *userAgent = nil; - (NGMimeMessage *) mimeMessageWithHeaders: (NSDictionary *) _headers excluding: (NSArray *) _exclude extractingImages: (BOOL) _extractImages + bodyOnly: (BOOL) _bodyOnly { NSMutableArray *extractedBodyParts; NGMimeMessage *message; @@ -1597,6 +1624,7 @@ static NSString *userAgent = nil; map = [self mimeHeaderMapWithHeaders: _headers excluding: _exclude]; + if (map) { //[self debugWithFormat: @"MIME Envelope: %@", map]; @@ -1608,14 +1636,16 @@ static NSString *userAgent = nil; //[self debugWithFormat: @"attachments: %@", bodyParts]; if ([extractedBodyParts count] == 0 && [allBodyParts count] == 0) - /* no attachments */ - message = [self mimeMessageForContentWithHeaderMap: map]; + { + // no attachment + message = [self mimeMessageForContentWithHeaderMap: (_bodyOnly ? nil : map)]; + } else { - message = [self mimeMultiPartMessageWithHeaderMap: map + message = [self mimeMultiPartMessageWithHeaderMap: (_bodyOnly ? [NGMutableHashMap hashMap] : map) extractedBodyParts: extractedBodyParts - andBodyParts: allBodyParts]; - + andBodyParts: allBodyParts + bodyOnly: _bodyOnly]; //[self debugWithFormat: @"message: %@", message]; } } @@ -1626,33 +1656,73 @@ static NSString *userAgent = nil; // // Return a NGMimeMessage object with inline HTML images () extracted as attachments (). // -- (NGMimeMessage *) mimeMessage +- (NSData *) mimeMessageForRecipient: (NSString *) theRecipient { - return [self mimeMessageWithHeaders: nil excluding: nil extractingImages: YES]; -} + NGMimeMessageGenerator *generator, *partGenerator; + NGMutableHashMap *hashMap; + NGMimeMessage *message; + NSMutableData *d; + NSData *content; -// -// Return a NSData object of the message with no alteration. -// -- (NSData *) mimeMessageAsData -{ - NGMimeMessageGenerator *generator; - NGMimeMessage *mimeMessage; - NSData *message; - - generator = [NGMimeMessageGenerator new]; - mimeMessage = [self mimeMessageWithHeaders: nil excluding: nil extractingImages: NO]; - - if (!mimeMessage) + // Nothing to sign or encrypt, let's generate the message and return immediately + if (![self sign] && ![self encrypt]) { - [generator release]; - return nil; + generator = [[[NGMimeMessageGenerator alloc] init] autorelease]; + return [generator generateMimeFromPart: [self mimeMessageWithHeaders: nil excluding: nil extractingImages: YES bodyOnly: NO]]; } - message = [generator generateMimeFromPart: mimeMessage]; - [generator release]; + // We'll sign and/or encrypt our message. Let's generate the actual body of the message to work with + partGenerator = [[[NGMimePartGenerator alloc] init] autorelease]; + content = [partGenerator generateMimeFromPart: [self mimeMessageWithHeaders: nil excluding: nil extractingImages: YES bodyOnly: YES]]; - return message; + if ([self sign]) + { + NSData *certificate; + + certificate = [[self mailAccountFolder] certificate]; + content = [content signUsingCertificateAndKey: certificate]; + + if (!content) + return nil; + + if (![self encrypt]) + goto finish_smime; + } + + if ([self encrypt]) + { + NSData *certificate; + + if (theRecipient) + { + SOGoContactFolders *contactFolders; + contactFolders = [[[context activeUser] homeFolderInContext: context] + lookupName: @"Contacts" + inContext: context + acquire: NO]; + certificate = [[contactFolders certificateForEmail: theRecipient] convertPKCS7ToPEM]; + } + else + { + certificate = [[self mailAccountFolder] certificate]; + } + + content = [content encryptUsingCertificate: certificate]; + } + + finish_smime: + // We got our mime part, let's add our mail headers + hashMap = [self mimeHeaderMapWithHeaders: nil + excluding: [NSArray arrayWithObjects: @"MIME-Version", @"Content-Type", @"Content-Transfer-Encoding", nil]]; + message = [NGMimeMessage messageWithHeader: hashMap]; + generator = [[[NGMimeMessageGenerator alloc] init] autorelease]; + d = [NSMutableData dataWithData: [generator generateMimeFromPart: message]]; + [d replaceBytesInRange: NSMakeRange([d length]-4, 4) + withBytes: NULL + length: 0]; + [d appendData: content]; + + return d; } // @@ -1700,23 +1770,56 @@ static NSString *userAgent = nil; // - (NSException *) sendMail { + SOGoContactFolders *contactFolders; SOGoUserDefaults *ud; + NSArray *recipients; + NSString *recipient; + int i; ud = [[context activeUser] userDefaults]; + // If we are trying to sign an email but we don't have a S/MIME certificate for that + // IMAP account, we abort + if ([self sign] && ![[self mailAccountFolder] certificate]) + { + return [NSException exceptionWithHTTPStatus: 500 /* server error */ + reason: @"cannot sign email without certificate"]; + } + + // If we are encrypting emails, we must make sure that we have the certificate + // for all recipients otherwise we cannot, of course, encrypt the email. + if ([self encrypt]) + { + NSData *certificate; + + contactFolders = [[[context activeUser] homeFolderInContext: context] + lookupName: @"Contacts" + inContext: context + acquire: NO]; + recipients = [self allBareRecipients]; + for (i = 0; i < [recipients count]; i++) + { + recipient = [recipients objectAtIndex: i]; + certificate = [contactFolders certificateForEmail: recipient]; + if (!certificate) + { + return [NSException exceptionWithHTTPStatus: 500 /* server error */ + reason: @"cannot encrypt email without recipient certificate"]; + } + [certificates setObject: certificate forKey: recipient]; + } + } + if ([ud mailAddOutgoingAddresses]) { - NSString *recipient, *emailAddress, *addressBook, *uid; - NSArray *matchingContacts, *recipients; - SOGoContactFolders *contactFolders; + NSString *emailAddress, *addressBook, *uid; + NSArray *matchingContacts; SOGoContactGCSEntry *newContact; NGMailAddress *parsedRecipient; NGMailAddressParser *parser; SOGoFolder *folder; NGVCard *card; - int i; - // Get all the addressbooks contactFolders = [[[context activeUser] homeFolderInContext: context] lookupName: @"Contacts" @@ -1750,13 +1853,13 @@ static NSString *userAgent = nil; [card addEmail: emailAddress types: nil]; [card setFn: [parsedRecipient displayName]]; - newContact = [SOGoContactGCSEntry objectWithName: uid - inContainer: folder]; + newContact = [SOGoContactGCSEntry objectWithName: uid inContainer: folder]; [newContact setIsNew: YES]; [newContact saveComponent: card]; } } } + return [self sendMailAndCopyToSent: YES]; } @@ -1771,18 +1874,52 @@ static NSString *userAgent = nil; NSException *error; NSData *message; - // We strip the BCC fields prior sending any mails - NGMimeMessageGenerator *generator; - - generator = [[[NGMimeMessageGenerator alloc] init] autorelease]; - message = [generator generateMimeFromPart: [self mimeMessage]]; dd = [[context activeUser] domainDefaults]; + + // If we are encrypting mails, let's generate and + // send them individually + if ([self encrypt]) + { + NSArray *recipients; + NSString *recipient; + int i; + + recipients = [self allBareRecipients]; + + for (i = 0; i < [recipients count]; i++) + { + recipient = [recipients objectAtIndex: i]; + message = [self mimeMessageForRecipient: recipient];; + + if (!message) + return [NSException exceptionWithHTTPStatus: 500 + reason: @"could not generate message content"]; + + error = [[SOGoMailer mailerWithDomainDefaults: dd] + sendMailData: message + toRecipients: [NSArray arrayWithObject: recipient] + sender: [self sender] + withAuthenticator: [self authenticatorInContext: context] + inContext: context]; + } + + // Next we generate + } + + // Encryption is done or not, if we didn't have to. + message = [self mimeMessageForRecipient: nil]; + + if (!message) + return [NSException exceptionWithHTTPStatus: 500 + reason: @"could not generate message content"]; + error = [[SOGoMailer mailerWithDomainDefaults: dd] sendMailData: message toRecipients: [self allBareRecipients] sender: [self sender] withAuthenticator: [self authenticatorInContext: context] inContext: context]; + if (!error && copyToSent) { sentFolder = [[self mailAccountFolder] sentFolderInContext: context]; @@ -1841,7 +1978,7 @@ static NSString *userAgent = nil; NSString *str; NSData *message; - message = [self mimeMessageAsData]; + message = [self mimeMessageForRecipient: nil]; if (message) { str = [[NSString alloc] initWithData: message