/*
 *  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.directory.kerberos.client;


import java.io.IOException;
import java.net.InetAddress;
import java.nio.ByteBuffer;
import java.security.SecureRandom;
import java.text.ParseException;
import java.util.List;

import javax.security.auth.kerberos.KerberosPrincipal;

import org.apache.directory.api.asn1.Asn1Object;
import org.apache.directory.api.asn1.ber.Asn1Decoder;
import org.apache.directory.api.util.Strings;
import org.apache.directory.server.kerberos.changepwd.exceptions.ChangePasswdErrorType;
import org.apache.directory.server.kerberos.changepwd.exceptions.ChangePasswordException;
import org.apache.directory.server.kerberos.changepwd.io.ChangePasswordDecoder;
import org.apache.directory.server.kerberos.changepwd.io.ChangePasswordEncoder;
import org.apache.directory.server.kerberos.changepwd.messages.AbstractPasswordMessage;
import org.apache.directory.server.kerberos.changepwd.messages.ChangePasswordError;
import org.apache.directory.server.kerberos.changepwd.messages.ChangePasswordReply;
import org.apache.directory.server.kerberos.changepwd.messages.ChangePasswordRequest;
import org.apache.directory.server.kerberos.protocol.codec.KerberosDecoder;
import org.apache.directory.server.kerberos.protocol.codec.KerberosEncoder;
import org.apache.directory.server.kerberos.shared.crypto.encryption.CipherTextHandler;
import org.apache.directory.server.kerberos.shared.crypto.encryption.KerberosKeyFactory;
import org.apache.directory.server.kerberos.shared.crypto.encryption.KeyUsage;
import org.apache.directory.server.kerberos.shared.crypto.encryption.RandomKeyFactory;
import org.apache.directory.shared.kerberos.KerberosTime;
import org.apache.directory.shared.kerberos.codec.KerberosMessageContainer;
import org.apache.directory.shared.kerberos.codec.options.ApOptions;
import org.apache.directory.shared.kerberos.codec.types.EncryptionType;
import org.apache.directory.shared.kerberos.codec.types.PaDataType;
import org.apache.directory.shared.kerberos.codec.types.PrincipalNameType;
import org.apache.directory.shared.kerberos.components.EncKdcRepPart;
import org.apache.directory.shared.kerberos.components.EncKrbPrivPart;
import org.apache.directory.shared.kerberos.components.EncryptedData;
import org.apache.directory.shared.kerberos.components.EncryptionKey;
import org.apache.directory.shared.kerberos.components.HostAddress;
import org.apache.directory.shared.kerberos.components.HostAddresses;
import org.apache.directory.shared.kerberos.components.KdcReqBody;
import org.apache.directory.shared.kerberos.components.PaData;
import org.apache.directory.shared.kerberos.components.PaEncTsEnc;
import org.apache.directory.shared.kerberos.components.PrincipalName;
import org.apache.directory.shared.kerberos.exceptions.ErrorType;
import org.apache.directory.shared.kerberos.exceptions.KerberosException;
import org.apache.directory.shared.kerberos.messages.ApRep;
import org.apache.directory.shared.kerberos.messages.ApReq;
import org.apache.directory.shared.kerberos.messages.AsRep;
import org.apache.directory.shared.kerberos.messages.AsReq;
import org.apache.directory.shared.kerberos.messages.Authenticator;
import org.apache.directory.shared.kerberos.messages.ChangePasswdData;
import org.apache.directory.shared.kerberos.messages.EncApRepPart;
import org.apache.directory.shared.kerberos.messages.EncAsRepPart;
import org.apache.directory.shared.kerberos.messages.EncTgsRepPart;
import org.apache.directory.shared.kerberos.messages.KerberosMessage;
import org.apache.directory.shared.kerberos.messages.KrbError;
import org.apache.directory.shared.kerberos.messages.KrbPriv;
import org.apache.directory.shared.kerberos.messages.TgsRep;
import org.apache.directory.shared.kerberos.messages.TgsReq;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 
 * A client to connect to kerberos servers using TCP or UDP transports.
 * 
 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
 */
public class KdcConnection
{

    private static final Logger LOG = LoggerFactory.getLogger( KdcConnection.class );

    /** a secure random number generator used for creating nonces */
    private SecureRandom nonceGenerator;

