From 53dfa7e61494fc38de8b527a91dfdb6051260e2a Mon Sep 17 00:00:00 2001 From: Robert Greig Date: Tue, 20 Feb 2007 16:51:32 +0000 Subject: (Path submitted by Rupert Smith) Qpid-338. Custom SASL implementation for Java 1.4 retrotranslation of the Java client. git-svn-id: https://svn.apache.org/repos/asf/incubator/qpid/trunk/qpid@509642 13f79535-47bb-0310-9956-ffa450edef68 --- java/client-java14/README.txt | 33 ++ java/client-java14/client-java14.iml | 469 +++++++++++++++++++++ java/client-java14/etc/sasl.properties | 20 + java/client-java14/pom.xml | 152 +++++++ .../src/main/assembly/client-java14-bin.xml | 69 +++ .../org/apache/qpid/sasl/ClientFactoryImpl.java | 343 +++++++++++++++ .../java/org/apache/qpid/sasl/CramMD5Client.java | 349 +++++++++++++++ .../java/org/apache/qpid/sasl/PlainClient.java | 277 ++++++++++++ .../main/java/org/apache/qpid/sasl/Provider.java | 61 +++ .../test/integration/client/ConnectionTest.java | 66 +++ java/client/distribution/pom.xml | 15 - java/client/pom.xml | 107 ++--- .../handler/ConnectionStartMethodHandler.java | 71 ++-- .../client/security/CallbackHandlerRegistry.java | 103 ++++- .../qpid/client/security/DynamicSaslRegistrar.java | 99 ++++- .../security/DynamicSaslRegistrar.properties | 4 +- .../apache/qpid/client/security/JCAProvider.java | 36 +- java/common/pom.xml | 74 ++-- .../main/java/org/apache/qpid/util/FileUtils.java | 161 +++++++ .../org/apache/qpid/util/PrettyPrintingUtils.java | 73 ++++ java/pom.xml | 188 +++++---- 21 files changed, 2514 insertions(+), 256 deletions(-) create mode 100644 java/client-java14/README.txt create mode 100644 java/client-java14/client-java14.iml create mode 100644 java/client-java14/etc/sasl.properties create mode 100644 java/client-java14/pom.xml create mode 100644 java/client-java14/src/main/assembly/client-java14-bin.xml create mode 100644 java/client-java14/src/main/java/org/apache/qpid/sasl/ClientFactoryImpl.java create mode 100644 java/client-java14/src/main/java/org/apache/qpid/sasl/CramMD5Client.java create mode 100644 java/client-java14/src/main/java/org/apache/qpid/sasl/PlainClient.java create mode 100644 java/client-java14/src/main/java/org/apache/qpid/sasl/Provider.java create mode 100644 java/client-java14/src/test/java/org/apache/qpid/test/integration/client/ConnectionTest.java create mode 100644 java/common/src/main/java/org/apache/qpid/util/FileUtils.java create mode 100644 java/common/src/main/java/org/apache/qpid/util/PrettyPrintingUtils.java (limited to 'java') diff --git a/java/client-java14/README.txt b/java/client-java14/README.txt new file mode 100644 index 0000000000..66621c7eb2 --- /dev/null +++ b/java/client-java14/README.txt @@ -0,0 +1,33 @@ +An implementation of SASL is provided here, for the PLAIN and CRAM-MD5 mechanisms as well as maven assembly +instructions for producing a backported Java client for Java 1.4. In order to use the custom SASL implementation +on JRE 1.4 the following steps must be taken: + + * Install the SASL JSR-28 API jar. + * Install the qpid-client-java14 jar or set it up as a dynamically registered SASL provider. + * Set up java.security to add the SASL provider to the list of security providers if hte SASL implemenation jar was installed as an extension. + +Installing the SASL JSR-28 API jar. + + Download, http://www.worldspot.com/jsr28/v1.1/download/sasl.jar, and copy it to the JAVA_HOME\lib\ext directory. + +Install or set up the qpid-client-java14 jar. + + Copy the output jar for this project, qpid-client-java14-1.0-incubating-M2-SNAPSHOT.jar, to JAVA_HOME\lib\ext. + + OR + + Create a properties file and register the SASL implementations in the jar so that Qpids dynamic SASL registry can find them. In a properties file + add the lines: + + PLAIN=org.apache.qpid.sasl.ClientFactoryImpl + CRAM-MD5=org.apache.qpid.sasl.ClientFactoryImpl + + Place this somewhere on the classpath and put the qpid-client-java14-1.0-incubating-M2-SNAPSHOT.jar on the classpath too. When starting your application + pass in the system property amq.dynamicsaslregistrar.properties and set it to point to the location of the properties file. + +Set up the SASL provider. + + You only need to do this if the custom SASL jar, qpid-client-java14-1.0-incubating-M2-SNAPSHOT.jar, was copied to JAVA_HOME\lib\ext. + Add the following line to JAVA_HOME\lib\security\java.security file (where n is the providers preference order): + + security.provider.n=org.apache.qpid.sasl.Provider \ No newline at end of file diff --git a/java/client-java14/client-java14.iml b/java/client-java14/client-java14.iml new file mode 100644 index 0000000000..3ae83132fa --- /dev/null +++ b/java/client-java14/client-java14.iml @@ -0,0 +1,469 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/java/client-java14/etc/sasl.properties b/java/client-java14/etc/sasl.properties new file mode 100644 index 0000000000..04519e2a30 --- /dev/null +++ b/java/client-java14/etc/sasl.properties @@ -0,0 +1,20 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +PLAIN=org.apache.qpid.sasl.ClientFactoryImpl +CRAM-MD5=org.apache.qpid.sasl.ClientFactoryImpl diff --git a/java/client-java14/pom.xml b/java/client-java14/pom.xml new file mode 100644 index 0000000000..cf45250f94 --- /dev/null +++ b/java/client-java14/pom.xml @@ -0,0 +1,152 @@ + + + + 4.0.0 + org.apache.qpid + qpid-client-java14 + jar + 1.0-incubating-M2-SNAPSHOT + Qpid Client for Java 1.4 + http://cwiki.apache.org/confluence/display/qpid + + + org.apache.qpid + qpid + 1.0-incubating-M2-SNAPSHOT + + + + .. + 1.4 + ${pom.version} + ${project.build.directory} + ${basedir}/.. + ${basedir}/etc/sasl.properties + path/to/java1.4 + + + + + + + org.apache.qpid + qpid-client + jar + ${pom.version} + java14 + + + + + org.apache.qpid + qpid-common + jar + ${pom.version} + java14 + + + + net.sf.retrotranslator + retrotranslator-runtime + package + + + + + junit + junit + test + + + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + ${java.source.version} + ${java.source.version} + + + + + + org.apache.maven.plugins + maven-assembly-plugin + ${assembly.version} + + single + + + + src/main/assembly/client-java14-bin.xml + + qpid-${pom.version} + ${qpid.targetDir} + gnu + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + true + + + + surefire-it + integration-test + + test + + + false + once + ${jvm.1.4.bin} + + + amqj.logging.level + ${amqj.logging.level} + + + log4j.configuration + ${log4j.configuration} + + + amq.dynamicsaslregistrar.properties + ${sasl.properties} + + + + + + + + + + diff --git a/java/client-java14/src/main/assembly/client-java14-bin.xml b/java/client-java14/src/main/assembly/client-java14-bin.xml new file mode 100644 index 0000000000..87c7f498f9 --- /dev/null +++ b/java/client-java14/src/main/assembly/client-java14-bin.xml @@ -0,0 +1,69 @@ + + + + + java-client-java14-bin + false + + tar.gz + zip + + + + + + ../resources + qpid-${qpid.version} + + DISCLAIMER + LICENSE.txt + NOTICE.txt + README.txt + + + + + ../release-docs + qpid-${qpid.version}/docs + + RELEASE_NOTES.txt + + + + + + + + qpid-${qpid.version}/lib + false + + + org.apache.qpid:qpid-client:jar + org.apache.qpid:qpid-common:jar + + + org.apache.mina:mina-java5 + org.apache.mina:mina-filter-ssl + + + + + + diff --git a/java/client-java14/src/main/java/org/apache/qpid/sasl/ClientFactoryImpl.java b/java/client-java14/src/main/java/org/apache/qpid/sasl/ClientFactoryImpl.java new file mode 100644 index 0000000000..691020307a --- /dev/null +++ b/java/client-java14/src/main/java/org/apache/qpid/sasl/ClientFactoryImpl.java @@ -0,0 +1,343 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.sasl; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Properties; + +import javax.security.auth.callback.*; +import javax.security.sasl.Sasl; +import javax.security.sasl.SaslClient; +import javax.security.sasl.SaslClientFactory; +import javax.security.sasl.SaslException; + +import org.apache.log4j.Logger; + +import org.apache.qpid.util.PrettyPrintingUtils; + +/** + * Implements a factory for generating Sasl client implementations. + * + *

