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 | * Simple Machines Forum (SMF) |
||||
8 | * |
||||
9 | * @package SMF |
||||
10 | * @author Simple Machines https://www.simplemachines.org |
||||
11 | * @copyright 2022 Simple Machines and individual contributors |
||||
12 | * @license https://www.simplemachines.org/about/smf/license.php BSD |
||||
13 | * |
||||
14 | * @version 2.1.0 |
||||
15 | */ |
||||
16 | |||||
17 | if (!defined('SMF')) |
||||
18 | die('No direct access...'); |
||||
19 | |||||
20 | /** |
||||
21 | * Check if the user is who he/she says he is |
||||
22 | * Makes sure the user is who they claim to be by requiring a password to be typed in every hour. |
||||
23 | * Is turned on and off by the securityDisable setting. |
||||
24 | * Uses the adminLogin() function of Subs-Auth.php if they need to login, which saves all request (post and get) data. |
||||
25 | * |
||||
26 | * @param string $type What type of session this is |
||||
27 | * @param string $force When true, require a password even if we normally wouldn't |
||||
28 | * @return void|string Returns 'session_verify_fail' if verification failed |
||||
29 | */ |
||||
30 | function validateSession($type = 'admin', $force = false) |
||||
31 | { |
||||
32 | global $modSettings, $sourcedir, $user_info; |
||||
33 | |||||
34 | // We don't care if the option is off, because Guests should NEVER get past here. |
||||
35 | is_not_guest(); |
||||
36 | |||||
37 | // Validate what type of session check this is. |
||||
38 | $types = array(); |
||||
39 | call_integration_hook('integrate_validateSession', array(&$types)); |
||||
40 | $type = in_array($type, $types) || $type == 'moderate' ? $type : 'admin'; |
||||
41 | |||||
42 | // If we're using XML give an additional ten minutes grace as an admin can't log on in XML mode. |
||||
43 | $refreshTime = isset($_GET['xml']) ? 4200 : 3600; |
||||
44 | |||||
45 | if (empty($force)) |
||||
46 | { |
||||
47 | // Is the security option off? |
||||
48 | if (!empty($modSettings['securityDisable' . ($type != 'admin' ? '_' . $type : '')])) |
||||
49 | return; |
||||
50 | |||||
51 | // Or are they already logged in?, Moderator or admin session is need for this area |
||||
52 | if ((!empty($_SESSION[$type . '_time']) && $_SESSION[$type . '_time'] + $refreshTime >= time()) || (!empty($_SESSION['admin_time']) && $_SESSION['admin_time'] + $refreshTime >= time())) |
||||
53 | return; |
||||
54 | } |
||||
55 | |||||
56 | require_once($sourcedir . '/Subs-Auth.php'); |
||||
57 | |||||
58 | // Posting the password... check it. |
||||
59 | if (isset($_POST[$type . '_pass'])) |
||||
60 | { |
||||
61 | // Check to ensure we're forcing SSL for authentication |
||||
62 | if (!empty($modSettings['force_ssl']) && empty($maintenance) && !httpsOn()) |
||||
0 ignored issues
–
show
Comprehensibility
Best Practice
introduced
by
![]() |
|||||
63 | fatal_lang_error('login_ssl_required'); |
||||
64 | |||||
65 | checkSession(); |
||||
66 | |||||
67 | $good_password = in_array(true, call_integration_hook('integrate_verify_password', array($user_info['username'], $_POST[$type . '_pass'], false)), true); |
||||
68 | |||||
69 | // Password correct? |
||||
70 | if ($good_password || hash_verify_password($user_info['username'], $_POST[$type . '_pass'], $user_info['passwd'])) |
||||
71 | { |
||||
72 | $_SESSION[$type . '_time'] = time(); |
||||
73 | unset($_SESSION['request_referer']); |
||||
74 | return; |
||||
75 | } |
||||
76 | } |
||||
77 | |||||
78 | // Better be sure to remember the real referer |
||||
79 | if (empty($_SESSION['request_referer'])) |
||||
80 | $_SESSION['request_referer'] = isset($_SERVER['HTTP_REFERER']) ? @parse_iri($_SERVER['HTTP_REFERER']) : array(); |
||||
81 | elseif (empty($_POST)) |
||||
82 | unset($_SESSION['request_referer']); |
||||
83 | |||||
84 | // Need to type in a password for that, man. |
||||
85 | if (!isset($_GET['xml'])) |
||||
86 | adminLogin($type); |
||||
87 | else |
||||
88 | return 'session_verify_fail'; |
||||
89 | } |
||||
90 | |||||
91 | /** |
||||
92 | * Require a user who is logged in. (not a guest.) |
||||
93 | * Checks if the user is currently a guest, and if so asks them to login with a message telling them why. |
||||
94 | * Message is what to tell them when asking them to login. |
||||
95 | * |
||||
96 | * @param string $message The message to display to the guest |
||||
97 | */ |
||||
98 | function is_not_guest($message = '') |
||||
99 | { |
||||
100 | global $user_info, $txt, $context, $scripturl, $modSettings; |
||||
101 | |||||
102 | // Luckily, this person isn't a guest. |
||||
103 | if (!$user_info['is_guest']) |
||||
104 | return; |
||||
105 | |||||
106 | // Log what they were trying to do didn't work) |
||||
107 | if (!empty($modSettings['who_enabled'])) |
||||
108 | $_GET['error'] = 'guest_login'; |
||||
109 | writeLog(true); |
||||
110 | |||||
111 | // Just die. |
||||
112 | if (isset($_REQUEST['xml'])) |
||||
113 | obExit(false); |
||||
114 | |||||
115 | // Attempt to detect if they came from dlattach. |
||||
116 | if (SMF != 'SSI' && empty($context['theme_loaded'])) |
||||
0 ignored issues
–
show
|
|||||
117 | loadTheme(); |
||||
118 | |||||
119 | // Never redirect to an attachment |
||||
120 | if (strpos($_SERVER['REQUEST_URL'], 'dlattach') === false) |
||||
121 | $_SESSION['login_url'] = $_SERVER['REQUEST_URL']; |
||||
122 | |||||
123 | // Load the Login template and language file. |
||||
124 | loadLanguage('Login'); |
||||
125 | |||||
126 | // Apparently we're not in a position to handle this now. Let's go to a safer location for now. |
||||
127 | if (empty($context['template_layers'])) |
||||
128 | { |
||||
129 | $_SESSION['login_url'] = $scripturl . '?' . $_SERVER['QUERY_STRING']; |
||||
130 | redirectexit('action=login'); |
||||
131 | } |
||||
132 | else |
||||
133 | { |
||||
134 | loadTemplate('Login'); |
||||
135 | $context['sub_template'] = 'kick_guest'; |
||||
136 | $context['robot_no_index'] = true; |
||||
137 | } |
||||
138 | |||||
139 | // Use the kick_guest sub template... |
||||
140 | $context['kick_message'] = $message; |
||||
141 | $context['page_title'] = $txt['login']; |
||||
142 | |||||
143 | obExit(); |
||||
144 | |||||
145 | // We should never get to this point, but if we did we wouldn't know the user isn't a guest. |
||||
146 | trigger_error('No direct access...', E_USER_ERROR); |
||||
147 | } |
||||
148 | |||||
149 | /** |
||||
150 | * Do banning related stuff. (ie. disallow access....) |
||||
151 | * Checks if the user is banned, and if so dies with an error. |
||||
152 | * Caches this information for optimization purposes. |
||||
153 | * |
||||
154 | * @param bool $forceCheck Whether to force a recheck |
||||
155 | */ |
||||
156 | function is_not_banned($forceCheck = false) |
||||
157 | { |
||||
158 | global $txt, $modSettings, $context, $user_info; |
||||
159 | global $sourcedir, $cookiename, $user_settings, $smcFunc; |
||||
160 | |||||
161 | // You cannot be banned if you are an admin - doesn't help if you log out. |
||||
162 | if ($user_info['is_admin']) |
||||
163 | return; |
||||
164 | |||||
165 | // Only check the ban every so often. (to reduce load.) |
||||
166 | 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'])) |
||||
167 | { |
||||
168 | // Innocent until proven guilty. (but we know you are! :P) |
||||
169 | $_SESSION['ban'] = array( |
||||
170 | 'last_checked' => time(), |
||||
171 | 'id_member' => $user_info['id'], |
||||
172 | 'ip' => $user_info['ip'], |
||||
173 | 'ip2' => $user_info['ip2'], |
||||
174 | 'email' => $user_info['email'], |
||||
175 | ); |
||||
176 | |||||
177 | $ban_query = array(); |
||||
178 | $ban_query_vars = array('current_time' => time()); |
||||
179 | $flag_is_activated = false; |
||||
180 | |||||
181 | // Check both IP addresses. |
||||
182 | foreach (array('ip', 'ip2') as $ip_number) |
||||
183 | { |
||||
184 | if ($ip_number == 'ip2' && $user_info['ip2'] == $user_info['ip']) |
||||
185 | continue; |
||||
186 | $ban_query[] = ' {inet:' . $ip_number . '} BETWEEN bi.ip_low and bi.ip_high'; |
||||
187 | $ban_query_vars[$ip_number] = $user_info[$ip_number]; |
||||
188 | // IP was valid, maybe there's also a hostname... |
||||
189 | if (empty($modSettings['disableHostnameLookup']) && $user_info[$ip_number] != 'unknown') |
||||
190 | { |
||||
191 | $hostname = host_from_ip($user_info[$ip_number]); |
||||
192 | if (strlen($hostname) > 0) |
||||
193 | { |
||||
194 | $ban_query[] = '({string:hostname' . $ip_number . '} LIKE bi.hostname)'; |
||||
195 | $ban_query_vars['hostname' . $ip_number] = $hostname; |
||||
196 | } |
||||
197 | } |
||||
198 | } |
||||
199 | |||||
200 | // Is their email address banned? |
||||
201 | if (strlen($user_info['email']) != 0) |
||||
202 | { |
||||
203 | $ban_query[] = '({string:email} LIKE bi.email_address)'; |
||||
204 | $ban_query_vars['email'] = $user_info['email']; |
||||
205 | } |
||||
206 | |||||
207 | // How about this user? |
||||
208 | if (!$user_info['is_guest'] && !empty($user_info['id'])) |
||||
209 | { |
||||
210 | $ban_query[] = 'bi.id_member = {int:id_member}'; |
||||
211 | $ban_query_vars['id_member'] = $user_info['id']; |
||||
212 | } |
||||
213 | |||||
214 | // Check the ban, if there's information. |
||||
215 | if (!empty($ban_query)) |
||||
216 | { |
||||
217 | $restrictions = array( |
||||
218 | 'cannot_access', |
||||
219 | 'cannot_login', |
||||
220 | 'cannot_post', |
||||
221 | 'cannot_register', |
||||
222 | ); |
||||
223 | $request = $smcFunc['db_query']('', ' |
||||
224 | SELECT bi.id_ban, bi.email_address, bi.id_member, bg.cannot_access, bg.cannot_register, |
||||
225 | bg.cannot_post, bg.cannot_login, bg.reason, COALESCE(bg.expire_time, 0) AS expire_time |
||||
226 | FROM {db_prefix}ban_items AS bi |
||||
227 | 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})) |
||||
228 | WHERE |
||||
229 | (' . implode(' OR ', $ban_query) . ')', |
||||
230 | $ban_query_vars |
||||
231 | ); |
||||
232 | // Store every type of ban that applies to you in your session. |
||||
233 | while ($row = $smcFunc['db_fetch_assoc']($request)) |
||||
234 | { |
||||
235 | foreach ($restrictions as $restriction) |
||||
236 | if (!empty($row[$restriction])) |
||||
237 | { |
||||
238 | $_SESSION['ban'][$restriction]['reason'] = $row['reason']; |
||||
239 | $_SESSION['ban'][$restriction]['ids'][] = $row['id_ban']; |
||||
240 | if (!isset($_SESSION['ban']['expire_time']) || ($_SESSION['ban']['expire_time'] != 0 && ($row['expire_time'] == 0 || $row['expire_time'] > $_SESSION['ban']['expire_time']))) |
||||
241 | $_SESSION['ban']['expire_time'] = $row['expire_time']; |
||||
242 | |||||
243 | if (!$user_info['is_guest'] && $restriction == 'cannot_access' && ($row['id_member'] == $user_info['id'] || $row['email_address'] == $user_info['email'])) |
||||
244 | $flag_is_activated = true; |
||||
245 | } |
||||
246 | } |
||||
247 | $smcFunc['db_free_result']($request); |
||||
248 | } |
||||
249 | |||||
250 | // Mark the cannot_access and cannot_post bans as being 'hit'. |
||||
251 | if (isset($_SESSION['ban']['cannot_access']) || isset($_SESSION['ban']['cannot_post']) || isset($_SESSION['ban']['cannot_login'])) |
||||
252 | 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())); |
||||
253 | |||||
254 | // If for whatever reason the is_activated flag seems wrong, do a little work to clear it up. |
||||
255 | if ($user_info['id'] && (($user_settings['is_activated'] >= 10 && !$flag_is_activated) |
||||
256 | || ($user_settings['is_activated'] < 10 && $flag_is_activated))) |
||||
257 | { |
||||
258 | require_once($sourcedir . '/ManageBans.php'); |
||||
259 | updateBanMembers(); |
||||
260 | } |
||||
261 | } |
||||
262 | |||||
263 | // Hey, I know you! You're ehm... |
||||
264 | if (!isset($_SESSION['ban']['cannot_access']) && !empty($_COOKIE[$cookiename . '_'])) |
||||
265 | { |
||||
266 | $bans = explode(',', $_COOKIE[$cookiename . '_']); |
||||
267 | foreach ($bans as $key => $value) |
||||
268 | $bans[$key] = (int) $value; |
||||
269 | $request = $smcFunc['db_query']('', ' |
||||
270 | SELECT bi.id_ban, bg.reason, COALESCE(bg.expire_time, 0) AS expire_time |
||||
271 | FROM {db_prefix}ban_items AS bi |
||||
272 | INNER JOIN {db_prefix}ban_groups AS bg ON (bg.id_ban_group = bi.id_ban_group) |
||||
273 | WHERE bi.id_ban IN ({array_int:ban_list}) |
||||
274 | AND (bg.expire_time IS NULL OR bg.expire_time > {int:current_time}) |
||||
275 | AND bg.cannot_access = {int:cannot_access} |
||||
276 | LIMIT {int:limit}', |
||||
277 | array( |
||||
278 | 'cannot_access' => 1, |
||||
279 | 'ban_list' => $bans, |
||||
280 | 'current_time' => time(), |
||||
281 | 'limit' => count($bans), |
||||
282 | ) |
||||
283 | ); |
||||
284 | while ($row = $smcFunc['db_fetch_assoc']($request)) |
||||
285 | { |
||||
286 | $_SESSION['ban']['cannot_access']['ids'][] = $row['id_ban']; |
||||
287 | $_SESSION['ban']['cannot_access']['reason'] = $row['reason']; |
||||
288 | $_SESSION['ban']['expire_time'] = $row['expire_time']; |
||||
289 | } |
||||
290 | $smcFunc['db_free_result']($request); |
||||
291 | |||||
292 | // My mistake. Next time better. |
||||
293 | if (!isset($_SESSION['ban']['cannot_access'])) |
||||
294 | { |
||||
295 | require_once($sourcedir . '/Subs-Auth.php'); |
||||
296 | $cookie_url = url_parts(!empty($modSettings['localCookies']), !empty($modSettings['globalCookies'])); |
||||
297 | smf_setcookie($cookiename . '_', '', time() - 3600, $cookie_url[1], $cookie_url[0], false, false); |
||||
298 | } |
||||
299 | } |
||||
300 | |||||
301 | // If you're fully banned, it's end of the story for you. |
||||
302 | if (isset($_SESSION['ban']['cannot_access'])) |
||||
303 | { |
||||
304 | // We don't wanna see you! |
||||
305 | if (!$user_info['is_guest']) |
||||
306 | $smcFunc['db_query']('', ' |
||||
307 | DELETE FROM {db_prefix}log_online |
||||
308 | WHERE id_member = {int:current_member}', |
||||
309 | array( |
||||
310 | 'current_member' => $user_info['id'], |
||||
311 | ) |
||||
312 | ); |
||||
313 | |||||
314 | // 'Log' the user out. Can't have any funny business... (save the name!) |
||||
315 | $old_name = isset($user_info['name']) && $user_info['name'] != '' ? $user_info['name'] : $txt['guest_title']; |
||||
316 | $user_info['name'] = ''; |
||||
317 | $user_info['username'] = ''; |
||||
318 | $user_info['is_guest'] = true; |
||||
319 | $user_info['is_admin'] = false; |
||||
320 | $user_info['permissions'] = array(); |
||||
321 | $user_info['id'] = 0; |
||||
322 | $context['user'] = array( |
||||
323 | 'id' => 0, |
||||
324 | 'username' => '', |
||||
325 | 'name' => $txt['guest_title'], |
||||
326 | 'is_guest' => true, |
||||
327 | 'is_logged' => false, |
||||
328 | 'is_admin' => false, |
||||
329 | 'is_mod' => false, |
||||
330 | 'can_mod' => false, |
||||
331 | 'language' => $user_info['language'], |
||||
332 | ); |
||||
333 | |||||
334 | // A goodbye present. |
||||
335 | require_once($sourcedir . '/Subs-Auth.php'); |
||||
336 | require_once($sourcedir . '/LogInOut.php'); |
||||
337 | $cookie_url = url_parts(!empty($modSettings['localCookies']), !empty($modSettings['globalCookies'])); |
||||
338 | smf_setcookie($cookiename . '_', implode(',', $_SESSION['ban']['cannot_access']['ids']), time() + 3153600, $cookie_url[1], $cookie_url[0], false, false); |
||||
339 | |||||
340 | // Don't scare anyone, now. |
||||
341 | $_GET['action'] = ''; |
||||
342 | $_GET['board'] = ''; |
||||
343 | $_GET['topic'] = ''; |
||||
344 | writeLog(true); |
||||
345 | Logout(true, false); |
||||
346 | |||||
347 | // You banned, sucka! |
||||
348 | fatal_error(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'], timeformat($_SESSION['ban']['expire_time'], false)) : $txt['your_ban_expires_never']), false, 403); |
||||
349 | |||||
350 | // If we get here, something's gone wrong.... but let's try anyway. |
||||
351 | trigger_error('No direct access...', E_USER_ERROR); |
||||
352 | } |
||||
353 | // You're not allowed to log in but yet you are. Let's fix that. |
||||
354 | elseif (isset($_SESSION['ban']['cannot_login']) && !$user_info['is_guest']) |
||||
355 | { |
||||
356 | // We don't wanna see you! |
||||
357 | $smcFunc['db_query']('', ' |
||||
358 | DELETE FROM {db_prefix}log_online |
||||
359 | WHERE id_member = {int:current_member}', |
||||
360 | array( |
||||
361 | 'current_member' => $user_info['id'], |
||||
362 | ) |
||||
363 | ); |
||||
364 | |||||
365 | // 'Log' the user out. Can't have any funny business... (save the name!) |
||||
366 | $old_name = isset($user_info['name']) && $user_info['name'] != '' ? $user_info['name'] : $txt['guest_title']; |
||||
367 | $user_info['name'] = ''; |
||||
368 | $user_info['username'] = ''; |
||||
369 | $user_info['is_guest'] = true; |
||||
370 | $user_info['is_admin'] = false; |
||||
371 | $user_info['permissions'] = array(); |
||||
372 | $user_info['id'] = 0; |
||||
373 | $context['user'] = array( |
||||
374 | 'id' => 0, |
||||
375 | 'username' => '', |
||||
376 | 'name' => $txt['guest_title'], |
||||
377 | 'is_guest' => true, |
||||
378 | 'is_logged' => false, |
||||
379 | 'is_admin' => false, |
||||
380 | 'is_mod' => false, |
||||
381 | 'can_mod' => false, |
||||
382 | 'language' => $user_info['language'], |
||||
383 | ); |
||||
384 | |||||
385 | // SMF's Wipe 'n Clean(r) erases all traces. |
||||
386 | $_GET['action'] = ''; |
||||
387 | $_GET['board'] = ''; |
||||
388 | $_GET['topic'] = ''; |
||||
389 | writeLog(true); |
||||
390 | |||||
391 | require_once($sourcedir . '/LogInOut.php'); |
||||
392 | Logout(true, false); |
||||
393 | |||||
394 | fatal_error(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'], timeformat($_SESSION['ban']['expire_time'], false)) : $txt['your_ban_expires_never']) . '<br>' . $txt['ban_continue_browse'], false, 403); |
||||
395 | } |
||||
396 | |||||
397 | // Fix up the banning permissions. |
||||
398 | if (isset($user_info['permissions'])) |
||||
399 | banPermissions(); |
||||
400 | } |
||||
401 | |||||
402 | /** |
||||
403 | * Fix permissions according to ban status. |
||||
404 | * Applies any states of banning by removing permissions the user cannot have. |
||||
405 | */ |
||||
406 | function banPermissions() |
||||
407 | { |
||||
408 | global $user_info, $sourcedir, $modSettings, $context; |
||||
409 | |||||
410 | // Somehow they got here, at least take away all permissions... |
||||
411 | if (isset($_SESSION['ban']['cannot_access'])) |
||||
412 | $user_info['permissions'] = array(); |
||||
413 | // Okay, well, you can watch, but don't touch a thing. |
||||
414 | elseif (isset($_SESSION['ban']['cannot_post']) || (!empty($modSettings['warning_mute']) && $modSettings['warning_mute'] <= $user_info['warning'])) |
||||
415 | { |
||||
416 | $denied_permissions = array( |
||||
417 | 'pm_send', |
||||
418 | 'calendar_post', 'calendar_edit_own', 'calendar_edit_any', |
||||
419 | 'poll_post', |
||||
420 | 'poll_add_own', 'poll_add_any', |
||||
421 | 'poll_edit_own', 'poll_edit_any', |
||||
422 | 'poll_lock_own', 'poll_lock_any', |
||||
423 | 'poll_remove_own', 'poll_remove_any', |
||||
424 | 'manage_attachments', 'manage_smileys', 'manage_boards', 'admin_forum', 'manage_permissions', |
||||
425 | 'moderate_forum', 'manage_membergroups', 'manage_bans', 'send_mail', 'edit_news', |
||||
426 | 'profile_identity_any', 'profile_extra_any', 'profile_title_any', |
||||
427 | 'profile_forum_any', 'profile_other_any', 'profile_signature_any', |
||||
428 | 'post_new', 'post_reply_own', 'post_reply_any', |
||||
429 | 'delete_own', 'delete_any', 'delete_replies', |
||||
430 | 'make_sticky', |
||||
431 | 'merge_any', 'split_any', |
||||
432 | 'modify_own', 'modify_any', 'modify_replies', |
||||
433 | 'move_any', |
||||
434 | 'lock_own', 'lock_any', |
||||
435 | 'remove_own', 'remove_any', |
||||
436 | 'post_unapproved_topics', 'post_unapproved_replies_own', 'post_unapproved_replies_any', |
||||
437 | ); |
||||
438 | call_integration_hook('integrate_post_ban_permissions', array(&$denied_permissions)); |
||||
439 | $user_info['permissions'] = array_diff($user_info['permissions'], $denied_permissions); |
||||
440 | } |
||||
441 | // Are they absolutely under moderation? |
||||
442 | elseif (!empty($modSettings['warning_moderate']) && $modSettings['warning_moderate'] <= $user_info['warning']) |
||||
443 | { |
||||
444 | // Work out what permissions should change... |
||||
445 | $permission_change = array( |
||||
446 | 'post_new' => 'post_unapproved_topics', |
||||
447 | 'post_reply_own' => 'post_unapproved_replies_own', |
||||
448 | 'post_reply_any' => 'post_unapproved_replies_any', |
||||
449 | 'post_attachment' => 'post_unapproved_attachments', |
||||
450 | ); |
||||
451 | call_integration_hook('integrate_warn_permissions', array(&$permission_change)); |
||||
452 | foreach ($permission_change as $old => $new) |
||||
453 | { |
||||
454 | if (!in_array($old, $user_info['permissions'])) |
||||
455 | unset($permission_change[$old]); |
||||
456 | else |
||||
457 | $user_info['permissions'][] = $new; |
||||
458 | } |
||||
459 | $user_info['permissions'] = array_diff($user_info['permissions'], array_keys($permission_change)); |
||||
460 | } |
||||
461 | |||||
462 | // @todo Find a better place to call this? Needs to be after permissions loaded! |
||||
463 | // Finally, some bits we cache in the session because it saves queries. |
||||
464 | if (isset($_SESSION['mc']) && $_SESSION['mc']['time'] > $modSettings['settings_updated'] && $_SESSION['mc']['id'] == $user_info['id']) |
||||
465 | $user_info['mod_cache'] = $_SESSION['mc']; |
||||
466 | else |
||||
467 | { |
||||
468 | require_once($sourcedir . '/Subs-Auth.php'); |
||||
469 | rebuildModCache(); |
||||
470 | } |
||||
471 | |||||
472 | // Now that we have the mod cache taken care of lets setup a cache for the number of mod reports still open |
||||
473 | if (isset($_SESSION['rc']['reports']) && isset($_SESSION['rc']['member_reports']) && $_SESSION['rc']['time'] > $modSettings['last_mod_report_action'] && $_SESSION['rc']['id'] == $user_info['id']) |
||||
474 | { |
||||
475 | $context['open_mod_reports'] = $_SESSION['rc']['reports']; |
||||
476 | $context['open_member_reports'] = $_SESSION['rc']['member_reports']; |
||||
477 | } |
||||
478 | elseif ($_SESSION['mc']['bq'] != '0=1') |
||||
479 | { |
||||
480 | require_once($sourcedir . '/Subs-ReportedContent.php'); |
||||
481 | $context['open_mod_reports'] = recountOpenReports('posts'); |
||||
482 | $context['open_member_reports'] = recountOpenReports('members'); |
||||
483 | } |
||||
484 | else |
||||
485 | { |
||||
486 | $context['open_mod_reports'] = 0; |
||||
487 | $context['open_member_reports'] = 0; |
||||
488 | } |
||||
489 | } |
||||
490 | |||||
491 | /** |
||||
492 | * Log a ban in the database. |
||||
493 | * Log the current user in the ban logs. |
||||
494 | * Increment the hit counters for the specified ban ID's (if any.) |
||||
495 | * |
||||
496 | * @param array $ban_ids The IDs of the bans |
||||
497 | * @param string $email The email address associated with the user that triggered this hit |
||||
498 | */ |
||||
499 | function log_ban($ban_ids = array(), $email = null) |
||||
500 | { |
||||
501 | global $user_info, $smcFunc; |
||||
502 | |||||
503 | // Don't log web accelerators, it's very confusing... |
||||
504 | if (isset($_SERVER['HTTP_X_MOZ']) && $_SERVER['HTTP_X_MOZ'] == 'prefetch') |
||||
505 | return; |
||||
506 | |||||
507 | $smcFunc['db_insert']('', |
||||
508 | '{db_prefix}log_banned', |
||||
509 | array('id_member' => 'int', 'ip' => 'inet', 'email' => 'string', 'log_time' => 'int'), |
||||
510 | array($user_info['id'], $user_info['ip'], ($email === null ? ($user_info['is_guest'] ? '' : $user_info['email']) : $email), time()), |
||||
511 | array('id_ban_log') |
||||
512 | ); |
||||
513 | |||||
514 | // One extra point for these bans. |
||||
515 | if (!empty($ban_ids)) |
||||
516 | $smcFunc['db_query']('', ' |
||||
517 | UPDATE {db_prefix}ban_items |
||||
518 | SET hits = hits + 1 |
||||
519 | WHERE id_ban IN ({array_int:ban_ids})', |
||||
520 | array( |
||||
521 | 'ban_ids' => $ban_ids, |
||||
522 | ) |
||||
523 | ); |
||||
524 | } |
||||
525 | |||||
526 | /** |
||||
527 | * Checks if a given email address might be banned. |
||||
528 | * Check if a given email is banned. |
||||
529 | * Performs an immediate ban if the turns turns out positive. |
||||
530 | * |
||||
531 | * @param string $email The email to check |
||||
532 | * @param string $restriction What type of restriction (cannot_post, cannot_register, etc.) |
||||
533 | * @param string $error The error message to display if they are indeed banned |
||||
534 | */ |
||||
535 | function isBannedEmail($email, $restriction, $error) |
||||
536 | { |
||||
537 | global $txt, $smcFunc; |
||||
538 | |||||
539 | // Can't ban an empty email |
||||
540 | if (empty($email) || trim($email) == '') |
||||
541 | return; |
||||
542 | |||||
543 | // Let's start with the bans based on your IP/hostname/memberID... |
||||
544 | $ban_ids = isset($_SESSION['ban'][$restriction]) ? $_SESSION['ban'][$restriction]['ids'] : array(); |
||||
545 | $ban_reason = isset($_SESSION['ban'][$restriction]) ? $_SESSION['ban'][$restriction]['reason'] : ''; |
||||
546 | |||||
547 | // ...and add to that the email address you're trying to register. |
||||
548 | $request = $smcFunc['db_query']('', ' |
||||
549 | SELECT bi.id_ban, bg.' . $restriction . ', bg.cannot_access, bg.reason |
||||
550 | FROM {db_prefix}ban_items AS bi |
||||
551 | INNER JOIN {db_prefix}ban_groups AS bg ON (bg.id_ban_group = bi.id_ban_group) |
||||
552 | WHERE {string:email} LIKE bi.email_address |
||||
553 | AND (bg.' . $restriction . ' = {int:cannot_access} OR bg.cannot_access = {int:cannot_access}) |
||||
554 | AND (bg.expire_time IS NULL OR bg.expire_time >= {int:now})', |
||||
555 | array( |
||||
556 | 'email' => $email, |
||||
557 | 'cannot_access' => 1, |
||||
558 | 'now' => time(), |
||||
559 | ) |
||||
560 | ); |
||||
561 | while ($row = $smcFunc['db_fetch_assoc']($request)) |
||||
562 | { |
||||
563 | if (!empty($row['cannot_access'])) |
||||
564 | { |
||||
565 | $_SESSION['ban']['cannot_access']['ids'][] = $row['id_ban']; |
||||
566 | $_SESSION['ban']['cannot_access']['reason'] = $row['reason']; |
||||
567 | } |
||||
568 | if (!empty($row[$restriction])) |
||||
569 | { |
||||
570 | $ban_ids[] = $row['id_ban']; |
||||
571 | $ban_reason = $row['reason']; |
||||
572 | } |
||||
573 | } |
||||
574 | $smcFunc['db_free_result']($request); |
||||
575 | |||||
576 | // You're in biiig trouble. Banned for the rest of this session! |
||||
577 | if (isset($_SESSION['ban']['cannot_access'])) |
||||
578 | { |
||||
579 | log_ban($_SESSION['ban']['cannot_access']['ids']); |
||||
580 | $_SESSION['ban']['last_checked'] = time(); |
||||
581 | |||||
582 | fatal_error(sprintf($txt['your_ban'], $txt['guest_title']) . $_SESSION['ban']['cannot_access']['reason'], false); |
||||
583 | } |
||||
584 | |||||
585 | if (!empty($ban_ids)) |
||||
586 | { |
||||
587 | // Log this ban for future reference. |
||||
588 | log_ban($ban_ids, $email); |
||||
589 | fatal_error($error . $ban_reason, false); |
||||
590 | } |
||||
591 | } |
||||
592 | |||||
593 | /** |
||||
594 | * Make sure the user's correct session was passed, and they came from here. |
||||
595 | * Checks the current session, verifying that the person is who he or she should be. |
||||
596 | * Also checks the referrer to make sure they didn't get sent here. |
||||
597 | * Depends on the disableCheckUA setting, which is usually missing. |
||||
598 | * Will check GET, POST, or REQUEST depending on the passed type. |
||||
599 | * Also optionally checks the referring action if passed. (note that the referring action must be by GET.) |
||||
600 | * |
||||
601 | * @param string $type The type of check (post, get, request) |
||||
602 | * @param string $from_action The action this is coming from |
||||
603 | * @param bool $is_fatal Whether to die with a fatal error if the check fails |
||||
604 | * @return string The error message if is_fatal is false. |
||||
605 | */ |
||||
606 | function checkSession($type = 'post', $from_action = '', $is_fatal = true) |
||||
607 | { |
||||
608 | global $context, $sc, $modSettings, $boardurl; |
||||
609 | |||||
610 | // Is it in as $_POST['sc']? |
||||
611 | if ($type == 'post') |
||||
612 | { |
||||
613 | $check = isset($_POST[$_SESSION['session_var']]) ? $_POST[$_SESSION['session_var']] : (empty($modSettings['strictSessionCheck']) && isset($_POST['sc']) ? $_POST['sc'] : null); |
||||
614 | if ($check !== $sc) |
||||
615 | $error = 'session_timeout'; |
||||
616 | } |
||||
617 | |||||
618 | // How about $_GET['sesc']? |
||||
619 | elseif ($type == 'get') |
||||
620 | { |
||||
621 | $check = isset($_GET[$_SESSION['session_var']]) ? $_GET[$_SESSION['session_var']] : (empty($modSettings['strictSessionCheck']) && isset($_GET['sesc']) ? $_GET['sesc'] : null); |
||||
622 | if ($check !== $sc) |
||||
623 | $error = 'session_verify_fail'; |
||||
624 | } |
||||
625 | |||||
626 | // Or can it be in either? |
||||
627 | elseif ($type == 'request') |
||||
628 | { |
||||
629 | $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))); |
||||
630 | |||||
631 | if ($check !== $sc) |
||||
632 | $error = 'session_verify_fail'; |
||||
633 | } |
||||
634 | |||||
635 | // Verify that they aren't changing user agents on us - that could be bad. |
||||
636 | if ((!isset($_SESSION['USER_AGENT']) || $_SESSION['USER_AGENT'] != $_SERVER['HTTP_USER_AGENT']) && empty($modSettings['disableCheckUA'])) |
||||
637 | $error = 'session_verify_fail'; |
||||
638 | |||||
639 | // Make sure a page with session check requirement is not being prefetched. |
||||
640 | if (isset($_SERVER['HTTP_X_MOZ']) && $_SERVER['HTTP_X_MOZ'] == 'prefetch') |
||||
641 | { |
||||
642 | ob_end_clean(); |
||||
643 | send_http_status(403); |
||||
644 | die; |
||||
0 ignored issues
–
show
|
|||||
645 | } |
||||
646 | |||||
647 | // Check the referring site - it should be the same server at least! |
||||
648 | if (isset($_SESSION['request_referer'])) |
||||
649 | $referrer = $_SESSION['request_referer']; |
||||
650 | else |
||||
651 | $referrer = isset($_SERVER['HTTP_REFERER']) ? @parse_url($_SERVER['HTTP_REFERER']) : array(); |
||||
652 | |||||
653 | // Check the refer but if we have CORS enabled and it came from a trusted source, we can skip this check. |
||||
654 | if (!empty($referrer['host']) && (empty($modSettings['allow_cors']) || empty($context['valid_cors_found']) || !in_array($context['valid_cors_found'], array('same', 'subdomain')))) |
||||
655 | { |
||||
656 | if (strpos($_SERVER['HTTP_HOST'], ':') !== false) |
||||
657 | $real_host = substr($_SERVER['HTTP_HOST'], 0, strpos($_SERVER['HTTP_HOST'], ':')); |
||||
658 | else |
||||
659 | $real_host = $_SERVER['HTTP_HOST']; |
||||
660 | |||||
661 | $parsed_url = parse_iri($boardurl); |
||||
662 | |||||
663 | // Are global cookies on? If so, let's check them ;). |
||||
664 | if (!empty($modSettings['globalCookies'])) |
||||
665 | { |
||||
666 | if (preg_match('~(?:[^\.]+\.)?([^\.]{3,}\..+)\z~i', $parsed_url['host'], $parts) == 1) |
||||
667 | $parsed_url['host'] = $parts[1]; |
||||
668 | |||||
669 | if (preg_match('~(?:[^\.]+\.)?([^\.]{3,}\..+)\z~i', $referrer['host'], $parts) == 1) |
||||
670 | $referrer['host'] = $parts[1]; |
||||
671 | |||||
672 | if (preg_match('~(?:[^\.]+\.)?([^\.]{3,}\..+)\z~i', $real_host, $parts) == 1) |
||||
673 | $real_host = $parts[1]; |
||||
674 | } |
||||
675 | |||||
676 | // Okay: referrer must either match parsed_url or real_host. |
||||
677 | if (isset($parsed_url['host']) && strtolower($referrer['host']) != strtolower($parsed_url['host']) && strtolower($referrer['host']) != strtolower($real_host)) |
||||
678 | { |
||||
679 | $error = 'verify_url_fail'; |
||||
680 | $log_error = true; |
||||
681 | } |
||||
682 | } |
||||
683 | |||||
684 | // Well, first of all, if a from_action is specified you'd better have an old_url. |
||||
685 | if (!empty($from_action) && (!isset($_SESSION['old_url']) || preg_match('~[?;&]action=' . $from_action . '([;&]|$)~', $_SESSION['old_url']) == 0)) |
||||
686 | { |
||||
687 | $error = 'verify_url_fail'; |
||||
688 | $log_error = true; |
||||
689 | } |
||||
690 | |||||
691 | if (strtolower($_SERVER['HTTP_USER_AGENT']) == 'hacker') |
||||
692 | fatal_error('Sound the alarm! It\'s a hacker! Close the castle gates!!', false); |
||||
693 | |||||
694 | // Everything is ok, return an empty string. |
||||
695 | if (!isset($error)) |
||||
696 | return ''; |
||||
697 | // A session error occurred, show the error. |
||||
698 | elseif ($is_fatal) |
||||
699 | { |
||||
700 | if (isset($_GET['xml'])) |
||||
701 | { |
||||
702 | ob_end_clean(); |
||||
703 | send_http_status(403, 'Forbidden - Session timeout'); |
||||
704 | die; |
||||
0 ignored issues
–
show
|
|||||
705 | } |
||||
706 | else |
||||
707 | fatal_lang_error($error, isset($log_error) ? 'user' : false); |
||||
708 | } |
||||
709 | // A session error occurred, return the error to the calling function. |
||||
710 | else |
||||
711 | return $error; |
||||
712 | |||||
713 | // We really should never fall through here, for very important reasons. Let's make sure. |
||||
714 | trigger_error('No direct access...', E_USER_ERROR); |
||||
715 | } |
||||
716 | |||||
717 | /** |
||||
718 | * Check if a specific confirm parameter was given. |
||||
719 | * |
||||
720 | * @param string $action The action we want to check against |
||||
721 | * @return bool|string True if the check passed or a token |
||||
722 | */ |
||||
723 | function checkConfirm($action) |
||||
724 | { |
||||
725 | global $modSettings, $smcFunc; |
||||
726 | |||||
727 | if (isset($_GET['confirm']) && isset($_SESSION['confirm_' . $action]) && md5($_GET['confirm'] . $_SERVER['HTTP_USER_AGENT']) == $_SESSION['confirm_' . $action]) |
||||
728 | return true; |
||||
729 | |||||
730 | else |
||||
731 | { |
||||
732 | $token = md5($smcFunc['random_int']() . session_id() . (string) microtime() . $modSettings['rand_seed']); |
||||
733 | $_SESSION['confirm_' . $action] = md5($token . $_SERVER['HTTP_USER_AGENT']); |
||||
734 | |||||
735 | return $token; |
||||
736 | } |
||||
737 | } |
||||
738 | |||||
739 | /** |
||||
740 | * Lets give you a token of our appreciation. |
||||
741 | * |
||||
742 | * @param string $action The action to create the token for |
||||
743 | * @param string $type The type of token ('post', 'get' or 'request') |
||||
744 | * @return array An array containing the name of the token var and the actual token |
||||
745 | */ |
||||
746 | function createToken($action, $type = 'post') |
||||
747 | { |
||||
748 | global $modSettings, $context, $smcFunc; |
||||
749 | |||||
750 | $token = md5($smcFunc['random_int']() . session_id() . (string) microtime() . $modSettings['rand_seed'] . $type); |
||||
751 | $token_var = substr(preg_replace('~^\d+~', '', md5($smcFunc['random_int']() . (string) microtime() . $smcFunc['random_int']())), 0, $smcFunc['random_int'](7, 12)); |
||||
752 | |||||
753 | $_SESSION['token'][$type . '-' . $action] = array($token_var, md5($token . $_SERVER['HTTP_USER_AGENT']), time(), $token); |
||||
754 | |||||
755 | $context[$action . '_token'] = $token; |
||||
756 | $context[$action . '_token_var'] = $token_var; |
||||
757 | |||||
758 | return array($action . '_token_var' => $token_var, $action . '_token' => $token); |
||||
759 | } |
||||
760 | |||||
761 | /** |
||||
762 | * Only patrons with valid tokens can ride this ride. |
||||
763 | * |
||||
764 | * @param string $action The action to validate the token for |
||||
765 | * @param string $type The type of request (get, request, or post) |
||||
766 | * @param bool $reset Whether to reset the token and display an error if validation fails |
||||
767 | * @return bool returns whether the validation was successful |
||||
768 | */ |
||||
769 | function validateToken($action, $type = 'post', $reset = true) |
||||
770 | { |
||||
771 | $type = $type == 'get' || $type == 'request' ? $type : 'post'; |
||||
772 | |||||
773 | // This nasty piece of code validates a token. |
||||
774 | /* |
||||
775 | 1. The token exists in session. |
||||
776 | 2. The {$type} variable should exist. |
||||
777 | 3. We concat the variable we received with the user agent |
||||
778 | 4. Match that result against what is in the session. |
||||
779 | 5. If it matches, success, otherwise we fallout. |
||||
780 | */ |
||||
781 | if (isset($_SESSION['token'][$type . '-' . $action], $GLOBALS['_' . strtoupper($type)][$_SESSION['token'][$type . '-' . $action][0]]) && md5($GLOBALS['_' . strtoupper($type)][$_SESSION['token'][$type . '-' . $action][0]] . $_SERVER['HTTP_USER_AGENT']) === $_SESSION['token'][$type . '-' . $action][1]) |
||||
782 | { |
||||
783 | // Invalidate this token now. |
||||
784 | unset($_SESSION['token'][$type . '-' . $action]); |
||||
785 | |||||
786 | return true; |
||||
787 | } |
||||
788 | |||||
789 | // Patrons with invalid tokens get the boot. |
||||
790 | if ($reset) |
||||
791 | { |
||||
792 | // Might as well do some cleanup on this. |
||||
793 | cleanTokens(); |
||||
794 | |||||
795 | // I'm back baby. |
||||
796 | createToken($action, $type); |
||||
797 | |||||
798 | fatal_lang_error('token_verify_fail', false); |
||||
799 | } |
||||
800 | // Remove this token as its useless |
||||
801 | else |
||||
802 | unset($_SESSION['token'][$type . '-' . $action]); |
||||
803 | |||||
804 | // Randomly check if we should remove some older tokens. |
||||
805 | if (mt_rand(0, 138) == 23) |
||||
806 | cleanTokens(); |
||||
807 | |||||
808 | return false; |
||||
809 | } |
||||
810 | |||||
811 | /** |
||||
812 | * Removes old unused tokens from session |
||||
813 | * defaults to 3 hours before a token is considered expired |
||||
814 | * if $complete = true will remove all tokens |
||||
815 | * |
||||
816 | * @param bool $complete Whether to remove all tokens or only expired ones |
||||
817 | */ |
||||
818 | function cleanTokens($complete = false) |
||||
819 | { |
||||
820 | // We appreciate cleaning up after yourselves. |
||||
821 | if (!isset($_SESSION['token'])) |
||||
822 | return; |
||||
823 | |||||
824 | // Clean up tokens, trying to give enough time still. |
||||
825 | foreach ($_SESSION['token'] as $key => $data) |
||||
826 | if ($data[2] + 10800 < time() || $complete) |
||||
827 | unset($_SESSION['token'][$key]); |
||||
828 | } |
||||
829 | |||||
830 | /** |
||||
831 | * Check whether a form has been submitted twice. |
||||
832 | * Registers a sequence number for a form. |
||||
833 | * Checks whether a submitted sequence number is registered in the current session. |
||||
834 | * Depending on the value of is_fatal shows an error or returns true or false. |
||||
835 | * Frees a sequence number from the stack after it's been checked. |
||||
836 | * Frees a sequence number without checking if action == 'free'. |
||||
837 | * |
||||
838 | * @param string $action The action - can be 'register', 'check' or 'free' |
||||
839 | * @param bool $is_fatal Whether to die with a fatal error |
||||
840 | * @return void|bool If the action isn't check, returns nothing, otherwise returns whether the check was successful |
||||
841 | */ |
||||
842 | function checkSubmitOnce($action, $is_fatal = true) |
||||
843 | { |
||||
844 | global $context, $txt; |
||||
845 | |||||
846 | if (!isset($_SESSION['forms'])) |
||||
847 | $_SESSION['forms'] = array(); |
||||
848 | |||||
849 | // Register a form number and store it in the session stack. (use this on the page that has the form.) |
||||
850 | if ($action == 'register') |
||||
851 | { |
||||
852 | $context['form_sequence_number'] = 0; |
||||
853 | while (empty($context['form_sequence_number']) || in_array($context['form_sequence_number'], $_SESSION['forms'])) |
||||
854 | $context['form_sequence_number'] = mt_rand(1, 16000000); |
||||
855 | } |
||||
856 | // Check whether the submitted number can be found in the session. |
||||
857 | elseif ($action == 'check') |
||||
858 | { |
||||
859 | if (!isset($_REQUEST['seqnum'])) |
||||
860 | return true; |
||||
861 | elseif (!in_array($_REQUEST['seqnum'], $_SESSION['forms'])) |
||||
862 | { |
||||
863 | $_SESSION['forms'][] = (int) $_REQUEST['seqnum']; |
||||
864 | return true; |
||||
865 | } |
||||
866 | elseif ($is_fatal) |
||||
867 | fatal_lang_error('error_form_already_submitted', false); |
||||
868 | else |
||||
869 | return false; |
||||
870 | } |
||||
871 | // Don't check, just free the stack number. |
||||
872 | elseif ($action == 'free' && isset($_REQUEST['seqnum']) && in_array($_REQUEST['seqnum'], $_SESSION['forms'])) |
||||
873 | $_SESSION['forms'] = array_diff($_SESSION['forms'], array($_REQUEST['seqnum'])); |
||||
874 | elseif ($action != 'free') |
||||
875 | { |
||||
876 | loadLanguage('Errors'); |
||||
877 | trigger_error(sprintf($txt['check_submit_once_invalid_action'], $action), E_USER_WARNING); |
||||
878 | } |
||||
879 | } |
||||
880 | |||||
881 | /** |
||||
882 | * Check the user's permissions. |
||||
883 | * checks whether the user is allowed to do permission. (ie. post_new.) |
||||
884 | * If boards is specified, checks those boards instead of the current one. |
||||
885 | * If any is true, will return true if the user has the permission on any of the specified boards |
||||
886 | * Always returns true if the user is an administrator. |
||||
887 | * |
||||
888 | * @param string|array $permission A single permission to check or an array of permissions to check |
||||
889 | * @param int|array $boards The ID of a board or an array of board IDs if we want to check board-level permissions |
||||
890 | * @param bool $any Whether to check for permission on at least one board instead of all boards |
||||
891 | * @return bool Whether the user has the specified permission |
||||
892 | */ |
||||
893 | function allowedTo($permission, $boards = null, $any = false) |
||||
894 | { |
||||
895 | global $user_info, $smcFunc; |
||||
896 | static $perm_cache = array(); |
||||
897 | |||||
898 | // You're always allowed to do nothing. (unless you're a working man, MR. LAZY :P!) |
||||
899 | if (empty($permission)) |
||||
900 | return true; |
||||
901 | |||||
902 | // You're never allowed to do something if your data hasn't been loaded yet! |
||||
903 | if (empty($user_info) || !isset($user_info['permissions'])) |
||||
904 | return false; |
||||
905 | |||||
906 | // Administrators are supermen :P. |
||||
907 | if ($user_info['is_admin']) |
||||
908 | return true; |
||||
909 | |||||
910 | // Let's ensure this is an array. |
||||
911 | $permission = (array) $permission; |
||||
912 | |||||
913 | // This should be a boolean. |
||||
914 | $any = (bool) $any; |
||||
915 | |||||
916 | // Are we checking the _current_ board, or some other boards? |
||||
917 | if ($boards === null) |
||||
918 | { |
||||
919 | $user_permissions = $user_info['permissions']; |
||||
920 | |||||
921 | // Allow temporary overrides for general permissions? |
||||
922 | call_integration_hook('integrate_allowed_to_general', array(&$user_permissions, $permission)); |
||||
923 | |||||
924 | if (count(array_intersect($permission, $user_permissions)) != 0) |
||||
925 | return true; |
||||
926 | // You aren't allowed, by default. |
||||
927 | else |
||||
928 | return false; |
||||
929 | } |
||||
930 | elseif (!is_array($boards)) |
||||
931 | $boards = array($boards); |
||||
932 | |||||
933 | $cache_key = hash('md5', $user_info['id'] . '-' . implode(',', $permission) . '-' . implode(',', $boards) . '-' . (int) $any); |
||||
934 | |||||
935 | if (isset($perm_cache[$cache_key])) |
||||
936 | return $perm_cache[$cache_key]; |
||||
937 | |||||
938 | $request = $smcFunc['db_query']('', ' |
||||
939 | SELECT MIN(bp.add_deny) AS add_deny |
||||
940 | FROM {db_prefix}boards AS b |
||||
941 | INNER JOIN {db_prefix}board_permissions AS bp ON (bp.id_profile = b.id_profile) |
||||
942 | LEFT JOIN {db_prefix}moderators AS mods ON (mods.id_board = b.id_board AND mods.id_member = {int:current_member}) |
||||
943 | LEFT JOIN {db_prefix}moderator_groups AS modgs ON (modgs.id_board = b.id_board AND modgs.id_group IN ({array_int:group_list})) |
||||
944 | WHERE b.id_board IN ({array_int:board_list}) |
||||
945 | AND bp.id_group IN ({array_int:group_list}, {int:moderator_group}) |
||||
946 | AND bp.permission IN ({array_string:permission_list}) |
||||
947 | AND (mods.id_member IS NOT NULL OR modgs.id_group IS NOT NULL OR bp.id_group != {int:moderator_group}) |
||||
948 | GROUP BY b.id_board', |
||||
949 | array( |
||||
950 | 'current_member' => $user_info['id'], |
||||
951 | 'board_list' => $boards, |
||||
952 | 'group_list' => $user_info['groups'], |
||||
953 | 'moderator_group' => 3, |
||||
954 | 'permission_list' => $permission, |
||||
955 | ) |
||||
956 | ); |
||||
957 | |||||
958 | if ($any) |
||||
959 | { |
||||
960 | $result = false; |
||||
961 | while ($row = $smcFunc['db_fetch_assoc']($request)) |
||||
962 | { |
||||
963 | $result = !empty($row['add_deny']); |
||||
964 | if ($result == true) |
||||
0 ignored issues
–
show
|
|||||
965 | break; |
||||
966 | } |
||||
967 | $smcFunc['db_free_result']($request); |
||||
968 | $return = $result; |
||||
969 | } |
||||
970 | |||||
971 | // Make sure they can do it on all of the boards. |
||||
972 | elseif ($smcFunc['db_num_rows']($request) != count($boards)) |
||||
973 | $return = false; |
||||
974 | |||||
975 | else |
||||
976 | { |
||||
977 | $result = true; |
||||
978 | while ($row = $smcFunc['db_fetch_assoc']($request)) |
||||
979 | $result &= !empty($row['add_deny']); |
||||
980 | $smcFunc['db_free_result']($request); |
||||
981 | $return = $result; |
||||
982 | } |
||||
983 | |||||
984 | // Allow temporary overrides for board permissions? |
||||
985 | call_integration_hook('integrate_allowed_to_board', array(&$return, $permission, $boards, $any)); |
||||
986 | |||||
987 | $perm_cache[$cache_key] = $return; |
||||
988 | |||||
989 | // If the query returned 1, they can do it... otherwise, they can't. |
||||
990 | return $return; |
||||
991 | } |
||||
992 | |||||
993 | /** |
||||
994 | * Fatal error if they cannot. |
||||
995 | * Uses allowedTo() to check if the user is allowed to do permission. |
||||
996 | * Checks the passed boards or current board for the permission. |
||||
997 | * If $any is true, the user only needs permission on at least one of the boards to pass |
||||
998 | * If they are not, it loads the Errors language file and shows an error using $txt['cannot_' . $permission]. |
||||
999 | * If they are a guest and cannot do it, this calls is_not_guest(). |
||||
1000 | * |
||||
1001 | * @param string|array $permission A single permission to check or an array of permissions to check |
||||
1002 | * @param int|array $boards The ID of a single board or an array of board IDs if we're checking board-level permissions (null otherwise) |
||||
1003 | * @param bool $any Whether to check for permission on at least one board instead of all boards |
||||
1004 | */ |
||||
1005 | function isAllowedTo($permission, $boards = null, $any = false) |
||||
1006 | { |
||||
1007 | global $user_info, $txt; |
||||
1008 | |||||
1009 | $heavy_permissions = array( |
||||
1010 | 'admin_forum', |
||||
1011 | 'manage_attachments', |
||||
1012 | 'manage_smileys', |
||||
1013 | 'manage_boards', |
||||
1014 | 'edit_news', |
||||
1015 | 'moderate_forum', |
||||
1016 | 'manage_bans', |
||||
1017 | 'manage_membergroups', |
||||
1018 | 'manage_permissions', |
||||
1019 | ); |
||||
1020 | |||||
1021 | // Make it an array, even if a string was passed. |
||||
1022 | $permission = (array) $permission; |
||||
1023 | |||||
1024 | call_integration_hook('integrate_heavy_permissions_session', array(&$heavy_permissions)); |
||||
1025 | |||||
1026 | // Check the permission and return an error... |
||||
1027 | if (!allowedTo($permission, $boards, $any)) |
||||
1028 | { |
||||
1029 | // Pick the last array entry as the permission shown as the error. |
||||
1030 | $error_permission = array_shift($permission); |
||||
1031 | |||||
1032 | // If they are a guest, show a login. (because the error might be gone if they do!) |
||||
1033 | if ($user_info['is_guest']) |
||||
1034 | { |
||||
1035 | loadLanguage('Errors'); |
||||
1036 | is_not_guest($txt['cannot_' . $error_permission]); |
||||
1037 | } |
||||
1038 | |||||
1039 | // Clear the action because they aren't really doing that! |
||||
1040 | $_GET['action'] = ''; |
||||
1041 | $_GET['board'] = ''; |
||||
1042 | $_GET['topic'] = ''; |
||||
1043 | writeLog(true); |
||||
1044 | |||||
1045 | fatal_lang_error('cannot_' . $error_permission, false); |
||||
1046 | |||||
1047 | // Getting this far is a really big problem, but let's try our best to prevent any cases... |
||||
1048 | trigger_error('No direct access...', E_USER_ERROR); |
||||
1049 | } |
||||
1050 | |||||
1051 | // If you're doing something on behalf of some "heavy" permissions, validate your session. |
||||
1052 | // (take out the heavy permissions, and if you can't do anything but those, you need a validated session.) |
||||
1053 | if (!allowedTo(array_diff($permission, $heavy_permissions), $boards)) |
||||
1054 | validateSession(); |
||||
1055 | } |
||||
1056 | |||||
1057 | /** |
||||
1058 | * Return the boards a user has a certain (board) permission on. (array(0) if all.) |
||||
1059 | * - returns a list of boards on which the user is allowed to do the specified permission. |
||||
1060 | * - returns an array with only a 0 in it if the user has permission to do this on every board. |
||||
1061 | * - returns an empty array if he or she cannot do this on any board. |
||||
1062 | * If check_access is true will also make sure the group has proper access to that board. |
||||
1063 | * |
||||
1064 | * @param string|array $permissions A single permission to check or an array of permissions to check |
||||
1065 | * @param bool $check_access Whether to check only the boards the user has access to |
||||
1066 | * @param bool $simple Whether to return a simple array of board IDs or one with permissions as the keys |
||||
1067 | * @return array An array of board IDs or an array containing 'permission' => 'board,board2,...' pairs |
||||
1068 | */ |
||||
1069 | function boardsAllowedTo($permissions, $check_access = true, $simple = true) |
||||
1070 | { |
||||
1071 | global $user_info, $smcFunc; |
||||
1072 | |||||
1073 | // Arrays are nice, most of the time. |
||||
1074 | $permissions = (array) $permissions; |
||||
1075 | |||||
1076 | /* |
||||
1077 | * Set $simple to true to use this function as it were in SMF 2.0.x. |
||||
1078 | * Otherwise, the resultant array becomes split into the multiple |
||||
1079 | * permissions that were passed. Other than that, it's just the normal |
||||
1080 | * state of play that you're used to. |
||||
1081 | */ |
||||
1082 | |||||
1083 | // Administrators are all powerful, sorry. |
||||
1084 | if ($user_info['is_admin']) |
||||
1085 | { |
||||
1086 | if ($simple) |
||||
1087 | return array(0); |
||||
1088 | else |
||||
1089 | { |
||||
1090 | $boards = array(); |
||||
1091 | foreach ($permissions as $permission) |
||||
1092 | $boards[$permission] = array(0); |
||||
1093 | |||||
1094 | return $boards; |
||||
1095 | } |
||||
1096 | } |
||||
1097 | |||||
1098 | // All groups the user is in except 'moderator'. |
||||
1099 | $groups = array_diff($user_info['groups'], array(3)); |
||||
1100 | |||||
1101 | $request = $smcFunc['db_query']('', ' |
||||
1102 | SELECT b.id_board, bp.add_deny' . ($simple ? '' : ', bp.permission') . ' |
||||
1103 | FROM {db_prefix}board_permissions AS bp |
||||
1104 | INNER JOIN {db_prefix}boards AS b ON (b.id_profile = bp.id_profile) |
||||
1105 | LEFT JOIN {db_prefix}moderators AS mods ON (mods.id_board = b.id_board AND mods.id_member = {int:current_member}) |
||||
1106 | LEFT JOIN {db_prefix}moderator_groups AS modgs ON (modgs.id_board = b.id_board AND modgs.id_group IN ({array_int:group_list})) |
||||
1107 | WHERE bp.id_group IN ({array_int:group_list}, {int:moderator_group}) |
||||
1108 | AND bp.permission IN ({array_string:permissions}) |
||||
1109 | AND (mods.id_member IS NOT NULL OR modgs.id_group IS NOT NULL OR bp.id_group != {int:moderator_group})' . |
||||
1110 | ($check_access ? ' AND {query_see_board}' : ''), |
||||
1111 | array( |
||||
1112 | 'current_member' => $user_info['id'], |
||||
1113 | 'group_list' => $groups, |
||||
1114 | 'moderator_group' => 3, |
||||
1115 | 'permissions' => $permissions, |
||||
1116 | ) |
||||
1117 | ); |
||||
1118 | $boards = array(); |
||||
1119 | $deny_boards = array(); |
||||
1120 | while ($row = $smcFunc['db_fetch_assoc']($request)) |
||||
1121 | { |
||||
1122 | if ($simple) |
||||
1123 | { |
||||
1124 | if (empty($row['add_deny'])) |
||||
1125 | $deny_boards[] = $row['id_board']; |
||||
1126 | else |
||||
1127 | $boards[] = $row['id_board']; |
||||
1128 | } |
||||
1129 | else |
||||
1130 | { |
||||
1131 | if (empty($row['add_deny'])) |
||||
1132 | $deny_boards[$row['permission']][] = $row['id_board']; |
||||
1133 | else |
||||
1134 | $boards[$row['permission']][] = $row['id_board']; |
||||
1135 | } |
||||
1136 | } |
||||
1137 | $smcFunc['db_free_result']($request); |
||||
1138 | |||||
1139 | if ($simple) |
||||
1140 | $boards = array_unique(array_values(array_diff($boards, $deny_boards))); |
||||
1141 | else |
||||
1142 | { |
||||
1143 | foreach ($permissions as $permission) |
||||
1144 | { |
||||
1145 | // never had it to start with |
||||
1146 | if (empty($boards[$permission])) |
||||
1147 | $boards[$permission] = array(); |
||||
1148 | else |
||||
1149 | { |
||||
1150 | // Or it may have been removed |
||||
1151 | $deny_boards[$permission] = isset($deny_boards[$permission]) ? $deny_boards[$permission] : array(); |
||||
1152 | $boards[$permission] = array_unique(array_values(array_diff($boards[$permission], $deny_boards[$permission]))); |
||||
1153 | } |
||||
1154 | } |
||||
1155 | } |
||||
1156 | |||||
1157 | // Maybe a mod needs to tweak the list of allowed boards on the fly? |
||||
1158 | call_integration_hook('integrate_boards_allowed_to', array(&$boards, $deny_boards, $permissions, $check_access, $simple)); |
||||
1159 | |||||
1160 | return $boards; |
||||
1161 | } |
||||
1162 | |||||
1163 | /** |
||||
1164 | * This function attempts to protect from spammed messages and the like. |
||||
1165 | * The time taken depends on error_type - generally uses the modSetting. |
||||
1166 | * |
||||
1167 | * @param string $error_type The error type. Also used as a $txt index (not an actual string). |
||||
1168 | * @param boolean $only_return_result Whether you want the function to die with a fatal_lang_error. |
||||
1169 | * @return bool Whether they've posted within the limit |
||||
1170 | */ |
||||
1171 | function spamProtection($error_type, $only_return_result = false) |
||||
1172 | { |
||||
1173 | global $modSettings, $user_info, $smcFunc; |
||||
1174 | |||||
1175 | // Certain types take less/more time. |
||||
1176 | $timeOverrides = array( |
||||
1177 | 'login' => 2, |
||||
1178 | 'register' => 2, |
||||
1179 | 'remind' => 30, |
||||
1180 | 'sendmail' => $modSettings['spamWaitTime'] * 5, |
||||
1181 | 'reporttm' => $modSettings['spamWaitTime'] * 4, |
||||
1182 | 'search' => !empty($modSettings['search_floodcontrol_time']) ? $modSettings['search_floodcontrol_time'] : 1, |
||||
1183 | ); |
||||
1184 | |||||
1185 | call_integration_hook('integrate_spam_protection', array(&$timeOverrides)); |
||||
1186 | |||||
1187 | // Moderators are free... |
||||
1188 | if (!allowedTo('moderate_board')) |
||||
1189 | $timeLimit = isset($timeOverrides[$error_type]) ? $timeOverrides[$error_type] : $modSettings['spamWaitTime']; |
||||
1190 | else |
||||
1191 | $timeLimit = 2; |
||||
1192 | |||||
1193 | // Delete old entries... |
||||
1194 | $smcFunc['db_query']('', ' |
||||
1195 | DELETE FROM {db_prefix}log_floodcontrol |
||||
1196 | WHERE log_time < {int:log_time} |
||||
1197 | AND log_type = {string:log_type}', |
||||
1198 | array( |
||||
1199 | 'log_time' => time() - $timeLimit, |
||||
1200 | 'log_type' => $error_type, |
||||
1201 | ) |
||||
1202 | ); |
||||
1203 | |||||
1204 | // Add a new entry, deleting the old if necessary. |
||||
1205 | $smcFunc['db_insert']('replace', |
||||
1206 | '{db_prefix}log_floodcontrol', |
||||
1207 | array('ip' => 'inet', 'log_time' => 'int', 'log_type' => 'string'), |
||||
1208 | array($user_info['ip'], time(), $error_type), |
||||
1209 | array('ip', 'log_type') |
||||
1210 | ); |
||||
1211 | |||||
1212 | // If affected is 0 or 2, it was there already. |
||||
1213 | if ($smcFunc['db_affected_rows']() != 1) |
||||
1214 | { |
||||
1215 | // Spammer! You only have to wait a *few* seconds! |
||||
1216 | if (!$only_return_result) |
||||
1217 | fatal_lang_error($error_type . '_WaitTime_broken', false, array($timeLimit)); |
||||
1218 | |||||
1219 | return true; |
||||
1220 | } |
||||
1221 | |||||
1222 | // They haven't posted within the limit. |
||||
1223 | return false; |
||||
1224 | } |
||||
1225 | |||||
1226 | /** |
||||
1227 | * A generic function to create a pair of index.php and .htaccess files in a directory |
||||
1228 | * |
||||
1229 | * @param string|array $paths The (absolute) directory path |
||||
1230 | * @param boolean $attachments Whether this is an attachment directory |
||||
1231 | * @return bool|array True on success an array of errors if anything fails |
||||
1232 | */ |
||||
1233 | function secureDirectory($paths, $attachments = false) |
||||
1234 | { |
||||
1235 | $errors = array(); |
||||
1236 | |||||
1237 | // Work with arrays |
||||
1238 | $paths = (array) $paths; |
||||
1239 | |||||
1240 | if (empty($paths)) |
||||
1241 | $errors[] = 'empty_path'; |
||||
1242 | |||||
1243 | if (!empty($errors)) |
||||
1244 | return $errors; |
||||
1245 | |||||
1246 | foreach ($paths as $path) |
||||
1247 | { |
||||
1248 | if (!is_writable($path)) |
||||
1249 | { |
||||
1250 | $errors[] = 'path_not_writable'; |
||||
1251 | |||||
1252 | continue; |
||||
1253 | } |
||||
1254 | |||||
1255 | $directory_name = basename($path); |
||||
1256 | |||||
1257 | $close = empty($attachments) ? ' |
||||
1258 | </Files>' : ' |
||||
1259 | Allow from localhost |
||||
1260 | </Files> |
||||
1261 | |||||
1262 | RemoveHandler .php .php3 .phtml .cgi .fcgi .pl .fpl .shtml'; |
||||
1263 | |||||
1264 | if (file_exists($path . '/.htaccess')) |
||||
1265 | { |
||||
1266 | $errors[] = 'htaccess_exists'; |
||||
1267 | |||||
1268 | continue; |
||||
1269 | } |
||||
1270 | |||||
1271 | else |
||||
1272 | { |
||||
1273 | $fh = @fopen($path . '/.htaccess', 'w'); |
||||
1274 | |||||
1275 | if ($fh) |
||||
1276 | { |
||||
1277 | fwrite($fh, '<Files *> |
||||
1278 | Order Deny,Allow |
||||
1279 | Deny from all' . $close); |
||||
1280 | fclose($fh); |
||||
1281 | } |
||||
1282 | |||||
1283 | else |
||||
1284 | $errors[] = 'htaccess_cannot_create_file'; |
||||
1285 | } |
||||
1286 | |||||
1287 | if (file_exists($path . '/index.php')) |
||||
1288 | { |
||||
1289 | $errors[] = 'index-php_exists'; |
||||
1290 | |||||
1291 | continue; |
||||
1292 | } |
||||
1293 | |||||
1294 | else |
||||
1295 | { |
||||
1296 | $fh = @fopen($path . '/index.php', 'w'); |
||||
1297 | |||||
1298 | if ($fh) |
||||
1299 | { |
||||
1300 | fwrite($fh, '<' . '?php |
||||
1301 | |||||
1302 | /** |
||||
1303 | * This file is here solely to protect your ' . $directory_name . ' directory. |
||||
1304 | */ |
||||
1305 | |||||
1306 | // Look for Settings.php.... |
||||
1307 | if (file_exists(dirname(dirname(__FILE__)) . \'/Settings.php\')) |
||||
1308 | { |
||||
1309 | // Found it! |
||||
1310 | require(dirname(dirname(__FILE__)) . \'/Settings.php\'); |
||||
1311 | header(\'location: \' . $boardurl); |
||||
1312 | } |
||||
1313 | // Can\'t find it... just forget it. |
||||
1314 | else |
||||
1315 | exit; |
||||
1316 | |||||
1317 | ?' . '>'); |
||||
1318 | fclose($fh); |
||||
1319 | } |
||||
1320 | |||||
1321 | else |
||||
1322 | $errors[] = 'index-php_cannot_create_file'; |
||||
1323 | } |
||||
1324 | } |
||||
1325 | |||||
1326 | if (!empty($errors)) |
||||
1327 | return $errors; |
||||
1328 | |||||
1329 | else |
||||
1330 | return true; |
||||
1331 | } |
||||
1332 | |||||
1333 | /** |
||||
1334 | * This sets the X-Frame-Options header. |
||||
1335 | * |
||||
1336 | * @param string $override An option to override (either 'SAMEORIGIN' or 'DENY') |
||||
1337 | * @since 2.1 |
||||
1338 | */ |
||||
1339 | function frameOptionsHeader($override = null) |
||||
1340 | { |
||||
1341 | global $modSettings; |
||||
1342 | |||||
1343 | $option = 'SAMEORIGIN'; |
||||
1344 | if (is_null($override) && !empty($modSettings['frame_security'])) |
||||
1345 | $option = $modSettings['frame_security']; |
||||
1346 | elseif (in_array($override, array('SAMEORIGIN', 'DENY'))) |
||||
1347 | $option = $override; |
||||
1348 | |||||
1349 | // Don't bother setting the header if we have disabled it. |
||||
1350 | if ($option == 'DISABLE') |
||||
1351 | return; |
||||
1352 | |||||
1353 | // Finally set it. |
||||
1354 | header('x-frame-options: ' . $option); |
||||
1355 | |||||
1356 | // And some other useful ones. |
||||
1357 | header('x-xss-protection: 1'); |
||||
1358 | header('x-content-type-options: nosniff'); |
||||
1359 | } |
||||
1360 | |||||
1361 | /** |
||||
1362 | * This sets the Access-Control-Allow-Origin header. |
||||
1363 | * @link https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS |
||||
1364 | * |
||||
1365 | * @param bool $set_header (Default: true): When false, we will do the logic, but not send the headers. The relevant logic is still saved in the $context and can be sent manually. |
||||
1366 | * |
||||
1367 | * @since 2.1 |
||||
1368 | */ |
||||
1369 | function corsPolicyHeader($set_header = true) |
||||
1370 | { |
||||
1371 | global $boardurl, $modSettings, $context; |
||||
1372 | |||||
1373 | if (empty($modSettings['allow_cors']) || empty($_SERVER['HTTP_ORIGIN'])) |
||||
1374 | return; |
||||
1375 | |||||
1376 | foreach (array('origin' => $_SERVER['HTTP_ORIGIN'], 'boardurl_parts' => $boardurl) as $var => $url) |
||||
1377 | { |
||||
1378 | // Convert any Punycode to Unicode for the sake of comparision, then parse. |
||||
1379 | $$var = parse_iri(url_to_iri((string) validate_iri(normalize_iri(trim($url))))); |
||||
1380 | } |
||||
1381 | |||||
1382 | // The admin wants weak security... :( |
||||
1383 | if (!empty($modSettings['cors_domains']) && $modSettings['cors_domains'] === '*') |
||||
1384 | { |
||||
1385 | $context['cors_domain'] = '*'; |
||||
1386 | $context['valid_cors_found'] = 'wildcard'; |
||||
1387 | } |
||||
1388 | |||||
1389 | // Oh good, the admin cares about security. :) |
||||
1390 | else |
||||
1391 | { |
||||
1392 | $i = 0; |
||||
1393 | |||||
1394 | // Build our list of allowed CORS origins. |
||||
1395 | $allowed_origins = array(); |
||||
1396 | |||||
1397 | // If subdomain-independent cookies are on, allow CORS requests from subdomains. |
||||
1398 | if (!empty($modSettings['globalCookies']) && !empty($modSettings['globalCookiesDomain'])) |
||||
1399 | { |
||||
1400 | $allowed_origins[++$i] = array_merge(parse_iri('//*.' . trim($modSettings['globalCookiesDomain'])), array('type' => 'subdomain')); |
||||
0 ignored issues
–
show
parse_iri('//*.' . trim(...globalCookiesDomain'])) of type string is incompatible with the type array expected by parameter $arrays of array_merge() .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
1401 | } |
||||
1402 | |||||
1403 | // Support forum_alias_urls as well, since those are supported by our login cookie. |
||||
1404 | if (!empty($modSettings['forum_alias_urls'])) |
||||
1405 | { |
||||
1406 | foreach (explode(',', $modSettings['forum_alias_urls']) as $alias) |
||||
1407 | $allowed_origins[++$i] = array_merge(parse_iri((strpos($alias, '//') === false ? '//' : '') . trim($alias)), array('type' => 'alias')); |
||||
1408 | } |
||||
1409 | |||||
1410 | // Additional CORS domains. |
||||
1411 | if (!empty($modSettings['cors_domains'])) |
||||
1412 | { |
||||
1413 | foreach (explode(',', $modSettings['cors_domains']) as $cors_domain) |
||||
1414 | { |
||||
1415 | $allowed_origins[++$i] = array_merge(parse_iri((strpos($cors_domain, '//') === false ? '//' : '') . trim($cors_domain)), array('type' => 'additional')); |
||||
1416 | |||||
1417 | if (strpos($allowed_origins[$i]['host'], '*') === 0) |
||||
1418 | $allowed_origins[$i]['type'] .= '_wildcard'; |
||||
1419 | } |
||||
1420 | } |
||||
1421 | |||||
1422 | // Does the origin match any of our allowed domains? |
||||
1423 | foreach ($allowed_origins as $allowed_origin) |
||||
1424 | { |
||||
1425 | // If a specific scheme is required, it must match. |
||||
1426 | if (!empty($allowed_origin['scheme']) && $allowed_origin['scheme'] !== $origin['scheme']) |
||||
0 ignored issues
–
show
Comprehensibility
Best Practice
introduced
by
|
|||||
1427 | continue; |
||||
1428 | |||||
1429 | // If a specific port is required, it must match. |
||||
1430 | if (!empty($allowed_origin['port'])) |
||||
1431 | { |
||||
1432 | // Automatically supply the default port for the "special" schemes. |
||||
1433 | // See https://url.spec.whatwg.org/#special-scheme |
||||
1434 | if (empty($origin['port'])) |
||||
1435 | { |
||||
1436 | switch ($origin['scheme']) |
||||
1437 | { |
||||
1438 | case 'http': |
||||
1439 | case 'ws': |
||||
1440 | $origin['port'] = 80; |
||||
1441 | break; |
||||
1442 | |||||
1443 | case 'https': |
||||
1444 | case 'wss': |
||||
1445 | $origin['port'] = 443; |
||||
1446 | break; |
||||
1447 | |||||
1448 | case 'ftp': |
||||
1449 | $origin['port'] = 21; |
||||
1450 | break; |
||||
1451 | |||||
1452 | case 'file': |
||||
1453 | default: |
||||
1454 | $origin['port'] = null; |
||||
1455 | break; |
||||
1456 | } |
||||
1457 | } |
||||
1458 | |||||
1459 | if ((int) $allowed_origin['port'] !== (int) $origin['port']) |
||||
1460 | continue; |
||||
1461 | } |
||||
1462 | |||||
1463 | // Wildcard can only be the first character. |
||||
1464 | if (strrpos($allowed_origin['host'], '*') > 0) |
||||
1465 | continue; |
||||
1466 | |||||
1467 | // Wildcard means allow the domain or any subdomains. |
||||
1468 | if (strpos($allowed_origin['host'], '*') === 0) |
||||
1469 | $host_regex = '(?:^|\.)' . preg_quote(ltrim($allowed_origin['host'], '*.'), '~') . '$'; |
||||
1470 | |||||
1471 | // No wildcard means allow the domain only. |
||||
1472 | else |
||||
1473 | $host_regex = '^' . preg_quote($allowed_origin['host'], '~') . '$'; |
||||
1474 | |||||
1475 | if (preg_match('~' . $host_regex . '~u', $origin['host'])) |
||||
1476 | { |
||||
1477 | $context['cors_domain'] = trim($_SERVER['HTTP_ORIGIN']); |
||||
1478 | $context['valid_cors_found'] = $allowed_origin['type']; |
||||
1479 | break; |
||||
1480 | } |
||||
1481 | } |
||||
1482 | } |
||||
1483 | |||||
1484 | // The default is just to place the root URL of the forum into the policy. |
||||
1485 | if (empty($context['cors_domain'])) |
||||
1486 | { |
||||
1487 | $context['cors_domain'] = iri_to_url($boardurl_parts['scheme'] . '://' . $boardurl_parts['host']); |
||||
0 ignored issues
–
show
Comprehensibility
Best Practice
introduced
by
|
|||||
1488 | |||||
1489 | // Attach the port if needed. |
||||
1490 | if (!empty($boardurl_parts['port'])) |
||||
0 ignored issues
–
show
Comprehensibility
Best Practice
introduced
by
|
|||||
1491 | $context['cors_domain'] .= ':' . $boardurl_parts['port']; |
||||
1492 | |||||
1493 | $context['valid_cors_found'] = 'same'; |
||||
1494 | } |
||||
1495 | |||||
1496 | $context['cors_headers'] = 'X-SMF-AJAX'; |
||||
1497 | |||||
1498 | // Any additional headers? |
||||
1499 | if (!empty($modSettings['cors_headers'])) |
||||
1500 | { |
||||
1501 | // Cleanup any typos. |
||||
1502 | $cors_headers = explode(',', $modSettings['cors_headers']); |
||||
1503 | foreach ($cors_headers as &$ch) |
||||
1504 | $ch = str_replace(' ', '-', trim($ch)); |
||||
1505 | |||||
1506 | $context['cors_headers'] += implode(',', $cors_headers); |
||||
1507 | } |
||||
1508 | |||||
1509 | // Allowing Cross-Origin Resource Sharing (CORS). |
||||
1510 | if ($set_header && !empty($context['valid_cors_found']) && !empty($context['cors_domain'])) |
||||
1511 | { |
||||
1512 | header('Access-Control-Allow-Origin: ' . $context['cors_domain']); |
||||
1513 | header('Access-Control-Allow-Headers: ' . $context['cors_headers']); |
||||
1514 | |||||
1515 | // Be careful with this, you're allowing an external site to allow the browser to send cookies with this. |
||||
1516 | if (!empty($modSettings['allow_cors_credentials'])) |
||||
1517 | header('Access-Control-Allow-Credentials: true'); |
||||
1518 | } |
||||
1519 | } |
||||
1520 | |||||
1521 | ?> |