    static final String TIME_OUT_ERROR = "TimeOut occured";
    
    /** the cipher text handler */
    private CipherTextHandler cipherTextHandler;

    /** underlying network channel handler */
    private KerberosChannel channel;
    
    private KdcConfig config;
    
    /**
     * 
     * Creates a new instance of KdcConnection.
     *
     * @param config the configuration of KDC
     */
    public KdcConnection( KdcConfig config )
    {
        this.config = config;
        
        nonceGenerator = new SecureRandom( String.valueOf( System.currentTimeMillis() ).getBytes() );
        cipherTextHandler = new CipherTextHandler();
        channel = new KerberosChannel();
    }


    private void connect() throws IOException
    {
        channel.openConnection( config.getHostName(), config.getKdcPort(), config.getTimeout(), config.isUseUdp() );
    }
    
    
    /**
     * Authenticates to the Kerberos server and gets the initial Ticket Granting Ticket
     * 
     * @param principal the client's principal 
     * @param password password of the client
     * @return
     * @throws Exception
     */
    public TgTicket getTgt( String principal, String password ) throws Exception
    {
        TgtRequest clientTgtReq = new TgtRequest();
        
        clientTgtReq.setClientPrincipal( principal );
        clientTgtReq.setPassword( password );
        
        return getTgt( clientTgtReq );
    }
    

    /**
     * Authenticates to the Kerberos server and gets a service ticket for the given server principal
     * 
     * @param principal the client's principal 
     * @param password password of the client
     * @param serverPrincipal the application server's principal
     * @return
     * @throws Exception
     */
    public ServiceTicket getServiceTicket( String clientPrincipal, String password, String serverPrincipal ) throws KerberosException
    {
        TgtRequest clientTgtReq = new TgtRequest();
        clientTgtReq.setClientPrincipal( clientPrincipal );
        clientTgtReq.setPassword( password );
        
        TgTicket tgt = getTgt( clientTgtReq );
        
        return getServiceTicket( new ServiceTicketRequest( tgt, serverPrincipal ) );
    }

    public TgTicket getTgt( TgtRequest clientTgtReq ) throws KerberosException
    {
        TgTicket tgt = null;
        
        KerberosException ke = null;
        
        for( int i=0; i < 2; i++ )
        {
            ke = null;
            
            try
            {
                tgt = _getTgt( clientTgtReq );
            }
            catch( KerberosException e )
            {
                // using exception for control flow, b.a.d, but here it is better than
                // defining a new Result class to hold ticket and exception and validating
                // the Result instance from _getTgt()
                ke = e;
            }
            
            if( ke != null )
            {
                if ( ke.getErrorCode() == ErrorType.KDC_ERR_PREAUTH_REQUIRED.getValue() )
                {
                    clientTgtReq.setETypes( KdcClientUtil.getEtypesFromError( ke.getError() ) );
                    clientTgtReq.setPreAuthEnabled( true );
                }
            }
        }
        
        if( ke != null )
        {
            throw ke;
        }
        
        return tgt;
    }
    
    private EncryptionType usedEType;
    
