Location: PHPKode > scripts > Authen_DAP > DAP.php
<?php
// +----------------------------------------------------------------------+
// | Dictionary Attack Protection class for PHP5.                         |
// | Copyright (C) 2005 Craig Manley                                      |
// +----------------------------------------------------------------------+
// | This library is free software; you can redistribute it and/or modify |
// | it under the terms of the GNU Lesser General Public License as       |
// | published by the Free Software Foundation; either version 2.1 of the |
// | License, or (at your option) any later version.                      |
// |                                                                      |
// | This library is distributed in the hope that it will be useful, but  |
// | WITHOUT ANY WARRANTY; without even the implied warranty of           |
// | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU     |
// | Lesser General Public License for more details.                      |
// |                                                                      |
// | You should have received a copy of the GNU Lesser General Public     |
// | License along with this library; if not, write to the Free Software  |
// | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  |
// | USA                                                                  |
// |                                                                      |
// | LGPL license URL: http://opensource.org/licenses/lgpl-license.php    |
// +----------------------------------------------------------------------+
// | Author: Craig Manley                                                 |
// +----------------------------------------------------------------------+
//
// $Id: DAP.php,v 1.1 2005/01/09 22:44:19 cmanley Exp $
//



/**
 * @author    Craig Manley
 * @copyright Copyright © 2004, Craig Manley. All rights reserved.
 * @package   com.craigmanley.classes.authen.DAP
 * @version   $Revision: 1.1 $
 */




/**
 * Offers protection against dictionary attacks.
 *
 * @package  com.craigmanley.classes.authen.DAP
 */
class Authen_DAP {

  // Private members
  private $shm          = null; // Object implementing IPC_ISharedMem interface.
  private $max_attempts = null;
  private $period       = null;


  /**
   * Constructor.
   *
   * @param object  $shm shared memory object that implements the IPC_ISharedMem interface.
   * @param integer $max_attempts maximum failed (login) attempts after which to block the identity, default 3.
   * @param integer $period time in seconds to block an identity for after too many failed access attempts, default 180.
   */
  public function __construct($shm, $max_attempts = 3, $period = 180) {
    if (!(isset($shm) && ($shm instanceof IPC_ISharedMem))) {
      throw Exception('You must pass a shared memory management object that implements the IPC_ISharedMem interface as the 1st parameter!');
    }
    $this->shm = $shm;
    $this->max_attempts($max_attempts);
    $this->period($period);
  }


  /**
   * Checks if the parameter is a positive integer (representation).
   *
   * @param mixed $value
   * @return boolean
   */
  protected function _is_pos_int($value) {
    return (is_int($value) || (is_string($value) && preg_match('/^\+?\d+$/', $value))) && ($value > 0);
  }


  /**
   * Gets or sets the maximum number of failed access attempts after which an identity
   * will be temporarily blocked. The default value is 3.
   *
   * @param value $name Optional new value. Must be an integer >= 1.
   * @return integer
   */
  public function max_attempts() {
    if (func_num_args()) {
      $value = func_get_arg(0);
      if (!(isset($value) && $this->_is_pos_int($value))) {
        throw Exception("Method parameter is not a positive integer!");
      }
      $this->max_attempts = intval($value);;
    }
    return $this->max_attempts;
  }


  /**
   * Gets or sets the period (in seconds) that identities are blocked for after
   * too many failed access attempts. The default value is 180.
   *
   * @param value $name Optional new value. Must be an integer >= 1.
   * @return integer
   */
  public function period() {
    if (func_num_args()) {
      $value = func_get_arg(0);
      if (!(isset($value) && $this->_is_pos_int($value))) {
        throw Exception("Parameter is not a positive integer!");
      }
      $this->period = intval($value);;
    }
    return $this->period;
  }


