001: /*
002: * Copyright 2007 The Kuali Foundation.
003: *
004: * Licensed under the Educational Community License, Version 1.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.opensource.org/licenses/ecl1.php
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016: package edu.yale.its.tp.cas.auth.provider;
017:
018: import java.util.ArrayList;
019: import java.util.HashMap;
020: import java.util.Iterator;
021: import java.util.List;
022: import java.util.Map;
023: import java.util.Set;
024:
025: import edu.yale.its.tp.cas.auth.PasswordHandler;
026:
027: /**
028: * A PasswordHandler base class that implements logic to block IP addresses that engage in too many unsuccessful login attempts. The
029: * goal is to limit the damage that a dictionary-based password attack can achieve. We implement this with a token-based strategy;
030: * failures are regularly forgotten, and only build up when they occur faster than expiry.
031: */
032: public abstract class WatchfulPasswordHandler implements
033: PasswordHandler {
034:
035: // *********************************************************************
036: // KFSConstants
037:
038: /**
039: * The number of failed login attempts to allow before locking out the source IP address. (Note that failed login attempts
040: * "expire" regularly.)
041: */
042: private static final int FAILURE_THRESHOLD = 100;
043:
044: /**
045: * The interval to wait before expiring recorded failure attempts.
046: */
047: private static final int FAILURE_TIMEOUT = 60;
048:
049: // *********************************************************************
050: // Private state
051:
052: /** Map of offenders to the number of their offenses. */
053: private static Map offenders = new HashMap();
054:
055: /** Thread to manage offenders. */
056: private static Thread offenderThread = new Thread() {
057: public void run() {
058: try {
059: while (true) {
060: Thread.sleep(FAILURE_TIMEOUT * 1000);
061: expireFailures();
062: }
063: } catch (InterruptedException ex) {
064: // ignore
065: }
066: }
067: };
068:
069: static {
070: offenderThread.setDaemon(true);
071: offenderThread.start();
072: }
073:
074: // *********************************************************************
075: // Gating logic
076:
077: /**
078: * Returns true if the given request comes from an IP address whose allotment of failed login attemps is within reasonable
079: * bounds; false otherwise. Note: We don't actually validate the user and password; this functionality must be implemented by
080: * subclasses.
081: */
082: public synchronized boolean authenticate(
083: javax.servlet.ServletRequest request, String netid,
084: String password) {
085: return (getFailures(request.getRemoteAddr()) < FAILURE_THRESHOLD);
086: }
087:
088: /** Registers a login failure initiated by the given address. */
089: protected synchronized void registerFailure(
090: javax.servlet.ServletRequest r) {
091: String address = r.getRemoteAddr();
092: offenders.put(address, new Integer(getFailures(address) + 1));
093: }
094:
095: /** Returns the number of "active" failures for the given address. */
096: private synchronized static int getFailures(String address) {
097: Object o = offenders.get(address);
098: if (o == null)
099: return 0;
100: else
101: return ((Integer) o).intValue();
102: }
103:
104: /**
105: * Removes one failure record from each offender; if any offender's resulting total is zero, remove it from the list.
106: */
107: private synchronized static void expireFailures() {
108: // scoop up addresses from Map so as to avoid modifying the Map in-place
109: ;
110: Set keys = offenders.keySet();
111: Iterator ki = keys.iterator();
112: List l = new ArrayList();
113: while (ki.hasNext())
114: l.add(ki.next());
115:
116: // now, decrement and prune as appropriate
117: for (int i = 0; i < l.size(); i++) {
118: String address = (String) l.get(i);
119: int failures = getFailures(address) - 1;
120: if (failures > 0)
121: offenders.put(address, new Integer(failures));
122: else
123: offenders.remove(address);
124: }
125: }
126:
127: }
|