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    package org.apache.directory.server.kerberos.shared.replay;
021    
022    
023    import java.util.ArrayList;
024    import java.util.Collection;
025    import java.util.HashMap;
026    import java.util.Iterator;
027    import java.util.List;
028    import java.util.Map;
029    
030    import javax.security.auth.kerberos.KerberosPrincipal;
031    
032    import org.apache.directory.server.kerberos.shared.messages.value.KerberosTime;
033    
034    
035    /**
036     * "The replay cache will store at least the server name, along with the client name,
037     * time, and microsecond fields from the recently-seen authenticators, and if a
038     * matching tuple is found, the KRB_AP_ERR_REPEAT error is returned."
039     * 
040     * We will store the entries using an HashMap which key will be the client
041     * principal, and we will store a list of entries for each client principal.
042     * 
043     * A thread will run every N seconds to clean the cache from entries out of the 
044     * clockSkew
045     *    
046     * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
047     * @version $Rev: 589775 $, $Date: 2007-10-29 19:04:59 +0100 (Mon, 29 Oct 2007) $
048     */
049    public class InMemoryReplayCache extends Thread implements ReplayCache
050    {
051        /** Stores the entries in memory */
052        private Map<KerberosPrincipal, List<ReplayCacheEntry>> cache = new HashMap<KerberosPrincipal, List<ReplayCacheEntry>>();
053    
054        /** default clock skew */
055        private static final long DEFAULT_CLOCK_SKEW = 5 * KerberosTime.MINUTE;
056        
057        /** The clock skew */
058        private long clockSkew = DEFAULT_CLOCK_SKEW;
059    
060        /** The default delay between each run of the cleaning process : 5 s */
061        private static long DEFAULT_DELAY = 5 * 1000;  
062        
063        /** The delay to wait between each cache cleaning */
064        private long delay;
065    
066        /**
067         * A structure to hold an entry
068         */
069        public class ReplayCacheEntry
070        {
071            private KerberosPrincipal serverPrincipal;
072            private KerberosPrincipal clientPrincipal;
073            private KerberosTime clientTime;
074            private int clientMicroSeconds;
075    
076    
077            /**
078             * Creates a new instance of ReplayCacheEntry.
079             * 
080             * @param serverPrincipal 
081             * @param clientPrincipal 
082             * @param clientTime 
083             * @param clientMicroSeconds 
084             */
085            public ReplayCacheEntry( KerberosPrincipal serverPrincipal, KerberosPrincipal clientPrincipal,
086                KerberosTime clientTime, int clientMicroSeconds )
087            {
088                this.serverPrincipal = serverPrincipal;
089                this.clientPrincipal = clientPrincipal;
090                this.clientTime = clientTime;
091                this.clientMicroSeconds = clientMicroSeconds;
092            }
093    
094    
095            /**
096             * Returns whether this {@link ReplayCacheEntry} is equal to another {@link ReplayCacheEntry}.
097             * {@link ReplayCacheEntry}'s are equal when the server name, client name, client time, and
098             * the client microseconds are equal.
099             *
100             * @param that
101             * @return true if the ReplayCacheEntry's are equal.
102             */
103            public boolean equals( ReplayCacheEntry that )
104            {
105                return serverPrincipal.equals( that.serverPrincipal ) && clientPrincipal.equals( that.clientPrincipal )
106                    && clientTime.equals( that.clientTime ) && clientMicroSeconds == that.clientMicroSeconds;
107            }
108    
109    
110            /**
111             * Returns whether this {@link ReplayCacheEntry} is older than a given time.
112             *
113             * @param clockSkew
114             * @return true if the {@link ReplayCacheEntry}'s client time is outside the clock skew time.
115             */
116            public boolean isOutsideClockSkew( long clockSkew )
117            {
118                return !clientTime.isInClockSkew( clockSkew );
119            }
120        }
121    
122        
123        /**
124         * Creates a new instance of InMemoryReplayCache. Sets the
125         * delay between each cleaning run to 5 seconds.
126         */
127        public InMemoryReplayCache()
128        {
129            cache = new HashMap<KerberosPrincipal, List<ReplayCacheEntry>>();
130            delay = DEFAULT_DELAY;
131            this.start();
132        }
133        
134        
135        /**
136         * Creates a new instance of InMemoryReplayCache. Sets the
137         * delay between each cleaning run to 5 seconds. Sets the
138         * clockSkew to the given value
139         * 
140         * @param clockSkew the allowed skew (milliseconds)
141         */
142        public InMemoryReplayCache( long clockSkew )
143        {
144            cache = new HashMap<KerberosPrincipal, List<ReplayCacheEntry>>();
145            delay = DEFAULT_DELAY;
146            this.clockSkew = clockSkew;
147            this.start();
148        }
149        
150        
151        /**
152         * Creates a new instance of InMemoryReplayCache. Sets the
153         * clockSkew to the given value, and set the cleaning thread 
154         * kick off delay
155         * 
156         * @param clockSkew the allowed skew (milliseconds)
157         * @param delay the interval between each run of the cache 
158         * cleaning thread (milliseconds)
159         */
160        public InMemoryReplayCache( long clockSkew, int delay  )
161        {
162            cache = new HashMap<KerberosPrincipal, List<ReplayCacheEntry>>();
163            this.delay = (long)delay;
164            this.clockSkew = clockSkew;
165            this.start();
166        }
167        
168        
169        /**
170         * Creates a new instance of InMemoryReplayCache. Sets the
171         * delay between each cleaning run to 5 seconds. Sets the 
172         * cleaning thread kick off delay
173         * 
174         * @param delay the interval between each run of the cache 
175         * cleaning thread (milliseconds).
176         */
177        public InMemoryReplayCache( int delay )
178        {
179            cache = new HashMap<KerberosPrincipal, List<ReplayCacheEntry>>();
180            this.delay = (long)delay;
181            this.clockSkew = DEFAULT_CLOCK_SKEW;
182        }
183        
184        
185        /**
186         * Sets the clock skew.
187         *
188         * @param clockSkew
189         */
190        public void setClockSkew( long clockSkew )
191        {
192            this.clockSkew = clockSkew;
193        }
194    
195        
196        /**
197         * Set the delay between each cleaning thread run.
198         *
199         * @param delay delay in milliseconds
200         */
201        public void setDelay( long delay )
202        {
203            this.delay = delay;
204        }
205    
206        /**
207         * Check if an entry is a replay or not.
208         */
209        public synchronized boolean isReplay( KerberosPrincipal serverPrincipal, KerberosPrincipal clientPrincipal,
210            KerberosTime clientTime, int clientMicroSeconds )
211        {
212            List<ReplayCacheEntry> entries = cache.get( clientPrincipal );
213            
214            if ( ( entries == null ) || ( entries.size() == 0 ) )
215            {
216                return false;
217            }
218            
219            for ( ReplayCacheEntry entry:entries )
220            {
221                if ( serverPrincipal.equals( entry.serverPrincipal ) && 
222                     clientTime.equals( entry.clientTime ) && 
223                     (clientMicroSeconds == entry.clientMicroSeconds ) )
224                {
225                    return true;
226                }
227            }
228    
229            return false;
230        }
231    
232    
233        /**
234         * Add a new entry into the cache. A thread will clean all the timed out
235         * entries.
236         */
237        public synchronized void save( KerberosPrincipal serverPrincipal, KerberosPrincipal clientPrincipal,
238            KerberosTime clientTime, int clientMicroSeconds )
239        {
240            List<ReplayCacheEntry> entries = cache.get( clientPrincipal );
241            
242            if ( entries == null )
243            {
244                entries = new ArrayList<ReplayCacheEntry>();
245            }
246            
247            entries.add( new ReplayCacheEntry( serverPrincipal, clientPrincipal, clientTime, clientMicroSeconds ) );
248            
249            cache.put( clientPrincipal, entries );
250        }
251    
252        
253        public Map<KerberosPrincipal, List<ReplayCacheEntry>> getCache()
254        {
255            return cache;
256        }
257        
258        /**
259         * A method to remove all the expired entries from the cache.
260         */
261        private synchronized void cleanCache()
262        {
263            Collection<List<ReplayCacheEntry>> entryList = cache.values();
264            
265            if ( ( entryList == null ) || ( entryList.size() == 0 ) )
266            {
267                return;
268            }
269            
270            for ( List<ReplayCacheEntry> entries:entryList )
271            {
272                if ( ( entries == null ) || ( entries.size() == 0 ) )
273                {
274                    continue;
275                }
276                
277                Iterator<ReplayCacheEntry> iterator = entries.iterator();
278                
279                while ( iterator.hasNext() )
280                {
281                    ReplayCacheEntry entry = iterator.next();
282                    
283                    if ( entry.isOutsideClockSkew( clockSkew ) )
284                    {
285                        iterator.remove();
286                    }
287                    else
288                    {
289                        break;
290                    }
291                }
292            }
293        }
294        
295        
296        /** 
297         * The cleaning thread. It runs every N seconds.
298         */
299        public void run()
300        {
301            while ( true )
302            {
303                try
304                {
305                    Thread.sleep( delay );
306                    
307                    cleanCache();
308                }
309                catch ( InterruptedException ie )
310                {
311                    return;
312                }
313            }
314        }
315    }