= 0 ? time() + $cookie_length : 1); // If changing state force them to re-address some permission caching. $_SESSION['mc']['time'] = 0; // Extract our cookie domain and path from $boardurl $cookie_url = url_parts(!empty($modSettings['localCookies']), !empty($modSettings['globalCookies'])); // The cookie may already exist, and have been set with different options. if (isset($_COOKIE[$cookiename])) { // First check for 2.1 json-format cookie if (preg_match('~^{"0":\d+,"1":"[0-9a-f]*","2":\d+,"3":"[^"]+","4":"[^"]+"~', $_COOKIE[$cookiename]) === 1) list(,,, $old_domain, $old_path) = $smcFunc['json_decode']($_COOKIE[$cookiename], true); // Legacy format (for recent 2.0 --> 2.1 upgrades) elseif (preg_match('~^a:[34]:\{i:0;i:\d+;i:1;s:(0|40):"([a-fA-F0-9]{40})?";i:2;[id]:\d+;(i:3;i:\d;)?~', $_COOKIE[$cookiename]) === 1) { list(,,, $old_state) = safe_unserialize($_COOKIE[$cookiename]); $cookie_state = (empty($modSettings['localCookies']) ? 0 : 1) | (empty($modSettings['globalCookies']) ? 0 : 2); // Maybe we need to temporarily pretend to be using local cookies if ($cookie_state == 0 && $old_state == 1) list($old_domain, $old_path) = url_parts(true, false); else list($old_domain, $old_path) = url_parts($old_state & 1 > 0, $old_state & 2 > 0); } // Out with the old, in with the new! if (isset($old_domain) && $old_domain != $cookie_url[0] || isset($old_path) && $old_path != $cookie_url[1]) smf_setcookie($cookiename, $smcFunc['json_encode'](array(0, '', 0, $old_domain, $old_path), JSON_FORCE_OBJECT), 1, $old_path, $old_domain); } // Get the data and path to set it on. $data = empty($id) ? array(0, '', 0, $cookie_url[0], $cookie_url[1]) : array($id, $password, $expiry_time, $cookie_url[0], $cookie_url[1]); // Allow mods to add custom info to the cookie $custom_data = array(); call_integration_hook('integrate_cookie_data', array($data, &$custom_data)); $data = $smcFunc['json_encode'](array_merge($data, $custom_data), JSON_FORCE_OBJECT); // Set the cookie, $_COOKIE, and session variable. smf_setcookie($cookiename, $data, $expiry_time, $cookie_url[1], $cookie_url[0]); // If subdomain-independent cookies are on, unset the subdomain-dependent cookie too. if (empty($id) && !empty($modSettings['globalCookies'])) smf_setcookie($cookiename, $data, $expiry_time, $cookie_url[1], ''); // Any alias URLs? This is mainly for use with frames, etc. if (!empty($modSettings['forum_alias_urls'])) { $aliases = explode(',', $modSettings['forum_alias_urls']); $temp = $boardurl; foreach ($aliases as $alias) { // Fake the $boardurl so we can set a different cookie. $alias = strtr(trim($alias), array('http://' => '', 'https://' => '')); $boardurl = 'http://' . $alias; $cookie_url = url_parts(!empty($modSettings['localCookies']), !empty($modSettings['globalCookies'])); if ($cookie_url[0] == '') $cookie_url[0] = strtok($alias, '/'); $alias_data = $smcFunc['json_decode']($data, true); $alias_data[3] = $cookie_url[0]; $alias_data[4] = $cookie_url[1]; $alias_data = $smcFunc['json_encode']($alias_data, JSON_FORCE_OBJECT); smf_setcookie($cookiename, $alias_data, $expiry_time, $cookie_url[1], $cookie_url[0]); } $boardurl = $temp; } $_COOKIE[$cookiename] = $data; // Make sure the user logs in with a new session ID. if (!isset($_SESSION['login_' . $cookiename]) || $_SESSION['login_' . $cookiename] !== $data) { // We need to meddle with the session. require_once($sourcedir . '/Session.php'); // Backup and remove the old session. $oldSessionData = $_SESSION; $_SESSION = array(); session_destroy(); // Recreate and restore the new session. loadSession(); // @todo should we use session_regenerate_id(true); now that we are 5.1+ session_regenerate_id(); $_SESSION = $oldSessionData; $_SESSION['login_' . $cookiename] = $data; } } /** * Sets Two Factor Auth cookie * * @param int $cookie_length How long the cookie should last, in seconds * @param int $id The ID of the member * @param string $secret Should be a salted secret using hash_salt */ function setTFACookie($cookie_length, $id, $secret) { global $smcFunc, $modSettings, $cookiename; $expiry_time = ($cookie_length >= 0 ? time() + $cookie_length : 1); $identifier = $cookiename . '_tfa'; $cookie_url = url_parts(!empty($modSettings['localCookies']), !empty($modSettings['globalCookies'])); // Get the data and path to set it on. $data = $smcFunc['json_encode'](empty($id) ? array(0, '', 0, $cookie_url[0], $cookie_url[1], false) : array($id, $secret, $expiry_time, $cookie_url[0], $cookie_url[1]), JSON_FORCE_OBJECT); // Set the cookie, $_COOKIE, and session variable. smf_setcookie($identifier, $data, $expiry_time, $cookie_url[1], $cookie_url[0]); // If subdomain-independent cookies are on, unset the subdomain-dependent cookie too. if (empty($id) && !empty($modSettings['globalCookies'])) smf_setcookie($identifier, $data, $expiry_time, $cookie_url[1], ''); $_COOKIE[$identifier] = $data; } /** * Get the domain and path for the cookie * - normally, local and global should be the localCookies and globalCookies settings, respectively. * - uses boardurl to determine these two things. * * @param bool $local Whether we want local cookies * @param bool $global Whether we want global cookies * @return array An array to set the cookie on with domain and path in it, in that order */ function url_parts($local, $global) { global $boardurl, $modSettings; // Parse the URL with PHP to make life easier. $parsed_url = parse_iri($boardurl); // Is local cookies off? if (empty($parsed_url['path']) || !$local) $parsed_url['path'] = ''; if (!empty($modSettings['globalCookiesDomain']) && strpos($boardurl, $modSettings['globalCookiesDomain']) !== false) $parsed_url['host'] = $modSettings['globalCookiesDomain']; // Globalize cookies across domains (filter out IP-addresses)? elseif ($global && preg_match('~^\d{1,3}(\.\d{1,3}){3}$~', $parsed_url['host']) == 0 && preg_match('~(?:[^\.]+\.)?([^\.]{2,}\..+)\z~i', $parsed_url['host'], $parts) == 1) $parsed_url['host'] = '.' . $parts[1]; // We shouldn't use a host at all if both options are off. elseif (!$local && !$global) $parsed_url['host'] = ''; // The host also shouldn't be set if there aren't any dots in it. elseif (!isset($parsed_url['host']) || strpos($parsed_url['host'], '.') === false) $parsed_url['host'] = ''; return array($parsed_url['host'], $parsed_url['path'] . '/'); } /** * Throws guests out to the login screen when guest access is off. * - sets $_SESSION['login_url'] to $_SERVER['REQUEST_URL']. * - uses the 'kick_guest' sub template found in Login.template.php. */ function KickGuest() { global $txt, $context; loadTheme(); loadLanguage('Login'); loadTemplate('Login'); createToken('login'); // Never redirect to an attachment if (strpos($_SERVER['REQUEST_URL'], 'dlattach') === false) $_SESSION['login_url'] = $_SERVER['REQUEST_URL']; $context['sub_template'] = 'kick_guest'; $context['page_title'] = $txt['login']; } /** * Display a message about the forum being in maintenance mode. * - display a login screen with sub template 'maintenance'. * - sends a 503 header, so search engines don't bother indexing while we're in maintenance mode. */ function InMaintenance() { global $txt, $mtitle, $mmessage, $context, $smcFunc; loadLanguage('Login'); loadTemplate('Login'); createToken('login'); // Send a 503 header, so search engines don't bother indexing while we're in maintenance mode. send_http_status(503, 'Service Temporarily Unavailable'); // Basic template stuff.. $context['sub_template'] = 'maintenance'; $context['title'] = $smcFunc['htmlspecialchars']($mtitle); $context['description'] = &$mmessage; $context['page_title'] = $txt['maintain_mode']; } /** * Question the verity of the admin by asking for his or her password. * - loads Login.template.php and uses the admin_login sub template. * - sends data to template so the admin is sent on to the page they * wanted if their password is correct, otherwise they can try again. * * @param string $type What login type is this - can be 'admin' or 'moderate' */ function adminLogin($type = 'admin') { global $context, $txt, $user_info; loadLanguage('Admin'); loadTemplate('Login'); // Validate what type of session check this is. $types = array(); call_integration_hook('integrate_validateSession', array(&$types)); $type = in_array($type, $types) || $type == 'moderate' ? $type : 'admin'; // They used a wrong password, log it and unset that. if (isset($_POST[$type . '_hash_pass']) || isset($_POST[$type . '_pass'])) { $txt['security_wrong'] = sprintf($txt['security_wrong'], isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : $txt['unknown'], $_SERVER['HTTP_USER_AGENT'], $user_info['ip']); log_error($txt['security_wrong'], 'critical'); if (isset($_POST[$type . '_hash_pass'])) unset($_POST[$type . '_hash_pass']); if (isset($_POST[$type . '_pass'])) unset($_POST[$type . '_pass']); $context['incorrect_password'] = true; } createToken('admin-login'); // Figure out the get data and post data. $context['get_data'] = '?' . construct_query_string($_GET); $context['post_data'] = ''; // Now go through $_POST. Make sure the session hash is sent. $_POST[$context['session_var']] = $context['session_id']; foreach ($_POST as $k => $v) $context['post_data'] .= adminLogin_outputPostVars($k, $v); // Now we'll use the admin_login sub template of the Login template. $context['sub_template'] = 'admin_login'; // And title the page something like "Login". if (!isset($context['page_title'])) $context['page_title'] = $txt['login']; // The type of action. $context['sessionCheckType'] = $type; obExit(); // We MUST exit at this point, because otherwise we CANNOT KNOW that the user is privileged. trigger_error('No direct access...', E_USER_ERROR); } /** * Used by the adminLogin() function. * if 'value' is an array, the function is called recursively. * * @param string $k The keys * @param string $v The values * @return string 'hidden' HTML form fields, containing key-value-pairs */ function adminLogin_outputPostVars($k, $v) { global $smcFunc; if (!is_array($v)) return ' '"', '<' => '<', '>' => '>')) . '">'; else { $ret = ''; foreach ($v as $k2 => $v2) $ret .= adminLogin_outputPostVars($k . '[' . $k2 . ']', $v2); return $ret; } } /** * Properly urlencodes a string to be used in a query * * @param string $get * @return string Our query string */ function construct_query_string($get) { global $scripturl; $query_string = ''; // Awww, darn. The $scripturl contains GET stuff! $q = strpos($scripturl, '?'); if ($q !== false) { parse_str(preg_replace('/&(\w+)(?=&|$)/', '&$1=', strtr(substr($scripturl, $q + 1), ';', '&')), $temp); foreach ($get as $k => $v) { // Only if it's not already in the $scripturl! if (!isset($temp[$k])) $query_string .= urlencode($k) . '=' . urlencode($v) . ';'; // If it changed, put it out there, but with an ampersand. elseif ($temp[$k] != $get[$k]) $query_string .= urlencode($k) . '=' . urlencode($v) . '&'; } } else { // Add up all the data from $_GET into get_data. foreach ($get as $k => $v) $query_string .= urlencode($k) . '=' . urlencode($v) . ';'; } $query_string = substr($query_string, 0, -1); return $query_string; } /** * Finds members by email address, username, or real name. * - searches for members whose username, display name, or e-mail address match the given pattern of array names. * - searches only buddies if buddies_only is set. * * @param array $names The names of members to search for * @param bool $use_wildcards Whether to use wildcards. Accepts wildcards ? and * in the pattern if true * @param bool $buddies_only Whether to only search for the user's buddies * @param int $max The maximum number of results * @return array An array containing information about the matching members */ function findMembers($names, $use_wildcards = false, $buddies_only = false, $max = 500) { global $scripturl, $user_info, $smcFunc; // If it's not already an array, make it one. if (!is_array($names)) $names = explode(',', $names); $maybe_email = false; $names_list = array(); foreach (array_values($names) as $i => $name) { // Trim, and fix wildcards for each name. $names[$i] = trim($smcFunc['strtolower']($name)); $maybe_email |= strpos($name, '@') !== false; // Make it so standard wildcards will work. (* and ?) if ($use_wildcards) $names[$i] = strtr($names[$i], array('%' => '\%', '_' => '\_', '*' => '%', '?' => '_', '\'' => ''')); else $names[$i] = strtr($names[$i], array('\'' => ''')); $names_list[] = '{string:lookup_name_' . $i . '}'; $where_params['lookup_name_' . $i] = $names[$i]; } // What are we using to compare? $comparison = $use_wildcards ? 'LIKE' : '='; // Nothing found yet. $results = array(); // This ensures you can't search someones email address if you can't see it. if (($use_wildcards || $maybe_email) && allowedTo('moderate_forum')) $email_condition = ' OR (email_address ' . $comparison . ' \'' . implode('\') OR (email_address ' . $comparison . ' \'', $names) . '\')'; else $email_condition = ''; // Get the case of the columns right - but only if we need to as things like MySQL will go slow needlessly otherwise. $member_name = $smcFunc['db_case_sensitive'] ? 'LOWER(member_name)' : 'member_name'; $real_name = $smcFunc['db_case_sensitive'] ? 'LOWER(real_name)' : 'real_name'; // Searches. $member_name_search = $member_name . ' ' . $comparison . ' ' . implode(' OR ' . $member_name . ' ' . $comparison . ' ', $names_list); $real_name_search = $real_name . ' ' . $comparison . ' ' . implode(' OR ' . $real_name . ' ' . $comparison . ' ', $names_list); // Search by username, display name, and email address. $request = $smcFunc['db_query']('', ' SELECT id_member, member_name, real_name, email_address FROM {db_prefix}members WHERE (' . $member_name_search . ' OR ' . $real_name_search . ' ' . $email_condition . ') ' . ($buddies_only ? 'AND id_member IN ({array_int:buddy_list})' : '') . ' AND is_activated IN (1, 11) LIMIT {int:limit}', array_merge($where_params, array( 'buddy_list' => $user_info['buddies'], 'limit' => $max, )) ); while ($row = $smcFunc['db_fetch_assoc']($request)) { $results[$row['id_member']] = array( 'id' => $row['id_member'], 'name' => $row['real_name'], 'username' => $row['member_name'], 'email' => allowedTo('moderate_forum') ? $row['email_address'] : '', 'href' => $scripturl . '?action=profile;u=' . $row['id_member'], 'link' => '' . $row['real_name'] . '' ); } $smcFunc['db_free_result']($request); // Return all the results. return $results; } /** * Called by index.php?action=findmember. * - is used as a popup for searching members. * - uses sub template find_members of the Help template. * - also used to add members for PM's sent using wap2/imode protocol. */ function JSMembers() { global $context, $scripturl, $user_info, $smcFunc; checkSession('get'); // Why is this in the Help template, you ask? Well, erm... it helps you. Does that work? loadTemplate('Help'); $context['template_layers'] = array(); $context['sub_template'] = 'find_members'; if (isset($_REQUEST['search'])) $context['last_search'] = $smcFunc['htmlspecialchars']($_REQUEST['search'], ENT_QUOTES); else $_REQUEST['start'] = 0; // Allow the user to pass the input to be added to to the box. $context['input_box_name'] = isset($_REQUEST['input']) && preg_match('~^[\w-]+$~', $_REQUEST['input']) === 1 ? $_REQUEST['input'] : 'to'; // Take the delimiter over GET in case it's \n or something. $context['delimiter'] = isset($_REQUEST['delim']) ? ($_REQUEST['delim'] == 'LB' ? "\n" : $_REQUEST['delim']) : ', '; $context['quote_results'] = !empty($_REQUEST['quote']); // List all the results. $context['results'] = array(); // Some buddy related settings ;) $context['show_buddies'] = !empty($user_info['buddies']); $context['buddy_search'] = isset($_REQUEST['buddies']); // If the user has done a search, well - search. if (isset($_REQUEST['search'])) { $_REQUEST['search'] = $smcFunc['htmlspecialchars']($_REQUEST['search'], ENT_QUOTES); $context['results'] = findMembers(array($_REQUEST['search']), true, $context['buddy_search']); $total_results = count($context['results']); $context['page_index'] = constructPageIndex($scripturl . '?action=findmember;search=' . $context['last_search'] . ';' . $context['session_var'] . '=' . $context['session_id'] . ';input=' . $context['input_box_name'] . ($context['quote_results'] ? ';quote=1' : '') . ($context['buddy_search'] ? ';buddies' : ''), $_REQUEST['start'], $total_results, 7); // Determine the navigation context. $base_url = $scripturl . '?action=findmember;search=' . urlencode($context['last_search']) . (empty($_REQUEST['u']) ? '' : ';u=' . $_REQUEST['u']) . ';' . $context['session_var'] . '=' . $context['session_id']; $context['links'] = array( 'first' => $_REQUEST['start'] >= 7 ? $base_url . ';start=0' : '', 'prev' => $_REQUEST['start'] >= 7 ? $base_url . ';start=' . ($_REQUEST['start'] - 7) : '', 'next' => $_REQUEST['start'] + 7 < $total_results ? $base_url . ';start=' . ($_REQUEST['start'] + 7) : '', 'last' => $_REQUEST['start'] + 7 < $total_results ? $base_url . ';start=' . (floor(($total_results - 1) / 7) * 7) : '', 'up' => $scripturl . '?action=pm;sa=send' . (empty($_REQUEST['u']) ? '' : ';u=' . $_REQUEST['u']), ); $context['page_info'] = array( 'current_page' => $_REQUEST['start'] / 7 + 1, 'num_pages' => floor(($total_results - 1) / 7) + 1 ); $context['results'] = array_slice($context['results'], $_REQUEST['start'], 7); } else $context['links']['up'] = $scripturl . '?action=pm;sa=send' . (empty($_REQUEST['u']) ? '' : ';u=' . $_REQUEST['u']); } /** * Outputs each member name on its own line. * - used by javascript to find members matching the request. */ function RequestMembers() { global $user_info, $txt, $smcFunc; checkSession('get'); $_REQUEST['search'] = $smcFunc['htmlspecialchars']($_REQUEST['search']) . '*'; $_REQUEST['search'] = trim($smcFunc['strtolower']($_REQUEST['search'])); $_REQUEST['search'] = strtr($_REQUEST['search'], array('%' => '\%', '_' => '\_', '*' => '%', '?' => '_', '&' => '&')); if (function_exists('iconv')) header('content-type: text/plain; charset=UTF-8'); $request = $smcFunc['db_query']('', ' SELECT real_name FROM {db_prefix}members WHERE {raw:real_name} LIKE {string:search}' . (isset($_REQUEST['buddies']) ? ' AND id_member IN ({array_int:buddy_list})' : '') . ' AND is_activated IN (1, 11) LIMIT ' . ($smcFunc['strlen']($_REQUEST['search']) <= 2 ? '100' : '800'), array( 'real_name' => $smcFunc['db_case_sensitive'] ? 'LOWER(real_name)' : 'real_name', 'buddy_list' => $user_info['buddies'], 'search' => $_REQUEST['search'], ) ); while ($row = $smcFunc['db_fetch_assoc']($request)) { if (function_exists('iconv')) { $utf8 = iconv($txt['lang_character_set'], 'UTF-8', $row['real_name']); if ($utf8) $row['real_name'] = $utf8; } $row['real_name'] = strtr($row['real_name'], array('&' => '&', '<' => '<', '>' => '>', '"' => '"')); if (preg_match('~&#\d+;~', $row['real_name']) != 0) $row['real_name'] = preg_replace_callback('~&#(\d+);~', 'fixchar__callback', $row['real_name']); echo $row['real_name'], "\n"; } $smcFunc['db_free_result']($request); obExit(false); } /** * Generates a random password for a user and emails it to them. * - called by Profile.php when changing someone's username. * - checks the validity of the new username. * - generates and sets a new password for the given user. * - mails the new password to the email address of the user. * - if username is not set, only a new password is generated and sent. * * @param int $memID The ID of the member * @param string $username The new username. If set, also checks the validity of the username */ function resetPassword($memID, $username = null) { global $sourcedir, $modSettings, $smcFunc, $language; // Language... and a required file. loadLanguage('Login'); require_once($sourcedir . '/Subs-Post.php'); // Get some important details. $request = $smcFunc['db_query']('', ' SELECT member_name, email_address, lngfile FROM {db_prefix}members WHERE id_member = {int:id_member}', array( 'id_member' => $memID, ) ); list ($user, $email, $lngfile) = $smcFunc['db_fetch_row']($request); $smcFunc['db_free_result']($request); if ($username !== null) { $old_user = $user; $user = trim($username); } // Generate a random password. $newPassword = substr(preg_replace('/\W/', '', md5($smcFunc['random_int']())), 0, 10); $newPassword_sha1 = hash_password($user, $newPassword); // Do some checks on the username if needed. if ($username !== null) { validateUsername($memID, $user); // Update the database... updateMemberData($memID, array('member_name' => $user, 'passwd' => $newPassword_sha1)); } else updateMemberData($memID, array('passwd' => $newPassword_sha1)); call_integration_hook('integrate_reset_pass', array($old_user, $user, $newPassword)); $replacements = array( 'USERNAME' => $user, 'PASSWORD' => $newPassword, ); $emaildata = loadEmailTemplate('change_password', $replacements, empty($lngfile) || empty($modSettings['userLanguage']) ? $language : $lngfile); // Send them the email informing them of the change - then we're done! sendmail($email, $emaildata['subject'], $emaildata['body'], null, 'chgpass' . $memID, $emaildata['is_html'], 0); } /** * Checks a username obeys a load of rules * * @param int $memID The ID of the member * @param string $username The username to validate * @param boolean $return_error Whether to return errors * @param boolean $check_reserved_name Whether to check this against the list of reserved names * @return array|null Null if there are no errors, otherwise an array of errors if return_error is true */ function validateUsername($memID, $username, $return_error = false, $check_reserved_name = true) { global $sourcedir, $txt, $smcFunc, $user_info; $errors = array(); // Don't use too long a name. if ($smcFunc['strlen']($username) > 25) $errors[] = array('lang', 'error_long_name'); // No name?! How can you register with no name? if ($username == '') $errors[] = array('lang', 'need_username'); // Only these characters are permitted. if (in_array($username, array('_', '|')) || preg_match('~[<>&"\'=\\\\]~', preg_replace('~&#(?:\\d{1,7}|x[0-9a-fA-F]{1,6});~', '', $username)) != 0 || strpos($username, '[code') !== false || strpos($username, '[/code') !== false) $errors[] = array('lang', 'error_invalid_characters_username'); if (stristr($username, $txt['guest_title']) !== false) $errors[] = array('lang', 'username_reserved', 'general', array($txt['guest_title'])); if ($check_reserved_name) { require_once($sourcedir . '/Subs-Members.php'); if (isReservedName($username, $memID, false)) $errors[] = array('done', '(' . $smcFunc['htmlspecialchars']($username) . ') ' . $txt['name_in_use']); } // Maybe a mod wants to perform more checks? call_integration_hook('integrate_validate_username', array($username, &$errors)); if ($return_error) return $errors; elseif (empty($errors)) return null; loadLanguage('Errors'); $error = $errors[0]; $message = $error[0] == 'lang' ? (empty($error[3]) ? $txt[$error[1]] : vsprintf($txt[$error[1]], (array) $error[3])) : $error[1]; fatal_error($message, empty($error[2]) || $user_info['is_admin'] ? false : $error[2]); } /** * Checks whether a password meets the current forum rules * - called when registering/choosing a password. * - checks the password obeys the current forum settings for password strength. * - if password checking is enabled, will check that none of the words in restrict_in appear in the password. * - returns an error identifier if the password is invalid, or null. * * @param string $password The desired password * @param string $username The username * @param array $restrict_in An array of restricted strings that cannot be part of the password (email address, username, etc.) * @return null|string Null if valid or a string indicating what the problem was */ function validatePassword($password, $username, $restrict_in = array()) { global $modSettings, $smcFunc; // Perform basic requirements first. if ($smcFunc['strlen']($password) < (empty($modSettings['password_strength']) ? 4 : 8)) return 'short'; // Maybe we need some more fancy password checks. $pass_error = ''; call_integration_hook('integrate_validatePassword', array($password, $username, $restrict_in, &$pass_error)); if (!empty($pass_error)) return $pass_error; // Is this enough? if (empty($modSettings['password_strength'])) return null; // Otherwise, perform the medium strength test - checking if password appears in the restricted string. if (preg_match('~\b' . preg_quote($password, '~') . '\b~', implode(' ', $restrict_in)) != 0) return 'restricted_words'; elseif ($smcFunc['strpos']($password, $username) !== false) return 'restricted_words'; // If just medium, we're done. if ($modSettings['password_strength'] == 1) return null; // Otherwise, hard test next, check for numbers and letters, uppercase too. $good = preg_match('~(\D\d|\d\D)~', $password) != 0; $good &= $smcFunc['strtolower']($password) != $password; return $good ? null : 'chars'; } /** * Quickly find out what moderation authority this user has * - builds the moderator, group and board level querys for the user * - stores the information on the current users moderation powers in $user_info['mod_cache'] and $_SESSION['mc'] */ function rebuildModCache() { global $user_info, $smcFunc; // What groups can they moderate? $group_query = allowedTo('manage_membergroups') ? '1=1' : '0=1'; if ($group_query == '0=1' && !$user_info['is_guest']) { $request = $smcFunc['db_query']('', ' SELECT id_group FROM {db_prefix}group_moderators WHERE id_member = {int:current_member}', array( 'current_member' => $user_info['id'], ) ); $groups = array(); while ($row = $smcFunc['db_fetch_assoc']($request)) $groups[] = $row['id_group']; $smcFunc['db_free_result']($request); if (empty($groups)) $group_query = '0=1'; else $group_query = 'id_group IN (' . implode(',', $groups) . ')'; } // Then, same again, just the boards this time! $board_query = allowedTo('moderate_forum') ? '1=1' : '0=1'; if ($board_query == '0=1' && !$user_info['is_guest']) { $boards = boardsAllowedTo('moderate_board', true); if (empty($boards)) $board_query = '0=1'; else $board_query = 'id_board IN (' . implode(',', $boards) . ')'; } // What boards are they the moderator of? $boards_mod = array(); if (!$user_info['is_guest']) { $request = $smcFunc['db_query']('', ' SELECT id_board FROM {db_prefix}moderators WHERE id_member = {int:current_member}', array( 'current_member' => $user_info['id'], ) ); while ($row = $smcFunc['db_fetch_assoc']($request)) $boards_mod[] = $row['id_board']; $smcFunc['db_free_result']($request); // Can any of the groups they're in moderate any of the boards? $request = $smcFunc['db_query']('', ' SELECT id_board FROM {db_prefix}moderator_groups WHERE id_group IN({array_int:groups})', array( 'groups' => $user_info['groups'], ) ); while ($row = $smcFunc['db_fetch_assoc']($request)) $boards_mod[] = $row['id_board']; $smcFunc['db_free_result']($request); // Just in case we've got duplicates here... $boards_mod = array_unique($boards_mod); } $mod_query = empty($boards_mod) ? '0=1' : 'b.id_board IN (' . implode(',', $boards_mod) . ')'; $_SESSION['mc'] = array( 'time' => time(), // This looks a bit funny but protects against the login redirect. 'id' => $user_info['id'] && $user_info['name'] ? $user_info['id'] : 0, // If you change the format of 'gq' and/or 'bq' make sure to adjust 'can_mod' in Load.php. 'gq' => $group_query, 'bq' => $board_query, 'ap' => boardsAllowedTo('approve_posts'), 'mb' => $boards_mod, 'mq' => $mod_query, ); call_integration_hook('integrate_mod_cache'); $user_info['mod_cache'] = $_SESSION['mc']; // Might as well clean up some tokens while we are at it. cleanTokens(); } /** * A wrapper for setcookie that gives integration hook access to it * * @param string $name * @param string $value = '' * @param int $expire = 0 * @param string $path = '' * @param string $domain = '' * @param bool $secure = false * @param bool $httponly = true * @param string $samesite = lax */ function smf_setcookie($name, $value = '', $expire = 0, $path = '', $domain = '', $secure = null, $httponly = true, $samesite = null) { global $modSettings; // In case a customization wants to override the default settings if ($httponly === null) $httponly = !empty($modSettings['httponlyCookies']); if ($secure === null) $secure = !empty($modSettings['secureCookies']); if ($samesite === null) $samesite = !empty($modSettings['samesiteCookies']) ? $modSettings['samesiteCookies'] : 'lax'; // Intercept cookie? call_integration_hook('integrate_cookie', array($name, $value, $expire, $path, $domain, $secure, $httponly, $samesite)); if(PHP_VERSION_ID < 70300) return setcookie($name, $value, $expire, $path . ';samesite=' . $samesite, $domain, $secure, $httponly); else return setcookie($name, $value, array( 'expires' => $expire, 'path' => $path, 'domain' => $domain, 'secure' => $secure, 'httponly' => $httponly, 'samesite' => $samesite )); } /** * Hashes username with password * * @param string $username The username * @param string $password The unhashed password * @param int $cost The cost * @return string The hashed password */ function hash_password($username, $password, $cost = null) { global $smcFunc, $modSettings; $cost = empty($cost) ? (empty($modSettings['bcrypt_hash_cost']) ? 10 : $modSettings['bcrypt_hash_cost']) : $cost; return password_hash($smcFunc['strtolower']($username) . $password, PASSWORD_BCRYPT, array( 'cost' => $cost, )); } /** * Hashes password with salt and authentication secret. This is solely used for cookies. * * @param string $password The password * @param string $salt The salt * @return string The hashed password */ function hash_salt($password, $salt) { // Append the salt to get a user-specific authentication secret. $secret_key = get_auth_secret() . $salt; // Now use that to generate an HMAC of the password. return hash_hmac('sha512', $password, $secret_key); } /** * Verifies a raw SMF password against the bcrypt'd string * * @param string $username The username * @param string $password The password * @param string $hash The hashed string * @return bool Whether the hashed password matches the string */ function hash_verify_password($username, $password, $hash) { global $smcFunc; return password_verify($smcFunc['strtolower']($username) . $password, $hash); } /** * Returns the length for current hash * * @return int The length for the current hash */ function hash_length() { return 60; } /** * Benchmarks the server to figure out an appropriate cost factor (minimum 9) * * @param float $hashTime Time to target, in seconds * @return int The cost */ function hash_benchmark($hashTime = 0.2) { $cost = 9; do { $timeStart = microtime(true); hash_password('test', 'thisisatestpassword', $cost); $timeTaken = microtime(true) - $timeStart; $cost++; } while ($timeTaken < $hashTime); return $cost; } // Based on code by "examplehash at user dot com". // https://www.php.net/manual/en/function.hash-equals.php#125034 if (!function_exists('hash_equals')) { /** * A compatibility function for when PHP's "hash_equals" function isn't available * @param string $known_string A known hash * @param string $user_string The hash of the user string * @return bool Whether or not the two are equal */ function hash_equals($known_string, $user_string) { $known_string = (string) $known_string; $user_string = (string) $user_string; $sx = 0; $sy = strlen($known_string); $uy = strlen($user_string); $result = $sy - $uy; for ($ux = 0; $ux < $uy; $ux++) { $result |= ord($user_string[$ux]) ^ ord($known_string[$sx]); $sx = ($sx + 1) % $sy; } return !$result; } } ?>