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    public void sessionCreated( IoSession session ) throws Exception
079    {
080        if ( LOG.isDebugEnabled() )
081        {
082            LOG.debug( "{} CREATED:  {}", session.getRemoteAddress(), session.getTransportMetadata() );
083        }
084
085        session.getFilterChain().addFirst( "codec",
086            new ProtocolCodecFilter( ChangePasswordProtocolCodecFactory.getInstance() ) );
087    }
088
089
090    public void sessionOpened( IoSession session )
091    {
092        LOG.debug( "{} OPENED", session.getRemoteAddress() );
093    }
094
095
096    public void sessionClosed( IoSession session )
097    {
098        LOG.debug( "{} CLOSED", session.getRemoteAddress() );
099    }
100
101
102    public void sessionIdle( IoSession session, IdleStatus status )
103    {
104        LOG.debug( "{} IDLE ({})", session.getRemoteAddress(), status );
105    }
106
107
108    public void exceptionCaught( IoSession session, Throwable cause )
109    {
110        LOG.debug( session.getRemoteAddress() + " EXCEPTION", cause );
111        session.closeNow();
112    }
113
114
115    public void messageReceived( IoSession session, Object message )
116    {
117        LOG.debug( "{} RCVD:  {}", session.getRemoteAddress(), message );
118
119        InetAddress clientAddress = ( ( InetSocketAddress ) session.getRemoteAddress() ).getAddress();
120        ChangePasswordRequest request = ( ChangePasswordRequest ) message;
121
122        try
123        {
124            ChangePasswordContext changepwContext = new ChangePasswordContext();
125            changepwContext.setConfig( server.getConfig() );
126            changepwContext.setStore( store );
127            changepwContext.setClientAddress( clientAddress );
128            changepwContext.setRequest( request );
129            changepwContext.setReplayCache( server.getReplayCache() );
130            session.setAttribute( getContextKey(), changepwContext );
131
132            ChangePasswordService.execute( session, changepwContext );
133
134            session.write( changepwContext.getReply() );
135        }
136        catch ( KerberosException ke )
137        {
138            if ( LOG.isDebugEnabled() )
139            {
140                LOG.warn( ke.getLocalizedMessage(), ke );
141            }
142            else
143            {
144                LOG.warn( ke.getLocalizedMessage() );
145            }
146
147            KrbError errorMessage = getErrorMessage( server.getConfig().getServicePrincipal(), ke );
148
149            session.write( new ChangePasswordError( request.getVersionNumber(), errorMessage ) );
150        }
151        catch ( Exception e )
152        {
153            LOG.error( I18n.err( I18n.ERR_152, e.getLocalizedMessage() ), e );
154
155            KrbError error = getErrorMessage( server.getConfig().getServicePrincipal(), new ChangePasswordException(
156                ChangePasswdErrorType.KRB5_KPASSWD_UNKNOWN_ERROR ) );
157            session.write( new ChangePasswordError( request.getVersionNumber(), error ) );
158        }
159    }
160
161
162    public void messageSent( IoSession session, Object message )
163    {
164        if ( LOG.isDebugEnabled() )
165        {
166            LOG.debug( "{} SENT:  {}", session.getRemoteAddress(), message );
167        }
168    }
169
170
171    protected String getContextKey()
172    {
173        return ( this.contextKey );
174    }
175
176
177    private KrbError getErrorMessage( KerberosPrincipal principal, KerberosException exception )
178    {
179        KrbError krbError = new KrbError();
180
181        KerberosTime now = new KerberosTime();
182
183        //FIXME not sure if this is the correct error to set for KrbError instance
184        // the correct change password protocol related error code is set in e-data anyway
185        krbError.setErrorCode( ErrorType.KRB_ERR_GENERIC );
186        krbError.setEText( exception.getLocalizedMessage() );
187        krbError.setSName( new PrincipalName( principal ) );
188        krbError.setSTime( now );
189        krbError.setSusec( 0 );
190        krbError.setRealm( principal.getRealm() );
191        krbError.setEData( buildExplanatoryData( exception ) );
192
193        return krbError;
194    }
195
196
197    private byte[] buildExplanatoryData( KerberosException exception )
198    {
199        short resultCode = ( short ) exception.getErrorCode();
200
201        byte[] resultString =
202            { ( byte ) 0x00 };
203
204        if ( exception.getExplanatoryData() == null || exception.getExplanatoryData().length == 0 )
205        {
206            try
207            {
208                resultString = exception.getLocalizedMessage().getBytes( "UTF-8" );
209            }
210            catch ( UnsupportedEncodingException uee )
211            {
212                LOG.error( uee.getLocalizedMessage() );
213            }
214        }
215        else
216        {
217            resultString = exception.getExplanatoryData();
218        }
219
220        ByteBuffer byteBuffer = ByteBuffer.allocate( 2 + resultString.length );
221        byteBuffer.putShort( resultCode );
222        byteBuffer.put( resultString );
223
224        return byteBuffer.array();
225    }
226
227    
228    public void inputClosed( IoSession session )
229    {
230    }
231}