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.kerberos.shared.replay;
21  
22  
23  import java.util.ArrayList;
24  import java.util.Collection;
25  import java.util.HashMap;
26  import java.util.Iterator;
27  import java.util.List;
28  import java.util.Map;
29  
30  import javax.security.auth.kerberos.KerberosPrincipal;
31  
32  import org.apache.directory.server.kerberos.shared.messages.value.KerberosTime;
33  
34  
35  /**
36   * "The replay cache will store at least the server name, along with the client name,
37   * time, and microsecond fields from the recently-seen authenticators, and if a
38   * matching tuple is found, the KRB_AP_ERR_REPEAT error is returned."
39   * 
40   * We will store the entries using an HashMap which key will be the client
41   * principal, and we will store a list of entries for each client principal.
42   * 
43   * A thread will run every N seconds to clean the cache from entries out of the 
44   * clockSkew
45   *    
46   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
47   * @version $Rev: 589775 $, $Date: 2007-10-29 19:04:59 +0100 (Mo, 29 Okt 2007) $
48   */
49  public class InMemoryReplayCache extends Thread implements ReplayCache
50  {
51      /** Stores the entries in memory */
52      private Map<KerberosPrincipal, List<ReplayCacheEntry>> cache = new HashMap<KerberosPrincipal, List<ReplayCacheEntry>>();
53  
54      /** default clock skew */
55      private static final long DEFAULT_CLOCK_SKEW = 5 * KerberosTime.MINUTE;
56      
57      /** The clock skew */
58      private long clockSkew = DEFAULT_CLOCK_SKEW;
59  
60      /** The default delay between each run of the cleaning process : 5 s */
61      private static long DEFAULT_DELAY = 5 * 1000;  
62      
63      /** The delay to wait between each cache cleaning */
64      private long delay;
65  
66      /**
67       * A structure to hold an entry
68       */
69      public class ReplayCacheEntry
70      {
71          private KerberosPrincipal serverPrincipal;
72          private KerberosPrincipal clientPrincipal;
73          private KerberosTime clientTime;
74          private int clientMicroSeconds;
75  
76  
77          /**
78           * Creates a new instance of ReplayCacheEntry.
79           * 
80           * @param serverPrincipal 
81           * @param clientPrincipal 
82           * @param clientTime 
83           * @param clientMicroSeconds 
84           */
85          public ReplayCacheEntry( KerberosPrincipal serverPrincipal, KerberosPrincipal clientPrincipal,
86              KerberosTime clientTime, int clientMicroSeconds )
87          {
88              this.serverPrincipal = serverPrincipal;
89              this.clientPrincipal = clientPrincipal;
90              this.clientTime = clientTime;
91              this.clientMicroSeconds = clientMicroSeconds;
92          }
93  
94  
95          /**
96           * Returns whether this {@link ReplayCacheEntry} is equal to another {@link ReplayCacheEntry}.
97           * {@link ReplayCacheEntry}'s are equal when the server name, client name, client time, and
98           * the client microseconds are equal.
99           *
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 }