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;
021
022
023import java.net.InetAddress;
024import java.net.InetSocketAddress;
025
026import javax.security.auth.kerberos.KerberosPrincipal;
027
028import org.apache.directory.api.ldap.model.constants.Loggers;
029import org.apache.directory.server.i18n.I18n;
030import org.apache.directory.server.kerberos.kdc.KdcServer;
031import org.apache.directory.server.kerberos.kdc.authentication.AuthenticationContext;
032import org.apache.directory.server.kerberos.kdc.authentication.AuthenticationService;
033import org.apache.directory.server.kerberos.kdc.ticketgrant.TicketGrantingContext;
034import org.apache.directory.server.kerberos.kdc.ticketgrant.TicketGrantingService;
035import org.apache.directory.server.kerberos.shared.store.PrincipalStore;
036import org.apache.directory.shared.kerberos.KerberosMessageType;
037import org.apache.directory.shared.kerberos.KerberosTime;
038import org.apache.directory.shared.kerberos.components.KdcReq;
039import org.apache.directory.shared.kerberos.components.PrincipalName;
040import org.apache.directory.shared.kerberos.exceptions.ErrorType;
041import org.apache.directory.shared.kerberos.exceptions.KerberosException;
042import org.apache.directory.shared.kerberos.messages.KrbError;
043import org.apache.mina.core.service.IoHandlerAdapter;
044import org.apache.mina.core.session.IdleStatus;
045import org.apache.mina.core.session.IoSession;
046import org.slf4j.Logger;
047import org.slf4j.LoggerFactory;
048
049
050/**
051 * The Kerberos protocol handler for MINA which handles requests for the authentication
052 * service and the ticket granting service of the KDC.
053 *
054 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
055 */
056public class KerberosProtocolHandler extends IoHandlerAdapter
057{
058    /** The loggers for this class */
059    private static final Logger LOG = LoggerFactory.getLogger( KerberosProtocolHandler.class );
060    private static final Logger LOG_KRB = LoggerFactory.getLogger( Loggers.KERBEROS_LOG.getName() );
061
062    /** The KDC server */
063    private KdcServer kdcServer;
064
065    /** The principal Name store */
066    private PrincipalStore store;
067
068    private static final String CONTEXT_KEY = "context";
069
070
071    /**
072     * Creates a new instance of KerberosProtocolHandler.
073     *
074     * @param kdcServer The KdcServer instance
075     * @param store The Principal store
076     */
077    public KerberosProtocolHandler( KdcServer kdcServer, PrincipalStore store )
078    {
079        this.kdcServer = kdcServer;
080        this.store = store;
081    }
082
083
084    /**
085     * {@inheritDoc}
086     */
087    public void sessionCreated( IoSession session ) throws Exception
088    {
089        if ( LOG.isDebugEnabled() )
090        {
091            LOG.debug( "{} CREATED:  {}", session.getRemoteAddress(), session.getTransportMetadata() );
092        }
093
094        if ( LOG_KRB.isDebugEnabled() )
095        {
096            LOG_KRB.debug( "{} CREATED:  {}", session.getRemoteAddress(), session.getTransportMetadata() );
097        }
098    }
099
100
101    /**
102     * {@inheritDoc}
103     */
104    public void sessionOpened( IoSession session )
105    {
106        if ( LOG.isDebugEnabled() )
107        {
108            LOG.debug( "{} OPENED", session.getRemoteAddress() );
109        }
110
111        if ( LOG_KRB.isDebugEnabled() )
112        {
113            LOG_KRB.debug( "{} OPENED", session.getRemoteAddress() );
114        }
115    }
116
117
118    /**
119     * {@inheritDoc}
120     */
121    public void sessionClosed( IoSession session )
122    {
123        if ( LOG.isDebugEnabled() )
124        {
125            LOG.debug( "{} CLOSED", session.getRemoteAddress() );
126        }
127
128        if ( LOG_KRB.isDebugEnabled() )
129        {
130            LOG_KRB.debug( "{} CLOSED", session.getRemoteAddress() );
131        }
132    }
133
134
135    /**
136     * {@inheritDoc}
137     */
138    public void sessionIdle( IoSession session, IdleStatus status )
139    {
140        if ( LOG.isDebugEnabled() )
141        {
142            LOG.debug( "{} IDLE ({})", session.getRemoteAddress(), status );
143        }
144
145        if ( LOG_KRB.isDebugEnabled() )
146        {
147            LOG_KRB.debug( "{} IDLE ({})", session.getRemoteAddress(), status );
148        }
149    }
150
151
152    /**
153     * {@inheritDoc}
154     */
155    public void exceptionCaught( IoSession session, Throwable cause )
156    {
157        LOG.error( "{} EXCEPTION", session.getRemoteAddress(), cause );
158        LOG_KRB.error( "{} EXCEPTION", session.getRemoteAddress(), cause );
159        session.closeNow();
160    }
161
162
163    /**
164     * {@inheritDoc}
165     */
166    public void messageReceived( IoSession session, Object message )
167    {
168        if ( LOG.isDebugEnabled() )
169        {
170            LOG.debug( "{} RCVD: {}", session.getRemoteAddress(), message );
171        }
172
173        if ( LOG_KRB.isDebugEnabled() )
174        {
175            LOG_KRB.debug( "{} RCVD: {}", session.getRemoteAddress(), message );
176        }
177
178        InetAddress clientAddress = ( ( InetSocketAddress ) session.getRemoteAddress() ).getAddress();
179
180        if ( !( message instanceof KdcReq ) )
181        {
182            LOG.error( I18n.err( I18n.ERR_152, ErrorType.KRB_AP_ERR_BADDIRECTION ) );
183            LOG_KRB.error( I18n.err( I18n.ERR_152, ErrorType.KRB_AP_ERR_BADDIRECTION ) );
184
185            session.write( getErrorMessage( kdcServer.getConfig().getServicePrincipal(), new KerberosException(
186                ErrorType.KRB_AP_ERR_BADDIRECTION ) ) );
187            return;
188        }
189
190        KdcReq request = ( KdcReq ) message;
191
192        KerberosMessageType messageType = request.getMessageType();
193
194        try
195        {
196            switch ( messageType )
197            {
198                case AS_REQ:
199                    AuthenticationContext authContext = new AuthenticationContext();
200                    authContext.setConfig( kdcServer.getConfig() );
201                    authContext.setStore( store );
202                    
203                    if ( request.getKdcReqBody().getAddresses() != null )
204                    {
205                        authContext.setClientAddress( clientAddress );
206                    }
207                    
208                    authContext.setRequest( request );
209                    session.setAttribute( CONTEXT_KEY, authContext );
210
211                    AuthenticationService.execute( authContext );
212
213                    LOG_KRB.debug( "AuthenticationContext for AS_REQ : \n{}", authContext );
214
215                    session.write( authContext.getReply() );
216                    break;
217
218                case TGS_REQ:
219                    TicketGrantingContext tgsContext = new TicketGrantingContext();
220                    tgsContext.setConfig( kdcServer.getConfig() );
221                    tgsContext.setReplayCache( kdcServer.getReplayCache() );
222                    tgsContext.setStore( store );
223                    tgsContext.setClientAddress( clientAddress );
224                    tgsContext.setRequest( request );
225                    session.setAttribute( CONTEXT_KEY, tgsContext );
226
227                    TicketGrantingService.execute( tgsContext );
228
229                    LOG_KRB.debug( "TGSContext for TGS_REQ : \n {}", tgsContext );
230
231                    session.write( tgsContext.getReply() );
232                    break;
233
234                case AS_REP:
235                case TGS_REP:
236                    throw new KerberosException( ErrorType.KRB_AP_ERR_BADDIRECTION );
237
238                default:
239                    throw new KerberosException( ErrorType.KRB_AP_ERR_MSG_TYPE );
240            }
241        }
242        catch ( KerberosException ke )
243        {
244            String messageText = ke.getLocalizedMessage() + " (" + ke.getErrorCode() + ")";
245
246            LOG.warn( messageText );
247            LOG_KRB.warn( messageText );
248
249            KrbError error = getErrorMessage( kdcServer.getConfig().getServicePrincipal(), ke );
250
251            logErrorMessage( error );
252
253            session.write( error );
254        }
255        catch ( Exception e )
256        {
257            LOG.error( I18n.err( I18n.ERR_152, e.getLocalizedMessage() ), e );
258            LOG_KRB.error( I18n.err( I18n.ERR_152, e.getLocalizedMessage() ), e );
259
260            session.write( getErrorMessage( kdcServer.getConfig().getServicePrincipal(), new KerberosException(
261                ErrorType.KDC_ERR_SVC_UNAVAILABLE ) ) );
262        }
263    }
264
265
266    /**
267     * {@inheritDoc}
268     */
269    public void messageSent( IoSession session, Object message )
270    {
271        if ( LOG.isDebugEnabled() )
272        {
273            LOG.debug( "{} SENT:  {}", session.getRemoteAddress(), message );
274        }
275
276        if ( LOG_KRB.isDebugEnabled() )
277        {
278            LOG_KRB.debug( "{} SENT:  {}", session.getRemoteAddress(), message );
279        }
280    }
281
282
283    /**
284     * Construct an error message given some conditions
285     * 
286     * @param principal The Kerberos Principal
287     * @param exception The Exception we've got
288     * @return The resulting KrbError
289     */
290    protected KrbError getErrorMessage( KerberosPrincipal principal, KerberosException exception )
291    {
292        KrbError krbError = new KrbError();
293
294        KerberosTime now = new KerberosTime();
295
296        krbError.setErrorCode( ErrorType.getTypeByValue( exception.getErrorCode() ) );
297        krbError.setEText( exception.getLocalizedMessage() );
298        krbError.setSName( new PrincipalName( principal ) );
299        krbError.setRealm( principal.getRealm() );
300        krbError.setSTime( now );
301        krbError.setSusec( 0 );
302        krbError.setEData( exception.getExplanatoryData() );
303
304        return krbError;
305    }
306
307
308    /**
309     * Creates an explicit error message
310     * The error we've get 
311     * 
312     * @param error The Kerberos error to log
313     */
314    protected void logErrorMessage( KrbError error )
315    {
316        try
317        {
318            StringBuilder sb = new StringBuilder();
319
320            sb.append( "Responding to request with error:" );
321            sb.append( "\n\t" + "explanatory text:      " + error.getEText() );
322            sb.append( "\n\t" + "error code:            " + error.getErrorCode() );
323            sb.append( "\n\t" + "clientPrincipal:       " + error.getCName() ).append( "@" ).append( error.getCRealm() );
324            sb.append( "\n\t" + "client time:           " + error.getCTime() );
325            sb.append( "\n\t" + "serverPrincipal:       " + error.getSName() ).append( "@" ).append( error.getRealm() );
326            sb.append( "\n\t" + "server time:           " + error.getSTime() );
327
328            String message = sb.toString();
329
330            LOG.debug( message );
331            LOG_KRB.debug( message );
332        }
333        catch ( Exception e )
334        {
335            // This is a monitor.  No exceptions should bubble up.
336            LOG.error( I18n.err( I18n.ERR_155 ), e );
337            LOG_KRB.error( I18n.err( I18n.ERR_155 ), e );
338        }
339    }
340
341    
342    public void inputClosed( IoSession session )
343    {
344        session.closeNow();
345    }
346}