+ *
CRC Card
Responsibilities Collaborations + *
Provide a list of supported encryption mechansims that meet a defined set of Sasl properties. + *
Provide the best matching supported Sasl mechanism to a preference ordered list of mechanisms and Sasl + * properties. + *
Perform username and password request call backs. CallBackHandler + *
+ */ +public class ClientFactoryImpl implements SaslClientFactory +{ + //private static final Logger log = Logger.getLogger(ClientFactoryImpl.class); + + /** Holds the names of the supported encryption mechanisms. */ + private static final String[] SUPPORTED_MECHANISMS = { "CRAM-MD5", "PLAIN" }; + + /** Defines index of the CRAM-MD5 mechanism within the supported mechanisms. */ + private static final int CRAM_MD5 = 0; + + /** Defines index of the PLAIN mechanism within the supported mechanisms. */ + private static final int PLAIN = 1; + + /** Bit mapping of the no plain text policy. */ + private static final int NOPLAINTEXT = 0x0001; + + /** Bit mapping of the no susceptible active attacks policy. */ + private static final int NOACTIVE = 0x0002; + + /** Bit mapping of the no susceptible to dictionary attacks policy. */ + private static final int NODICTIONARY = 0x0004; + + /** Bit mapping of the must use forward secrecy between sessions policy. */ + private static final int FORWARD_SECRECY = 0x0008; + + /** Bit mapping of the no anonymous logins policy. */ + private static final int NOANONYMOUS = 0x0010; + + /** Bit mapping of the must pass credentials policy. */ + private static final int PASS_CREDENTIALS = 0x0020; + + /** Defines a mapping from supported mechanisms to supported policy flags. */ + private static final int[] SUPPPORTED_MECHANISMS_POLICIES = + { + NOPLAINTEXT | NOANONYMOUS, // CRAM-MD5 + NOANONYMOUS // PLAIN + }; + + /** + * Creates a SaslClient using the parameters supplied. + * + * @param mechanisms The non-null list of mechanism names to try. Each is the IANA-registered name of a SASL + * mechanism. (e.g. "GSSAPI", "CRAM-MD5"). + * @param authorizationId The possibly null protocol-dependent identification to be used for authorization. + * If null or empty, the server derives an authorization ID from the client's authentication + * credentials. When the SASL authentication completes successfully, the specified entity is + * granted access. + * @param protocol The non-null string name of the protocol for which the authentication is being performed + * (e.g., "ldap"). + * @param serverName The non-null fully qualified host name of the server to authenticate to. + * @param props The possibly null set of properties used to select the SASL mechanism and to configure the + * authentication exchange of the selected mechanism. See the Sasl class for a list + * of standard properties. Other, possibly mechanism-specific, properties can be included. + * Properties not relevant to the selected mechanism are ignored. + * @param cbh The possibly null callback handler to used by the SASL mechanisms to get further + * information from the application/library to complete the authentication. For example, a + * SASL mechanism might require the authentication ID, password and realm from the caller. + * The authentication ID is requested by using a NameCallback. + * The password is requested by using a PasswordCallback. + * The realm is requested by using a RealmChoiceCallback if there is a list + * of realms to choose from, and by using a RealmCallback if + * the realm must be entered. + * + * @return A possibly null SaslClient created using the parameters supplied. If null, this factory cannot + * produce a SaslClient using the parameters supplied. + * + * @throws javax.security.sasl.SaslException If cannot create a SaslClient because of an error. + */ + public SaslClient createSaslClient(String[] mechanisms, String authorizationId, String protocol, String serverName, + Map props, CallbackHandler cbh) throws SaslException + { + /*log.debug("public SaslClient createSaslClient(String[] mechanisms = " + PrettyPrintingUtils.printArray(mechanisms) + + ", String authorizationId = " + authorizationId + ", String protocol = " + protocol + + ", String serverName = " + serverName + ", Map props = " + props + ", CallbackHandler cbh): called");*/ + + // Get a list of all supported mechanisms that matched the required properties. + String[] matchingMechanisms = getMechanismNames(props); + //log.debug("matchingMechanisms = " + PrettyPrintingUtils.printArray(matchingMechanisms)); + + // Scan down the list of mechanisms until the first one that matches one of the matching supported mechanisms + // is found. + String chosenMechanism = null; + + for (int i = 0; i < mechanisms.length; i++) + { + String mechanism = mechanisms[i]; + + for (int j = 0; j < matchingMechanisms.length; j++) + { + String matchingMechanism = matchingMechanisms[j]; + + if (mechanism.equals(matchingMechanism)) + { + chosenMechanism = mechanism; + + break; + } + } + + // Stop scanning if a match has been found. + if (chosenMechanism != null) + { + break; + } + } + + // Check that a matching mechanism was found or return null otherwise. + if (chosenMechanism == null) + { + //log.debug("No matching mechanism could be found."); + + return null; + } + + // Instantiate an appropriate client type for the chosen mechanism. + if (chosenMechanism.equals(SUPPORTED_MECHANISMS[CRAM_MD5])) + { + Object[] uinfo = getUserInfo("CRAM-MD5", authorizationId, cbh); + + //log.debug("Using CRAM-MD5 mechanism."); + + return new CramMD5Client((String) uinfo[0], (byte[]) uinfo[1]); + } + else + { + Object[] uinfo = getUserInfo("PLAIN", authorizationId, cbh); + + //log.debug("Using PLAIN mechanism."); + + return new PlainClient(authorizationId, (String) uinfo[0], (byte[]) uinfo[1]); + } + } + + /** + * Returns an array of names of mechanisms that match the specified + * mechanism selection policies. + * + * @param props The possibly null set of properties used to specify the + * security policy of the SASL mechanisms. For example, if props + * contains the Sasl.POLICY_NOPLAINTEXT property with the value + * "true", then the factory must not return any SASL mechanisms + * that are susceptible to simple plain passive attacks. + * See the Sasl class for a complete list of policy properties. + * Non-policy related properties, if present in props, are ignored. + * + * @return A non-null array containing a IANA-registered SASL mechanism names. + */ + public String[] getMechanismNames(Map props) + { + //log.debug("public String[] getMechanismNames(Map props = " + props + "): called"); + + // Used to build up the valid mechanisms in. + List validMechanisms = new ArrayList(); + + // Transform the Sasl properties into a set of bit mapped flags indicating the required properties of the + // encryption mechanism employed. + int requiredFlags = bitMapSaslProperties(props); + //log.debug("requiredFlags = " + requiredFlags); + + // Scan down the list of supported mechanisms filtering in only those that satisfy all of the desired + // encryption properties. + for (int i = 0; i < SUPPORTED_MECHANISMS.length; i++) + { + int mechanismFlags = SUPPPORTED_MECHANISMS_POLICIES[i]; + //log.debug("mechanismFlags = " + mechanismFlags); + + // Check if the current mechanism contains all of the required flags. + if ((requiredFlags & ~mechanismFlags) == 0) + { + //log.debug("Mechanism " + SUPPORTED_MECHANISMS[i] + " meets the required properties."); + validMechanisms.add(SUPPORTED_MECHANISMS[i]); + } + } + + String[] result = (String[]) validMechanisms.toArray(new String[validMechanisms.size()]); + + //log.debug("result = " + PrettyPrintingUtils.printArray(result)); + + return result; + } + + /** + * Transforms a set of Sasl properties, defined using the property names in javax.security.sasl.Sasl, into + * a bit mapped set of property flags encoded using the bit mapping constants defined in this class. + * + * @param properties The Sasl properties to bit map. + * + * @return A set of bit mapped properties encoded in an integer. + */ + private int bitMapSaslProperties(Map properties) + { + //log.debug("private int bitMapSaslProperties(Map properties = " + properties + "): called"); + + int result = 0; + + // No flags set if no properties are set. + if (properties == null) + { + return result; + } + + if ("true".equalsIgnoreCase((String) properties.get(Sasl.POLICY_NOPLAINTEXT))) + { + result |= NOPLAINTEXT; + } + + if ("true".equalsIgnoreCase((String) properties.get(Sasl.POLICY_NOACTIVE))) + { + result |= NOACTIVE; + } + + if ("true".equalsIgnoreCase((String) properties.get(Sasl.POLICY_NODICTIONARY))) + { + result |= NODICTIONARY; + } + + if ("true".equalsIgnoreCase((String) properties.get(Sasl.POLICY_NOANONYMOUS))) + { + result |= NOANONYMOUS; + } + + if ("true".equalsIgnoreCase((String) properties.get(Sasl.POLICY_FORWARD_SECRECY))) + { + result |= FORWARD_SECRECY; + } + + if ("true".equalsIgnoreCase((String) properties.get(Sasl.POLICY_PASS_CREDENTIALS))) + { + result |= PASS_CREDENTIALS; + } + + return result; + } + + /** + * Uses the specified call back handler to query for the users log in name and password. + * + * @param prefix A prefix to prepend onto the username and password queries. + * @param authorizationId The default autorhization name. + * @param cbh The call back handler. + * + * @return The username and password from the callback. + * + * @throws SaslException If the callback fails for any reason. + */ + private Object[] getUserInfo(String prefix, String authorizationId, CallbackHandler cbh) throws SaslException + { + // Check that the callback handler is defined. + if (cbh == null) + { + throw new SaslException("Callback handler to get username/password required."); + } + + try + { + String userPrompt = prefix + " authentication id: "; + String passwdPrompt = prefix + " password: "; + + NameCallback ncb = + (authorizationId == null) ? new NameCallback(userPrompt) : new NameCallback(userPrompt, authorizationId); + PasswordCallback pcb = new PasswordCallback(passwdPrompt, false); + + // Ask the call back handler to get the users name and password. + cbh.handle(new Callback[] { ncb, pcb }); + + char[] pw = pcb.getPassword(); + + byte[] bytepw; + String authId; + + if (pw != null) + { + bytepw = new String(pw).getBytes("UTF8"); + pcb.clearPassword(); + } + else + { + bytepw = null; + } + + authId = ncb.getName(); + + return new Object[] { authId, bytepw }; + } + catch (IOException e) + { + throw new SaslException("Cannot get password.", e); + } + catch (UnsupportedCallbackException e) + { + throw new SaslException("Cannot get userid/password.", e); + } + } +} diff --git a/java/client-java14/src/main/java/org/apache/qpid/sasl/CramMD5Client.java b/java/client-java14/src/main/java/org/apache/qpid/sasl/CramMD5Client.java new file mode 100644 index 0000000000..c771df458e --- /dev/null +++ b/java/client-java14/src/main/java/org/apache/qpid/sasl/CramMD5Client.java @@ -0,0 +1,349 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.sasl; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +import javax.security.sasl.Sasl; +import javax.security.sasl.SaslClient; +import javax.security.sasl.SaslException; + +/** + * Implements the CRAM-MD5 SASL mechanism. + * + *

+ *
CRC Card
Responsibilities Collaborations + *
Concatenate the user id and password hashed by a challenge string as the challenge response. + *
Ensure password is wiped once a challenge has been processed. + *
+ * + * @author Rupert Smith + */ +public class CramMD5Client implements SaslClient +{ + /** Defines the HMAC block size. */ + private static final int MD5_BLOCKSIZE = 64; + + /** Flag used to indicate that the authentication has completed. */ + private boolean completed = false; + + private String authenticationId; + private byte[] password; + + /** + * Creates a PLAIN SASL client for an authorization id, authentication id and password. + * + * @param authenticationId The authentication id. + * @param password The password. + * + * @throws SaslException If the authentication id or password is null. + */ + CramMD5Client(String authenticationId, byte[] password) throws SaslException + { + // Check that a username and password are specified. + if ((authenticationId == null) || (password == null)) + { + throw new SaslException("CRAM: authentication id and password must be specified"); + } + + // Keep the log in credentials. + this.authenticationId = authenticationId; + this.password = password; + } + + /** + * Returns the IANA-registered mechanism name of this SASL client. (e.g. "CRAM-MD5", "GSSAPI"). + * + * @return A non-null string representing the IANA-registered mechanism name. + */ + public String getMechanismName() + { + return "CRAM-MD5"; + } + + /** + * Determines whether this mechanism has an optional initial response. If true, caller should call + * evaluateChallenge() with an empty array to get the initial response. + * + *

CRAM-MD5 has no intial response. + * + * @return true if this mechanism has an initial response. + */ + public boolean hasInitialResponse() + { + return false; + } + + /** + * Evaluates the challenge data and generates a response. If a challenge is received from the server during the + * authentication process, this method is called to prepare an appropriate next response to submit to the server. + * + *

The initial response for the SASL command, for the CRAM-MD5 mechanism is the concatenation of authentication + * ID and password HMAC-MD5 hashed by the server challenge. + * + * @param challenge The non-null challenge sent from the server. The challenge array may have zero length. + * + * @return The possibly null reponse to send to the server. It is null if the challenge accompanied a "SUCCESS" + * status and the challenge only contains data for the client to update its state and no response + * needs to be sent to the server. The response is a zero-length byte array if the client is to send a + * response with no data. + * + * @throws javax.security.sasl.SaslException If an error occurred while processing the challenge or generating a + * response. + */ + public byte[] evaluateChallenge(byte[] challenge) throws SaslException + { + // Check that the authentication has not already been performed. + if (completed) + { + throw new IllegalStateException("CRAM-MD5 authentication already completed."); + } + + // Check if the password is null, this will be the case if a previous attempt to authenticated failed. + if (password == null) + { + throw new IllegalStateException("CRAM-MD5 authentication previously aborted due to error."); + } + + // Generate a keyed-MD5 digest from the user's password keyed by the challenge bytes. + try + { + String digest = hmac_md5(password, challenge); + String result = authenticationId + " " + digest; + + completed = true; + + return result.getBytes("UTF8"); + } + catch (java.security.NoSuchAlgorithmException e) + { + throw new SaslException("MD5 algorithm not available on platform", e); + } + catch (java.io.UnsupportedEncodingException e) + { + throw new SaslException("UTF8 not available on platform", e); + } + finally + { + clearPassword(); + } + } + + /** + * Determines whether the authentication exchange has completed. This method may be called at any time, but + * typically, it will not be called until the caller has received indication from the server (in a protocol-specific + * manner) that the exchange has completed. + * + * @return true if the authentication exchange has completed; false otherwise. + */ + public boolean isComplete() + { + return completed; + } + + /** + * Unwraps a byte array received from the server. This method can be called only after the authentication exchange + * has completed (i.e., when isComplete() returns true) and only if the authentication exchange has + * negotiated integrity and/or privacy as the quality of protection; otherwise, an IllegalStateException is + * thrown. + * + *

incoming is the contents of the SASL buffer as defined in RFC 2222 without the leading four octet + * field that represents the length. offset and len specify the portion of incoming + * to use. + * + * @param incoming A non-null byte array containing the encoded bytes from the server. + * @param offset The starting position at incoming of the bytes to use. + * @param len The number of bytes from incoming to use. + * + * @return A non-null byte array containing the decoded bytes. + * + * @throws javax.security.sasl.SaslException If incoming cannot be successfully unwrapped. + * @throws IllegalStateException If the authentication exchange has not completed, or if the negotiated quality of + * protection has neither integrity nor privacy. + */ + public byte[] unwrap(byte[] incoming, int offset, int len) throws SaslException + { + throw new SaslException("CRAM-MD5 does not support quality of protection."); + } + + /** + * Wraps a byte array to be sent to the server. This method can be called only after the authentication exchange has + * completed (i.e., when isComplete() returns true) and only if the authentication exchange has negotiated + * integrity and/or privacy as the quality of protection; otherwise, an IllegalStateException is thrown. + * + *

The result of this method will make up the contents of the SASL buffer as defined in RFC 2222 without the + * leading four octet field that represents the length. offset and len specify the portion of + * outgoing to use. + * + * @param outgoing A non-null byte array containing the bytes to encode. + * @param offset The starting position at outgoing of the bytes to use. + * @param len The number of bytes from outgoing to use. + * + * @return A non-null byte array containing the encoded bytes. + * + * @throws javax.security.sasl.SaslException If outgoing cannot be successfully wrapped. + * @throws IllegalStateException If the authentication exchange has not completed, or if the negotiated quality of + * protection has neither integrity nor privacy. + */ + public byte[] wrap(byte[] outgoing, int offset, int len) throws SaslException + { + throw new SaslException("CRAM-MD5 does not support quality of protection."); + } + + /** + * Retrieves the negotiated property. This method can be called only after the authentication exchange has + * completed (i.e., when isComplete() returns true); otherwise, an IllegalStateException is thrown. + * + * @param propName The non-null property name. + * + * @return The value of the negotiated property. If null, the property was not negotiated or is not applicable to + * this mechanism. + * + * @throws IllegalStateException If this authentication exchange has not completed. + */ + public Object getNegotiatedProperty(String propName) + { + if (completed) + { + if (propName.equals(Sasl.QOP)) + { + return "auth"; + } + else + { + return null; + } + } + else + { + throw new IllegalStateException("CRAM-MD5 authentication not completed"); + } + } + + /** + * Disposes of any system resources or security-sensitive information the SaslClient might be using. Invoking this + * method invalidates the SaslClient instance. This method is idempotent. + * + * @throws javax.security.sasl.SaslException If a problem was encountered while disposing the resources. + */ + public void dispose() throws SaslException + { } + + /* + * Hashes its input arguments according to HMAC-MD5 (RFC 2104) and returns the resulting digest in its ASCII + * representation. + * + *

The HMAC-MD5 function is described as follows: + *

+     *     MD5(key XOR opad, MD5(key XOR ipad, text))
+     * 
+ * + *

Where key is an n byte key, ipad is the byte 0x36 repeated 64 times, opad is the byte 0x5c repeated 64 times + * and text is the data to be protected. + * + * @param key The key to hash by. + * @param text The plain text to hash. + * + * @return The hashed text. + * + * @throws NoSuchAlgorithmException If the Java platform does not supply an MD5 implementation. + */ + private static final String hmac_md5(byte[] key, byte[] text) throws NoSuchAlgorithmException + { + MessageDigest md5 = MessageDigest.getInstance("MD5"); + + /* digest the key if longer than 64 bytes */ + if (key.length > 64) + { + key = md5.digest(key); + } + + byte[] ipad = new byte[MD5_BLOCKSIZE]; /* inner padding */ + byte[] opad = new byte[MD5_BLOCKSIZE]; /* outer padding */ + byte[] digest; + int i; + + /* store key in pads */ + for (i = 0; i < MD5_BLOCKSIZE; i++) + { + for (; i < key.length; i++) + { + ipad[i] = key[i]; + opad[i] = key[i]; + } + + ipad[i] = 0x00; + opad[i] = 0x00; + } + + /* XOR key with pads */ + for (i = 0; i < MD5_BLOCKSIZE; i++) + { + ipad[i] ^= 0x36; + opad[i] ^= 0x5c; + } + + /* inner MD5 */ + md5.update(ipad); + md5.update(text); + digest = md5.digest(); + + /* outer MD5 */ + md5.update(opad); + md5.update(digest); + digest = md5.digest(); + + // Get character representation of digest + StringBuffer digestString = new StringBuffer(); + + for (i = 0; i < digest.length; i++) + { + if ((digest[i] & 0x000000ff) < 0x10) + { + digestString.append("0" + Integer.toHexString(digest[i] & 0x000000ff)); + } + else + { + digestString.append(Integer.toHexString(digest[i] & 0x000000ff)); + } + } + + return (digestString.toString()); + } + + /** + * Overwrites the password with zeros. + */ + private void clearPassword() + { + if (password != null) + { + // Zero out password. + for (int i = 0; i < password.length; i++) + { + password[i] = (byte) 0; + } + + password = null; + } + } +} diff --git a/java/client-java14/src/main/java/org/apache/qpid/sasl/PlainClient.java b/java/client-java14/src/main/java/org/apache/qpid/sasl/PlainClient.java new file mode 100644 index 0000000000..67d00fd7d4 --- /dev/null +++ b/java/client-java14/src/main/java/org/apache/qpid/sasl/PlainClient.java @@ -0,0 +1,277 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.sasl; + +import javax.security.sasl.Sasl; +import javax.security.sasl.SaslClient; +import javax.security.sasl.SaslException; + +/** + * Implements the PLAIN SASL mechanism. + * + *

+ *
CRC Card
Responsibilities Collaborations + *
Concatenate the user id and password in plain text as the challenge respose. + *
Ensure password is wiped once a challenge has been processed. + *
+ * + * @author Rupert Smith + */ +public class PlainClient implements SaslClient +{ + /** Flag used to indicate that the authentication has completed. */ + private boolean completed = false; + + private String authorizationId; + private String authenticationId; + private byte[] password; + + private static byte SEPERATOR = 0; // US-ASCII + + /** + * Creates a PLAIN SASL client for an authorization id, authentication id and password. + * + * @param authorizationId The authorization id. May be null. + * @param authenticationId The authentication id. + * @param password The password. + * + * @throws SaslException If the authentication id or password is null. + */ + PlainClient(String authorizationId, String authenticationId, byte[] password) throws SaslException + { + // Check that a username and password are specified. + if ((authenticationId == null) || (password == null)) + { + throw new SaslException("PLAIN: authentication ID and password must be specified"); + } + + // Keep the log in credentials. + this.authorizationId = authorizationId; + this.authenticationId = authenticationId; + this.password = password; + } + + /** + * Returns the IANA-registered mechanism name of this SASL client. (e.g. "CRAM-MD5", "GSSAPI"). + * + * @return A non-null string representing the IANA-registered mechanism name. + */ + public String getMechanismName() + { + return "PLAIN"; + } + + /** + * Determines whether this mechanism has an optional initial response. If true, caller should call + * evaluateChallenge() with an empty array to get the initial response. + * + * @return true if this mechanism has an initial response. + */ + public boolean hasInitialResponse() + { + return true; + } + + /** + * Evaluates the challenge data and generates a response. If a challenge is received from the server during the + * authentication process, this method is called to prepare an appropriate next response to submit to the server. + * + *

The initial response for the SASL command, for the PLAIN mechanism is the concatenation of authorization ID, + * authentication ID and password, with each component separated by the US-ASCII byte. + * + * @param challenge The non-null challenge sent from the server. The challenge array may have zero length. + * + * @return The possibly null reponse to send to the server. It is null if the challenge accompanied a "SUCCESS" + * status and the challenge only contains data for the client to update its state and no response + * needs to be sent to the server. The response is a zero-length byte array if the client is to send a + * response with no data. + * + * @throws javax.security.sasl.SaslException If an error occurred while processing the challenge or generating a + * response. + */ + public byte[] evaluateChallenge(byte[] challenge) throws SaslException + { + // Check that the authentication has not already been performed. + if (completed) + { + throw new IllegalStateException("PLAIN authentication already completed"); + } + + try + { + // Get the authorization and authentication ids in bytes. + byte[] authorizationBytes = (authorizationId != null) ? authorizationId.getBytes("UTF8") : null; + byte[] authenticationBytes = authenticationId.getBytes("UTF8"); + + // Create an array big enough to hold the results. + byte[] result = + new byte[password.length + authenticationBytes.length + 2 + + ((authorizationBytes == null) ? 0 : authorizationBytes.length)]; + + // Copy the authorization id, authentication id and password into the results. + int pos = 0; + if (authorizationBytes != null) + { + System.arraycopy(authorizationBytes, 0, result, 0, authorizationBytes.length); + pos = authorizationBytes.length; + } + + result[pos++] = SEPERATOR; + System.arraycopy(authenticationBytes, 0, result, pos, authenticationBytes.length); + + pos += authenticationBytes.length; + result[pos++] = SEPERATOR; + + System.arraycopy(password, 0, result, pos, password.length); + + completed = true; + + return result; + } + catch (java.io.UnsupportedEncodingException e) + { + throw new SaslException("Cannot get UTF-8 encoding of ids", e); + } + finally + { + clearPassword(); + } + } + + /** + * Determines whether the authentication exchange has completed. This method may be called at any time, but + * typically, it will not be called until the caller has received indication from the server (in a protocol-specific + * manner) that the exchange has completed. + * + * @return true if the authentication exchange has completed; false otherwise. + */ + public boolean isComplete() + { + return completed; + } + + /** + * Unwraps a byte array received from the server. This method can be called only after the authentication exchange has + * completed (i.e., when isComplete() returns true) and only if the authentication exchange has negotiated + * integrity and/or privacy as the quality of protection; otherwise, an IllegalStateException is thrown. + * + *

incoming is the contents of the SASL buffer as defined in RFC 2222 without the leading four octet + * field that represents the length. offset and len specify the portion of incoming + * to use. + * + * @param incoming A non-null byte array containing the encoded bytes + * from the server. + * @param offset The starting position at incoming of the bytes to use. + * @param len The number of bytes from incoming to use. + * + * @return A non-null byte array containing the decoded bytes. + * + * @throws javax.security.sasl.SaslException If incoming cannot be successfully unwrapped. + * @throws IllegalStateException If the authentication exchange has not completed, or if the negotiated quality of + * protection has neither integrity nor privacy. + */ + public byte[] unwrap(byte[] incoming, int offset, int len) throws SaslException + { + throw new SaslException("PLAIN does not support quality of protection."); + } + + /** + * Wraps a byte array to be sent to the server. This method can be called only after the authentication exchange has + * completed (i.e., when isComplete() returns true) and only if the authentication exchange has negotiated + * integrity and/or privacy as the quality of protection; otherwise, an IllegalStateException is thrown. + * + *

The result of this method will make up the contents of the SASL buffer as defined in RFC 2222 without the + * leading four octet field that represents the length. offset and len specify the portion of + * outgoing to use. + * + * @param outgoing A non-null byte array containing the bytes to encode. + * @param offset The starting position at outgoing of the bytes to use. + * @param len The number of bytes from outgoing to use. + * + * @return A non-null byte array containing the encoded bytes. + * + * @throws javax.security.sasl.SaslException If outgoing cannot be successfully wrapped. + * @throws IllegalStateException If the authentication exchange has not completed, or if the negotiated quality of + * protection has neither integrity nor privacy. + */ + public byte[] wrap(byte[] outgoing, int offset, int len) throws SaslException + { + throw new SaslException("PLAIN does not support quality of protection."); + } + + /** + * Retrieves the negotiated property. This method can be called only after the authentication exchange has + * completed (i.e., when isComplete() returns true); otherwise, an IllegalStateException is thrown. + * + * @param propName The non-null property name. + * + * @return The value of the negotiated property. If null, the property was not negotiated or is not applicable to + * this mechanism. + * + * @throws IllegalStateException If this authentication exchange has not completed. + */ + public Object getNegotiatedProperty(String propName) + { + if (completed) + { + if (propName.equals(Sasl.QOP)) + { + return "auth"; + } + else + { + return null; + } + } + else + { + throw new IllegalStateException("PLAIN authentication not completed"); + } + } + + /** + * Disposes of any system resources or security-sensitive information the SaslClient might be using. Invoking this + * method invalidates the SaslClient instance. This method is idempotent. + * + * @throws javax.security.sasl.SaslException If a problem was encountered while disposing the resources. + */ + public void dispose() throws SaslException + { + clearPassword(); + } + + /** + * Overwrites the password with zeros. + */ + private void clearPassword() + { + if (password != null) + { + // Zero out password. + for (int i = 0; i < password.length; i++) + { + password[i] = (byte) 0; + } + + password = null; + } + } +} diff --git a/java/client-java14/src/main/java/org/apache/qpid/sasl/Provider.java b/java/client-java14/src/main/java/org/apache/qpid/sasl/Provider.java new file mode 100644 index 0000000000..f9a5c42c3d --- /dev/null +++ b/java/client-java14/src/main/java/org/apache/qpid/sasl/Provider.java @@ -0,0 +1,61 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.sasl; + +import java.security.AccessController; +import java.security.PrivilegedAction; + +import org.apache.log4j.Logger; + +/** + * A SASL provider for Java 1.4. Declares the capabilities of this implementation, which supports PLAIN and CRAM-MD5 + * client implementations only. + * + *

+ *
CRC Card
Responsibilities Collaborations + *
Declare PLAIN SASL support. + *
Declare CRAM-MD5 SASL support. + *
+ */ +public class Provider extends java.security.Provider +{ + //Logger log = Logger.getLogger(Provider.class); + + private static final String info = "Qpid SASL provider" + "(implements client mechanisms for: PLAIN, CRAM-MD5)"; + + public Provider() + { + super("QpidSASL", 1.4, info); + + //log.debug("public Provider(): called"); + + AccessController.doPrivileged(new PrivilegedAction() + { + public Object run() + { + put("SaslClientFactory.PLAIN", "org.apache.qpid.sasl.ClientFactoryImpl"); + put("SaslClientFactory.CRAM-MD5", "org.apache.qpid.sasl.ClientFactoryImpl"); + + return null; + } + }); + } +} diff --git a/java/client-java14/src/test/java/org/apache/qpid/test/integration/client/ConnectionTest.java b/java/client-java14/src/test/java/org/apache/qpid/test/integration/client/ConnectionTest.java new file mode 100644 index 0000000000..468beda5b5 --- /dev/null +++ b/java/client-java14/src/test/java/org/apache/qpid/test/integration/client/ConnectionTest.java @@ -0,0 +1,66 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.test.integration.client; + +import junit.framework.Assert; +import junit.framework.TestCase; + +import org.apache.log4j.Logger; +import org.apache.log4j.NDC; + +import org.apache.qpid.client.AMQConnection; + +/** + * Implements a trivial broker connection test, to a broker on the default port on localhost, to check that SASL + * authentication works. + * + *

+ *
CRC Card
Responsibilities Collaborations + *
Check that a connection to a broker can be established. + *
+ */ +public class ConnectionTest extends TestCase +{ + private String BROKER_URL = "tcp://localhost:5672"; + + public ConnectionTest(String name) + { + super(name); + } + + /** Check that a connection to a broker can be established. */ + public void testConnection() throws Exception + { + // Open a connection to the broker and close it again. + AMQConnection conn = new AMQConnection(BROKER_URL, "guest", "guest", "clientid", "test"); + conn.close(); + } + + protected void setUp() + { + NDC.push(getName()); + } + + protected void tearDown() + { + NDC.pop(); + } +} diff --git a/java/client/distribution/pom.xml b/java/client/distribution/pom.xml index 3e9c0e493f..4ad14d40d2 100644 --- a/java/client/distribution/pom.xml +++ b/java/client/distribution/pom.xml @@ -48,20 +48,6 @@ jar ${pom.version} - - org.apache.qpid - qpid-client - jar - ${pom.version} - java1.4 - - - org.apache.qpid - qpid-common - jar - ${pom.version} - java1.4 - @@ -119,7 +105,6 @@ src/main/assembly/client-bin.xml - src/main/assembly/client-java1.4-bin.xml src/main/assembly/client-src.xml qpid-${pom.version} diff --git a/java/client/pom.xml b/java/client/pom.xml index af85c5e63a..3a425cae1a 100644 --- a/java/client/pom.xml +++ b/java/client/pom.xml @@ -15,7 +15,7 @@ KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. - --> +--> @@ -100,12 +100,18 @@ test - - - net.sf.retrotranslator - retrotranslator-runtime - provided - + + + net.sf.retrotranslator + retrotranslator-runtime + provided + + + + junit + junit + provided + @@ -121,17 +127,17 @@ maven-surefire-plugin - - amqj.noAutoCreateVMBroker - true - amqj.logging.level ${amqj.logging.level} log4j.configuration - file:///${basedir}/src/main/java/client.log4j + ${log4j.configuration} + + + amqj.noAutoCreateVMBroker + true @@ -152,35 +158,38 @@ - org.codehaus.mojo - retrotranslator-maven-plugin - - - package - - translate - - - ${project.build.directory}/${project.build.finalName}-java1.4.jar - ${retrotranslator.verify} - - ${retrotranslator.1.4-rt-path} - ${retrotranslator.1.4-jce-path} - ${retrotranslator.1.4-jsse-path} - - - - ${project.build.directory} - ${project.build.finalName}.jar - - - - - - + org.codehaus.mojo + retrotranslator-maven-plugin + + + retro-client + package + + translate + + + + ${project.build.directory}/${project.build.finalName}-java14.jar + ${retrotranslator.verify} + + ${retrotranslator.1.4-rt-path} + ${retrotranslator.1.4-jce-path} + ${retrotranslator.1.4-jsse-path} + ${retrotranslator.1.4-sasl-path} + + false + + + ${project.build.directory} + ${project.build.finalName}.jar + + + + + - - + + org.codehaus.mojo build-helper-maven-plugin @@ -194,9 +203,9 @@ - ${project.build.directory}/${project.build.finalName}-java1.4.jar + ${project.build.directory}/${project.build.finalName}-java14.jar jar - java1.4 + java14 @@ -215,15 +224,18 @@ ** - + + + false @@ -233,8 +245,7 @@ - + - \ No newline at end of file diff --git a/java/client/src/main/java/org/apache/qpid/client/handler/ConnectionStartMethodHandler.java b/java/client/src/main/java/org/apache/qpid/client/handler/ConnectionStartMethodHandler.java index 66ae92113c..2aa2c1872b 100644 --- a/java/client/src/main/java/org/apache/qpid/client/handler/ConnectionStartMethodHandler.java +++ b/java/client/src/main/java/org/apache/qpid/client/handler/ConnectionStartMethodHandler.java @@ -29,6 +29,7 @@ import javax.security.sasl.SaslClient; import javax.security.sasl.SaslException; import org.apache.log4j.Logger; + import org.apache.qpid.AMQException; import org.apache.qpid.client.protocol.AMQProtocolSession; import org.apache.qpid.client.security.AMQCallbackHandler; @@ -48,7 +49,6 @@ import org.apache.qpid.protocol.AMQMethodEvent; public class ConnectionStartMethodHandler implements StateAwareMethodListener { - private static final Logger _log = Logger.getLogger(ConnectionStartMethodHandler.class); private static final ConnectionStartMethodHandler _instance = new ConnectionStartMethodHandler(); @@ -59,19 +59,23 @@ public class ConnectionStartMethodHandler implements StateAwareMethodListener } private ConnectionStartMethodHandler() - { - } + { } - public void methodReceived(AMQStateManager stateManager, AMQProtocolSession protocolSession, AMQMethodEvent evt) throws AMQException + public void methodReceived(AMQStateManager stateManager, AMQProtocolSession protocolSession, AMQMethodEvent evt) + throws AMQException { + _log.debug("public void methodReceived(AMQStateManager stateManager, AMQProtocolSession protocolSession, " + + "AMQMethodEvent evt): called"); + ConnectionStartBody body = (ConnectionStartBody) evt.getMethod(); byte major = (byte) body.versionMajor; byte minor = (byte) body.versionMinor; boolean versionOk = false; - // for the purposes of interop, we can make the client accept the broker's version string. - // if it does, it then internally records the version as being the latest one that it understands. - // it needs to do this since frame lookup is done by version. + + // For the purposes of interop, we can make the client accept the broker's version string. + // If it does, it then internally records the version as being the latest one that it understands. + // It needs to do this since frame lookup is done by version. if (Boolean.getBoolean("qpid.accept.broker.version")) { versionOk = true; @@ -90,8 +94,9 @@ public class ConnectionStartMethodHandler implements StateAwareMethodListener try { - // the mechanism we are going to use + // Used to hold the SASL mechanism to authenticate with. String mechanism; + if (body.mechanisms == null) { throw new AMQException("mechanism not specified in ConnectionStart method frame"); @@ -99,6 +104,7 @@ public class ConnectionStartMethodHandler implements StateAwareMethodListener else { mechanism = chooseMechanism(body.mechanisms); + _log.debug("mechanism = " + mechanism); } if (mechanism == null) @@ -109,15 +115,17 @@ public class ConnectionStartMethodHandler implements StateAwareMethodListener byte[] saslResponse; try { - SaslClient sc = Sasl.createSaslClient(new String[]{mechanism}, - null, "AMQP", "localhost", - null,createCallbackHandler(mechanism, protocolSession)); + SaslClient sc = + Sasl.createSaslClient(new String[] { mechanism }, null, "AMQP", "localhost", null, + createCallbackHandler(mechanism, protocolSession)); if (sc == null) { - throw new AMQException("Client SASL configuration error: no SaslClient could be created for mechanism " + - mechanism + ". Please ensure all factories are registered. See DynamicSaslRegistrar for " + - " details of how to register non-standard SASL client providers."); + throw new AMQException( + "Client SASL configuration error: no SaslClient could be created for mechanism " + mechanism + + ". Please ensure all factories are registered. See DynamicSaslRegistrar for " + + " details of how to register non-standard SASL client providers."); } + protocolSession.setSaslClient(sc); saslResponse = (sc.hasInitialResponse() ? sc.evaluateChallenge(new byte[0]) : null); } @@ -131,6 +139,7 @@ public class ConnectionStartMethodHandler implements StateAwareMethodListener { throw new AMQException("Locales is not defined in Connection Start method"); } + final String locales = new String(body.locales, "utf8"); final StringTokenizer tokenizer = new StringTokenizer(locales, " "); String selectedLocale = null; @@ -146,21 +155,24 @@ public class ConnectionStartMethodHandler implements StateAwareMethodListener stateManager.changeState(AMQState.CONNECTION_NOT_TUNED); FieldTable clientProperties = FieldTableFactory.newFieldTable(); - clientProperties.setString(new AMQShortString(ClientProperties.instance.toString()), protocolSession.getClientID()); - clientProperties.setString(new AMQShortString(ClientProperties.product.toString()), QpidProperties.getProductName()); - clientProperties.setString(new AMQShortString(ClientProperties.version.toString()), QpidProperties.getReleaseVersion()); + clientProperties.setString(new AMQShortString(ClientProperties.instance.toString()), + protocolSession.getClientID()); + clientProperties.setString(new AMQShortString(ClientProperties.product.toString()), + QpidProperties.getProductName()); + clientProperties.setString(new AMQShortString(ClientProperties.version.toString()), + QpidProperties.getReleaseVersion()); clientProperties.setString(new AMQShortString(ClientProperties.platform.toString()), getFullSystemInfo()); // AMQP version change: Hardwire the version to 0-8 (major=8, minor=0) // TODO: Connect this to the session version obtained from ProtocolInitiation for this session. // Be aware of possible changes to parameter order as versions change. protocolSession.writeFrame(ConnectionStartOkBody.createAMQFrame(evt.getChannelId(), - protocolSession.getProtocolMajorVersion(), - protocolSession.getProtocolMinorVersion(), - clientProperties, // clientProperties - new AMQShortString(selectedLocale), // locale - new AMQShortString(mechanism), // mechanism - saslResponse)); // response + protocolSession.getProtocolMajorVersion(), + protocolSession.getProtocolMinorVersion(), + clientProperties, // clientProperties + new AMQShortString(selectedLocale), // locale + new AMQShortString(mechanism), // mechanism + saslResponse)); // response } catch (UnsupportedEncodingException e) @@ -170,11 +182,8 @@ public class ConnectionStartMethodHandler implements StateAwareMethodListener } else { - _log.error("Broker requested Protocol [" - + body.versionMajor - + "-" - + body.versionMinor - + "] which is not supported by this version of the client library"); + _log.error("Broker requested Protocol [" + body.versionMajor + "-" + body.versionMinor + + "] which is not supported by this version of the client library"); protocolSession.closeProtocolSession(); } @@ -185,7 +194,7 @@ public class ConnectionStartMethodHandler implements StateAwareMethodListener byte[][] supportedVersions = ProtocolVersionList.pv; boolean supported = false; int i = supportedVersions.length; - while(i-- != 0 && !supported) + while ((i-- != 0) && !supported) { supported = (supportedVersions[i][ProtocolVersionList.PROTOCOL_MAJOR] == versionMajor) && (supportedVersions[i][ProtocolVersionList.PROTOCOL_MINOR] == versionMinor); @@ -228,11 +237,12 @@ public class ConnectionStartMethodHandler implements StateAwareMethodListener return mech; } } + return null; } private AMQCallbackHandler createCallbackHandler(String mechanism, AMQProtocolSession protocolSession) - throws AMQException + throws AMQException { Class mechanismClass = CallbackHandlerRegistry.getInstance().getCallbackHandlerClass(mechanism); try @@ -240,6 +250,7 @@ public class ConnectionStartMethodHandler implements StateAwareMethodListener Object instance = mechanismClass.newInstance(); AMQCallbackHandler cbh = (AMQCallbackHandler) instance; cbh.initialise(protocolSession); + return cbh; } catch (Exception e) diff --git a/java/client/src/main/java/org/apache/qpid/client/security/CallbackHandlerRegistry.java b/java/client/src/main/java/org/apache/qpid/client/security/CallbackHandlerRegistry.java index bcf77e1586..5c0f1de5bb 100644 --- a/java/client/src/main/java/org/apache/qpid/client/security/CallbackHandlerRegistry.java +++ b/java/client/src/main/java/org/apache/qpid/client/security/CallbackHandlerRegistry.java @@ -7,9 +7,9 @@ * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY @@ -33,45 +33,102 @@ import java.util.Properties; import org.apache.log4j.Logger; +import org.apache.qpid.util.FileUtils; + +/** + * CallbackHandlerRegistry is a registry for call back handlers for user authentication and interaction during user + * authentication. It is capable of reading its configuration from a properties file containing call back handler + * implementing class names for different SASL mechanism names. Instantiating this registry also has the effect of + * configuring and registering the SASL client factory implementations using {@link DynamicSaslRegistrar}. + * + *

The callback configuration should be specified in a properties file, refered to by the System property + * "amp.callbackhandler.properties". The format of the properties file is: + * + *

+ * CallbackHanlder.mechanism=fully.qualified.class.name
+ * 
+ * + *

Where mechanism is an IANA-registered mechanism name and the fully qualified class name refers to a + * class that implements org.apache.qpid.client.security.AMQCallbackHanlder and provides a call back handler for the + * specified mechanism. + * + *

+ *
CRC Card
Responsibilities Collaborations + *
Parse callback properties. + *
Provide mapping from SASL mechanisms to callback implementations. + *
+ */ public class CallbackHandlerRegistry { + private static final Logger _logger = Logger.getLogger(CallbackHandlerRegistry.class); + + /** The name of the system property that holds the name of the callback handler properties file. */ private static final String FILE_PROPERTY = "amq.callbackhandler.properties"; - private static final Logger _logger = Logger.getLogger(CallbackHandlerRegistry.class); + /** The default name of the callback handler properties resource. */ + public static final String DEFAULT_RESOURCE_NAME = "org/apache/qpid/client/security/CallbackHandlerRegistry.properties"; + /** A static reference to the singleton instance of this registry. */ private static CallbackHandlerRegistry _instance = new CallbackHandlerRegistry(); - private Map _mechanismToHandlerClassMap = new HashMap(); + /** Holds a map from SASL mechanism names to call back handlers. */ + private Map _mechanismToHandlerClassMap = new HashMap(); + /** Holds a space delimited list of mechanisms that callback handlers exist for. */ private String _mechanisms; + /** + * Gets the singleton instance of this registry. + * + * @return The singleton instance of this registry. + */ public static CallbackHandlerRegistry getInstance() { - return _instance; + return _instance; } + /** + * Gets the callback handler class for a given SASL mechanism name. + * + * @param mechanism The SASL mechanism name. + * + * @return The callback handler class for the mechanism, or null if none is configured for that mechanism. + */ public Class getCallbackHandlerClass(String mechanism) { return (Class) _mechanismToHandlerClassMap.get(mechanism); } + /** + * Gets a space delimited list of supported SASL mechanisms. + * + * @return A space delimited list of supported SASL mechanisms. + */ public String getMechanisms() { return _mechanisms; } + /** + * Creates the call back handler registry from its configuration resource or file. This also has the side effect + * of configuring and registering the SASL client factory implementations using {@link DynamicSaslRegistrar}. + */ private CallbackHandlerRegistry() { - // first we register any Sasl client factories + // Register any configured SASL client factories. DynamicSaslRegistrar.registerSaslProviders(); - InputStream is = openPropertiesInputStream(); + String filename = System.getProperty(FILE_PROPERTY); + InputStream is = + FileUtils.openFileOrDefaultResource(filename, DEFAULT_RESOURCE_NAME, + CallbackHandlerRegistry.class.getClassLoader()); + try { Properties props = new Properties(); props.load(is); parseProperties(props); - _logger.info("Available SASL mechanisms: " + _mechanisms); + _logger.info("Callback handlers available for SASL mechanisms: " + _mechanisms); } catch (IOException e) { @@ -94,9 +151,8 @@ public class CallbackHandlerRegistry } } - private InputStream openPropertiesInputStream() + /*private InputStream openPropertiesInputStream(String filename) { - String filename = System.getProperty(FILE_PROPERTY); boolean useDefault = true; InputStream is = null; if (filename != null) @@ -111,15 +167,23 @@ public class CallbackHandlerRegistry _logger.error("Unable to read from file " + filename + ": " + e, e); } } - + if (useDefault) { - is = CallbackHandlerRegistry.class.getResourceAsStream("CallbackHandlerRegistry.properties"); + is = CallbackHandlerRegistry.class.getResourceAsStream(DEFAULT_RESOURCE_NAME); } - + return is; - } - + }*/ + + /** + * Scans the specified properties as a mapping from IANA registered SASL mechanism to call back handler + * implementations, that provide the necessary call back handling for obtaining user log in credentials + * during authentication for the specified mechanism, and builds a map from mechanism names to handler + * classes. + * + * @param props + */ private void parseProperties(Properties props) { Enumeration e = props.propertyNames(); @@ -130,8 +194,10 @@ public class CallbackHandlerRegistry if (period < 0) { _logger.warn("Unable to parse property " + propertyName + " when configuring SASL providers"); + continue; } + String mechanism = propertyName.substring(period + 1); String className = props.getProperty(propertyName); Class clazz = null; @@ -140,10 +206,12 @@ public class CallbackHandlerRegistry clazz = Class.forName(className); if (!AMQCallbackHandler.class.isAssignableFrom(clazz)) { - _logger.warn("SASL provider " + clazz + " does not implement " + AMQCallbackHandler.class + - ". Skipping"); + _logger.warn("SASL provider " + clazz + " does not implement " + AMQCallbackHandler.class + + ". Skipping"); + continue; } + _mechanismToHandlerClassMap.put(mechanism, clazz); if (_mechanisms == null) { @@ -158,6 +226,7 @@ public class CallbackHandlerRegistry catch (ClassNotFoundException ex) { _logger.warn("Unable to load class " + className + ". Skipping that SASL provider"); + continue; } } diff --git a/java/client/src/main/java/org/apache/qpid/client/security/DynamicSaslRegistrar.java b/java/client/src/main/java/org/apache/qpid/client/security/DynamicSaslRegistrar.java index 078c5e4989..f8ee22a5d9 100644 --- a/java/client/src/main/java/org/apache/qpid/client/security/DynamicSaslRegistrar.java +++ b/java/client/src/main/java/org/apache/qpid/client/security/DynamicSaslRegistrar.java @@ -7,9 +7,9 @@ * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY @@ -36,20 +36,62 @@ import javax.security.sasl.SaslClientFactory; import org.apache.log4j.Logger; +import org.apache.qpid.util.FileUtils; + +/** + * DynamicSaslRegistrar provides a collection of helper methods for reading a configuration file that contains a mapping + * from SASL mechanism names to implementing client factory class names and registering a security provider with the + * Java runtime system, that uses the configured client factory implementations. + * + *

The sasl configuration should be specified in a properties file, refered to by the System property + * "amp.dynamicsaslregistrar.properties". The format of the properties file is: + * + *

+ * mechanism=fully.qualified.class.name
+ * 
+ * + *

Where mechanism is an IANA-registered mechanism name and the fully qualified class name refers to a + * class that implements javax.security.sasl.SaslClientFactory and provides the specified mechanism. + * + *

+ *
CRC Card
Responsibilities Collaborations + *
Parse SASL mechanism properties. + *
Create and register security provider for SASL mechanisms. + *
+ */ public class DynamicSaslRegistrar { + private static final Logger _logger = Logger.getLogger(DynamicSaslRegistrar.class); + + /** The name of the system property that holds the name of the SASL configuration properties. */ private static final String FILE_PROPERTY = "amq.dynamicsaslregistrar.properties"; - private static final Logger _logger = Logger.getLogger(DynamicSaslRegistrar.class); + /** The default name of the SASL properties file resource. */ + public static final String DEFAULT_RESOURCE_NAME = "org/apache/qpid/client/security/DynamicSaslRegistrar.properties"; + /** + * Reads the properties file, and creates a dynamic security provider to register the SASL implementations + * with. + */ public static void registerSaslProviders() { - InputStream is = openPropertiesInputStream(); + _logger.debug("public static void registerSaslProviders(): called"); + + // Open the SASL properties file, using the default name is one is not specified. + String filename = System.getProperty(FILE_PROPERTY); + InputStream is = + FileUtils.openFileOrDefaultResource(filename, DEFAULT_RESOURCE_NAME, + DynamicSaslRegistrar.class.getClassLoader()); + try { Properties props = new Properties(); props.load(is); + + _logger.debug("props = " + props); + Map> factories = parseProperties(props); + if (factories.size() > 0) { Security.addProvider(new JCAProvider(factories)); @@ -77,16 +119,30 @@ public class DynamicSaslRegistrar } } - private static InputStream openPropertiesInputStream() + /** + * Either attempts to open the specified filename as an input stream, or uses the default SASL configuration + * resource. + * + * @param filename The name of the file to get the SASL properties from, null to use the default. + * + * @return An input stream to read the dynamic SASL configuration from, or null if one could not be opened. + */ + /*private static InputStream openPropertiesInputStream(String filename) { - String filename = System.getProperty(FILE_PROPERTY); - boolean useDefault = true; InputStream is = null; + + // Flag to indicate whether the default resource should be used. By default this is true, so that the default + // is used when opening the file fails. + boolean useDefault = true; + + // Try to open the file if one was specified. if (filename != null) { try { is = new BufferedInputStream(new FileInputStream(new File(filename))); + + // Clear the default flag because the file was succesfully opened. useDefault = false; } catch (FileNotFoundException e) @@ -95,19 +151,35 @@ public class DynamicSaslRegistrar } } + // Load the default resource if a file was not specified, or if opening the file failed. if (useDefault) { - is = CallbackHandlerRegistry.class.getResourceAsStream("DynamicSaslRegistrar.properties"); + is = CallbackHandlerRegistry.class.getResourceAsStream(DEFAULT_RESOURCE_NAME); } return is; - } + }*/ + /** + * Parses the specified properties as a mapping from IANA registered SASL mechanism names to implementing client + * factories. If the client factories cannot be instantiated or do not implement SaslClientFactory then the + * properties refering to them are ignored. + * + * @param props The properties to scan for Sasl client factory implementations. + * + * @return A map from SASL mechanism names to implementing client factory classes. + * + * @todo Why tree map here? Do really want mechanisms in alphabetical order? Seems more likely that the declared + * order of the mechanisms is intended to be preserved, so that they are registered in the declared order + * of preference. Consider LinkedHashMap instead. + */ private static Map> parseProperties(Properties props) { Enumeration e = props.propertyNames(); + TreeMap> factoriesToRegister = - new TreeMap>(); + new TreeMap>(); + while (e.hasMoreElements()) { String mechanism = (String) e.nextElement(); @@ -118,17 +190,18 @@ public class DynamicSaslRegistrar if (!(SaslClientFactory.class.isAssignableFrom(clazz))) { _logger.error("Class " + clazz + " does not implement " + SaslClientFactory.class + " - skipping"); + continue; } + factoriesToRegister.put(mechanism, (Class) clazz); } catch (Exception ex) { - _logger.error("Error instantiating SaslClientFactory calss " + className + " - skipping"); + _logger.error("Error instantiating SaslClientFactory calss " + className + " - skipping"); } } + return factoriesToRegister; } - - } diff --git a/java/client/src/main/java/org/apache/qpid/client/security/DynamicSaslRegistrar.properties b/java/client/src/main/java/org/apache/qpid/client/security/DynamicSaslRegistrar.properties index ee66664455..c2a7d7928c 100644 --- a/java/client/src/main/java/org/apache/qpid/client/security/DynamicSaslRegistrar.properties +++ b/java/client/src/main/java/org/apache/qpid/client/security/DynamicSaslRegistrar.properties @@ -6,9 +6,9 @@ # to you under the Apache License, Version 2.0 (the # "License"); you may not use this file except in compliance # with the License. You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY diff --git a/java/client/src/main/java/org/apache/qpid/client/security/JCAProvider.java b/java/client/src/main/java/org/apache/qpid/client/security/JCAProvider.java index ff3ead6d42..2fa8dcddde 100644 --- a/java/client/src/main/java/org/apache/qpid/client/security/JCAProvider.java +++ b/java/client/src/main/java/org/apache/qpid/client/security/JCAProvider.java @@ -7,9 +7,9 @@ * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY @@ -26,22 +26,46 @@ import java.util.Map; import javax.security.sasl.SaslClientFactory; +import org.apache.log4j.Logger; + +/** + * JCAProvider is a security provider for SASL client factories that is configured from a map of SASL mechanism names + * to client factories implementation class names. It is intended that the map of client factories can be read from a + * configuration file or other application configuration mechanism. + * + *

+ *
CRC Card
Responsibilities Collaborations + *
Register SASL mechanism implementations. + *
+ */ public class JCAProvider extends Provider { + private static final Logger log = Logger.getLogger(JCAProvider.class); + + /** + * Creates the security provider with a map from SASL mechanisms to implementing factories. + * + * @param providerMap The map from SASL mechanims to implementing factory classes. + */ public JCAProvider(Map> providerMap) { - super("AMQSASLProvider", 1.0, "A JCA provider that registers all " + - "AMQ SASL providers that want to be registered"); + super("AMQSASLProvider", 1.0, "A JCA provider that registers all " + + "AMQ SASL providers that want to be registered"); register(providerMap); Security.addProvider(this); } + /** + * Registers client factory classes for a map of mechanism names to client factory classes. + * + * @param providerMap The map from SASL mechanims to implementing factory classes. + */ private void register(Map> providerMap) { - for (Map.Entry> me : - providerMap.entrySet()) + for (Map.Entry> me : providerMap.entrySet()) { put("SaslClientFactory." + me.getKey(), me.getValue().getName()); + log.debug("Registered SASL Client factory for " + me.getKey() + " as " + me.getValue().getName()); } } } diff --git a/java/common/pom.xml b/java/common/pom.xml index c8168c34bb..5e94153346 100644 --- a/java/common/pom.xml +++ b/java/common/pom.xml @@ -15,7 +15,7 @@ KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. - --> +--> @@ -67,35 +67,37 @@ - org.codehaus.mojo - retrotranslator-maven-plugin - - - package - - translate - - - ${project.build.directory}/${project.build.finalName}-java1.4.jar - ${retrotranslator.verify} - - ${retrotranslator.1.4-rt-path} - ${retrotranslator.1.4-jce-path} - ${retrotranslator.1.4-jsse-path} - - - - ${project.build.directory} - ${project.build.finalName}.jar - - - - + org.codehaus.mojo + retrotranslator-maven-plugin + + + package + + translate + + + ${project.build.directory}/${project.build.finalName}-java14.jar + ${retrotranslator.verify} + + ${retrotranslator.1.4-rt-path} + ${retrotranslator.1.4-jce-path} + ${retrotranslator.1.4-jsse-path} + ${retrotranslator.1.4-sasl-path} + + false + + + ${project.build.directory} + ${project.build.finalName}.jar + + + + - + - - + + org.codehaus.mojo build-helper-maven-plugin @@ -109,9 +111,9 @@ - ${project.build.directory}/${project.build.finalName}-java1.4.jar + ${project.build.directory}/${project.build.finalName}-java14.jar jar - java1.4 + java14 @@ -156,12 +158,12 @@ test - - - net.sf.retrotranslator - retrotranslator-runtime - provided - + + + net.sf.retrotranslator + retrotranslator-runtime + provided + diff --git a/java/common/src/main/java/org/apache/qpid/util/FileUtils.java b/java/common/src/main/java/org/apache/qpid/util/FileUtils.java new file mode 100644 index 0000000000..ba79a6e8d4 --- /dev/null +++ b/java/common/src/main/java/org/apache/qpid/util/FileUtils.java @@ -0,0 +1,161 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.util; + +import java.io.*; + +import org.apache.log4j.Logger; + +/** + * FileUtils provides some simple helper methods for working with files. It follows the convention of wrapping all + * checked exceptions as runtimes, so code using these methods is free of try-catch blocks but does not expect to + * recover from errors. + * + *

+ *
CRC Card
Responsibilities Collaborations + *
Read a text file as a string. + *
Open a file or default resource as an input stream. + *
+ */ +public class FileUtils +{ + /** + * Reads a text file as a string. + * + * @param filename The name of the file. + * + * @return The contents of the file. + */ + public static String readFileAsString(String filename) + { + BufferedInputStream is = null; + + try + { + is = new BufferedInputStream(new FileInputStream(filename)); + } + catch (FileNotFoundException e) + { + throw new RuntimeException(e); + } + + return readStreamAsString(is); + } + + /** + * Reads a text file as a string. + * + * @param file The file. + * + * @return The contents of the file. + */ + public static String readFileAsString(File file) + { + BufferedInputStream is = null; + + try + { + is = new BufferedInputStream(new FileInputStream(file)); + } + catch (FileNotFoundException e) + { + throw new RuntimeException(e); + } + + return readStreamAsString(is); + } + + /** + * Reads the contents of a reader, one line at a time until the end of stream is encountered, and returns all + * together as a string. + * + * @param is The reader. + * + * @return The contents of the reader. + */ + private static String readStreamAsString(BufferedInputStream is) + { + try + { + byte[] data = new byte[4096]; + + StringBuffer inBuffer = new StringBuffer(); + + String line; + int read; + + while ((read = is.read(data)) != -1) + { + String s = new String(data, 0, read); + inBuffer.append(s); + } + + return inBuffer.toString(); + } + catch (IOException e) + { + throw new RuntimeException(e); + } + } + + /** + * Either opens the specified filename as an input stream, or uses the default resource loaded using the + * specified class loader, if opening the file fails or no file name is specified. + * + * @param filename The name of the file to open. + * @param defaultResource The name of the default resource on the classpath if the file cannot be opened. + * @param cl The classloader to load the default resource with. + * + * @return An input stream for the file or resource, or null if one could not be opened. + */ + public static InputStream openFileOrDefaultResource(String filename, String defaultResource, ClassLoader cl) + { + InputStream is = null; + + // Flag to indicate whether the default resource should be used. By default this is true, so that the default + // is used when opening the file fails. + boolean useDefault = true; + + // Try to open the file if one was specified. + if (filename != null) + { + try + { + is = new BufferedInputStream(new FileInputStream(new File(filename))); + + // Clear the default flag because the file was succesfully opened. + useDefault = false; + } + catch (FileNotFoundException e) + { + // Ignore this exception, the default will be used instead. + } + } + + // Load the default resource if a file was not specified, or if opening the file failed. + if (useDefault) + { + is = cl.getResourceAsStream(defaultResource); + } + + return is; + } +} diff --git a/java/common/src/main/java/org/apache/qpid/util/PrettyPrintingUtils.java b/java/common/src/main/java/org/apache/qpid/util/PrettyPrintingUtils.java new file mode 100644 index 0000000000..faeb9d7167 --- /dev/null +++ b/java/common/src/main/java/org/apache/qpid/util/PrettyPrintingUtils.java @@ -0,0 +1,73 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.util; + +/** + * Contains pretty printing convenienve methods for producing formatted logging output, mostly for debugging purposes. + * + *

+ *
CRC Card
Responsibilities Collaborations + *
+ */ +public class PrettyPrintingUtils +{ + /** + * Pretty prints an array of ints as a string. + * + * @param array The array to pretty print. + * + * @return The pretty printed string. + */ + public static String printArray(int[] array) + { + String result = "["; + for (int i = 0; i < array.length; i++) + { + result += array[i]; + result += (i < (array.length - 1)) ? ", " : ""; + } + + result += "]"; + + return result; + } + + /** + * Pretty prints an array of strings as a string. + * + * @param array The array to pretty print. + * + * @return The pretty printed string. + */ + public static String printArray(String[] array) + { + String result = "["; + for (int i = 0; i < array.length; i++) + { + result += array[i]; + result += (i < (array.length - 1)) ? ", " : ""; + } + + result += "]"; + + return result; + } +} diff --git a/java/pom.xml b/java/pom.xml index 41b0b97674..0b7ecba55b 100644 --- a/java/pom.xml +++ b/java/pom.xml @@ -1,22 +1,22 @@ +Licensed to the Apache Software Foundation (ASF) under one +or more contributor license agreements. See the NOTICE file +distributed with this work for additional information +regarding copyright ownership. The ASF licenses this file +to you under the Apache License, Version 2.0 (the +"License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. +--> @@ -49,9 +49,9 @@ - Qpid_Site - Qpid Site - file:/temp + Qpid_Site + Qpid Site + file:/temp @@ -98,9 +98,9 @@ -Xlint:fallthrough,finally + This should always point to a default minimal log4j configuration that all developers are happy with as a useable default. To use your own + log4j preferences set up an alternative in your settings.xml and avoid corrupting the default with private preferences. + --> warn @@ -121,16 +121,17 @@ 2.0-beta-5 2.1-SNAPSHOT 2.2 - 1.0-alpha-1 - 1.0 + 1.0-SNAPSHOT + 1.0 ${basedir}/${topDirectoryLocation}/../workspace /set/clover/license/path/here - - false + + false pathto/rt.jar pathto/jce.jar pathto/jsse.jar + pathto/sasl.jar @@ -213,49 +214,49 @@ - - - version_properties - compile - - - - - - - - - - - - - - - - - - run - + + + version_properties + compile + + + + + + + + + + + + + + + + + + run + - + @@ -306,16 +307,16 @@ ${surefire.fork.mode} false -ea - + amqj.logging.level ${amqj.logging.level} - - + @@ -384,15 +385,15 @@ - - - net.sf.retrotranslator - retrotranslator-runtime - 1.0.8 - provided - + + + net.sf.retrotranslator + retrotranslator-runtime + 1.2.1 + provided + - + commons-cli commons-cli @@ -566,14 +567,14 @@ @@ -586,6 +587,15 @@ true + + + codehaus.snapshots + Codehaus SNAPSHOT Repository + http://snapshots.repository.codehaus.org + + true + + -- cgit v1.2.1