001: // The contents of this file are subject to the Mozilla Public License Version
002: // 1.1
003: //(the "License"); you may not use this file except in compliance with the
004: //License. You may obtain a copy of the License at http://www.mozilla.org/MPL/
005: //
006: //Software distributed under the License is distributed on an "AS IS" basis,
007: //WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
008: //for the specific language governing rights and
009: //limitations under the License.
010: //
011: //The Original Code is "The Columba Project"
012: //
013: //The Initial Developers of the Original Code are Frederik Dietz and Timo
014: // Stich.
015: //Portions created by Frederik Dietz and Timo Stich are Copyright (C) 2003.
016: //
017: //All Rights Reserved.
018: package org.columba.mail.gui.message.filter;
019:
020: import java.io.IOException;
021: import java.io.InputStream;
022: import java.util.ArrayList;
023: import java.util.Iterator;
024: import java.util.List;
025:
026: import javax.swing.SwingUtilities;
027:
028: import org.columba.core.io.StreamUtils;
029: import org.columba.core.logging.Logging;
030: import org.columba.mail.command.IMailFolderCommandReference;
031: import org.columba.mail.command.MailFolderCommandReference;
032: import org.columba.mail.config.AccountItem;
033: import org.columba.mail.config.MailConfig;
034: import org.columba.mail.config.SecurityItem;
035: import org.columba.mail.folder.IMailbox;
036: import org.columba.mail.gui.frame.MailFrameMediator;
037: import org.columba.mail.gui.message.IMessageController;
038: import org.columba.mail.gui.message.viewer.SecurityStatusViewer;
039: import org.columba.mail.message.ColumbaMessage;
040: import org.columba.mail.pgp.JSCFController;
041: import org.columba.mail.pgp.PGPPassChecker;
042: import org.columba.mail.util.MailResourceLoader;
043: import org.columba.ristretto.io.CharSequenceSource;
044: import org.columba.ristretto.io.Source;
045: import org.columba.ristretto.message.MimeHeader;
046: import org.columba.ristretto.message.MimePart;
047: import org.columba.ristretto.message.MimeTree;
048: import org.columba.ristretto.message.MimeType;
049: import org.columba.ristretto.parser.BodyParser;
050: import org.columba.ristretto.parser.HeaderParser;
051: import org.columba.ristretto.parser.ParserException;
052: import org.waffel.jscf.JSCFConnection;
053: import org.waffel.jscf.JSCFException;
054: import org.waffel.jscf.JSCFResultSet;
055: import org.waffel.jscf.JSCFStatement;
056:
057: /**
058: * Filter decrypting and verifying messages.
059: * <p>
060: * A {@link SecurityStatusEvent}is used to notify all listeners.
061: * <p>
062: * {@link SecurityStatusViewer}is currently the only listener. In the future a
063: * status icon will be added to the message header, too.
064: * <p>
065: *
066: * @author fdietz
067: *
068: */
069: public class PGPMessageFilter extends AbstractFilter {
070:
071: private static final java.util.logging.Logger LOG = java.util.logging.Logger
072: .getLogger("org.columba.mail.gui.message.filter");
073:
074: private MimeTree mimePartTree;
075:
076: private int pgpMode = SecurityStatusViewer.NOOP;
077:
078: private String pgpMessage = "";
079:
080: private ColumbaMessage message;
081:
082: private List listeners;
083:
084: public PGPMessageFilter(MailFrameMediator mediator,
085: IMessageController messageController) {
086: super (mediator, messageController);
087:
088: listeners = new ArrayList();
089: }
090:
091: public void addSecurityStatusListener(SecurityStatusListener l) {
092: listeners.add(l);
093: }
094:
095: public void fireSecurityStatusEvent(SecurityStatusEvent ev) {
096: final SecurityStatusEvent event = ev;
097:
098: Iterator it = listeners.iterator();
099: while (it.hasNext()) {
100: final SecurityStatusListener l = (SecurityStatusListener) it
101: .next();
102: Runnable doWorkRunnable = new Runnable() {
103: public void run() {
104: l.statusUpdate(event);
105:
106: }
107: };
108: SwingUtilities.invokeLater(doWorkRunnable);
109:
110: }
111: }
112:
113: /**
114: * @see org.columba.mail.gui.message.filter.Filter#filter(org.columba.mail.folder.Folder,
115: * java.lang.Object)
116: */
117: public IMailFolderCommandReference filter(IMailbox folder,
118: Object uid) throws Exception {
119:
120: mimePartTree = folder.getMimePartTree(uid);
121:
122: // Check if the message still exists
123: // or has been moved by e.g. a filter
124: if (mimePartTree == null)
125: return null;
126:
127: // TODO (@author waffel): encrypt AND sign dosN#t work. The message is
128: // always only
129: // encrypted. We need a function that knows, here
130: // is an encrypted AND signed Message. Thus first encyrpt and then
131: // verifySign the message
132: // if this message is signed/encrypted we have to use
133: // GnuPG to extract the decrypted bodypart
134: // - multipart/encrypted
135: // - multipart/signed
136: MimeType firstPartMimeType = mimePartTree.getRootMimeNode()
137: .getHeader().getMimeType();
138:
139: AccountItem defAccount = MailConfig.getInstance()
140: .getAccountList().getDefaultAccount();
141:
142: boolean pgpActive = false;
143:
144: if (defAccount != null) {
145: SecurityItem pgpItem = defAccount.getPGPItem();
146: LOG.fine("pgp activated: " + pgpItem.get("enabled"));
147: pgpActive = new Boolean((pgpItem.get("enabled")))
148: .booleanValue();
149: }
150:
151: IMailFolderCommandReference result = null;
152: LOG.fine("pgp is true");
153: if (firstPartMimeType.getSubtype().equals("signed")) {
154: result = verify(folder, uid, pgpActive);
155:
156: } else if (firstPartMimeType.getSubtype().equals("encrypted")) {
157: LOG.fine("Mimepart type encrypted found");
158: result = decrypt(folder, uid, pgpActive);
159:
160: } else {
161: pgpMode = SecurityStatusViewer.NOOP;
162: }
163:
164: // notify listeners
165: fireSecurityStatusEvent(new SecurityStatusEvent(this ,
166: pgpMessage, pgpMode));
167:
168: return result;
169: }
170:
171: /**
172: * Decrypt message.
173: *
174: * @param folder
175: * selected folder
176: * @param uid
177: * selected message UID
178: * @throws Exception
179: * @throws IOException
180: */
181: private IMailFolderCommandReference decrypt(IMailbox folder,
182: Object uid, boolean pgpActive) throws Exception,
183: IOException {
184: InputStream decryptedStream = null;
185: LOG.fine("start decrypting");
186: if (!pgpActive) {
187: pgpMessage = "";
188: pgpMode = SecurityStatusViewer.NO_KEY;
189: } else {
190:
191: MimePart encryptedMultipart = mimePartTree
192: .getRootMimeNode();
193:
194: // the second child must be the encrypted message
195: InputStream encryptedPart = folder.getMimePartBodyStream(
196: uid, encryptedMultipart.getChild(1).getAddress());
197:
198: try {
199: JSCFController controller = JSCFController
200: .getInstance();
201: JSCFConnection con = controller.getConnection();
202: LOG.fine("new JSCConnection");
203: JSCFStatement stmt = con.createStatement();
204: LOG.fine("new Statement");
205: PGPPassChecker passCheck = PGPPassChecker.getInstance();
206: boolean check = passCheck.checkPassphrase(con);
207: LOG.fine("after pass check, check is " + check);
208: if (!check) {
209: pgpMode = SecurityStatusViewer.DECRYPTION_FAILURE;
210: // TODO (@author fdietz): make i18n!
211: pgpMessage = "wrong passphrase";
212: return null;
213: }
214: LOG.fine("encrypted is != null?: "
215: + (encryptedPart != null));
216: JSCFResultSet res = stmt.executeDecrypt(encryptedPart);
217: LOG.fine("after calling decrypting");
218: if (res.isError()) {
219: LOG.fine("the result set contains errors ");
220: pgpMode = SecurityStatusViewer.DECRYPTION_FAILURE;
221: pgpMessage = StreamUtils.readCharacterStream(
222: res.getErrorStream()).toString();
223: LOG.fine("error message: " + pgpMessage);
224: decryptedStream = res.getResultStream();
225: // return null;
226: } else {
227: decryptedStream = res.getResultStream();
228: pgpMode = SecurityStatusViewer.DECRYPTION_SUCCESS;
229: }
230: } catch (JSCFException e) {
231: e.printStackTrace();
232: LOG.severe(e.getMessage());
233: pgpMode = SecurityStatusViewer.DECRYPTION_FAILURE;
234: pgpMessage = e.getMessage();
235:
236: // just show the encrypted raw message
237: decryptedStream = encryptedPart;
238: }
239: }
240: try {
241: LOG.fine("decrypted Stream is: " + decryptedStream);
242: CharSequence decryptedBodyPart = "";
243: // if the pgp mode is active we should get the decrypted part
244: if (pgpActive) {
245: // TODO (@author fdietz): should be removed if we only use
246: // Streams!
247: decryptedBodyPart = StreamUtils
248: .readCharacterStream(decryptedStream);
249: // check if the returned String is has a length != 0
250: if (decryptedBodyPart.length() == 0) {
251: LOG
252: .fine("decrypted body part has a 0 length ... fixing it");
253: decryptedBodyPart = new StringBuffer(
254: "Content-Type: text/plain; charset=\"ISO-8859-15\"\n\n");
255: }
256: }
257: // else we set the body to the i18n String
258: else {
259: decryptedBodyPart = new StringBuffer(
260: "Content-Type: text/plain; charset=\"ISO-8859-15\"\n\n"
261: + MailResourceLoader.getString("menu",
262: "mainframe",
263: "security_decrypt_encrypted")
264: + "\n");
265: }
266: LOG.fine("the decrypted Body part: " + decryptedBodyPart);
267: // construct new Message from decrypted string
268: message = new ColumbaMessage(folder.getAllHeaderFields(uid));
269:
270: Source decryptedSource = new CharSequenceSource(
271: decryptedBodyPart);
272: MimeHeader mimeHeader = new MimeHeader(HeaderParser
273: .parse(decryptedSource));
274: mimePartTree = new MimeTree(BodyParser.parseMimePart(
275: mimeHeader, decryptedSource));
276: message.setMimePartTree(mimePartTree);
277:
278: InputStream messageSourceStream = folder
279: .getMessageSourceStream(uid);
280: message.setSource(new CharSequenceSource(StreamUtils
281: .readCharacterStream(messageSourceStream)));
282: messageSourceStream.close();
283:
284: // call AbstractFilter to do the tricky part
285: return filter(folder, uid, message);
286: // header = (ColumbaHeader) message.getHeaderInterface();
287: } catch (ParserException e) {
288: e.printStackTrace();
289:
290: } catch (IOException e) {
291: e.printStackTrace();
292:
293: }
294:
295: /*
296: * controlPart.close(); encryptedPart.close(); if (decryptedStream !=
297: * null) { decryptedStream.close(); }
298: */
299: return null;
300: }
301:
302: /**
303: * Verify message.
304: *
305: * @param folder
306: * selected folder
307: * @param uid
308: * selected message UID
309: * @throws Exception
310: * @throws IOException
311: */
312: private MailFolderCommandReference verify(IMailbox folder,
313: Object uid, boolean pgpActive) throws Exception,
314: IOException {
315: if (!pgpActive) {
316: pgpMessage = "";
317: pgpMode = SecurityStatusViewer.NO_KEY;
318: return null;
319: }
320: MimePart signedMultipart = mimePartTree.getRootMimeNode();
321:
322: // the first child must be the signed part
323: InputStream signedPart = folder.getMimePartSourceStream(uid,
324: signedMultipart.getChild(0).getAddress());
325:
326: // the second child must be the pgp-signature
327: InputStream signature = folder.getMimePartBodyStream(uid,
328: signedMultipart.getChild(1).getAddress());
329:
330: try {
331: JSCFController controller = JSCFController.getInstance();
332: JSCFConnection con = controller.getConnection();
333: JSCFStatement stmt = con.createStatement();
334: String micalg = signedMultipart.getHeader()
335: .getContentParameter("micalg").substring(4);
336: JSCFResultSet res = stmt.executeVerify(signedPart,
337: signature, micalg);
338: if (res.isError()) {
339: pgpMode = SecurityStatusViewer.VERIFICATION_FAILURE;
340: pgpMessage = StreamUtils.readCharacterStream(
341: res.getErrorStream()).toString();
342: } else {
343: pgpMode = SecurityStatusViewer.VERIFICATION_SUCCESS;
344: pgpMessage = StreamUtils.readCharacterStream(
345: res.getResultStream()).toString();
346: }
347:
348: } catch (JSCFException e) {
349:
350: if (Logging.DEBUG)
351: e.printStackTrace();
352:
353: pgpMode = SecurityStatusViewer.VERIFICATION_FAILURE;
354: pgpMessage = e.getMessage();
355: // something really got wrong here -> show error dialog
356: // JOptionPane.showMessageDialog(null, e.getMessage());
357:
358: pgpMode = SecurityStatusViewer.VERIFICATION_FAILURE;
359: }
360:
361: signedPart.close();
362: signature.close();
363:
364: return null;
365: }
366:
367: }
|