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.ldap;
021
022
023import java.io.IOException;
024import java.net.SocketAddress;
025import java.util.Collections;
026import java.util.HashMap;
027import java.util.Map;
028import java.util.concurrent.ConcurrentHashMap;
029import java.util.concurrent.locks.Lock;
030import java.util.concurrent.locks.ReentrantLock;
031
032import org.apache.directory.api.ldap.model.cursor.Cursor;
033import org.apache.directory.api.ldap.model.entry.Entry;
034import org.apache.directory.api.ldap.model.message.AbandonableRequest;
035import org.apache.directory.api.ldap.model.message.BindStatus;
036import org.apache.directory.api.ldap.model.message.SearchRequest;
037import org.apache.directory.server.core.api.CoreSession;
038import org.apache.directory.server.core.api.LdapPrincipal;
039import org.apache.directory.server.core.api.SearchRequestContainer;
040import org.apache.directory.server.i18n.I18n;
041import org.apache.directory.server.ldap.handlers.controls.PagedSearchContext;
042import org.apache.mina.core.session.IoSession;
043import org.slf4j.Logger;
044import org.slf4j.LoggerFactory;
045
046
047/**
048 * An object representing an LdapSession. Any connection established with the
049 * LDAP server forms a session.
050 *
051 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
052 */
053public class LdapSession
054{
055    /** The logger */
056    private static final Logger LOG = LoggerFactory.getLogger( LdapSession.class );
057
058    /** A speedup for logs */
059    private static final boolean IS_DEBUG = LOG.isDebugEnabled();
060
061    /** The list of requests we can abandon */
062    private static final AbandonableRequest[] EMPTY_ABANDONABLES = new AbandonableRequest[0];
063
064    /** A lock to protect the abandonableRequests against concurrent access */
065    private final Lock outstandingLock;
066
067    /**
068     * The associated IoSession. Usually, a LdapSession is established
069     * at the user request, which means we have a IoSession.
070     */
071    private final IoSession ioSession;
072
073    /** The CoreSession */
074    private CoreSession coreSession;
075
076    /** A reference on the LdapServer instance */
077    private LdapServer ldapServer;
078
079    /** A map of all the running requests */
080    private Map<Integer, AbandonableRequest> outstandingRequests;
081
082    /** A map of all the pending search requests */
083    private Map<Integer, SearchRequestContainer> searchRequests;
084
085    /** The current Bind status */
086    private BindStatus bindStatus;
087
088    /** The current mechanism used to authenticate the user */
089    private String currentMechanism;
090
091    /**
092     * A Map containing Objects used during the SASL negotiation
093     */
094    private Map<String, Object> saslProperties;
095
096    /** A map containing all the paged search context */
097    private Map<Integer, PagedSearchContext> pagedSearchContexts;
098
099
100    /**
101     * Creates a new instance of LdapSession associated with the underlying
102     * connection (MINA IoSession) to the server.
103     *
104     * @param ioSession the MINA session associated this LdapSession
105     */
106    public LdapSession( IoSession ioSession )
107    {
108        this.ioSession = ioSession;
109        outstandingLock = new ReentrantLock();
110        outstandingRequests = new ConcurrentHashMap<>();
111        searchRequests = new ConcurrentHashMap<>();
112        bindStatus = BindStatus.ANONYMOUS;
113        saslProperties = new HashMap<>();
114        pagedSearchContexts = new ConcurrentHashMap<>();
115    }
116
117
118    /**
119     * Check if the session is authenticated. There are two conditions for
120     * a session to be authenticated :<br>
121     * - the coreSession must not be null<br>
122     * - and the state should be Authenticated.
123     *
124     * @return <code>true</code> if the session is not anonymous
125     */
126    public boolean isAuthenticated()
127    {
128        return ( coreSession != null ) && ( bindStatus == BindStatus.AUTHENTICATED );
129    }
130
131
132    /**
133     * Check if the session is authenticated. There are two conditions for
134     * a session to be authenticated :<br>
135     * - it has to exist<br>
136     * - and the session should not be anonymous.
137     *
138     * @return <code>true</code> if the session is not anonymous
139     */
140    public boolean isAnonymous()
141    {
142        return bindStatus == BindStatus.ANONYMOUS;
143    }
144
145
146    /**
147     * Check if the session is processing a BindRequest, either Simple
148     * or SASL
149     *
150     * @return <code>true</code> if the session is in AuthPending state
151     */
152    public boolean isAuthPending()
153    {
154        return ( bindStatus == BindStatus.SIMPLE_AUTH_PENDING ) || ( bindStatus == BindStatus.SASL_AUTH_PENDING );
155    }
156
157
158    /**
159     * Check if the session is processing a Simple BindRequest
160     *
161     * @return <code>true</code> if the session is in AuthPending state
162     */
163    public boolean isSimpleAuthPending()
164    {
165        return bindStatus == BindStatus.SIMPLE_AUTH_PENDING;
166    }
167
168
169    /**
170     * Check if the session is processing a SASL BindRequest
171     *
172     * @return <code>true</code> if the session is in AuthPending state
173     */
174    public boolean isSaslAuthPending()
175    {
176        return bindStatus == BindStatus.SASL_AUTH_PENDING;
177    }
178
179
180    /**
181     * Gets the MINA IoSession associated with this LdapSession.
182     *
183     * @return the MINA IoSession
184     */
185    public IoSession getIoSession()
186    {
187        return ioSession;
188    }
189
190
191    /**
192     * Gets the logical core DirectoryService session associated with this
193     * LdapSession.
194     *
195     * @return the logical core DirectoryService session
196     */
197    public CoreSession getCoreSession()
198    {
199        return coreSession;
200    }
201
202
203    /**
204     * Sets the logical core DirectoryService session.
205     *
206     * @param coreSession the logical core DirectoryService session
207     */
208    public void setCoreSession( CoreSession coreSession )
209    {
210        this.coreSession = coreSession;
211    }
212
213
214    /**
215     * Abandons all outstanding requests associated with this session.
216     */
217    public void abandonAllOutstandingRequests()
218    {
219        try
220        {
221            outstandingLock.lock();
222            AbandonableRequest[] abandonables = outstandingRequests.values().toArray( EMPTY_ABANDONABLES );
223
224            for ( AbandonableRequest abandonable : abandonables )
225            {
226                abandonOutstandingRequest( abandonable.getMessageId() );
227            }
228        }
229        finally 
230        {
231            outstandingLock.unlock();   
232        }
233    }
234
235
236    /**
237     * Abandons a specific request by messageId.
238     *
239     * @param messageId The request ID to abandon
240     * @return The found request
241     */
242    public AbandonableRequest abandonOutstandingRequest( int messageId )
243    {
244        AbandonableRequest request = null;
245
246        try
247        {
248            outstandingLock.lock();
249            request = outstandingRequests.remove( messageId );
250        }
251        finally 
252        {
253            outstandingLock.unlock();   
254        }
255
256        // Remove the PagedSearch cursors now
257        try
258        {
259            closeAllPagedSearches();
260        }
261        catch ( Exception e )
262        {
263            LOG.error( I18n.err( I18n.ERR_172, e.getLocalizedMessage() ) );
264        }
265
266        if ( request == null )
267        {
268            LOG.warn( "AbandonableRequest with messageId {} not found in outstandingRequests.", messageId );
269            
270            return null;
271        }
272
273        if ( request.isAbandoned() )
274        {
275            LOG.info( "AbandonableRequest with messageId {} has already been abandoned", messageId );
276            
277            return request;
278        }
279
280        request.abandon();
281
282        if ( IS_DEBUG )
283        {
284            LOG.debug( "AbandonRequest on AbandonableRequest wth messageId {} was successful.", messageId );
285        }
286
287        return request;
288    }
289
290
291    /**
292     * Registers an outstanding request which can be abandoned later.
293     *
294     * @param request an outstanding request that can be abandoned
295     */
296    public void registerOutstandingRequest( AbandonableRequest request )
297    {
298        try
299        {
300            outstandingLock.lock();
301            outstandingRequests.put( request.getMessageId(), request );
302        }
303        finally 
304        {
305            outstandingLock.unlock();   
306        }
307
308    }
309
310
311    /**
312     * Unregisters an outstanding request.
313     *
314     * @param request the request to unregister
315     */
316    public void unregisterOutstandingRequest( AbandonableRequest request )
317    {
318        try
319        {
320            outstandingLock.lock();
321            outstandingRequests.remove( request.getMessageId() );
322        }
323        finally 
324        {
325            outstandingLock.unlock();   
326        }
327    }
328
329
330    /**
331     * @return A list of all the abandonable requests for this session.
332     */
333    public Map<Integer, AbandonableRequest> getOutstandingRequests()
334    {
335        try
336        {
337            outstandingLock.lock();
338
339            return Collections.unmodifiableMap( outstandingRequests );
340        }
341        finally 
342        {
343            outstandingLock.unlock();   
344        }
345    }
346
347
348    /**
349     * Registers a new searchRequest
350     *
351     * @param searchRequest a new searchRequest
352     * @param cursor The cursor to register
353     */
354    public void registerSearchRequest( SearchRequest searchRequest, Cursor<Entry> cursor )
355    {
356        try
357        {
358            outstandingLock.lock();
359            SearchRequestContainer searchRequestContainer = new SearchRequestContainer( searchRequest, cursor );
360            searchRequests.put( searchRequest.getMessageId(), searchRequestContainer );
361        }
362        finally 
363        {
364            outstandingLock.unlock();   
365        }
366    }
367
368
369    /**
370     * Unregisters a completed search request.
371     *
372     * @param searchRequest the searchRequest to unregister
373     */
374    public void unregisterSearchRequest( SearchRequest searchRequest )
375    {
376        searchRequests.remove( searchRequest.getMessageId() );
377    }
378
379
380    /**
381     * Find the searchRequestContainer associated with a MessageID
382     *
383     * @param messageId the SearchRequestContainer MessageID we are looking for
384     * @return The found SearchRequestContainer 
385     */
386    public SearchRequestContainer getSearchRequest( int messageId )
387    {
388        return searchRequests.get( messageId );
389    }
390
391
392    /**
393     * @return the current bind status for this session
394     */
395    public BindStatus getBindStatus()
396    {
397        return bindStatus;
398    }
399
400
401    /**
402     * Set the current BindStatus to Simple authentication pending
403     */
404    public void setSimpleAuthPending()
405    {
406        bindStatus = BindStatus.SIMPLE_AUTH_PENDING;
407    }
408
409
410    /**
411     * Set the current BindStatus to SASL authentication pending
412     */
413    public void setSaslAuthPending()
414    {
415        bindStatus = BindStatus.SASL_AUTH_PENDING;
416    }
417
418
419    /**
420     * Set the current BindStatus to Anonymous
421     */
422    public void setAnonymous()
423    {
424        bindStatus = BindStatus.ANONYMOUS;
425    }
426
427
428    /**
429     * Set the current BindStatus to authenticated
430     */
431    public void setAuthenticated()
432    {
433        bindStatus = BindStatus.AUTHENTICATED;
434    }
435
436
437    /**
438     * Get the mechanism selected by a user during a SASL Bind negotiation.
439     *
440     * @return The used mechanism, if any
441     */
442    public String getCurrentMechanism()
443    {
444        return currentMechanism;
445    }
446
447
448    /**
449     * Add a Sasl property and value
450     *
451     * @param property the property to add
452     * @param value the value for this property
453     */
454    public void putSaslProperty( String property, Object value )
455    {
456        saslProperties.put( property, value );
457    }
458
459
460    /**
461     * Get a Sasl property's value
462     *
463     * @param property the property to get
464     * @return the associated value, or null if we don't have such a property
465     */
466    public Object getSaslProperty( String property )
467    {
468        return saslProperties.get( property );
469    }
470
471
472    /**
473     * Clear all the Sasl values stored into the Map
474     */
475    public void clearSaslProperties()
476    {
477        saslProperties.clear();
478    }
479
480
481    /**
482     * Remove a property from the SaslProperty map
483     *
484     * @param property the property to remove
485     */
486    public void removeSaslProperty( String property )
487    {
488        saslProperties.remove( property );
489    }
490
491
492    /**
493     *  @return The LdapServer reference
494     */
495    public LdapServer getLdapServer()
496    {
497        return ldapServer;
498    }
499
500
501    /**
502     * Store a reference on the LdapServer intance
503     *
504     * @param ldapServer the LdapServer instance
505     */
506    public void setLdapServer( LdapServer ldapServer )
507    {
508        this.ldapServer = ldapServer;
509    }
510
511
512    /**
513     * Add a new Paged Search context into the stored context. If some
514     * context with the same id already exists, it will be closed and
515     * removed.
516     *
517     * @param context The context to add
518     */
519    public void addPagedSearchContext( PagedSearchContext context )
520    {
521        PagedSearchContext oldContext = pagedSearchContexts.put( context.getCookieValue(), context );
522
523        if ( oldContext != null )
524        {
525            // ??? Very unlikely to happen ...
526            Cursor<Entry> cursor = oldContext.getCursor();
527
528            if ( cursor != null )
529            {
530                try
531                {
532                    cursor.close();
533                }
534                catch ( Exception e )
535                {
536                    LOG.error( I18n.err( I18n.ERR_172, e.getLocalizedMessage() ) );
537                }
538            }
539        }
540    }
541
542
543    /**
544     * Remove a Paged Search context from the map storing all of them.
545     *
546     * @param contextId The context ID to remove
547     * @return The removed context if any found
548     */
549    public PagedSearchContext removePagedSearchContext( int contextId )
550    {
551        return pagedSearchContexts.remove( contextId );
552    }
553
554
555    /**
556     * Close all the pending cursors for all the pending PagedSearches
557     *
558     * @throws IOException If we've got an exception.
559     */
560    public void closeAllPagedSearches() throws IOException
561    {
562        for ( Map.Entry<Integer, PagedSearchContext> entry : pagedSearchContexts.entrySet() )
563        {
564            Cursor<Entry> cursor = entry.getValue().getCursor();
565
566            if ( cursor != null )
567            {
568                cursor.close();
569            }
570        }
571    }
572
573    /**
574     * Get paged search context associated with an ID
575     * @param contextId The id for teh context we want to get
576     * @return The associated context, if any
577     */
578    public PagedSearchContext getPagedSearchContext( int contextId )
579    {
580        return pagedSearchContexts.get( contextId );
581    }
582
583
584    /**
585     * The principal and remote address associated with this session.
586     * @see Object#toString()
587     */
588    public String toString()
589    {
590        if ( coreSession == null )
591        {
592            return "LdapSession : No Ldap session ...";
593        }
594
595        StringBuilder sb = new StringBuilder();
596
597        LdapPrincipal principal = coreSession.getAuthenticatedPrincipal();
598        SocketAddress address = coreSession.getClientAddress();
599
600        sb.append( "LdapSession : <" );
601
602        if ( principal != null )
603        {
604            sb.append( principal );
605            sb.append( "," );
606        }
607
608        if ( address != null )
609        {
610            sb.append( address );
611        }
612        else
613        {
614            sb.append( "..." );
615        }
616
617        sb.append( ">" );
618
619        return sb.toString();
620    }
621}