  /**
   * Checks to see if the identity's access has been blocked using the given record structure.
   * Returns the seconds the identity is (still) blocked for (0 means not blocked).
   *
   * @param scalar $identity Anything used to identify someone such as an IP, alias, etc.
   * @param array $records array of [$identity, $last_attempt] arrays.
   * @return integer
   */
  private function _blocked($identity, &$records) {
    $attempts = 0;
    $max_attempts = $this->max_attempts();
    $last_attempt = null;
    // Search for the last $max_attempts records if any.
    for ($i = count($records) - 1; $i >= 0; $i--) {
      if ($records[$i][0] == $identity) {
        $attempts++;
        if (is_null($last_attempt)) {
          $last_attempt = $records[$i][1];
        }
        if ($attempts >= $max_attempts) {
          break;
        }
      }
    }
    $block_for = null;
    if ($attempts >= $max_attempts) {
      $block_for = $last_attempt + $this->period() - time();
      if ($block_for < 0) { // could happen in theory if code executes slowly.
        $block_for = 0;
      }
    }
    else {
      $block_for = 0;
    }
    return $block_for;
  }


  /**
   * Deletes expired records from the record structure passed as parameter.
   * Returns the seconds the identity is (still) blocked for (0 means not blocked).
   * Returns the amount of records deleted.
   *
   * @param array $records array of [identity, time] arrays.
   * @return integer
   */
  private function _delete_expired_records(&$records) {
    $result = 0;
    $expire_before = time() - $this->period();
    while (count($records)) {
      if ($records[0][1] <= $expire_before) {
        $result++;
        array_shift($records);
      }
      else {
        break;
      }
    }
    return $result;
  }


  /**
   * Loads the recorded failed access events structure from shared memory,
   * cleans out all expired records if any, saves the structure back into
   * shared memory (if changed), and returns the new record structure.
   *
   * @return array
   */
  private function &_records() {
    $records = null;
    $shm = $this->shm;
    $shm->transaction_start();
    try {
      $s = $shm->fetch();
      if (isset($s) && strlen($s)) {
      	$records = unserialize($s);
        if ($this->_delete_expired_records($records)) {
          $s = serialize($records);
          $shm->store($s);
        }
      }
      $shm->transaction_finish();
    }
    catch(Exception $e) {
      $shm->transaction_finish();
      throw $e;
    }
    if (!isset($records)) {
      $records = array();
    }
    return $records;
  }


  /**
   * Records a failed access attempt and returns the number of seconds the identity
   * has been blocked for (0 meaning not blocked).
   *
   * @param string $identity Anything used to identify somebody such as an IP address, login alias, session key, etc.
   * @return integer
   */
  public function record_failed_attempt($identity) {
    $records = null;
    $shm = $this->shm;
    $shm->transaction_start();
    try {
      $s = $shm->fetch();
      if (isset($s) && strlen($s)) {
      	$records = unserialize($s);
        $this->_delete_expired_records($records);
      }
      else {
        $records = array();
      }
      array_push($records, array($identity, time()));
      $s = serialize($records);
      $shm->store($s);
      $shm->transaction_finish();
    }
    catch(Exception $e) {
      $shm->transaction_finish();
      throw $e;
    }
    return $this->_blocked($identity,$records);
  }


  /**
   * Dumps the records currently stored in shared memory to stdout. Only useful for debugging.
   */
  public function dump_records() {
    $records = $this->_records();
    print_r($records);
  }


  /**
   * Clears all interal records of failed access attempts for the given identity.
   *
   * @param string $identity Anything used to identify somebody such as an IP address, login alias, session key, etc.
  */
  public function clear($identity) {
    $shm = $this->shm;
    $shm->transaction_start();
    try {
      $s = $shm->fetch();
      if (isset($s) && strlen($s)) {
        $records = unserialize($s);
        $changed = false;
        if ($this->_delete_expired_records($result)) {
          $changed = true;
        }
        $i = 0;
        while (count($records) && ($i < count($records))) {
          if ($records[$i][0] == $identity) {
            array_splice($records,$i,1);
            $changed = true;
          }
          else {
            $i++;
          }
        }
        if ($changed) {
          $s = serialize($result);
          $shm->store($s);
        }
      }
      $shm->transaction_finish();
    }
    catch(Exception $e) {
      $shm->transaction_finish();
      throw $e;
    }
  }


  /**
   * Checks to see if the indentity's access has been blocked.
   * Returns the number of seconds the identity has been blocked for (0 means not blocked).
   *
   * @param string $identity Anything used to identify somebody such as an IP address, login alias, session key, etc.
   * @return integer
   */
  public function blocked($identity) {
    return $this->_blocked($identity, $this->_records());
  }

}


?>
Return current item: Authen_DAP