    /* default protected */ TgTicket _getTgt( TgtRequest clientTgtReq ) throws KerberosException
    {
        String realm = clientTgtReq.getRealm();
        
        if ( clientTgtReq.getServerPrincipal() == null )
        {
            String serverPrincipal = "krbtgt/" + realm + "@" + realm;
            clientTgtReq.setServerPrincipal( serverPrincipal );
        }

        if( clientTgtReq.getETypes() == null )
        {
            clientTgtReq.setETypes( config.getEncryptionTypes() );
        }
        
        KdcReqBody body = new KdcReqBody();
        
        body.setFrom( new KerberosTime( clientTgtReq.getStartTime() ) );
        
        PrincipalName cName = null;
        try
        {
            cName = new PrincipalName( clientTgtReq.getCName(), PrincipalNameType.KRB_NT_PRINCIPAL );
            body.setCName( cName );
            body.setRealm( realm );
            PrincipalName sName = new PrincipalName( clientTgtReq.getSName(), PrincipalNameType.KRB_NT_SRV_INST );
            body.setSName( sName );
        }
        catch( ParseException e )
        {
            throw new IllegalArgumentException( "Couldn't parse the given principals", e );
        }
        
        body.setTill( new KerberosTime( clientTgtReq.getExpiryTime() ) );
        int currentNonce = nonceGenerator.nextInt();
        body.setNonce( currentNonce );
        body.setEType( clientTgtReq.getETypes() );
        body.setKdcOptions( clientTgtReq.getOptions() );
        
        List<HostAddress> lstAddresses = clientTgtReq.getHostAddresses();
        if ( !lstAddresses.isEmpty() )
        {
            HostAddresses addresses = new HostAddresses();
            for( HostAddress h : lstAddresses )
            {
                addresses.addHostAddress( h );
            }
            
            body.setAddresses( addresses );
        }
        
        EncryptionType encryptionType = clientTgtReq.getETypes().iterator().next();
        usedEType = encryptionType;
        EncryptionKey clientKey = KerberosKeyFactory.string2Key( clientTgtReq.getClientPrincipal(), clientTgtReq.getPassword(), encryptionType );

        AsReq req = new AsReq();
        req.setKdcReqBody( body );

        if ( clientTgtReq.isPreAuthEnabled() )
        {
            PaEncTsEnc tmstmp = new PaEncTsEnc();
            tmstmp.setPaTimestamp( new KerberosTime() );
            
            EncryptedData paDataValue = cipherTextHandler.encrypt( clientKey, getEncoded( tmstmp ), KeyUsage.AS_REQ_PA_ENC_TIMESTAMP_WITH_CKEY );
            
            PaData paEncTstmp = new PaData();
            paEncTstmp.setPaDataType( PaDataType.PA_ENC_TIMESTAMP );
            paEncTstmp.setPaDataValue( getEncoded( paDataValue ) );
            
            req.addPaData( paEncTstmp );
        }
        
        // Get the result from the future
        try
        {
            connect();
            
            // Read the response, waiting for it if not available immediately
            // Get the response, blocking
            KerberosMessage kdcRep = sendAndReceiveKrbMsg( req );

            if ( kdcRep == null )
            {
                // We didn't received anything : this is an error
                LOG.error( "Authentication failed : timeout occured" );
                throw new KerberosException( ErrorType.KRB_ERR_GENERIC, TIME_OUT_ERROR );
            }

            if ( kdcRep instanceof KrbError )
            {
                // We have an error
                LOG.debug( "Authentication failed : {}", kdcRep );
                throw new KerberosException( ( KrbError ) kdcRep );
            }

            AsRep rep = ( AsRep ) kdcRep;
            
            if ( !cName.getNameString().equals( rep.getCName().getNameString() ) )
            {
                throw new KerberosException( ErrorType.KDC_ERR_CLIENT_NAME_MISMATCH );
            }
            
            if ( !realm.equals( rep.getCRealm() ) )
            {
                throw new KerberosException( ErrorType.KRB_ERR_WRONG_REALM );
            }
            
            byte[] decryptedEncAsRepPart = cipherTextHandler.decrypt( clientKey, rep.getEncPart(), KeyUsage.AS_REP_ENC_PART_WITH_CKEY );
            
            EncKdcRepPart encKdcRepPart = null;
            try
            {
                EncAsRepPart encAsRepPart = KerberosDecoder.decodeEncAsRepPart( decryptedEncAsRepPart );
                encKdcRepPart = encAsRepPart.getEncKdcRepPart();
            } 
            catch( KerberosException e ) 
            {
                LOG.info("Trying an encTgsRepPart instead");
                EncTgsRepPart encTgsRepPart = KerberosDecoder.decodeEncTgsRepPart( decryptedEncAsRepPart );
                encKdcRepPart = encTgsRepPart.getEncKdcRepPart();
            }
            
            if ( currentNonce != encKdcRepPart.getNonce() )
            {
                throw new KerberosException( ErrorType.KRB_ERR_GENERIC, "received nonce didn't match with the nonce sent in the request" );
            }
                       
            if ( !encKdcRepPart.getSName().getNameString().equals( clientTgtReq.getSName() ) )
            {
                throw new KerberosException( ErrorType.KDC_ERR_SERVER_NOMATCH );
            }
            
            if ( !encKdcRepPart.getSRealm().equals( clientTgtReq.getRealm() ) )
            {
                throw new KerberosException( ErrorType.KRB_ERR_GENERIC, "received server realm does not match with requested server realm" );
            }
            
            List<HostAddress> hosts = clientTgtReq.getHostAddresses();
            
            if( !hosts.isEmpty() )
            {
                HostAddresses addresses = encKdcRepPart.getClientAddresses();
                for( HostAddress h : hosts )
                {
                    if ( !addresses.contains( h ) )
                    {
                        throw new KerberosException( ErrorType.KRB_ERR_GENERIC, "requested client address" + h + " is not found in the ticket" );
                    }
                }
            }
            
            // Everything is fine, return the response
            LOG.debug( "Authentication successful : {}", kdcRep );
            
            TgTicket tgTicket = new TgTicket( rep.getTicket(), encKdcRepPart, rep.getCName().getNameString() );
            
            return tgTicket;
        }
        catch( KerberosException ke )
        {
            throw ke;
        }
        catch ( Exception e )
        {
            // We didn't received anything : this is an error
            LOG.error( "Authentication failed" );
            throw new KerberosException( ErrorType.KRB_ERR_GENERIC, TIME_OUT_ERROR );
        }
        finally
        {
            if ( channel != null )
            {
                try
                {
                    channel.close();
                }
                catch( IOException e )
                {
                    LOG.warn( "Failed to close the channel", e );
                }
            }
        }
    }

    
    private ServiceTicket getServiceTicket( ServiceTicketRequest srvTktReq ) throws KerberosException
    {
        String serverPrincipal = srvTktReq.getServerPrincipal();
        
        // session key
        EncryptionKey sessionKey = srvTktReq.getTgt().getSessionKey();
        
        Authenticator authenticator = new Authenticator();
        
        try
        {
            authenticator.setCName( new PrincipalName( srvTktReq.getTgt().getClientName(), PrincipalNameType.KRB_NT_PRINCIPAL ) );
        }
        catch( ParseException e )
        {
            throw new IllegalArgumentException( "Couldn't parse the given principal", e );
        }
        
        authenticator.setCRealm( srvTktReq.getTgt().getRealm() );
        authenticator.setCTime( new KerberosTime() );
        authenticator.setCusec( 0 );

        if( srvTktReq.getSubSessionKey() != null )
        {
            sessionKey = srvTktReq.getSubSessionKey();
            authenticator.setSubKey( sessionKey );
        }
        
        EncryptedData authnData = cipherTextHandler.encrypt( sessionKey, getEncoded( authenticator ), KeyUsage.TGS_REQ_PA_TGS_REQ_PADATA_AP_REQ_TGS_SESS_KEY );
        
        ApReq apReq = new ApReq();
        
        apReq.setAuthenticator( authnData );
        apReq.setTicket( srvTktReq.getTgt().getTicket() );

        apReq.setApOptions( srvTktReq.getApOptions() );
        
        KdcReqBody tgsReqBody = new KdcReqBody();
        tgsReqBody.setKdcOptions( srvTktReq.getKdcOptions() );
        tgsReqBody.setRealm( KdcClientUtil.extractRealm( serverPrincipal ) );
        tgsReqBody.setTill( getDefaultTill() );
        int currentNonce = nonceGenerator.nextInt();
        tgsReqBody.setNonce( currentNonce );
        tgsReqBody.setEType( config.getEncryptionTypes() );
        
        PrincipalName principalName = new PrincipalName( KdcClientUtil.extractName( serverPrincipal ), KerberosPrincipal.KRB_NT_SRV_HST );
        tgsReqBody.setSName( principalName );
        
        TgsReq tgsReq = new TgsReq();
        tgsReq.setKdcReqBody( tgsReqBody );
        
        PaData authnHeader = new PaData();
        authnHeader.setPaDataType( PaDataType.PA_TGS_REQ );
        authnHeader.setPaDataValue( getEncoded( apReq ) );
        
        tgsReq.addPaData( authnHeader );
        
        // Get the result from the future
        try
        {
            connect();
            
            // Read the response, waiting for it if not available immediately
            // Get the response, blocking
            KerberosMessage kdcRep = sendAndReceiveKrbMsg( tgsReq );

            if ( kdcRep == null )
            {
                // We didn't received anything : this is an error
                LOG.error( "TGT request failed : timeout occured" );
                throw new KerberosException( ErrorType.KRB_ERR_GENERIC, TIME_OUT_ERROR );
            }

            if ( kdcRep instanceof KrbError )
            {
                // We have an error
                LOG.debug( "TGT request failed : {}", kdcRep );
                throw new KerberosException( ( KrbError ) kdcRep );
            }

            TgsRep rep = ( TgsRep ) kdcRep;
            byte[] decryptedData = cipherTextHandler.decrypt( sessionKey, rep.getEncPart(), KeyUsage.TGS_REP_ENC_PART_TGS_SESS_KEY );
            EncTgsRepPart encTgsRepPart = KerberosDecoder.decodeEncTgsRepPart( decryptedData );
            
            if ( currentNonce != encTgsRepPart.getEncKdcRepPart().getNonce() )
            {
                throw new KerberosException( ErrorType.KRB_ERR_GENERIC, "received nonce didn't match with the nonce sent in the request" );
            }
            
            
            // Everything is fine, return the response
            LOG.debug( "TGT request successful : {}", rep );

            ServiceTicket srvTkt = new ServiceTicket( rep.getTicket(), encTgsRepPart.getEncKdcRepPart() );
            
            return srvTkt;
        }
        catch( KerberosException e )
        {
            throw e;
        }
        catch ( Exception te )
        {
            // We didn't receive anything : this is an error
            LOG.error( "TGT request failed : timeout occured" );
            throw new KerberosException( ErrorType.KRB_ERR_GENERIC, TIME_OUT_ERROR );
        }
        finally
        {
            if ( channel != null )
            {
                try
                {
                    channel.close();
                }
                catch( IOException e )
                {
                    LOG.warn( "Failed to close the channel", e );
                }
            }
        }
    }
    
    
    public ChangePasswordResult changePassword( String clientPrincipal, String oldPassword, String newPassword ) throws ChangePasswordException
    {
        KerberosChannel channel = null;
        
        try
        {
            TgtRequest clientTgtReq = new TgtRequest();
            clientTgtReq.setClientPrincipal( clientPrincipal );
            clientTgtReq.setPassword( oldPassword );
            clientTgtReq.setServerPrincipal( "kadmin/changepw@" + KdcClientUtil.extractRealm( clientPrincipal ) );
            
            TgTicket tgt = getTgt( clientTgtReq );
            
            ApReq apReq = new ApReq();
            ApOptions options = new ApOptions();
            apReq.setApOptions( options );
            apReq.setTicket( tgt.getTicket() );
            
            Authenticator authenticator = new Authenticator();
            authenticator.setCName( new PrincipalName( tgt.getClientName(), PrincipalNameType.KRB_NT_PRINCIPAL ) );
            authenticator.setCRealm( tgt.getRealm() );
            KerberosTime ctime = new KerberosTime();
            authenticator.setCTime( ctime );
            authenticator.setCusec( 0 );
            authenticator.setSeqNumber( nonceGenerator.nextInt() );
            
            EncryptionKey subKey = RandomKeyFactory.getRandomKey( usedEType );
            
            authenticator.setSubKey( subKey );
            
            EncryptedData authData = cipherTextHandler.encrypt( tgt.getSessionKey(), getEncoded( authenticator ), KeyUsage.AP_REQ_AUTHNT_SESS_KEY );
            apReq.setAuthenticator( authData );
            
            
            KrbPriv privateMessage = new KrbPriv();
            
            EncKrbPrivPart part = new EncKrbPrivPart();
            part.setSenderAddress( new HostAddress( InetAddress.getLocalHost() ) );
            part.setSeqNumber( authenticator.getSeqNumber() );
            part.setTimestamp( authenticator.getCtime() );

            short changePwdPVNO = ChangePasswordRequest.PVNO;
            
            if( config.isUseLegacyChngPwdProtocol() )
            {
                part.setUserData( Strings.getBytesUtf8( newPassword ) );
                changePwdPVNO = ChangePasswordRequest.OLD_PVNO;
            }
            else
            {
                ChangePasswdData chngPwdData = new ChangePasswdData();
                chngPwdData.setNewPasswd( Strings.getBytesUtf8( newPassword ) );
                //chngPwdData.setTargName( new PrincipalName( clientPrincipal, PrincipalNameType.KRB_NT_PRINCIPAL ) );
                //chngPwdData.setTargRealm( clientTgtReq.getRealm() );
                byte[] data = getEncoded( chngPwdData );
                part.setUserData( data );
            }
            
            EncryptedData encKrbPrivPartData = cipherTextHandler.encrypt( subKey, getEncoded( part ), KeyUsage.KRB_PRIV_ENC_PART_CHOSEN_KEY );
            privateMessage.setEncPart( encKrbPrivPartData );
            
            ChangePasswordRequest req = new ChangePasswordRequest( changePwdPVNO, apReq, privateMessage );
            
            channel = new KerberosChannel();
            channel.openConnection( config.getHostName(), config.getPasswdPort(), config.getTimeout(), config.isUseUdp() );
            
            AbstractPasswordMessage reply = sendAndReceiveChngPwdMsg( req, channel );
            
            if ( reply instanceof ChangePasswordError )
            {
                ChangePasswordError err = ( ChangePasswordError ) reply;
                
                ChangePasswordResult result = new ChangePasswordResult( err.getKrbError().getEData() );

                return result;
            }
            
            ChangePasswordReply chngPwdReply = ( ChangePasswordReply ) reply;
            ApRep chngApRep = chngPwdReply.getApplicationReply();
            byte[] apRepData = cipherTextHandler.decrypt( tgt.getSessionKey(), chngApRep.getEncPart(), KeyUsage.AP_REP_ENC_PART_SESS_KEY );
            
            EncApRepPart encApRepPart = KerberosDecoder.decodeEncApRepPart( apRepData );
            
            KrbPriv replyPriv = chngPwdReply.getPrivateMessage();
            byte[] data = cipherTextHandler.decrypt( encApRepPart.getSubkey(), replyPriv.getEncPart(), KeyUsage.KRB_PRIV_ENC_PART_CHOSEN_KEY );
            part = KerberosDecoder.decodeEncKrbPrivPart( data );
            
            ChangePasswordResult result = new ChangePasswordResult( part.getUserData() );
            
            return result;
        }
        catch( ChangePasswordException e )
        {
            throw e;
        }
        catch( Exception e )
        {
            LOG.warn( "failed to change the password", e );
            throw new ChangePasswordException( ChangePasswdErrorType.KRB5_KPASSWD_HARDERROR, e );
        }
        finally
        {
            if ( channel != null )
            {
                try
                {
                    channel.close();
                }
                catch( IOException e )
                {
                    LOG.warn( "Failed to close the channel", e );
                }
            }
        }
    }
    
    
    private byte[] getEncoded( Asn1Object obj )
    {
        try
        {
            ByteBuffer buf = ByteBuffer.allocate( obj.computeLength() );
            obj.encode( buf );
            
            return buf.array();
        }
        catch( Exception e )
        {
            // shouldn't happen, but if it does then log it and give  up
            LOG.error( "Failed to encode the ASN.1 object {}", obj );
            throw new RuntimeException( e );
        }
    }

    
    private KerberosTime getDefaultTill()
    {
        return new KerberosTime( System.currentTimeMillis() + ( KerberosTime.MINUTE * 60 ) );
    }
    

    private KerberosMessage sendAndReceiveKrbMsg( KerberosMessage req ) throws Exception
    {
        ByteBuffer encodedBuf = KerberosEncoder.encode( req, channel.isUseTcp() );
        encodedBuf.flip();
        
        ByteBuffer repData = channel.sendAndReceive( encodedBuf );
        
        KerberosMessageContainer kerberosMessageContainer = new KerberosMessageContainer();
        kerberosMessageContainer.setStream( repData );
        kerberosMessageContainer.setGathering( true );
        kerberosMessageContainer.setTCP( channel.isUseTcp() );

        return ( KerberosMessage ) KerberosDecoder.decode( kerberosMessageContainer, new Asn1Decoder() );
    }
    
    
    private AbstractPasswordMessage sendAndReceiveChngPwdMsg( AbstractPasswordMessage req, KerberosChannel chngPwdChannel ) throws Exception
    {
        ByteBuffer encodedBuf = ChangePasswordEncoder.encode( req, chngPwdChannel.isUseTcp() );
        encodedBuf.flip();
        ByteBuffer repData = chngPwdChannel.sendAndReceive( encodedBuf );
        
        return ChangePasswordDecoder.decode( repData, chngPwdChannel.isUseTcp() );
    }
}
