diff options
Diffstat (limited to 'java/common')
20 files changed, 902 insertions, 54 deletions
diff --git a/java/common/src/main/java/org/apache/qpidity/Channel.java b/java/common/src/main/java/org/apache/qpidity/Channel.java index f20c65e467..483b5e7f21 100644 --- a/java/common/src/main/java/org/apache/qpidity/Channel.java +++ b/java/common/src/main/java/org/apache/qpidity/Channel.java @@ -35,30 +35,31 @@ import static org.apache.qpidity.Functions.*; * @author Rafael H. Schloming */ -class Channel extends Invoker implements Handler<Frame> +public class Channel extends Invoker implements Handler<Frame> { final private Connection connection; final private int channel; final private TrackSwitch<Channel> tracks; final private Delegate<Channel> delegate; - + final private SessionDelegate sessionDelegate; // session may be null private Session session; private Method method = null; private List<ByteBuffer> data = null; private int dataSize; - + public Channel(Connection connection, int channel, SessionDelegate delegate) { this.connection = connection; this.channel = channel; this.delegate = new ChannelDelegate(); - + this.sessionDelegate = delegate; + tracks = new TrackSwitch<Channel>(); tracks.map(L1, new MethodHandler<Channel> - (getMajor(), getMinor(), this.delegate)); + (getMajor(), getMinor(), connection.getConnectionDelegate())); tracks.map(L2, new MethodHandler<Channel> (getMajor(), getMinor(), this.delegate)); tracks.map(L3, new SessionResolver<Frame> diff --git a/java/common/src/main/java/org/apache/qpidity/Connection.java b/java/common/src/main/java/org/apache/qpidity/Connection.java index 9171208a28..c387a38b17 100644 --- a/java/common/src/main/java/org/apache/qpidity/Connection.java +++ b/java/common/src/main/java/org/apache/qpidity/Connection.java @@ -36,7 +36,8 @@ import java.nio.ByteBuffer; * short instead of Short */ -class Connection implements ProtocolActions +// RA making this public until we sort out the package issues +public class Connection implements ProtocolActions { final private Handler<ByteBuffer> input; @@ -58,6 +59,11 @@ class Connection implements ProtocolActions this.delegate = delegate; } + public ConnectionDelegate getConnectionDelegate() + { + return delegate; + } + public Connection(Handler<ByteBuffer> output, ConnectionDelegate delegate) { @@ -103,6 +109,9 @@ class Connection implements ProtocolActions output.handle(header.toByteBuffer()); // XXX: how do we close the connection? } + + // not sure if this is the right place + getChannel(0).connectionStart(header.getMajor(), header.getMinor(), null, "PLAIN", "utf8"); } public Channel getChannel(int number) diff --git a/java/common/src/main/java/org/apache/qpidity/ConnectionDelegate.java b/java/common/src/main/java/org/apache/qpidity/ConnectionDelegate.java index 9df264561c..537a7ef586 100644 --- a/java/common/src/main/java/org/apache/qpidity/ConnectionDelegate.java +++ b/java/common/src/main/java/org/apache/qpidity/ConnectionDelegate.java @@ -20,6 +20,17 @@ */ package org.apache.qpidity; +import java.io.UnsupportedEncodingException; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.Lock; + +import javax.security.sasl.Sasl; +import javax.security.sasl.SaslClient; +import javax.security.sasl.SaslException; +import javax.security.sasl.SaslServer; + /** * ConnectionDelegate @@ -27,9 +38,240 @@ package org.apache.qpidity; * @author Rafael H. Schloming */ -public interface ConnectionDelegate +/** + * Currently only implemented client specific methods + * the server specific methods are dummy impls for testing + * + * the connectionClose is kind of different for both sides + */ +public abstract class ConnectionDelegate extends Delegate<Channel> { + private String _username; + private String _password; + private String _mechanism; + private String _virtualHost; + private SaslClient saslClient; + private SaslServer saslServer; + private String _locale = "utf8"; + private int maxFrame = 64*1024; + private Condition _negotiationComplete; + private Lock _negotiationCompleteLock; + + public abstract SessionDelegate getSessionDelegate(); + + public void setCondition(Lock negotiationCompleteLock,Condition negotiationComplete) + { + _negotiationComplete = negotiationComplete; + _negotiationCompleteLock = negotiationCompleteLock; + } + + // ---------------------------------------------- + // Client side + //----------------------------------------------- + @Override public void connectionStart(Channel context, ConnectionStart struct) + { + System.out.println("The broker has sent connection-start"); + + String mechanism = null; + String response = null; + try + { + mechanism = SecurityHelper.chooseMechanism(struct.getMechanisms()); + saslClient = Sasl.createSaslClient(new String[]{ mechanism },null, "AMQP", "localhost", null, + SecurityHelper.createCallbackHandler(mechanism,_username,_password )); + response = new String(saslClient.evaluateChallenge(new byte[0]),_locale); + } + catch (UnsupportedEncodingException e) + { + // need error handling + } + catch (SaslException e) + { + // need error handling + } + catch (QpidException e) + { + // need error handling + } + + Map<String,?> props = new HashMap<String,String>(); + context.connectionStartOk(props, mechanism, response, _locale); + } + + @Override public void connectionSecure(Channel context, ConnectionSecure struct) + { + System.out.println("The broker has sent connection-secure with chanllenge " + struct.getChallenge()); + + try + { + String response = new String(saslClient.evaluateChallenge(struct.getChallenge().getBytes()),_locale); + context.connectionSecureOk(response); + } + catch (UnsupportedEncodingException e) + { + // need error handling + } + catch (SaslException e) + { + // need error handling + } + } + + @Override public void connectionTune(Channel context, ConnectionTune struct) + { + System.out.println("The broker has sent connection-tune " + struct.toString()); + + // should update the channel max given by the broker. + context.connectionTuneOk(struct.getChannelMax(), struct.getFrameMax(), struct.getHeartbeat()); + context.connectionOpen(_virtualHost, null, Option.INSIST); + } + + + @Override public void connectionOpenOk(Channel context, ConnectionOpenOk struct) + { + String knownHosts = struct.getKnownHosts(); + System.out.println("The broker has opened the connection for use"); + System.out.println("The broker supplied the following hosts for failover " + knownHosts); + _negotiationCompleteLock.lock(); + try + { + _negotiationComplete.signalAll(); + } + finally + { + _negotiationCompleteLock.unlock(); + } + } + + public void connectionRedirect(Channel context, ConnectionRedirect struct) + { + // not going to bother at the moment + } + + // ---------------------------------------------- + // Server side + //----------------------------------------------- + @Override public void connectionStartOk(Channel context, ConnectionStartOk struct) + { + //set the client side locale on the server side + _locale = struct.getLocale(); + _mechanism = struct.getMechanism(); + + System.out.println("The client has sent connection-start-ok"); + + //try + //{ + //saslServer = Sasl.createSaslServer(_mechanism, "AMQP", "ABC",null,SecurityHelper.createCallbackHandler(_mechanism,_username,_password)); + //byte[] challenge = saslServer.evaluateResponse(struct.getResponse().getBytes()); + byte[] challenge = null; + if ( challenge == null) + { + System.out.println("Authentication sucessfull"); + context.connectionTune(Integer.MAX_VALUE,maxFrame, 0); + } + else + { + System.out.println("Authentication failed"); + try + { + context.connectionSecure(new String(challenge,_locale)); + } + catch(Exception e) + { + + } + } + + + /*} + catch (SaslException e) + { + // need error handling + } + catch (QpidException e) + { + // need error handling + }*/ + } + + @Override public void connectionTuneOk(Channel context, ConnectionTuneOk struct) + { + System.out.println("The client has excepted the tune params"); + } + + @Override public void connectionSecureOk(Channel context, ConnectionSecureOk struct) + { + System.out.println("The client has sent connection-secure-ok"); + try + { + saslServer = Sasl.createSaslServer(_mechanism, "AMQP", "ABC",new HashMap(),SecurityHelper.createCallbackHandler(_mechanism,_username,_password)); + byte[] challenge = saslServer.evaluateResponse(struct.getResponse().getBytes()); + if ( challenge == null) + { + System.out.println("Authentication sucessfull"); + context.connectionTune(Integer.MAX_VALUE,maxFrame, 0); + } + else + { + System.out.println("Authentication failed"); + try + { + context.connectionSecure(new String(challenge,_locale)); + } + catch(Exception e) + { + + } + } + + + } + catch (SaslException e) + { + // need error handling + } + catch (QpidException e) + { + // need error handling + } + } + + + @Override public void connectionOpen(Channel context, ConnectionOpen struct) + { + String hosts = "amqp:1223243232325"; + System.out.println("The client has sent connection-open-ok"); + context.connectionOpenOk(hosts); + } + + + public String getPassword() + { + return _password; + } + + public void setPassword(String password) + { + _password = password; + } + + public String getUsername() + { + return _username; + } + + public void setUsername(String username) + { + _username = username; + } - SessionDelegate getSessionDelegate(); + public String getVirtualHost() + { + return _virtualHost; + } + public void setVirtualHost(String host) + { + _virtualHost = host; + } } diff --git a/java/common/src/main/java/org/apache/qpidity/Frame.java b/java/common/src/main/java/org/apache/qpidity/Frame.java index d5076e0ef0..89e7579cb3 100644 --- a/java/common/src/main/java/org/apache/qpidity/Frame.java +++ b/java/common/src/main/java/org/apache/qpidity/Frame.java @@ -35,7 +35,8 @@ import static org.apache.qpidity.Functions.*; * @author Rafael H. Schloming */ -class Frame implements Iterable<ByteBuffer> +// RA: changed it to public until we sort the package issues +public class Frame implements Iterable<ByteBuffer> { public static final int HEADER_SIZE = 12; diff --git a/java/common/src/main/java/org/apache/qpidity/MinaHandler.java b/java/common/src/main/java/org/apache/qpidity/MinaHandler.java index a40753ed91..f255b56d0b 100644 --- a/java/common/src/main/java/org/apache/qpidity/MinaHandler.java +++ b/java/common/src/main/java/org/apache/qpidity/MinaHandler.java @@ -42,8 +42,8 @@ import org.apache.mina.transport.socket.nio.SocketConnector; * * @author Rafael H. Schloming */ - -class MinaHandler implements IoHandler +//RA making this public until we sort out the package issues +public class MinaHandler implements IoHandler { private final ConnectionDelegate delegate; @@ -124,7 +124,8 @@ class MinaHandler implements IoHandler { IoAcceptor acceptor = new SocketAcceptor(); acceptor.bind(new InetSocketAddress(host, port), - new MinaHandler(delegate, InputHandler.State.PROTO_HDR)); + new MinaHandler(delegate, InputHandler.State.PROTO_HDR)); + } public static final Connection connect(String host, int port, diff --git a/java/common/src/main/java/org/apache/qpidity/ProtocolHeader.java b/java/common/src/main/java/org/apache/qpidity/ProtocolHeader.java index 56c73d1f00..140d5ecbe3 100644 --- a/java/common/src/main/java/org/apache/qpidity/ProtocolHeader.java +++ b/java/common/src/main/java/org/apache/qpidity/ProtocolHeader.java @@ -29,7 +29,9 @@ import java.nio.ByteBuffer; * @author Rafael H. Schloming */ -class ProtocolHeader +//RA making this public until we sort out the package issues + +public class ProtocolHeader { private static final byte[] AMQP = {'A', 'M', 'Q', 'P' }; diff --git a/java/common/src/main/java/org/apache/qpidity/QpidConfig.java b/java/common/src/main/java/org/apache/qpidity/QpidConfig.java new file mode 100644 index 0000000000..b5aad12f10 --- /dev/null +++ b/java/common/src/main/java/org/apache/qpidity/QpidConfig.java @@ -0,0 +1,90 @@ +package org.apache.qpidity; + +/** + * API to configure the Security parameters of the client. + * The user can choose to pick the config from any source + * and set it using this class. + * + */ +public class QpidConfig +{ + private static QpidConfig _instance = new QpidConfig(); + + private SecurityMechanism[] securityMechanisms = + new SecurityMechanism[]{new SecurityMechanism("PLAIN","org.apache.qpidity.security.UsernamePasswordCallbackHandler"), + new SecurityMechanism("CRAM_MD5","org.apache.qpidity.security.UsernamePasswordCallbackHandler")}; + + private SaslClientFactory[] saslClientFactories = + new SaslClientFactory[]{new SaslClientFactory("AMQPLAIN","org.apache.qpidity.security.amqplain.AmqPlainSaslClientFactory")}; + + private QpidConfig(){} + + public static QpidConfig get() + { + return _instance; + } + + public void setSecurityMechanisms(SecurityMechanism... securityMechanisms) + { + this.securityMechanisms = securityMechanisms; + } + + public SecurityMechanism[] getSecurityMechanisms() + { + return securityMechanisms; + } + + public void setSaslClientFactories(SaslClientFactory... saslClientFactories) + { + this.saslClientFactories = saslClientFactories; + } + + public SaslClientFactory[] getSaslClientFactories() + { + return saslClientFactories; + } + + public class SecurityMechanism + { + String type; + String handler; + + SecurityMechanism(String type,String handler) + { + this.type = type; + this.handler = handler; + } + + public String getHandler() + { + return handler; + } + + public String getType() + { + return type; + } + } + + public class SaslClientFactory + { + String type; + String factoryClass; + + SaslClientFactory(String type,String factoryClass) + { + this.type = type; + this.factoryClass = factoryClass; + } + + public String getFactoryClass() + { + return factoryClass; + } + + public String getType() + { + return type; + } + } +} diff --git a/java/common/src/main/java/org/apache/qpidity/QpidException.java b/java/common/src/main/java/org/apache/qpidity/QpidException.java index 4ab99b677f..5b3671cebd 100644 --- a/java/common/src/main/java/org/apache/qpidity/QpidException.java +++ b/java/common/src/main/java/org/apache/qpidity/QpidException.java @@ -25,12 +25,9 @@ package org.apache.qpidity; public class QpidException extends Exception { /** - * This exception error code. - * <p> This error code is used for internationalisation purpose. - * <p> This error code is set from the AMQP ones. - * <TODO> So we may want to use the AMQP error code directly. + * AMQP error code */ - private String _errorCode; + private int _errorCode; /** * Constructor for a Qpid Exception. @@ -38,20 +35,27 @@ public class QpidException extends Exception * they are unknown. * @param message A description of the reason of this exception . * @param errorCode A string specifyin the error code of this exception. - * @param cause The linked Execption. + * @param cause The linked Execption. * + * */ - public QpidException(String message, String errorCode, Throwable cause) + public QpidException(String message, int errorCode, Throwable cause) { super(message, cause); _errorCode = errorCode; } + + //hack to get rid of a compile error from a generated class + public QpidException(String message, String errorCode, Throwable cause) + { + + } /** * Get this execption error code. * * @return This exception error code. */ - public String getErrorCode() + public int getErrorCode() { return _errorCode; } diff --git a/java/common/src/main/java/org/apache/qpidity/SecurityHelper.java b/java/common/src/main/java/org/apache/qpidity/SecurityHelper.java new file mode 100644 index 0000000000..474e2f7e8f --- /dev/null +++ b/java/common/src/main/java/org/apache/qpidity/SecurityHelper.java @@ -0,0 +1,71 @@ +/* + * + * 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.qpidity; + +import java.io.UnsupportedEncodingException; +import java.util.HashSet; +import java.util.StringTokenizer; + +import org.apache.qpidity.security.AMQPCallbackHandler; +import org.apache.qpidity.security.CallbackHandlerRegistry; + +public class SecurityHelper +{ + public static String chooseMechanism(String mechanisms) throws UnsupportedEncodingException + { + StringTokenizer tokenizer = new StringTokenizer(mechanisms, " "); + HashSet mechanismSet = new HashSet(); + while (tokenizer.hasMoreTokens()) + { + mechanismSet.add(tokenizer.nextToken()); + } + + String preferredMechanisms = CallbackHandlerRegistry.getInstance().getMechanisms(); + StringTokenizer prefTokenizer = new StringTokenizer(preferredMechanisms, " "); + while (prefTokenizer.hasMoreTokens()) + { + String mech = prefTokenizer.nextToken(); + if (mechanismSet.contains(mech)) + { + return mech; + } + } + return null; + } + + public static AMQPCallbackHandler createCallbackHandler(String mechanism, String username,String password) + throws QpidException + { + Class mechanismClass = CallbackHandlerRegistry.getInstance().getCallbackHandlerClass(mechanism); + try + { + Object instance = mechanismClass.newInstance(); + AMQPCallbackHandler cbh = (AMQPCallbackHandler) instance; + cbh.initialise(username,password); + return cbh; + } + catch (Exception e) + { + throw new QpidException("Unable to create callback handler: " + e,0, e.getCause()); + } + } + +} diff --git a/java/common/src/main/java/org/apache/qpidity/Session.java b/java/common/src/main/java/org/apache/qpidity/Session.java index 3b33fde2df..d3a24ffd8a 100644 --- a/java/common/src/main/java/org/apache/qpidity/Session.java +++ b/java/common/src/main/java/org/apache/qpidity/Session.java @@ -42,11 +42,12 @@ public class Session extends Invoker // completed incoming commands private final RangeSet processed = new RangeSet(); private Range syncPoint = null; - + // outgoing command count private long commandsOut = 0; private Map<Long,Method> commands = new HashMap<Long,Method>(); private long mark = 0; + public Map<Long,Method> getOutstandingCommands() { @@ -231,7 +232,6 @@ public class Session extends Invoker } future.set(result); } - protected <T> Future<T> invoke(Method m, Class<T> klass) { long command = commandsOut; diff --git a/java/common/src/main/java/org/apache/qpidity/ToyBroker.java b/java/common/src/main/java/org/apache/qpidity/ToyBroker.java index 651241f63c..683008fe8a 100644 --- a/java/common/src/main/java/org/apache/qpidity/ToyBroker.java +++ b/java/common/src/main/java/org/apache/qpidity/ToyBroker.java @@ -190,13 +190,20 @@ class ToyBroker extends SessionDelegate { final Map<String,Queue<Message>> queues = new HashMap<String,Queue<Message>>(); - MinaHandler.accept("0.0.0.0", 5672, new ConnectionDelegate() - { - public SessionDelegate getSessionDelegate() - { - return new ToyBroker(queues); - } - }); + + ConnectionDelegate delegate = new ConnectionDelegate() + { + public SessionDelegate getSessionDelegate() + { + return new ToyBroker(queues); + } + }; + + //hack + delegate.setUsername("guest"); + delegate.setPassword("guest"); + + MinaHandler.accept("0.0.0.0", 5672, delegate); } } diff --git a/java/common/src/main/java/org/apache/qpidity/api/Message.java b/java/common/src/main/java/org/apache/qpidity/api/Message.java index 2305027556..ccad3577f0 100644 --- a/java/common/src/main/java/org/apache/qpidity/api/Message.java +++ b/java/common/src/main/java/org/apache/qpidity/api/Message.java @@ -1,5 +1,7 @@ package org.apache.qpidity.api; +import java.nio.ByteBuffer; + import org.apache.qpidity.MessageProperties; import org.apache.qpidity.DeliveryProperties; @@ -43,6 +45,8 @@ public interface Message */ public void appendData(byte[] src); + public void appendData(ByteBuffer src); + /** * This will abstract the underlying message data. * The Message implementation may not hold all message diff --git a/java/common/src/main/java/org/apache/qpidity/filter/PropertyExpression.java b/java/common/src/main/java/org/apache/qpidity/filter/PropertyExpression.java index 7db5cd5e11..ac3888d5bb 100644 --- a/java/common/src/main/java/org/apache/qpidity/filter/PropertyExpression.java +++ b/java/common/src/main/java/org/apache/qpidity/filter/PropertyExpression.java @@ -56,7 +56,7 @@ public class PropertyExpression implements Expression } catch (Exception e) { - throw new QpidException("cannot evaluate property ", "message selector", e); + throw new QpidException("cannot evaluate property ", 0, e); } } return result; diff --git a/java/common/src/main/java/org/apache/qpidity/CommonSessionDelegate.java b/java/common/src/main/java/org/apache/qpidity/security/AMQPCallbackHandler.java index cd9d31b1c2..2e7afa1b87 100644 --- a/java/common/src/main/java/org/apache/qpidity/CommonSessionDelegate.java +++ b/java/common/src/main/java/org/apache/qpidity/security/AMQPCallbackHandler.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 @@ -18,28 +18,11 @@ * under the License. * */ -package org.apache.qpidity; +package org.apache.qpidity.security; -/** - * CommonSessionDelegate - */ +import javax.security.auth.callback.CallbackHandler; -public class CommonSessionDelegate extends Delegate<Session> +public interface AMQPCallbackHandler extends CallbackHandler { - - @Override public void sessionAttached(Session session, SessionAttached struct) {} - - @Override public void sessionFlow(Session session, SessionFlow struct) {} - - @Override public void sessionFlowOk(Session session, SessionFlowOk struct) {} - - @Override public void sessionClose(Session session, SessionClose struct) {} - - @Override public void sessionClosed(Session session, SessionClosed struct) {} - - @Override public void sessionResume(Session session, SessionResume struct) {} - - @Override public void sessionSuspend(Session session, SessionSuspend struct) {} - - @Override public void sessionDetached(Session session, SessionDetached struct) {} + void initialise(String username,String password); } diff --git a/java/common/src/main/java/org/apache/qpidity/security/CallbackHandlerRegistry.java b/java/common/src/main/java/org/apache/qpidity/security/CallbackHandlerRegistry.java new file mode 100644 index 0000000000..624b015c69 --- /dev/null +++ b/java/common/src/main/java/org/apache/qpidity/security/CallbackHandlerRegistry.java @@ -0,0 +1,92 @@ +/* + * + * 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.qpidity.security; + +import java.util.HashMap; +import java.util.Map; + +import org.apache.qpidity.QpidConfig; + +public class CallbackHandlerRegistry +{ + + private static CallbackHandlerRegistry _instance = new CallbackHandlerRegistry(); + + private Map<String,Class> _mechanismToHandlerClassMap = new HashMap<String,Class>(); + + private StringBuilder _mechanisms; + + public static CallbackHandlerRegistry getInstance() + { + return _instance; + } + + public Class getCallbackHandlerClass(String mechanism) + { + return _mechanismToHandlerClassMap.get(mechanism); + } + + public String getMechanisms() + { + return _mechanisms.toString(); + } + + private CallbackHandlerRegistry() + { + // first we register any Sasl client factories + DynamicSaslRegistrar.registerSaslProviders(); + registerMechanisms(); + } + + private void registerMechanisms() + { + for (QpidConfig.SecurityMechanism securityMechanism: QpidConfig.get().getSecurityMechanisms() ) + { + Class clazz = null; + try + { + clazz = Class.forName(securityMechanism.getHandler()); + if (!AMQPCallbackHandler.class.isAssignableFrom(clazz)) + { + System.out.println("SASL provider " + clazz + " does not implement " + AMQPCallbackHandler.class + + ". Skipping"); + continue; + } + _mechanismToHandlerClassMap.put(securityMechanism.getType(), clazz); + if (_mechanisms == null) + { + + _mechanisms = new StringBuilder(); + _mechanisms.append(securityMechanism.getType()); + } + else + { + _mechanisms.append(" " + securityMechanism.getType()); + } + } + catch (ClassNotFoundException ex) + { + System.out.println("Unable to load class " + securityMechanism.getHandler() + ". Skipping that SASL provider"); + continue; + } + } + } +} diff --git a/java/common/src/main/java/org/apache/qpidity/security/DynamicSaslRegistrar.java b/java/common/src/main/java/org/apache/qpidity/security/DynamicSaslRegistrar.java new file mode 100644 index 0000000000..52dacc6985 --- /dev/null +++ b/java/common/src/main/java/org/apache/qpidity/security/DynamicSaslRegistrar.java @@ -0,0 +1,70 @@ +/* + * + * 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.qpidity.security; + +import java.security.Security; +import java.util.Map; +import java.util.TreeMap; + +import javax.security.sasl.SaslClientFactory; + +import org.apache.qpidity.QpidConfig; + +public class DynamicSaslRegistrar +{ + public static void registerSaslProviders() + { + Map<String, Class> factories = registerSaslClientFactories(); + if (factories.size() > 0) + { + Security.addProvider(new JCAProvider(factories)); + System.out.println("Dynamic SASL provider added as a security provider"); + } + } + + private static Map<String, Class> registerSaslClientFactories() + { + TreeMap<String, Class> factoriesToRegister = + new TreeMap<String, Class>(); + + for (QpidConfig.SaslClientFactory factory: QpidConfig.get().getSaslClientFactories()) + { + String className = factory.getFactoryClass(); + try + { + Class clazz = Class.forName(className); + if (!(SaslClientFactory.class.isAssignableFrom(clazz))) + { + System.out.println("Class " + clazz + " does not implement " + SaslClientFactory.class + " - skipping"); + continue; + } + factoriesToRegister.put(factory.getType(), clazz); + } + catch (Exception ex) + { + System.out.println("Error instantiating SaslClientFactory calss " + className + " - skipping"); + } + } + return factoriesToRegister; + } + + +} diff --git a/java/common/src/main/java/org/apache/qpidity/security/JCAProvider.java b/java/common/src/main/java/org/apache/qpidity/security/JCAProvider.java new file mode 100644 index 0000000000..c775171a5f --- /dev/null +++ b/java/common/src/main/java/org/apache/qpidity/security/JCAProvider.java @@ -0,0 +1,44 @@ +/* + * + * 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.qpidity.security; + +import java.security.Provider; +import java.security.Security; +import java.util.Map; + +public class JCAProvider extends Provider +{ + public JCAProvider(Map<String, Class> providerMap) + { + super("AMQSASLProvider", 1.0, "A JCA provider that registers all " + + "AMQ SASL providers that want to be registered"); + register(providerMap); + Security.addProvider(this); + } + + private void register(Map<String, Class> providerMap) + { + for (Map.Entry<String, Class> me :providerMap.entrySet()) + { + put("SaslClientFactory." + me.getKey(), me.getValue().getName()); + } + } +}
\ No newline at end of file diff --git a/java/common/src/main/java/org/apache/qpidity/security/UsernamePasswordCallbackHandler.java b/java/common/src/main/java/org/apache/qpidity/security/UsernamePasswordCallbackHandler.java new file mode 100644 index 0000000000..0fd647e015 --- /dev/null +++ b/java/common/src/main/java/org/apache/qpidity/security/UsernamePasswordCallbackHandler.java @@ -0,0 +1,60 @@ +/* + * + * 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.qpidity.security; + +import java.io.IOException; + +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.NameCallback; +import javax.security.auth.callback.PasswordCallback; +import javax.security.auth.callback.UnsupportedCallbackException; + +public class UsernamePasswordCallbackHandler implements AMQPCallbackHandler +{ + private String _username; + private String _password; + + public void initialise(String username,String password) + { + _username = username; + _password = password; + } + + public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException + { + for (int i = 0; i < callbacks.length; i++) + { + Callback cb = callbacks[i]; + if (cb instanceof NameCallback) + { + ((NameCallback)cb).setName(_username); + } + else if (cb instanceof PasswordCallback) + { + ((PasswordCallback)cb).setPassword((_password).toCharArray()); + } + else + { + throw new UnsupportedCallbackException(cb); + } + } + } +} diff --git a/java/common/src/main/java/org/apache/qpidity/security/amqplain/AmqPlainSaslClient.java b/java/common/src/main/java/org/apache/qpidity/security/amqplain/AmqPlainSaslClient.java new file mode 100644 index 0000000000..6e4a0218d2 --- /dev/null +++ b/java/common/src/main/java/org/apache/qpidity/security/amqplain/AmqPlainSaslClient.java @@ -0,0 +1,105 @@ +/* + * + * 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.qpidity.security.amqplain; + +import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.framing.FieldTableFactory; + +import javax.security.sasl.SaslClient; +import javax.security.sasl.SaslException; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.callback.NameCallback; +import javax.security.auth.callback.PasswordCallback; +import javax.security.auth.callback.Callback; + +/** + * Implements the "AMQPlain" authentication protocol that uses FieldTables to send username and pwd. + * + */ +public class AmqPlainSaslClient implements SaslClient +{ + /** + * The name of this mechanism + */ + public static final String MECHANISM = "AMQPLAIN"; + + private CallbackHandler _cbh; + + public AmqPlainSaslClient(CallbackHandler cbh) + { + _cbh = cbh; + } + + public String getMechanismName() + { + return "AMQPLAIN"; + } + + public boolean hasInitialResponse() + { + return true; + } + + public byte[] evaluateChallenge(byte[] challenge) throws SaslException + { + // we do not care about the prompt or the default name + NameCallback nameCallback = new NameCallback("prompt", "defaultName"); + PasswordCallback pwdCallback = new PasswordCallback("prompt", false); + Callback[] callbacks = new Callback[]{nameCallback, pwdCallback}; + try + { + _cbh.handle(callbacks); + } + catch (Exception e) + { + throw new SaslException("Error handling SASL callbacks: " + e, e); + } + FieldTable table = FieldTableFactory.newFieldTable(); + table.setString("LOGIN", nameCallback.getName()); + table.setString("PASSWORD", new String(pwdCallback.getPassword())); + return table.getDataAsBytes(); + } + + public boolean isComplete() + { + return true; + } + + public byte[] unwrap(byte[] incoming, int offset, int len) throws SaslException + { + throw new SaslException("Not supported"); + } + + public byte[] wrap(byte[] outgoing, int offset, int len) throws SaslException + { + throw new SaslException("Not supported"); + } + + public Object getNegotiatedProperty(String propName) + { + return null; + } + + public void dispose() throws SaslException + { + _cbh = null; + } +} diff --git a/java/common/src/main/java/org/apache/qpidity/security/amqplain/AmqPlainSaslClientFactory.java b/java/common/src/main/java/org/apache/qpidity/security/amqplain/AmqPlainSaslClientFactory.java new file mode 100644 index 0000000000..abc881f433 --- /dev/null +++ b/java/common/src/main/java/org/apache/qpidity/security/amqplain/AmqPlainSaslClientFactory.java @@ -0,0 +1,62 @@ +/* + * + * 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.qpidity.security.amqplain; + +import javax.security.sasl.SaslClientFactory; +import javax.security.sasl.SaslClient; +import javax.security.sasl.SaslException; +import javax.security.sasl.Sasl; +import javax.security.auth.callback.CallbackHandler; +import java.util.Map; + +public class AmqPlainSaslClientFactory implements SaslClientFactory +{ + public SaslClient createSaslClient(String[] mechanisms, String authorizationId, String protocol, String serverName, Map props, CallbackHandler cbh) throws SaslException + { + for (int i = 0; i < mechanisms.length; i++) + { + if (mechanisms[i].equals(AmqPlainSaslClient.MECHANISM)) + { + if (cbh == null) + { + throw new SaslException("CallbackHandler must not be null"); + } + return new AmqPlainSaslClient(cbh); + } + } + return null; + } + + public String[] getMechanismNames(Map props) + { + if (props.containsKey(Sasl.POLICY_NOPLAINTEXT) || + props.containsKey(Sasl.POLICY_NODICTIONARY) || + props.containsKey(Sasl.POLICY_NOACTIVE)) + { + // returned array must be non null according to interface documentation + return new String[0]; + } + else + { + return new String[]{AmqPlainSaslClient.MECHANISM}; + } + } +} |
