Author: bago Date: Tue Oct 6 17:56:14 2009 New Revision: 822379 URL: http://svn.apache.org/viewvc?rev=822379&view=rev Log: First draft of jDKIM mailets: DKIMSign and ConvertTo7Bit.
Added: james/jdkim/trunk/mailets/src/ james/jdkim/trunk/mailets/src/main/ james/jdkim/trunk/mailets/src/main/java/ james/jdkim/trunk/mailets/src/main/java/org/ james/jdkim/trunk/mailets/src/main/java/org/apache/ james/jdkim/trunk/mailets/src/main/java/org/apache/james/ james/jdkim/trunk/mailets/src/main/java/org/apache/james/jdkim/ james/jdkim/trunk/mailets/src/main/java/org/apache/james/jdkim/mailets/ james/jdkim/trunk/mailets/src/main/java/org/apache/james/jdkim/mailets/ConvertTo7Bit.java (with props) james/jdkim/trunk/mailets/src/main/java/org/apache/james/jdkim/mailets/DKIMSign.java (with props) james/jdkim/trunk/mailets/src/main/java/org/apache/james/jdkim/mailets/HeaderSkippingOutputStream.java (with props) Added: james/jdkim/trunk/mailets/src/main/java/org/apache/james/jdkim/mailets/ConvertTo7Bit.java URL: http://svn.apache.org/viewvc/james/jdkim/trunk/mailets/src/main/java/org/apache/james/jdkim/mailets/ConvertTo7Bit.java?rev=822379&view=auto ============================================================================== --- james/jdkim/trunk/mailets/src/main/java/org/apache/james/jdkim/mailets/ConvertTo7Bit.java (added) +++ james/jdkim/trunk/mailets/src/main/java/org/apache/james/jdkim/mailets/ConvertTo7Bit.java Tue Oct 6 17:56:14 2009 @@ -0,0 +1,75 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.jdkim.mailets; + +import java.io.IOException; + +import javax.mail.MessagingException; +import javax.mail.internet.MimeMessage; +import javax.mail.internet.MimeMultipart; +import javax.mail.internet.MimePart; + +import org.apache.mailet.Mail; +import org.apache.mailet.base.GenericMailet; + +/** + * Make sure the message stream is 7bit. + * Every 8bit part is encoded to quoted-printable or base64 and the message is saved. + */ +public class ConvertTo7Bit extends GenericMailet { + + public void service(Mail mail) throws MessagingException { + MimeMessage message = mail.getMessage(); + try { + convertTo7Bit(message); + message.saveChanges(); + } catch (IOException e) { + throw new MessagingException("IOException converting message to 7bit: "+e.getMessage(), e); + } + } + + /** + * Converts a message to 7 bit. + * + * @param part + */ + private void convertTo7Bit(MimePart part) throws MessagingException, IOException { + if (part.isMimeType("multipart/*")) { + MimeMultipart parts = (MimeMultipart) part.getContent(); + int count = parts.getCount(); + for (int i = 0; i < count; i++) { + convertTo7Bit((MimePart)parts.getBodyPart(i)); + } + } else if ("8bit".equals(part.getEncoding())) { + // The content may already be in encoded the form (likely with mail created from a + // stream). In that case, just changing the encoding to quoted-printable will mangle + // the result when this is transmitted. We must first convert the content into its + // native format, set it back, and only THEN set the transfer encoding to force the + // content to be encoded appropriately. + + // if the part doesn't contain text it will be base64 encoded. + String contentTransferEncoding = part.isMimeType("text/*") ? "quoted-printable" : "base64"; + part.setContent(part.getContent(), part.getContentType()); + part.setHeader("Content-Transfer-Encoding", contentTransferEncoding); + part.addHeader("X-MIME-Autoconverted", "from 8bit to "+contentTransferEncoding+" by "+getMailetContext().getServerInfo()); + } + } + +} Propchange: james/jdkim/trunk/mailets/src/main/java/org/apache/james/jdkim/mailets/ConvertTo7Bit.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: james/jdkim/trunk/mailets/src/main/java/org/apache/james/jdkim/mailets/ConvertTo7Bit.java ------------------------------------------------------------------------------ svn:mime-type = text/plain Added: james/jdkim/trunk/mailets/src/main/java/org/apache/james/jdkim/mailets/DKIMSign.java URL: http://svn.apache.org/viewvc/james/jdkim/trunk/mailets/src/main/java/org/apache/james/jdkim/mailets/DKIMSign.java?rev=822379&view=auto ============================================================================== --- james/jdkim/trunk/mailets/src/main/java/org/apache/james/jdkim/mailets/DKIMSign.java (added) +++ james/jdkim/trunk/mailets/src/main/java/org/apache/james/jdkim/mailets/DKIMSign.java Tue Oct 6 17:56:14 2009 @@ -0,0 +1,160 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.jdkim.mailets; + +import java.io.IOException; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.spec.InvalidKeySpecException; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import javax.mail.Header; +import javax.mail.MessagingException; +import javax.mail.internet.MimeMessage; + +import org.apache.james.jdkim.BodyHashJob; +import org.apache.james.jdkim.DKIMCommon; +import org.apache.james.jdkim.DKIMSigner; +import org.apache.james.jdkim.Headers; +import org.apache.james.jdkim.PermFailException; +import org.apache.james.jdkim.SignatureRecord; +import org.apache.mailet.Mail; +import org.apache.mailet.base.GenericMailet; + +/** + * This mailet sign a message using the DKIM protocol + * + * Sample configuration: + * <pre><code> + * <mailet match="All" class="DKIMSign"> + * <signatureTemplate>v=1; s=selector; d=example.com; h=from:to:received:received; a=rsa-sha256; bh=; b=;</signatureTemplate> + * <privateKey>MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBANgNpgpfPBVjCpZsuGa4nrppMA3zCYNH6t8cTwd+eRI5rHSgihMznOq5mtMujfTzvRgx9jPHB8HqP83PdB3CtQP+3RgxgmJQrJYmcIp9lcckEn7J9Eevuhb5RbdxWj0IbZsF8jGwifBh7XvmD1SPKe0mla56p0QijVzZuG/0ynrpAgMBAAECgYEAjxdzCdmLRKrk3z3AX6AU2GdEQWjeuwkNoJjyKod0DkMOWevdptv/KGKnDQj/UeWALp8gbah7Fc5cVaX5RKCpG3WRO32NeFUUTGDyY2SjZR6UDAW2yXwJGNVxhA5x514f9Yz+ZeODbBSqpl6cGaUqUPq81vvSMUl5VoMn/ufuPwECQQD02QfYPhmCP8g4BVhxxlgfvj5WA7R7tWRSNCT3C0naPpwaono9+PSuhUgxRbOgFvxh8StHyXomdVBt/LzeAl6JAkEA4eTejDsmMCfxe47JnHbgpxNphYpSQBB9FZgMUU5hAXgpX3EtIS3JxjSSOx3EYoO51ZywBOWUXNcMJAXoNM0hYQJAQDnZ4/BOMqtWctN8IsQbg6Acq+Vm53hqa2HAPIlagwQfYKE0HaN7U3gkusAE4T6GT466gqcoAoSNZ3x/cmD+uQJAePyZCaiAephaKSA/8VJmXnXyNXjxNqjeJduq9T0yjZPrLNg0IKoigMsVax41WcJNnRBv4h+IR/VR5lVXmjgn4QJANq02dLdX2phQqOP+Ss1EP9TT7t6HxLbKUuoPdGVKf0q1gZEyAC1Re2I4SLMEfpt3+ivMj1X2zDzIHP5mogfblA==</privateKey> + * </mailet> + * </code></pre> + * + * @version CVS $Revision: 713949 $ $Date: 2008-11-14 08:40:21 +0100 (ven, 14 nov 2008) $ + * @since 2.2.0 + */ +public class DKIMSign extends GenericMailet { + + /** + * An adapter to let DKIMSigner read headers from MimeMessage + */ + private final class MimeMessageHeaders implements Headers { + + private Map/* String, String */ headers; + private List/* String */ fields; + + public MimeMessageHeaders(MimeMessage message) throws MessagingException { + headers = new HashMap(); + fields = new LinkedList(); + for (Enumeration e = message.getAllHeaderLines(); e.hasMoreElements(); ) { + String head = (String) e.nextElement(); + int p = head.indexOf(':'); + if (p <= 0) throw new MessagingException("Bad header line: "+head); + String headerName = head.substring(0, p).trim(); + String headerNameLC = headerName.toLowerCase(); + fields.add(headerName); + List/* String */ strings = (List) headers.get(headerNameLC); + if (strings == null) { + strings = new LinkedList(); + headers.put(headerNameLC, strings); + } + strings.add(head); + } + } + + public List getFields() { + return fields; + } + + public List getFields(String name) { + return (List) headers.get(name); + } + } + + private String signatureTemplate; + private PrivateKey privateKey; + + public void init() throws MessagingException { + signatureTemplate = getInitParameter("signatureTemplate"); + String privateKeyString = getInitParameter("privateKey"); + try { + privateKey = DKIMSigner.getPrivateKey(privateKeyString); + } catch (NoSuchAlgorithmException e) { + throw new MessagingException("Unknown private key algorythm: "+e.getMessage(), e); + } catch (InvalidKeySpecException e) { + throw new MessagingException("PrivateKey should be in base64 encoded PKCS8 (der) format: "+e.getMessage(), e); + } + } + + public void service(Mail mail) throws MessagingException { + DKIMSigner signer = new DKIMSigner(signatureTemplate, privateKey); + SignatureRecord signRecord = signer.newSignatureRecord(signatureTemplate); + try { + BodyHashJob bhj = DKIMCommon.prepareBodyHashJob(signRecord, signatureTemplate); + MimeMessage message = mail.getMessage(); + Headers headers = new MimeMessageHeaders(message); + try { + message.writeTo(new HeaderSkippingOutputStream(bhj.getOutputStream())); + bhj.getOutputStream().close(); + } catch (IOException e) { + throw new MessagingException("Exception calculating bodyhash: "+e.getMessage(), e); + } + String signatureHeader = signer.sign(headers, bhj); + // Unfortunately JavaMail does not give us a method to add headers on top. + // message.addHeaderLine(signatureHeader); + prependHeader(message, signatureHeader); + } catch (NoSuchAlgorithmException e) { + throw new MessagingException("Unknown algorythm: "+e.getMessage(), e); + } catch (PermFailException e) { + throw new MessagingException("PermFail while signing: "+e.getMessage(), e); + } + + } + + private void prependHeader(MimeMessage message, String signatureHeader) + throws MessagingException { + List prevHeader = new LinkedList(); + // read all the headers + for (Enumeration e = message.getAllHeaderLines(); e.hasMoreElements(); ) { + String headerLine = (String) e.nextElement(); + prevHeader.add(headerLine); + } + // remove all the headers + for (Enumeration e = message.getAllHeaders(); e.hasMoreElements(); ) { + Header header = (Header) e.nextElement(); + message.removeHeader(header.getName()); + } + // add our header + message.addHeaderLine(signatureHeader); + // add the remaining headers using "addHeaderLine" that won't alter the insertion order. + for (Iterator i = prevHeader.iterator(); i.hasNext(); ) { + String header = (String) i.next(); + message.addHeaderLine(header); + } + } + +} Propchange: james/jdkim/trunk/mailets/src/main/java/org/apache/james/jdkim/mailets/DKIMSign.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: james/jdkim/trunk/mailets/src/main/java/org/apache/james/jdkim/mailets/DKIMSign.java ------------------------------------------------------------------------------ svn:mime-type = text/plain Added: james/jdkim/trunk/mailets/src/main/java/org/apache/james/jdkim/mailets/HeaderSkippingOutputStream.java URL: http://svn.apache.org/viewvc/james/jdkim/trunk/mailets/src/main/java/org/apache/james/jdkim/mailets/HeaderSkippingOutputStream.java?rev=822379&view=auto ============================================================================== --- james/jdkim/trunk/mailets/src/main/java/org/apache/james/jdkim/mailets/HeaderSkippingOutputStream.java (added) +++ james/jdkim/trunk/mailets/src/main/java/org/apache/james/jdkim/mailets/HeaderSkippingOutputStream.java Tue Oct 6 17:56:14 2009 @@ -0,0 +1,69 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.jdkim.mailets; + +import java.io.FilterOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +/** + * Ignore writes until the given sequence is found. + */ +public class HeaderSkippingOutputStream extends FilterOutputStream { + + boolean inHeaders = true; + byte[] skipTo = "\r\n\r\n".getBytes(); + int pos = 0; + + public HeaderSkippingOutputStream(OutputStream out) { + super(out); + } + + public void write(byte[] b, int off, int len) throws IOException { + if (inHeaders) { + for (int i = off; i < off+len; i++) { + if (b[i] == skipTo[pos]) { + pos++; + if (pos == skipTo.length) { + inHeaders = false; + if (len-i-1 > 0) out.write(b, i+1, len-i-1); + break; + } + } else pos = 0; + } + } else { + out.write(b, off, len); + } + } + + public void write(int b) throws IOException { + if (inHeaders) { + if (skipTo[pos] == b) { + pos++; + if (pos == skipTo.length) inHeaders = false; + } else pos = 0; + } else { + out.write(b); + } + } + + + +} Propchange: james/jdkim/trunk/mailets/src/main/java/org/apache/james/jdkim/mailets/HeaderSkippingOutputStream.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: james/jdkim/trunk/mailets/src/main/java/org/apache/james/jdkim/mailets/HeaderSkippingOutputStream.java ------------------------------------------------------------------------------ svn:mime-type = text/plain --------------------------------------------------------------------- To unsubscribe, e-mail: server-dev-unsubscr...@james.apache.org For additional commands, e-mail: server-dev-h...@james.apache.org