View Javadoc
1   /*
2    *   Licensed to the Apache Software Foundation (ASF) under one
3    *   or more contributor license agreements.  See the NOTICE file
4    *   distributed with this work for additional information
5    *   regarding copyright ownership.  The ASF licenses this file
6    *   to you under the Apache License, Version 2.0 (the
7    *   "License"); you may not use this file except in compliance
8    *   with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   *   Unless required by applicable law or agreed to in writing,
13   *   software distributed under the License is distributed on an
14   *   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   *   KIND, either express or implied.  See the License for the
16   *   specific language governing permissions and limitations
17   *   under the License.
18   *
19   */
20  package org.apache.directory.server.ldap;
21  
22  
23  import org.apache.directory.api.ldap.codec.api.LdapDecoder;
24  import org.apache.directory.api.ldap.codec.api.LdapMessageContainer;
25  import org.apache.directory.api.ldap.codec.api.SchemaBinaryAttributeDetector;
26  import org.apache.directory.api.ldap.model.exception.ResponseCarryingMessageException;
27  import org.apache.directory.api.ldap.model.message.Control;
28  import org.apache.directory.api.ldap.model.message.Message;
29  import org.apache.directory.api.ldap.model.message.Request;
30  import org.apache.directory.api.ldap.model.message.ResultCodeEnum;
31  import org.apache.directory.api.ldap.model.message.ResultResponse;
32  import org.apache.directory.api.ldap.model.message.ResultResponseRequest;
33  import org.apache.directory.api.ldap.model.message.extended.NoticeOfDisconnect;
34  import org.apache.mina.core.buffer.IoBuffer;
35  import org.apache.mina.core.session.IoSession;
36  import org.apache.mina.filter.FilterEvent;
37  import org.apache.mina.filter.ssl.SslEvent;
38  import org.apache.mina.handler.demux.DemuxingIoHandler;
39  import org.slf4j.Logger;
40  import org.slf4j.LoggerFactory;
41  
42  
43  /**
44   * The MINA IoHandler implementation extending {@link DemuxingIoHandler} for
45   * the LDAP protocol.  THe {@link LdapServer} creates this multiplexing
46   * {@link IoHandler} handler and populates it with subordinate handlers for
47   * the various kinds of LDAP {@link Request} messages.  This is done in the
48   * setXxxHandler() methods of the LdapServer where Xxxx is Add, Modify, etc.
49   *
50   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
51   */
52  class LdapProtocolHandler extends DemuxingIoHandler
53  {
54      /** The logger */
55      private static final Logger LOG = LoggerFactory.getLogger( LdapProtocolHandler.class );
56  
57      /** the {@link LdapServer} this handler is associated with */
58      private final LdapServer ldapServer;
59  
60  
61      /**
62       * Creates a new instance of LdapProtocolHandler.
63       *
64       * @param ldapServer The LDAP server instance
65       */
66      LdapProtocolHandler( LdapServer ldapServer )
67      {
68          this.ldapServer = ldapServer;
69      }
70  
71  
72      /**
73       * This method is called when a new session is created. We will store some
74       * informations that the session will need to process incoming requests.
75       * 
76       * @param session the newly created session
77       */
78      @Override
79      public void sessionCreated( IoSession session ) throws Exception
80      {
81          // First, create a new LdapSession and store it i the manager
82          LdapSessionapSession.html#LdapSession">LdapSession ldapSession = new LdapSession( session );
83          ldapServer.getLdapSessionManager().addLdapSession( ldapSession );
84  
85          // Now, we have to store the DirectoryService instance into the session
86          session.setAttribute( LdapDecoder.MAX_PDU_SIZE_ATTR, ldapServer.getDirectoryService().getMaxPDUSize() );
87  
88          // Last, store the message container
89          LdapMessageContainer<Message> ldapMessageContainer =
90              new LdapMessageContainer<>(
91                  ldapServer.getDirectoryService().getLdapCodecService(),
92                  new SchemaBinaryAttributeDetector(
93                      ldapServer.getDirectoryService().getSchemaManager() ) );
94  
95          session.setAttribute( LdapDecoder.MESSAGE_CONTAINER_ATTR, ldapMessageContainer );
96      }
97  
98  
99      /**
100      * This method is called when a session is closed. If we have some
101      * cleanup to do, it's done there.
102      * 
103      * @param session the closing session
104      */
105     @Override
106     public void sessionClosed( IoSession session )
107     {
108         // Get the associated LdapSession
109         LdapSession ldapSession = ldapServer.getLdapSessionManager().removeLdapSession( session );
110 
111         // Clean it up !
112         cleanUpSession( ldapSession );
113     }
114 
115 
116     /**
117      * Explicitly handles {@link LdapSession} and {@link IoSession} cleanup tasks.
118      *
119      * @param ldapSession the LdapSession to cleanup after being removed from
120      * the {@link LdapSessionManager}
121      */
122     private void cleanUpSession( LdapSession ldapSession )
123     {
124         if ( ldapSession == null )
125         {
126             LOG.debug( "Null LdapSession given to cleanUpSession." );
127             return;
128         }
129 
130         LOG.debug( "Cleaning the {} session", ldapSession );
131 
132         // Abandon all the requests
133         ldapSession.abandonAllOutstandingRequests();
134 
135         if ( !ldapSession.getIoSession().isClosing() || ldapSession.getIoSession().isConnected() )
136         {
137             try
138             {
139                 ldapSession.getIoSession().closeNow();
140             }
141             catch ( Throwable t )
142             {
143                 LOG.warn( "Failed to close IoSession for LdapSession." );
144             }
145         }
146     }
147 
148 
149     /**
150      * {@inheritDoc}
151      */
152     @Override
153     public void messageSent( IoSession session, Object message ) throws Exception
154     {
155         // Do nothing : we have to ignore this message, otherwise we get an exception,
156         // thanks to the way MINA 2 works ...
157         if ( message instanceof IoBuffer )
158         {
159             // Nothing to do in this case
160             return;
161         }
162 
163         super.messageSent( session, message );
164     }
165 
166     
167     /**
168      * {@inheritDoc}
169      */
170     @Override
171     public void event( IoSession session, FilterEvent event ) throws Exception 
172     {
173         if ( event instanceof SslEvent )
174         {
175             if ( ( ( SslEvent ) event ) == SslEvent.SECURED ) 
176             {
177                 LdapSession ldapSession = ldapServer.getLdapSessionManager().getLdapSession( session );
178                 LOG.debug( "Session {} secured", ldapSession ); 
179             }
180             else
181             {
182                 LdapSession ldapSession = ldapServer.getLdapSessionManager().getLdapSession( session );
183                 LOG.debug( "Session {} not secured", ldapSession ); 
184             }
185         }
186     }
187 
188 
189     /**
190      * {@inheritDoc}
191      */
192     @Override
193     public void messageReceived( IoSession session, Object message ) throws Exception
194     {
195         // Translate SSLFilter messages into LDAP extended request
196         // defined in RFC #2830, 'Lightweight Directory Access Protocol (v3):
197         // Extension for Transport Layer Security'.
198         //
199         // The RFC specifies the payload should be empty, but we use
200         // it to notify the TLS state changes.  This hack should be
201         // OK from the viewpointd of security because StartTLS
202         // handler should react to only SESSION_UNSECURED message
203         // and degrade authentication level to 'anonymous' as specified
204         // in the RFC, and this is no threat.
205         if ( ( ( Request ) message ).getControls().size() > 0
206             && message instanceof ResultResponseRequest )
207         {
208             ResultResponseRequest req = ( ResultResponseRequest ) message;
209 
210             for ( Control control : req.getControls().values() )
211             {
212                 if ( control.isCritical() && !ldapServer.getSupportedControls().contains( control.getOid() ) )
213                 {
214                     ResultResponse resp = req.getResultResponse();
215                     resp.getLdapResult().setDiagnosticMessage( "Unsupport critical control: " + control.getOid() );
216                     resp.getLdapResult().setResultCode( ResultCodeEnum.UNAVAILABLE_CRITICAL_EXTENSION );
217                     session.write( resp );
218 
219                     return;
220                 }
221             }
222         }
223 
224         super.messageReceived( session, message );
225     }
226 
227 
228     /**
229      * {@inheritDoc}
230      */
231     @Override
232     public void exceptionCaught( IoSession session, Throwable cause )
233     {
234         if ( cause.getCause() instanceof ResponseCarryingMessageException )
235         {
236             ResponseCarryingMessageException rcme = ( ResponseCarryingMessageException ) cause.getCause();
237 
238             if ( rcme.getResponse() != null )
239             {
240                 session.write( rcme.getResponse() );
241                 return;
242             }
243         }
244 
245         LOG.warn( "Unexpected exception forcing session to close: sending disconnect notice to client.", cause );
246 
247         session.write( NoticeOfDisconnect.PROTOCOLERROR );
248         session.closeOnFlush();
249         //LdapSession ldapSession = this.ldapServer.getLdapSessionManager().removeLdapSession( session );
250         //cleanUpSession( ldapSession );
251     }
252 }