<?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());
}
}
?>
|