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 }