qa-app-limits.php 6.69 KB
Newer Older
Gideon Greenspan committed
1
<?php
Scott Vivian committed
2

Gideon Greenspan committed
3 4 5 6 7
/*
	Question2Answer (c) Gideon Greenspan

	http://www.question2answer.org/

Scott Vivian committed
8

Gideon Greenspan committed
9 10 11 12 13 14 15 16 17
	File: qa-include/qa-app-limits.php
	Version: See define()s at top of qa-include/qa-base.php
	Description: Monitoring and rate-limiting user actions (application level)


	This program is free software; you can redistribute it and/or
	modify it under the terms of the GNU General Public License
	as published by the Free Software Foundation; either version 2
	of the License, or (at your option) any later version.
Scott Vivian committed
18

Gideon Greenspan committed
19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
	This program 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 General Public License for more details.

	More about this license: http://www.question2answer.org/license.php
*/

	if (!defined('QA_VERSION')) { // don't allow this page to be requested directly from browser
		header('Location: ../');
		exit;
	}


	define('QA_LIMIT_QUESTIONS', 'Q');
	define('QA_LIMIT_ANSWERS', 'A');
	define('QA_LIMIT_COMMENTS', 'C');
	define('QA_LIMIT_VOTES', 'V');
	define('QA_LIMIT_REGISTRATIONS', 'R');
	define('QA_LIMIT_LOGINS', 'L');
	define('QA_LIMIT_UPLOADS', 'U');
	define('QA_LIMIT_FLAGS', 'F');
Gideon Greenspan committed
41 42
	define('QA_LIMIT_MESSAGES', 'M'); // i.e. private messages
	define('QA_LIMIT_WALL_POSTS', 'W');
Gideon Greenspan committed
43

Scott Vivian committed
44

Gideon Greenspan committed
45
	function qa_user_limits_remaining($action)
Gideon Greenspan committed
46 47 48 49
/*
	Return how many more times the logged in user (and requesting IP address) can perform $action this hour,
	where $action is one of the QA_LIMIT_* constants defined above.
*/
Gideon Greenspan committed
50 51 52
	{
		$userlimits=qa_db_get_pending_result('userlimits', qa_db_user_limits_selectspec(qa_get_logged_in_userid()));
		$iplimits=qa_db_get_pending_result('iplimits', qa_db_ip_limits_selectspec(qa_remote_ip_address()));
Scott Vivian committed
53

Gideon Greenspan committed
54 55
		return qa_limits_calc_remaining($action, @$userlimits[$action], @$iplimits[$action]);
	}
Scott Vivian committed
56 57


58 59 60 61 62
	/**
	 * Return how many more times user $userid and/or the requesting IP can perform $action (a QA_LIMIT_* constant) this hour.
	 *
	 * @deprecated Deprecated from 1.6.0; use `qa_user_limits_remaining($action)` instead.
	 */
Gideon Greenspan committed
63 64
	function qa_limits_remaining($userid, $action)
	{
Gideon Greenspan committed
65
		if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
Scott Vivian committed
66

Gideon Greenspan committed
67 68 69
		require_once QA_INCLUDE_DIR.'qa-db-limits.php';

		$dblimits=qa_db_limits_get($userid, qa_remote_ip_address(), $action);
Scott Vivian committed
70

Gideon Greenspan committed
71
		return qa_limits_calc_remaining($action, @$dblimits['user'], @$dblimits['ip']);
Gideon Greenspan committed
72
	}
Scott Vivian committed
73 74


Gideon Greenspan committed
75
	function qa_limits_calc_remaining($action, $userlimits, $iplimits)
Gideon Greenspan committed
76 77 78
/*
	Calculate how many more times $action can be performed this hour, based on $userlimits and $iplimits retrieved from the database
*/
Gideon Greenspan committed
79
	{
Gideon Greenspan committed
80 81
		switch ($action) {
			case QA_LIMIT_QUESTIONS:
Gideon Greenspan committed
82 83
				$usermax=qa_opt('max_rate_user_qs');
				$ipmax=qa_opt('max_rate_ip_qs');
Gideon Greenspan committed
84
				break;
Scott Vivian committed
85

Gideon Greenspan committed
86
			case QA_LIMIT_ANSWERS:
Gideon Greenspan committed
87 88
				$usermax=qa_opt('max_rate_user_as');
				$ipmax=qa_opt('max_rate_ip_as');
Gideon Greenspan committed
89
				break;
Scott Vivian committed
90

Gideon Greenspan committed
91
			case QA_LIMIT_COMMENTS:
Gideon Greenspan committed
92 93
				$usermax=qa_opt('max_rate_user_cs');
				$ipmax=qa_opt('max_rate_ip_cs');
Gideon Greenspan committed
94 95 96
				break;

			case QA_LIMIT_VOTES:
Gideon Greenspan committed
97 98
				$usermax=qa_opt('max_rate_user_votes');
				$ipmax=qa_opt('max_rate_ip_votes');
Gideon Greenspan committed
99
				break;
Scott Vivian committed
100

Gideon Greenspan committed
101
			case QA_LIMIT_REGISTRATIONS:
Gideon Greenspan committed
102 103
				$usermax=1; // not really relevant
				$ipmax=qa_opt('max_rate_ip_registers');
Gideon Greenspan committed
104 105 106
				break;

			case QA_LIMIT_LOGINS:
Gideon Greenspan committed
107 108
				$usermax=1; // not really relevant
				$ipmax=qa_opt('max_rate_ip_logins');
Gideon Greenspan committed
109
				break;
Scott Vivian committed
110

Gideon Greenspan committed
111
			case QA_LIMIT_UPLOADS:
Gideon Greenspan committed
112 113
				$usermax=qa_opt('max_rate_user_uploads');
				$ipmax=qa_opt('max_rate_ip_uploads');
Gideon Greenspan committed
114
				break;
Scott Vivian committed
115

Gideon Greenspan committed
116
			case QA_LIMIT_FLAGS:
Gideon Greenspan committed
117 118
				$usermax=qa_opt('max_rate_user_flags');
				$ipmax=qa_opt('max_rate_ip_flags');
Gideon Greenspan committed
119
				break;
Scott Vivian committed
120

Gideon Greenspan committed
121
			case QA_LIMIT_MESSAGES:
Gideon Greenspan committed
122
			case QA_LIMIT_WALL_POSTS:
Gideon Greenspan committed
123 124
				$usermax=qa_opt('max_rate_user_messages');
				$ipmax=qa_opt('max_rate_ip_messages');
Gideon Greenspan committed
125
				break;
Scott Vivian committed
126

Gideon Greenspan committed
127
			default:
Gideon Greenspan committed
128
				qa_fatal_error('Unknown limit code in qa_limits_calc_remaining: '.$action);
Gideon Greenspan committed
129 130
				break;
		}
Scott Vivian committed
131

Gideon Greenspan committed
132 133
		$period=(int)(qa_opt('db_time')/3600);

Gideon Greenspan committed
134
		return max(0, min(
Gideon Greenspan committed
135 136
			$usermax-((@$userlimits['period']==$period) ? $userlimits['count'] : 0),
			$ipmax-((@$iplimits['period']==$period) ? $iplimits['count'] : 0)
Gideon Greenspan committed
137 138
		));
	}
