//$Header: /cvsroot-fuse/mec-as2/39/mendelson/comm/as2/message/AS2MDNCreation.java,v 1.1 2012/04/18 14:10:30 heller Exp $
package de.mendelson.comm.as2.message;
import com.sun.mail.util.LineOutputStream;
import de.mendelson.comm.as2.AS2Exception;
import de.mendelson.comm.as2.AS2ServerVersion;
import de.mendelson.util.security.cert.CertificateManager;
import de.mendelson.comm.as2.partner.Partner;
import de.mendelson.util.MecResourceBundle;
import de.mendelson.util.security.BCCryptoHelper;
import java.io.ByteArrayOutputStream;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.security.PrivateKey;
import java.security.cert.Certificate;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Enumeration;
import java.util.MissingResourceException;
import java.util.Properties;
import java.util.ResourceBundle;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.activation.DataHandler;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;
import javax.mail.internet.MimeUtility;
import javax.mail.util.ByteArrayDataSource;
/*
* Copyright (C) mendelson-e-commerce GmbH Berlin Germany
*
* This software is subject to the license agreement set forth in the license.
* Please read and agree to all terms before using this software.
* Other product and brand names are trademarks of their respective owners.
*/
/**
* Packs a message with all necessary headers and attachments
* @author S.Heller
* @version $Revision: 1.1 $
*/
public class AS2MDNCreation {
private Logger logger = null;
private MecResourceBundle rb = null;
private CertificateManager certificateManager = null;
public AS2MDNCreation(CertificateManager certificateManager) {
//Load resourcebundle
try {
this.rb = (MecResourceBundle) ResourceBundle.getBundle(
ResourceBundleAS2MessagePacker.class.getName());
} //load up resourcebundle
catch (MissingResourceException e) {
throw new RuntimeException("Oops..resource bundle " + e.getClassName() + " not found.");
}
this.certificateManager = certificateManager;
}
/**Build the header for the sync response and returns them
*/
public Properties buildHeaderForSyncMDN(AS2Message message) {
String ediintFeatures = "multiple-attachments, CEM";
AS2MDNInfo info = (AS2MDNInfo) message.getAS2Info();
Properties header = new Properties();
header.setProperty("server", AS2ServerVersion.getUserAgent());
header.setProperty("as2-version", "1.2");
header.setProperty("ediint-features", ediintFeatures);
header.setProperty("mime-version", "1.0");
header.setProperty("message-id", "<" + info.getMessageId() + ">");
DateFormat headerFormat = new SimpleDateFormat("EE, dd MMM yyyy HH:mm:ss zz");
header.setProperty("date", headerFormat.format(new Date()));
header.setProperty("connection", "close");
if (info.getReceiverId() != null) {
header.setProperty("as2-to", AS2Message.escapeFromToHeader(info.getReceiverId()));
}
if (info.getSenderId() != null) {
header.setProperty("as2-from", AS2Message.escapeFromToHeader(info.getSenderId()));
}
header.setProperty("content-type", message.getContentType());
header.setProperty("content-length", String.valueOf(message.getRawDataSize()));
return (header);
}
/**Displays a bundle of byte arrays as hex string, for debug purpose only*/
private String toHexDisplay(byte[] data) {
StringBuilder result = new StringBuilder();
for (int i = 0; i < data.length; i++) {
result.append(Integer.toString((data[i] & 0xff) + 0x100, 16).substring(1));
result.append(" ");
}
return result.toString();
}
/**Creates an mdn that could be returned to the sender and indicates that
*everything is ok
*/
public AS2Message createMDNProcessed(AS2MessageInfo releatedMessageInfo, Partner mdnSender, String mdnReceiverAS2Id) throws Exception {
return (this.createMDNProcessed(releatedMessageInfo, mdnSender, mdnReceiverAS2Id, MDNText.get(MDNText.RECEIVED, releatedMessageInfo.getMessageType())));
}
/**Creates an mdn that could be returned to the sender and indicates that
*everything is ok
*/
public AS2Message createMDNProcessed(AS2MessageInfo releatedMessageInfo, Partner mdnSender, String mdnReceiverAS2Id, String detailText) throws Exception {
AS2Message mdn = this.createMDN(releatedMessageInfo, mdnSender, mdnSender.getAS2Identification(), mdnReceiverAS2Id, "processed", detailText);
mdn.getAS2Info().setState(AS2Message.STATE_FINISHED);
return (mdn);
}
/**Creates an mdn that could be returned to the sender and indicates an error
*by processing the message
*/
public AS2Message createMDNError(AS2Exception exception, String as2MessageSenderId, Partner as2MessageReceiver, String as2MessageReceiverId) throws Exception {
AS2MessageInfo info = (AS2MessageInfo) exception.getAS2Message().getAS2Info();
AS2Message mdn = this.createMDN(info, as2MessageReceiver, as2MessageReceiverId,
as2MessageSenderId, "processed/error: " + exception.getErrorType(), MDNText.get(MDNText.ERROR, info.getMessageType()) + exception.getMessage());
if (this.logger != null) {
this.logger.log(Level.SEVERE, this.rb.getResourceString("mdn.details",
new Object[]{
info.getMessageId(),
exception.getMessage()
}), info);
}
mdn.getAS2Info().setState(AS2Message.STATE_STOPPED);
return (mdn);
}
/**Creates a MDN to return. It may be confusing that the sender and the sender id is passed but the sender
* is null if the partner with the sender id has not been found in the db
*@param dispositionState State that will be written into the disposition header
*/
private AS2Message createMDN(AS2MessageInfo relatedMessageInfo, Partner sender,
String senderAS2Id, String receiverAS2Id, String dispositionState,
String additionalText) throws Exception {
AS2Message message = new AS2Message(new AS2MDNInfo());
AS2MDNInfo info = (AS2MDNInfo) message.getAS2Info();
info.setMessageId(UniqueId.createMessageId(senderAS2Id, receiverAS2Id));
info.setSenderId(senderAS2Id);
info.setReceiverId(receiverAS2Id);
info.setRelatedMessageId(relatedMessageInfo.getMessageId());
try {
info.setSenderHost(InetAddress.getLocalHost().getCanonicalHostName());
} catch (UnknownHostException e) {
//nop
}
String contentTransferEncoding = "7bit";
//String contentTransferEncoding = "base64";
MimeMultipart multiPart = new MimeMultipart();
multiPart.addBodyPart(this.createMDNNotesBody(additionalText, contentTransferEncoding));
multiPart.addBodyPart(this.createMDNDispositionBody(relatedMessageInfo, dispositionState, contentTransferEncoding));
multiPart.setSubType("report; report-type=disposition-notification");
MimeMessage messagePart = new MimeMessage(Session.getInstance(System.getProperties(), null));
messagePart.setContent(multiPart, MimeUtility.unfold(multiPart.getContentType()));
messagePart.saveChanges();
ByteArrayOutputStream memOutUnsigned = new ByteArrayOutputStream();
//normally the content type header is folded (which is correct but some products are not able to parse this properly)
//Now take the content-type, unfold it and write it
Enumeration hdrLines = messagePart.getMatchingHeaderLines(new String[]{"Content-Type"});
LineOutputStream los = new LineOutputStream(memOutUnsigned);
while (hdrLines.hasMoreElements()) {
//requires java mail API >= 1.4
String nextHeaderLine = MimeUtility.unfold((String) hdrLines.nextElement());
los.writeln(nextHeaderLine);
}
messagePart.writeTo(memOutUnsigned,
new String[]{"Message-ID", "Mime-Version", "Content-Type"});
memOutUnsigned.flush();
memOutUnsigned.close();
message.setDecryptedRawData(memOutUnsigned.toByteArray());
//check if authentification of sender is ok, then sign if possible
if (sender != null) {
MimeMessage signedMessage = this.signMDN(messagePart, sender, message, relatedMessageInfo);
message.setContentType(MimeUtility.unfold(signedMessage.getContentType()));
ByteArrayOutputStream memOutSigned = new ByteArrayOutputStream();
signedMessage.writeTo(memOutSigned,
new String[]{"Message-ID", "Mime-Version", "Content-Type"});
memOutSigned.flush();
memOutSigned.close();
message.setRawData(memOutSigned.toByteArray());
} //there occured an authentification error: the system was unable to authentificate the sender,
//do not sign MDN
else {
ByteArrayOutputStream memOut = new ByteArrayOutputStream();
messagePart.writeTo(memOut,
new String[]{"Message-ID", "Mime-Version", "Content-Type"});
memOut.flush();
memOut.close();
message.getAS2Info().setSignType(AS2Message.SIGNATURE_NONE);
message.setContentType(MimeUtility.unfold(messagePart.getContentType()));
message.setRawData(memOut.toByteArray());
}
if (dispositionState.indexOf("error") >= 0) {
if (this.logger != null) {
this.logger.log(Level.SEVERE, this.rb.getResourceString("mdn.created",
new Object[]{
info.getMessageId(), dispositionState
}), info);
}
} else {
if (this.logger != null) {
this.logger.log(Level.FINE, this.rb.getResourceString("mdn.created",
new Object[]{
info.getMessageId(), dispositionState
}), info);
}
}
return (message);
}
/**Its necessary to transmit additional notes
*/
private MimeBodyPart createMDNNotesBody(String text, String contentTransferEncoding) throws MessagingException {
MimeBodyPart body = new MimeBodyPart();
body.setDataHandler(new DataHandler(new ByteArrayDataSource(text.getBytes(), "text/plain")));
body.setHeader("Content-Type", "text/plain");
body.setHeader("Content-Transfer-Encoding", contentTransferEncoding);
return (body);
}
/**Creates the MDN body and returns it
*
*/
private MimeBodyPart createMDNDispositionBody(AS2MessageInfo relatedMessageInfo, String dispositionState,
String contentTransferEncoding) throws MessagingException {
MimeBodyPart body = new MimeBodyPart();
StringBuilder buffer = new StringBuilder();
buffer.append("Reporting-UA: ").append(AS2ServerVersion.getProductName()).append("\r\n");
buffer.append("Original-Recipient: rfc822; ").append(relatedMessageInfo.getReceiverId()).append("\r\n");
buffer.append("Final-Recipient: rfc822; ").append(relatedMessageInfo.getReceiverId()).append("\r\n");
buffer.append("Original-Message-ID: <").append(relatedMessageInfo.getMessageId()).append(">\r\n");
buffer.append("Disposition: automatic-action/MDN-sent-automatically; ").append(dispositionState).append("\r\n");
if (relatedMessageInfo.getReceivedContentMIC() != null) {
buffer.append("Received-Content-MIC: ").append(relatedMessageInfo.getReceivedContentMIC()).append("\r\n");
}
body.setDataHandler(new DataHandler(new ByteArrayDataSource(buffer.toString().getBytes(),
"message/disposition-notification")));
body.setHeader("Content-Transfer-Encoding", contentTransferEncoding);
return (body);
}
/**Signs the passed mdn and returns it
*/
private MimeMessage signMDN(MimeMessage mimeMessage, Partner sender, AS2Message as2Message, AS2MessageInfo relatedMessageInfo) throws Exception {
if (relatedMessageInfo.getDispositionNotificationOptions().signMDN()) {
int[] possibleAlgorithm = relatedMessageInfo.getDispositionNotificationOptions().getSignatureAlgorithm();
boolean sha1Possible = false;
boolean md5Possible = false;
for (int i = 0; i < possibleAlgorithm.length; i++) {
if (possibleAlgorithm[i] == AS2Message.SIGNATURE_MD5) {
md5Possible = true;
} else if (possibleAlgorithm[i] == AS2Message.SIGNATURE_SHA1) {
sha1Possible = true;
}
}
String digest = null;
if (sha1Possible) {
digest = "sha1";
as2Message.getAS2Info().setSignType(AS2Message.SIGNATURE_SHA1);
} else if (md5Possible) {
digest = "md5";
as2Message.getAS2Info().setSignType(AS2Message.SIGNATURE_MD5);
}
if (digest == null) {
as2Message.getAS2Info().setSignType(AS2Message.SIGNATURE_NONE);
if (this.logger != null) {
this.logger.log(Level.INFO, this.rb.getResourceString("mdn.notsigned",
new Object[]{
as2Message.getAS2Info().getMessageId(),}), as2Message.getAS2Info());
}
return (mimeMessage);
}
PrivateKey senderKey = this.certificateManager.getPrivateKeyByFingerprintSHA1(sender.getSignFingerprintSHA1());
String senderSignAlias = this.certificateManager.getAliasByFingerprint(sender.getSignFingerprintSHA1());
Certificate[] chain = this.certificateManager.getCertificateChain(senderSignAlias);
BCCryptoHelper helper = new BCCryptoHelper();
MimeMessage signedMimeMessage = helper.signToMessage(mimeMessage, chain, senderKey, digest.toUpperCase());
if (this.logger != null) {
this.logger.log(Level.INFO, this.rb.getResourceString("mdn.signed",
new Object[]{
as2Message.getAS2Info().getMessageId(), digest.toUpperCase()
}), as2Message.getAS2Info());
}
return (signedMimeMessage);
} else {
as2Message.getAS2Info().setSignType(AS2Message.SIGNATURE_NONE);
if (this.logger != null) {
this.logger.log(Level.INFO, this.rb.getResourceString("mdn.notsigned",
new Object[]{
as2Message.getAS2Info().getMessageId(),}), as2Message.getAS2Info());
}
return (mimeMessage);
}
}
/**
* @param logger the logger to set. If no logger is passed to this class there will be no logging
*/
public void setLogger(Logger logger) {
this.logger = logger;
}
}