/* * * 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.transport; import org.ietf.jgss.GSSContext; import org.ietf.jgss.GSSException; import org.ietf.jgss.GSSManager; import org.ietf.jgss.GSSName; import org.ietf.jgss.Oid; import org.apache.qpid.security.UsernamePasswordCallbackHandler; import static org.apache.qpid.transport.Connection.State.OPEN; import static org.apache.qpid.transport.Connection.State.RESUMING; import org.apache.qpid.transport.util.Logger; import javax.security.sasl.Sasl; import javax.security.sasl.SaslClient; import javax.security.sasl.SaslException; import java.lang.management.ManagementFactory; import java.lang.management.RuntimeMXBean; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; /** * ClientDelegate * */ public class ClientDelegate extends ConnectionDelegate { private static final Logger log = Logger.get(ClientDelegate.class); private static final String KRB5_OID_STR = "1.2.840.113554.1.2.2"; protected static final Oid KRB5_OID; static { Oid oid; try { oid = new Oid(KRB5_OID_STR); } catch (GSSException ignore) { oid = null; } KRB5_OID = oid; } private List clientMechs; private ConnectionSettings conSettings; public ClientDelegate(ConnectionSettings settings) { this.conSettings = settings; this.clientMechs = Arrays.asList(settings.getSaslMechs().split(" ")); } public void init(Connection conn, ProtocolHeader hdr) { if (!(hdr.getMajor() == 0 && hdr.getMinor() == 10)) { conn.exception(new ProtocolVersionException(hdr.getMajor(), hdr.getMinor())); } } @Override public void connectionStart(Connection conn, ConnectionStart start) { Map clientProperties = new HashMap(); if(this.conSettings.getClientProperties() != null) { clientProperties.putAll(this.conSettings.getClientProperties()); } clientProperties.put("qpid.session_flow", 1); clientProperties.put("qpid.client_pid",getPID()); clientProperties.put("qpid.client_process", System.getProperty("qpid.client_process","Qpid Java Client")); List brokerMechs = start.getMechanisms(); if (brokerMechs == null || brokerMechs.isEmpty()) { conn.connectionStartOk (clientProperties, null, null, conn.getLocale()); return; } List choosenMechs = new ArrayList(); for (String mech:clientMechs) { if (brokerMechs.contains(mech)) { choosenMechs.add(mech); } } if (choosenMechs.size() == 0) { conn.exception(new ConnectionException("The following SASL mechanisms " + clientMechs.toString() + " specified by the client are not supported by the broker")); return; } String[] mechs = new String[choosenMechs.size()]; choosenMechs.toArray(mechs); conn.setServerProperties(start.getServerProperties()); try { Map saslProps = new HashMap(); if (conSettings.isUseSASLEncryption()) { saslProps.put(Sasl.QOP, "auth-conf"); } UsernamePasswordCallbackHandler handler = new UsernamePasswordCallbackHandler(); handler.initialise(conSettings.getUsername(), conSettings.getPassword()); SaslClient sc = Sasl.createSaslClient (mechs, null, conSettings.getSaslProtocol(), conSettings.getSaslServerName(), saslProps, handler); conn.setSaslClient(sc); byte[] response = sc.hasInitialResponse() ? sc.evaluateChallenge(new byte[0]) : null; conn.connectionStartOk (clientProperties, sc.getMechanismName(), response, conn.getLocale()); } catch (SaslException e) { conn.exception(e); } } @Override public void connectionSecure(Connection conn, ConnectionSecure secure) { SaslClient sc = conn.getSaslClient(); try { byte[] response = sc.evaluateChallenge(secure.getChallenge()); conn.connectionSecureOk(response); } catch (SaslException e) { conn.exception(e); } } @Override public void connectionTune(Connection conn, ConnectionTune tune) { int hb_interval = calculateHeartbeatInterval(conSettings.getHeartbeatInterval(), tune.getHeartbeatMin(), tune.getHeartbeatMax() ); conn.connectionTuneOk(tune.getChannelMax(), tune.getMaxFrameSize(), hb_interval); // The idle timeout is twice the heartbeat amount (in milisecs) conn.setIdleTimeout(hb_interval*1000*2); int channelMax = tune.getChannelMax(); //0 means no implied limit, except available server resources //(or that forced by protocol limitations [0xFFFF]) conn.setChannelMax(channelMax == 0 ? Connection.MAX_CHANNEL_MAX : channelMax); conn.connectionOpen(conSettings.getVhost(), null, Option.INSIST); } @Override public void connectionOpenOk(Connection conn, ConnectionOpenOk ok) { SaslClient sc = conn.getSaslClient(); if (sc != null) { if (sc.getMechanismName().equals("GSSAPI")) { String id = getKerberosUser(); if (id != null) { conn.setUserID(id); } } else if (sc.getMechanismName().equals("EXTERNAL")) { if (conn.getSecurityLayer() != null) { conn.setUserID(conn.getSecurityLayer().getUserID()); } } } if (conn.isConnectionResuming()) { conn.setState(RESUMING); } else { conn.setState(OPEN); } } @Override public void connectionRedirect(Connection conn, ConnectionRedirect redir) { throw new UnsupportedOperationException(); } @Override public void connectionHeartbeat(Connection conn, ConnectionHeartbeat hearbeat) { conn.connectionHeartbeat(); } /** * Currently the spec specified the min and max for heartbeat using secs */ private int calculateHeartbeatInterval(int heartbeat,int min, int max) { int i = heartbeat; if (i == 0) { log.info("Idle timeout is 0 sec. Heartbeats are disabled."); return 0; // heartbeats are disabled. } else if (i >= min && i <= max) { return i; } else { log.info("The broker does not support the configured connection idle timeout of %s sec," + " using the brokers max supported value of %s sec instead.", i,max); return max; } } private int getPID() { RuntimeMXBean rtb = ManagementFactory.getRuntimeMXBean(); String processName = rtb.getName(); if (processName != null && processName.indexOf('@')>0) { try { return Integer.parseInt(processName.substring(0,processName.indexOf('@'))); } catch(Exception e) { log.warn("Unable to get the client PID due to error",e); return -1; } } else { log.warn("Unable to get the client PID due to unsupported format : " + processName); return -1; } } private String getKerberosUser() { log.debug("Obtaining userID from kerberos"); String service = conSettings.getSaslProtocol() + "@" + conSettings.getSaslServerName(); GSSManager manager = GSSManager.getInstance(); try { GSSName acceptorName = manager.createName(service, GSSName.NT_HOSTBASED_SERVICE, KRB5_OID); GSSContext secCtx = manager.createContext(acceptorName, KRB5_OID, null, GSSContext.INDEFINITE_LIFETIME); secCtx.initSecContext(new byte[0], 0, 1); if (secCtx.getSrcName() != null) { return secCtx.getSrcName().toString(); } } catch (GSSException e) { log.warn("Unable to retrieve userID from Kerberos due to error",e); } return null; } }