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 */
020
021package org.apache.directory.server.kerberos.changepwd.protocol;
022
023
024import java.io.UnsupportedEncodingException;
025import java.net.InetAddress;
026import java.net.InetSocketAddress;
027import java.nio.ByteBuffer;
028
029import javax.security.auth.kerberos.KerberosPrincipal;
030
031import org.apache.directory.server.i18n.I18n;
032import org.apache.directory.server.kerberos.changepwd.ChangePasswordServer;
033import org.apache.directory.server.kerberos.changepwd.exceptions.ChangePasswdErrorType;
034import org.apache.directory.server.kerberos.changepwd.exceptions.ChangePasswordException;
035import org.apache.directory.server.kerberos.changepwd.messages.ChangePasswordError;
036import org.apache.directory.server.kerberos.changepwd.messages.ChangePasswordRequest;
037import org.apache.directory.server.kerberos.changepwd.service.ChangePasswordContext;
038import org.apache.directory.server.kerberos.changepwd.service.ChangePasswordService;
039import org.apache.directory.server.kerberos.shared.store.PrincipalStore;
040import org.apache.directory.shared.kerberos.KerberosTime;
041import org.apache.directory.shared.kerberos.components.PrincipalName;
042import org.apache.directory.shared.kerberos.exceptions.ErrorType;
043import org.apache.directory.shared.kerberos.exceptions.KerberosException;
044import org.apache.directory.shared.kerberos.messages.KrbError;
045import org.apache.mina.core.service.IoHandlerAdapter;
046import org.apache.mina.core.session.IdleStatus;
047import org.apache.mina.core.session.IoSession;
048import org.apache.mina.filter.codec.ProtocolCodecFilter;
049import org.slf4j.Logger;
050import org.slf4j.LoggerFactory;
051
052
053/**
054 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
055 */
056public class ChangePasswordProtocolHandler extends IoHandlerAdapter
057{
058    private static final Logger LOG = LoggerFactory.getLogger( ChangePasswordProtocolHandler.class );
059
060    private ChangePasswordServer server;
061    private PrincipalStore store;
062    private String contextKey = "context";
063
064
065    /**
066     * Creates a new instance of ChangePasswordProtocolHandler.
067     *
068     * @param config The ChangePassword server configuration
069     * @param store The Principal store
070     */
071    public ChangePasswordProtocolHandler( ChangePasswordServer config, PrincipalStore store )
072    {
073        this.server = config;
074        this.store = store;
075    }
076
077
078    @Override
079    public void sessionCreated( IoSession session ) throws Exception
080    {
081        if ( LOG.isDebugEnabled() )
082        {
083            LOG.debug( "{} CREATED:  {}", session.getRemoteAddress(), session.getTransportMetadata() );
084        }
085
086        session.getFilterChain().addFirst( "codec",
087            new ProtocolCodecFilter( ChangePasswordProtocolCodecFactory.getInstance() ) );
088    }
089
090
091    @Override
092    public void sessionOpened( IoSession session )
093    {
094        LOG.debug( "{} OPENED", session.getRemoteAddress() );
095    }
096
097
098    @Override
099    public void sessionClosed( IoSession session )
100    {
101        LOG.debug( "{} CLOSED", session.getRemoteAddress() );
102    }
103
104
105    @Override
106    public void sessionIdle( IoSession session, IdleStatus status )
107    {
108        LOG.debug( "{} IDLE ({})", session.getRemoteAddress(), status );
109    }
110
111
112    @Override
113    public void exceptionCaught( IoSession session, Throwable cause )
114    {
115        LOG.debug( session.getRemoteAddress() + " EXCEPTION", cause );
116        session.closeNow();
117    }
118
119
120    @Override
121    public void messageReceived( IoSession session, Object message )
122    {
123        LOG.debug( "{} RCVD:  {}", session.getRemoteAddress(), message );
124
125        InetAddress clientAddress = ( ( InetSocketAddress ) session.getRemoteAddress() ).getAddress();
126        ChangePasswordRequest request = ( ChangePasswordRequest ) message;
127
128        try
129        {
130            ChangePasswordContext changepwContext = new ChangePasswordContext();
131            changepwContext.setConfig( server.getConfig() );
132            changepwContext.setStore( store );
133            changepwContext.setClientAddress( clientAddress );
134            changepwContext.setRequest( request );
135            changepwContext.setReplayCache( server.getReplayCache() );
136            session.setAttribute( getContextKey(), changepwContext );
137
138            ChangePasswordService.execute( session, changepwContext );
139
140            session.write( changepwContext.getReply() );
141        }
142        catch ( KerberosException ke )
143        {
144            if ( LOG.isDebugEnabled() )
145            {
146                LOG.warn( ke.getLocalizedMessage(), ke );
147            }
148            else
149            {
150                LOG.warn( ke.getLocalizedMessage() );
151            }
152
153            KrbError errorMessage = getErrorMessage( server.getConfig().getServicePrincipal(), ke );
154
155            session.write( new ChangePasswordError( request.getVersionNumber(), errorMessage ) );
156        }
157        catch ( Exception e )
158        {
159            LOG.error( I18n.err( I18n.ERR_152, e.getLocalizedMessage() ), e );
160
161            KrbError error = getErrorMessage( server.getConfig().getServicePrincipal(), new ChangePasswordException(
162                ChangePasswdErrorType.KRB5_KPASSWD_UNKNOWN_ERROR ) );
163            session.write( new ChangePasswordError( request.getVersionNumber(), error ) );
164        }
165    }
166
167
168    @Override
169    public void messageSent( IoSession session, Object message )
170    {
171        if ( LOG.isDebugEnabled() )
172        {
173            LOG.debug( "{} SENT:  {}", session.getRemoteAddress(), message );
174        }
175    }
176
177
178    protected String getContextKey()
179    {
180        return ( this.contextKey );
181    }
182
183
184    private KrbError getErrorMessage( KerberosPrincipal principal, KerberosException exception )
185    {
186        KrbError krbError = new KrbError();
187
188        KerberosTime now = new KerberosTime();
189
190        //FIXME not sure if this is the correct error to set for KrbError instance
191        // the correct change password protocol related error code is set in e-data anyway
192        krbError.setErrorCode( ErrorType.KRB_ERR_GENERIC );
193        krbError.setEText( exception.getLocalizedMessage() );
194        krbError.setSName( new PrincipalName( principal ) );
195        krbError.setSTime( now );
196        krbError.setSusec( 0 );
197        krbError.setRealm( principal.getRealm() );
198        krbError.setEData( buildExplanatoryData( exception ) );
199
200        return krbError;
201    }
202
203
204    private byte[] buildExplanatoryData( KerberosException exception )
205    {
206        short resultCode = ( short ) exception.getErrorCode();
207
208        byte[] resultString =
209            { ( byte ) 0x00 };
210
211        if ( exception.getExplanatoryData() == null || exception.getExplanatoryData().length == 0 )
212        {
213            try
214            {
215                resultString = exception.getLocalizedMessage().getBytes( "UTF-8" );
216            }
217            catch ( UnsupportedEncodingException uee )
218            {
219                LOG.error( uee.getLocalizedMessage() );
220            }
221        }
222        else
223        {
224            resultString = exception.getExplanatoryData();
225        }
226
227        ByteBuffer byteBuffer = ByteBuffer.allocate( 2 + resultString.length );
228        byteBuffer.putShort( resultCode );
229        byteBuffer.put( resultString );
230
231        return byteBuffer.array();
232    }
233
234    
235    @Override
236    public void inputClosed( IoSession session )
237    {
238    }
239}