diff --git a/ChangeLog b/ChangeLog index 875c18e80..bb289b14c 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,13 @@ +2009-09-14 Wolfgang Sourdeau + + * UI/MailPartViewers/UIxMailPartSignedViewer.[hm]: new module + class that implements the viewer for multipart/signed messages. + + * UI/WebServerResources/MailerUI.js (configureSignatureFlagImage): + new method that check whether the message being displayed is + signed and if so, displays the proper image depending on the + validation status. + 2009-09-14 Cyril Robert * UI/Common/UIxPageFrame.m (commonLocalizableStrings): clabels are no longer diff --git a/NEWS b/NEWS index b123e3837..447509613 100644 --- a/NEWS +++ b/NEWS @@ -14,6 +14,7 @@ - added support for remote ICS subscriptions - added support for ICS and vCard/LDIF import - added support for event delegation (resend an invitation to someone else) +- added alpha support for checking and displaying signed messages 1.0-20090812 (1.0.4) -------------------- diff --git a/UI/MailPartViewers/GNUmakefile b/UI/MailPartViewers/GNUmakefile index a8cc1428e..25b5dce99 100644 --- a/UI/MailPartViewers/GNUmakefile +++ b/UI/MailPartViewers/GNUmakefile @@ -20,6 +20,7 @@ MailPartViewers_OBJC_FILES += \ UIxMailPartImageViewer.m \ UIxMailPartLinkViewer.m \ UIxMailPartMixedViewer.m \ + UIxMailPartSignedViewer.m \ UIxMailPartAlternativeViewer.m \ UIxMailPartMessageViewer.m \ UIxMailPartICalViewer.m \ diff --git a/UI/MailPartViewers/UIxMailPartSignedViewer.h b/UI/MailPartViewers/UIxMailPartSignedViewer.h new file mode 100644 index 000000000..08d390760 --- /dev/null +++ b/UI/MailPartViewers/UIxMailPartSignedViewer.h @@ -0,0 +1,47 @@ +/* UIxMailPartSignedViewer.h - this file is part of SOGo + * + * Copyright (C) 2009 Inverse inc. + * + * Author: Wolfgang Sourdeau + * + * 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 2, 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. + */ + +#ifndef UIXMAILPARTSIGNEDVIEWER_H +#define UIXMAILPARTSIGNEDVIEWER_H + +#import "UIxMailPartViewer.h" + +@class NSString; +@class NGPart; + +@interface UIxMailPartSignedViewer : UIxMailPartViewer +{ + BOOL processed; + NGPart *messagePart; + + BOOL validSignature; + NSString *validationError; +} + +- (NSString *) flatContentAsString; + +- (BOOL) validSignature; +- (NSString *) validationError; + +@end + +#endif /* UIXMAILPARTSIGNEDVIEWER_H */ diff --git a/UI/MailPartViewers/UIxMailPartSignedViewer.m b/UI/MailPartViewers/UIxMailPartSignedViewer.m new file mode 100644 index 000000000..7514a14c5 --- /dev/null +++ b/UI/MailPartViewers/UIxMailPartSignedViewer.m @@ -0,0 +1,267 @@ +/* UIxMailPartSignedViewer.m - this file is part of SOGo + * + * Copyright (C) 2009 Inverse inc. + * + * Author: Wolfgang Sourdeau + * + * 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 2, 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. + */ + +#include +#include +#include +#include +#include + +#import + +#import +#import +#import +#import +#import +#import +#import + +#import +#import + +#import +#import + +#import "UIxMailPartSignedViewer.h" + +@interface NGMimeMessage (SOGoExtension) + +- (id ) mimePartFromPath: (NSArray *) path; + +@end + +@implementation NGMimeMessage (SOGoExtension) + +- (id ) mimePartFromPath: (NSArray *) path +{ + int count, max, partNumber; + BOOL overflow; + id currentPart, foundPart; + NSArray *parts; + + foundPart = nil; + overflow = NO; + + currentPart = [self body]; + + max = [path count]; + for (count = 0; (!overflow && !foundPart) && count < max; count++) + { + partNumber = [[path objectAtIndex: count] intValue]; + if (partNumber == 0) + foundPart = currentPart; + else + { + if ([currentPart isKindOfClass: [NGMimeMultipartBody class]]) + { + parts = [(NGMimeMultipartBody *) currentPart parts]; + if (partNumber <= [parts count]) + currentPart = [parts objectAtIndex: partNumber - 1]; + else + overflow = YES; + } + else + overflow = YES; + } + } + if (!overflow) + foundPart = currentPart; + + return foundPart; +} + +@end + +@interface NGMimeType (SOGoExtension) + +- (BOOL) isEqualToString: (NSString *) otherType; + +@end + +@implementation NGMimeType (SOGoExtension) + +- (BOOL) isEqualToString: (NSString *) otherType +{ + NSString *thisString; + + thisString = [NSString stringWithFormat: @"%@/%@", + [self type], [self subType]]; + + return [thisString isEqualToString: otherType]; +} + +@end + +@implementation UIxMailPartSignedViewer : UIxMailPartViewer + +- (void) _setupParts +{ + NSData *content; + NGMimeMessageParser *parser; + NGMimeMessage *message; + NSArray *path; + id co; + NSArray *parts; + + co = [self clientObject]; + + content = [co content]; + parser = [NGMimeMessageParser new]; + message = [parser parsePartFromData: content]; + if ([co respondsToSelector: @selector (bodyPartPath)]) + path = [(SOGoMailBodyPart *) co bodyPartPath]; + else + path = [NSArray arrayWithObject: @"0"]; + parts = [(NGMimeMultipartBody *) [message mimePartFromPath: path] parts]; + if ([parts count] == 2) + messagePart = [parts objectAtIndex: 0]; + + [parser release]; +} + +- (NSString *) flatContentAsString +{ + NSString *body, *content; + NGMimeType *mimeType; + + [self _setupParts]; + + mimeType = [messagePart contentType]; + if ([mimeType isEqualToString: @"text/plain"]) + { + body = [[messagePart body] stringByEscapingHTMLString]; + content = [[body stringByDetectingURLs] + stringByConvertingCRLNToHTML]; + } + else + { + NSLog (@"unhandled mime type in multipart/signed: '%@'", mimeType); + content = nil; + } + + return content; +} + +- (X509_STORE *) _setupVerify +{ + X509_STORE *store; + X509_LOOKUP *lookup; + BOOL success; + + success = NO; + + store = X509_STORE_new(); + + 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; + } + } + + return store; +} + +- (void) _processMessage +{ + BIO *msgBio, *inData; + X509_STORE *x509Store; + PKCS7 *p7; + int err; + NSData *signedData; + char sslError[1024]; + + *sslError = 0; + + ERR_clear_error(); + + signedData = [[self clientObject] content]; + msgBio = BIO_new_mem_buf ((void *) [signedData bytes], [signedData length]); + + inData = NULL; + p7 = SMIME_read_PKCS7 (msgBio, &inData); + err = ERR_get_error(); + if (err) + { + ERR_error_string_n (err, sslError, 1023); + validSignature = NO; + } + else + { + x509Store = [self _setupVerify]; + validSignature = (PKCS7_verify(p7, NULL, x509Store, inData, + NULL, PKCS7_DETACHED) == 1); + + err = ERR_get_error(); + if (err) + ERR_error_string_n (err, sslError, 1023); + + if (x509Store) + X509_STORE_free (x509Store); + } + + BIO_free (msgBio); + if (inData) + BIO_free (inData); + + if (!validSignature) + validationError = [NSString stringWithFormat: @"%s", sslError]; + + processed = YES; +} + +- (BOOL) validSignature +{ + if (!processed) + [self _processMessage]; + + return validSignature; +} + +- (NSString *) validationError +{ + if (!processed) + [self _processMessage]; + + return validationError; +} + +@end diff --git a/UI/MailPartViewers/UIxMailPartViewer.m b/UI/MailPartViewers/UIxMailPartViewer.m index ee388b018..99c8c4ea9 100644 --- a/UI/MailPartViewers/UIxMailPartViewer.m +++ b/UI/MailPartViewers/UIxMailPartViewer.m @@ -156,7 +156,7 @@ - (NSData *) content { - return [[self clientPart] fetchBLOB]; + return [[self clientObject] fetchBLOB]; } - (NSString *) flatContentAsString diff --git a/UI/MailPartViewers/UIxMailRenderingContext.m b/UI/MailPartViewers/UIxMailRenderingContext.m index b24856f5b..867dd00cc 100644 --- a/UI/MailPartViewers/UIxMailRenderingContext.m +++ b/UI/MailPartViewers/UIxMailRenderingContext.m @@ -132,7 +132,7 @@ static BOOL showNamedTextAttachmentsInline = NO; /* Note: we cannot cache the multipart viewers, because it can be nested */ // TODO: temporary workaround (treat it like a plain mixed part) - return [self mixedViewer]; + return [viewer pageWithName: @"UIxMailPartSignedViewer"]; } - (WOComponent *) alternativeViewer diff --git a/UI/WebServerResources/MailerUI.css b/UI/WebServerResources/MailerUI.css index b06985c6d..7817d93a8 100644 --- a/UI/WebServerResources/MailerUI.css +++ b/UI/WebServerResources/MailerUI.css @@ -331,6 +331,19 @@ INPUT#loadImagesButton right: 1em; } +#signedImage +{ + position: absolute; + top: 5px; + right: 5px; +} + +.popup #signedImage +{ + top: 54px; + right: 5px; +} + TABLE.mailer_fieldtable { top: 0px; left: 0px; diff --git a/UI/WebServerResources/MailerUI.js b/UI/WebServerResources/MailerUI.js index f511a947d..2132e1da5 100644 --- a/UI/WebServerResources/MailerUI.js +++ b/UI/WebServerResources/MailerUI.js @@ -947,6 +947,7 @@ function loadMessage(idx) { } configureLoadImagesButton(); + configureSignatureFlagImage(); } function configureLoadImagesButton() { @@ -965,6 +966,30 @@ function configureLoadImagesButton() { } } +function configureSignatureFlagImage() { + var signedPart = $("signedMessage"); + if (signedPart) { + var loadImagesButton = $("loadImagesButton"); + var parentNode = loadImagesButton.parentNode; + var valid = parseInt(signedPart.getAttribute("valid")); + var flagImage; + var error = null; + if (valid) { + flagImage = "signature-ok.png"; + } else { + flagImage = "signature-not-ok.png"; + error = signedPart.getAttribute("error"); + } + var attrs = { src: ResourcesURL + "/" + flagImage }; + if (error) { + attrs["title"] = error; + } + var newImg = createElement("img", "signedImage", null, + null, attrs); + loadImagesButton.parentNode.insertBefore(newImg, loadImagesButton.nextSibling); + } +} + function configureLinksInMessage() { var messageDiv = $('messageContent'); var mailContentDiv = document.getElementsByClassName('mailer_mailcontent', @@ -1190,6 +1215,7 @@ function messageCallback(http) { configureLinksInMessage(); resizeMailContent(); configureLoadImagesButton(); + configureSignatureFlagImage(); if (http.callbackData) { var cachedMessage = new Array(); diff --git a/UI/WebServerResources/UIxMailPopupView.js b/UI/WebServerResources/UIxMailPopupView.js index 3d53b91aa..d021baa3a 100644 --- a/UI/WebServerResources/UIxMailPopupView.js +++ b/UI/WebServerResources/UIxMailPopupView.js @@ -1,57 +1,58 @@ /* -*- Mode: java; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ function onPrintCurrentMessage(event) { - window.print(); + window.print(); - preventDefault(event); + preventDefault(event); } function initPopupMailer(event) { - configureLinksInMessage(); - resizeMailContent(); + configureLinksInMessage(); + resizeMailContent(); - var loadImagesButton = $("loadImagesButton"); - if (loadImagesButton) - loadImagesButton.observe("click", - onMessageLoadImages.bindAsEventListener(loadImagesButton)); + var loadImagesButton = $("loadImagesButton"); + if (loadImagesButton) + loadImagesButton.observe("click", + onMessageLoadImages.bindAsEventListener(loadImagesButton)); - configureLoadImagesButton(); + configureLoadImagesButton(); + configureSignatureFlagImage(); } function onMessageLoadImages(event) { - var msguid = window.opener.Mailer.currentMessages[window.opener.Mailer.currentMailbox]; - var url = (window.opener.ApplicationBaseURL + window.opener.encodeURI(window.opener.Mailer.currentMailbox) + "/" - + msguid + "/view?noframe=1&unsafe=1"); - document.messageAjaxRequest - = triggerAjaxRequest(url, messageCallback, msguid); + var msguid = window.opener.Mailer.currentMessages[window.opener.Mailer.currentMailbox]; + var url = (window.opener.ApplicationBaseURL + window.opener.encodeURI(window.opener.Mailer.currentMailbox) + "/" + + msguid + "/view?noframe=1&unsafe=1"); + document.messageAjaxRequest + = triggerAjaxRequest(url, messageCallback, msguid); } function onICalendarButtonClick(event) { - var link = $("iCalendarAttachment").value; - if (link) { - var urlstr = link + "/" + this.action; - var currentMsg; - if (window.opener && window.opener.open && !window.opener.closed && window.messageUID) { - var c = window.opener; - window.opener.triggerAjaxRequest(urlstr, - window.opener.ICalendarButtonCallback, - window.messageUID); + var link = $("iCalendarAttachment").value; + if (link) { + var urlstr = link + "/" + this.action; + var currentMsg; + if (window.opener && window.opener.open && !window.opener.closed && window.messageUID) { + var c = window.opener; + window.opener.triggerAjaxRequest(urlstr, + window.opener.ICalendarButtonCallback, + window.messageUID); + } } - } - else - log("no link"); + else + log("no link"); } function onMenuDeleteMessage(event) { - if (window.opener && window.opener.open && !window.opener.closed) { - var url = ApplicationBaseURL + encodeURI(mailboxName) + "/deleteMessages"; - var path = mailboxName + "/" + messageName; - - window.opener.deleteMessageWithDelay(url, messageName, mailboxName, path); - } + if (window.opener && window.opener.open && !window.opener.closed) { + var url = ApplicationBaseURL + encodeURI(mailboxName) + "/deleteMessages"; + var path = mailboxName + "/" + messageName; + + window.opener.deleteMessageWithDelay(url, messageName, mailboxName, path); + } - window.close(); - return false; + window.close(); + return false; } document.observe("dom:loaded", initPopupMailer); diff --git a/UI/WebServerResources/signature-not-ok.png b/UI/WebServerResources/signature-not-ok.png new file mode 100644 index 000000000..34df3bffd Binary files /dev/null and b/UI/WebServerResources/signature-not-ok.png differ diff --git a/UI/WebServerResources/signature-ok.png b/UI/WebServerResources/signature-ok.png new file mode 100644 index 000000000..03ec5ecf0 Binary files /dev/null and b/UI/WebServerResources/signature-ok.png differ