/*
* 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.geode.internal.cache.tier.sockets;
import static org.apache.geode.distributed.ConfigurationProperties.*;
import java.io.DataOutputStream;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.security.Principal;
import java.util.Properties;
import org.apache.logging.log4j.Logger;
import org.apache.shiro.subject.Subject;
import org.apache.geode.DataSerializer;
import org.apache.geode.cache.IncompatibleVersionException;
import org.apache.geode.cache.UnsupportedVersionException;
import org.apache.geode.cache.VersionException;
import org.apache.geode.distributed.DistributedMember;
import org.apache.geode.distributed.DistributedSystem;
import org.apache.geode.distributed.internal.InternalDistributedSystem;
import org.apache.geode.internal.HeapDataOutputStream;
import org.apache.geode.internal.Version;
import org.apache.geode.internal.VersionedDataStream;
import org.apache.geode.internal.cache.tier.Acceptor;
import org.apache.geode.internal.i18n.LocalizedStrings;
import org.apache.geode.internal.logging.InternalLogWriter;
import org.apache.geode.internal.logging.LogService;
import org.apache.geode.internal.logging.log4j.LocalizedMessage;
import org.apache.geode.internal.security.AuthorizeRequest;
import org.apache.geode.internal.security.AuthorizeRequestPP;
import org.apache.geode.security.AuthenticationFailedException;
import org.apache.geode.security.AuthenticationRequiredException;
/**
* A <code>ServerHandShakeProcessor</code> verifies the client's version compatibility with server.
*
* @since GemFire 5.7
*/
public class ServerHandShakeProcessor {
private static final Logger logger = LogService.getLogger();
protected static final byte REPLY_REFUSED = (byte) 60;
protected static final byte REPLY_INVALID = (byte) 61;
public static Version currentServerVersion = Acceptor.VERSION;
/**
* Test hook for server version support
*
* @since GemFire 5.7
*/
public static void setSeverVersionForTesting(short ver) {
currentServerVersion = Version.fromOrdinalOrCurrent(ver);
}
public static boolean readHandShake(ServerConnection connection) {
boolean validHandShake = false;
Version clientVersion = null;
try {
// Read the version byte from the socket
clientVersion = readClientVersion(connection);
} catch (IOException e) {
// Only log an exception if the server is still running.
if (connection.getAcceptor().isRunning()) {
// Server logging
logger.warn("{} {}", connection.getName(), e.getMessage(), e);
}
connection.stats.incFailedConnectionAttempts();
connection.cleanup();
validHandShake = false;
} catch (UnsupportedVersionException uve) {
// Server logging
logger.warn("{} {}", connection.getName(), uve.getMessage(), uve);
// Client logging
connection.refuseHandshake(uve.getMessage(), REPLY_REFUSED);
connection.stats.incFailedConnectionAttempts();
connection.cleanup();
validHandShake = false;
} catch (Exception e) {
// Server logging
logger.warn("{} {}", connection.getName(), e.getMessage(), e);
// Client logging
connection.refuseHandshake(
LocalizedStrings.ServerHandShakeProcessor_0_SERVERS_CURRENT_VERSION_IS_1
.toLocalizedString(new Object[] {e.getMessage(), Acceptor.VERSION.toString()}),
REPLY_REFUSED);
connection.stats.incFailedConnectionAttempts();
connection.cleanup();
validHandShake = false;
}
if (clientVersion != null) {
if (logger.isDebugEnabled())
logger.debug("Client version: {}", clientVersion);
// Read the appropriate handshake
if (clientVersion.compareTo(Version.GFE_57) >= 0) {
validHandShake = readGFEHandshake(connection, clientVersion);
} else {
connection.refuseHandshake(
"Unsupported version " + clientVersion + "Server's current version " + Acceptor.VERSION,
REPLY_REFUSED);
}
}
return validHandShake;
}
/**
* Refuse a received handshake.
*
* @param out the Stream to the waiting greeter.
* @param message providing details about the refusal reception, mainly for client logging.
* @throws IOException
*/
public static void refuse(OutputStream out, String message) throws IOException {
refuse(out, message, REPLY_REFUSED);
}
/**
* Refuse a received handshake.
*
* @param out the Stream to the waiting greeter.
* @param message providing details about the refusal reception, mainly for client logging.
* @param exception providing details about exception occurred.
* @throws IOException
*/
public static void refuse(OutputStream out, String message, byte exception) throws IOException {
HeapDataOutputStream hdos = new HeapDataOutputStream(32, Version.CURRENT);
DataOutputStream dos = new DataOutputStream(hdos);
// Write refused reply
dos.writeByte(exception);
// write dummy epType
dos.writeByte(0);
// write dummy qSize
dos.writeInt(0);
// Write the server's member
DistributedMember member = InternalDistributedSystem.getAnyInstance().getDistributedMember();
writeServerMember(member, dos);
// Write the refusal message
if (message == null) {
message = "";
}
dos.writeUTF(message);
// Write dummy delta-propagation property value. This will never be read at
// receiver because the exception byte above will cause the receiver code
// throw an exception before the below byte could be read.
dos.writeBoolean(Boolean.TRUE);
out.write(hdos.toByteArray());
out.flush();
}
// Keep the writeServerMember/readServerMember compatible with C++ native
// client
protected static void writeServerMember(DistributedMember member, DataOutputStream dos)
throws IOException {
Version v = Version.CURRENT;
if (dos instanceof VersionedDataStream) {
v = ((VersionedDataStream) dos).getVersion();
}
HeapDataOutputStream hdos = new HeapDataOutputStream(v);
DataSerializer.writeObject(member, hdos);
DataSerializer.writeByteArray(hdos.toByteArray(), dos);
hdos.close();
}
private static boolean readGFEHandshake(ServerConnection connection, Version clientVersion) {
int handShakeTimeout = connection.getHandShakeTimeout();
InternalLogWriter securityLogWriter = connection.getSecurityLogWriter();
try {
Socket socket = connection.getSocket();
DistributedSystem system = connection.getDistributedSystem();
// hitesh:it will set credentials and principals
HandShake handshake = new HandShake(socket, handShakeTimeout, system, clientVersion,
connection.getCommunicationMode());
connection.setHandshake(handshake);
ClientProxyMembershipID proxyId = handshake.getMembership();
connection.setProxyId(proxyId);
// hitesh: it gets principals
// Hitesh:for older version we should set this
if (clientVersion.compareTo(Version.GFE_65) < 0
|| connection.getCommunicationMode() == Acceptor.GATEWAY_TO_GATEWAY) {
long uniqueId = setAuthAttributes(connection);
connection.setUserAuthId(uniqueId);// for older clients < 6.5
}
} catch (SocketTimeoutException timeout) {
logger.warn(LocalizedMessage.create(
LocalizedStrings.ServerHandShakeProcessor_0_HANDSHAKE_REPLY_CODE_TIMEOUT_NOT_RECEIVED_WITH_IN_1_MS,
new Object[] {connection.getName(), Integer.valueOf(handShakeTimeout)}));
connection.stats.incFailedConnectionAttempts();
connection.cleanup();
return false;
} catch (EOFException e) {
// no need to warn client just gave up on this server before we could
// handshake
logger.info("{} {}", connection.getName(), e);
connection.stats.incFailedConnectionAttempts();
connection.cleanup();
return false;
} catch (SocketException e) { // no need to warn client just gave up on this
// server before we could handshake
logger.info("{} {}", connection.getName(), e);
connection.stats.incFailedConnectionAttempts();
connection.cleanup();
return false;
} catch (IOException e) {
logger.warn(LocalizedMessage.create(
LocalizedStrings.ServerHandShakeProcessor_0_RECEIVED_NO_HANDSHAKE_REPLY_CODE,
connection.getName()), e);
connection.stats.incFailedConnectionAttempts();
connection.cleanup();
return false;
} catch (AuthenticationRequiredException noauth) {
String exStr = noauth.getLocalizedMessage();
if (noauth.getCause() != null) {
exStr += " : " + noauth.getCause().getLocalizedMessage();
}
if (securityLogWriter.warningEnabled()) {
securityLogWriter.warning(LocalizedStrings.ONE_ARG,
connection.getName() + ": Security exception: " + exStr);
}
connection.stats.incFailedConnectionAttempts();
connection.refuseHandshake(noauth.getMessage(),
HandShake.REPLY_EXCEPTION_AUTHENTICATION_REQUIRED);
connection.cleanup();
return false;
} catch (AuthenticationFailedException failed) {
String exStr = failed.getLocalizedMessage();
if (failed.getCause() != null) {
exStr += " : " + failed.getCause().getLocalizedMessage();
}
if (securityLogWriter.warningEnabled()) {
securityLogWriter.warning(LocalizedStrings.ONE_ARG,
connection.getName() + ": Security exception: " + exStr);
}
connection.stats.incFailedConnectionAttempts();
connection.refuseHandshake(failed.getMessage(),
HandShake.REPLY_EXCEPTION_AUTHENTICATION_FAILED);
connection.cleanup();
return false;
} catch (Exception ex) {
logger.warn("{} {}", connection.getName(), ex.getLocalizedMessage());
connection.stats.incFailedConnectionAttempts();
connection.refuseHandshake(ex.getMessage(), REPLY_REFUSED);
connection.cleanup();
return false;
}
return true;
}
public static long setAuthAttributes(ServerConnection connection) throws Exception {
try {
logger.debug("setAttributes()");
Object principal = ((HandShake) connection.getHandshake()).verifyCredentials();
long uniqueId;
if (principal instanceof Subject) {
uniqueId =
connection.getClientUserAuths(connection.getProxyID()).putSubject((Subject) principal);
} else {
// this sets principal in map as well....
uniqueId = getUniqueId(connection, (Principal) principal);
connection.setPrincipal((Principal) principal);// TODO:hitesh is this require now ???
}
return uniqueId;
} catch (Exception ex) {
throw ex;
}
}
public static long getUniqueId(ServerConnection connection, Principal principal)
throws Exception {
try {
InternalLogWriter securityLogWriter = connection.getSecurityLogWriter();
DistributedSystem system = connection.getDistributedSystem();
Properties systemProperties = system.getProperties();
// hitesh:auth callbacks
String authzFactoryName = systemProperties.getProperty(SECURITY_CLIENT_ACCESSOR);
String postAuthzFactoryName = systemProperties.getProperty(SECURITY_CLIENT_ACCESSOR_PP);
AuthorizeRequest authzRequest = null;
AuthorizeRequestPP postAuthzRequest = null;
if (authzFactoryName != null && authzFactoryName.length() > 0) {
if (securityLogWriter.fineEnabled())
securityLogWriter.fine(connection.getName()
+ ": Setting pre-process authorization callback to: " + authzFactoryName);
if (principal == null) {
if (securityLogWriter.warningEnabled()) {
securityLogWriter.warning(
LocalizedStrings.ServerHandShakeProcessor_0_AUTHORIZATION_ENABLED_BUT_AUTHENTICATION_CALLBACK_1_RETURNED_WITH_NULL_CREDENTIALS_FOR_PROXYID_2,
new Object[] {connection.getName(), SECURITY_CLIENT_AUTHENTICATOR,
connection.getProxyID()});
}
}
authzRequest = new AuthorizeRequest(authzFactoryName, connection.getProxyID(), principal,
connection.getCache());
// connection.setAuthorizeRequest(authzRequest);
}
if (postAuthzFactoryName != null && postAuthzFactoryName.length() > 0) {
if (securityLogWriter.fineEnabled())
securityLogWriter.fine(connection.getName()
+ ": Setting post-process authorization callback to: " + postAuthzFactoryName);
if (principal == null) {
if (securityLogWriter.warningEnabled()) {
securityLogWriter.warning(
LocalizedStrings.ServerHandShakeProcessor_0_POSTPROCESS_AUTHORIZATION_ENABLED_BUT_NO_AUTHENTICATION_CALLBACK_2_IS_CONFIGURED,
new Object[] {connection.getName(), SECURITY_CLIENT_AUTHENTICATOR});
}
}
postAuthzRequest = new AuthorizeRequestPP(postAuthzFactoryName, connection.getProxyID(),
principal, connection.getCache());
// connection.setPostAuthorizeRequest(postAuthzRequest);
}
return connection.setUserAuthorizeAndPostAuthorizeRequest(authzRequest, postAuthzRequest);
} catch (Exception ex) {
throw ex;
}
}
private static Version readClientVersion(ServerConnection connection)
throws IOException, VersionException {
Socket socket = connection.getSocket();
int timeout = connection.getHandShakeTimeout();
int soTimeout = -1;
try {
soTimeout = socket.getSoTimeout();
socket.setSoTimeout(timeout);
InputStream is = socket.getInputStream();
short clientVersionOrdinal = Version.readOrdinalFromInputStream(is);
if (clientVersionOrdinal == -1) {
throw new EOFException(
LocalizedStrings.ServerHandShakeProcessor_HANDSHAKEREADER_EOF_REACHED_BEFORE_CLIENT_VERSION_COULD_BE_READ
.toLocalizedString());
}
Version clientVersion = null;
try {
clientVersion = Version.fromOrdinal(clientVersionOrdinal, true);
} catch (UnsupportedVersionException uve) {
// Allows higher version of wan site to connect to server
if (connection.getCommunicationMode() == Acceptor.GATEWAY_TO_GATEWAY
&& !(clientVersionOrdinal == Version.NOT_SUPPORTED_ORDINAL)) {
return Acceptor.VERSION;
} else {
SocketAddress sa = socket.getRemoteSocketAddress();
String sInfo = "";
if (sa != null) {
sInfo = " Client: " + sa.toString() + ".";
}
throw new UnsupportedVersionException(uve.getMessage() + sInfo);
}
}
if (!clientVersion.compatibleWith(Acceptor.VERSION)) {
throw new IncompatibleVersionException(clientVersion, Acceptor.VERSION);// we can throw this
// to restrict
} // Backward Compatibilty Support to limited no of versions
return clientVersion;
} finally {
if (soTimeout != -1) {
try {
socket.setSoTimeout(soTimeout);
} catch (IOException ignore) {
}
}
}
}
}