1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
/** |
4
|
|
|
* This file has the very important job of ensuring forum security. |
5
|
|
|
* This task includes banning and permissions, namely. |
6
|
|
|
* |
7
|
|
|
* @name ElkArte Forum |
8
|
|
|
* @copyright ElkArte Forum contributors |
9
|
|
|
* @license BSD http://opensource.org/licenses/BSD-3-Clause |
10
|
|
|
* |
11
|
|
|
* This file contains code covered by: |
12
|
|
|
* copyright: 2011 Simple Machines (http://www.simplemachines.org) |
13
|
|
|
* license: BSD, See included LICENSE.TXT for terms and conditions. |
14
|
|
|
* |
15
|
|
|
* @version 1.1 Release Candidate 1 |
16
|
|
|
* |
17
|
|
|
*/ |
18
|
|
|
|
19
|
|
|
/** |
20
|
|
|
* Check if the user is who he/she says he is. |
21
|
|
|
* |
22
|
|
|
* What it does: |
23
|
|
|
* |
24
|
|
|
* - This function makes sure the user is who they claim to be by requiring a |
25
|
|
|
* password to be typed in every hour. |
26
|
|
|
* - This check can be turned on and off by the securityDisable setting. |
27
|
|
|
* - Uses the adminLogin() function of subs/Auth.subs.php if they need to login, |
28
|
|
|
* which saves all request (POST and GET) data. |
29
|
|
|
* |
30
|
|
|
* @event integrate_validateSession Called at start of validateSession |
31
|
|
|
* @param string $type = admin |
32
|
|
|
* @throws Elk_Exception |
33
|
|
|
*/ |
34
|
|
|
function validateSession($type = 'admin') |
35
|
|
|
{ |
36
|
|
|
global $modSettings, $user_settings; |
37
|
|
|
|
38
|
|
|
// Guests are not welcome here. |
39
|
|
|
is_not_guest(); |
40
|
|
|
|
41
|
|
|
// Validate what type of session check this is. |
42
|
|
|
$types = array(); |
43
|
|
|
call_integration_hook('integrate_validateSession', array(&$types)); |
44
|
|
|
$type = in_array($type, $types) || $type == 'moderate' ? $type : 'admin'; |
45
|
|
|
|
46
|
|
|
// Set the lifetime for our admin session. Default is ten minutes. |
47
|
|
|
$refreshTime = 10; |
48
|
|
|
|
49
|
|
|
if (isset($modSettings['admin_session_lifetime'])) |
50
|
|
|
{ |
51
|
|
|
// Maybe someone is paranoid or mistakenly misconfigured the param? Give them at least 5 minutes. |
52
|
|
|
if ($modSettings['admin_session_lifetime'] < 5) |
53
|
|
|
$refreshTime = 5; |
54
|
|
|
|
55
|
|
|
// A whole day should be more than enough.. |
56
|
|
|
elseif ($modSettings['admin_session_lifetime'] > 14400) |
57
|
|
|
$refreshTime = 14400; |
58
|
|
|
|
59
|
|
|
// We are between our internal min and max. Let's keep the board owner's value. |
60
|
|
|
else |
61
|
|
|
$refreshTime = $modSettings['admin_session_lifetime']; |
62
|
|
|
} |
63
|
|
|
|
64
|
|
|
// If we're using XML give an additional ten minutes grace as an admin can't log on in XML mode. |
65
|
|
|
if (isset($_GET['xml'])) |
66
|
|
|
$refreshTime += 10; |
67
|
|
|
|
68
|
|
|
$refreshTime = $refreshTime * 60; |
69
|
|
|
|
70
|
|
|
// Is the security option off? |
71
|
|
|
// @todo remove the exception (means update the db as well) |
72
|
|
|
if (!empty($modSettings['securityDisable' . ($type != 'admin' ? '_' . $type : '')])) |
73
|
|
|
return true; |
74
|
|
|
|
75
|
|
|
// If their admin or moderator session hasn't expired yet, let it pass, let the admin session trump a moderation one as well |
76
|
|
|
if ((!empty($_SESSION[$type . '_time']) && $_SESSION[$type . '_time'] + $refreshTime >= time()) || (!empty($_SESSION['admin_time']) && $_SESSION['admin_time'] + $refreshTime >= time())) |
77
|
|
|
return true; |
78
|
|
|
|
79
|
|
|
require_once(SUBSDIR . '/Auth.subs.php'); |
80
|
|
|
|
81
|
|
|
// Coming from the login screen |
82
|
|
|
if (isset($_POST[$type . '_pass']) || isset($_POST[$type . '_hash_pass'])) |
83
|
|
|
{ |
84
|
|
|
checkSession(); |
85
|
|
|
validateToken('admin-login'); |
86
|
|
|
|
87
|
|
|
// Hashed password, ahoy! |
88
|
|
View Code Duplication |
if (isset($_POST[$type . '_hash_pass']) && strlen($_POST[$type . '_hash_pass']) === 64) |
89
|
|
|
{ |
90
|
|
|
if (checkPassword($type, true)) |
91
|
|
|
return true; |
92
|
|
|
} |
93
|
|
|
|
94
|
|
|
// Posting the password... check it. |
95
|
|
View Code Duplication |
if (isset($_POST[$type . '_pass']) && str_replace('*', '', $_POST[$type . '_pass']) !== '') |
96
|
|
|
{ |
97
|
|
|
if (checkPassword($type)) |
98
|
|
|
return true; |
99
|
|
|
} |
100
|
|
|
} |
101
|
|
|
|
102
|
|
|
// OpenID? |
103
|
|
|
if (!empty($user_settings['openid_uri'])) |
104
|
|
|
{ |
105
|
|
|
require_once(SUBSDIR . '/OpenID.subs.php'); |
106
|
|
|
$openID = new OpenID(); |
107
|
|
|
$openID->revalidate(); |
108
|
|
|
|
109
|
|
|
$_SESSION[$type . '_time'] = time(); |
110
|
|
|
unset($_SESSION['request_referer']); |
111
|
|
|
|
112
|
|
|
return true; |
113
|
|
|
} |
114
|
|
|
|
115
|
|
|
// Better be sure to remember the real referer |
116
|
|
|
if (empty($_SESSION['request_referer'])) |
117
|
|
|
$_SESSION['request_referer'] = isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : ''; |
118
|
|
|
elseif (empty($_POST)) |
119
|
|
|
unset($_SESSION['request_referer']); |
120
|
|
|
|
121
|
|
|
// Need to type in a password for that, man. |
122
|
|
|
if (!isset($_GET['xml'])) |
123
|
|
|
adminLogin($type); |
124
|
|
|
|
125
|
|
|
return 'session_verify_fail'; |
126
|
|
|
} |
127
|
|
|
|
128
|
|
|
/** |
129
|
|
|
* Validates a supplied password is correct |
130
|
|
|
* |
131
|
|
|
* What it does: |
132
|
|
|
* |
133
|
|
|
* - Uses integration function to verify password is enabled |
134
|
|
|
* - Uses validateLoginPassword to check using standard ElkArte methods |
135
|
|
|
* |
136
|
|
|
* @event integrate_verify_password allows integration to verify the password |
137
|
|
|
* @param string $type |
138
|
|
|
* @param bool $hash if the supplied password is in _hash_pass |
139
|
|
|
* |
140
|
|
|
* @return bool |
141
|
|
|
*/ |
142
|
|
|
function checkPassword($type, $hash = false) |
143
|
|
|
{ |
144
|
|
|
global $user_info; |
145
|
|
|
|
146
|
|
|
$password = $_POST[$type . ($hash ? '_hash_pass' : '_pass')]; |
147
|
|
|
|
148
|
|
|
// Allow integration to verify the password |
149
|
|
|
$good_password = in_array(true, call_integration_hook('integrate_verify_password', array($user_info['username'], $password, $hash ? true : false)), true); |
150
|
|
|
|
151
|
|
|
// Password correct? |
152
|
|
|
if ($good_password || validateLoginPassword($password, $user_info['passwd'], $hash ? '' : $user_info['username'])) |
153
|
|
|
{ |
154
|
|
|
$_SESSION[$type . '_time'] = time(); |
155
|
|
|
unset($_SESSION['request_referer']); |
156
|
|
|
|
157
|
|
|
return true; |
158
|
|
|
} |
159
|
|
|
|
160
|
|
|
return false; |
161
|
|
|
} |
162
|
|
|
|
163
|
|
|
/** |
164
|
|
|
* Require a user who is logged in. (not a guest.) |
165
|
|
|
* |
166
|
|
|
* What it does: |
167
|
|
|
* |
168
|
|
|
* - Checks if the user is currently a guest, and if so asks them to login with a message telling them why. |
169
|
|
|
* - Message is what to tell them when asking them to login. |
170
|
|
|
* |
171
|
|
|
* @param string $message = '' |
172
|
|
|
* @param boolean $is_fatal = true |
173
|
|
|
* @throws Elk_Exception |
174
|
|
|
*/ |
175
|
|
|
function is_not_guest($message = '', $is_fatal = true) |
176
|
|
|
{ |
177
|
1 |
|
global $user_info, $txt, $context, $scripturl; |
178
|
|
|
|
179
|
|
|
// Luckily, this person isn't a guest. |
180
|
1 |
|
if (isset($user_info['is_guest']) && !$user_info['is_guest']) |
181
|
1 |
|
return true; |
182
|
|
|
|
183
|
|
|
// People always worry when they see people doing things they aren't actually doing... |
184
|
|
|
$_GET['action'] = ''; |
185
|
|
|
$_GET['board'] = ''; |
186
|
|
|
$_GET['topic'] = ''; |
187
|
|
|
writeLog(true); |
188
|
|
|
|
189
|
|
|
// Just die. |
190
|
|
|
if (isset($_REQUEST['xml']) || !$is_fatal) |
191
|
|
|
obExit(false); |
192
|
|
|
|
193
|
|
|
// Attempt to detect if they came from dlattach. |
194
|
|
|
if (ELK != 'SSI' && empty($context['theme_loaded'])) |
195
|
|
|
loadTheme(); |
196
|
|
|
|
197
|
|
|
// Never redirect to an attachment |
198
|
|
|
if (validLoginUrl($_SERVER['REQUEST_URL'])) |
199
|
|
|
{ |
200
|
|
|
$_SESSION['login_url'] = $_SERVER['REQUEST_URL']; |
201
|
|
|
} |
202
|
|
|
|
203
|
|
|
// Load the Login template and language file. |
204
|
|
|
loadLanguage('Login'); |
205
|
|
|
|
206
|
|
|
// Apparently we're not in a position to handle this now. Let's go to a safer location for now. |
207
|
|
|
if (!Template_Layers::getInstance()->hasLayers()) |
208
|
|
|
{ |
209
|
|
|
$_SESSION['login_url'] = $scripturl . '?' . $_SERVER['QUERY_STRING']; |
210
|
|
|
redirectexit('action=login'); |
211
|
|
|
} |
212
|
|
|
elseif (isset($_GET['api'])) |
213
|
|
|
return false; |
214
|
|
|
else |
215
|
|
|
{ |
216
|
|
|
loadTemplate('Login'); |
217
|
|
|
loadJavascriptFile('sha256.js', array('defer' => true)); |
218
|
|
|
$context['sub_template'] = 'kick_guest'; |
219
|
|
|
$context['robot_no_index'] = true; |
220
|
|
|
} |
221
|
|
|
|
222
|
|
|
// Use the kick_guest sub template... |
223
|
|
|
$context['kick_message'] = $message; |
224
|
|
|
$context['page_title'] = $txt['login']; |
225
|
|
|
$context['default_password'] = ''; |
226
|
|
|
|
227
|
|
|
obExit(); |
228
|
|
|
|
229
|
|
|
// We should never get to this point, but if we did we wouldn't know the user isn't a guest. |
230
|
|
|
trigger_error('Hacking attempt...', E_USER_ERROR); |
231
|
|
|
} |
232
|
|
|
|
233
|
|
|
/** |
234
|
|
|
* Apply restrictions for banned users. For example, disallow access. |
235
|
|
|
* |
236
|
|
|
* What it does: |
237
|
|
|
* |
238
|
|
|
* - If the user is banned, it dies with an error. |
239
|
|
|
* - Caches this information for optimization purposes. |
240
|
|
|
* - Forces a recheck if force_check is true. |
241
|
|
|
* |
242
|
|
|
* @param bool $forceCheck = false |
243
|
|
|
* |
244
|
|
|
* @throws Elk_Exception |
245
|
|
|
*/ |
246
|
|
|
function is_not_banned($forceCheck = false) |
247
|
|
|
{ |
248
|
|
|
global $txt, $modSettings, $context, $user_info, $cookiename, $user_settings; |
249
|
|
|
|
250
|
|
|
$db = database(); |
251
|
|
|
|
252
|
|
|
// You cannot be banned if you are an admin - doesn't help if you log out. |
253
|
|
|
if ($user_info['is_admin']) |
254
|
|
|
return; |
255
|
|
|
|
256
|
|
|
// Only check the ban every so often. (to reduce load.) |
257
|
|
|
if ($forceCheck || !isset($_SESSION['ban']) || empty($modSettings['banLastUpdated']) || ($_SESSION['ban']['last_checked'] < $modSettings['banLastUpdated']) || $_SESSION['ban']['id_member'] != $user_info['id'] || $_SESSION['ban']['ip'] != $user_info['ip'] || $_SESSION['ban']['ip2'] != $user_info['ip2'] || (isset($user_info['email'], $_SESSION['ban']['email']) && $_SESSION['ban']['email'] != $user_info['email'])) |
258
|
|
|
{ |
259
|
|
|
// Innocent until proven guilty. (but we know you are! :P) |
260
|
|
|
$_SESSION['ban'] = array( |
261
|
|
|
'last_checked' => time(), |
262
|
|
|
'id_member' => $user_info['id'], |
263
|
|
|
'ip' => $user_info['ip'], |
264
|
|
|
'ip2' => $user_info['ip2'], |
265
|
|
|
'email' => $user_info['email'], |
266
|
|
|
); |
267
|
|
|
|
268
|
|
|
$ban_query = array(); |
269
|
|
|
$ban_query_vars = array('current_time' => time()); |
270
|
|
|
$flag_is_activated = false; |
271
|
|
|
|
272
|
|
|
// Check both IP addresses. |
273
|
|
|
foreach (array('ip', 'ip2') as $ip_number) |
274
|
|
|
{ |
275
|
|
|
if ($ip_number == 'ip2' && $user_info['ip2'] == $user_info['ip']) |
276
|
|
|
continue; |
277
|
|
|
$ban_query[] = constructBanQueryIP($user_info[$ip_number]); |
278
|
|
|
|
279
|
|
|
// IP was valid, maybe there's also a hostname... |
280
|
|
|
if (empty($modSettings['disableHostnameLookup']) && $user_info[$ip_number] != 'unknown') |
281
|
|
|
{ |
282
|
|
|
$hostname = host_from_ip($user_info[$ip_number]); |
283
|
|
|
if (strlen($hostname) > 0) |
284
|
|
|
{ |
285
|
|
|
$ban_query[] = '({string:hostname} LIKE bi.hostname)'; |
286
|
|
|
$ban_query_vars['hostname'] = $hostname; |
287
|
|
|
} |
288
|
|
|
} |
289
|
|
|
} |
290
|
|
|
|
291
|
|
|
// Is their email address banned? |
292
|
|
|
if (strlen($user_info['email']) != 0) |
293
|
|
|
{ |
294
|
|
|
$ban_query[] = '({string:email} LIKE bi.email_address)'; |
295
|
|
|
$ban_query_vars['email'] = $user_info['email']; |
296
|
|
|
} |
297
|
|
|
|
298
|
|
|
// How about this user? |
299
|
|
|
if (!$user_info['is_guest'] && !empty($user_info['id'])) |
300
|
|
|
{ |
301
|
|
|
$ban_query[] = 'bi.id_member = {int:id_member}'; |
302
|
|
|
$ban_query_vars['id_member'] = $user_info['id']; |
303
|
|
|
} |
304
|
|
|
|
305
|
|
|
// Check the ban, if there's information. |
306
|
|
|
if (!empty($ban_query)) |
307
|
|
|
{ |
308
|
|
|
$restrictions = array( |
309
|
|
|
'cannot_access', |
310
|
|
|
'cannot_login', |
311
|
|
|
'cannot_post', |
312
|
|
|
'cannot_register', |
313
|
|
|
); |
314
|
|
|
$db->fetchQueryCallback(' |
315
|
|
|
SELECT bi.id_ban, bi.email_address, bi.id_member, bg.cannot_access, bg.cannot_register, |
316
|
|
|
bg.cannot_post, bg.cannot_login, bg.reason, COALESCE(bg.expire_time, 0) AS expire_time |
317
|
|
|
FROM {db_prefix}ban_items AS bi |
318
|
|
|
INNER JOIN {db_prefix}ban_groups AS bg ON (bg.id_ban_group = bi.id_ban_group AND (bg.expire_time IS NULL OR bg.expire_time > {int:current_time})) |
319
|
|
|
WHERE |
320
|
|
|
(' . implode(' OR ', $ban_query) . ')', |
321
|
|
|
$ban_query_vars, |
322
|
|
|
function ($row) use($user_info, $restrictions, &$flag_is_activated) |
323
|
|
|
{ |
324
|
|
|
// Store every type of ban that applies to you in your session. |
325
|
|
|
foreach ($restrictions as $restriction) |
326
|
|
|
{ |
327
|
|
|
if (!empty($row[$restriction])) |
328
|
|
|
{ |
329
|
|
|
$_SESSION['ban'][$restriction]['reason'] = $row['reason']; |
330
|
|
|
$_SESSION['ban'][$restriction]['ids'][] = $row['id_ban']; |
331
|
|
|
if (!isset($_SESSION['ban']['expire_time']) || ($_SESSION['ban']['expire_time'] != 0 && ($row['expire_time'] == 0 || $row['expire_time'] > $_SESSION['ban']['expire_time']))) |
332
|
|
|
$_SESSION['ban']['expire_time'] = $row['expire_time']; |
333
|
|
|
|
334
|
|
|
if (!$user_info['is_guest'] && $restriction == 'cannot_access' && ($row['id_member'] == $user_info['id'] || $row['email_address'] == $user_info['email'])) |
335
|
|
|
$flag_is_activated = true; |
336
|
|
|
} |
337
|
|
|
} |
338
|
|
|
} |
339
|
|
|
); |
340
|
|
|
} |
341
|
|
|
|
342
|
|
|
// Mark the cannot_access and cannot_post bans as being 'hit'. |
343
|
|
|
if (isset($_SESSION['ban']['cannot_access']) || isset($_SESSION['ban']['cannot_post']) || isset($_SESSION['ban']['cannot_login'])) |
344
|
|
|
log_ban(array_merge(isset($_SESSION['ban']['cannot_access']) ? $_SESSION['ban']['cannot_access']['ids'] : array(), isset($_SESSION['ban']['cannot_post']) ? $_SESSION['ban']['cannot_post']['ids'] : array(), isset($_SESSION['ban']['cannot_login']) ? $_SESSION['ban']['cannot_login']['ids'] : array())); |
345
|
|
|
|
346
|
|
|
// If for whatever reason the is_activated flag seems wrong, do a little work to clear it up. |
347
|
|
|
if ($user_info['id'] && (($user_settings['is_activated'] >= 10 && !$flag_is_activated) |
348
|
|
|
|| ($user_settings['is_activated'] < 10 && $flag_is_activated))) |
349
|
|
|
{ |
350
|
|
|
require_once(SUBSDIR . '/Bans.subs.php'); |
351
|
|
|
updateBanMembers(); |
352
|
|
|
} |
353
|
|
|
} |
354
|
|
|
|
355
|
|
|
// Hey, I know you! You're ehm... |
356
|
|
|
if (!isset($_SESSION['ban']['cannot_access']) && !empty($_COOKIE[$cookiename . '_'])) |
357
|
|
|
{ |
358
|
|
|
$bans = explode(',', $_COOKIE[$cookiename . '_']); |
359
|
|
|
foreach ($bans as $key => $value) |
360
|
|
|
$bans[$key] = (int) $value; |
361
|
|
|
|
362
|
|
|
$db->fetchQueryCallback(' |
363
|
|
|
SELECT bi.id_ban, bg.reason |
364
|
|
|
FROM {db_prefix}ban_items AS bi |
365
|
|
|
INNER JOIN {db_prefix}ban_groups AS bg ON (bg.id_ban_group = bi.id_ban_group) |
366
|
|
|
WHERE bi.id_ban IN ({array_int:ban_list}) |
367
|
|
|
AND (bg.expire_time IS NULL OR bg.expire_time > {int:current_time}) |
368
|
|
|
AND bg.cannot_access = {int:cannot_access} |
369
|
|
|
LIMIT ' . count($bans), |
370
|
|
|
array( |
371
|
|
|
'cannot_access' => 1, |
372
|
|
|
'ban_list' => $bans, |
373
|
|
|
'current_time' => time(), |
374
|
|
|
), |
375
|
|
|
function ($row) |
376
|
|
|
{ |
377
|
|
|
$_SESSION['ban']['cannot_access']['ids'][] = $row['id_ban']; |
378
|
|
|
$_SESSION['ban']['cannot_access']['reason'] = $row['reason']; |
379
|
|
|
} |
380
|
|
|
); |
381
|
|
|
|
382
|
|
|
// My mistake. Next time better. |
383
|
|
|
if (!isset($_SESSION['ban']['cannot_access'])) |
384
|
|
|
{ |
385
|
|
|
require_once(SUBSDIR . '/Auth.subs.php'); |
386
|
|
|
$cookie_url = url_parts(!empty($modSettings['localCookies']), !empty($modSettings['globalCookies'])); |
387
|
|
|
elk_setcookie($cookiename . '_', '', time() - 3600, $cookie_url[1], $cookie_url[0], false, false); |
388
|
|
|
} |
389
|
|
|
} |
390
|
|
|
|
391
|
|
|
// If you're fully banned, it's end of the story for you. |
392
|
|
|
if (isset($_SESSION['ban']['cannot_access'])) |
393
|
|
|
{ |
394
|
|
|
require_once(SUBSDIR . '/Auth.subs.php'); |
395
|
|
|
|
396
|
|
|
// We don't wanna see you! |
397
|
|
|
if (!$user_info['is_guest']) |
398
|
|
|
{ |
399
|
|
|
$controller = new Auth_Controller(); |
400
|
|
|
$controller->action_logout(true, false); |
401
|
|
|
} |
402
|
|
|
|
403
|
|
|
// 'Log' the user out. Can't have any funny business... (save the name!) |
404
|
|
|
$old_name = isset($user_info['name']) && $user_info['name'] != '' ? $user_info['name'] : $txt['guest_title']; |
405
|
|
|
$user_info['name'] = ''; |
406
|
|
|
$user_info['username'] = ''; |
407
|
|
|
$user_info['is_guest'] = true; |
408
|
|
|
$user_info['is_admin'] = false; |
409
|
|
|
$user_info['permissions'] = array(); |
410
|
|
|
$user_info['id'] = 0; |
411
|
|
|
$context['user'] = array( |
412
|
|
|
'id' => 0, |
413
|
|
|
'username' => '', |
414
|
|
|
'name' => $txt['guest_title'], |
415
|
|
|
'is_guest' => true, |
416
|
|
|
'is_logged' => false, |
417
|
|
|
'is_admin' => false, |
418
|
|
|
'is_mod' => false, |
419
|
|
|
'is_moderator' => false, |
420
|
|
|
'can_mod' => false, |
421
|
|
|
'language' => $user_info['language'], |
422
|
|
|
); |
423
|
|
|
|
424
|
|
|
// A goodbye present. |
425
|
|
|
$cookie_url = url_parts(!empty($modSettings['localCookies']), !empty($modSettings['globalCookies'])); |
426
|
|
|
elk_setcookie($cookiename . '_', implode(',', $_SESSION['ban']['cannot_access']['ids']), time() + 3153600, $cookie_url[1], $cookie_url[0], false, false); |
427
|
|
|
|
428
|
|
|
// Don't scare anyone, now. |
429
|
|
|
$_GET['action'] = ''; |
430
|
|
|
$_GET['board'] = ''; |
431
|
|
|
$_GET['topic'] = ''; |
432
|
|
|
writeLog(true); |
433
|
|
|
|
434
|
|
|
// You banned, sucka! |
435
|
|
|
throw new Elk_Exception(sprintf($txt['your_ban'], $old_name) . (empty($_SESSION['ban']['cannot_access']['reason']) ? '' : '<br />' . $_SESSION['ban']['cannot_access']['reason']) . '<br />' . (!empty($_SESSION['ban']['expire_time']) ? sprintf($txt['your_ban_expires'], standardTime($_SESSION['ban']['expire_time'], false)) : $txt['your_ban_expires_never']), 'user'); |
436
|
|
|
} |
437
|
|
|
// You're not allowed to log in but yet you are. Let's fix that. |
438
|
|
|
elseif (isset($_SESSION['ban']['cannot_login']) && !$user_info['is_guest']) |
439
|
|
|
{ |
440
|
|
|
// We don't wanna see you! |
441
|
|
|
require_once(SUBSDIR . '/Logging.subs.php'); |
442
|
|
|
deleteMemberLogOnline(); |
443
|
|
|
|
444
|
|
|
// 'Log' the user out. Can't have any funny business... (save the name!) |
445
|
|
|
$old_name = isset($user_info['name']) && $user_info['name'] != '' ? $user_info['name'] : $txt['guest_title']; |
446
|
|
|
$user_info['name'] = ''; |
447
|
|
|
$user_info['username'] = ''; |
448
|
|
|
$user_info['is_guest'] = true; |
449
|
|
|
$user_info['is_admin'] = false; |
450
|
|
|
$user_info['permissions'] = array(); |
451
|
|
|
$user_info['id'] = 0; |
452
|
|
|
$context['user'] = array( |
453
|
|
|
'id' => 0, |
454
|
|
|
'username' => '', |
455
|
|
|
'name' => $txt['guest_title'], |
456
|
|
|
'is_guest' => true, |
457
|
|
|
'is_logged' => false, |
458
|
|
|
'is_admin' => false, |
459
|
|
|
'is_mod' => false, |
460
|
|
|
'is_moderator' => false, |
461
|
|
|
'can_mod' => false, |
462
|
|
|
'language' => $user_info['language'], |
463
|
|
|
); |
464
|
|
|
|
465
|
|
|
// Wipe 'n Clean(r) erases all traces. |
466
|
|
|
$_GET['action'] = ''; |
467
|
|
|
$_GET['board'] = ''; |
468
|
|
|
$_GET['topic'] = ''; |
469
|
|
|
writeLog(true); |
470
|
|
|
|
471
|
|
|
// Log them out |
472
|
|
|
$controller = new Auth_Controller(); |
473
|
|
|
$controller->action_logout(true, false); |
474
|
|
|
|
475
|
|
|
// Tell them thanks |
476
|
|
|
throw new Elk_Exception(sprintf($txt['your_ban'], $old_name) . (empty($_SESSION['ban']['cannot_login']['reason']) ? '' : '<br />' . $_SESSION['ban']['cannot_login']['reason']) . '<br />' . (!empty($_SESSION['ban']['expire_time']) ? sprintf($txt['your_ban_expires'], standardTime($_SESSION['ban']['expire_time'], false)) : $txt['your_ban_expires_never']) . '<br />' . $txt['ban_continue_browse'], 'user'); |
477
|
|
|
} |
478
|
|
|
|
479
|
|
|
// Fix up the banning permissions. |
480
|
|
|
if (isset($user_info['permissions'])) |
481
|
|
|
banPermissions(); |
482
|
|
|
} |
483
|
|
|
|
484
|
|
|
/** |
485
|
|
|
* Fix permissions according to ban status. |
486
|
|
|
* |
487
|
|
|
* What it does: |
488
|
|
|
* |
489
|
|
|
* - Applies any states of banning by removing permissions the user cannot have. |
490
|
|
|
* |
491
|
|
|
* @event integrate_post_ban_permissions Allows to update denied permissions |
492
|
|
|
* @event integrate_warn_permissions Allows changing of permissions for users on warning moderate |
493
|
|
|
* @package Bans |
494
|
|
|
*/ |
495
|
|
|
function banPermissions() |
496
|
|
|
{ |
497
|
|
|
global $user_info, $modSettings, $context; |
498
|
|
|
|
499
|
|
|
// Somehow they got here, at least take away all permissions... |
500
|
|
|
if (isset($_SESSION['ban']['cannot_access'])) |
501
|
|
|
$user_info['permissions'] = array(); |
502
|
|
|
// Okay, well, you can watch, but don't touch a thing. |
503
|
|
|
elseif (isset($_SESSION['ban']['cannot_post']) || (!empty($modSettings['warning_mute']) && $modSettings['warning_mute'] <= $user_info['warning'])) |
504
|
|
|
{ |
505
|
|
|
$denied_permissions = array( |
506
|
|
|
'pm_send', |
507
|
|
|
'calendar_post', 'calendar_edit_own', 'calendar_edit_any', |
508
|
|
|
'poll_post', |
509
|
|
|
'poll_add_own', 'poll_add_any', |
510
|
|
|
'poll_edit_own', 'poll_edit_any', |
511
|
|
|
'poll_lock_own', 'poll_lock_any', |
512
|
|
|
'poll_remove_own', 'poll_remove_any', |
513
|
|
|
'manage_attachments', 'manage_smileys', 'manage_boards', 'admin_forum', 'manage_permissions', |
514
|
|
|
'moderate_forum', 'manage_membergroups', 'manage_bans', 'send_mail', 'edit_news', |
515
|
|
|
'profile_identity_any', 'profile_extra_any', 'profile_title_any', |
516
|
|
|
'post_new', 'post_reply_own', 'post_reply_any', |
517
|
|
|
'delete_own', 'delete_any', 'delete_replies', |
518
|
|
|
'make_sticky', |
519
|
|
|
'merge_any', 'split_any', |
520
|
|
|
'modify_own', 'modify_any', 'modify_replies', |
521
|
|
|
'move_any', |
522
|
|
|
'send_topic', |
523
|
|
|
'lock_own', 'lock_any', |
524
|
|
|
'remove_own', 'remove_any', |
525
|
|
|
'post_unapproved_topics', 'post_unapproved_replies_own', 'post_unapproved_replies_any', |
526
|
|
|
); |
527
|
|
|
Template_Layers::getInstance()->addAfter('admin_warning', 'body'); |
528
|
|
|
|
529
|
|
|
call_integration_hook('integrate_post_ban_permissions', array(&$denied_permissions)); |
530
|
|
|
$user_info['permissions'] = array_diff($user_info['permissions'], $denied_permissions); |
531
|
|
|
} |
532
|
|
|
// Are they absolutely under moderation? |
533
|
|
|
elseif (!empty($modSettings['warning_moderate']) && $modSettings['warning_moderate'] <= $user_info['warning']) |
534
|
|
|
{ |
535
|
|
|
// Work out what permissions should change... |
536
|
|
|
$permission_change = array( |
537
|
|
|
'post_new' => 'post_unapproved_topics', |
538
|
|
|
'post_reply_own' => 'post_unapproved_replies_own', |
539
|
|
|
'post_reply_any' => 'post_unapproved_replies_any', |
540
|
|
|
'post_attachment' => 'post_unapproved_attachments', |
541
|
|
|
); |
542
|
|
|
call_integration_hook('integrate_warn_permissions', array(&$permission_change)); |
543
|
|
View Code Duplication |
foreach ($permission_change as $old => $new) |
544
|
|
|
{ |
545
|
|
|
if (!in_array($old, $user_info['permissions'])) |
546
|
|
|
unset($permission_change[$old]); |
547
|
|
|
else |
548
|
|
|
$user_info['permissions'][] = $new; |
549
|
|
|
} |
550
|
|
|
$user_info['permissions'] = array_diff($user_info['permissions'], array_keys($permission_change)); |
551
|
|
|
} |
552
|
|
|
|
553
|
|
|
// @todo Find a better place to call this? Needs to be after permissions loaded! |
554
|
|
|
// Finally, some bits we cache in the session because it saves queries. |
555
|
|
|
if (isset($_SESSION['mc']) && $_SESSION['mc']['time'] > $modSettings['settings_updated'] && $_SESSION['mc']['id'] == $user_info['id']) |
556
|
|
|
$user_info['mod_cache'] = $_SESSION['mc']; |
557
|
|
|
else |
558
|
|
|
{ |
559
|
|
|
require_once(SUBSDIR . '/Auth.subs.php'); |
560
|
|
|
rebuildModCache(); |
561
|
|
|
} |
562
|
|
|
|
563
|
|
|
// Now that we have the mod cache taken care of lets setup a cache for the number of mod reports still open |
564
|
|
|
if (isset($_SESSION['rc']) && $_SESSION['rc']['time'] > $modSettings['last_mod_report_action'] && $_SESSION['rc']['id'] == $user_info['id']) |
565
|
|
|
{ |
566
|
|
|
$context['open_mod_reports'] = $_SESSION['rc']['reports']; |
567
|
|
|
if (allowedTo('admin_forum')) |
568
|
|
|
{ |
569
|
|
|
$context['open_pm_reports'] = $_SESSION['rc']['pm_reports']; |
570
|
|
|
} |
571
|
|
|
} |
572
|
|
|
elseif ($_SESSION['mc']['bq'] != '0=1') |
573
|
|
|
{ |
574
|
|
|
require_once(SUBSDIR . '/Moderation.subs.php'); |
575
|
|
|
recountOpenReports(true, allowedTo('admin_forum')); |
576
|
|
|
} |
577
|
|
|
else |
578
|
|
|
$context['open_mod_reports'] = 0; |
579
|
|
|
} |
580
|
|
|
|
581
|
|
|
/** |
582
|
|
|
* Log a ban in the database. |
583
|
|
|
* |
584
|
|
|
* What it does: |
585
|
|
|
* |
586
|
|
|
* - Log the current user in the ban logs. |
587
|
|
|
* - Increment the hit counters for the specified ban ID's (if any.) |
588
|
|
|
* |
589
|
|
|
* @package Bans |
590
|
|
|
* @param int[] $ban_ids = array() |
591
|
|
|
* @param string|null $email = null |
592
|
|
|
*/ |
593
|
|
|
function log_ban($ban_ids = array(), $email = null) |
594
|
|
|
{ |
595
|
|
|
global $user_info; |
596
|
|
|
|
597
|
|
|
$db = database(); |
598
|
|
|
|
599
|
|
|
// Don't log web accelerators, it's very confusing... |
600
|
|
|
if (isset($_SERVER['HTTP_X_MOZ']) && $_SERVER['HTTP_X_MOZ'] == 'prefetch') |
601
|
|
|
return; |
602
|
|
|
|
603
|
|
|
$db->insert('', |
604
|
|
|
'{db_prefix}log_banned', |
605
|
|
|
array('id_member' => 'int', 'ip' => 'string-16', 'email' => 'string', 'log_time' => 'int'), |
606
|
|
|
array($user_info['id'], $user_info['ip'], ($email === null ? ($user_info['is_guest'] ? '' : $user_info['email']) : $email), time()), |
607
|
|
|
array('id_ban_log') |
608
|
|
|
); |
609
|
|
|
|
610
|
|
|
// One extra point for these bans. |
611
|
|
|
if (!empty($ban_ids)) |
612
|
|
|
$db->query('', ' |
613
|
|
|
UPDATE {db_prefix}ban_items |
614
|
|
|
SET hits = hits + 1 |
615
|
|
|
WHERE id_ban IN ({array_int:ban_ids})', |
616
|
|
|
array( |
617
|
|
|
'ban_ids' => $ban_ids, |
618
|
|
|
) |
619
|
|
|
); |
620
|
|
|
} |
621
|
|
|
|
622
|
|
|
/** |
623
|
|
|
* Checks if a given email address might be banned. |
624
|
|
|
* |
625
|
|
|
* What it does: |
626
|
|
|
* |
627
|
|
|
* - Check if a given email is banned. |
628
|
|
|
* - Performs an immediate ban if the turns turns out positive. |
629
|
|
|
* |
630
|
|
|
* @package Bans |
631
|
|
|
* @param string $email |
632
|
|
|
* @param string $restriction |
633
|
|
|
* @param string $error |
634
|
|
|
* |
635
|
|
|
* @throws Elk_Exception |
636
|
|
|
*/ |
637
|
|
|
function isBannedEmail($email, $restriction, $error) |
638
|
|
|
{ |
639
|
1 |
|
global $txt; |
640
|
|
|
|
641
|
1 |
|
$db = database(); |
642
|
|
|
|
643
|
|
|
// Can't ban an empty email |
644
|
1 |
|
if (empty($email) || trim($email) == '') |
645
|
1 |
|
return; |
646
|
|
|
|
647
|
|
|
// Let's start with the bans based on your IP/hostname/memberID... |
648
|
1 |
|
$ban_ids = isset($_SESSION['ban'][$restriction]) ? $_SESSION['ban'][$restriction]['ids'] : array(); |
649
|
1 |
|
$ban_reason = isset($_SESSION['ban'][$restriction]) ? $_SESSION['ban'][$restriction]['reason'] : ''; |
650
|
|
|
|
651
|
|
|
// ...and add to that the email address you're trying to register. |
652
|
1 |
|
$request = $db->query('', ' |
653
|
1 |
|
SELECT bi.id_ban, bg.' . $restriction . ', bg.cannot_access, bg.reason |
654
|
|
|
FROM {db_prefix}ban_items AS bi |
655
|
|
|
INNER JOIN {db_prefix}ban_groups AS bg ON (bg.id_ban_group = bi.id_ban_group) |
656
|
|
|
WHERE {string:email} LIKE bi.email_address |
657
|
1 |
|
AND (bg.' . $restriction . ' = {int:cannot_access} OR bg.cannot_access = {int:cannot_access}) |
658
|
1 |
|
AND (bg.expire_time IS NULL OR bg.expire_time >= {int:now})', |
659
|
|
|
array( |
660
|
1 |
|
'email' => $email, |
661
|
1 |
|
'cannot_access' => 1, |
662
|
1 |
|
'now' => time(), |
663
|
|
|
) |
664
|
1 |
|
); |
665
|
1 |
|
while ($row = $db->fetch_assoc($request)) |
666
|
|
|
{ |
667
|
|
|
if (!empty($row['cannot_access'])) |
668
|
|
|
{ |
669
|
|
|
$_SESSION['ban']['cannot_access']['ids'][] = $row['id_ban']; |
670
|
|
|
$_SESSION['ban']['cannot_access']['reason'] = $row['reason']; |
671
|
|
|
} |
672
|
|
|
if (!empty($row[$restriction])) |
673
|
|
|
{ |
674
|
|
|
$ban_ids[] = $row['id_ban']; |
675
|
|
|
$ban_reason = $row['reason']; |
676
|
|
|
} |
677
|
|
|
} |
678
|
1 |
|
$db->free_result($request); |
679
|
|
|
|
680
|
|
|
// You're in biiig trouble. Banned for the rest of this session! |
681
|
1 |
|
if (isset($_SESSION['ban']['cannot_access'])) |
682
|
1 |
|
{ |
683
|
|
|
log_ban($_SESSION['ban']['cannot_access']['ids']); |
684
|
|
|
$_SESSION['ban']['last_checked'] = time(); |
685
|
|
|
|
686
|
|
|
throw new Elk_Exception(sprintf($txt['your_ban'], $txt['guest_title']) . $_SESSION['ban']['cannot_access']['reason'], false); |
687
|
|
|
} |
688
|
|
|
|
689
|
1 |
|
if (!empty($ban_ids)) |
690
|
1 |
|
{ |
691
|
|
|
// Log this ban for future reference. |
692
|
|
|
log_ban($ban_ids, $email); |
693
|
|
|
throw new Elk_Exception($error . $ban_reason, false); |
694
|
|
|
} |
695
|
1 |
|
} |
696
|
|
|
|
697
|
|
|
/** |
698
|
|
|
* Make sure the user's correct session was passed, and they came from here. |
699
|
|
|
* |
700
|
|
|
* What it does: |
701
|
|
|
* |
702
|
|
|
* - Checks the current session, verifying that the person is who he or she should be. |
703
|
|
|
* - Also checks the referrer to make sure they didn't get sent here. |
704
|
|
|
* - Depends on the disableCheckUA setting, which is usually missing. |
705
|
|
|
* - Will check GET, POST, or REQUEST depending on the passed type. |
706
|
|
|
* - Also optionally checks the referring action if passed. (note that the referring action must be by GET.) |
707
|
|
|
* |
708
|
|
|
* @param string $type = 'post' (post, get, request) |
709
|
|
|
* @param string $from_action = '' |
710
|
|
|
* @param bool $is_fatal = true |
711
|
|
|
* |
712
|
|
|
* @return string the error message if is_fatal is false. |
713
|
|
|
* @throws Elk_Exception |
714
|
|
|
*/ |
715
|
|
|
function checkSession($type = 'post', $from_action = '', $is_fatal = true) |
716
|
|
|
{ |
717
|
|
|
global $modSettings, $boardurl; |
718
|
|
|
|
719
|
|
|
// We'll work out user agent checks |
720
|
|
|
$req = request(); |
721
|
|
|
|
722
|
|
|
// Is it in as $_POST['sc']? |
723
|
|
|
if ($type == 'post') |
724
|
|
|
{ |
725
|
|
|
$check = isset($_POST[$_SESSION['session_var']]) ? $_POST[$_SESSION['session_var']] : (empty($modSettings['strictSessionCheck']) && isset($_POST['sc']) ? $_POST['sc'] : null); |
726
|
|
|
if ($check !== $_SESSION['session_value']) |
727
|
|
|
$error = 'session_timeout'; |
728
|
|
|
} |
729
|
|
|
// How about $_GET['sesc']? |
730
|
|
|
elseif ($type === 'get') |
731
|
|
|
{ |
732
|
|
|
$check = isset($_GET[$_SESSION['session_var']]) ? $_GET[$_SESSION['session_var']] : (empty($modSettings['strictSessionCheck']) && isset($_GET['sesc']) ? $_GET['sesc'] : null); |
733
|
|
|
if ($check !== $_SESSION['session_value']) |
734
|
|
|
$error = 'session_verify_fail'; |
735
|
|
|
} |
736
|
|
|
// Or can it be in either? |
737
|
|
|
elseif ($type == 'request') |
738
|
|
|
{ |
739
|
|
|
$check = isset($_GET[$_SESSION['session_var']]) ? $_GET[$_SESSION['session_var']] : (empty($modSettings['strictSessionCheck']) && isset($_GET['sesc']) ? $_GET['sesc'] : (isset($_POST[$_SESSION['session_var']]) ? $_POST[$_SESSION['session_var']] : (empty($modSettings['strictSessionCheck']) && isset($_POST['sc']) ? $_POST['sc'] : null))); |
740
|
|
|
|
741
|
|
|
if ($check !== $_SESSION['session_value']) |
742
|
|
|
$error = 'session_verify_fail'; |
743
|
|
|
} |
744
|
|
|
|
745
|
|
|
// Verify that they aren't changing user agents on us - that could be bad. |
746
|
|
|
if ((!isset($_SESSION['USER_AGENT']) || $_SESSION['USER_AGENT'] != $req->user_agent()) && empty($modSettings['disableCheckUA'])) |
747
|
|
|
$error = 'session_verify_fail'; |
748
|
|
|
|
749
|
|
|
// Make sure a page with session check requirement is not being prefetched. |
750
|
|
|
stop_prefetching(); |
751
|
|
|
|
752
|
|
|
// Check the referring site - it should be the same server at least! |
753
|
|
|
if (isset($_SESSION['request_referer'])) |
754
|
|
|
$referrer_url = $_SESSION['request_referer']; |
755
|
|
|
else |
756
|
|
|
$referrer_url = isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : ''; |
757
|
|
|
|
758
|
|
|
$referrer = @parse_url($referrer_url); |
759
|
|
|
|
760
|
|
|
if (!empty($referrer['host'])) |
761
|
|
|
{ |
762
|
|
|
if (strpos($_SERVER['HTTP_HOST'], ':') !== false) |
763
|
|
|
$real_host = substr($_SERVER['HTTP_HOST'], 0, strpos($_SERVER['HTTP_HOST'], ':')); |
764
|
|
|
else |
765
|
|
|
$real_host = $_SERVER['HTTP_HOST']; |
766
|
|
|
|
767
|
|
|
$parsed_url = parse_url($boardurl); |
768
|
|
|
|
769
|
|
|
// Are global cookies on? If so, let's check them ;). |
770
|
|
|
if (!empty($modSettings['globalCookies'])) |
771
|
|
|
{ |
772
|
|
|
if (preg_match('~(?:[^\.]+\.)?([^\.]{3,}\..+)\z~i', $parsed_url['host'], $parts) == 1) |
773
|
|
|
$parsed_url['host'] = $parts[1]; |
774
|
|
|
|
775
|
|
|
if (preg_match('~(?:[^\.]+\.)?([^\.]{3,}\..+)\z~i', $referrer['host'], $parts) == 1) |
776
|
|
|
$referrer['host'] = $parts[1]; |
777
|
|
|
|
778
|
|
|
if (preg_match('~(?:[^\.]+\.)?([^\.]{3,}\..+)\z~i', $real_host, $parts) == 1) |
779
|
|
|
$real_host = $parts[1]; |
780
|
|
|
} |
781
|
|
|
|
782
|
|
|
// Okay: referrer must either match parsed_url or real_host. |
783
|
|
|
if (isset($parsed_url['host']) && strtolower($referrer['host']) != strtolower($parsed_url['host']) && strtolower($referrer['host']) != strtolower($real_host)) |
784
|
|
|
{ |
785
|
|
|
$error = 'verify_url_fail'; |
786
|
|
|
$log_error = true; |
787
|
|
|
$sprintf = array(Util::htmlspecialchars($referrer_url)); |
788
|
|
|
} |
789
|
|
|
} |
790
|
|
|
|
791
|
|
|
// Well, first of all, if a from_action is specified you'd better have an old_url. |
792
|
|
|
if (!empty($from_action) && (!isset($_SESSION['old_url']) || preg_match('~[?;&]action=' . $from_action . '([;&]|$)~', $_SESSION['old_url']) == 0)) |
793
|
|
|
{ |
794
|
|
|
$error = 'verify_url_fail'; |
795
|
|
|
$log_error = true; |
796
|
|
|
$sprintf = array(Util::htmlspecialchars($referrer_url)); |
797
|
|
|
} |
798
|
|
|
|
799
|
|
|
// Everything is ok, return an empty string. |
800
|
|
|
if (!isset($error)) |
801
|
|
|
return ''; |
802
|
|
|
// A session error occurred, show the error. |
803
|
|
|
elseif ($is_fatal) |
804
|
|
|
{ |
805
|
|
|
if (isset($_GET['xml']) || isset($_REQUEST['api'])) |
806
|
|
|
{ |
807
|
|
|
@ob_end_clean(); |
808
|
|
|
header('HTTP/1.1 403 Forbidden - Session timeout'); |
809
|
|
|
die; |
|
|
|
|
810
|
|
|
} |
811
|
|
|
else |
812
|
|
|
throw new Elk_Exception($error, isset($log_error) ? 'user' : false, isset($sprintf) ? $sprintf : array()); |
813
|
|
|
} |
814
|
|
|
// A session error occurred, return the error to the calling function. |
815
|
|
|
else |
816
|
|
|
return $error; |
817
|
|
|
|
818
|
|
|
// We really should never fall through here, for very important reasons. Let's make sure. |
819
|
|
|
trigger_error('Hacking attempt...', E_USER_ERROR); |
|
|
|
|
820
|
|
|
} |
821
|
|
|
|
822
|
|
|
/** |
823
|
|
|
* Lets give you a token of our appreciation. |
824
|
|
|
* |
825
|
|
|
* What it does: |
826
|
|
|
* |
827
|
|
|
* - Creates a one time use form token |
828
|
|
|
* |
829
|
|
|
* @param string $action The specific site action that a token will be generated for |
830
|
|
|
* @param string $type = 'post' If the token will be returned via post or get |
831
|
|
|
* |
832
|
|
|
* @return string[] array of token var, time, csrf, token |
833
|
|
|
*/ |
834
|
|
|
function createToken($action, $type = 'post') |
835
|
|
|
{ |
836
|
6 |
|
global $context; |
837
|
|
|
|
838
|
|
|
// Generate a new token token_var pair |
839
|
6 |
|
$tokenizer = new Token_Hash(); |
840
|
6 |
|
$token_var = $tokenizer->generate_hash(rand(7, 12)); |
841
|
6 |
|
$token = $tokenizer->generate_hash(32); |
842
|
|
|
|
843
|
|
|
// We need user agent and the client IP |
844
|
6 |
|
$req = request(); |
845
|
6 |
|
$csrf_hash = hash('sha1', $token . $req->client_ip() . $req->user_agent()); |
846
|
|
|
|
847
|
|
|
// Save the session token and make it available to the forms |
848
|
6 |
|
$_SESSION['token'][$type . '-' . $action] = array($token_var, $csrf_hash, time(), $token); |
849
|
6 |
|
$context[$action . '_token'] = $token; |
850
|
6 |
|
$context[$action . '_token_var'] = $token_var; |
851
|
|
|
|
852
|
6 |
|
return array($action . '_token_var' => $token_var, $action . '_token' => $token); |
853
|
|
|
} |
854
|
|
|
|
855
|
|
|
/** |
856
|
|
|
* Only patrons with valid tokens can ride this ride. |
857
|
|
|
* |
858
|
|
|
* What it does: |
859
|
|
|
* |
860
|
|
|
* - Validates that the received token is correct |
861
|
|
|
* - 1. The token exists in session. |
862
|
|
|
* - 2. The {$type} variable should exist. |
863
|
|
|
* - 3. We concatenate the variable we received with the user agent |
864
|
|
|
* - 4. Match that result against what is in the session. |
865
|
|
|
* - 5. If it matches, success, otherwise we fallout. |
866
|
|
|
* |
867
|
|
|
* @param string $action |
868
|
|
|
* @param string $type = 'post' (get, request, or post) |
869
|
|
|
* @param bool $reset = true Reset the token on failure |
870
|
|
|
* @param bool $fatal if true a fatal_lang_error is issued for invalid tokens, otherwise false is returned |
871
|
|
|
* |
872
|
|
|
* @return bool except for $action == 'login' where the token is returned |
873
|
|
|
* @throws Elk_Exception token_verify_fail |
874
|
|
|
*/ |
875
|
|
|
function validateToken($action, $type = 'post', $reset = true, $fatal = true) |
876
|
|
|
{ |
877
|
4 |
|
$type = ($type === 'get' || $type === 'request') ? $type : 'post'; |
878
|
4 |
|
$token_index = $type . '-' . $action; |
879
|
|
|
|
880
|
|
|
// Logins are special: the token is used to have the password with javascript before POST it |
881
|
4 |
View Code Duplication |
if ($action == 'login') |
882
|
4 |
|
{ |
883
|
|
|
if (isset($_SESSION['token'][$token_index])) |
884
|
|
|
{ |
885
|
|
|
$return = $_SESSION['token'][$token_index][3]; |
886
|
|
|
unset($_SESSION['token'][$token_index]); |
887
|
|
|
|
888
|
|
|
return $return; |
889
|
|
|
} |
890
|
|
|
else |
891
|
|
|
return ''; |
892
|
|
|
} |
893
|
|
|
|
894
|
4 |
|
if (!isset($_SESSION['token'][$token_index])) |
895
|
4 |
|
return false; |
896
|
|
|
|
897
|
|
|
// We need the user agent and client IP |
898
|
|
|
$req = request(); |
899
|
|
|
|
900
|
|
|
// Shortcut |
901
|
|
|
$passed_token_var = isset($GLOBALS['_' . strtoupper($type)][$_SESSION['token'][$token_index][0]]) ? $GLOBALS['_' . strtoupper($type)][$_SESSION['token'][$token_index][0]] : null; |
902
|
|
|
$csrf_hash = hash('sha1', $passed_token_var . $req->client_ip() . $req->user_agent()); |
903
|
|
|
|
904
|
|
|
// Checked what was passed in combination with the user agent |
905
|
|
View Code Duplication |
if (isset($_SESSION['token'][$token_index], $passed_token_var) |
906
|
|
|
&& $csrf_hash === $_SESSION['token'][$token_index][1]) |
907
|
|
|
{ |
908
|
|
|
// Consume the token, let them pass |
909
|
|
|
unset($_SESSION['token'][$token_index]); |
910
|
|
|
|
911
|
|
|
return true; |
912
|
|
|
} |
913
|
|
|
|
914
|
|
|
// Patrons with invalid tokens get the boot. |
915
|
|
|
if ($reset) |
916
|
|
|
{ |
917
|
|
|
// Might as well do some cleanup on this. |
918
|
|
|
cleanTokens(); |
919
|
|
|
|
920
|
|
|
// I'm back baby. |
921
|
|
|
createToken($action, $type); |
922
|
|
|
|
923
|
|
|
if ($fatal) |
924
|
|
|
throw new Elk_Exception('token_verify_fail', false); |
925
|
|
|
} |
926
|
|
|
// You don't get a new token |
927
|
|
|
else |
928
|
|
|
{ |
929
|
|
|
// Explicitly remove this token |
930
|
|
|
unset($_SESSION['token'][$token_index]); |
931
|
|
|
|
932
|
|
|
// Remove older tokens. |
933
|
|
|
cleanTokens(); |
934
|
|
|
} |
935
|
|
|
|
936
|
|
|
return false; |
937
|
|
|
} |
938
|
|
|
|
939
|
|
|
/** |
940
|
|
|
* Removes old unused tokens from session |
941
|
|
|
* |
942
|
|
|
* What it does: |
943
|
|
|
* |
944
|
|
|
* - Defaults to 3 hours before a token is considered expired |
945
|
|
|
* - if $complete = true will remove all tokens |
946
|
|
|
* |
947
|
|
|
* @param bool $complete = false |
948
|
|
|
* @param string $suffix = false |
949
|
|
|
*/ |
950
|
|
|
function cleanTokens($complete = false, $suffix = '') |
951
|
|
|
{ |
952
|
|
|
// We appreciate cleaning up after yourselves. |
953
|
|
|
if (!isset($_SESSION['token'])) |
954
|
|
|
return; |
955
|
|
|
|
956
|
|
|
// Clean up tokens, trying to give enough time still. |
957
|
|
|
foreach ($_SESSION['token'] as $key => $data) |
958
|
|
|
{ |
959
|
|
|
if (!empty($suffix)) |
960
|
|
|
$force = $complete || strpos($key, $suffix); |
961
|
|
|
else |
962
|
|
|
$force = $complete; |
963
|
|
|
|
964
|
|
|
if ($data[2] + 10800 < time() || $force) |
965
|
|
|
unset($_SESSION['token'][$key]); |
966
|
|
|
} |
967
|
|
|
} |
968
|
|
|
|
969
|
|
|
/** |
970
|
|
|
* Check whether a form has been submitted twice. |
971
|
|
|
* |
972
|
|
|
* What it does: |
973
|
|
|
* |
974
|
|
|
* - Registers a sequence number for a form. |
975
|
|
|
* - Checks whether a submitted sequence number is registered in the current session. |
976
|
|
|
* - Depending on the value of is_fatal shows an error or returns true or false. |
977
|
|
|
* - Frees a sequence number from the stack after it's been checked. |
978
|
|
|
* - Frees a sequence number without checking if action == 'free'. |
979
|
|
|
* |
980
|
|
|
* @param string $action |
981
|
|
|
* @param bool $is_fatal = true |
982
|
|
|
* |
983
|
|
|
* @return bool |
984
|
|
|
* @throws Elk_Exception error_form_already_submitted |
985
|
|
|
*/ |
986
|
|
|
function checkSubmitOnce($action, $is_fatal = false) |
987
|
|
|
{ |
988
|
|
|
global $context; |
989
|
|
|
|
990
|
|
|
if (!isset($_SESSION['forms'])) |
991
|
|
|
$_SESSION['forms'] = array(); |
992
|
|
|
|
993
|
|
|
// Register a form number and store it in the session stack. (use this on the page that has the form.) |
994
|
|
|
if ($action == 'register') |
995
|
|
|
{ |
996
|
|
|
$tokenizer = new Token_Hash(); |
997
|
|
|
$context['form_sequence_number'] = ''; |
998
|
|
|
while (empty($context['form_sequence_number']) || in_array($context['form_sequence_number'], $_SESSION['forms'])) |
999
|
|
|
$context['form_sequence_number'] = $tokenizer->generate_hash(); |
1000
|
|
|
} |
1001
|
|
|
// Check whether the submitted number can be found in the session. |
1002
|
|
|
elseif ($action == 'check') |
1003
|
|
|
{ |
1004
|
|
|
if (!isset($_REQUEST['seqnum'])) |
1005
|
|
|
return true; |
1006
|
|
|
elseif (!in_array($_REQUEST['seqnum'], $_SESSION['forms'])) |
1007
|
|
|
{ |
1008
|
|
|
// Mark this one as used |
1009
|
|
|
$_SESSION['forms'][] = (string) $_REQUEST['seqnum']; |
1010
|
|
|
return true; |
1011
|
|
|
} |
1012
|
|
|
elseif ($is_fatal) |
1013
|
|
|
throw new Elk_Exception('error_form_already_submitted', false); |
1014
|
|
|
else |
1015
|
|
|
return false; |
1016
|
|
|
} |
1017
|
|
|
// Don't check, just free the stack number. |
1018
|
|
|
elseif ($action == 'free' && isset($_REQUEST['seqnum']) && in_array($_REQUEST['seqnum'], $_SESSION['forms'])) |
1019
|
|
|
$_SESSION['forms'] = array_diff($_SESSION['forms'], array($_REQUEST['seqnum'])); |
1020
|
|
|
elseif ($action != 'free') |
1021
|
|
|
trigger_error('checkSubmitOnce(): Invalid action \'' . $action . '\'', E_USER_WARNING); |
1022
|
|
|
} |
1023
|
|
|
|
1024
|
|
|
/** |
1025
|
|
|
* This function checks whether the user is allowed to do permission. (ie. post_new.) |
1026
|
|
|
* |
1027
|
|
|
* What it does: |
1028
|
|
|
* |
1029
|
|
|
* - If boards parameter is specified, checks those boards instead of the current one (if applicable). |
1030
|
|
|
* - Always returns true if the user is an administrator. |
1031
|
|
|
* |
1032
|
|
|
* @param string[]|string $permission permission |
1033
|
|
|
* @param int[]|int|null $boards array of board IDs, a single id or null |
1034
|
|
|
* |
1035
|
|
|
* @return boolean if the user can do the permission |
1036
|
|
|
*/ |
1037
|
|
|
function allowedTo($permission, $boards = null) |
1038
|
|
|
{ |
1039
|
33 |
|
global $user_info; |
1040
|
|
|
|
1041
|
33 |
|
$db = database(); |
1042
|
|
|
|
1043
|
|
|
// You're always allowed to do nothing. (unless you're a working man, MR. LAZY :P!) |
1044
|
33 |
|
if (empty($permission)) |
1045
|
33 |
|
return true; |
1046
|
|
|
|
1047
|
|
|
// You're never allowed to do something if your data hasn't been loaded yet! |
1048
|
33 |
|
if (empty($user_info)) |
1049
|
33 |
|
return false; |
1050
|
|
|
|
1051
|
|
|
// Administrators are supermen :P. |
1052
|
33 |
|
if ($user_info['is_admin']) |
1053
|
33 |
|
return true; |
1054
|
|
|
|
1055
|
|
|
// Make sure permission is a valid array |
1056
|
10 |
|
if (!is_array($permission)) |
1057
|
10 |
|
$permission = array($permission); |
1058
|
|
|
|
1059
|
|
|
// Are we checking the _current_ board, or some other boards? |
1060
|
10 |
|
if ($boards === null) |
1061
|
10 |
|
{ |
1062
|
10 |
|
if (empty($user_info['permissions'])) |
1063
|
10 |
|
return false; |
1064
|
|
|
|
1065
|
|
|
// Check if they can do it, you aren't allowed, by default. |
1066
|
5 |
|
return count(array_intersect($permission, $user_info['permissions'])) !== 0 ? true : false; |
1067
|
|
|
} |
1068
|
|
|
|
1069
|
|
|
if (!is_array($boards)) |
1070
|
|
|
$boards = array($boards); |
1071
|
|
|
|
1072
|
|
|
if (empty($user_info['groups'])) |
1073
|
|
|
return false; |
1074
|
|
|
|
1075
|
|
|
$request = $db->query('', ' |
1076
|
|
|
SELECT MIN(bp.add_deny) AS add_deny |
1077
|
|
|
FROM {db_prefix}boards AS b |
1078
|
|
|
INNER JOIN {db_prefix}board_permissions AS bp ON (bp.id_profile = b.id_profile) |
1079
|
|
|
LEFT JOIN {db_prefix}moderators AS mods ON (mods.id_board = b.id_board AND mods.id_member = {int:current_member}) |
1080
|
|
|
WHERE b.id_board IN ({array_int:board_list}) |
1081
|
|
|
AND bp.id_group IN ({array_int:group_list}, {int:moderator_group}) |
1082
|
|
|
AND bp.permission IN ({array_string:permission_list}) |
1083
|
|
|
AND (mods.id_member IS NOT NULL OR bp.id_group != {int:moderator_group}) |
1084
|
|
|
GROUP BY b.id_board', |
1085
|
|
|
array( |
1086
|
|
|
'current_member' => $user_info['id'], |
1087
|
|
|
'board_list' => $boards, |
1088
|
|
|
'group_list' => $user_info['groups'], |
1089
|
|
|
'moderator_group' => 3, |
1090
|
|
|
'permission_list' => $permission, |
1091
|
|
|
) |
1092
|
|
|
); |
1093
|
|
|
|
1094
|
|
|
// Make sure they can do it on all of the boards. |
1095
|
|
|
if ($db->num_rows($request) != count($boards)) |
1096
|
|
|
return false; |
1097
|
|
|
|
1098
|
|
|
$result = true; |
1099
|
|
|
while ($row = $db->fetch_assoc($request)) |
1100
|
|
|
$result &= !empty($row['add_deny']); |
1101
|
|
|
$db->free_result($request); |
1102
|
|
|
|
1103
|
|
|
// If the query returned 1, they can do it... otherwise, they can't. |
1104
|
|
|
return $result; |
1105
|
|
|
} |
1106
|
|
|
|
1107
|
|
|
/** |
1108
|
|
|
* This function returns fatal error if the user doesn't have the respective permission. |
1109
|
|
|
* |
1110
|
|
|
* What it does: |
1111
|
|
|
* |
1112
|
|
|
* - Uses allowedTo() to check if the user is allowed to do permission. |
1113
|
|
|
* - Checks the passed boards or current board for the permission. |
1114
|
|
|
* - If they are not, it loads the Errors language file and shows an error using $txt['cannot_' . $permission]. |
1115
|
|
|
* - If they are a guest and cannot do it, this calls is_not_guest(). |
1116
|
|
|
* |
1117
|
|
|
* @param string[]|string $permission array of or single string, of permissions to check |
1118
|
|
|
* @param int[]|null $boards = null |
1119
|
|
|
* |
1120
|
|
|
* @throws Elk_Exception |
1121
|
|
|
*/ |
1122
|
|
|
function isAllowedTo($permission, $boards = null) |
1123
|
|
|
{ |
1124
|
16 |
|
global $user_info, $txt; |
1125
|
|
|
|
1126
|
|
|
static $heavy_permissions = array( |
1127
|
|
|
'admin_forum', |
1128
|
|
|
'manage_attachments', |
1129
|
|
|
'manage_smileys', |
1130
|
|
|
'manage_boards', |
1131
|
|
|
'edit_news', |
1132
|
|
|
'moderate_forum', |
1133
|
|
|
'manage_bans', |
1134
|
|
|
'manage_membergroups', |
1135
|
|
|
'manage_permissions', |
1136
|
16 |
|
); |
1137
|
|
|
|
1138
|
|
|
// Make it an array, even if a string was passed. |
1139
|
16 |
|
$permission = is_array($permission) ? $permission : array($permission); |
1140
|
|
|
|
1141
|
|
|
// Check the permission and return an error... |
1142
|
16 |
|
if (!allowedTo($permission, $boards)) |
1143
|
16 |
|
{ |
1144
|
|
|
// Pick the last array entry as the permission shown as the error. |
1145
|
|
|
$error_permission = array_shift($permission); |
1146
|
|
|
|
1147
|
|
|
// If they are a guest, show a login. (because the error might be gone if they do!) |
1148
|
|
|
if ($user_info['is_guest']) |
1149
|
|
|
{ |
1150
|
|
|
loadLanguage('Errors'); |
1151
|
|
|
is_not_guest($txt['cannot_' . $error_permission]); |
1152
|
|
|
} |
1153
|
|
|
|
1154
|
|
|
// Clear the action because they aren't really doing that! |
1155
|
|
|
$_GET['action'] = ''; |
1156
|
|
|
$_GET['board'] = ''; |
1157
|
|
|
$_GET['topic'] = ''; |
1158
|
|
|
writeLog(true); |
1159
|
|
|
|
1160
|
|
|
throw new Elk_Exception('cannot_' . $error_permission, false); |
1161
|
|
|
} |
1162
|
|
|
|
1163
|
|
|
// If you're doing something on behalf of some "heavy" permissions, validate your session. |
1164
|
|
|
// (take out the heavy permissions, and if you can't do anything but those, you need a validated session.) |
1165
|
16 |
|
if (!allowedTo(array_diff($permission, $heavy_permissions), $boards)) |
1166
|
16 |
|
validateSession(); |
1167
|
16 |
|
} |
1168
|
|
|
|
1169
|
|
|
/** |
1170
|
|
|
* Return the boards a user has a certain (board) permission on. (array(0) if all.) |
1171
|
|
|
* |
1172
|
|
|
* What it does: |
1173
|
|
|
* |
1174
|
|
|
* - Returns a list of boards on which the user is allowed to do the specified permission. |
1175
|
|
|
* - Returns an array with only a 0 in it if the user has permission to do this on every board. |
1176
|
|
|
* - Returns an empty array if he or she cannot do this on any board. |
1177
|
|
|
* - If check_access is true will also make sure the group has proper access to that board. |
1178
|
|
|
* |
1179
|
|
|
* @param string[]|string $permissions array of permission names to check access against |
1180
|
|
|
* @param bool $check_access = true |
1181
|
|
|
* @param bool $simple = true |
1182
|
|
|
*/ |
1183
|
|
|
function boardsAllowedTo($permissions, $check_access = true, $simple = true) |
1184
|
|
|
{ |
1185
|
1 |
|
global $user_info; |
1186
|
|
|
|
1187
|
1 |
|
$db = database(); |
1188
|
|
|
|
1189
|
|
|
// Arrays are nice, most of the time. |
1190
|
1 |
|
if (!is_array($permissions)) |
1191
|
1 |
|
$permissions = array($permissions); |
1192
|
|
|
|
1193
|
|
|
/* |
1194
|
|
|
* Set $simple to true to use this function in compatibility mode |
1195
|
|
|
* Otherwise, the resultant array becomes split into the multiple |
1196
|
|
|
* permissions that were passed. Other than that, it's just the normal |
1197
|
|
|
* state of play that you're used to. |
1198
|
|
|
*/ |
1199
|
|
|
|
1200
|
|
|
// I am the master, the master of the universe! |
1201
|
1 |
|
if ($user_info['is_admin']) |
1202
|
1 |
|
{ |
1203
|
|
|
if ($simple) |
1204
|
1 |
|
return array(0); |
1205
|
|
|
else |
1206
|
|
|
{ |
1207
|
|
|
$boards = array(); |
1208
|
|
|
foreach ($permissions as $permission) |
1209
|
|
|
$boards[$permission] = array(0); |
1210
|
|
|
|
1211
|
|
|
return $boards; |
1212
|
|
|
} |
1213
|
|
|
} |
1214
|
|
|
|
1215
|
|
|
// All groups the user is in except 'moderator'. |
1216
|
|
|
$groups = array_diff($user_info['groups'], array(3)); |
1217
|
|
|
|
1218
|
|
|
$request = $db->query('', ' |
1219
|
|
|
SELECT b.id_board, bp.add_deny' . ($simple ? '' : ', bp.permission') . ' |
1220
|
|
|
FROM {db_prefix}board_permissions AS bp |
1221
|
|
|
INNER JOIN {db_prefix}boards AS b ON (b.id_profile = bp.id_profile) |
1222
|
|
|
LEFT JOIN {db_prefix}moderators AS mods ON (mods.id_board = b.id_board AND mods.id_member = {int:current_member}) |
1223
|
|
|
WHERE bp.id_group IN ({array_int:group_list}, {int:moderator_group}) |
1224
|
|
|
AND bp.permission IN ({array_string:permissions}) |
1225
|
|
|
AND (mods.id_member IS NOT NULL OR bp.id_group != {int:moderator_group})' . |
1226
|
|
|
($check_access ? ' AND {query_see_board}' : ''), |
1227
|
|
|
array( |
1228
|
|
|
'current_member' => $user_info['id'], |
1229
|
|
|
'group_list' => $groups, |
1230
|
|
|
'moderator_group' => 3, |
1231
|
|
|
'permissions' => $permissions, |
1232
|
|
|
) |
1233
|
|
|
); |
1234
|
|
|
$boards = array(); |
1235
|
|
|
$deny_boards = array(); |
1236
|
|
|
while ($row = $db->fetch_assoc($request)) |
1237
|
|
|
{ |
1238
|
|
|
if ($simple) |
1239
|
|
|
{ |
1240
|
|
|
if (empty($row['add_deny'])) |
1241
|
|
|
$deny_boards[] = $row['id_board']; |
1242
|
|
|
else |
1243
|
|
|
$boards[] = $row['id_board']; |
1244
|
|
|
} |
1245
|
|
|
else |
1246
|
|
|
{ |
1247
|
|
|
if (empty($row['add_deny'])) |
1248
|
|
|
$deny_boards[$row['permission']][] = $row['id_board']; |
1249
|
|
|
else |
1250
|
|
|
$boards[$row['permission']][] = $row['id_board']; |
1251
|
|
|
} |
1252
|
|
|
} |
1253
|
|
|
$db->free_result($request); |
1254
|
|
|
|
1255
|
|
|
if ($simple) |
1256
|
|
|
$boards = array_unique(array_values(array_diff($boards, $deny_boards))); |
1257
|
|
|
else |
1258
|
|
|
{ |
1259
|
|
|
foreach ($permissions as $permission) |
1260
|
|
|
{ |
1261
|
|
|
// Never had it to start with |
1262
|
|
|
if (empty($boards[$permission])) |
1263
|
|
|
$boards[$permission] = array(); |
1264
|
|
|
else |
1265
|
|
|
{ |
1266
|
|
|
// Or it may have been removed |
1267
|
|
|
$deny_boards[$permission] = isset($deny_boards[$permission]) ? $deny_boards[$permission] : array(); |
1268
|
|
|
$boards[$permission] = array_unique(array_values(array_diff($boards[$permission], $deny_boards[$permission]))); |
1269
|
|
|
} |
1270
|
|
|
} |
1271
|
|
|
} |
1272
|
|
|
|
1273
|
|
|
return $boards; |
1274
|
|
|
} |
1275
|
|
|
|
1276
|
|
|
/** |
1277
|
|
|
* Returns whether an email address should be shown and how. |
1278
|
|
|
* |
1279
|
|
|
* What it does: |
1280
|
|
|
* |
1281
|
|
|
* Possible outcomes are: |
1282
|
|
|
* - 'yes': show the full email address |
1283
|
|
|
* - 'yes_permission_override': show the full email address, either you |
1284
|
|
|
* are a moderator or it's your own email address. |
1285
|
|
|
* - 'no_through_forum': don't show the email address, but do allow |
1286
|
|
|
* things to be mailed using the built-in forum mailer. |
1287
|
|
|
* - 'no': keep the email address hidden. |
1288
|
|
|
* |
1289
|
|
|
* @param bool $userProfile_hideEmail |
1290
|
|
|
* @param int $userProfile_id |
1291
|
|
|
* |
1292
|
|
|
* @return string (yes, yes_permission_override, no_through_forum, no) |
1293
|
|
|
*/ |
1294
|
|
|
function showEmailAddress($userProfile_hideEmail, $userProfile_id) |
1295
|
|
|
{ |
1296
|
1 |
|
global $user_info; |
1297
|
|
|
|
1298
|
|
|
// Should this user's email address be shown? |
1299
|
|
|
// If you're guest: no. |
1300
|
|
|
// If the user is post-banned: no. |
1301
|
|
|
// If it's your own profile and you've not set your address hidden: yes_permission_override. |
1302
|
|
|
// If you're a moderator with sufficient permissions: yes_permission_override. |
1303
|
|
|
// If the user has set their profile to do not email me: no. |
1304
|
|
|
// Otherwise: no_through_forum. (don't show it but allow emailing the member) |
1305
|
|
|
|
1306
|
1 |
|
if ($user_info['is_guest'] || isset($_SESSION['ban']['cannot_post'])) |
1307
|
1 |
|
return 'no'; |
1308
|
1 |
|
elseif ((!$user_info['is_guest'] && $user_info['id'] == $userProfile_id && !$userProfile_hideEmail)) |
1309
|
1 |
|
return 'yes_permission_override'; |
1310
|
|
|
elseif (allowedTo('moderate_forum')) |
1311
|
|
|
return 'yes_permission_override'; |
1312
|
|
|
elseif ($userProfile_hideEmail) |
1313
|
|
|
return 'no'; |
1314
|
|
|
else |
1315
|
|
|
return 'no_through_forum'; |
1316
|
|
|
} |
1317
|
|
|
|
1318
|
|
|
/** |
1319
|
|
|
* This function attempts to protect from carrying out specific actions repeatedly. |
1320
|
|
|
* |
1321
|
|
|
* What it does: |
1322
|
|
|
* |
1323
|
|
|
* - Checks if a user is trying specific actions faster than a given minimum wait threshold. |
1324
|
|
|
* - The time taken depends on error_type - generally uses the modSetting. |
1325
|
|
|
* - Generates a fatal message when triggered, suspending execution. |
1326
|
|
|
* |
1327
|
|
|
* @event integrate_spam_protection Allows to update action wait timeOverrides |
1328
|
|
|
* @param string $error_type used also as a $txt index. (not an actual string.) |
1329
|
|
|
* @param boolean $fatal is the spam check a fatal error on failure |
1330
|
|
|
* |
1331
|
|
|
* @return bool|int|mixed |
1332
|
|
|
* @throws Elk_Exception |
1333
|
|
|
*/ |
1334
|
|
|
function spamProtection($error_type, $fatal = true) |
1335
|
|
|
{ |
1336
|
|
|
global $modSettings, $user_info; |
1337
|
|
|
|
1338
|
|
|
$db = database(); |
1339
|
|
|
|
1340
|
|
|
// Certain types take less/more time. |
1341
|
|
|
$timeOverrides = array( |
1342
|
|
|
'login' => 2, |
1343
|
|
|
'register' => 2, |
1344
|
|
|
'remind' => 30, |
1345
|
|
|
'contact' => 30, |
1346
|
|
|
'sendtopic' => $modSettings['spamWaitTime'] * 4, |
1347
|
|
|
'sendmail' => $modSettings['spamWaitTime'] * 5, |
1348
|
|
|
'reporttm' => $modSettings['spamWaitTime'] * 4, |
1349
|
|
|
'search' => !empty($modSettings['search_floodcontrol_time']) ? $modSettings['search_floodcontrol_time'] : 1, |
1350
|
|
|
); |
1351
|
|
|
call_integration_hook('integrate_spam_protection', array(&$timeOverrides)); |
1352
|
|
|
|
1353
|
|
|
// Moderators are free... |
1354
|
|
|
if (!allowedTo('moderate_board')) |
1355
|
|
|
$timeLimit = isset($timeOverrides[$error_type]) ? $timeOverrides[$error_type] : $modSettings['spamWaitTime']; |
1356
|
|
|
else |
1357
|
|
|
$timeLimit = 2; |
1358
|
|
|
|
1359
|
|
|
// Delete old entries... |
1360
|
|
|
$db->query('', ' |
1361
|
|
|
DELETE FROM {db_prefix}log_floodcontrol |
1362
|
|
|
WHERE log_time < {int:log_time} |
1363
|
|
|
AND log_type = {string:log_type}', |
1364
|
|
|
array( |
1365
|
|
|
'log_time' => time() - $timeLimit, |
1366
|
|
|
'log_type' => $error_type, |
1367
|
|
|
) |
1368
|
|
|
); |
1369
|
|
|
|
1370
|
|
|
// Add a new entry, deleting the old if necessary. |
1371
|
|
|
$db->insert('replace', |
1372
|
|
|
'{db_prefix}log_floodcontrol', |
1373
|
|
|
array('ip' => 'string-16', 'log_time' => 'int', 'log_type' => 'string'), |
1374
|
|
|
array($user_info['ip'], time(), $error_type), |
1375
|
|
|
array('ip', 'log_type') |
1376
|
|
|
); |
1377
|
|
|
|
1378
|
|
|
// If affected is 0 or 2, it was there already. |
1379
|
|
|
if ($db->affected_rows() != 1) |
1380
|
|
|
{ |
1381
|
|
|
// Spammer! You only have to wait a *few* seconds! |
1382
|
|
|
if ($fatal) |
1383
|
|
|
{ |
1384
|
|
|
throw new Elk_Exception($error_type . '_WaitTime_broken', false, array($timeLimit)); |
1385
|
|
|
} |
1386
|
|
|
else |
1387
|
|
|
return $timeLimit; |
1388
|
|
|
} |
1389
|
|
|
|
1390
|
|
|
// They haven't posted within the limit. |
1391
|
|
|
return false; |
1392
|
|
|
} |
1393
|
|
|
|
1394
|
|
|
/** |
1395
|
|
|
* A generic function to create a pair of index.php and .htaccess files in a directory |
1396
|
|
|
* |
1397
|
|
|
* @param string $path the (absolute) directory path |
1398
|
|
|
* @param boolean $allow_localhost if access should be allowed to localhost |
1399
|
|
|
* @param string $files (optional, default '*') parameter for the Files tag |
1400
|
|
|
* |
1401
|
|
|
* @return string|boolean on success error string if anything fails |
1402
|
|
|
*/ |
1403
|
|
|
function secureDirectory($path, $allow_localhost = false, $files = '*') |
1404
|
|
|
{ |
1405
|
|
|
if (empty($path)) |
1406
|
|
|
return 'empty_path'; |
1407
|
|
|
|
1408
|
|
|
if (!is_writable($path)) |
1409
|
|
|
return 'path_not_writable'; |
1410
|
|
|
|
1411
|
|
|
$directoryname = basename($path); |
1412
|
|
|
|
1413
|
|
|
$errors = array(); |
1414
|
|
|
$close = empty($allow_localhost) ? ' |
1415
|
|
|
</Files>' : ' |
1416
|
|
|
Allow from localhost |
1417
|
|
|
</Files> |
1418
|
|
|
|
1419
|
|
|
RemoveHandler .php .php3 .phtml .cgi .fcgi .pl .fpl .shtml'; |
1420
|
|
|
|
1421
|
|
View Code Duplication |
if (file_exists($path . '/.htaccess')) |
1422
|
|
|
$errors[] = 'htaccess_exists'; |
1423
|
|
|
else |
1424
|
|
|
{ |
1425
|
|
|
$fh = @fopen($path . '/.htaccess', 'w'); |
1426
|
|
|
if ($fh) |
1427
|
|
|
{ |
1428
|
|
|
fwrite($fh, '<Files ' . $files . '> |
1429
|
|
|
Order Deny,Allow |
1430
|
|
|
Deny from all' . $close); |
1431
|
|
|
fclose($fh); |
1432
|
|
|
} |
1433
|
|
|
$errors[] = 'htaccess_cannot_create_file'; |
1434
|
|
|
} |
1435
|
|
|
|
1436
|
|
View Code Duplication |
if (file_exists($path . '/index.php')) |
1437
|
|
|
$errors[] = 'index-php_exists'; |
1438
|
|
|
else |
1439
|
|
|
{ |
1440
|
|
|
$fh = @fopen($path . '/index.php', 'w'); |
1441
|
|
|
if ($fh) |
1442
|
|
|
{ |
1443
|
|
|
fwrite($fh, '<?php |
1444
|
|
|
|
1445
|
|
|
/** |
1446
|
|
|
* This file is here solely to protect your ' . $directoryname . ' directory. |
1447
|
|
|
*/ |
1448
|
|
|
|
1449
|
|
|
// Look for Settings.php.... |
1450
|
|
|
if (file_exists(dirname(dirname(__FILE__)) . \'/Settings.php\')) |
1451
|
|
|
{ |
1452
|
|
|
// Found it! |
1453
|
|
|
require(dirname(dirname(__FILE__)) . \'/Settings.php\'); |
1454
|
|
|
header(\'Location: \' . $boardurl); |
1455
|
|
|
} |
1456
|
|
|
// Can\'t find it... just forget it. |
1457
|
|
|
else |
1458
|
|
|
exit;'); |
1459
|
|
|
fclose($fh); |
1460
|
|
|
} |
1461
|
|
|
$errors[] = 'index-php_cannot_create_file'; |
1462
|
|
|
} |
1463
|
|
|
|
1464
|
|
|
if (!empty($errors)) |
1465
|
|
|
return $errors; |
1466
|
|
|
else |
1467
|
|
|
return true; |
1468
|
|
|
} |
1469
|
|
|
|
1470
|
|
|
/** |
1471
|
|
|
* Helper function that puts together a ban query for a given ip |
1472
|
|
|
* |
1473
|
|
|
* What it does: |
1474
|
|
|
* |
1475
|
|
|
* - Builds the query for ipv6, ipv4 or 255.255.255.255 depending on whats supplied |
1476
|
|
|
* |
1477
|
|
|
* @param string $fullip An IP address either IPv6 or not |
1478
|
|
|
* |
1479
|
|
|
* @return string A SQL condition |
1480
|
|
|
*/ |
1481
|
|
|
function constructBanQueryIP($fullip) |
1482
|
|
|
{ |
1483
|
|
|
// First attempt a IPv6 address. |
1484
|
1 |
|
if (isValidIPv6($fullip)) |
1485
|
1 |
|
{ |
1486
|
|
|
$ip_parts = convertIPv6toInts($fullip); |
1487
|
|
|
|
1488
|
|
|
$ban_query = '((' . $ip_parts[0] . ' BETWEEN bi.ip_low1 AND bi.ip_high1) |
1489
|
|
|
AND (' . $ip_parts[1] . ' BETWEEN bi.ip_low2 AND bi.ip_high2) |
1490
|
|
|
AND (' . $ip_parts[2] . ' BETWEEN bi.ip_low3 AND bi.ip_high3) |
1491
|
|
|
AND (' . $ip_parts[3] . ' BETWEEN bi.ip_low4 AND bi.ip_high4) |
1492
|
|
|
AND (' . $ip_parts[4] . ' BETWEEN bi.ip_low5 AND bi.ip_high5) |
1493
|
|
|
AND (' . $ip_parts[5] . ' BETWEEN bi.ip_low6 AND bi.ip_high6) |
1494
|
|
|
AND (' . $ip_parts[6] . ' BETWEEN bi.ip_low7 AND bi.ip_high7) |
1495
|
|
|
AND (' . $ip_parts[7] . ' BETWEEN bi.ip_low8 AND bi.ip_high8))'; |
1496
|
|
|
} |
1497
|
|
|
// Check if we have a valid IPv4 address. |
1498
|
1 |
|
elseif (preg_match('/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/', $fullip, $ip_parts) == 1) |
1499
|
1 |
|
$ban_query = '((' . $ip_parts[1] . ' BETWEEN bi.ip_low1 AND bi.ip_high1) |
1500
|
1 |
|
AND (' . $ip_parts[2] . ' BETWEEN bi.ip_low2 AND bi.ip_high2) |
1501
|
1 |
|
AND (' . $ip_parts[3] . ' BETWEEN bi.ip_low3 AND bi.ip_high3) |
1502
|
1 |
|
AND (' . $ip_parts[4] . ' BETWEEN bi.ip_low4 AND bi.ip_high4))'; |
1503
|
|
|
// We use '255.255.255.255' for 'unknown' since it's not valid anyway. |
1504
|
|
|
else |
1505
|
|
|
$ban_query = '(bi.ip_low1 = 255 AND bi.ip_high1 = 255 |
1506
|
|
|
AND bi.ip_low2 = 255 AND bi.ip_high2 = 255 |
1507
|
|
|
AND bi.ip_low3 = 255 AND bi.ip_high3 = 255 |
1508
|
|
|
AND bi.ip_low4 = 255 AND bi.ip_high4 = 255)'; |
1509
|
|
|
|
1510
|
1 |
|
return $ban_query; |
1511
|
|
|
} |
1512
|
|
|
|
1513
|
|
|
/** |
1514
|
|
|
* Decide if we are going to enable bad behavior scanning for this user |
1515
|
|
|
* |
1516
|
|
|
* What it does: |
1517
|
|
|
* |
1518
|
|
|
* - Admins and Moderators get a free pass |
1519
|
|
|
* - Optionally existing users with post counts over a limit are bypassed |
1520
|
|
|
* - Others get a humane frisking |
1521
|
|
|
*/ |
1522
|
|
|
function loadBadBehavior() |
1523
|
|
|
{ |
1524
|
|
|
global $modSettings, $user_info, $bb2_results; |
1525
|
|
|
|
1526
|
|
|
// Bad Behavior Enabled? |
1527
|
|
|
if (!empty($modSettings['badbehavior_enabled'])) |
1528
|
|
|
{ |
1529
|
|
|
require_once(EXTDIR . '/bad-behavior/badbehavior-plugin.php'); |
1530
|
|
|
$bb_run = true; |
1531
|
|
|
|
1532
|
|
|
// We may want to give some folks a hallway pass |
1533
|
|
|
if (!$user_info['is_guest']) |
1534
|
|
|
{ |
1535
|
|
|
if (!empty($user_info['is_moderator']) || !empty($user_info['is_admin'])) |
1536
|
|
|
$bb_run = false; |
1537
|
|
|
elseif (!empty($modSettings['badbehavior_postcount_wl']) && $modSettings['badbehavior_postcount_wl'] < 0) |
1538
|
|
|
$bb_run = false; |
1539
|
|
View Code Duplication |
elseif (!empty($modSettings['badbehavior_postcount_wl']) && $modSettings['badbehavior_postcount_wl'] > 0 && ($user_info['posts'] > $modSettings['badbehavior_postcount_wl'])) |
1540
|
|
|
$bb_run = false; |
1541
|
|
|
} |
1542
|
|
|
|
1543
|
|
|
// Put on the sanitary gloves, its time for a patdown ! |
1544
|
|
|
if ($bb_run === true) |
1545
|
|
|
{ |
1546
|
|
|
$bb2_results = bb2_start(bb2_read_settings()); |
1547
|
|
|
addInlineJavascript(bb2_insert_head()); |
1548
|
|
|
} |
1549
|
|
|
} |
1550
|
|
|
} |
1551
|
|
|
|
1552
|
|
|
/** |
1553
|
|
|
* This protects against brute force attacks on a member's password. |
1554
|
|
|
* |
1555
|
|
|
* What it does: |
1556
|
|
|
* |
1557
|
|
|
* - Importantly, even if the password was right we DON'T TELL THEM! |
1558
|
|
|
* - Allows 5 attempts every 10 seconds |
1559
|
|
|
* |
1560
|
|
|
* @param int $id_member |
1561
|
|
|
* @param string|bool $password_flood_value = false or string joined on |'s |
1562
|
|
|
* @param boolean $was_correct = false |
1563
|
|
|
* |
1564
|
|
|
* @throws Elk_Exception no_access |
1565
|
|
|
*/ |
1566
|
|
|
function validatePasswordFlood($id_member, $password_flood_value = false, $was_correct = false) |
1567
|
|
|
{ |
1568
|
|
|
global $cookiename; |
1569
|
|
|
|
1570
|
|
|
// As this is only brute protection, we allow 5 attempts every 10 seconds. |
1571
|
|
|
|
1572
|
|
|
// Destroy any session or cookie data about this member, as they validated wrong. |
1573
|
|
|
require_once(SUBSDIR . '/Auth.subs.php'); |
1574
|
|
|
setLoginCookie(-3600, 0); |
1575
|
|
|
|
1576
|
|
|
if (isset($_SESSION['login_' . $cookiename])) |
1577
|
|
|
unset($_SESSION['login_' . $cookiename]); |
1578
|
|
|
|
1579
|
|
|
// We need a member! |
1580
|
|
|
if (!$id_member) |
1581
|
|
|
{ |
1582
|
|
|
// Redirect back! |
1583
|
|
|
redirectexit(); |
1584
|
|
|
|
1585
|
|
|
// Probably not needed, but still make sure... |
1586
|
|
|
throw new Elk_Exception('no_access', false); |
1587
|
|
|
} |
1588
|
|
|
|
1589
|
|
|
// Let's just initialize to something (and 0 is better than nothing) |
1590
|
|
|
$time_stamp = 0; |
1591
|
|
|
$number_tries = 0; |
1592
|
|
|
|
1593
|
|
|
// Right, have we got a flood value? |
1594
|
|
|
if ($password_flood_value !== false) |
1595
|
|
|
@list ($time_stamp, $number_tries) = explode('|', $password_flood_value); |
1596
|
|
|
|
1597
|
|
|
// Timestamp invalid or non-existent? |
1598
|
|
|
if (empty($number_tries) || $time_stamp < (time() - 10)) |
1599
|
|
|
{ |
1600
|
|
|
// If it wasn't *that* long ago, don't give them another five goes. |
1601
|
|
|
$number_tries = !empty($number_tries) && $time_stamp < (time() - 20) ? 2 : $number_tries; |
1602
|
|
|
$time_stamp = time(); |
1603
|
|
|
} |
1604
|
|
|
|
1605
|
|
|
$number_tries++; |
1606
|
|
|
|
1607
|
|
|
// Broken the law? |
1608
|
|
|
if ($number_tries > 5) |
1609
|
|
|
throw new Elk_Exception('login_threshold_brute_fail', 'critical'); |
1610
|
|
|
|
1611
|
|
|
// Otherwise set the members data. If they correct on their first attempt then we actually clear it, otherwise we set it! |
1612
|
|
|
require_once(SUBSDIR . '/Members.subs.php'); |
1613
|
|
|
updateMemberData($id_member, array('passwd_flood' => $was_correct && $number_tries == 1 ? '' : $time_stamp . '|' . $number_tries)); |
1614
|
|
|
} |
1615
|
|
|
|
1616
|
|
|
/** |
1617
|
|
|
* This sets the X-Frame-Options header. |
1618
|
|
|
* |
1619
|
|
|
* @param string|null $override the frame option, defaults to deny. |
1620
|
|
|
*/ |
1621
|
|
|
function frameOptionsHeader($override = null) |
1622
|
|
|
{ |
1623
|
|
|
global $modSettings; |
1624
|
|
|
|
1625
|
|
|
$option = 'SAMEORIGIN'; |
1626
|
|
|
|
1627
|
|
|
if (is_null($override) && !empty($modSettings['frame_security'])) |
1628
|
|
|
$option = $modSettings['frame_security']; |
1629
|
|
|
elseif (in_array($override, array('SAMEORIGIN', 'DENY'))) |
1630
|
|
|
$option = $override; |
1631
|
|
|
|
1632
|
|
|
// Don't bother setting the header if we have disabled it. |
1633
|
|
|
if ($option == 'DISABLE') |
1634
|
|
|
return; |
1635
|
|
|
|
1636
|
|
|
// Finally set it. |
1637
|
|
|
header('X-Frame-Options: ' . $option); |
1638
|
|
|
} |
1639
|
|
|
|
1640
|
|
|
/** |
1641
|
|
|
* This adds additional security headers that may prevent browsers from doing something they should not |
1642
|
|
|
* |
1643
|
|
|
* What it does: |
1644
|
|
|
* |
1645
|
|
|
* - X-XSS-Protection header - This header enables the Cross-site scripting (XSS) filter |
1646
|
|
|
* built into most recent web browsers. It's usually enabled by default, so the role of this |
1647
|
|
|
* header is to re-enable the filter for this particular website if it was disabled by the user. |
1648
|
|
|
* - X-Content-Type-Options header - It prevents the browser from doing MIME-type sniffing, |
1649
|
|
|
* only IE and Chrome are honouring this header. This reduces exposure to drive-by download attacks |
1650
|
|
|
* and sites serving user uploaded content that could be treated as executable or dynamic HTML files. |
1651
|
|
|
* |
1652
|
|
|
* @param boolean|null $override |
1653
|
|
|
*/ |
1654
|
|
|
function securityOptionsHeader($override = null) |
1655
|
|
|
{ |
1656
|
|
|
if ($override !== true) |
1657
|
|
|
{ |
1658
|
|
|
header('X-XSS-Protection: 1'); |
1659
|
|
|
header('X-Content-Type-Options: nosniff'); |
1660
|
|
|
} |
1661
|
|
|
} |
1662
|
|
|
|
1663
|
|
|
/** |
1664
|
|
|
* Stop some browsers pre fetching activity to reduce server load |
1665
|
|
|
*/ |
1666
|
|
|
function stop_prefetching() |
1667
|
|
|
{ |
1668
|
2 |
|
if (isset($_SERVER['HTTP_X_PURPOSE']) && in_array($_SERVER['HTTP_X_PURPOSE'], array('preview', 'instant')) |
1669
|
2 |
|
|| (isset($_SERVER['HTTP_X_MOZ']) && $_SERVER['HTTP_X_MOZ'] === 'prefetch')) |
1670
|
2 |
|
{ |
1671
|
|
|
@ob_end_clean(); |
1672
|
|
|
header('HTTP/1.1 403 Forbidden'); |
1673
|
|
|
die; |
|
|
|
|
1674
|
|
|
} |
1675
|
2 |
|
} |
1676
|
|
|
|
1677
|
|
|
/** |
1678
|
|
|
* Check if the admin's session is active |
1679
|
|
|
* |
1680
|
|
|
* @return bool |
1681
|
|
|
*/ |
1682
|
|
|
function isAdminSessionActive() |
1683
|
|
|
{ |
1684
|
|
|
global $modSettings; |
1685
|
|
|
|
1686
|
|
|
return empty($modSettings['securityDisable']) && (isset($_SESSION['admin_time']) && $_SESSION['admin_time'] + ($modSettings['admin_session_lifetime'] * 60) > time()); |
1687
|
|
|
} |
1688
|
|
|
|
1689
|
|
|
/** |
1690
|
|
|
* Check if security files exist |
1691
|
|
|
* |
1692
|
|
|
* If files are found, populate $context['security_controls_files']: |
1693
|
|
|
* * 'title' - $txt['security_risk'] |
1694
|
|
|
* * 'errors' - An array of strings with the key being the filename and the value an error with the filename in it |
1695
|
|
|
* |
1696
|
|
|
* @event integrate_security_files Allows to add / modify to security files array |
1697
|
|
|
* |
1698
|
|
|
* @return bool |
1699
|
|
|
*/ |
1700
|
|
|
function checkSecurityFiles() |
1701
|
|
|
{ |
1702
|
|
|
global $txt, $context; |
1703
|
|
|
|
1704
|
|
|
$has_files = false; |
1705
|
|
|
|
1706
|
|
|
$securityFiles = array('install.php', 'upgrade.php', 'convert.php', 'repair_paths.php', 'repair_settings.php', 'Settings.php~', 'Settings_bak.php~'); |
1707
|
|
|
call_integration_hook('integrate_security_files', array(&$securityFiles)); |
1708
|
|
|
|
1709
|
|
|
foreach ($securityFiles as $securityFile) |
1710
|
|
|
{ |
1711
|
|
|
if (file_exists(BOARDDIR . '/' . $securityFile)) |
1712
|
|
|
{ |
1713
|
|
|
$has_files = true; |
1714
|
|
|
|
1715
|
|
|
$context['security_controls_files']['title'] = $txt['security_risk']; |
1716
|
|
|
$context['security_controls_files']['errors'][$securityFile] = sprintf($txt['not_removed'], $securityFile); |
1717
|
|
|
|
1718
|
|
|
if ($securityFile == 'Settings.php~' || $securityFile == 'Settings_bak.php~') |
1719
|
|
|
{ |
1720
|
|
|
$context['security_controls_files']['errors'][$securityFile] .= '<span class="smalltext">' . sprintf($txt['not_removed_extra'], $securityFile, substr($securityFile, 0, -1)) . '</span>'; |
1721
|
|
|
} |
1722
|
|
|
} |
1723
|
|
|
} |
1724
|
|
|
|
1725
|
|
|
return $has_files; |
1726
|
|
|
} |
1727
|
|
|
|
1728
|
|
|
/** |
1729
|
|
|
* The login URL should not redirect to certain areas (attachments, js actions, etc) |
1730
|
|
|
* this function does these checks and return if the URL is valid or not. |
1731
|
|
|
* |
1732
|
|
|
* @param string $url - The URL to validate |
1733
|
|
|
* @param bool $match_board - If true tries to match board|topic in the URL as well |
1734
|
|
|
* @return bool |
1735
|
|
|
*/ |
1736
|
|
|
function validLoginUrl($url, $match_board = false) |
1737
|
|
|
{ |
1738
|
|
|
if (empty($url)) |
1739
|
|
|
{ |
1740
|
|
|
return false; |
1741
|
|
|
} |
1742
|
|
|
|
1743
|
|
|
if (substr($url, 0, 7) !== 'http://' && substr($url, 0, 8) !== 'https://') |
1744
|
|
|
{ |
1745
|
|
|
return false; |
1746
|
|
|
} |
1747
|
|
|
|
1748
|
|
|
$invalid_strings = array('dlattach' => '~(board|topic)[=,]~', 'jslocale' => '', 'login' => ''); |
1749
|
|
|
call_integration_hook('integrate_validLoginUrl', array(&$invalid_strings)); |
1750
|
|
|
|
1751
|
|
|
foreach ($invalid_strings as $invalid_string => $valid_match) |
1752
|
|
|
{ |
1753
|
|
|
if (strpos($url, $invalid_string) !== false || ($match_board === true && !empty($valid_match) && preg_match($valid_match, $url) == 0)) |
1754
|
|
|
{ |
1755
|
|
|
return false; |
1756
|
|
|
} |
1757
|
|
|
} |
1758
|
|
|
|
1759
|
|
|
return true; |
1760
|
|
|
} |
An exit expression should only be used in rare cases. For example, if you write a short command line script.
In most cases however, using an
exit
expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.