/*
* JBoss, Home of Professional Open Source.
* Copyright 2008, Red Hat Middleware LLC, 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.iiop.csiv2;
/***************************************
* *
* JBoss: The OpenSource J2EE WebOS *
* *
* Distributable under LGPL license. *
* See terms of license at gnu.org. *
* *
***************************************/
import org.omg.CORBA.Any;
import org.omg.CORBA.BAD_PARAM;
import org.omg.CORBA.MARSHAL;
import org.omg.CORBA.NO_PERMISSION;
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.TypeMismatch;
import org.omg.IOP.CodecPackage.InvalidTypeForEncoding;
import org.omg.IOP.ServiceContext;
import org.omg.PortableInterceptor.ServerRequestInfo;
import org.omg.PortableInterceptor.ServerRequestInterceptor;
import org.jboss.iiop.CorbaORBService;
import org.jboss.logging.Logger;
/**
* This implementation of
* <code>org.omg.PortableInterceptor.ServerRequestInterceptor</code>
* extracts the security attribute service (SAS) context from incoming IIOP
* and inserts SAS messages into the SAS context of outgoing IIOP replies.
*
* @author <a href="mailto:reverbel@ime.usp.br">Francisco Reverbel</a>
* @version $Revision: 81018 $
*/
public class SASTargetInterceptor
extends LocalObject
implements ServerRequestInterceptor
{
// Static fields and initializers ---------------------------------
private static final Logger log =
Logger.getLogger(SASTargetInterceptor.class);
private static final boolean traceEnabled = log.isTraceEnabled();
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<code> messages */
private static final SASContextBody msgBodyCtxAccepted;
/** Ready-to-go <code>CompleteEstablishContext<code> 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);
}
// Static methods ------------------------------------------------
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;
}
// Fields ---------------------------------------------------------
private final Codec codec;
/** Scratch field for <code>ContextError<code> messages */
private final SASContextBody msgBodyCtxError;
/** Ready-to-go <code>ContextError<code> 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
}
};
// Nested class -------------------------------------------------
/**
* The <code>CurrentRequestInfo</code> 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; // true if sasReply is
// CompleteEstablishContext (for
// interoperability with IONA's ASP 6.0)
CurrentRequestInfo()
{
}
}
// Private method ------------------------------------------------
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;
}
// Constructor ---------------------------------------------------
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 new RuntimeException("Unexpected exception: " + 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);
}
// Methods -------------------------------------------------------
/**
* Returns true if an SAS context arrived with the current IIOP request.
*/
boolean sasContextReceived()
{
CurrentRequestInfo threadLocal =
(CurrentRequestInfo)threadLocalData.get();
return threadLocal.sasContextReceived;
}
/**
* Returns true if a client authentication token arrived with the
* current IIOP request.
*/
boolean authenticationTokenReceived()
{
CurrentRequestInfo threadLocal =
(CurrentRequestInfo)threadLocalData.get();
return threadLocal.authenticationTokenReceived;
}
/**
* Returns the username that arrived in the current IIOP request.
*/
byte[] getIncomingUsername()
{
CurrentRequestInfo threadLocal =
(CurrentRequestInfo)threadLocalData.get();
return threadLocal.incomingUsername;
}
/**
* Returns the password that arrived in the current IIOP request.
*/
byte[] getIncomingPassword()
{
CurrentRequestInfo threadLocal =
(CurrentRequestInfo)threadLocalData.get();
return threadLocal.incomingPassword;
}
/**
* Returns the target name that arrived in the current IIOP request.
*/
byte[] getIncomingTargetName()
{
CurrentRequestInfo threadLocal =
(CurrentRequestInfo)threadLocalData.get();
return threadLocal.incomingTargetName;
}
/**
* Returns the <code>org.omg.CSI.IdentityToken<code> that arrived in
* the current IIOP request.
*/
IdentityToken getIncomingIdentity()
{
CurrentRequestInfo threadLocal =
(CurrentRequestInfo)threadLocalData.get();
return threadLocal.incomingIdentity;
}
/**
* Returns the principal name that arrived in the current IIOP request.
*/
byte[] getIncomingPrincipalName()
{
CurrentRequestInfo threadLocal =
(CurrentRequestInfo)threadLocalData.get();
return threadLocal.incomingPrincipalName;
}
/**
* Sets the outgoing SAS reply to <code>ContextError</code>, with major
* status "invalid evidence".
*/
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;
}
}
// org.omg.PortableInterceptor.Interceptor operations ------------
public String name()
{
return "SASTargetInterceptor";
}
public void destroy()
{
// do nothing
}
// ServerRequestInterceptor operations ---------------------------
public void receive_request_service_contexts(ServerRequestInfo ri)
{
// do nothing
}
// ServerRequestInterceptor operations ---------------------------
public void receive_request(ServerRequestInfo ri)
{
if (traceEnabled)
log.trace("receive_request " + 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)
{
// we're done
return;
}
else 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 new NO_PERMISSION("SAS context does not exist.");
}
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)
{
if (traceEnabled)
log.trace("received client authentication token");
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 new NO_PERMISSION("Could not decode " +
"initial context token.");
}
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 new NO_PERMISSION("Could not decode target name " +
"in initial context token.");
}
threadLocal.authenticationTokenReceived = true;
}
if (message.identity_token != null)
{
if (traceEnabled)
log.trace("received identity token");
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 new NO_PERMISSION("Could not decode " +
"incoming principal name.");
}
}
}
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 new MARSHAL("Exception decoding context data in " +
"SASTargetInterceptor: " + e);
}
catch (TypeMismatch e)
{
throw new MARSHAL("Exception decoding context data in " +
"SASTargetInterceptor: " + e);
}
}
public void send_reply(ServerRequestInfo ri)
{
if (traceEnabled)
log.trace("send_reply " + 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 new MARSHAL("Unexpected exception: " + e);
}
}
}
public void send_exception(ServerRequestInfo ri)
{
if (traceEnabled)
log.trace("send_exception " + ri.operation() + ": ");
CurrentRequestInfo threadLocal =
(CurrentRequestInfo)threadLocalData.get();
// The check for sasReplyIsAccept 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 sasReplyIsAccept flag
// serves the purpose of refraining from sending an SAS accept
// (CompleteEstablishContext) reply together with an exception.
//
// The CSIv2 spec does not explicitly disallow an SAS accept in an
// IIOP exception reply.
//
if (threadLocal.sasReply != null &&
(!threadLocal.sasReplyIsAccept ||
CorbaORBService.getSendSASAcceptWithExceptionEnabledFlag() == true))
{
try
{
ServiceContext sc =
new ServiceContext(sasContextId,
codec.encode_value(threadLocal.sasReply));
ri.add_reply_service_context(sc, true);
}
catch (InvalidTypeForEncoding e)
{
throw new MARSHAL("Unexpected exception: " + e);
}
}
}
public void send_other(ServerRequestInfo ri)
{
// Do nothing. According to the SAS spec, LOCATION_FORWARD reply
// carries no SAS message.
}
}