/*
* JBoss, Home of Professional Open Source.
* Copyright 2011, 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.jacorb.csiv2;
import org.jboss.as.jacorb.JacORBLogger;
import org.jboss.as.jacorb.JacORBMessages;
import org.jboss.as.jacorb.JacORBSubsystemConstants;
import org.jboss.as.jacorb.service.CorbaORBService;
import org.omg.CORBA.Any;
import org.omg.CORBA.BAD_PARAM;
import org.omg.CORBA.LocalObject;
import org.omg.CORBA.ORB;
import org.omg.CSI.CompleteEstablishContext;
import org.omg.CSI.ContextError;
import org.omg.CSI.EstablishContext;
import org.omg.CSI.GSS_NT_ExportedNameHelper;
import org.omg.CSI.ITTPrincipalName;
import org.omg.CSI.IdentityToken;
import org.omg.CSI.MTEstablishContext;
import org.omg.CSI.MTMessageInContext;
import org.omg.CSI.SASContextBody;
import org.omg.CSI.SASContextBodyHelper;
import org.omg.GSSUP.ErrorToken;
import org.omg.GSSUP.ErrorTokenHelper;
import org.omg.GSSUP.GSS_UP_S_G_UNSPECIFIED;
import org.omg.GSSUP.InitialContextToken;
import org.omg.IOP.Codec;
import org.omg.IOP.CodecPackage.FormatMismatch;
import org.omg.IOP.CodecPackage.InvalidTypeForEncoding;
import org.omg.IOP.CodecPackage.TypeMismatch;
import org.omg.IOP.ServiceContext;
import org.omg.PortableInterceptor.ServerRequestInfo;
import org.omg.PortableInterceptor.ServerRequestInterceptor;
/**
* <p>
* This implementation of {@code org.omg.PortableInterceptor.ServerRequestInterceptor} extracts the security attribute
* service (SAS) context from incoming IIOP and inserts SAS messages into the SAS context of outgoing IIOP replies.
* </p>
*
* @author <a href="mailto:reverbel@ime.usp.br">Francisco Reverbel</a>
* @author <a href="mailto:sguilhen@redhat.com">Stefan Guilhen</a>
*/
public class SASTargetInterceptor extends LocalObject implements ServerRequestInterceptor {
private static final int sasContextId = org.omg.IOP.SecurityAttributeService.value;
private static final byte[] empty = new byte[0];
private static final IdentityToken absent;
/**
* scratch field for {@code CompleteEstablishContext} messages
*/
private static final SASContextBody msgBodyCtxAccepted;
/**
* Ready-to-go {@code CompleteEstablishContext} message with context id set to zero
*/
private static final Any msgCtx0Accepted;
static {
// initialize absent.
absent = new IdentityToken();
absent.absent(true);
// initialize msgBodyCtxAccepted (Note that "context stateful" is always set to false. Even if the
// client wants a stateful context, we negotiate the context down to stateless).
CompleteEstablishContext ctxAccepted =
new CompleteEstablishContext(0, /* context id */
false, /* context stateful */
new byte[0] /* no final token */);
msgBodyCtxAccepted = new SASContextBody();
msgBodyCtxAccepted.complete_msg(ctxAccepted);
// initialize msgCtx0Accepted.
msgCtx0Accepted = createMsgCtxAccepted(0);
}
private static Any createMsgCtxAccepted(long contextId) {
Any any = ORB.init().create_any();
synchronized (msgBodyCtxAccepted) {
msgBodyCtxAccepted.complete_msg().client_context_id = contextId;
SASContextBodyHelper.insert(any, msgBodyCtxAccepted);
}
return any;
}
private final Codec codec;
/**
* scratch field for {@code ContextError} messages
*/
private final SASContextBody msgBodyCtxError;
/**
* ready-to-go {@code ContextError} message with context id set to zero and major status "invalid evidence"
*/
private final Any msgCtx0Rejected;
private ThreadLocal threadLocalData = new ThreadLocal() {
protected synchronized Object initialValue() {
return new CurrentRequestInfo(); // see nested class below
}
};
/**
* The {@code CurrentRequestInfo} class holds SAS information associated with IIOP request handled by the current thread.
*/
private static class CurrentRequestInfo {
boolean sasContextReceived;
boolean authenticationTokenReceived;
byte[] incomingUsername;
byte[] incomingPassword;
byte[] incomingTargetName;
IdentityToken incomingIdentity;
byte[] incomingPrincipalName;
long contextId;
Any sasReply;
boolean sasReplyIsAccept;
/**
* <p>
* Creates an instance of {@code CurrentRequestInfo}.
* </p>
*/
CurrentRequestInfo() {
}
}
private Any createMsgCtxError(long contextId, int majorStatus) {
Any any = ORB.init().create_any();
synchronized (msgBodyCtxError) {
msgBodyCtxError.error_msg().client_context_id = contextId;
msgBodyCtxError.error_msg().major_status = majorStatus;
SASContextBodyHelper.insert(any, msgBodyCtxError);
}
return any;
}
/**
* <p>
* Creates an instance of {@code SASTargetInterceptor} with the specified codec.
* </p>
*
* @param codec the {@code Codec} used to encode and decode the SAS components.
*/
public SASTargetInterceptor(Codec codec) {
this.codec = codec;
// build encapsulated GSSUP error token for ContextError messages (the error code within the error token is
// GSS_UP_S_G_UNSPECIFIED, which says nothing about the cause of the error).
ErrorToken errorToken = new ErrorToken(GSS_UP_S_G_UNSPECIFIED.value);
Any any = ORB.init().create_any();
byte[] encapsulatedErrorToken;
ErrorTokenHelper.insert(any, errorToken);
try {
encapsulatedErrorToken = codec.encode_value(any);
} catch (InvalidTypeForEncoding e) {
throw JacORBMessages.MESSAGES.unexpectedException(e);
}
// initialize msgBodyCtxError.
ContextError ctxError =
new ContextError(0, /* context id */
1, /* major status: invalid evidence */
1, /* minor status (always 1) */
encapsulatedErrorToken);
msgBodyCtxError = new SASContextBody();
msgBodyCtxError.error_msg(ctxError);
// initialize msgCtx0Rejected (major status: invalid evidence).
msgCtx0Rejected = createMsgCtxError(0, 1);
}
/**
* <p>
* Indicates whether an SAS context has arrived with the current request or not.
* </p>
*
* @return {@code true} if an SAS context arrived with the current IIOP request; {@code false} otherwise.
*/
boolean sasContextReceived() {
CurrentRequestInfo threadLocal = (CurrentRequestInfo) threadLocalData.get();
return threadLocal.sasContextReceived;
}
/**
* <p>
* Indicates whether a client authentication token has arrived with the current request or not.
* </p>
*
* @return {@code true} if a client authentication token arrived with the current IIOP request; {@code false}
* otherwise.
*/
boolean authenticationTokenReceived() {
CurrentRequestInfo threadLocal = (CurrentRequestInfo) threadLocalData.get();
return threadLocal.authenticationTokenReceived;
}
/**
* <p>
* Obtains the username that arrived with the current request.
* </p>
*
* @return the username that arrived in the current IIOP request.
*/
byte[] getIncomingUsername() {
CurrentRequestInfo threadLocal = (CurrentRequestInfo) threadLocalData.get();
return threadLocal.incomingUsername;
}
/**
* <p>
* Obtains the password that arrived in the current request.
* </p>
*
* @return the password that arrived in the current IIOP request.
*/
byte[] getIncomingPassword() {
CurrentRequestInfo threadLocal = (CurrentRequestInfo) threadLocalData.get();
return threadLocal.incomingPassword;
}
/**
* <p>
* Obtains the target name that arrived in the current request.
* </p>
*
* @return the target name that arrived in the current IIOP request.
*/
byte[] getIncomingTargetName() {
CurrentRequestInfo threadLocal = (CurrentRequestInfo) threadLocalData.get();
return threadLocal.incomingTargetName;
}
/**
* <p>
* Obtains the {@code org.omg.CSI.IdentityToken} that arrived in the current request.
* </p>
*
* @return the {@code IdentityToken} that arrived in the current IIOP request.
*/
IdentityToken getIncomingIdentity() {
CurrentRequestInfo threadLocal = (CurrentRequestInfo) threadLocalData.get();
return threadLocal.incomingIdentity;
}
/**
* <p>
* Obtains the principal name that arrived in the current request.
* </p>
*
* @return the principal name that arrived in the current IIOP request.
*/
byte[] getIncomingPrincipalName() {
CurrentRequestInfo threadLocal = (CurrentRequestInfo) threadLocalData.get();
return threadLocal.incomingPrincipalName;
}
/**
* <p>
* Sets the outgoing SAS reply to <code>ContextError</code>, with major status "invalid evidence".
* </p>
*/
void rejectIncomingContext() {
CurrentRequestInfo threadLocal = (CurrentRequestInfo) threadLocalData.get();
if (threadLocal.sasContextReceived) {
threadLocal.sasReply = (threadLocal.contextId == 0) ? msgCtx0Rejected :
createMsgCtxError(threadLocal.contextId, 1 /* major status: invalid evidence */);
threadLocal.sasReplyIsAccept = false;
}
}
@Override
public String name() {
return "SASTargetInterceptor";
}
@Override
public void destroy() {
}
@Override
public void receive_request_service_contexts(ServerRequestInfo ri) {
}
@Override
public void receive_request(ServerRequestInfo ri) {
JacORBLogger.ROOT_LOGGER.traceReceiveRequest(ri.operation());
CurrentRequestInfo threadLocal = (CurrentRequestInfo) threadLocalData.get();
threadLocal.sasContextReceived = false;
threadLocal.authenticationTokenReceived = false;
threadLocal.incomingUsername = empty;
threadLocal.incomingPassword = empty;
threadLocal.incomingTargetName = empty;
threadLocal.incomingIdentity = absent;
threadLocal.incomingPrincipalName = empty;
threadLocal.sasReply = null;
threadLocal.sasReplyIsAccept = false;
try {
ServiceContext sc = ri.get_request_service_context(sasContextId);
Any any = codec.decode_value(sc.context_data, SASContextBodyHelper.type());
SASContextBody contextBody = SASContextBodyHelper.extract(any);
if (contextBody != null) {
if (contextBody.discriminator() == MTMessageInContext.value) {
// should not happen, as stateful context requests are always negotiated down to stateless in this implementation.
long contextId = contextBody.in_context_msg().client_context_id;
threadLocal.sasReply = createMsgCtxError(contextId, 4 /* major status: no context */);
throw JacORBMessages.MESSAGES.missingSASContext();
} else if (contextBody.discriminator() == MTEstablishContext.value) {
EstablishContext message = contextBody.establish_msg();
threadLocal.contextId = message.client_context_id;
threadLocal.sasContextReceived = true;
if (message.client_authentication_token != null && message.client_authentication_token.length > 0) {
JacORBLogger.ROOT_LOGGER.authTokenReceived();
InitialContextToken authToken = CSIv2Util.decodeInitialContextToken(
message.client_authentication_token, codec);
if (authToken == null) {
threadLocal.sasReply = createMsgCtxError(message.client_context_id, 2 /* major status: invalid mechanism */);
throw JacORBMessages.MESSAGES.errorDecodingInitContextToken();
}
threadLocal.incomingUsername = authToken.username;
threadLocal.incomingPassword = authToken.password;
threadLocal.incomingTargetName = CSIv2Util.decodeGssExportedName(authToken.target_name);
if (threadLocal.incomingTargetName == null) {
threadLocal.sasReply = createMsgCtxError(message.client_context_id, 2 /* major status: invalid mechanism */);
throw JacORBMessages.MESSAGES.errorDecodingTargetInContextToken();
}
threadLocal.authenticationTokenReceived = true;
}
if (message.identity_token != null) {
JacORBLogger.ROOT_LOGGER.identityTokenReceived();
threadLocal.incomingIdentity = message.identity_token;
if (message.identity_token.discriminator() == ITTPrincipalName.value) {
// Extract the RFC2743-encoded name from CDR encapsulation.
Any a = codec.decode_value(message.identity_token.principal_name(),
GSS_NT_ExportedNameHelper.type());
byte[] encodedName = GSS_NT_ExportedNameHelper.extract(a);
// Decode the principal name.
threadLocal.incomingPrincipalName = CSIv2Util.decodeGssExportedName(encodedName);
if (threadLocal.incomingPrincipalName == null) {
threadLocal.sasReply = createMsgCtxError(message.client_context_id, 2 /* major status: invalid mechanism */);
throw JacORBMessages.MESSAGES.errorDecodingPrincipalName();
}
}
}
threadLocal.sasReply = (threadLocal.contextId == 0) ? msgCtx0Accepted :
createMsgCtxAccepted(threadLocal.contextId);
threadLocal.sasReplyIsAccept = true;
}
}
} catch (BAD_PARAM e) {
// no service context with sasContextId: do nothing.
} catch (FormatMismatch e) {
throw JacORBMessages.MESSAGES.errorDecodingContextData(this.name(), e);
} catch (TypeMismatch e) {
throw JacORBMessages.MESSAGES.errorDecodingContextData(this.name(), e);
}
}
@Override
public void send_reply(ServerRequestInfo ri) {
JacORBLogger.ROOT_LOGGER.traceSendReply(ri.operation());
CurrentRequestInfo threadLocal = (CurrentRequestInfo) threadLocalData.get();
if (threadLocal.sasReply != null) {
try {
ServiceContext sc = new ServiceContext(sasContextId, codec.encode_value(threadLocal.sasReply));
ri.add_reply_service_context(sc, true);
} catch (InvalidTypeForEncoding e) {
throw JacORBMessages.MESSAGES.unexpectedException(e);
}
}
}
@Override
public void send_exception(ServerRequestInfo ri) {
JacORBLogger.ROOT_LOGGER.traceSendException(ri.operation());
CurrentRequestInfo threadLocal = (CurrentRequestInfo) threadLocalData.get();
// The check below was added for interoperability with IONA's ASP 6.0, which throws an
// ArrayIndexOutOfBoundsException when it receives an IIOP reply carrying both an application exception
// and a SAS reply CompleteEstablishContext. The flag serves the purpose of refraining fromsending an SAS
// accept (CompleteEstablishContext) reply together with an exception.
//
// The CSIv2 spec does not explicitly disallow an SAS accept in an IIOP exception reply.
boolean interopIONA = "on".equalsIgnoreCase(CorbaORBService.getORBProperty(JacORBSubsystemConstants.INTEROP_IONA));
if (threadLocal.sasReply != null && !interopIONA) {
try {
ServiceContext sc = new ServiceContext(sasContextId, codec.encode_value(threadLocal.sasReply));
ri.add_reply_service_context(sc, true);
} catch (InvalidTypeForEncoding e) {
throw JacORBMessages.MESSAGES.unexpectedException(e);
}
}
}
@Override
public void send_other(ServerRequestInfo ri) {
// Do nothing. According to the SAS spec, LOCATION_FORWARD reply carries no SAS message.
}
}