001/*
002 *  Licensed to the Apache Software Foundation (ASF) under one
003 *  or more contributor license agreements.  See the NOTICE file
004 *  distributed with this work for additional information
005 *  regarding copyright ownership.  The ASF licenses this file
006 *  to you under the Apache License, Version 2.0 (the
007 *  "License"); you may not use this file except in compliance
008 *  with the License.  You may obtain a copy of the License at
009 *  
010 *    http://www.apache.org/licenses/LICENSE-2.0
011 *  
012 *  Unless required by applicable law or agreed to in writing,
013 *  software distributed under the License is distributed on an
014 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 *  KIND, either express or implied.  See the License for the
016 *  specific language governing permissions and limitations
017 *  under the License. 
018 *  
019 */
020package org.apache.directory.server.kerberos.protocol.codec;
021
022
023import java.nio.ByteBuffer;
024import java.util.Locale;
025
026import org.apache.directory.api.asn1.DecoderException;
027import org.apache.directory.api.asn1.ber.Asn1Decoder;
028import org.apache.directory.api.asn1.ber.tlv.TLVStateEnum;
029import org.apache.directory.api.ldap.model.constants.Loggers;
030import org.apache.directory.api.util.Strings;
031import org.apache.directory.shared.kerberos.codec.KerberosMessageContainer;
032import org.apache.mina.core.buffer.IoBuffer;
033import org.apache.mina.core.session.IoSession;
034import org.apache.mina.filter.codec.CumulativeProtocolDecoder;
035import org.apache.mina.filter.codec.ProtocolDecoderOutput;
036import org.slf4j.Logger;
037import org.slf4j.LoggerFactory;
038
039
040/**
041 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
042 */
043public class MinaKerberosDecoder extends CumulativeProtocolDecoder
044{
045    /** the key used while storing message container in the session */
046    private static final String KERBEROS_MESSAGE_CONTAINER = "kerberosMessageContainer";
047
048    private static final int DEFAULT_MAX_PDU_SIZE = 1024 * 7; // 7KB
049    
050    /** the maximum allowed PDU size for a Kerberos request */
051    private int maxPduSize = DEFAULT_MAX_PDU_SIZE;
052    
053    private static final Logger LOG_KRB = LoggerFactory.getLogger( Loggers.KERBEROS_LOG.getName() );
054    
055    /** A speedup for logger */
056    private static final boolean IS_DEBUG = LOG_KRB.isDebugEnabled();
057
058    @Override
059    public boolean doDecode( IoSession session, IoBuffer in, ProtocolDecoderOutput out ) throws Exception
060    {
061        ByteBuffer incomingBuf = in.buf();
062
063        KerberosMessageContainer krbMsgContainer = ( KerberosMessageContainer ) session
064            .getAttribute( KERBEROS_MESSAGE_CONTAINER );
065
066        if ( krbMsgContainer == null )
067        {
068            krbMsgContainer = new KerberosMessageContainer();
069            krbMsgContainer.setMaxPDUSize( maxPduSize );
070            session.setAttribute( KERBEROS_MESSAGE_CONTAINER, krbMsgContainer );
071            krbMsgContainer.setGathering( true );
072            
073            boolean tcp = !session.getTransportMetadata().isConnectionless();
074            krbMsgContainer.setTCP( tcp );
075            
076            if ( tcp )
077            {
078                if ( incomingBuf.remaining() > 4 )
079                {
080                    int len = incomingBuf.getInt();
081                    
082                    if ( len > maxPduSize )
083                    {
084                        session.removeAttribute( KERBEROS_MESSAGE_CONTAINER );
085                        
086                        String err = "Request length %d exceeds allowed max PDU size %d";
087                        err = String.format( Locale.ROOT, err, len, maxPduSize );
088                        
089                        throw new DecoderException( err );
090                    }
091                    
092                    krbMsgContainer.setTcpLength( len );
093                    incomingBuf.mark();
094                    
095                    ByteBuffer tmp = ByteBuffer.allocate( len );
096                    tmp.put( incomingBuf );
097                    
098                    krbMsgContainer.setStream( tmp );
099                }
100                else
101                {
102                    String err = "Could not determine the length of TCP buffer";
103                    LOG_KRB.warn( "{} {}", err, Strings.dumpBytes( incomingBuf.array() ) );
104                    throw new IllegalStateException( err );
105                }
106            }
107            else // UDP
108            {
109                krbMsgContainer.setStream( incomingBuf );
110            }
111        }
112        else // must be a fragmented TCP stream, copy the incomingBuf into the existing buffer of the container
113        {
114            int totLen = incomingBuf.limit() + krbMsgContainer.getStream().position();
115            if ( totLen > maxPduSize )
116            {
117                session.removeAttribute( KERBEROS_MESSAGE_CONTAINER );
118                
119                String err = "Total length of recieved bytes %d exceeds allowed max PDU size %d";
120                err = String.format( Locale.ROOT, err, totLen, maxPduSize );
121                
122                throw new DecoderException( err );
123            }
124            
125            krbMsgContainer.getStream().put( incomingBuf );
126        }
127
128        if ( krbMsgContainer.isTCP() )
129        {
130            int curLen = krbMsgContainer.getStream().position();
131            if ( curLen < krbMsgContainer.getTcpLength() )
132            {
133                return false;
134            }
135        }
136
137        try
138        {
139            ByteBuffer stream = krbMsgContainer.getStream();
140            if ( stream.position() != 0 )
141            {
142                stream.flip();
143            }
144            
145            Asn1Decoder.decode( stream, krbMsgContainer );
146            
147            if ( krbMsgContainer.getState() == TLVStateEnum.PDU_DECODED )
148            {
149                if ( IS_DEBUG )
150                {
151                    LOG_KRB.debug( "Decoded KerberosMessage : {}", krbMsgContainer.getMessage() );
152                    incomingBuf.mark();
153                }
154                
155                out.write( krbMsgContainer.getMessage() );
156                
157                return true;
158            }
159        }
160        catch ( DecoderException de )
161        {
162            LOG_KRB.warn( "Error while decoding kerberos message", de );
163            incomingBuf.clear();
164            krbMsgContainer.clean();
165            throw de;
166        }
167        finally
168        {
169            session.removeAttribute( KERBEROS_MESSAGE_CONTAINER );
170        }
171        
172        throw new DecoderException( "Invalid buffer" );
173   }
174
175    
176    /**
177     * @return the maxPduSize
178     */
179    public int getMaxPduSize()
180    {
181        return maxPduSize;
182    }
183
184    
185    /**
186     * @param maxPduSize the maxPduSize to set
187     */
188    public void setMaxPduSize( int maxPduSize )
189    {
190        this.maxPduSize = maxPduSize;
191    }
192}