Subs-Auth.php 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824
  1. <?php
  2. /**
  3. * This file has functions in it to do with authentication, user handling, and the like.
  4. *
  5. * Simple Machines Forum (SMF)
  6. *
  7. * @package SMF
  8. * @author Simple Machines http://www.simplemachines.org
  9. * @copyright 2014 Simple Machines and individual contributors
  10. * @license http://www.simplemachines.org/about/smf/license.php BSD
  11. *
  12. * @version 2.1 Alpha 1
  13. */
  14. if (!defined('SMF'))
  15. die('No direct access...');
  16. /**
  17. * Sets the SMF-style login cookie and session based on the id_member and password passed.
  18. * - password should be already encrypted with the cookie salt.
  19. * - logs the user out if id_member is zero.
  20. * - sets the cookie and session to last the number of seconds specified by cookie_length.
  21. * - when logging out, if the globalCookies setting is enabled, attempts to clear the subdomain's cookie too.
  22. *
  23. * @param int $cookie_length
  24. * @param int $id The id of the member
  25. * @param string $password = ''
  26. */
  27. function setLoginCookie($cookie_length, $id, $password = '')
  28. {
  29. global $cookiename, $boardurl, $modSettings, $sourcedir;
  30. $id = (int) $id;
  31. // If changing state force them to re-address some permission caching.
  32. $_SESSION['mc']['time'] = 0;
  33. // The cookie may already exist, and have been set with different options.
  34. $cookie_state = (empty($modSettings['localCookies']) ? 0 : 1) | (empty($modSettings['globalCookies']) ? 0 : 2);
  35. if (isset($_COOKIE[$cookiename]) && preg_match('~^a:[34]:\{i:0;i:\d{1,7};i:1;s:(0|40):"([a-fA-F0-9]{40})?";i:2;[id]:\d{1,14};(i:3;i:\d;)?\}$~', $_COOKIE[$cookiename]) === 1)
  36. {
  37. $array = @unserialize($_COOKIE[$cookiename]);
  38. // Out with the old, in with the new!
  39. if (isset($array[3]) && $array[3] != $cookie_state)
  40. {
  41. $cookie_url = url_parts($array[3] & 1 > 0, $array[3] & 2 > 0);
  42. smf_setcookie($cookiename, serialize(array(0, '', 0)), time() - 3600, $cookie_url[1], $cookie_url[0]);
  43. }
  44. }
  45. // Get the data and path to set it on.
  46. $data = serialize(empty($id) ? array(0, '', 0) : array($id, $password, time() + $cookie_length, $cookie_state));
  47. $cookie_url = url_parts(!empty($modSettings['localCookies']), !empty($modSettings['globalCookies']));
  48. // Set the cookie, $_COOKIE, and session variable.
  49. smf_setcookie($cookiename, $data, time() + $cookie_length, $cookie_url[1], $cookie_url[0]);
  50. // If subdomain-independent cookies are on, unset the subdomain-dependent cookie too.
  51. if (empty($id) && !empty($modSettings['globalCookies']))
  52. smf_setcookie($cookiename, $data, time() + $cookie_length, $cookie_url[1], '');
  53. // Any alias URLs? This is mainly for use with frames, etc.
  54. if (!empty($modSettings['forum_alias_urls']))
  55. {
  56. $aliases = explode(',', $modSettings['forum_alias_urls']);
  57. $temp = $boardurl;
  58. foreach ($aliases as $alias)
  59. {
  60. // Fake the $boardurl so we can set a different cookie.
  61. $alias = strtr(trim($alias), array('http://' => '', 'https://' => ''));
  62. $boardurl = 'http://' . $alias;
  63. $cookie_url = url_parts(!empty($modSettings['localCookies']), !empty($modSettings['globalCookies']));
  64. if ($cookie_url[0] == '')
  65. $cookie_url[0] = strtok($alias, '/');
  66. smf_setcookie($cookiename, $data, time() + $cookie_length, $cookie_url[1], $cookie_url[0]);
  67. }
  68. $boardurl = $temp;
  69. }
  70. $_COOKIE[$cookiename] = $data;
  71. // Make sure the user logs in with a new session ID.
  72. if (!isset($_SESSION['login_' . $cookiename]) || $_SESSION['login_' . $cookiename] !== $data)
  73. {
  74. // We need to meddle with the session.
  75. require_once($sourcedir . '/Session.php');
  76. // Backup and remove the old session.
  77. $oldSessionData = $_SESSION;
  78. $_SESSION = array();
  79. session_destroy();
  80. // Recreate and restore the new session.
  81. loadSession();
  82. // @todo should we use session_regenerate_id(true); now that we are 5.1+
  83. session_regenerate_id();
  84. $_SESSION = $oldSessionData;
  85. $_SESSION['login_' . $cookiename] = $data;
  86. }
  87. }
  88. /**
  89. * Get the domain and path for the cookie
  90. * - normally, local and global should be the localCookies and globalCookies settings, respectively.
  91. * - uses boardurl to determine these two things.
  92. *
  93. * @param bool $local
  94. * @param bool $global
  95. * @return array an array to set the cookie on with domain and path in it, in that order
  96. */
  97. function url_parts($local, $global)
  98. {
  99. global $boardurl, $modSettings;
  100. // Parse the URL with PHP to make life easier.
  101. $parsed_url = parse_url($boardurl);
  102. // Is local cookies off?
  103. if (empty($parsed_url['path']) || !$local)
  104. $parsed_url['path'] = '';
  105. if (!empty($modSettings['globalCookiesDomain']) && strpos($boardurl, $modSettings['globalCookiesDomain']) !== false)
  106. $parsed_url['host'] = $modSettings['globalCookiesDomain'];
  107. // Globalize cookies across domains (filter out IP-addresses)?
  108. 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)
  109. $parsed_url['host'] = '.' . $parts[1];
  110. // We shouldn't use a host at all if both options are off.
  111. elseif (!$local && !$global)
  112. $parsed_url['host'] = '';
  113. // The host also shouldn't be set if there aren't any dots in it.
  114. elseif (!isset($parsed_url['host']) || strpos($parsed_url['host'], '.') === false)
  115. $parsed_url['host'] = '';
  116. return array($parsed_url['host'], $parsed_url['path'] . '/');
  117. }
  118. /**
  119. * Throws guests out to the login screen when guest access is off.
  120. * - sets $_SESSION['login_url'] to $_SERVER['REQUEST_URL'].
  121. * - uses the 'kick_guest' sub template found in Login.template.php.
  122. */
  123. function KickGuest()
  124. {
  125. global $txt, $context;
  126. loadLanguage('Login');
  127. loadTemplate('Login');
  128. createToken('login');
  129. // Never redirect to an attachment
  130. if (strpos($_SERVER['REQUEST_URL'], 'dlattach') === false)
  131. $_SESSION['login_url'] = $_SERVER['REQUEST_URL'];
  132. $context['sub_template'] = 'kick_guest';
  133. $context['page_title'] = $txt['login'];
  134. }
  135. /**
  136. * Display a message about the forum being in maintenance mode.
  137. * - display a login screen with sub template 'maintenance'.
  138. * - sends a 503 header, so search engines don't bother indexing while we're in maintenance mode.
  139. */
  140. function InMaintenance()
  141. {
  142. global $txt, $mtitle, $mmessage, $context;
  143. loadLanguage('Login');
  144. loadTemplate('Login');
  145. createToken('login');
  146. // Send a 503 header, so search engines don't bother indexing while we're in maintenance mode.
  147. header('HTTP/1.1 503 Service Temporarily Unavailable');
  148. // Basic template stuff..
  149. $context['sub_template'] = 'maintenance';
  150. $context['title'] = &$mtitle;
  151. $context['description'] = &$mmessage;
  152. $context['page_title'] = $txt['maintain_mode'];
  153. }
  154. /**
  155. * Question the verity of the admin by asking for his or her password.
  156. * - loads Login.template.php and uses the admin_login sub template.
  157. * - sends data to template so the admin is sent on to the page they
  158. * wanted if their password is correct, otherwise they can try again.
  159. *
  160. * @param string $type = 'admin'
  161. */
  162. function adminLogin($type = 'admin')
  163. {
  164. global $context, $scripturl, $txt, $user_info, $user_settings;
  165. loadLanguage('Admin');
  166. loadTemplate('Login');
  167. // Validate what type of session check this is.
  168. $types = array();
  169. call_integration_hook('integrate_validateSession', array(&$types));
  170. $type = in_array($type, $types) || $type == 'moderate' ? $type : 'admin';
  171. // They used a wrong password, log it and unset that.
  172. if (isset($_POST[$type . '_hash_pass']) || isset($_POST[$type . '_pass']))
  173. {
  174. $txt['security_wrong'] = sprintf($txt['security_wrong'], isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : $txt['unknown'], $_SERVER['HTTP_USER_AGENT'], $user_info['ip']);
  175. log_error($txt['security_wrong'], 'critical');
  176. if (isset($_POST[$type . '_hash_pass']))
  177. unset($_POST[$type . '_hash_pass']);
  178. if (isset($_POST[$type . '_pass']))
  179. unset($_POST[$type . '_pass']);
  180. $context['incorrect_password'] = true;
  181. }
  182. createToken('admin-login');
  183. // Figure out the get data and post data.
  184. $context['get_data'] = '?' . construct_query_string($_GET);
  185. $context['post_data'] = '';
  186. // Now go through $_POST. Make sure the session hash is sent.
  187. $_POST[$context['session_var']] = $context['session_id'];
  188. foreach ($_POST as $k => $v)
  189. $context['post_data'] .= adminLogin_outputPostVars($k, $v);
  190. // Now we'll use the admin_login sub template of the Login template.
  191. $context['sub_template'] = 'admin_login';
  192. // And title the page something like "Login".
  193. if (!isset($context['page_title']))
  194. $context['page_title'] = $txt['login'];
  195. // The type of action.
  196. $context['sessionCheckType'] = $type;
  197. obExit();
  198. // We MUST exit at this point, because otherwise we CANNOT KNOW that the user is privileged.
  199. trigger_error('Hacking attempt...', E_USER_ERROR);
  200. }
  201. /**
  202. * Used by the adminLogin() function.
  203. * if 'value' is an array, the function is called recursively.
  204. *
  205. * @param string $k key
  206. * @param string $v value
  207. * @return string 'hidden' HTML form fields, containing key-value-pairs
  208. */
  209. function adminLogin_outputPostVars($k, $v)
  210. {
  211. global $smcFunc;
  212. if (!is_array($v))
  213. return '
  214. <input type="hidden" name="' . $smcFunc['htmlspecialchars']($k) . '" value="' . strtr($v, array('"' => '&quot;', '<' => '&lt;', '>' => '&gt;')) . '" />';
  215. else
  216. {
  217. $ret = '';
  218. foreach ($v as $k2 => $v2)
  219. $ret .= adminLogin_outputPostVars($k . '[' . $k2 . ']', $v2);
  220. return $ret;
  221. }
  222. }
  223. /**
  224. * Properly urlencodes a string to be used in a query
  225. *
  226. * @global type $scripturl
  227. * @param type $get
  228. * @return our query string
  229. */
  230. function construct_query_string($get)
  231. {
  232. global $scripturl;
  233. $query_string = '';
  234. // Awww, darn. The $scripturl contains GET stuff!
  235. $q = strpos($scripturl, '?');
  236. if ($q !== false)
  237. {
  238. parse_str(preg_replace('/&(\w+)(?=&|$)/', '&$1=', strtr(substr($scripturl, $q + 1), ';', '&')), $temp);
  239. foreach ($get as $k => $v)
  240. {
  241. // Only if it's not already in the $scripturl!
  242. if (!isset($temp[$k]))
  243. $query_string .= urlencode($k) . '=' . urlencode($v) . ';';
  244. // If it changed, put it out there, but with an ampersand.
  245. elseif ($temp[$k] != $get[$k])
  246. $query_string .= urlencode($k) . '=' . urlencode($v) . '&amp;';
  247. }
  248. }
  249. else
  250. {
  251. // Add up all the data from $_GET into get_data.
  252. foreach ($get as $k => $v)
  253. $query_string .= urlencode($k) . '=' . urlencode($v) . ';';
  254. }
  255. $query_string = substr($query_string, 0, -1);
  256. return $query_string;
  257. }
  258. /**
  259. * Finds members by email address, username, or real name.
  260. * - searches for members whose username, display name, or e-mail address match the given pattern of array names.
  261. * - searches only buddies if buddies_only is set.
  262. *
  263. * @param array $names
  264. * @param bool $use_wildcards = false, accepts wildcards ? and * in the patern if true
  265. * @param bool $buddies_only = false,
  266. * @param int $max = 500 retrieves a maximum of max members, if passed
  267. * @return array containing information about the matching members
  268. */
  269. function findMembers($names, $use_wildcards = false, $buddies_only = false, $max = 500)
  270. {
  271. global $scripturl, $user_info, $smcFunc;
  272. // If it's not already an array, make it one.
  273. if (!is_array($names))
  274. $names = explode(',', $names);
  275. $maybe_email = false;
  276. foreach ($names as $i => $name)
  277. {
  278. // Trim, and fix wildcards for each name.
  279. $names[$i] = trim($smcFunc['strtolower']($name));
  280. $maybe_email |= strpos($name, '@') !== false;
  281. // Make it so standard wildcards will work. (* and ?)
  282. if ($use_wildcards)
  283. $names[$i] = strtr($names[$i], array('%' => '\%', '_' => '\_', '*' => '%', '?' => '_', '\'' => '&#039;'));
  284. else
  285. $names[$i] = strtr($names[$i], array('\'' => '&#039;'));
  286. }
  287. // What are we using to compare?
  288. $comparison = $use_wildcards ? 'LIKE' : '=';
  289. // Nothing found yet.
  290. $results = array();
  291. // This ensures you can't search someones email address if you can't see it.
  292. $email_condition = allowedTo('moderate_forum') ? '' : 'hide_email = 0 AND ';
  293. if ($use_wildcards || $maybe_email)
  294. $email_condition = '
  295. OR (' . $email_condition . 'email_address ' . $comparison . ' \'' . implode( '\') OR (' . $email_condition . ' email_address ' . $comparison . ' \'', $names) . '\')';
  296. else
  297. $email_condition = '';
  298. // Get the case of the columns right - but only if we need to as things like MySQL will go slow needlessly otherwise.
  299. $member_name = $smcFunc['db_case_sensitive'] ? 'LOWER(member_name)' : 'member_name';
  300. $real_name = $smcFunc['db_case_sensitive'] ? 'LOWER(real_name)' : 'real_name';
  301. // Search by username, display name, and email address.
  302. $request = $smcFunc['db_query']('', '
  303. SELECT id_member, member_name, real_name, email_address, hide_email
  304. FROM {db_prefix}members
  305. WHERE ({raw:member_name_search}
  306. OR {raw:real_name_search} {raw:email_condition})
  307. ' . ($buddies_only ? 'AND id_member IN ({array_int:buddy_list})' : '') . '
  308. AND is_activated IN (1, 11)
  309. LIMIT {int:limit}',
  310. array(
  311. 'buddy_list' => $user_info['buddies'],
  312. 'member_name_search' => $member_name . ' ' . $comparison . ' \'' . implode( '\' OR ' . $member_name . ' ' . $comparison . ' \'', $names) . '\'',
  313. 'real_name_search' => $real_name . ' ' . $comparison . ' \'' . implode( '\' OR ' . $real_name . ' ' . $comparison . ' \'', $names) . '\'',
  314. 'email_condition' => $email_condition,
  315. 'limit' => $max,
  316. )
  317. );
  318. while ($row = $smcFunc['db_fetch_assoc']($request))
  319. {
  320. $results[$row['id_member']] = array(
  321. 'id' => $row['id_member'],
  322. 'name' => $row['real_name'],
  323. 'username' => $row['member_name'],
  324. 'email' => in_array(showEmailAddress(!empty($row['hide_email']), $row['id_member']), array('yes', 'yes_permission_override')) ? $row['email_address'] : '',
  325. 'href' => $scripturl . '?action=profile;u=' . $row['id_member'],
  326. 'link' => '<a href="' . $scripturl . '?action=profile;u=' . $row['id_member'] . '">' . $row['real_name'] . '</a>'
  327. );
  328. }
  329. $smcFunc['db_free_result']($request);
  330. // Return all the results.
  331. return $results;
  332. }
  333. /**
  334. * Called by index.php?action=findmember.
  335. * - is used as a popup for searching members.
  336. * - uses sub template find_members of the Help template.
  337. * - also used to add members for PM's sent using wap2/imode protocol.
  338. */
  339. function JSMembers()
  340. {
  341. global $context, $scripturl, $user_info, $smcFunc;
  342. checkSession('get');
  343. if (WIRELESS)
  344. $context['sub_template'] = WIRELESS_PROTOCOL . '_pm';
  345. else
  346. {
  347. // Why is this in the Help template, you ask? Well, erm... it helps you. Does that work?
  348. loadTemplate('Help');
  349. $context['template_layers'] = array();
  350. $context['sub_template'] = 'find_members';
  351. }
  352. if (isset($_REQUEST['search']))
  353. $context['last_search'] = $smcFunc['htmlspecialchars']($_REQUEST['search'], ENT_QUOTES);
  354. else
  355. $_REQUEST['start'] = 0;
  356. // Allow the user to pass the input to be added to to the box.
  357. $context['input_box_name'] = isset($_REQUEST['input']) && preg_match('~^[\w-]+$~', $_REQUEST['input']) === 1 ? $_REQUEST['input'] : 'to';
  358. // Take the delimiter over GET in case it's \n or something.
  359. $context['delimiter'] = isset($_REQUEST['delim']) ? ($_REQUEST['delim'] == 'LB' ? "\n" : $_REQUEST['delim']) : ', ';
  360. $context['quote_results'] = !empty($_REQUEST['quote']);
  361. // List all the results.
  362. $context['results'] = array();
  363. // Some buddy related settings ;)
  364. $context['show_buddies'] = !empty($user_info['buddies']);
  365. $context['buddy_search'] = isset($_REQUEST['buddies']);
  366. // If the user has done a search, well - search.
  367. if (isset($_REQUEST['search']))
  368. {
  369. $_REQUEST['search'] = $smcFunc['htmlspecialchars']($_REQUEST['search'], ENT_QUOTES);
  370. $context['results'] = findMembers(array($_REQUEST['search']), true, $context['buddy_search']);
  371. $total_results = count($context['results']);
  372. $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);
  373. // Determine the navigation context (especially useful for the wireless template).
  374. $base_url = $scripturl . '?action=findmember;search=' . urlencode($context['last_search']) . (empty($_REQUEST['u']) ? '' : ';u=' . $_REQUEST['u']) . ';' . $context['session_var'] . '=' . $context['session_id'];
  375. $context['links'] = array(
  376. 'first' => $_REQUEST['start'] >= 7 ? $base_url . ';start=0' : '',
  377. 'prev' => $_REQUEST['start'] >= 7 ? $base_url . ';start=' . ($_REQUEST['start'] - 7) : '',
  378. 'next' => $_REQUEST['start'] + 7 < $total_results ? $base_url . ';start=' . ($_REQUEST['start'] + 7) : '',
  379. 'last' => $_REQUEST['start'] + 7 < $total_results ? $base_url . ';start=' . (floor(($total_results - 1) / 7) * 7) : '',
  380. 'up' => $scripturl . '?action=pm;sa=send' . (empty($_REQUEST['u']) ? '' : ';u=' . $_REQUEST['u']),
  381. );
  382. $context['page_info'] = array(
  383. 'current_page' => $_REQUEST['start'] / 7 + 1,
  384. 'num_pages' => floor(($total_results - 1) / 7) + 1
  385. );
  386. $context['results'] = array_slice($context['results'], $_REQUEST['start'], 7);
  387. }
  388. else
  389. $context['links']['up'] = $scripturl . '?action=pm;sa=send' . (empty($_REQUEST['u']) ? '' : ';u=' . $_REQUEST['u']);
  390. }
  391. /**
  392. * Outputs each member name on its own line.
  393. * - used by javascript to find members matching the request.
  394. */
  395. function RequestMembers()
  396. {
  397. global $user_info, $txt, $smcFunc;
  398. checkSession('get');
  399. $_REQUEST['search'] = $smcFunc['htmlspecialchars']($_REQUEST['search']) . '*';
  400. $_REQUEST['search'] = trim($smcFunc['strtolower']($_REQUEST['search']));
  401. $_REQUEST['search'] = strtr($_REQUEST['search'], array('%' => '\%', '_' => '\_', '*' => '%', '?' => '_', '&#038;' => '&amp;'));
  402. if (function_exists('iconv'))
  403. header('Content-Type: text/plain; charset=UTF-8');
  404. $request = $smcFunc['db_query']('', '
  405. SELECT real_name
  406. FROM {db_prefix}members
  407. WHERE {raw:real_name} LIKE {string:search}' . (isset($_REQUEST['buddies']) ? '
  408. AND id_member IN ({array_int:buddy_list})' : '') . '
  409. AND is_activated IN (1, 11)
  410. LIMIT ' . ($smcFunc['strlen']($_REQUEST['search']) <= 2 ? '100' : '800'),
  411. array(
  412. 'real_name' => $smcFunc['db_case_sensitive'] ? 'LOWER(real_name)' : 'real_name',
  413. 'buddy_list' => $user_info['buddies'],
  414. 'search' => $_REQUEST['search'],
  415. )
  416. );
  417. while ($row = $smcFunc['db_fetch_assoc']($request))
  418. {
  419. if (function_exists('iconv'))
  420. {
  421. $utf8 = iconv($txt['lang_character_set'], 'UTF-8', $row['real_name']);
  422. if ($utf8)
  423. $row['real_name'] = $utf8;
  424. }
  425. $row['real_name'] = strtr($row['real_name'], array('&amp;' => '&#038;', '&lt;' => '&#060;', '&gt;' => '&#062;', '&quot;' => '&#034;'));
  426. if (preg_match('~&#\d+;~', $row['real_name']) != 0)
  427. $row['real_name'] = preg_replace_callback('~&#(\d+);~', 'fixchar__callback', $row['real_name']);
  428. echo $row['real_name'], "\n";
  429. }
  430. $smcFunc['db_free_result']($request);
  431. obExit(false);
  432. }
  433. /**
  434. * Generates a random password for a user and emails it to them.
  435. * - called by Profile.php when changing someone's username.
  436. * - checks the validity of the new username.
  437. * - generates and sets a new password for the given user.
  438. * - mails the new password to the email address of the user.
  439. * - if username is not set, only a new password is generated and sent.
  440. *
  441. * @param int $memID
  442. * @param string $username = null
  443. */
  444. function resetPassword($memID, $username = null)
  445. {
  446. global $scripturl, $context, $txt, $sourcedir, $modSettings, $smcFunc, $language;
  447. // Language... and a required file.
  448. loadLanguage('Login');
  449. require_once($sourcedir . '/Subs-Post.php');
  450. // Get some important details.
  451. $request = $smcFunc['db_query']('', '
  452. SELECT member_name, email_address, lngfile
  453. FROM {db_prefix}members
  454. WHERE id_member = {int:id_member}',
  455. array(
  456. 'id_member' => $memID,
  457. )
  458. );
  459. list ($user, $email, $lngfile) = $smcFunc['db_fetch_row']($request);
  460. $smcFunc['db_free_result']($request);
  461. if ($username !== null)
  462. {
  463. $old_user = $user;
  464. $user = trim($username);
  465. }
  466. // Generate a random password.
  467. $newPassword = substr(preg_replace('/\W/', '', md5(mt_rand())), 0, 10);
  468. $newPassword_sha1 = sha1(strtolower($user) . $newPassword);
  469. // Do some checks on the username if needed.
  470. if ($username !== null)
  471. {
  472. validateUsername($memID, $user);
  473. // Update the database...
  474. updateMemberData($memID, array('member_name' => $user, 'passwd' => $newPassword_sha1));
  475. }
  476. else
  477. updateMemberData($memID, array('passwd' => $newPassword_sha1));
  478. call_integration_hook('integrate_reset_pass', array($old_user, $user, $newPassword));
  479. $replacements = array(
  480. 'USERNAME' => $user,
  481. 'PASSWORD' => $newPassword,
  482. );
  483. $emaildata = loadEmailTemplate('change_password', $replacements, empty($lngfile) || empty($modSettings['userLanguage']) ? $language : $lngfile);
  484. // Send them the email informing them of the change - then we're done!
  485. sendmail($email, $emaildata['subject'], $emaildata['body'], null, 'chgpass' . $memID, false, 0);
  486. }
  487. /**
  488. * Checks a username obeys a load of rules
  489. *
  490. * @param int $memID
  491. * @param string $username
  492. * @param boolean $return_error
  493. * @param boolean $check_reserved_name
  494. * @return string Returns null if fine
  495. */
  496. function validateUsername($memID, $username, $return_error = false, $check_reserved_name = true)
  497. {
  498. global $sourcedir, $txt, $smcFunc, $user_info;
  499. $errors = array();
  500. // Don't use too long a name.
  501. if ($smcFunc['strlen']($username) > 25)
  502. $errors[] = array('lang', 'error_long_name');
  503. // No name?! How can you register with no name?
  504. if ($username == '')
  505. $errors[] = array('lang', 'need_username');
  506. // Only these characters are permitted.
  507. 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)
  508. $errors[] = array('lang', 'error_invalid_characters_username');
  509. if (stristr($username, $txt['guest_title']) !== false)
  510. $errors[] = array('lang', 'username_reserved', 'general', array($txt['guest_title']));
  511. if ($check_reserved_name)
  512. {
  513. require_once($sourcedir . '/Subs-Members.php');
  514. if (isReservedName($username, $memID, false))
  515. $errors[] = array('done', '(' . $smcFunc['htmlspecialchars']($username) . ') ' . $txt['name_in_use']);
  516. }
  517. if ($return_error)
  518. return $errors;
  519. elseif (empty($errors))
  520. return null;
  521. loadLanguage('Errors');
  522. $error = $errors[0];
  523. $message = $error[0] == 'lang' ? (empty($error[3]) ? $txt[$error[1]] : vsprintf($txt[$error[1]], $error[3])) : $error[1];
  524. fatal_error($message, empty($error[2]) || $user_info['is_admin'] ? false : $error[2]);
  525. }
  526. /**
  527. * Checks whether a password meets the current forum rules
  528. * - called when registering/choosing a password.
  529. * - checks the password obeys the current forum settings for password strength.
  530. * - if password checking is enabled, will check that none of the words in restrict_in appear in the password.
  531. * - returns an error identifier if the password is invalid, or null.
  532. *
  533. * @param string $password
  534. * @param string $username
  535. * @param array $restrict_in = array()
  536. * @return string an error identifier if the password is invalid
  537. */
  538. function validatePassword($password, $username, $restrict_in = array())
  539. {
  540. global $modSettings, $smcFunc;
  541. // Perform basic requirements first.
  542. if ($smcFunc['strlen']($password) < (empty($modSettings['password_strength']) ? 4 : 8))
  543. return 'short';
  544. // Is this enough?
  545. if (empty($modSettings['password_strength']))
  546. return null;
  547. // Otherwise, perform the medium strength test - checking if password appears in the restricted string.
  548. if (preg_match('~\b' . preg_quote($password, '~') . '\b~', implode(' ', $restrict_in)) != 0)
  549. return 'restricted_words';
  550. elseif ($smcFunc['strpos']($password, $username) !== false)
  551. return 'restricted_words';
  552. // If just medium, we're done.
  553. if ($modSettings['password_strength'] == 1)
  554. return null;
  555. // Otherwise, hard test next, check for numbers and letters, uppercase too.
  556. $good = preg_match('~(\D\d|\d\D)~', $password) != 0;
  557. $good &= $smcFunc['strtolower']($password) != $password;
  558. return $good ? null : 'chars';
  559. }
  560. /**
  561. * Quickly find out what moderation authority this user has
  562. * - builds the moderator, group and board level querys for the user
  563. * - stores the information on the current users moderation powers in $user_info['mod_cache'] and $_SESSION['mc']
  564. */
  565. function rebuildModCache()
  566. {
  567. global $user_info, $smcFunc;
  568. // What groups can they moderate?
  569. $group_query = allowedTo('manage_membergroups') ? '1=1' : '0=1';
  570. if ($group_query == '0=1')
  571. {
  572. $request = $smcFunc['db_query']('', '
  573. SELECT id_group
  574. FROM {db_prefix}group_moderators
  575. WHERE id_member = {int:current_member}',
  576. array(
  577. 'current_member' => $user_info['id'],
  578. )
  579. );
  580. $groups = array();
  581. while ($row = $smcFunc['db_fetch_assoc']($request))
  582. $groups[] = $row['id_group'];
  583. $smcFunc['db_free_result']($request);
  584. if (empty($groups))
  585. $group_query = '0=1';
  586. else
  587. $group_query = 'id_group IN (' . implode(',', $groups) . ')';
  588. }
  589. // Then, same again, just the boards this time!
  590. $board_query = allowedTo('moderate_forum') ? '1=1' : '0=1';
  591. if ($board_query == '0=1')
  592. {
  593. $boards = boardsAllowedTo('moderate_board', true);
  594. if (empty($boards))
  595. $board_query = '0=1';
  596. else
  597. $board_query = 'id_board IN (' . implode(',', $boards) . ')';
  598. }
  599. // What boards are they the moderator of?
  600. $boards_mod = array();
  601. if (!$user_info['is_guest'])
  602. {
  603. $request = $smcFunc['db_query']('', '
  604. SELECT id_board
  605. FROM {db_prefix}moderators
  606. WHERE id_member = {int:current_member}',
  607. array(
  608. 'current_member' => $user_info['id'],
  609. )
  610. );
  611. while ($row = $smcFunc['db_fetch_assoc']($request))
  612. $boards_mod[] = $row['id_board'];
  613. $smcFunc['db_free_result']($request);
  614. // Can any of the groups they're in moderate any of the boards?
  615. $request = $smcFunc['db_query']('', '
  616. SELECT id_board
  617. FROM {db_prefix}moderator_groups
  618. WHERE id_group IN({array_int:groups})',
  619. array(
  620. 'groups' => $user_info['groups'],
  621. )
  622. );
  623. while ($row = $smcFunc['db_fetch_assoc']($request))
  624. $boards_mod[] = $row['id_board'];
  625. $smcFunc['db_free_result']($request);
  626. // Just in case we've got duplicates here...
  627. $boards_mod = array_unique($boards_mod);
  628. }
  629. $mod_query = empty($boards_mod) ? '0=1' : 'b.id_board IN (' . implode(',', $boards_mod) . ')';
  630. $_SESSION['mc'] = array(
  631. 'time' => time(),
  632. // This looks a bit funny but protects against the login redirect.
  633. 'id' => $user_info['id'] && $user_info['name'] ? $user_info['id'] : 0,
  634. // If you change the format of 'gq' and/or 'bq' make sure to adjust 'can_mod' in Load.php.
  635. 'gq' => $group_query,
  636. 'bq' => $board_query,
  637. 'ap' => boardsAllowedTo('approve_posts'),
  638. 'mb' => $boards_mod,
  639. 'mq' => $mod_query,
  640. );
  641. call_integration_hook('integrate_mod_cache');
  642. $user_info['mod_cache'] = $_SESSION['mc'];
  643. // Might as well clean up some tokens while we are at it.
  644. cleanTokens();
  645. }
  646. /**
  647. * The same thing as setcookie but gives support for HTTP-Only cookies in PHP < 5.2
  648. *
  649. * @param string $name
  650. * @param string $value = ''
  651. * @param int $expire = 0
  652. * @param string $path = ''
  653. * @param string $domain = ''
  654. * @param bool $secure = false
  655. * @param bool $httponly = null
  656. */
  657. function smf_setcookie($name, $value = '', $expire = 0, $path = '', $domain = '', $secure = null, $httponly = null)
  658. {
  659. global $modSettings;
  660. // In case a customization wants to override the default settings
  661. if ($httponly === null)
  662. $httponly = !empty($modSettings['httponlyCookies']);
  663. if ($secure === null)
  664. $secure = !empty($modSettings['secureCookies']);
  665. // Intercept cookie?
  666. call_integration_hook('integrate_cookie', array($name, $value, $expire, $path, $domain, $secure, $httponly));
  667. // This function is pointless if we have PHP >= 5.2.
  668. if (version_compare(PHP_VERSION, '5.2', '>='))
  669. return setcookie($name, $value, $expire, $path, $domain, $secure, $httponly);
  670. // $httponly is the only reason I made this function. If it's not being used, use setcookie().
  671. if (!$httponly)
  672. return setcookie($name, $value, $expire, $path, $domain, $secure);
  673. // Ugh, looks like we have to resort to using a manual process.
  674. header('Set-Cookie: '.rawurlencode($name).'='.rawurlencode($value)
  675. .(empty($domain) ? '' : '; Domain='.$domain)
  676. .(empty($expire) ? '' : '; Max-Age='.$expire)
  677. .(empty($path) ? '' : '; Path='.$path)
  678. .(!$secure ? '' : '; Secure')
  679. .(!$httponly ? '' : '; HttpOnly'), false);
  680. }
  681. ?>