/*
* JBoss, Home of Professional Open Source.
* Copyright 2012, Red Hat, Inc., and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.as.protocol;
import org.jboss.as.protocol.logging.ProtocolLogger;
import org.jboss.remoting3.Connection;
import org.jboss.remoting3.Endpoint;
import org.jboss.remoting3.RemotingOptions;
import org.wildfly.security.auth.client.AuthenticationConfiguration;
import org.wildfly.security.auth.client.AuthenticationContext;
import org.wildfly.security.auth.client.AuthenticationContextConfigurationClient;
import org.wildfly.security.auth.client.MatchRule;
import org.xnio.IoFuture;
import org.xnio.OptionMap;
import org.xnio.Options;
import static java.security.AccessController.doPrivileged;
import static org.xnio.Options.SASL_POLICY_NOANONYMOUS;
import static org.xnio.Options.SASL_POLICY_NOPLAINTEXT;
import org.xnio.Property;
import org.xnio.Sequence;
import javax.net.ssl.SSLContext;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.UnsupportedCallbackException;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.NetworkInterface;
import java.net.URI;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
/**
* Protocol Connection utils.
*
* @author <a href="kabir.khan@jboss.com">Kabir Khan</a>
* @author Emanuel Muckenhuber
* @author <a href="mailto:darran.lofthouse@jboss.com">Darran Lofthouse</a>
*/
public class ProtocolConnectionUtils {
private static final String JBOSS_LOCAL_USER = "JBOSS-LOCAL-USER";
private static final AuthenticationContextConfigurationClient AUTH_CONFIGURATION_CLIENT = doPrivileged(AuthenticationContextConfigurationClient.ACTION);
/**
* Connect.
*
* @param configuration the connection configuration
* @return the future connection
* @throws IOException
*/
public static IoFuture<Connection> connect(final ProtocolConnectionConfiguration configuration) throws IOException {
return connect(configuration.getCallbackHandler(), configuration);
}
public static IoFuture<Connection> connect(final ProtocolConnectionConfiguration configuration, CallbackHandler handler) throws IOException {
final ProtocolConnectionConfiguration config = ProtocolConnectionConfiguration.copy(configuration);
config.setCallbackHandler(handler);
return connect(config);
}
public static IoFuture<Connection> connect(final ProtocolConnectionConfiguration configuration, CallbackHandler handler, Map<String, String> saslOptions, SSLContext sslContext) throws IOException {
final ProtocolConnectionConfiguration config = ProtocolConnectionConfiguration.copy(configuration);
config.setCallbackHandler(handler);
config.setSaslOptions(saslOptions);
config.setSslContext(sslContext);
return connect(config);
}
/**
* Connect sync.
*
* @param configuration the protocol configuration
* @return the connection
* @throws IOException
*/
public static Connection connectSync(final ProtocolConnectionConfiguration configuration) throws IOException {
long timeoutMillis = configuration.getConnectionTimeout();
CallbackHandler handler = configuration.getCallbackHandler();
final CallbackHandler actualHandler;
ProtocolTimeoutHandler timeoutHandler = configuration.getTimeoutHandler();
// Note: If a client supplies a ProtocolTimeoutHandler it is taking on full responsibility for timeout management.
if (timeoutHandler == null) {
GeneralTimeoutHandler defaultTimeoutHandler = new GeneralTimeoutHandler();
// No point wrapping our AnonymousCallbackHandler.
actualHandler = handler != null ? new WrapperCallbackHandler(defaultTimeoutHandler, handler) : null;
timeoutHandler = defaultTimeoutHandler;
} else {
actualHandler = handler != null ? handler : null;
}
final IoFuture<Connection> future = connect(actualHandler, configuration);
IoFuture.Status status = timeoutHandler.await(future, timeoutMillis);
if (status == IoFuture.Status.DONE) {
return future.get();
}
if (status == IoFuture.Status.FAILED) {
throw ProtocolLogger.ROOT_LOGGER.failedToConnect(configuration.getUri(), future.getException());
}
throw ProtocolLogger.ROOT_LOGGER.couldNotConnect(configuration.getUri());
}
public static Connection connectSync(final ProtocolConnectionConfiguration configuration, CallbackHandler handler) throws IOException {
final ProtocolConnectionConfiguration config = ProtocolConnectionConfiguration.copy(configuration);
config.setCallbackHandler(handler);
return connectSync(config);
}
public static Connection connectSync(final ProtocolConnectionConfiguration configuration, CallbackHandler handler, Map<String, String> saslOptions, SSLContext sslContext) throws IOException {
final ProtocolConnectionConfiguration config = ProtocolConnectionConfiguration.copy(configuration);
config.setCallbackHandler(handler);
config.setSaslOptions(saslOptions);
config.setSslContext(sslContext);
return connectSync(config);
}
private static IoFuture<Connection> connect(final CallbackHandler handler, final ProtocolConnectionConfiguration configuration) throws IOException {
configuration.validate();
final Endpoint endpoint = configuration.getEndpoint();
final OptionMap options = getOptions(configuration);
final SSLContext sslContext = configuration.getSslContext();
final URI uri = configuration.getUri();
String clientBindAddress = configuration.getClientBindAddress();
AuthenticationContext captured = AuthenticationContext.captureCurrent();
AuthenticationConfiguration mergedConfiguration = AUTH_CONFIGURATION_CLIENT.getAuthenticationConfiguration(uri, captured);
if (handler != null) mergedConfiguration = mergedConfiguration.useCallbackHandler(handler);
// We don't know the original index or the match rule so create a context with a single match all rule.
AuthenticationContext context = AuthenticationContext.empty().with(MatchRule.ALL, mergedConfiguration);
if (sslContext != null) context = context.withSsl(MatchRule.ALL, () -> sslContext);
if (clientBindAddress == null) {
return endpoint.connect(uri, options, context);
} else {
InetSocketAddress bindAddr = new InetSocketAddress(clientBindAddress, 0);
// TODO: bind address via connection builder
return endpoint.connect(configuration.getUri(), options, context);
}
}
private static OptionMap getOptions(final ProtocolConnectionConfiguration configuration) {
final Map<String, String> saslOptions = configuration.getSaslOptions();
final OptionMap.Builder builder = OptionMap.builder();
builder.set(SASL_POLICY_NOANONYMOUS, Boolean.FALSE);
builder.set(SASL_POLICY_NOPLAINTEXT, Boolean.FALSE);
builder.addAll(configuration.getOptionMap());
configureSaslMechnisms(saslOptions, isLocal(configuration.getUri()), builder);
List<Property> tempProperties = new ArrayList<Property>(saslOptions != null ? saslOptions.size() : 1);
tempProperties.add(Property.of("jboss.sasl.local-user.quiet-auth", "true"));
if (saslOptions != null) {
for (String currentKey : saslOptions.keySet()) {
tempProperties.add(Property.of(currentKey, saslOptions.get(currentKey)));
}
}
builder.set(Options.SASL_PROPERTIES, Sequence.of(tempProperties));
builder.set(Options.SSL_ENABLED, configuration.isSslEnabled());
builder.set(Options.SSL_STARTTLS, configuration.isUseStartTLS());
builder.set(RemotingOptions.SASL_PROTOCOL, "remote");
return builder.getMap();
}
private static void configureSaslMechnisms(Map<String, String> saslOptions, boolean isLocal, OptionMap.Builder builder) {
String[] mechanisms = null;
String listed;
if (saslOptions != null && (listed = saslOptions.get(Options.SASL_DISALLOWED_MECHANISMS.getName())) != null) {
// Disallowed mechanisms were passed via the saslOptions map; need to convert to an XNIO option
String[] split = listed.split(" ");
if (isLocal) {
mechanisms = new String[split.length + 1];
mechanisms[0] = JBOSS_LOCAL_USER;
System.arraycopy(split, 0, mechanisms, 1, split.length);
} else {
mechanisms = split;
}
} else if (!isLocal) {
mechanisms = new String[]{ JBOSS_LOCAL_USER };
}
if (mechanisms != null) {
builder.set(Options.SASL_DISALLOWED_MECHANISMS, Sequence.of(mechanisms));
}
if (saslOptions != null && (listed = saslOptions.get(Options.SASL_MECHANISMS.getName())) != null) {
// SASL mechanisms were passed via the saslOptions map; need to convert to an XNIO option
String[] split = listed.split(" ");
if (split.length > 0) {
builder.set(Options.SASL_MECHANISMS, Sequence.of(split));
}
}
}
private static boolean isLocal(final URI uri) {
try {
final String hostName = uri.getHost();
final InetAddress address = InetAddress.getByName(hostName);
NetworkInterface nic;
if (address.isLinkLocalAddress()) {
/*
* AS7-6382 On Windows the getByInetAddress was not identifying a NIC where the address did not have the zone
* ID, this manual iteration does allow for the address to be matched.
*/
Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
nic = null;
while (interfaces.hasMoreElements() && nic == null) {
NetworkInterface current = interfaces.nextElement();
Enumeration<InetAddress> addresses = current.getInetAddresses();
while (addresses.hasMoreElements() && nic == null) {
InetAddress currentAddress = addresses.nextElement();
if (address.equals(currentAddress)) {
nic = current;
}
}
}
} else {
nic = NetworkInterface.getByInetAddress(address);
}
return address.isLoopbackAddress() || nic != null;
} catch (Exception e) {
return false;
}
}
private static final class WrapperCallbackHandler implements CallbackHandler {
private final GeneralTimeoutHandler timeoutHandler;
private final CallbackHandler wrapped;
WrapperCallbackHandler(final GeneralTimeoutHandler timeoutHandler, final CallbackHandler toWrap) {
this.timeoutHandler = timeoutHandler;
this.wrapped = toWrap;
}
public void handle(final Callback[] callbacks) throws IOException, UnsupportedCallbackException {
try {
timeoutHandler.suspendAndExecute(new Runnable() {
@Override
public void run() {
try {
wrapped.handle(callbacks);
} catch (IOException e) {
throw new RuntimeException(e);
} catch (UnsupportedCallbackException e) {
throw new RuntimeException(e);
}
}
});
} catch (RuntimeException e) {
if (e.getCause() instanceof IOException) {
throw (IOException) e.getCause();
} else if (e.getCause() instanceof UnsupportedCallbackException) {
throw (UnsupportedCallbackException) e.getCause();
}
throw e;
}
}
}
}