<?php

	/*
	*	Gekko - Open Source Web Development Framework
	*	------------------------------------------------------------------------
	*	Copyright (C) 2004-2005, J. Carlos Nieto <xiam@users.sourceforge.net>
	*	This program is Free Software.
	*
	*	@package	Gekko
	*	@subpackage	/lib
	*	@license	http://www.gnu.org/copyleft/gpl.html GNU/GPL License 2.0
	*	@author		J. Carlos Nieto <xiam@users.sf.net>
	*	@version	CVS: $Id:$
	*	@link		http://www.gekkoware.org
	*/

	if (!defined("IN-GEKKO")) die("Get a life!");

	/*
		Stores and retrieves from the database user information about
		action times.
		This is very useful for regulating intervals between actions,
		this could help on preventing from a message flood.
	*/

	Class accessControl {

		var $rules = Array();

		/*
			initializing class
		*/
		function accessControl() {

			// I think it's safe to delete entries older than 36 hours
			$GLOBALS["db"]->query("DELETE FROM ".db_table("control")." WHERE Time < ".(time()-129600)."", __FILE__, __LINE__);
		}
		// drops $n entries
		function dropEntry($type, $n = 1) {
			// very util for redirections
			$GLOBALS["db"]->query("DELETE FROM ".db_table("control")." WHERE IP = '".IP_ADDR."' AND AccessType = '$type' ORDER BY Time DESC LIMIT $n", __FILE__, __LINE__, true);
		}
		/*
			check(String id, Int time interval, Int requests limit, Int persistent entry)

			Manages an entry for each ID to identify requests based on time.
			This could be used as an antiflood control.

				$this->check("requestControl", 3, 5, 1);

				The above example will return false if more than 5 requests
				has been made within 3 seconds. And also it's persistent, so, if
				client continues doing requests, this will continue returning
				false until client waits 3 seconds doing nothing.
		*/

		function check($id, $time_interval, $req_limit = 1, $persistent = false, $readonly = false) {
			// deleting expired entries
			$GLOBALS["db"]->query("DELETE FROM ".db_table("control")." WHERE IP = '".IP_ADDR."' AND AccessType = '$id' AND Time <= '".(time()-$time_interval)."'", __FILE__, __LINE__, true);

			// checking for a valid entry
			$q = $GLOBALS["db"]->query("SELECT Counter FROM ".db_table("control")." WHERE IP = '".IP_ADDR."' AND AccessType = '$id'", __FILE__, __LINE__, true);

			if ($GLOBALS["db"]->num_rows($q)) {
				// ** and entry has been found **

				// checking if it has reached the given limit
				$row = $GLOBALS["db"]->fetch_row($q);

				// increasing counter.
				if (!$readonly) $GLOBALS["db"]->query("UPDATE ".db_table("control")." SET Counter = Counter + 1 ".(($persistent) ? ", Time = '".time()."'": "")." WHERE IP = '".IP_ADDR."' AND AccessType = '$id'", __FILE__, __LINE__, true);

				return (($req_limit > 0) && ($row["Counter"]+1 > $req_limit)) ? true : false;

			} elseif (!$readonly) {
				// if there is no entry for this id, we'll create one
				$GLOBALS["db"]->query("INSERT ".db_table("control")." (IP, AccessType, Counter, Time) VALUES('".IP_ADDR."', '".$id."', '1', '".time()."')", __FILE__, __LINE__, true);
				return false;
			}
			return false;
		}
		/*
			set_rule((str) rule id, (int) time interval, (int) requests per time, (bool) is persistent?);
			Adds a rule to our access table.
		*/
		function set_rule($rule, $time, $requests, $persistent = false) {
			$rule .= ".rule";
			$this->rules[$rule] = Array (
				"time" => $time,
				"requests" => $requests,
				"persistent" => $persistent
			);
		}

		/*
			load_rule((str) rule id, (str) abort message [leave as false if you don't want script to abort], (bool) read only)
			Checks a rule and returns false when is *not* matched or reached
		*/
		function load_rule($rule, $abort = false, $readonly = false) {
			$rule .= ".rule";
			if (isset($this->rules[$rule])) {
				$check = $this->check($rule, $this->rules[$rule]["time"], $this->rules[$rule]["requests"], $this->rules[$rule]["persistent"], $readonly);
				return ($abort && $check) ? abort(_l($abort)) : $check;
			} else {
				trigger_error("Unknown rule '$rule'", E_USER_ERROR);
			}
		}

		/*
			set_generic_rule((str) rule prefix, (str) type of rule);
		*/
		function set_generic_rule($prefix, $type) {
			switch ($type) {
				case "post":
					// generic "http post" rule
					$t = Conf::getKey("core", "http_post.interval", 20);
					$r = Conf::getKey("core", "http_post.requests", 3);
					$p = true;
				break;
				case "auth":
					// generic "auth protection" rule
					$t = Conf::getKey("core", "http_auth.interval", 600);
					$r = Conf::getKey("core", "http_auth.requests", 10);
					$p = true;
				break;
				default:
					trigger_error("Unknown generic rule type '$type'", E_USER_ERROR);
				break;
			}

			/*
			This rule allows $r requests within $t seconds
			When $p is true, it means the time interval must be reached and
			this time is updated with every extra invalid request
			*/

			$this->set_rule($rule = $prefix."-".$type, $t, $r, $p);
			return $rule;
		}
		/*
			TODO: replace gateKeeper() with rule functions

			Access:gateKeeper(module, request type, abort message);
			This aims to be an antiflood system database.
		*/
		function gateKeeper($mod, $type, $abort = false, $readonly = false) {

			$idstr = "$mod-$type-gk";

			// admin knows what is he/she doing...
			//if (gauth("1")) return false;

			switch ($type) {
				// action "post" (module-post-gk)
				case "post":
					// allows 3 requests within 20 seconds
					$time = Conf::getKey("core", "http_post.interval", 20);
					$reqs = Conf::getKey("core", "http_post.requests", 3);
					// block user until he/she leaves to request
					$pers = true;
				break;
				case "login":
					// allows 8 requests within 120 seconds
					$time = Conf::getKey("users", "login_try.interval", 120);
					$reqs = Conf::getKey("users", "login_try.requests", 8);
					// block user until he/she leaves to request
					$pers = true;
				break;
				case "register":
					// allows 5 registrations per day
					$time = Conf::getKey("users", "account.register.interval", 60*60*24);
					$reqs = Conf::getKey("users", "account.register.limit", 5);
					//
					$pers = false;
				break;
				default:
					$type = explode(":", $type);
					switch ($type[0]) {
						case "custom":
							list ($time, $reqs, $pers) = $type;
						break;
						default:
							trigger_error("Undefined action type: '$type'.", E_USER_ERROR);
						break;
					}
				break;
			}

			$check = $this->check($idstr, $time, $reqs, $pers, $readonly);

			return ($abort && $check) ? abort(_l($abort)) : $check;
		}
	}

?>