Scott Vivian committed
139 140


Gideon Greenspan committed
141 142 143 144 145
	function qa_is_ip_blocked()
/*
	Return whether the requesting IP address has been blocked from write operations
*/
	{
Gideon Greenspan committed
146
		if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
Scott Vivian committed
147

Gideon Greenspan committed
148
		$blockipclauses=qa_block_ips_explode(qa_opt('block_ips_write'));
Scott Vivian committed
149

Gideon Greenspan committed
150 151 152
		foreach ($blockipclauses as $blockipclause)
			if (qa_block_ip_match(qa_remote_ip_address(), $blockipclause))
				return true;
Scott Vivian committed
153

Gideon Greenspan committed
154 155 156
		return false;
	}

Scott Vivian committed
157

Gideon Greenspan committed
158 159 160 161 162 163
	function qa_block_ips_explode($blockipstring)
/*
	Return an array of the clauses within $blockipstring, each of which can contain hyphens or asterisks
*/
	{
		$blockipstring=preg_replace('/\s*\-\s*/', '-', $blockipstring); // special case for 'x.x.x.x - x.x.x.x'
Scott Vivian committed
164

Gideon Greenspan committed
165 166 167
		return preg_split('/[^0-9\.\-\*]/', $blockipstring, -1, PREG_SPLIT_NO_EMPTY);
	}

Scott Vivian committed
168

Gideon Greenspan committed
169 170 171 172 173 174 175 176 177 178 179
	function qa_block_ip_match($ip, $blockipclause)
/*
	Returns whether the ip address $ip is matched by the clause $blockipclause, which can contain a hyphen or asterisk
*/
	{
		if (long2ip(ip2long($ip))==$ip) {
			if (preg_match('/^(.*)\-(.*)$/', $blockipclause, $matches)) {
				if ( (long2ip(ip2long($matches[1]))==$matches[1]) && (long2ip(ip2long($matches[2]))==$matches[2]) ) {
					$iplong=sprintf('%u', ip2long($ip));
					$end1long=sprintf('%u', ip2long($matches[1]));
					$end2long=sprintf('%u', ip2long($matches[2]));
Scott Vivian committed
180

Gideon Greenspan committed
181 182
					return (($iplong>=$end1long) && ($iplong<=$end2long)) || (($iplong>=$end2long) && ($iplong<=$end1long));
				}
Scott Vivian committed
183

Gideon Greenspan committed
184 185 186 187
			} elseif (strlen($blockipclause))
				return preg_match('/^'.str_replace('\\*', '[0-9]+', preg_quote($blockipclause, '/')).'$/', $ip) > 0;
					// preg_quote misses hyphens but that is OK here
		}
Scott Vivian committed
188

Gideon Greenspan committed
189 190
		return false;
	}
Scott Vivian committed
191 192


Gideon Greenspan committed
193 194 195 196 197 198
	function qa_report_write_action($userid, $cookieid, $action, $questionid, $answerid, $commentid)
/*
	Called after a database write $action performed by a user identified by $userid and/or $cookieid.
*/
	{}

Scott Vivian committed
199

Gideon Greenspan committed
200 201 202 203 204 205
	function qa_limits_increment($userid, $action)
/*
	Take note for rate limits that user $userid and/or the requesting IP just performed $action,
	where $action is one of the QA_LIMIT_* constants defined above.
*/
	{
Gideon Greenspan committed
206
		if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
Scott Vivian committed
207

Gideon Greenspan committed
208 209 210
		require_once QA_INCLUDE_DIR.'qa-db-limits.php';

		$period=(int)(qa_opt('db_time')/3600);
Scott Vivian committed
211

Gideon Greenspan committed
212 213
		if (isset($userid))
			qa_db_limits_user_add($userid, $action, $period, 1);
Scott Vivian committed
214

Gideon Greenspan committed
215 216 217 218 219 220 221
		qa_db_limits_ip_add(qa_remote_ip_address(), $action, $period, 1);
	}


/*
	Omit PHP closing tag to help avoid accidental output
*/