diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index 14c833a4..d0a56b9f 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -1,27 +1,25 @@ OpenAS2 Server - Version 2.0.0 + Version 2.1.0 RELEASE NOTES -The OpenAS2 project is pleased to announce the release of OpenAS2 2.0.0 +The OpenAS2 project is pleased to announce the release of OpenAS2 2.1.0 -The release download file is: OpenAS2Server-2.0.0.zip +The release download file is: OpenAS2Server-2.1.0.zip The zip file contains a PDF document providing information on installing and using the application. -This release is an enhancement and bug fix release that includes compatibility testing with other AS2 systems: - 1. Add support for custom HTTP headers - - configurable static headers as name/value pairs in the partnership - - configurable dynamic headers with header values set from parsing the name of the file to be sent - 2. Fix generator encoding for compression, encryption and signing - 3. Support configurable control of canonicalization when signing - 4. Support overriding digest "sha-1" algorithm name in signing to use "old" name without dash ("sha1") - 5. Support AES128, AES192, AES256 ciphers - 6. Support disabling the CMS algorithm protection OID for older AS2 systems that do not support it - 7. Added "Troubleshooting.." section to documentation +This release is an enhancement release: + 1. Add message tracking module to allow easy identification of sent and received messages based on state + - persists state change tracking of messages to H2 database + - allow user-plugin of custom tracking module(s) + 2. Enhanced documentation around certificate management + 3. Added properties element to config to allow easy custom config property access from java modules and helper classes + 4. Added support for parsing file name into partnership attributes using regular expressions + 5. Added script to support launching OpenAS2 as a daemon using the init.d paradigm + 6. Added system parameter to startup scripts to allow restricted HTTP headers that cause problems for some AS2 implementations Upgrade Notes: - 1. Canonicalization may affect existing working partnerships in prior versions of OpenAS2 if using a content transfer encoding other than "binary". - If the partnership stops working then add the following attribute to the partnership: - + 1. Add the new module to your existing config.xml (see classname="org.openas2.processor.msgtracking.DbTrackingModule" in release config.xml) + 2. If using a custom startup script, re-integrate your customisations into the new script Java 1.5 or later is required. NOTE FOR JAVA 1.5: Prior to java 1.6, the Javabeans Activation Framework is NOT included in the standard Java install. Download the 1.1.1 version and extract from the zip file from this web page: http://www.oracle.com/technetwork/java/javasebusiness/downloads/java-archive-downloads-java-plat-419418.html#jaf-1.1.1-fcs-oth-JPR diff --git a/Server/bin/openas2.d b/Server/bin/openas2.d new file mode 100755 index 00000000..6d15ca49 --- /dev/null +++ b/Server/bin/openas2.d @@ -0,0 +1,104 @@ +#!/bin/bash + +### BEGIN INIT INFO +# Provides: openas2.d +# Required-Start: +# Required-Stop: +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: Startup script to launch OpenAS2 appliction as a daemon +# Description: This script can be used in any NIX based system that implements the init.d mechanism +# The EXECUTABLE variable below must be set to point to the OpenAS2 startup script +# See the OpenAS2HowTo.pdf for details on configuration checks for this mode of running OpenAS2 +### END INIT INFO + +SERVICE_NAME=OpenAS2 +EXECUTABLE=/opt/OpenAS2/bin/start-openas2.sh +THIS_SCRIPT_NAME=`basename $0` +THIS_SCRIPT_EXEC=$0 +if [ "$THIS_SCRIPT_NAME" = "$0" ]; then + THIS_SCRIPT_EXEC="/etc/init.d/$0" +fi +PID=$(ps -ef | grep java | grep org.openas2.app.OpenAS2Server | awk '{print $2}') + +case "$1" in + start) + echo "Starting $SERVICE_NAME ..." + if [ -z $PID ]; then + export OPENAS2_AS_DAEMON=true + $EXECUTABLE + RETVAL="$?" + if [ "$RETVAL" = 0 ]; then + echo "$SERVICE_NAME started ..." + exit 0 + else + echo "ERROR $SERVICE_NAME could not be started. Review logs" + exit 1 + fi + else + echo "$SERVICE_NAME is already running ..." + fi + ;; + stop|kill) + if [ ! -z $PID ]; then + echo "Attempting to stop $SERVICE_NAME..." + kill $PID + if [ "$?" = 0 ]; then + echo "$SERVICE_NAME terminated ..." + exit 0 + else + echo "ERROR: $SERVICE_NAME failed to terminate. try force-stop" + exit 1 + fi + else + echo "$SERVICE_NAME is not running ..." + exit 0 + fi + ;; + force-stop) + if [ ! -z $PID ]; then + echo "Attempting to force termination of $SERVICE_NAME..." + kill -9 $PID + if [ "$?" = 0 ]; then + echo "$SERVICE_NAME terminated ..." + exit 0 + else + echo "ERROR: $SERVICE_NAME failed to terminate. " + exit 1 + fi + else + echo "$SERVICE_NAME is not running ..." + exit 0 + fi + ;; + status) + if [ -z $PID ]; then + echo "$SERVICE_NAME is not running" + else + echo "$SERVICE_NAME is running" + fi + ;; + force-reload|restart|reload) + $THIS_SCRIPT_EXEC stop + if [ ! -z $PID ]; then + CNT=0 + while ps -p $PID 2>/dev/null; do + sleep 1;CNT=$CNT+1; + CNT=$((CNT+1)); if [ $CNT -ge 5 ]; then break; fi + done + if ps -p $PID 2>/dev/null; then + echo "ERROR: Failed to stop $SERVICE_NAME" + exit 1 + else + PID="" + fi + fi + $THIS_SCRIPT_EXEC start + ;; + *) + echo "Usage: $0 {start|stop|restart|reload|force-reload}" + exit 1 + ;; + +esac + diff --git a/Server/bin/start-openas2.bat b/Server/bin/start-openas2.bat index 2836d5c9..cf1da9a2 100755 --- a/Server/bin/start-openas2.bat +++ b/Server/bin/start-openas2.bat @@ -1,6 +1,13 @@ @echo off rem Purpose: runs the OpenAS2 application +rem Set some of the base system properties for the Java environment and logging +rem remove -Dorg.apache.commons.logging.Log=org.openas2.logging.Log if using another logging package +rem +set EXTRA_PARMS=-Xms32m -Xmx384m -Dorg.apache.commons.logging.Log=org.openas2.logging.Log +rem By default allow restricted HTTP headers +set EXTRA_PARMS=%EXTRA_PARMS% -Dsun.net.http.allowRestrictedHeaders=true + rem Uncomment any of the following for enhanced debug rem set EXTRA_PARMS=%EXTRA_PARMS% -Dmaillogger.debug.enabled=true rem set EXTRA_PARMS=%EXTRA_PARMS% -DlogRxdMsgMimeBodyParts=true @@ -54,11 +61,9 @@ if not "%JAVA%" == "" goto :Check_JAVA_END ) set JAVA=%JAVA_HOME%\bin\java :Check_JAVA_END - +set LIB_JARS=../lib/h2-1.4.192.jar;../lib/javax.mail.jar;../lib/bcpkix-jdk15on-154.jar;../lib/bcprov-jdk15on-154.jar;../lib/bcmail-jdk15on-154.jar;../lib/commons-logging-1.2.jar;../lib/openas2-server.jar rem -rem remove -Dorg.apache.commons.logging.Log=org.openas2.logging.Log if using another logging package -rem -"%JAVA%" "%EXTRA_PARMS% -Xms32m -Xmx384m -Dorg.apache.commons.logging.Log=org.openas2.logging.Log -cp .;../lib/javax.mail.jar;../lib/bcpkix-jdk15on-154.jar;../lib/bcprov-jdk15on-154.jar;../lib/bcmail-jdk15on-154.jar;../lib/commons-logging-1.2.jar;../lib/openas2-server.jar org.openas2.app.OpenAS2Server ../config/config.xml +"%JAVA%" %EXTRA_PARMS% -cp .;%LIB_JARS% org.openas2.app.OpenAS2Server ../config/config.xml :warn :END diff --git a/Server/bin/start-openas2.sh b/Server/bin/start-openas2.sh index d42ef0ca..e6aa82a3 100755 --- a/Server/bin/start-openas2.sh +++ b/Server/bin/start-openas2.sh @@ -1,13 +1,22 @@ -#!/bin/sh +#!/bin/bash # purpose: runs the OpenAS2 application x=`basename $0` +binDir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" keyStorePwd=$1 PWD_OVERRIDE="" + +# Set some of the base system properties for the Java environment and logging +# remove -Dorg.apache.commons.logging.Log=org.openas2.logging.Log if using another logging package +# +EXTRA_PARMS="-Xms32m -Xmx384m -Dorg.apache.commons.logging.Log=org.openas2.logging.Log" +# By default allow restricted HTTP headers +EXTRA_PARMS="$EXTRA_PARMS -Dsun.net.http.allowRestrictedHeaders=true" # Uncomment any of the following for enhanced debug #EXTRA_PARMS="$EXTRA_PARMS -Dmaillogger.debug.enabled=true" #EXTRA_PARMS="$EXTRA_PARMS -DlogRxdMsgMimeBodyParts=true" #EXTRA_PARMS="$EXTRA_PARMS -DlogRxdMdnMimeBodyParts=true" +#EXTRA_PARMS="$EXTRA_PARMS -Djavax.net.debug=SSL" if [ ! -z $keyStorePwd ]; then PWD_OVERRIDE="-Dorg.openas2.cert.Password=$keyStorePwd" @@ -29,10 +38,16 @@ fi if [ -z $JAVA_HOME ]; then echo "ERROR: Cannot find JAVA_HOME" - exit + exit 1 fi + +LIB_JARS="${binDir}/../lib/h2-1.4.192.jar:${binDir}/../lib/javax.mail.jar:${binDir}/../lib/bcpkix-jdk15on-154.jar:${binDir}/../lib/bcprov-jdk15on-154.jar:${binDir}/../lib/bcmail-jdk15on-154.jar:${binDir}/../lib/commons-logging-1.2.jar:${binDir}/../lib/openas2-server.jar" JAVA_EXE=$JAVA_HOME/bin/java # -# remove -Dorg.apache.commons.logging.Log=org.openas2.logging.Log if using another logging package -# -$JAVA_EXE ${PWD_OVERRIDE} -Xms32m -Xmx384m -Dorg.apache.commons.logging.Log=org.openas2.logging.Log -cp .:../lib/javax.mail.jar:../lib/bcpkix-jdk15on-154.jar:../lib/bcprov-jdk15on-154.jar:../lib/bcmail-jdk15on-154.jar:../lib/commons-logging-1.2.jar:../lib/openas2-server.jar org.openas2.app.OpenAS2Server ../config/config.xml +CMD="$JAVA_EXE ${PWD_OVERRIDE} ${EXTRA_PARMS} -cp .:${LIB_JARS} org.openas2.app.OpenAS2Server ${binDir}/../config/config.xml" +if [ "true" = "$OPENAS2_AS_DAEMON" ]; then + $CMD & +else + $CMD +fi +exit $? \ No newline at end of file diff --git a/Server/config/DB/openas2.mv.db b/Server/config/DB/openas2.mv.db new file mode 100644 index 00000000..f7fe6aae Binary files /dev/null and b/Server/config/DB/openas2.mv.db differ diff --git a/Server/config/config.xml b/Server/config/config.xml index b17653b4..8be660c1 100644 --- a/Server/config/config.xml +++ b/Server/config/config.xml @@ -1,4 +1,8 @@ + - + + + @@ -56,6 +57,10 @@ Example for adding dynamic custom headers to Mime body part where filename is of form XXX-YYY.msg + + Example for parsing filename into parameters that can be referenced this is a file name of the form XXXNNNN.edi where X is alphabetic and N are numerics + + --> diff --git a/Server/dist/OpenAS2Server-2.0.0.zip b/Server/dist/OpenAS2Server-2.1.0.zip similarity index 70% rename from Server/dist/OpenAS2Server-2.0.0.zip rename to Server/dist/OpenAS2Server-2.1.0.zip index 6237165f..183a4852 100644 Binary files a/Server/dist/OpenAS2Server-2.0.0.zip and b/Server/dist/OpenAS2Server-2.1.0.zip differ diff --git a/Server/lib/h2-1.4.192.jar b/Server/lib/h2-1.4.192.jar new file mode 100755 index 00000000..8936686e Binary files /dev/null and b/Server/lib/h2-1.4.192.jar differ diff --git a/Server/lib/openas2-server.jar b/Server/lib/openas2-server.jar index b8c011c7..149df46c 100644 Binary files a/Server/lib/openas2-server.jar and b/Server/lib/openas2-server.jar differ diff --git a/Server/src/org/openas2/BaseSession.java b/Server/src/org/openas2/BaseSession.java index e014f2dd..65a4fbde 100644 --- a/Server/src/org/openas2/BaseSession.java +++ b/Server/src/org/openas2/BaseSession.java @@ -13,6 +13,7 @@ public abstract class BaseSession implements Session { private Map components; + private String baseDirectory; /** * Creates a BaseSession object, then calls the init() method. @@ -83,4 +84,13 @@ protected void initJavaMail() throws OpenAS2Exception { "message/disposition-notification;; x-java-content-handler=org.openas2.util.DispositionDataContentHandler"); CommandMap.setDefaultCommandMap(mc); } + + public String getBaseDirectory() { + return baseDirectory; + } + + public void setBaseDirectory(String dir) { + baseDirectory = dir; + } + } diff --git a/Server/src/org/openas2/Session.java b/Server/src/org/openas2/Session.java index 7d358b3b..6467b7c5 100644 --- a/Server/src/org/openas2/Session.java +++ b/Server/src/org/openas2/Session.java @@ -20,7 +20,7 @@ */ public interface Session { /** Official OpenAS2 release version */ - public static final String VERSION = "2.0.0"; + public static final String VERSION = "2.1.0"; /** Official OpenAS2 title */ public static final String TITLE = "OpenAS2 v" + VERSION; @@ -91,4 +91,9 @@ public interface Session { * @see Component */ public Processor getProcessor() throws ComponentNotFoundException; + + public String getBaseDirectory(); + + public void setBaseDirectory(String dir); + } diff --git a/Server/src/org/openas2/XMLSession.java b/Server/src/org/openas2/XMLSession.java index 30f581de..06d0dd18 100644 --- a/Server/src/org/openas2/XMLSession.java +++ b/Server/src/org/openas2/XMLSession.java @@ -4,6 +4,7 @@ import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; +import java.util.Map; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; @@ -19,6 +20,7 @@ import org.openas2.partner.PartnershipFactory; import org.openas2.processor.Processor; import org.openas2.processor.ProcessorModule; +import org.openas2.util.Properties; import org.openas2.util.XMLUtil; import org.w3c.dom.Document; import org.w3c.dom.Element; @@ -34,6 +36,7 @@ * */ public class XMLSession extends BaseSession implements CommandRegistryFactory { + public static final String EL_PROPERTIES = "properties"; public static final String EL_CERTIFICATES = "certificates"; public static final String EL_CMDPROCESSOR = "commandProcessors"; public static final String EL_PROCESSOR = "processor"; @@ -42,7 +45,6 @@ public class XMLSession extends BaseSession implements CommandRegistryFactory { public static final String EL_LOGGERS = "loggers"; public static final String PARAM_BASE_DIRECTORY = "basedir"; private CommandRegistry commandRegistry; - private String baseDirectory; private CommandManager cmdManager; @@ -90,7 +92,9 @@ protected void load(InputStream in) throws ParserConfigurationException, nodeName = rootNode.getNodeName(); - if (nodeName.equals(EL_CERTIFICATES)) { + if (nodeName.equals(EL_PROPERTIES)) { + loadProperties(rootNode); + } else if (nodeName.equals(EL_CERTIFICATES)) { loadCertificates(rootNode); } else if (nodeName.equals(EL_PROCESSOR)) { loadProcessor(rootNode); @@ -111,6 +115,12 @@ protected void load(InputStream in) throws ParserConfigurationException, } } } + + protected void loadProperties(Node propNode) + { + Map properties = XMLUtil.mapAttributes(propNode); + Properties.setProperties(properties); + } protected void loadCertificates(Node rootNode) throws OpenAS2Exception { CertificateFactory certFx = (CertificateFactory) XMLUtil.getComponent( @@ -206,12 +216,4 @@ protected void loadProcessorModule(Processor proc, Node moduleNode) proc.getModules().add(procmod); } - public String getBaseDirectory() { - return baseDirectory; - } - - public void setBaseDirectory(String dir) { - baseDirectory = dir; - } - } diff --git a/Server/src/org/openas2/app/OpenAS2Server.java b/Server/src/org/openas2/app/OpenAS2Server.java index 9740f109..a9ba151e 100644 --- a/Server/src/org/openas2/app/OpenAS2Server.java +++ b/Server/src/org/openas2/app/OpenAS2Server.java @@ -23,6 +23,8 @@ */ public class OpenAS2Server { protected BufferedWriter sysOut; + BaseCommandProcessor cmd = null; + XMLSession session = null; public static void main(String[] args) { @@ -31,10 +33,17 @@ public static void main(String[] args) { } public void start(String[] args) { - BaseCommandProcessor cmd = null; - XMLSession session = null; int exitStatus = 0; + Runtime.getRuntime().addShutdownHook(new Thread() + { + @Override + public void run() + { + shutdown(); + System.out.println("Shutdown due to process interruption!"); + } + }); try { Log logger = LogFactory.getLog(OpenAS2Server.class.getSimpleName()); @@ -94,29 +103,34 @@ public void start(String[] args) { exitStatus = -1; err.printStackTrace(); } finally { + shutdown(); + System.exit(exitStatus); + } + } - if (session != null) { - try { - session.getProcessor().stopActiveModules(); - } catch (OpenAS2Exception same) { - same.terminate(); - } + public void shutdown() + { + if (session != null) { + try { + session.getProcessor().stopActiveModules(); + } catch (OpenAS2Exception same) { + same.terminate(); } + } - if (cmd != null) { - try { - cmd.deInit(); - } catch (OpenAS2Exception cdie) { - cdie.terminate(); - } + if (cmd != null) { + try { + cmd.deInit(); + } catch (OpenAS2Exception cdie) { + cdie.terminate(); } + } - write("OpenAS2 has shut down\r\n"); + write("OpenAS2 has shut down\r\n"); + - System.exit(exitStatus); - } } - + public void write(String msg) { if (sysOut == null) { sysOut = new BufferedWriter(new OutputStreamWriter(System.out)); diff --git a/Server/src/org/openas2/cert/PKCS12CertificateFactory.java b/Server/src/org/openas2/cert/PKCS12CertificateFactory.java index ffc38294..480db371 100644 --- a/Server/src/org/openas2/cert/PKCS12CertificateFactory.java +++ b/Server/src/org/openas2/cert/PKCS12CertificateFactory.java @@ -171,13 +171,13 @@ public PrivateKey getPrivateKey(X509Certificate cert) throws OpenAS2Exception { alias = ks.getCertificateAlias(cert); if (alias == null) { - throw new KeyNotFoundException(cert, null); + throw new KeyNotFoundException(cert, "-- alias null from getCertificateAlias(cert) call"); } PrivateKey key = (PrivateKey) ks.getKey(alias, getPassword()); if (key == null) { - throw new KeyNotFoundException(cert, null); + throw new KeyNotFoundException(cert, "-- key null from getKey(" + alias + ") call"); } return key; @@ -219,6 +219,16 @@ public void addPrivateKey(String alias, Key key, String password) throws OpenAS2 } Certificate[] certChain = ks.getCertificateChain(alias); + if (certChain == null) + { + X509Certificate x509cert = (X509Certificate)ks.getCertificate(alias); + if (x509cert.getSubjectDN().equals(x509cert.getIssuerDN())) + { + // Trust chain is to itself + certChain = new X509Certificate[] { x509cert, x509cert }; + if (logger.isInfoEnabled()) logger.info("Detected self-signed certificate and allowed import. Alias: " + alias); + } + } ks.setKeyEntry(alias, key, password.toCharArray(), certChain); save(getFilename(), getPassword()); diff --git a/Server/src/org/openas2/database/H2DBHandler.java b/Server/src/org/openas2/database/H2DBHandler.java new file mode 100644 index 00000000..0864b4c8 --- /dev/null +++ b/Server/src/org/openas2/database/H2DBHandler.java @@ -0,0 +1,112 @@ +package org.openas2.database; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.concurrent.TimeUnit; + +import org.h2.jdbcx.JdbcConnectionPool; +import org.openas2.OpenAS2Exception; + +public class H2DBHandler implements IDBHandler +{ + JdbcConnectionPool cp = null; + + private String jdbcDriver = "org.h2.Driver"; + + private String connectString = "jdbc:h2:file:DB/openas2"; + + public H2DBHandler() + { + } + + /** + * @param jdbcDriver + */ + public H2DBHandler(String jdbcDriver) + { + setJdbcDriver(jdbcDriver); + } + + /** + * @param jdbcDriver + */ + public void setJdbcDriver(String jdbcDriver) + { + this.jdbcDriver = jdbcDriver; + } + + public void createConnectionPool(String connectString, String userName, String pwd) throws OpenAS2Exception + { + // Check that a connection pool is not already running + if (cp != null) + { + throw new OpenAS2Exception( + "Connection pool already initialized. Cannot create a new connection pool. Stop current one first. DB connect string:" + + connectString + " :: Active pool connect string: " + this.connectString); + } + this.connectString = connectString; + // Load the Database Engine JDBC driver + // Class.forName(jdbcDriver); + + cp = JdbcConnectionPool.create(connectString, userName, pwd); + } + + public void destroyConnectionPool() + { + if (cp == null) + return; + cp.dispose(); + cp = null; + } + + public Connection getConnection() throws SQLException, OpenAS2Exception + { + // Check that a connection pool is running + if (cp == null) + { + throw new OpenAS2Exception("Connection pool not initialized."); + } + return cp.getConnection(); + } + + public Connection connect(String connectString, String userName, String password) throws Exception + { + + // Load the Database Engine JDBC driver + Class.forName(jdbcDriver); + try + { + + return DriverManager.getConnection(connectString, userName, password); + } catch (SQLException e) + { + throw new Exception("Failed to obtain connection to database: " + connectString, e); + } + } + + public boolean shutdown(String connectString) throws SQLException, OpenAS2Exception + { + // Wait briefly if there are active connections + int waitCount = 0; + try + { + while (cp.getActiveConnections() > 0 && waitCount < 10) + { + TimeUnit.MILLISECONDS.sleep(100); + waitCount++; + } + } catch (InterruptedException e) + { + // Do nothing + } + Connection c = getConnection(); + Statement st = c.createStatement(); + + boolean result = st.execute("SHUTDOWN"); + c.close(); + return result; + } + +} diff --git a/Server/src/org/openas2/database/IDBHandler.java b/Server/src/org/openas2/database/IDBHandler.java new file mode 100644 index 00000000..a12d52b4 --- /dev/null +++ b/Server/src/org/openas2/database/IDBHandler.java @@ -0,0 +1,21 @@ +package org.openas2.database; + +import java.sql.Connection; +import java.sql.SQLException; + +import org.openas2.OpenAS2Exception; + +public interface IDBHandler +{ + public void setJdbcDriver(String jdbcDriver); + + public void createConnectionPool(String connectString, String userName, String pwd) throws OpenAS2Exception; + + public void destroyConnectionPool(); + + public Connection getConnection() throws SQLException, OpenAS2Exception; + + public Connection connect(String connectString, String userName, String password) throws Exception; + + public boolean shutdown(String connectString) throws SQLException, OpenAS2Exception; +} diff --git a/Server/src/org/openas2/lib/helper/BCCryptoHelper.java b/Server/src/org/openas2/lib/helper/BCCryptoHelper.java index 5d94f303..29b959d8 100644 --- a/Server/src/org/openas2/lib/helper/BCCryptoHelper.java +++ b/Server/src/org/openas2/lib/helper/BCCryptoHelper.java @@ -428,10 +428,10 @@ public MimeBodyPart verifySignature(MimeBodyPart part, Certificate cert) } if (signer.verify(signerInfoVerifier)) { - logSignerInfo("Verified signature for signer info", signer, part); + logSignerInfo("Verified signature for signer info", signer, part, x509Cert); return signedPart.getContent(); } - logSignerInfo("Failed to verify signature for signer info", signer, part); + logSignerInfo("Failed to verify signature for signer info", signer, part, x509Cert); } throw new SignatureException("Signature Verification failed"); } @@ -749,7 +749,7 @@ public String printHeaders(Enumeration
hdrs) return(headers); } - public void logSignerInfo(String msgPrefix, SignerInformation signer, MimeBodyPart part) + public void logSignerInfo(String msgPrefix, SignerInformation signer, MimeBodyPart part, X509Certificate cert) { if (logger.isDebugEnabled()) { @@ -764,6 +764,7 @@ public void logSignerInfo(String msgPrefix, SignerInformation signer, MimeBodyPa + "\n Signature: " + signer.getSignature() + "\n Unsigned attribs: " + signer.getUnsignedAttributes() + "\n Content-transfer-encoding: " + part.getEncoding() + + "\n Certificate: " + cert ); } catch (Throwable e) { diff --git a/Server/src/org/openas2/logging/EmailLogger.java b/Server/src/org/openas2/logging/EmailLogger.java index 0e5991ca..aaf7c535 100644 --- a/Server/src/org/openas2/logging/EmailLogger.java +++ b/Server/src/org/openas2/logging/EmailLogger.java @@ -137,8 +137,14 @@ protected String getShowDefaults() { return VALUE_SHOW_TERMINATED; } - protected String getSubject(Level level, String msg) { - StringBuffer subject = new StringBuffer("OpenAS2 Log (" + level.getName() + "): " + msg.substring(0, msg.indexOf("\n"))); + protected String getSubject(Level level, String msg) throws OpenAS2Exception { + if (msg == null) + throw new OpenAS2Exception("Message string to extract subject is null"); + String subj = ""; + int newlineNdx = msg.indexOf("\n"); + if (newlineNdx >= 1) + subj = msg.substring(0, newlineNdx); + StringBuffer subject = new StringBuffer("OpenAS2 Log (" + level.getName() + "): " + subj); return subject.toString(); } diff --git a/Server/src/org/openas2/message/AS2MessageMDN.java b/Server/src/org/openas2/message/AS2MessageMDN.java index 6860c71f..0063491b 100644 --- a/Server/src/org/openas2/message/AS2MessageMDN.java +++ b/Server/src/org/openas2/message/AS2MessageMDN.java @@ -20,8 +20,9 @@ public class AS2MessageMDN extends BaseMessageMDN { public static final String MDNA_DISPOSITION = "DISPOSITION"; public static final String MDNA_MIC = "MIC"; - public AS2MessageMDN(AS2Message msg) { + public AS2MessageMDN(AS2Message msg, boolean copyMsgHeaders) { super(msg); + if (copyMsgHeaders) copyHeaders(msg.getHeaders()); setHeader("AS2-To", msg.getHeader("AS2-From")); setHeader("AS2-From", msg.getHeader("AS2-To")); } diff --git a/Server/src/org/openas2/message/BaseMessage.java b/Server/src/org/openas2/message/BaseMessage.java index 6453f91f..627f5b92 100644 --- a/Server/src/org/openas2/message/BaseMessage.java +++ b/Server/src/org/openas2/message/BaseMessage.java @@ -1,6 +1,7 @@ package org.openas2.message; import java.io.ByteArrayOutputStream; +import java.io.File; import java.io.IOException; import java.util.Enumeration; import java.util.HashMap; @@ -8,14 +9,19 @@ import javax.mail.Header; import javax.mail.MessagingException; +import javax.mail.internet.ContentDisposition; import javax.mail.internet.InternetHeaders; import javax.mail.internet.MimeBodyPart; +import javax.mail.internet.ParseException; +import org.apache.commons.logging.LogFactory; import org.openas2.OpenAS2Exception; +import org.openas2.Session; import org.openas2.WrappedException; import org.openas2.lib.helper.ICryptoHelper; import org.openas2.params.InvalidParameterException; import org.openas2.partner.Partnership; +import org.openas2.processor.msgtracking.TrackingModule; public abstract class BaseMessage implements Message { @@ -37,6 +43,7 @@ public abstract class BaseMessage implements Message { private String logMsg = null; private String status = MSG_STATUS_MSG_INIT; private Map customOuterMimeHeaders = new HashMap(); + private String payloadFilename = null; public BaseMessage() { @@ -400,4 +407,65 @@ public void setCalculatedMIC(String calculatedMIC) this.calculatedMIC = calculatedMIC; } + public String getPayloadFilename() + { + return payloadFilename; + } + + public void setPayloadFilename(String filename) + { + payloadFilename = filename; + } + + public void trackMsgState(Session session) + { + // Log a start sending fail state but do not allow exceptions to stop the process + try + { + options.put("OPTIONAL_MODULE", "true"); + session.getProcessor().handle(TrackingModule.DO_TRACK_MSG, this, options); + } catch (Exception et) + { + setLogMsg("Unable to persist message tracking state: " + org.openas2.logging.Log.getExceptionMsg(et)); + LogFactory.getLog(BaseMessage.class.getSimpleName()).error(this, et); + } + + } + + public String extractPayloadFilename() throws ParseException + { + String s = getContentDisposition(); + if (s == null || s.length() < 1) + return null; + // TODO: This should be a case insensitive lookup per RFC6266 + String tmpFilename = null; + + ContentDisposition cd = new ContentDisposition(s); + tmpFilename = cd.getParameter("filename"); + + if (tmpFilename == null || tmpFilename.length() < 1) + { + /* Try to extract manually */ + int n = s.indexOf("filename="); + if (n > -1) + { + tmpFilename = s.substring(n); + tmpFilename = tmpFilename.replaceFirst("filename=", ""); + + int n1 = tmpFilename.indexOf(","); + if (n1 > -1) + s = s.substring(0, n1 - 1); + tmpFilename = tmpFilename.replaceAll("\"", ""); + s = s.trim(); + } + else + { + /* Try just using file separator */ + int pos = s.lastIndexOf(File.separator); + if (pos >= 0) + tmpFilename = s.substring(pos + 1); + } + } + return tmpFilename; + } } diff --git a/Server/src/org/openas2/message/BaseMessageMDN.java b/Server/src/org/openas2/message/BaseMessageMDN.java index 42463fd0..73c5590b 100644 --- a/Server/src/org/openas2/message/BaseMessageMDN.java +++ b/Server/src/org/openas2/message/BaseMessageMDN.java @@ -85,6 +85,14 @@ public InternetHeaders getHeaders() { return headers; } + public void copyHeaders(InternetHeaders srcHeaders) { + Enumeration
headerEn = srcHeaders.getAllHeaders(); + while (headerEn.hasMoreElements()) { + Header header = headerEn.nextElement(); + setHeader(header.getName(), header.getValue()); + } + } + public void setMessage(Message message) { this.message = message; } @@ -141,8 +149,8 @@ public DataHistory getHistory() { public String toString() { StringBuffer buf = new StringBuffer(); - buf.append("MDN From:").append(getPartnership().getSenderIDs()); - buf.append("To:").append(getPartnership().getReceiverIDs()); + buf.append("MDN From:").append(getPartnership().getReceiverIDs()); + buf.append("To:").append(getPartnership().getSenderIDs()); Enumeration
headerEn = getHeaders().getAllHeaders(); buf.append("\r\nHeaders:{"); diff --git a/Server/src/org/openas2/message/Message.java b/Server/src/org/openas2/message/Message.java index 493f08c0..83418115 100644 --- a/Server/src/org/openas2/message/Message.java +++ b/Server/src/org/openas2/message/Message.java @@ -1,12 +1,15 @@ package org.openas2.message; import java.io.Serializable; +import java.util.HashMap; import java.util.Map; import javax.mail.internet.InternetHeaders; import javax.mail.internet.MimeBodyPart; +import javax.mail.internet.ParseException; import org.openas2.OpenAS2Exception; +import org.openas2.Session; import org.openas2.params.InvalidParameterException; import org.openas2.partner.Partnership; @@ -22,8 +25,55 @@ public interface Message extends Serializable { public static final String MSG_STATUS_MDN_VERIFY = "verifying_mdn"; public static final String MSG_STATUS_MDN_PROCESS_INIT = "init_processing_mdn"; public static final String MSG_STATUS_MSG_CLEANUP = "cleanup"; + + public static final String MSG_STATE_SEND_START = "msg_send_start"; + public static final String MSG_STATE_SEND_EXCEPTION = "msg_send_exception"; + public static final String MSG_STATE_SEND_FAIL = "msg_send_fail"; + public static final String MSG_STATE_RECEIVE_START = "msg_receive_start"; + public static final String MSG_STATE_RECEIVE_EXCEPTION = "msg_receive_exception"; + public static final String MSG_STATE_RECEIVE_FAIL = "msg_receive_fail"; + public static final String MSG_STATE_MDN_ERROR_RESPONSE_START = "msg_receive_error_sending_mdn_error"; + public static final String MSG_STATE_MDN_SENDING_EXCEPTION = "mdn_sending_exception"; + public static final String MSG_STATE_MDN_RECEIVING_EXCEPTION = "mdn_receiving_exception"; + public static final String MSG_STATE_MDN_SEND_START = "mdn_send_start"; + public static final String MSG_STATE_MDN_RECEIVE_START = "mdn_receive_start"; + public static final String MSG_STATE_MSG_SENT_MDN_RECEIVED_ERROR = "msg_sent_mdn_received_error"; + public static final String MSG_STATE_MSG_SENT_MDN_RECEIVED_OK = "msg_sent_mdn_received_ok"; + public static final String MSG_STATE_MSG_RXD_MDN_SENDING_FAIL = "msg_rxd_mdn_sending_fail"; + public static final String MSG_STATE_MSG_RXD_MDN_SENT_OK = "msg_rxd_mdn_sent_ok"; + public static final String MSG_STATE_MIC_MISMATCH = "msg_sent_mdn_received_mic_mismatch"; + + public static Map STATE_MSGS = new HashMap() + { + private static final long serialVersionUID = 5L; + + { + put(MSG_STATE_SEND_START, "Message sending started"); + put(MSG_STATE_SEND_EXCEPTION, "Message sending exception occurred. Resend queued"); + put(MSG_STATE_SEND_FAIL, "Message sending failed."); + put(MSG_STATE_RECEIVE_START, "Message receiving started"); + put(MSG_STATE_RECEIVE_EXCEPTION, "Processing exception occurred receiving message. Resend queued"); + put(MSG_STATE_RECEIVE_FAIL, "Failed to receive inbound message successfully."); + put(MSG_STATE_MDN_ERROR_RESPONSE_START, + "Error processing received message. Sending MDN error response to partner"); + put(MSG_STATE_MDN_SENDING_EXCEPTION, "Processing exception sending MDN. Resend queued"); + put(MSG_STATE_MDN_RECEIVING_EXCEPTION, "Processing exception receiving MDN. Resend queued"); + put(MSG_STATE_MDN_SEND_START, "Message recieved. MDN sending started"); + put(MSG_STATE_MDN_RECEIVE_START, "Message sent. MDN receiving started"); + put(MSG_STATE_MSG_SENT_MDN_RECEIVED_ERROR, + "Message sent. Message MDN received indicates an error. Resend queued"); + put(MSG_STATE_MSG_SENT_MDN_RECEIVED_OK, "Message sent. Message MDN success response received."); + put(MSG_STATE_MSG_RXD_MDN_SENDING_FAIL, + "Message was received but failed to successfully send an MDN response to partner"); + put(MSG_STATE_MSG_RXD_MDN_SENT_OK, "Message received and MDN sent succesfully."); + } + }; public static final String SMIME_TYPE_COMPRESSED_DATA = "smime-type=compressed-data"; + + public String getPayloadFilename(); + public void setPayloadFilename(String filename); + public String extractPayloadFilename() throws ParseException; public void setStatus(String status); @@ -118,6 +168,8 @@ public interface Message extends Serializable { public String getLogMsg(); public void setLogMsg(String msg); + + public void trackMsgState(Session session); public String getCalculatedMIC(); diff --git a/Server/src/org/openas2/params/ComponentParameters.java b/Server/src/org/openas2/params/ComponentParameters.java new file mode 100644 index 00000000..6212b733 --- /dev/null +++ b/Server/src/org/openas2/params/ComponentParameters.java @@ -0,0 +1,35 @@ +package org.openas2.params; + +import org.openas2.Component; + +public class ComponentParameters extends ParameterParser +{ + public static final String KEY_COMPONENT_PARAMETER = "component"; + private Component target; + + public ComponentParameters(Component target) { + super(); + this.target = target; + } + + public void setParameter(String key, String value) throws InvalidParameterException { + throw new InvalidParameterException("Set not supported", this, key, value); + } + + public String getParameter(String key) throws InvalidParameterException { + if (key != null) { + return getTarget().getParameters().get(key); + } else { + throw new InvalidParameterException("Invalid area in key", this, key, null); + } + } + + public void setTarget(Component component) { + target = component; + } + + public Component getTarget() { + return target; + } + +} diff --git a/Server/src/org/openas2/params/MessageParameters.java b/Server/src/org/openas2/params/MessageParameters.java index 7d16856b..98e52a34 100644 --- a/Server/src/org/openas2/params/MessageParameters.java +++ b/Server/src/org/openas2/params/MessageParameters.java @@ -1,9 +1,8 @@ package org.openas2.params; -import java.io.File; import java.util.StringTokenizer; -import javax.mail.internet.ContentDisposition; +import javax.mail.internet.ParseException; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -69,53 +68,17 @@ public String getParameter(String key) throws InvalidParameterException { return getTarget().getHeader(areaID); } else if (area.equals(KEY_CONTENT_FILENAME) && areaID.equals("filename")) { String filename = "noContentDispositionFilename"; - String s = target.getContentDisposition(); - if (s == null || s.length() < 1) - return filename; - try { - if (logger.isDebugEnabled()) - logger.debug("Attempting filename extraction from Content-disposition: " + s); - // TODO: This should be a case insensitive lookup per RFC6266 - String tmpFilename = null; - - ContentDisposition cd = new ContentDisposition(s); - tmpFilename = cd.getParameter("filename"); - - if (tmpFilename == null || tmpFilename.length() < 1) - { - /* Try to extract manually */ - int n = s.indexOf("filename="); - if (n > -1) - { - tmpFilename = s.substring(n); - tmpFilename = tmpFilename.replaceFirst("filename=", ""); - - int n1 = tmpFilename.indexOf(","); - if (n1 > -1) - s = s.substring(0, n1 - 1); - tmpFilename = tmpFilename.replaceAll("\"", ""); - s = s.trim(); - } - else - { - /* Try just using file separator */ - int pos = s.lastIndexOf(File.separator); - if (pos >= 0) - tmpFilename = s.substring(pos + 1); - } - } - - if (tmpFilename != null && tmpFilename.length() > 0) - { - if (logger.isDebugEnabled()) - logger.debug("Filename extracted from Content-disposition: " + tmpFilename); - return tmpFilename; - } - - } catch (Exception e) { - e.printStackTrace(); + String s = null; + try + { + s = getTarget().extractPayloadFilename(); + } catch (ParseException e) + { + logger.warn("Failed to extract filename from content-disposition: " + org.openas2.logging.Log.getExceptionMsg(e), e); } - return filename; + if (s != null && s.length() > 0) + return s; + return filename; } else { throw new InvalidParameterException("Invalid area in key", this, key, null); } diff --git a/Server/src/org/openas2/partner/AS2Partnership.java b/Server/src/org/openas2/partner/AS2Partnership.java index d4a521c2..11215d21 100644 --- a/Server/src/org/openas2/partner/AS2Partnership.java +++ b/Server/src/org/openas2/partner/AS2Partnership.java @@ -10,9 +10,12 @@ public interface AS2Partnership { public static final String PA_AS2_RECEIPT_OPTION = "as2_receipt_option"; // URL destination for an async MDN public static final String PA_MESSAGEID = "messageid"; // format to use for message-id if not default public static final String PA_RESEND_MAX_RETRIES = "resend_max_retries"; // format to use for message-id if not default - public static final String PA_CUSTOM_MIME_HEADERS = "custom_mime_headers"; - public static final String PA_ADD_CUSTOM_MIME_HEADERS_TO_HTTP = "add_custom_mime_headers_to_http"; - public static final String PA_CUSTOM_MIME_HEADER_NAMES_FROM_FILENAME = "custom_mime_header_names_from_filename"; - public static final String PA_CUSTOM_MIME_HEADER_NAME_DELIMITERS_IN_FILENAME = "custom_mime_header_name_delimiters_in_filename"; - public static final String PA_CUSTOM_MIME_HEADER_NAMES_REGEX_ON_FILENAME = "custom_mime_header_names_regex_on_filename"; + public static final String PA_CUSTOM_MIME_HEADERS = "custom_mime_headers"; // list of nme/value pairs for setting custom mime headers + public static final String PA_ADD_CUSTOM_MIME_HEADERS_TO_HTTP = "add_custom_mime_headers_to_http"; // Add the custom mime headers (if any) to HTTP header if "true" + public static final String PA_CUSTOM_MIME_HEADER_NAMES_FROM_FILENAME = "custom_mime_header_names_from_filename"; // List of header names to be set from parsed filename + public static final String PA_CUSTOM_MIME_HEADER_NAME_DELIMITERS_IN_FILENAME = "custom_mime_header_name_delimiters_in_filename"; // Delimiters to split filename into values + public static final String PA_CUSTOM_MIME_HEADER_NAMES_REGEX_ON_FILENAME = "custom_mime_header_names_regex_on_filename"; // Regex to split filename into values + + public static final String PA_ATTRIB_NAMES_FROM_FILENAME = "attribute_names_from_filename"; // List of attribute names to be set from parsed filename + public static final String PA_ATTRIB_VALUES_REGEX_ON_FILENAME = "attribute_values_regex_on_filename"; // Regex to split filename into values } diff --git a/Server/src/org/openas2/partner/BasePartnershipFactory.java b/Server/src/org/openas2/partner/BasePartnershipFactory.java index ba9d594b..c077e9d0 100644 --- a/Server/src/org/openas2/partner/BasePartnershipFactory.java +++ b/Server/src/org/openas2/partner/BasePartnershipFactory.java @@ -4,9 +4,12 @@ import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import org.openas2.BaseComponent; import org.openas2.OpenAS2Exception; +import org.openas2.message.FileAttribute; import org.openas2.message.Message; import org.openas2.message.MessageMDN; import org.openas2.params.MessageParameters; @@ -15,11 +18,14 @@ public abstract class BasePartnershipFactory extends BaseComponent implements PartnershipFactory { private List partnerships; - public Partnership getPartnership(Partnership p) throws OpenAS2Exception { + public Partnership getPartnership(Partnership p, boolean reverseLookup) throws OpenAS2Exception { Partnership ps = (p.getName() == null) ? null : getPartnership(p.getName()); if (ps == null) { - ps = getPartnership(p.getSenderIDs(), p.getReceiverIDs()); + if (reverseLookup) + ps = getPartnership(p.getReceiverIDs(), p.getSenderIDs()); + else + ps = getPartnership(p.getSenderIDs(), p.getReceiverIDs()); } if (ps == null) { @@ -41,10 +47,36 @@ public List getPartnerships() { return partnerships; } - public void updatePartnership(Message msg, boolean overwrite) throws OpenAS2Exception { + public void updatePartnership(Message msg, boolean overwrite) throws OpenAS2Exception + { // Fill in any available partnership information - Partnership partnership = getPartnership(msg.getPartnership()); + Partnership partnership = getPartnership(msg.getPartnership(), false); msg.getPartnership().copy(partnership); + // Now set dynamic parms based on file name if configured to + String filename = msg.getAttribute(FileAttribute.MA_FILENAME); + String filenameToParmsList = msg.getPartnership().getAttribute(AS2Partnership.PA_ATTRIB_NAMES_FROM_FILENAME); + if (filename != null && filenameToParmsList != null && filenameToParmsList.length() > 0) + { + String[] headerNames = filenameToParmsList.split("\\s*,\\s*"); + + String regex = msg.getPartnership().getAttribute(AS2Partnership.PA_ATTRIB_VALUES_REGEX_ON_FILENAME); + if (regex != null) + { + Pattern p = Pattern.compile(regex); + Matcher m = p.matcher(filename); + if (!m.find() || m.groupCount() != headerNames.length) + { + throw new OpenAS2Exception("Could not match filename (" + filename + ") to parameters required using the regex provided (" + regex + "): " + + (m.find() ? ("Mismatch in parameter count to extracted group count: " + headerNames.length + + "::" + m.groupCount()) : "No match found in filename")); + } + for (int i = 0; i < headerNames.length; i++) + { + msg.setAttribute(headerNames[i], m.group(i + 1)); + } + } + } + // Set attributes if (overwrite) { @@ -57,7 +89,8 @@ public void updatePartnership(Message msg, boolean overwrite) throws OpenAS2Exce public void updatePartnership(MessageMDN mdn, boolean overwrite) throws OpenAS2Exception { // Fill in any available partnership information - Partnership partnership = getPartnership(mdn.getPartnership()); + // the MDN partnership should be the one used to send original message hence reverse lookup + Partnership partnership = getPartnership(mdn.getPartnership(), true); mdn.getPartnership().copy(partnership); } diff --git a/Server/src/org/openas2/partner/Partnership.java b/Server/src/org/openas2/partner/Partnership.java index 52dd5ec1..da811439 100644 --- a/Server/src/org/openas2/partner/Partnership.java +++ b/Server/src/org/openas2/partner/Partnership.java @@ -18,8 +18,9 @@ public class Partnership implements Serializable { public static final String PID_EMAIL = "email"; // Email address public static final String PA_PROTOCOL = "protocol"; // AS1 or AS2 public static final String PA_SUBJECT = "subject"; // Subject sent in messages - public static final String PA_CONTENT_TRANSFER_ENCODING = "content_transfer_encoding"; // optional content transer enc value + public static final String PA_CONTENT_TRANSFER_ENCODING = "content_transfer_encoding"; // optional content transfer enc value public static final String PA_REMOVE_PROTECTION_ATTRIB = "remove_cms_algorithm_protection_attrib"; // Some AS2 systems do not support the attribute + public static final String PA_SET_CONTENT_TRANSFER_ENCODING_OMBP = "set_content_transfer_encoding_on_outer_mime_bodypart"; // optional content transfer enc value private Map attributes; diff --git a/Server/src/org/openas2/partner/PartnershipFactory.java b/Server/src/org/openas2/partner/PartnershipFactory.java index 2c0bdf4d..7abcf742 100644 --- a/Server/src/org/openas2/partner/PartnershipFactory.java +++ b/Server/src/org/openas2/partner/PartnershipFactory.java @@ -19,7 +19,7 @@ public interface PartnershipFactory extends Component { public static final String COMPID_PARTNERSHIP_FACTORY = "partnershipfactory"; // throws an exception if the partnership doesn't exist - public Partnership getPartnership(Partnership p) throws OpenAS2Exception; + public Partnership getPartnership(Partnership p, boolean reverseLookup) throws OpenAS2Exception; // looks up and fills in any header info for a specific msg's partnership public void updatePartnership(Message msg, boolean overwrite) throws OpenAS2Exception; diff --git a/Server/src/org/openas2/processor/DefaultProcessor.java b/Server/src/org/openas2/processor/DefaultProcessor.java index 037d2be4..0395dad3 100644 --- a/Server/src/org/openas2/processor/DefaultProcessor.java +++ b/Server/src/org/openas2/processor/DefaultProcessor.java @@ -73,6 +73,7 @@ public void handle(String action, Message msg, Map options) if (pex != null) { throw pex; } else if (!moduleFound) { + if ("true".equalsIgnoreCase((String)options.get("OPTIONAL_MODULE"))) return; msg.setLogMsg("No handler found for action: " + action); logger.error(msg); throw new NoModuleException(action, msg, options); diff --git a/Server/src/org/openas2/processor/msgtracking/BaseMsgTrackingModule.java b/Server/src/org/openas2/processor/msgtracking/BaseMsgTrackingModule.java new file mode 100644 index 00000000..a593e6d0 --- /dev/null +++ b/Server/src/org/openas2/processor/msgtracking/BaseMsgTrackingModule.java @@ -0,0 +1,127 @@ +package org.openas2.processor.msgtracking; + +import java.util.HashMap; +import java.util.Map; + +import org.openas2.OpenAS2Exception; +import org.openas2.Session; +import org.openas2.lib.partner.IPartnership; +import org.openas2.message.AS2MessageMDN; +import org.openas2.message.Message; +import org.openas2.message.MessageMDN; +import org.openas2.partner.AS2Partnership; +import org.openas2.processor.BaseProcessorModule; + +public abstract class BaseMsgTrackingModule extends BaseProcessorModule implements TrackingModule { + /* + Enum Field + { + MSG_ID ("MSG_ID"), + MDN_ID ("MDN_ID"), + DIRECTION ("DIRECTION"), + IS_RESEND ("IS_RESEND"), + RESEND_COUNT ("RESEND_COUNT"), + SENDER_ID ("SENDER_ID"), + RECEIVER_ID ("RECEIVER_ID"), + STATUS ("STATUS"), + STATE ("STATE"), + SIGNATURE_ALGORITHM ("SIGNATURE_ALGORITHM"), + ENCRYPTION_ALGORITHM ("ENCRYPTION_ALGORITHM"), + COMPRESSION ("COMPRESSION"), + FILE_NAME ("FILE_NAME"), + CONTENT_TYPE ("CONTENT_TYPE"), + CONTENT_TRANSFER_ENCODING ("CONTENT_TRANSFER_ENCODING"), + MDN_MODE ("MDN_MODE"), + MDN_RESPONSE ("MDN_RESPONSE"), + STATE_MSG ("STATE_MSG"), + CREATE_DT ("CREATE_DT"), + UPDATE_DT ("UPDATE_DT"); + }; + */ + + public static class FIELDS + { + public static final String MSG_ID = "MSG_ID"; + public static final String MDN_ID = "MDN_ID"; + public static final String DIRECTION = "DIRECTION"; + public static final String IS_RESEND = "IS_RESEND"; + public static final String RESEND_COUNT = "RESEND_COUNT"; + public static final String SENDER_ID = "SENDER_ID"; + public static final String RECEIVER_ID = "RECEIVER_ID"; + public static final String STATUS = "STATUS"; + public static final String STATE = "STATE"; + public static final String SIGNATURE_ALGORITHM = "SIGNATURE_ALGORITHM"; + public static final String ENCRYPTION_ALGORITHM = "ENCRYPTION_ALGORITHM"; + public static final String COMPRESSION = "COMPRESSION"; + public static final String FILE_NAME = "FILE_NAME"; + public static final String CONTENT_TYPE = "CONTENT_TYPE"; + public static final String CONTENT_TRANSFER_ENCODING = "CONTENT_TRANSFER_ENCODING"; + public static final String MDN_MODE = "MDN_MODE"; + public static final String MDN_RESPONSE = "MDN_RESPONSE"; + public static final String STATE_MSG = "STATE_MSG"; + public static final String CREATE_DT = "CREATE_DT"; + public static final String UPDATE_DT = "UPDATE_DT"; + } + + public void handle(String action, Message msg, Map options) throws OpenAS2Exception { + + Map fields = buildMap(msg, options); + persist(msg, fields); + + } + + public boolean canHandle(String action, Message msg, Map options) { + return action.equals(getModuleAction()); + } + + public void init(Session session, Map options) throws OpenAS2Exception { + super.init(session, options); + } + + protected abstract String getModuleAction(); + + + protected abstract void persist(Message msg, Map map); + + protected Map buildMap(Message msg, Map options) + { + Map map = new HashMap(); + String msgId = msg.getMessageID(); + MessageMDN mdn = msg.getMDN(); + if (mdn != null) + { + map.put(FIELDS.MDN_ID, mdn.getMessageID()); + map.put(FIELDS.MDN_RESPONSE, msg.getMDN().getText()); + // Make sure we log against the original message ID since MDN can have different ID + String originalMsgId = mdn.getAttribute(AS2MessageMDN.MDNA_ORIG_MESSAGEID); + if (originalMsgId != null && !msgId.equals(originalMsgId)) msgId = originalMsgId; + } + map.put(FIELDS.MSG_ID, msgId); + // Default DIRECTION to SEND for now... + String direction = (String) options.get("DIRECTION"); + map.put(FIELDS.DIRECTION,direction==null?"SEND":direction); + String isResend = (String) options.get("IS_RESEND"); + if (isResend != null) map.put(FIELDS.IS_RESEND, isResend); + //map.put(FIELDS.RESEND_COUNT, ); + String sender = msg.getPartnership().getSenderID(AS2Partnership.PID_AS2); + if (sender == null) sender = mdn.getPartnership().getSenderID(AS2Partnership.PID_AS2); + map.put(FIELDS.SENDER_ID, sender); + String receiver = msg.getPartnership().getReceiverID(AS2Partnership.PID_AS2); + if (receiver == null) receiver = mdn.getPartnership().getReceiverID(AS2Partnership.PID_AS2); + map.put(FIELDS.RECEIVER_ID, receiver); + map.put(FIELDS.STATUS, msg.getStatus()); + String state = (String) options.get("STATE"); + map.put(FIELDS.STATE, state); + map.put(FIELDS.STATE_MSG, (String) Message.STATE_MSGS.get(state)); + map.put(FIELDS.SIGNATURE_ALGORITHM, msg.getPartnership().getAttribute(IPartnership.ATTRIBUTE_SIGNATURE_ALGORITHM)); + map.put(FIELDS.ENCRYPTION_ALGORITHM, msg.getPartnership().getAttribute(IPartnership.ATTRIBUTE_ENCRYPTION_ALGORITHM)); + map.put(FIELDS.COMPRESSION, msg.getPartnership().getAttribute(IPartnership.ATTRIBUTE_COMPRESSION_TYPE)); + map.put(FIELDS.FILE_NAME, msg.getPayloadFilename()); + map.put(FIELDS.CONTENT_TYPE, msg.getContentType()); + map.put(FIELDS.CONTENT_TRANSFER_ENCODING, msg.getHeader("Content-Transfer-Encoding")); + map.put(FIELDS.MDN_MODE, (msg.getPartnership().isAsyncMDN()?"ASYNC":"SYNC")); + + return map; + } + +} \ No newline at end of file diff --git a/Server/src/org/openas2/processor/msgtracking/DbTrackingModule.java b/Server/src/org/openas2/processor/msgtracking/DbTrackingModule.java new file mode 100644 index 00000000..975244e6 --- /dev/null +++ b/Server/src/org/openas2/processor/msgtracking/DbTrackingModule.java @@ -0,0 +1,298 @@ +package org.openas2.processor.msgtracking; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import java.sql.Statement; +import java.sql.Types; +import java.util.Map; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.h2.tools.Server; +import org.openas2.OpenAS2Exception; +import org.openas2.Session; +import org.openas2.database.H2DBHandler; +import org.openas2.message.Message; +import org.openas2.params.ComponentParameters; +import org.openas2.params.CompositeParameters; +import org.openas2.params.ParameterParser; +import org.openas2.util.DateUtil; +import org.openas2.util.Properties; + +public class DbTrackingModule extends BaseMsgTrackingModule +{ + public static final String PARAM_TCP_SERVER_START = "tcp_server_start"; + public static final String PARAM_TCP_SERVER_PORT = "tcp_server_port"; + public static final String PARAM_TCP_SERVER_PWD = "tcp_server_password"; + public static final String PARAM_DB_USER = "db_user"; + public static final String PARAM_DB_PWD = "db_pwd"; + public static final String PARAM_DB_NAME = "db_name"; + public static final String PARAM_DB_DIRECTORY = "db_directory"; + public static final String PARAM_JDBC_CONNECT_STRING = "jdbc_connect_string"; + public static final String PARAM_JDBC_DRIVER = "jdbc_driver"; + public static final String PARAM_JDBC_SERVER_URL = "jdbc_server_url"; + public static final String PARAM_JDBC_PARAMS = "jdbc_extra_paramters"; + + public static final String DEFAULT_TRACKING_DB_HANDLER_CLASS = "org.openas2.processor.msgtracking.DBHandler"; + public static final String PARAM_TRACKING_DB_HANDLER_CLASS = "tracking_db_handler_class"; + + private String dbUser = null; + private String dbPwd = null; + private String dbDirectory = null; + private String jdbcConnectString = null; + private String configBaseDir = null; + //private String jdbcDriver = null; + private boolean isRunning = false; + private String sqlEscapeChar = "'"; + Server server = null; + + private Log logger = LogFactory.getLog(DbTrackingModule.class.getSimpleName()); + + public void init(Session session, Map options) throws OpenAS2Exception + { + super.init(session, options); + CompositeParameters paramParser = createParser(); + dbUser = getParameter(PARAM_DB_USER, true); + dbPwd = getParameter(PARAM_DB_PWD, true); + dbDirectory = getParameter(PARAM_DB_DIRECTORY, true); + configBaseDir = session.getBaseDirectory(); + jdbcConnectString = getParameter(PARAM_JDBC_CONNECT_STRING, true); + jdbcConnectString.replace("%home%", configBaseDir); + // Support component attributes in connect string + jdbcConnectString = ParameterParser.parse(jdbcConnectString, paramParser); + //jdbcDriver = getParameter(PARAM_JDBC_DRIVER, false); + sqlEscapeChar = Properties.getProperty("sql_escape_character", "'"); + } + + protected String getModuleAction() + { + return DO_TRACK_MSG; + } + + protected CompositeParameters createParser() + { + CompositeParameters params = new CompositeParameters(true); + + params.add("component", new ComponentParameters(this)); + return params; + } + + protected void persist(Message msg, Map map) + { + Connection conn = null; + try + { + conn = DBConnection.getConnection(jdbcConnectString, dbUser, dbPwd); + Statement s = conn.createStatement(); + String msgIdField = FIELDS.MSG_ID; + ResultSet rs = s.executeQuery("select * from msg_metadata where " + msgIdField + " = '" + + map.get(msgIdField) + "'"); + ResultSetMetaData meta = rs.getMetaData(); + boolean isUpdate = rs.next(); // Record already exists so update + StringBuffer fieldStmt = new StringBuffer(); + for (int i = 0; i < meta.getColumnCount(); i++) + { + String colName = meta.getColumnLabel(i + 1); + if (colName.equalsIgnoreCase("ID")) continue; + else if (colName.equalsIgnoreCase(FIELDS.UPDATE_DT)) + { + // Ignore if not update mode + if (isUpdate) appendField(colName, DateUtil.getSqlTimestamp(), fieldStmt, meta.getColumnType(i + 1)); + } + else if (colName.equalsIgnoreCase(FIELDS.CREATE_DT)) + map.remove(FIELDS.CREATE_DT); + else if (isUpdate) + { + // Only write unchanged field values + String mapVal = map.get(colName.toUpperCase()); + if (mapVal == null) + { + continue; + } + String dbVal = rs.getString(colName); + if (dbVal != null && mapVal.equals(dbVal)) + { + // Unchanged value so remove from map + continue; + } + appendField(colName, mapVal, fieldStmt, meta.getColumnType(i + 1)); + } + else + { + // For new record add every field + appendField(colName, map.get(colName.toUpperCase()), fieldStmt, meta.getColumnType(i + 1)); + } + } + if (fieldStmt.length() > 0) + { + String stmt = ""; + if (isUpdate) + { + stmt = "update msg_metadata set " + fieldStmt.toString() + " where " + FIELDS.MSG_ID + " = '" + map.get(msgIdField) + "'"; + } + else + stmt = "insert into msg_metadata set " + fieldStmt.toString(); + if (s.executeUpdate(stmt) > 0) + { + if (logger.isDebugEnabled()) logger.debug("Tracking record successfully persisted to database: " + map); + } + else + { + throw new OpenAS2Exception("Failed to persist tracking record to DB: " + map); + } + } + else + { + if (logger.isInfoEnabled()) logger.info("No change from existing record in DB. Tracking record not updated: " + map); + } + } catch (Exception e) + { + msg.setLogMsg("Failed to persist a tracking event: " + org.openas2.logging.Log.getExceptionMsg(e) + + " ::: Data map: " + map); + logger.error(msg, e); + } + finally + { + if (conn != null) + { + try + { + DBConnection.releaseConnection(conn); + } catch (OpenAS2Exception e) + { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (SQLException e) + { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + } + + } + + private void appendField(String name, String value, StringBuffer sb, int dataType) + { + String valueEncap = "'"; + boolean requiresEncap = true; // Assume it is a field requiring encapsulation + if (value == null) requiresEncap = false; // setting field to NULL + else + { + switch (dataType) + { + case Types.BIGINT: + case Types.DECIMAL: + case Types.DOUBLE: + case Types.FLOAT: + case Types.INTEGER: + case Types.NUMERIC: + case Types.REAL: + case Types.ROWID: + case Types.SMALLINT: + requiresEncap = false; + break; + } + } + + if (sb.length() > 0) + sb.append(","); + + sb.append(name).append("="); + if (requiresEncap) sb.append(valueEncap).append(value.replaceAll("'", sqlEscapeChar+"'")).append(valueEncap); + else sb.append(value); + + } + + public boolean isRunning() + { + return isRunning; + } + + public void start() throws OpenAS2Exception + { + DBConnection.start(jdbcConnectString, dbUser, dbPwd); + isRunning = true; + if ("true".equalsIgnoreCase(getParameter(PARAM_TCP_SERVER_START, "true"))) + { + String tcpPort = getParameter(PARAM_TCP_SERVER_PORT, "9092"); + String tcpPwd = getParameter(PARAM_TCP_SERVER_PWD, "OpenAS2"); + + try + { + server = Server.createTcpServer( "-tcpPort", tcpPort, "-tcpPassword", tcpPwd, "-baseDir", dbDirectory, "-tcpAllowOthers").start(); + } catch (SQLException e) + { + throw new OpenAS2Exception("Failed to start TCP server", e); + } + } + } + + public void stop() + { + try + { + // Stopping the TCP server will stop the database so only do one of them + if (server != null) + { + server.shutdown(); + } + else + { + DBConnection.stop(jdbcConnectString); + } + isRunning = false; + } catch (Exception e) + { + if (logger.isErrorEnabled()) + logger.error("Failed to stop database for message tracking module.", e); + } + } + + /* + * Use a static class to make sure we only have one instance of the database + * connection pool no matter how many DbTrackingModule instances there are. + */ + private static class DBConnection + { + private static H2DBHandler dbHandler = null; + + public static Connection getConnection(String connectString, String userId, String pwd) + throws OpenAS2Exception, SQLException + { + if (dbHandler == null) + throw new OpenAS2Exception("Database has not been started: " + connectString); + return dbHandler.getConnection(); + } + + public static void releaseConnection(Connection c) throws OpenAS2Exception, SQLException + { + if (dbHandler == null) + throw new OpenAS2Exception("Database has not been started trying to release connection"); + c.close(); + + } + + public static void start(String connectString, String userId, String pwd) throws OpenAS2Exception + { + if (dbHandler == null) + { + dbHandler = new H2DBHandler(); + dbHandler.createConnectionPool(connectString, userId, pwd); + } else + throw new OpenAS2Exception("Database was already started: " + connectString); + } + + public static void stop(String connectString) throws OpenAS2Exception, SQLException + { + if (dbHandler != null) + { + dbHandler.shutdown(connectString); + dbHandler.destroyConnectionPool(); + } + } + } + +} \ No newline at end of file diff --git a/Server/src/org/openas2/processor/msgtracking/TrackingModule.java b/Server/src/org/openas2/processor/msgtracking/TrackingModule.java new file mode 100644 index 00000000..6ae493e7 --- /dev/null +++ b/Server/src/org/openas2/processor/msgtracking/TrackingModule.java @@ -0,0 +1,10 @@ + +package org.openas2.processor.msgtracking; + +import org.openas2.processor.ActiveModule; + + +public interface TrackingModule extends ActiveModule { + public static final String DO_TRACK_MSG = "track_msg"; + public static final String TRACK_MSG_TCP_SERVER = "track_msg_tcp_server"; +} diff --git a/Server/src/org/openas2/processor/receiver/AS2MDNReceiverHandler.java b/Server/src/org/openas2/processor/receiver/AS2MDNReceiverHandler.java index 860977dc..02adb545 100644 --- a/Server/src/org/openas2/processor/receiver/AS2MDNReceiverHandler.java +++ b/Server/src/org/openas2/processor/receiver/AS2MDNReceiverHandler.java @@ -3,6 +3,8 @@ import java.io.IOException; import java.net.HttpURLConnection; import java.net.Socket; +import java.util.HashMap; +import java.util.Map; import javax.activation.DataHandler; import javax.mail.internet.ContentType; @@ -15,6 +17,7 @@ import org.openas2.message.AS2MessageMDN; import org.openas2.message.Message; import org.openas2.message.MessageMDN; +import org.openas2.partner.AS2Partnership; import org.openas2.util.AS2Util; import org.openas2.util.ByteArrayDataSource; import org.openas2.util.HTTPUtil; @@ -46,22 +49,24 @@ public void handle(NetModule owner, Socket s) AS2Message msg = new AS2Message(); + Map options = new HashMap(); + options.put("DIRECTION", "SEND"); byte[] data = null; // Read in the message request, headers, and data try { data = HTTPUtil.readData(s.getInputStream(), s.getOutputStream(), msg); - // check if the requested URL is defined in attribute - // "as2_receipt_option" + // check if the requested URL is defined in attribute "as2_receipt_option" // in one of partnerships, if yes, then process incoming AsyncMDN if (logger.isInfoEnabled()) logger.info("incoming connection for receiving AsyncMDN" + " [" + getClientInfo(s) + "]" + msg.getLogMsgID()); + if (logger.isTraceEnabled()) + logger.trace("Incoming ASYNC MDN message - Message struct: " + msg.toString()); ContentType receivedContentType; MimeBodyPart receivedPart = new MimeBodyPart(msg.getHeaders(), data); - msg.setData(receivedPart); receivedContentType = new ContentType(receivedPart.getContentType()); // MimeBodyPart receivedPart = new MimeBodyPart(); @@ -70,11 +75,30 @@ public void handle(NetModule owner, Socket s) receivedPart.setHeader("Content-Type", receivedContentType.toString()); msg.setData(receivedPart); - // Create a MessageMDN and copy HTTP headers - MessageMDN mdn = new AS2MessageMDN(msg); - // copy headers from msg to MDN from msg - mdn.setHeaders(msg.getHeaders()); + + // Switch the msg headers since the original message went in the opposite direction + String to = msg.getHeader("AS2-To"); + msg.setHeader("AS2-To", msg.getHeader("AS2-From")); + msg.setHeader("AS2-From", to); + msg.getPartnership().setSenderID(AS2Partnership.PID_AS2, msg.getHeader("AS2-From")); + msg.getPartnership().setReceiverID(AS2Partnership.PID_AS2, msg.getHeader("AS2-To")); + getModule().getSession().getPartnershipFactory().updatePartnership(msg, true); + + // Create a MessageMDN + MessageMDN mdn = new AS2MessageMDN(msg, true); + + if (logger.isTraceEnabled()) + logger.trace("Incoming ASYNC MDN message - MDN struct: " + mdn.toString()); + /* + // Log significant msg state + options.put("STATE", Message.MSG_STATE_MDN_RECEIVE_START); + options.put("STATE_MSG", "MDN response received. Message processing started."); + msg.trackMsgState(getModule().getSession(), options); + */ AS2Util.processMDN(msg, data, s.getOutputStream(), true, getModule().getSession(), this); + // Log significant msg state + msg.setOption("STATE", Message.MSG_STATE_MSG_SENT_MDN_RECEIVED_OK); + msg.trackMsgState(getModule().getSession()); } catch (Exception e) { @@ -113,6 +137,9 @@ public void handle(NetModule owner, Socket s) logger.error(msg, e); } + // Log significant msg state + msg.setOption("STATE", Message.MSG_STATE_SEND_FAIL); + msg.trackMsgState(getModule().getSession()); AS2Util.cleanupFiles(msg, true); } diff --git a/Server/src/org/openas2/processor/receiver/AS2ReceiverHandler.java b/Server/src/org/openas2/processor/receiver/AS2ReceiverHandler.java index 0240a00e..e0def94f 100644 --- a/Server/src/org/openas2/processor/receiver/AS2ReceiverHandler.java +++ b/Server/src/org/openas2/processor/receiver/AS2ReceiverHandler.java @@ -8,11 +8,14 @@ import java.security.PrivateKey; import java.security.cert.X509Certificate; import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; import javax.activation.DataHandler; import javax.mail.MessagingException; import javax.mail.internet.ContentType; import javax.mail.internet.MimeBodyPart; +import javax.mail.internet.ParseException; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -24,6 +27,7 @@ import org.openas2.lib.helper.ICryptoHelper; import org.openas2.lib.util.MimeUtil; import org.openas2.message.AS2Message; +import org.openas2.message.Message; import org.openas2.message.MessageMDN; import org.openas2.message.NetAttribute; import org.openas2.partner.AS2Partnership; @@ -66,6 +70,8 @@ public void handle(NetModule owner, Socket s) { byte[] data = null; BufferedOutputStream out; + Map options = new HashMap(); + options.put("DIRECTION", "RECEIVE"); try { out = new BufferedOutputStream(s.getOutputStream()); } catch (IOException e1) { @@ -157,8 +163,19 @@ else if (logger.isTraceEnabled()) "processed", "Error", "authentication-failed"), AS2ReceiverModule.DISP_PARTNERSHIP_NOT_FOUND, oae); } + // Log significant msg state + msg.setOption("STATE", Message.MSG_STATE_RECEIVE_START); + msg.trackMsgState(getModule().getSession()); // Decrypt and verify signature of the data, and attach data to the message mic = decryptAndVerify(msg); + try + { + // Extract and Store the received filename of the payload + msg.setPayloadFilename(msg.extractPayloadFilename()); + } catch (ParseException e1) + { + logger.error("Failed to extract the file name from received content-disposition", e1); + } // Process the received message try { @@ -166,7 +183,10 @@ else if (logger.isTraceEnabled()) } catch (OpenAS2Exception oae) { msg.setLogMsg("Error handling received message: " + oae.getCause()); logger.error(msg, oae); - ; + // Log significant msg state + msg.setOption("STATE", Message.MSG_STATE_RECEIVE_EXCEPTION); + msg.trackMsgState(getModule().getSession()); + throw new DispositionException(new DispositionType("automatic-action", "MDN-sent-automatically", "processed", "Error", "unexpected-processing-error"), AS2ReceiverModule.DISP_STORAGE_FAILED, oae); @@ -175,7 +195,10 @@ else if (logger.isTraceEnabled()) // Transmit a success MDN if requested try { if (msg.isRequestingMDN()) { - processMDN(msg, out, + // Log significant msg state + msg.setOption("STATE", Message.MSG_STATE_MDN_SEND_START); + msg.trackMsgState(getModule().getSession()); + sendMDNResponse(msg, out, new DispositionType("automatic-action", "MDN-sent-automatically", "processed"), mic, AS2ReceiverModule.DISP_SUCCESS); //if asyncMDN requested, close connection and initiate separate MDN send @@ -187,6 +210,10 @@ else if (logger.isTraceEnabled()) logger.debug("Call to asynch MDN initiated"); return; } + // Log significant msg state + msg.setOption("STATE", Message.MSG_STATE_MSG_SENT_MDN_RECEIVED_OK); + msg.trackMsgState(getModule().getSession()); + } else { HTTPUtil.sendHTTPResponse(out, HttpURLConnection.HTTP_OK, false); out.flush(); @@ -195,12 +222,18 @@ else if (logger.isTraceEnabled()) } catch (Exception e) { msg.setLogMsg("Error processing MDN for received message: " + e.getCause()); logger.error(msg, e); + // Log significant msg state + msg.setOption("STATE", Message.MSG_STATE_MDN_SENDING_EXCEPTION); + msg.trackMsgState(getModule().getSession()); throw new WrappedException("Error creating and returning MDN, message was still processed", e); } } catch (DispositionException de) { - processMDN(msg, out, de.getDisposition(), mic, de.getText()); + // Log significant msg state + msg.setOption("STATE", Message.MSG_STATE_MSG_SENT_MDN_RECEIVED_ERROR); + msg.trackMsgState(getModule().getSession()); + sendMDNResponse(msg, out, de.getDisposition(), mic, de.getText()); //if asyncMDN requested, close connection and initiate separate MDN send if (msg.isRequestingAsynchMDN() ) { try { @@ -217,12 +250,18 @@ else if (logger.isTraceEnabled()) } catch (Exception e) { msg.setLogMsg("Failed to initiate async MDN send on DispositionException handling."); logger.error(msg, e); + // Log significant msg state + msg.setOption("STATE", Message.MSG_STATE_MSG_RXD_MDN_SENDING_FAIL); + msg.trackMsgState(getModule().getSession()); } return; } getModule().handleError(msg, de); } catch (OpenAS2Exception oae) { + // Log significant msg state + msg.setOption("STATE", Message.MSG_STATE_RECEIVE_FAIL); + msg.trackMsgState(getModule().getSession()); getModule().handleError(msg, oae); } } @@ -394,7 +433,7 @@ protected String decryptAndVerify(AS2Message msg) throws OpenAS2Exception { return mic; } - protected void processMDN(AS2Message msg, BufferedOutputStream out, DispositionType disposition, String mic, String text) { + protected void sendMDNResponse(AS2Message msg, BufferedOutputStream out, DispositionType disposition, String mic, String text) { boolean mdnBlocked = false; mdnBlocked = (msg.getPartnership().getAttribute(ASXPartnership.PA_BLOCK_ERROR_MDN) != null); @@ -427,12 +466,16 @@ protected void processMDN(AS2Message msg, BufferedOutputStream out, DispositionT Enumeration headers = mdn.getHeaders().getAllHeaderLines(); String header; + StringBuffer saveHeaders = new StringBuffer(); while (headers.hasMoreElements()) { header = (String) headers.nextElement() + "\r\n"; + saveHeaders = saveHeaders.append(";;").append(header); out.write(header.getBytes()); } + if (logger.isTraceEnabled()) + logger.trace("MDN HEADERS SENT: " + saveHeaders + msg.getLogMsgID()); out.write("\r\n".getBytes()); data.writeTo(out); out.flush(); diff --git a/Server/src/org/openas2/processor/receiver/DirectoryPollingModule.java b/Server/src/org/openas2/processor/receiver/DirectoryPollingModule.java index db6c643f..7335f18d 100644 --- a/Server/src/org/openas2/processor/receiver/DirectoryPollingModule.java +++ b/Server/src/org/openas2/processor/receiver/DirectoryPollingModule.java @@ -1,52 +1,26 @@ package org.openas2.processor.receiver; import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; -import java.util.Enumeration; import java.util.HashMap; import java.util.Iterator; import java.util.Map; -import java.util.StringTokenizer; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import javax.activation.DataHandler; -import javax.mail.Header; -import javax.mail.MessagingException; -import javax.mail.internet.MimeBodyPart; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.openas2.OpenAS2Exception; import org.openas2.Session; -import org.openas2.WrappedException; -import org.openas2.lib.util.MimeUtil; -import org.openas2.message.FileAttribute; -import org.openas2.message.InvalidMessageException; import org.openas2.message.Message; import org.openas2.params.InvalidParameterException; -import org.openas2.params.MessageParameters; -import org.openas2.params.ParameterParser; -import org.openas2.partner.AS2Partnership; -import org.openas2.partner.Partnership; -import org.openas2.processor.resender.ResenderModule; -import org.openas2.processor.sender.SenderModule; -import org.openas2.util.AS2Util; -import org.openas2.util.ByteArrayDataSource; import org.openas2.util.IOUtilOld; public abstract class DirectoryPollingModule extends PollingModule { public static final String PARAM_OUTBOX_DIRECTORY = "outboxdir"; public static final String PARAM_FILE_EXTENSION_FILTER = "fileextensionfilter"; - public static final String PARAM_ERROR_DIRECTORY = "errordir"; - public static final String PARAM_SENT_DIRECTORY = "sentdir"; - public static final String PARAM_FORMAT = "format"; - public static final String PARAM_DELIMITERS = "delimiters"; - public static final String PARAM_DEFAULTS = "defaults"; - public static final String PARAM_MIMETYPE = "mimetype"; - public static final String PARAM_RESEND_MAX_RETRIES = "resend_max_retries"; private Map trackedFiles; private String outboxDir; private String errorDir; @@ -215,220 +189,24 @@ protected void processFile(File file) throws OpenAS2Exception if (logger.isInfoEnabled()) logger.info("processing " + file.getAbsolutePath()); - Message msg = createMessage(); - msg.setAttribute(FileAttribute.MA_FILEPATH, file.getAbsolutePath()); - msg.setAttribute(FileAttribute.MA_FILENAME, file.getName()); - msg.setAttribute(FileAttribute.MA_ERROR_DIR, getParameter(PARAM_ERROR_DIRECTORY, true)); - if (getParameter(PARAM_SENT_DIRECTORY, false) != null) - msg.setAttribute(FileAttribute.MA_SENT_DIR, getParameter(PARAM_SENT_DIRECTORY, false)); - - /* - * save the source file name into message object, it will be stored into - * pending information file for async MDN - */ - msg.setAttribute(FileAttribute.MA_PENDINGFILE, file.getName()); - - updateMessage(msg, file); - String customHeaderList = msg.getPartnership().getAttribute(AS2Partnership.PA_CUSTOM_MIME_HEADER_NAMES_FROM_FILENAME); - if (customHeaderList != null && customHeaderList.length() > 0) + try { - String[] headerNames = customHeaderList.split("\\s*,\\s*"); - String delimiters = msg.getPartnership().getAttribute(AS2Partnership.PA_CUSTOM_MIME_HEADER_NAME_DELIMITERS_IN_FILENAME); - if (logger.isTraceEnabled()) logger.trace("Adding custom headers based on message file name to custom headers map. Delimeters: " + delimiters + msg.getLogMsgID()); - if (delimiters != null) + processDocument(new FileInputStream(file), file.getName(), file.getAbsolutePath()); + try { - // Extract the values based on delimiters which means the mime header names are prefixed with a target - StringTokenizer valueTokens = new StringTokenizer(file.getName(), delimiters, false); - if (valueTokens != null && valueTokens.countTokens()!= headerNames.length) - { - msg.setLogMsg("Filename does not match headers list: Headers=" + customHeaderList + " ::: Filename=" + file.getName() + " ::: String delimiters=" + delimiters); - logger.error(msg); - throw new OpenAS2Exception("Invalid filename for extracting custom headers: " + file.getName()); - } - for (int i = 0; i < headerNames.length; i++) - { - String[] header = headerNames[i].split("\\."); - if (logger.isTraceEnabled()) logger.trace("Adding custom header: " + headerNames[i] - + " :::Split count:" + header.length + msg.getLogMsgID()); - if (header.length != 2) throw new OpenAS2Exception("Invalid custom header: " + headerNames[i] + " :: The header name must be prefixed by \"header.\" or \"junk.\" etc"); - if (!"header".equalsIgnoreCase(header[0])) continue; // Ignore anything not prefixed by "header" - msg.addCustomOuterMimeHeader(header[1], valueTokens.nextToken()); - } - } - else + IOUtilOld.deleteFile(file); + } catch (IOException e) { - String regex = msg.getPartnership().getAttribute(AS2Partnership.PA_CUSTOM_MIME_HEADER_NAMES_REGEX_ON_FILENAME); - if (regex != null) - { - Pattern p = Pattern.compile(regex); - Matcher m = p.matcher(file.getName()); - if (!m.find() || m.groupCount() != headerNames.length) - { - msg.setLogMsg("Could not match filename to headers required using the regex provided: " - + (m.find()?("Mismatch in header count to extracted group count: " - + headerNames.length + "::" + m.groupCount()):"No match found in filename")); - logger.error(msg); - throw new OpenAS2Exception("Invalid filename for extracting custom headers: " + file.getName()); - } - for (int i = 0; i < headerNames.length; i++) - { - msg.addCustomOuterMimeHeader(headerNames[i], m.group(i+1)); - } - } + throw new OpenAS2Exception("Failed to delete file handed off for processing:" + file.getAbsolutePath(), e); } - } - if (logger.isInfoEnabled()) - logger.info("file assigned to message " + file.getAbsolutePath() + msg.getLogMsgID()); - - if (msg.getData() == null) - { - throw new InvalidMessageException("Failed to retrieve data for outbound AS2 message for file: " + file.getAbsolutePath()); - } - if (logger.isTraceEnabled()) - logger.trace("PARTNERSHIP parms: " + msg.getPartnership().getAttributes() + msg.getLogMsgID()); - // Retry count - first try on partnership then directory polling module - String maxRetryCnt = msg.getPartnership().getAttribute(AS2Partnership.PA_RESEND_MAX_RETRIES); - if (maxRetryCnt == null || maxRetryCnt.length() < 1) + } catch (FileNotFoundException e) { - maxRetryCnt = getSession().getProcessor().getParameters().get(PARAM_RESEND_MAX_RETRIES); - } - if (logger.isTraceEnabled()) - logger.trace("RESEND COUNT extracted from config: " + maxRetryCnt + msg.getLogMsgID()); - Map options = msg.getOptions(); - options.put(ResenderModule.OPTION_RETRIES, maxRetryCnt); - - if (logger.isTraceEnabled()) - try - { - String headers = ""; - Enumeration
headersEnum = msg.getData().getAllHeaders(); - while (headersEnum.hasMoreElements()) - { - Header hd = headersEnum.nextElement(); - headers = ";;" + hd.getName() + "::" + hd.getValue(); - - } - - logger.trace("Message object in directory polling module. Content-Disposition: " + msg.getContentDisposition() - + "\n Content-Type : " + msg.getContentType() - + "\n HEADERS : " + headers - + "\n Content-Disposition in MSG getData() MIMEPART: " - + msg.getData().getContentType() - +msg.getLogMsgID() ); - } catch (Exception e){} - try - { - msg.setStatus(Message.MSG_STATUS_MSG_SEND); - // Transmit the message - getSession().getProcessor().handle(SenderModule.DO_SEND, msg, options); - } catch (Exception e) - { - msg.setLogMsg("Fatal error sending message: " + org.openas2.logging.Log.getExceptionMsg(e)); - logger.error(msg, e); - AS2Util.cleanupFiles(msg, true); + throw new OpenAS2Exception("Failed to process file:" + file.getAbsolutePath(), e); } } protected abstract Message createMessage(); - public void updateMessage(Message msg, File file) throws OpenAS2Exception - { - MessageParameters params = new MessageParameters(msg); - - // Get the parameter that should provide the link between the polled directory and an AS2 sender and recipient - String defaults = getParameter(PARAM_DEFAULTS, false); - // Link the file to an AS2 sender and recipient via the Message object associated with the file - if (defaults != null) - { - params.setParameters(defaults); - } - - String filename = file.getName(); - String format = getParameter(PARAM_FORMAT, false); - - if (format != null) - { - String delimiters = getParameter(PARAM_DELIMITERS, ".-"); - params.setParameters(format, delimiters, filename); - } - - try - { - byte[] data = IOUtilOld.getFileBytes(file); - String contentType = getParameter(PARAM_MIMETYPE, false); - if (contentType == null) - { - contentType = "application/octet-stream"; - } else - { - try - { - contentType = ParameterParser.parse(contentType, params); - } catch (InvalidParameterException e) - { - msg.setLogMsg("Bad content-type" + contentType); - logger.error(msg, e); - contentType = "application/octet-stream"; - } - } - ByteArrayDataSource byteSource = new ByteArrayDataSource(data, contentType, null); - MimeBodyPart body = new MimeBodyPart(); - body.setDataHandler(new DataHandler(byteSource)); - - - // below statement is not filename related, just want to make it - // consist with the parameter "mimetype="application/EDI-X12"" - // defined in config.xml 2007-06-01 - - body.setHeader("Content-Type", contentType); - - // add below statement will tell the receiver to save the filename - // as the one sent by sender. 2007-06-01 - String sendFileName = getParameter("sendfilename", false); - if (sendFileName != null && sendFileName.equals("true")) - { - String contentDisposition = "Attachment; filename=\"" + msg.getAttribute(FileAttribute.MA_FILENAME) + "\""; - body.setHeader("Content-Disposition", contentDisposition); - msg.setContentDisposition(contentDisposition); - } - - msg.setData(body); - } catch (MessagingException me) - { - throw new WrappedException(me); - } catch (IOException ioe) - { - throw new WrappedException(ioe); - } - - // update the message's partnership with any stored information - getSession().getPartnershipFactory().updatePartnership(msg, true); - msg.updateMessageID(); - /* Not sure it should be set at this level as there is no encoding of the content at this point so make it configurable */ - if (msg.getPartnership().isSetTransferEncodingOnInitialBodyPart()) - { - String contentTxfrEncoding = msg.getPartnership().getAttribute(Partnership.PA_CONTENT_TRANSFER_ENCODING); - if (contentTxfrEncoding == null) - contentTxfrEncoding = Session.DEFAULT_CONTENT_TRANSFER_ENCODING; - try - { - msg.getData().setHeader("Content-Transfer-Encoding", contentTxfrEncoding); - } catch (MessagingException e) - { - logger.error("Failed to set content transfer encoding in created MimeBodyPart: " - + org.openas2.logging.Log.getExceptionMsg(e), e); - } - } - if (logger.isTraceEnabled()) - try - { - logger.trace("MimeBodyPart built in polling module:::: " + MimeUtil.toString(msg.getData(), true) + msg.getLogMsgID()); - } catch (Exception e) - { - e.printStackTrace(); - } - } - public Map getTrackedFiles() { if (trackedFiles == null) diff --git a/Server/src/org/openas2/processor/receiver/MessageBuilderModule.java b/Server/src/org/openas2/processor/receiver/MessageBuilderModule.java new file mode 100644 index 00000000..ac3df175 --- /dev/null +++ b/Server/src/org/openas2/processor/receiver/MessageBuilderModule.java @@ -0,0 +1,312 @@ +package org.openas2.processor.receiver; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Enumeration; +import java.util.Map; +import java.util.StringTokenizer; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.activation.DataHandler; +import javax.mail.Header; +import javax.mail.MessagingException; +import javax.mail.internet.MimeBodyPart; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.openas2.OpenAS2Exception; +import org.openas2.Session; +import org.openas2.WrappedException; +import org.openas2.message.FileAttribute; +import org.openas2.message.InvalidMessageException; +import org.openas2.message.Message; +import org.openas2.params.InvalidParameterException; +import org.openas2.params.MessageParameters; +import org.openas2.params.ParameterParser; +import org.openas2.partner.AS2Partnership; +import org.openas2.partner.Partnership; +import org.openas2.processor.resender.ResenderModule; +import org.openas2.processor.sender.SenderModule; +import org.openas2.util.AS2Util; +import org.openas2.util.IOUtilOld; + + +public abstract class MessageBuilderModule extends BaseReceiverModule { + + public static final String PARAM_ERROR_DIRECTORY = "errordir"; + public static final String PARAM_SENT_DIRECTORY = "sentdir"; + + public static final String PARAM_FORMAT = "format"; + public static final String PARAM_DELIMITERS = "delimiters"; + public static final String PARAM_DEFAULTS = "defaults"; + public static final String PARAM_MIMETYPE = "mimetype"; + public static final String PARAM_RESEND_MAX_RETRIES = "resend_max_retries"; + + private Log logger = LogFactory.getLog(MessageBuilderModule.class.getSimpleName()); + + public void init(Session session, Map options) throws OpenAS2Exception { + super.init(session, options); + } + + protected Message processDocument(InputStream ip, String filename, String fileSrcLocation) throws OpenAS2Exception, FileNotFoundException + { + Message msg = createMessage(); + msg.setAttribute(FileAttribute.MA_FILEPATH, fileSrcLocation); + msg.setAttribute(FileAttribute.MA_FILENAME, filename); + String pendingFile = AS2Util.buildPendingFileName(msg, getSession().getProcessor(), "pendingmdn"); + msg.setAttribute(FileAttribute.MA_PENDINGFILE, pendingFile); + File doc = new File(pendingFile); + FileOutputStream fo = null; + try + { + fo = new FileOutputStream(doc); + } catch (FileNotFoundException e1) + { + throw new OpenAS2Exception("Could not create file in pending folder: " + pendingFile, e1); + } + try + { + IOUtilOld.copy(ip, fo); + } catch (IOException e1) + { + fo = null; + throw new OpenAS2Exception("Could not write file to pending folder: " + pendingFile, e1); + } + try + { + ip.close(); + } catch (IOException e1) + { + // TODO Auto-generated catch block + e1.printStackTrace(); + } + ip = null; + try + { + fo.close(); + } catch (IOException e1) + { + // TODO Auto-generated catch block + e1.printStackTrace(); + } + fo = null; + msg.setAttribute(FileAttribute.MA_ERROR_DIR, getParameter(PARAM_ERROR_DIRECTORY, true)); + if (getParameter(PARAM_SENT_DIRECTORY, false) != null) + msg.setAttribute(FileAttribute.MA_SENT_DIR, getParameter(PARAM_SENT_DIRECTORY, false)); + + FileInputStream fis = new FileInputStream(doc); + try + { + updateMessage(msg, fis, filename); + } + finally + { + try + { + fis.close(); + } catch (IOException e) + { + // TODO Auto-generated catch block + e.printStackTrace(); + } + fis = null; + doc = null; + } + String customHeaderList = msg.getPartnership().getAttribute(AS2Partnership.PA_CUSTOM_MIME_HEADER_NAMES_FROM_FILENAME); + if (customHeaderList != null && customHeaderList.length() > 0) + { + String[] headerNames = customHeaderList.split("\\s*,\\s*"); + String delimiters = msg.getPartnership().getAttribute(AS2Partnership.PA_CUSTOM_MIME_HEADER_NAME_DELIMITERS_IN_FILENAME); + if (logger.isTraceEnabled()) logger.trace("Adding custom headers based on message file name to custom headers map. Delimeters: " + delimiters + msg.getLogMsgID()); + if (delimiters != null) + { + // Extract the values based on delimiters which means the mime header names are prefixed with a target + StringTokenizer valueTokens = new StringTokenizer(filename, delimiters, false); + if (valueTokens != null && valueTokens.countTokens()!= headerNames.length) + { + msg.setLogMsg("Filename does not match headers list: Headers=" + customHeaderList + " ::: Filename=" + filename + " ::: String delimiters=" + delimiters); + logger.error(msg); + throw new OpenAS2Exception("Invalid filename for extracting custom headers: " + filename); + } + for (int i = 0; i < headerNames.length; i++) + { + String[] header = headerNames[i].split("\\."); + if (logger.isTraceEnabled()) logger.trace("Adding custom header: " + headerNames[i] + + " :::Split count:" + header.length + msg.getLogMsgID()); + if (header.length != 2) throw new OpenAS2Exception("Invalid custom header: " + headerNames[i] + " :: The header name must be prefixed by \"header.\" or \"junk.\" etc"); + if (!"header".equalsIgnoreCase(header[0])) continue; // Ignore anything not prefixed by "header" + msg.addCustomOuterMimeHeader(header[1], valueTokens.nextToken()); + } + } + else + { + String regex = msg.getPartnership().getAttribute(AS2Partnership.PA_CUSTOM_MIME_HEADER_NAMES_REGEX_ON_FILENAME); + if (regex != null) + { + Pattern p = Pattern.compile(regex); + Matcher m = p.matcher(filename); + if (!m.find() || m.groupCount() != headerNames.length) + { + msg.setLogMsg("Could not match filename to headers required using the regex provided: " + + (m.find()?("Mismatch in header count to extracted group count: " + + headerNames.length + "::" + m.groupCount()):"No match found in filename")); + logger.error(msg); + throw new OpenAS2Exception("Invalid filename for extracting custom headers: " + filename); + } + for (int i = 0; i < headerNames.length; i++) + { + msg.addCustomOuterMimeHeader(headerNames[i], m.group(i+1)); + } + } + } + } + if (logger.isInfoEnabled()) + logger.info("file assigned to message " + fileSrcLocation + msg.getLogMsgID()); + + if (msg.getData() == null) + { + throw new InvalidMessageException("Failed to retrieve data for outbound AS2 message for file: " + fileSrcLocation); + } + if (logger.isTraceEnabled()) + logger.trace("PARTNERSHIP parms: " + msg.getPartnership().getAttributes() + msg.getLogMsgID()); + // Retry count - first try on partnership then directory polling module + String maxRetryCnt = msg.getPartnership().getAttribute(AS2Partnership.PA_RESEND_MAX_RETRIES); + if (maxRetryCnt == null || maxRetryCnt.length() < 1) + { + maxRetryCnt = getSession().getProcessor().getParameters().get(PARAM_RESEND_MAX_RETRIES); + } + if (logger.isTraceEnabled()) + logger.trace("RESEND COUNT extracted from config: " + maxRetryCnt + msg.getLogMsgID()); + Map options = msg.getOptions(); + options.put(ResenderModule.OPTION_RETRIES, maxRetryCnt); + + if (logger.isTraceEnabled()) + try + { + String headers = ""; + Enumeration
headersEnum = msg.getData().getAllHeaders(); + while (headersEnum.hasMoreElements()) + { + Header hd = headersEnum.nextElement(); + headers = ";;" + hd.getName() + "::" + hd.getValue(); + + } + + logger.trace("Message object in directory polling module. Content-Disposition: " + msg.getContentDisposition() + + "\n Content-Type : " + msg.getContentType() + + "\n HEADERS : " + headers + + "\n Content-Disposition in MSG getData() MIMEPART: " + + msg.getData().getContentType() + +msg.getLogMsgID() ); + } catch (Exception e){} + try + { + msg.setStatus(Message.MSG_STATUS_MSG_SEND); + // Transmit the message + getSession().getProcessor().handle(SenderModule.DO_SEND, msg, options); + } catch (Exception e) + { + msg.setLogMsg("Fatal error sending message: " + org.openas2.logging.Log.getExceptionMsg(e)); + logger.error(msg, e); + AS2Util.cleanupFiles(msg, true); + } + return msg; + + } + + protected abstract Message createMessage(); + + public void updateMessage(Message msg, InputStream ip, String filename) throws OpenAS2Exception + { + MessageParameters params = new MessageParameters(msg); + + // Get the parameter that should provide the link between the polled directory and an AS2 sender and recipient + String defaults = getParameter(PARAM_DEFAULTS, false); + // Link the file to an AS2 sender and recipient via the Message object associated with the file + if (defaults != null) + { + params.setParameters(defaults); + } + + String format = getParameter(PARAM_FORMAT, false); + + if (format != null) + { + String delimiters = getParameter(PARAM_DELIMITERS, ".-"); + params.setParameters(format, delimiters, filename); + } + + // Should have sender/receiver now so update the message's partnership with any stored information based on the identified partner IDs + getSession().getPartnershipFactory().updatePartnership(msg, true); + msg.updateMessageID(); + + try + { + //byte[] data = IOUtilOld.getFileBytes(file); + String contentType = getParameter(PARAM_MIMETYPE, false); + if (contentType == null) + { + contentType = "application/octet-stream"; + } else + { + try + { + contentType = ParameterParser.parse(contentType, params); + } catch (InvalidParameterException e) + { + throw new OpenAS2Exception("Bad content-type" + contentType, e); + } + } + javax.mail.util.ByteArrayDataSource byteSource = new javax.mail.util.ByteArrayDataSource(ip, contentType); + MimeBodyPart body = new MimeBodyPart(); + body.setDataHandler(new DataHandler(byteSource)); + + + // below statement is not filename related, just want to make it + // consist with the parameter "mimetype="application/EDI-X12"" + // defined in config.xml 2007-06-01 + + body.setHeader("Content-Type", contentType); + + // add below statement will tell the receiver to save the filename + // as the one sent by sender. 2007-06-01 + String sendFileName = getParameter("sendfilename", false); + if (sendFileName != null && sendFileName.equals("true")) + { + String contentDisposition = "Attachment; filename=\"" + msg.getAttribute(FileAttribute.MA_FILENAME) + "\""; + body.setHeader("Content-Disposition", contentDisposition); + msg.setContentDisposition(contentDisposition); + } + + msg.setData(body); + } catch (MessagingException me) + { + throw new WrappedException(me); + } catch (IOException ioe) + { + throw new WrappedException(ioe); + } + + /* Not sure it should be set at this level as there is no encoding of the content at this point so make it configurable */ + if (msg.getPartnership().isSetTransferEncodingOnInitialBodyPart()) + { + String contentTxfrEncoding = msg.getPartnership().getAttribute(Partnership.PA_CONTENT_TRANSFER_ENCODING); + if (contentTxfrEncoding == null) + contentTxfrEncoding = Session.DEFAULT_CONTENT_TRANSFER_ENCODING; + try + { + msg.getData().setHeader("Content-Transfer-Encoding", contentTxfrEncoding); + } catch (MessagingException e) + { + throw new OpenAS2Exception("Failed to set content transfer encoding in created MimeBodyPart: " + + org.openas2.logging.Log.getExceptionMsg(e), e); + } + } + } + +} diff --git a/Server/src/org/openas2/processor/receiver/NetModule.java b/Server/src/org/openas2/processor/receiver/NetModule.java index cf80f01b..e988d390 100644 --- a/Server/src/org/openas2/processor/receiver/NetModule.java +++ b/Server/src/org/openas2/processor/receiver/NetModule.java @@ -312,7 +312,7 @@ public void run() { } } - System.out.println("exited"); + System.out.println("NetModule terminated."); } diff --git a/Server/src/org/openas2/processor/receiver/PollingModule.java b/Server/src/org/openas2/processor/receiver/PollingModule.java index 8b080347..35b64ed0 100644 --- a/Server/src/org/openas2/processor/receiver/PollingModule.java +++ b/Server/src/org/openas2/processor/receiver/PollingModule.java @@ -10,7 +10,7 @@ import org.openas2.params.InvalidParameterException; -public abstract class PollingModule extends BaseReceiverModule { +public abstract class PollingModule extends MessageBuilderModule { public static final String PARAM_POLLING_INTERVAL = "interval"; private Timer timer; private boolean busy; diff --git a/Server/src/org/openas2/processor/sender/AS2SenderModule.java b/Server/src/org/openas2/processor/sender/AS2SenderModule.java index 3d7c6761..6fc1b177 100644 --- a/Server/src/org/openas2/processor/sender/AS2SenderModule.java +++ b/Server/src/org/openas2/processor/sender/AS2SenderModule.java @@ -1,7 +1,6 @@ package org.openas2.processor.sender; import java.io.ByteArrayOutputStream; -import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; @@ -64,7 +63,8 @@ public void handle(String action, Message msg, Map options) thro if (logger.isInfoEnabled()) logger.info("message sender invoked" + msg.getLogMsgID()); boolean isResend = Message.MSG_STATUS_MSG_RESEND.equals(msg.getStatus()); - + options.put("DIRECTION", "SEND"); + options.put("IS_RESEND", isResend?"Y":"N"); if (!(msg instanceof AS2Message)) { throw new OpenAS2Exception("Can't send non-AS2 message"); @@ -106,6 +106,11 @@ public void handle(String action, Message msg, Map options) thro storePendingInfo((AS2Message) msg, isResend); } catch (Exception e) { + msg.setLogMsg(org.openas2.logging.Log.getExceptionMsg(e)); + logger.error(msg, e); + // Log significant msg state + msg.setOption("STATE", Message.MSG_STATE_SEND_EXCEPTION); + msg.trackMsgState(getSession()); throw new OpenAS2Exception("Error setting up message for sending.", e); } if (logger.isTraceEnabled()) @@ -135,20 +140,30 @@ public void handle(String action, Message msg, Map options) thro // Create the HTTP connection and set up headers String url = msg.getPartnership().getAttribute(AS2Partnership.PA_AS2_URL); conn = getConnection(url, true, true, false, "POST"); + // Log significant msg state + msg.setOption("STATE", Message.MSG_STATE_SEND_START); + msg.trackMsgState(getSession()); sendMessage(conn, msg, securedData, retries); } catch (HttpResponseException hre) { // Will have been logged so just resend resend(msg, hre, retries); + // Log significant msg state + msg.setOption("STATE", Message.MSG_STATE_SEND_EXCEPTION); + msg.trackMsgState(getSession()); return; } catch (Exception e) { msg.setLogMsg("Unexpected error sending file: " + org.openas2.logging.Log.getExceptionMsg(e)); logger.error(msg, e); resend(msg, new OpenAS2Exception(org.openas2.logging.Log.getExceptionMsg(e)), retries); + // Log significant msg state + msg.setOption("STATE", Message.MSG_STATE_SEND_EXCEPTION); + msg.trackMsgState(getSession()); return; } + if (logger.isTraceEnabled()) logger.trace("Message sent. Checking if MDN will be returned..." + msg.getLogMsgID()); // Receive an MDN if (msg.isConfiguredForMDN()) { @@ -156,8 +171,9 @@ public void handle(String action, Message msg, Map options) thro // Check if the AsyncMDN is required if (msg.getPartnership().getAttribute(AS2Partnership.PA_AS2_RECEIPT_OPTION) == null) { + if (logger.isTraceEnabled()) logger.trace("Waiting for synchronous MDN response..." + msg.getLogMsgID()); // Create a MessageMDN and copy HTTP headers - MessageMDN mdn = new AS2MessageMDN((AS2Message) msg); + MessageMDN mdn = new AS2MessageMDN((AS2Message) msg, false); copyHttpHeaders(conn, mdn.getHeaders()); // Receive the MDN data @@ -171,17 +187,21 @@ public void handle(String action, Message msg, Map options) thro + org.openas2.logging.Log.getExceptionMsg(e1)); logger.error(msg, e1); resend(msg, new OpenAS2Exception(org.openas2.logging.Log.getExceptionMsg(e1)), retries); + // Log significant msg state + msg.setOption("STATE", Message.MSG_STATE_MDN_RECEIVING_EXCEPTION); + msg.trackMsgState(getSession()); } ByteArrayOutputStream mdnStream = new ByteArrayOutputStream(); try { + String cl = mdn.getHeader("Content-Length"); // Retrieve the message content - if (mdn.getHeader("Content-Length") != null) + if (cl != null) { try { - int contentSize = Integer.parseInt(mdn.getHeader("Content-Length")); + int contentSize = Integer.parseInt(cl); IOUtilOld.copy(connIn, mdnStream, contentSize); } catch (NumberFormatException nfe) @@ -194,8 +214,14 @@ public void handle(String action, Message msg, Map options) thro } } catch (IOException ioe) { + msg.setLogMsg("IO exception receiving MDN: " + + org.openas2.logging.Log.getExceptionMsg(ioe)); + logger.error(msg, ioe); // What to do??? resend(msg, new OpenAS2Exception(org.openas2.logging.Log.getExceptionMsg(ioe)), retries); + // Log significant msg state + msg.setOption("STATE", Message.MSG_STATE_MDN_RECEIVING_EXCEPTION); + msg.trackMsgState(getSession()); } finally { try @@ -207,10 +233,14 @@ public void handle(String action, Message msg, Map options) thro } } + if (logger.isTraceEnabled()) logger.trace("Synchronous MDN received. Start processing..." + msg.getLogMsgID()); msg.setStatus(Message.MSG_STATUS_MDN_PROCESS_INIT); try { AS2Util.processMDN((AS2Message) msg, mdnStream.toByteArray(), null, false, getSession(), this); + // Log significant msg state + msg.setOption("STATE", Message.MSG_STATE_MSG_SENT_MDN_RECEIVED_OK); + msg.trackMsgState(getSession()); } catch (Exception e) { if (Message.MSG_STATUS_MDN_PROCESS_INIT.equals(msg.getStatus()) @@ -222,7 +252,7 @@ public void handle(String action, Message msg, Map options) thro * state so not sure what the best course of action * is apart from do nothing */ - msg.setLogMsg("Unhandled error condition receiving asynchronous MDN. Message and asociated files cleanup will be attempted but may be in an unknown state."); + msg.setLogMsg("Unhandled error condition receiving synchronous MDN. Message and asociated files cleanup will be attempted but may be in an unknown state."); logger.error(msg, e); } /* @@ -233,10 +263,13 @@ public void handle(String action, Message msg, Map options) thro else { // Must have received MDN successfully - msg.setLogMsg("Exception receiving asynchronous MDN. Message and asociated files cleanup will be attempted but may be in an unknown state."); + msg.setLogMsg("Exception receiving synchronous MDN. Message and asociated files cleanup will be attempted but may be in an unknown state."); logger.error(msg, e); } + // Log significant msg state + msg.setOption("STATE", Message.MSG_STATE_SEND_FAIL); + msg.trackMsgState(getSession()); AS2Util.cleanupFiles(msg, true); } } @@ -343,7 +376,7 @@ protected MimeBodyPart secure(Message msg) throws Exception */ Partnership partnership = msg.getPartnership(); - String contentTxfrEncoding = msg.getPartnership().getAttribute(Partnership.PA_CONTENT_TRANSFER_ENCODING); + String contentTxfrEncoding = partnership.getAttribute(Partnership.PA_CONTENT_TRANSFER_ENCODING); if (contentTxfrEncoding == null) contentTxfrEncoding = Session.DEFAULT_CONTENT_TRANSFER_ENCODING; @@ -447,13 +480,20 @@ protected MimeBodyPart secure(Message msg) throws Exception logger.debug("encrypted data" + msg.getLogMsgID()); } + String t = dataBP.getEncoding(); + if ((t == null || t.length() < 1) && "true".equalsIgnoreCase(partnership.getAttribute(Partnership.PA_SET_CONTENT_TRANSFER_ENCODING_OMBP))) + { + dataBP.setHeader("Content-Transfer-Encoding", contentTxfrEncoding); + } return dataBP; } protected void addCustomOuterMimeHeaders(Message msg, MimeBodyPart dataBP) throws MessagingException { if (logger.isTraceEnabled()) logger.trace("Adding custom headers to outer MBP...." + msg.getLogMsgID()); - for (Map.Entry entry : msg.getCustomOuterMimeHeaders().entrySet()) + Map hdrs = msg.getCustomOuterMimeHeaders(); + if (hdrs == null) return; + for (Map.Entry entry :hdrs.entrySet()) { dataBP.addHeader(entry.getKey(), entry.getValue()); if (logger.isTraceEnabled()) @@ -484,7 +524,16 @@ protected void updateHttpHeaders(HttpURLConnection conn, Message msg, MimeBodyPa // AS2 V1.2 additionally supports EDIINT-Features // conn.setRequestProperty("EDIINT-Features", // "CEM,multiple-attachments"); // TODO (possibly implement???) - conn.setRequestProperty("Content-Transfer-Encoding", msg.getHeader("Content-Transfer-Encoding")); + String cte = null; + try + { + cte = securedData.getEncoding(); + } catch (MessagingException e1) + { + e1.printStackTrace(); + } + if (cte == null) cte = Session.DEFAULT_CONTENT_TRANSFER_ENCODING; + conn.setRequestProperty("Content-Transfer-Encoding", cte); conn.setRequestProperty("Recipient-Address", partnership.getAttribute(AS2Partnership.PA_AS2_URL)); conn.setRequestProperty("AS2-To", partnership.getReceiverID(AS2Partnership.PID_AS2)); conn.setRequestProperty("AS2-From", partnership.getSenderID(AS2Partnership.PID_AS2)); @@ -549,14 +598,11 @@ protected void updateHttpHeaders(HttpURLConnection conn, Message msg, MimeBodyPa protected void storePendingInfo(AS2Message msg, boolean isResend) throws Exception { ObjectOutputStream oos = null; - File pfo = null; - File pfi = null; try { String pendingInfoFile = AS2Util.buildPendingFileName(msg, getSession().getProcessor(), "pendingmdninfo"); - String pendingFile = isResend ? msg.getAttribute(FileAttribute.MA_PENDINGFILE) : AS2Util - .buildPendingFileName(msg, getSession().getProcessor(), "pendingmdn"); + String pendingFile = msg.getAttribute(FileAttribute.MA_PENDINGFILE); msg.setAttribute(FileAttribute.MA_PENDINGFILE, pendingFile); if (!isResend) { @@ -590,14 +636,6 @@ protected void storePendingInfo(AS2Message msg, boolean isResend) throws Excepti + msg.getAttribute(FileAttribute.MA_ERROR_DIR) + "\n Sent directory: " + msg.getAttribute(FileAttribute.MA_SENT_DIR) + msg.getLogMsgID()); - if (Message.MSG_STATUS_MSG_SEND.equals(msg.getStatus())) - { - // this is first attempt to send so move from polling directory - // to pending directory - pfi = new File(msg.getAttribute(FileAttribute.MA_FILEPATH)); - pfo = new File(pendingFile); - IOUtilOld.moveFile(pfi, pfo, true, false); - } msg.setAttribute(FileAttribute.MA_STATUS, FileAttribute.MA_PENDING); } catch (Exception e) @@ -614,10 +652,6 @@ protected void storePendingInfo(AS2Message msg, boolean isResend) throws Excepti } catch (IOException e) { } - if (pfi != null) - pfi = null; - if (pfo != null) - pfo = null; } } diff --git a/Server/src/org/openas2/processor/sender/AsynchMDNSenderModule.java b/Server/src/org/openas2/processor/sender/AsynchMDNSenderModule.java index 4aef2ce1..2ec8cd93 100644 --- a/Server/src/org/openas2/processor/sender/AsynchMDNSenderModule.java +++ b/Server/src/org/openas2/processor/sender/AsynchMDNSenderModule.java @@ -43,11 +43,14 @@ public void handle(String action, Message msg, Map options) throws OpenAS2Exception { if (logger.isDebugEnabled()) logger.debug("ASYNC MDN send started..."); + if (options == null) options = new HashMap(); + options.put("DIRECTION", "RECEIVE"); sendAsyncMDN((AS2Message) msg, options); } protected void updateHttpHeaders(HttpURLConnection conn, Message msg) { + MessageMDN mdn = msg.getMDN(); conn.setRequestProperty("Connection", "close, TE"); conn.setRequestProperty("User-Agent", Session.TITLE + " (AsynchMDNSender)"); @@ -61,10 +64,10 @@ protected void updateHttpHeaders(HttpURLConnection conn, Message msg) { conn.setRequestProperty("AS2-Version", "1.1"); conn.setRequestProperty("Recipient-Address", msg.getHeader("Recipient-Address")); - conn.setRequestProperty("AS2-To", msg.getHeader("AS2-To")); - conn.setRequestProperty("AS2-From", msg.getHeader("AS2-From")); + conn.setRequestProperty("AS2-To", mdn.getHeader("AS2-To")); + conn.setRequestProperty("AS2-From", mdn.getHeader("AS2-From")); conn.setRequestProperty("Subject", msg.getHeader("Subject")); - conn.setRequestProperty("From", msg.getHeader("From")); + conn.setRequestProperty("From", mdn.getHeader("From")); } @@ -144,6 +147,9 @@ private void sendAsyncMDN(AS2Message msg, Map options) // log & store mdn into backup folder. getSession().getProcessor().handle(StorageModule.DO_STOREMDN, msg, null); + // Log significant msg state + msg.setOption("STATE", Message.MSG_STATE_MSG_RXD_MDN_SENT_OK); + msg.trackMsgState(getSession()); } finally { conn.disconnect(); @@ -154,6 +160,9 @@ private void sendAsyncMDN(AS2Message msg, Map options) logger.warn("HTTP exception sending ASYNC MDN: " + org.openas2.logging.Log.getExceptionMsg(hre) + msg.getLogMsgID(), hre); hre.terminate(); resend(msg, hre); + // Log significant msg state + msg.setOption("STATE", Message.MSG_STATE_MDN_SENDING_EXCEPTION); + msg.trackMsgState(getSession()); } catch (IOException ioe) { logger.warn("IO exception sending ASYNC MDN: " + org.openas2.logging.Log.getExceptionMsg(ioe) + msg.getLogMsgID(), ioe); @@ -163,9 +172,17 @@ private void sendAsyncMDN(AS2Message msg, Map options) wioe.terminate(); resend(msg, wioe); + // Log significant msg state + msg.setOption("STATE", Message.MSG_STATE_MDN_SENDING_EXCEPTION); + msg.trackMsgState(getSession()); } catch (Exception e) { logger.warn("Unexpected exception sending ASYNC MDN: " + org.openas2.logging.Log.getExceptionMsg(e) + msg.getLogMsgID(), e); // Propagate error if it can't be handled by a resend + // log & store mdn into backup folder. + getSession().getProcessor().handle(StorageModule.DO_STOREMDN, msg, null); + // Log significant msg state + msg.setOption("STATE", Message.MSG_STATE_MDN_SENDING_EXCEPTION); + msg.trackMsgState(getSession()); throw new WrappedException(e); } } diff --git a/Server/src/org/openas2/processor/sender/HttpSenderModule.java b/Server/src/org/openas2/processor/sender/HttpSenderModule.java index 7f625bdc..f88d7424 100644 --- a/Server/src/org/openas2/processor/sender/HttpSenderModule.java +++ b/Server/src/org/openas2/processor/sender/HttpSenderModule.java @@ -77,8 +77,8 @@ public HttpURLConnection getConnection(String url, boolean output, boolean input connS.setSSLSocketFactory(context.getSocketFactory()); } catch (Exception e) { - logger.error("URL connection failed connecting to to : " + url, e); - throw new OpenAS2Exception("Error self signed certificate management", e); + logger.error("URL connection failed connecting to : " + url, e); + throw new OpenAS2Exception("Error in self signed certificate management", e); } } conn = connS; diff --git a/Server/src/org/openas2/util/AS2Util.java b/Server/src/org/openas2/util/AS2Util.java index e89a5e4a..76989e14 100644 --- a/Server/src/org/openas2/util/AS2Util.java +++ b/Server/src/org/openas2/util/AS2Util.java @@ -12,6 +12,7 @@ import java.util.Enumeration; import java.util.HashMap; import java.util.Map; +import java.util.UUID; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -24,7 +25,6 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.openas2.ComponentNotFoundException; import org.openas2.DispositionException; import org.openas2.OpenAS2Exception; import org.openas2.Session; @@ -64,16 +64,17 @@ public static ICryptoHelper getCryptoHelper() throws Exception { public static MessageMDN createMDN(Session session, AS2Message msg, String mic, DispositionType disposition, String text) throws Exception { - AS2MessageMDN mdn = new AS2MessageMDN(msg); + + AS2MessageMDN mdn = new AS2MessageMDN(msg, true); + mdn.setHeader("AS2-Version", "1.1"); // RFC2822 format: Wed, 04 Mar 2009 10:59:17 +0100 mdn.setHeader("Date", DateUtil.formatDate("EEE, dd MMM yyyy HH:mm:ss Z")); mdn.setHeader("Server", Session.TITLE); mdn.setHeader("Mime-Version", "1.0"); - mdn.setHeader("AS2-To", msg.getPartnership().getSenderID(AS2Partnership.PID_AS2)); - mdn.setHeader("AS2-From", msg.getPartnership().getReceiverID(AS2Partnership.PID_AS2)); - + // get the MDN partnership info + // not sure that it should be this way since the config should relfect the inbound original message settings but ... mdn.getPartnership().setSenderID(AS2Partnership.PID_AS2, mdn.getHeader("AS2-From")); mdn.getPartnership().setReceiverID(AS2Partnership.PID_AS2, mdn.getHeader("AS2-To")); session.getPartnershipFactory().updatePartnership(mdn, true); @@ -160,10 +161,11 @@ public static void createMDNData(Session session, MessageMDN mdn, String micAlg, CertificateFactory certFx = session.getCertificateFactory(); try { - X509Certificate senderCert = certFx.getCertificate(mdn, - Partnership.PTYPE_SENDER); + // The receiver of the original message is the sender of the MDN.... + X509Certificate senderCert = certFx.getCertificate(mdn, + Partnership.PTYPE_RECEIVER); PrivateKey senderKey = certFx.getPrivateKey(mdn, senderCert); - Partnership p = mdn.getMessage().getPartnership(); + Partnership p = mdn.getPartnership(); String contentTxfrEncoding = p.getAttribute(Partnership.PA_CONTENT_TRANSFER_ENCODING); boolean isRemoveCmsAlgorithmProtectionAttr = "true".equalsIgnoreCase(p.getAttribute(Partnership.PA_REMOVE_PROTECTION_ATTRIB)); if (contentTxfrEncoding == null) @@ -270,6 +272,7 @@ public static void parseMDN(Message msg, X509Certificate receiver) throws OpenAS e.printStackTrace(); } } + /** * @description Verify disposition sytus is "processed" then check MIC is matched * @param msg - the original message sent to the partner that the MDN relates to @@ -288,6 +291,7 @@ public static boolean checkMDN(AS2Message msg) throws DispositionException, Open try { new DispositionType(disposition).validate(); } catch (DispositionException de) { + if (logger.isWarnEnabled()) logger.warn("Disposition exception on MDN. Disposition: " + disposition + msg.getLogMsgID(), de); // Something wrong detected so flag it for later use dispositionHasWarning = true; de.setText(msg.getMDN().getText()); @@ -539,6 +543,11 @@ public static void processMDN(AS2Message msg, byte[] data, OutputStream out, boo // Create a MessageMDN and copy HTTP headers MessageMDN mdn = msg.getMDN(); + // get the MDN partnership info + mdn.getPartnership().setSenderID(AS2Partnership.PID_AS2, mdn.getHeader("AS2-From")); + mdn.getPartnership().setReceiverID(AS2Partnership.PID_AS2, mdn.getHeader("AS2-To")); + session.getPartnershipFactory().updatePartnership(mdn, false); + MimeBodyPart part; try { @@ -554,15 +563,11 @@ public static void processMDN(AS2Message msg, byte[] data, OutputStream out, boo throw new OpenAS2Exception("Error receiving MDN. Processing stopped."); } - // get the MDN partnership info - mdn.getPartnership().setSenderID(AS2Partnership.PID_AS2, mdn.getHeader("AS2-From")); - mdn.getPartnership().setReceiverID(AS2Partnership.PID_AS2, mdn.getHeader("AS2-To")); - session.getPartnershipFactory().updatePartnership(mdn, false); - CertificateFactory cFx = session.getCertificateFactory(); - X509Certificate senderCert = cFx.getCertificate(mdn, Partnership.PTYPE_SENDER); + X509Certificate senderCert = cFx.getCertificate(mdn, Partnership.PTYPE_RECEIVER); msg.setStatus(Message.MSG_STATUS_MDN_PARSE); + if (logger.isTraceEnabled()) logger.trace("Parsing MDN: " + mdn.toString() + msg.getLogMsgID()); AS2Util.parseMDN(msg, senderCert); if (isAsyncMDN) @@ -573,6 +578,7 @@ public static void processMDN(AS2Message msg, byte[] data, OutputStream out, boo String retries = (String) msg.getOption(ResenderModule.OPTION_RETRIES); msg.setStatus(Message.MSG_STATUS_MDN_VERIFY); + if (logger.isTraceEnabled()) logger.trace("MDN parsed. Checking MDN report..." + msg.getLogMsgID()); try { AS2Util.checkMDN(msg); @@ -587,16 +593,19 @@ public static void processMDN(AS2Message msg, byte[] data, OutputStream out, boo } catch (DispositionException de) { /* - * Issue with disposition but still sent OK at HTTP level to + * Issue with disposition but still send OK at HTTP level to * indicate message received */ if (isAsyncMDN) HTTPUtil.sendHTTPResponse(out, HttpURLConnection.HTTP_OK, false); // If a disposition exception occurs then there must have been an // error response in the disposition + if (logger.isErrorEnabled()) logger.error("Disposition exception processing MDN ..." + msg.getLogMsgID(), de); // Hmmmm... Error may require manual intervention but keep // trying.... possibly change retry count to 1 or just fail???? AS2Util.resend(session.getProcessor(), sourceClass, SenderModule.DO_SEND, msg, de, retries, true); + msg.setOption("STATE", Message.MSG_STATE_MSG_SENT_MDN_RECEIVED_ERROR); + msg.trackMsgState(session); return; } catch (OpenAS2Exception oae) { @@ -610,9 +619,13 @@ public static void processMDN(AS2Message msg, byte[] data, OutputStream out, boo oae2.addSource(OpenAS2Exception.SOURCE_MESSAGE, msg); oae2.terminate(); AS2Util.resend(session.getProcessor(), sourceClass, SenderModule.DO_SEND, msg, oae2, retries, true); + msg.setOption("STATE", Message.MSG_STATE_MSG_SENT_MDN_RECEIVED_ERROR); + msg.trackMsgState(session); return; } + msg.setOption("STATE", Message.MSG_STATE_MSG_SENT_MDN_RECEIVED_OK); + msg.trackMsgState(session); session.getProcessor().handle(StorageModule.DO_STOREMDN, msg, null); msg.setStatus(Message.MSG_STATUS_MSG_CLEANUP); // To support extended reporting via logging log info passing Message object @@ -624,15 +637,19 @@ public static void processMDN(AS2Message msg, byte[] data, OutputStream out, boo } /* - * @description This method buiold the name of the pending info file + * @description This method builds the name of the pending info file * @param msg - the Message object containing enough information to build the pending info file name */ public static String buildPendingFileName(Message msg, Processor processor, String directoryIdentifier) throws OpenAS2Exception { String msgId = msg.getMessageID(); // this includes enclosing angled brackets <> - return ((String) processor.getParameters().get(directoryIdentifier) - + "/" - + msgId.substring(1, msgId.length() - 1)); + String dir = (String) processor.getParameters().get(directoryIdentifier); + if (msgId == null || msgId.length() < 1) + { + // No ID set yet so generate a random number for uniqueness + msgId = msg.getAttribute(FileAttribute.MA_FILENAME) + "." + UUID.randomUUID(); + } + return (dir + "/" + msgId.substring(1, msgId.length() - 1)); } /* * @description This method retrieves the information from the pending information file written by @@ -642,18 +659,6 @@ public static String buildPendingFileName(Message msg, Processor processor, Stri public static void getMetaData(AS2Message msg, Session session) throws OpenAS2Exception { Log logger = LogFactory.getLog(AS2Util.class.getSimpleName()); - MessageMDN mdn = msg.getMDN(); - // in order to name & save the mdn with the original AS2-From + AS2-To + Message id., - // the 3 msg attributes have to be reset before calling MDNFileModule - msg.getPartnership().setReceiverID(AS2Partnership.PID_AS2, mdn.getHeader("AS2-From")); - msg.getPartnership().setSenderID(AS2Partnership.PID_AS2, mdn.getHeader("AS2-To")); - try - { - session.getPartnershipFactory().updatePartnership(msg, false); - } catch (ComponentNotFoundException e) - { - throw new OpenAS2Exception("Error updating partnership: " + org.openas2.logging.Log.getExceptionMsg(e), e); - } // use original message ID to open the pending information file from pendinginfo folder. String originalMsgId = msg.getMDN().getAttribute(AS2MessageMDN.MDNA_ORIG_MESSAGEID); // TODO: CB: Think we are supposed to verify the MDN received msg Id with what we sent diff --git a/Server/src/org/openas2/util/DateUtil.java b/Server/src/org/openas2/util/DateUtil.java index 2f3ad839..5ad41c94 100644 --- a/Server/src/org/openas2/util/DateUtil.java +++ b/Server/src/org/openas2/util/DateUtil.java @@ -32,4 +32,23 @@ private static SimpleDateFormat getDateFormat(String format) { } return df; } + + public static synchronized String getSqlTimestamp() + { + return getSqlTimestamp(new Date()); + } + + // ========================================================= + /** + * @return + */ + // ========================================================= + public static synchronized String getSqlTimestamp(Date date) + { + if (date == null) + return ""; + return formatDate(Properties.getProperty("sql_timestamp_format", "yyyy-MM-dd HH:mm:ss.SSS"), date); + } + + } diff --git a/Server/src/org/openas2/util/Properties.java b/Server/src/org/openas2/util/Properties.java new file mode 100644 index 00000000..e8da0ba0 --- /dev/null +++ b/Server/src/org/openas2/util/Properties.java @@ -0,0 +1,32 @@ +package org.openas2.util; + +import java.util.HashMap; +import java.util.Map; + +public class Properties +{ + private static Map _properties = new HashMap(); + + public static void setProperties(Map map) + { + _properties = map; + } + + public static Map getProperties() + { + return _properties; + } + + public static String getProperty(String key, String fallback) + { + String val = _properties.get(key); + if (val == null) + { + _properties.put(key, fallback); + return fallback; + } + return val; + } + + +} diff --git a/changes.txt b/changes.txt index ac4a1a01..ea110dda 100644 --- a/changes.txt +++ b/changes.txt @@ -1,3 +1,14 @@ +Version 2.1.0 - 2016-10-11 + 1. Add message tracking module to allow easy identification of sent and received messages based on state + - persists state change tracking of messages to H2 database + - allow user-plugin of custom tracking module(s) + 2. Enhanced documentation around certificate management + 3. Added properties element to config to allow easy custom config property access from java modules and helper classes + 4. Added support for parsing file name into partnership attributes using regular expressions + 5. Added script to support launching OpenAS2 as a daemon using the init.d paradigm + 6. Added system parameter to startup scripts to allow restricted HTTP headers that cause problems for some AS2 implementations + + Version 2.0.0 - 2016-05-31 1. Add support for custom HTTP headers - configurable static headers as name/value pairs in the partnership @@ -8,6 +19,7 @@ Version 2.0.0 - 2016-05-31 5. Support AES128, AES192, AES256 ciphers 6. Support disabling the CMS algorithm protection OID for older AS2 systems that do not support it 7. Added "Troubleshooting.." section to documentation + 8. Authenticated SMTP for email logging Version 1.3.6 - 2015-11-23 1. Fix handling creating a unique file name for storing message info for ASYNC MDN diff --git a/docs/OpenAS2HowTo.odt b/docs/OpenAS2HowTo.odt index 92c268b6..1252f557 100644 Binary files a/docs/OpenAS2HowTo.odt and b/docs/OpenAS2HowTo.odt differ diff --git a/docs/OpenAS2HowTo.pdf b/docs/OpenAS2HowTo.pdf index f2f414a6..4668353b 100644 Binary files a/docs/OpenAS2HowTo.pdf and b/docs/OpenAS2HowTo.pdf differ