1 | <?php |
||
2 | |||
3 | /** |
||
4 | * Handle sending out reminders, and checking the secret answer and question. It uses just a few functions to do this, which are: |
||
5 | * Simple Machines Forum (SMF) |
||
6 | * |
||
7 | * @package SMF |
||
8 | * @author Simple Machines https://www.simplemachines.org |
||
9 | * @copyright 2022 Simple Machines and individual contributors |
||
10 | * @license https://www.simplemachines.org/about/smf/license.php BSD |
||
11 | * |
||
12 | * @version 2.1.0 |
||
13 | */ |
||
14 | |||
15 | if (!defined('SMF')) |
||
16 | die('No direct access...'); |
||
17 | |||
18 | /** |
||
19 | * This is the controlling delegator |
||
20 | * |
||
21 | * Uses Profile language files and Reminder template |
||
22 | */ |
||
23 | function RemindMe() |
||
24 | { |
||
25 | global $txt, $context; |
||
26 | |||
27 | loadLanguage('Profile'); |
||
28 | loadTemplate('Reminder'); |
||
29 | |||
30 | $context['page_title'] = $txt['authentication_reminder']; |
||
31 | $context['robot_no_index'] = true; |
||
32 | |||
33 | // Delegation can be useful sometimes. |
||
34 | $subActions = array( |
||
35 | 'picktype' => 'RemindPick', |
||
36 | 'secret2' => 'SecretAnswer2', |
||
37 | 'setpassword' => 'setPassword', |
||
38 | 'setpassword2' => 'setPassword2' |
||
39 | ); |
||
40 | |||
41 | // Any subaction? If none, fall through to the main template, which will ask for one. |
||
42 | if (isset($_REQUEST['sa']) && isset($subActions[$_REQUEST['sa']])) |
||
43 | call_helper($subActions[$_REQUEST['sa']]); |
||
44 | |||
45 | // Creating a one time token. |
||
46 | else |
||
47 | createToken('remind'); |
||
48 | } |
||
49 | |||
50 | /** |
||
51 | * Allows the user to pick how they wish to be reminded |
||
52 | */ |
||
53 | function RemindPick() |
||
54 | { |
||
55 | global $context, $txt, $scripturl, $sourcedir, $user_info, $webmaster_email, $smcFunc, $language, $modSettings; |
||
56 | |||
57 | checkSession(); |
||
58 | validateToken('remind'); |
||
59 | createToken('remind'); |
||
60 | |||
61 | // Coming with a known ID? |
||
62 | if (!empty($_REQUEST['uid'])) |
||
63 | { |
||
64 | $where = 'id_member = {int:id_member}'; |
||
65 | $where_params['id_member'] = (int) $_REQUEST['uid']; |
||
0 ignored issues
–
show
Comprehensibility
Best Practice
introduced
by
![]() |
|||
66 | } |
||
67 | elseif (isset($_POST['user']) && $_POST['user'] != '') |
||
68 | { |
||
69 | $where = 'member_name = {string:member_name}'; |
||
70 | $where_params['member_name'] = $_POST['user']; |
||
71 | $where_params['email_address'] = $_POST['user']; |
||
72 | } |
||
73 | |||
74 | // You must enter a username/email address. |
||
75 | if (empty($where)) |
||
76 | fatal_lang_error('username_no_exist', false); |
||
77 | |||
78 | // Make sure we are not being slammed |
||
79 | // Don't call this if you're coming from the "Choose a reminder type" page - otherwise you'll likely get an error |
||
80 | if (!isset($_POST['reminder_type']) || !in_array($_POST['reminder_type'], array('email', 'secret'))) |
||
81 | { |
||
82 | spamProtection('remind'); |
||
83 | } |
||
84 | |||
85 | // Find the user! |
||
86 | $request = $smcFunc['db_query']('', ' |
||
87 | SELECT id_member, real_name, member_name, email_address, is_activated, validation_code, lngfile, secret_question |
||
88 | FROM {db_prefix}members |
||
89 | WHERE ' . $where . ' |
||
0 ignored issues
–
show
Comprehensibility
Best Practice
introduced
by
|
|||
90 | LIMIT 1', |
||
91 | $where_params |
||
0 ignored issues
–
show
Comprehensibility
Best Practice
introduced
by
|
|||
92 | ); |
||
93 | // Maybe email? |
||
94 | if ($smcFunc['db_num_rows']($request) == 0 && empty($_REQUEST['uid'])) |
||
95 | { |
||
96 | $smcFunc['db_free_result']($request); |
||
97 | |||
98 | $request = $smcFunc['db_query']('', ' |
||
99 | SELECT id_member, real_name, member_name, email_address, is_activated, validation_code, lngfile, secret_question |
||
100 | FROM {db_prefix}members |
||
101 | WHERE email_address = {string:email_address} |
||
102 | LIMIT 1', |
||
103 | $where_params |
||
104 | ); |
||
105 | if ($smcFunc['db_num_rows']($request) == 0) |
||
106 | fatal_lang_error('no_user_with_email', false); |
||
107 | } |
||
108 | |||
109 | $row = $smcFunc['db_fetch_assoc']($request); |
||
110 | $smcFunc['db_free_result']($request); |
||
111 | |||
112 | // If the user isn't activated/approved, give them some feedback on what to do next. |
||
113 | if ($row['is_activated'] != 1) |
||
114 | { |
||
115 | // Awaiting approval... |
||
116 | if (trim($row['validation_code']) == '') |
||
117 | fatal_error(sprintf($txt['registration_not_approved'], $scripturl . '?action=activate;user=' . $_POST['user']), false); |
||
118 | else |
||
119 | fatal_error(sprintf($txt['registration_not_activated'], $scripturl . '?action=activate;user=' . $_POST['user']), false); |
||
120 | } |
||
121 | |||
122 | // You can't get emailed if you have no email address. |
||
123 | $row['email_address'] = trim($row['email_address']); |
||
124 | if ($row['email_address'] == '') |
||
125 | fatal_error($txt['no_reminder_email'] . '<br>' . $txt['send_email_to'] . ' <a href="mailto:' . $webmaster_email . '">' . $txt['webmaster'] . '</a> ' . $txt['to_ask_password']); |
||
126 | |||
127 | // If they have no secret question then they can only get emailed the item, or they are requesting the email, send them an email. |
||
128 | if (empty($row['secret_question']) || (isset($_POST['reminder_type']) && $_POST['reminder_type'] == 'email')) |
||
129 | { |
||
130 | // Randomly generate a new password, with only alpha numeric characters that is a max length of 10 chars. |
||
131 | require_once($sourcedir . '/Subs-Members.php'); |
||
132 | $password = generateValidationCode(); |
||
133 | |||
134 | require_once($sourcedir . '/Subs-Post.php'); |
||
135 | $replacements = array( |
||
136 | 'REALNAME' => $row['real_name'], |
||
137 | 'REMINDLINK' => $scripturl . '?action=reminder;sa=setpassword;u=' . $row['id_member'] . ';code=' . $password, |
||
138 | 'IP' => $user_info['ip'], |
||
139 | 'MEMBERNAME' => $row['member_name'], |
||
140 | ); |
||
141 | |||
142 | $emaildata = loadEmailTemplate('forgot_password', $replacements, empty($row['lngfile']) || empty($modSettings['userLanguage']) ? $language : $row['lngfile']); |
||
143 | $context['description'] = $txt['reminder_sent']; |
||
144 | |||
145 | sendmail($row['email_address'], $emaildata['subject'], $emaildata['body'], null, 'reminder', $emaildata['is_html'], 1); |
||
146 | |||
147 | // Set the password in the database. |
||
148 | updateMemberData($row['id_member'], array('validation_code' => substr(md5($password), 0, 10))); |
||
149 | |||
150 | // Set up the template. |
||
151 | $context['sub_template'] = 'sent'; |
||
152 | |||
153 | // Don't really. |
||
154 | return; |
||
155 | } |
||
156 | // Otherwise are ready to answer the question? |
||
157 | elseif (isset($_POST['reminder_type']) && $_POST['reminder_type'] == 'secret') |
||
158 | { |
||
159 | return SecretAnswerInput(); |
||
160 | } |
||
161 | |||
162 | // No we're here setup the context for template number 2! |
||
163 | $context['sub_template'] = 'reminder_pick'; |
||
164 | $context['current_member'] = array( |
||
165 | 'id' => $row['id_member'], |
||
166 | 'name' => $row['member_name'], |
||
167 | ); |
||
168 | } |
||
169 | |||
170 | /** |
||
171 | * Allows the user to set their new password |
||
172 | */ |
||
173 | function setPassword() |
||
174 | { |
||
175 | global $txt, $context; |
||
176 | |||
177 | loadLanguage('Login'); |
||
178 | |||
179 | // You need a code! |
||
180 | if (!isset($_REQUEST['code'])) |
||
181 | fatal_lang_error('no_access', false); |
||
182 | |||
183 | // Fill the context array. |
||
184 | $context += array( |
||
185 | 'page_title' => $txt['reminder_set_password'], |
||
186 | 'sub_template' => 'set_password', |
||
187 | 'code' => $_REQUEST['code'], |
||
188 | 'memID' => (int) $_REQUEST['u'] |
||
189 | ); |
||
190 | |||
191 | loadJavaScriptFile('register.js', array('defer' => false, 'minimize' => true), 'smf_register'); |
||
192 | |||
193 | // Tokens! |
||
194 | createToken('remind-sp'); |
||
195 | } |
||
196 | |||
197 | /** |
||
198 | * Actually sets the new password |
||
199 | */ |
||
200 | function setPassword2() |
||
201 | { |
||
202 | global $context, $modSettings, $txt, $smcFunc, $sourcedir; |
||
203 | |||
204 | checkSession(); |
||
205 | validateToken('remind-sp'); |
||
206 | |||
207 | if (empty($_POST['u']) || !isset($_POST['passwrd1']) || !isset($_POST['passwrd2'])) |
||
208 | fatal_lang_error('no_access', false); |
||
209 | |||
210 | $_POST['u'] = (int) $_POST['u']; |
||
211 | |||
212 | if ($_POST['passwrd1'] != $_POST['passwrd2']) |
||
213 | fatal_lang_error('passwords_dont_match', false); |
||
214 | |||
215 | if ($_POST['passwrd1'] == '') |
||
216 | fatal_lang_error('no_password', false); |
||
217 | |||
218 | loadLanguage('Login'); |
||
219 | |||
220 | // Get the code as it should be from the database. |
||
221 | $request = $smcFunc['db_query']('', ' |
||
222 | SELECT validation_code, member_name, email_address, passwd_flood |
||
223 | FROM {db_prefix}members |
||
224 | WHERE id_member = {int:id_member} |
||
225 | AND is_activated = {int:is_activated} |
||
226 | AND validation_code != {string:blank_string} |
||
227 | LIMIT 1', |
||
228 | array( |
||
229 | 'id_member' => $_POST['u'], |
||
230 | 'is_activated' => 1, |
||
231 | 'blank_string' => '', |
||
232 | ) |
||
233 | ); |
||
234 | |||
235 | // Does this user exist at all? |
||
236 | if ($smcFunc['db_num_rows']($request) == 0) |
||
237 | fatal_lang_error('invalid_userid', false); |
||
238 | |||
239 | list ($realCode, $username, $email, $flood_value) = $smcFunc['db_fetch_row']($request); |
||
240 | $smcFunc['db_free_result']($request); |
||
241 | |||
242 | // Is the password actually valid? |
||
243 | require_once($sourcedir . '/Subs-Auth.php'); |
||
244 | $passwordError = validatePassword($_POST['passwrd1'], $username, array($email)); |
||
245 | |||
246 | // What - it's not? |
||
247 | if ($passwordError != null) |
||
248 | if ($passwordError == 'short') |
||
249 | fatal_lang_error('profile_error_password_' . $passwordError, false, array(empty($modSettings['password_strength']) ? 4 : 8)); |
||
250 | else |
||
251 | fatal_lang_error('profile_error_password_' . $passwordError, false); |
||
252 | |||
253 | require_once($sourcedir . '/LogInOut.php'); |
||
254 | |||
255 | // Quit if this code is not right. |
||
256 | if (empty($_POST['code']) || substr($realCode, 0, 10) !== substr(md5($_POST['code']), 0, 10)) |
||
257 | { |
||
258 | // Stop brute force attacks like this. |
||
259 | validatePasswordFlood($_POST['u'], $flood_value, false); |
||
260 | |||
261 | fatal_error($txt['invalid_activation_code'], false); |
||
262 | } |
||
263 | |||
264 | // Just in case, flood control. |
||
265 | validatePasswordFlood($_POST['u'], $flood_value, true); |
||
266 | |||
267 | // User validated. Update the database! |
||
268 | updateMemberData($_POST['u'], array('validation_code' => '', 'passwd' => hash_password($username, $_POST['passwrd1']))); |
||
269 | |||
270 | call_integration_hook('integrate_reset_pass', array($username, $username, $_POST['passwrd1'])); |
||
271 | |||
272 | loadTemplate('Login'); |
||
273 | $context += array( |
||
274 | 'page_title' => $txt['reminder_password_set'], |
||
275 | 'sub_template' => 'login', |
||
276 | 'default_username' => $username, |
||
277 | 'default_password' => $_POST['passwrd1'], |
||
278 | 'never_expire' => false, |
||
279 | 'description' => $txt['reminder_password_set'] |
||
280 | ); |
||
281 | |||
282 | createToken('login'); |
||
283 | } |
||
284 | |||
285 | /** |
||
286 | * Allows the user to enter their secret answer |
||
287 | */ |
||
288 | function SecretAnswerInput() |
||
289 | { |
||
290 | global $context, $smcFunc; |
||
291 | |||
292 | checkSession(); |
||
293 | |||
294 | // Strings for the register auto javascript clever stuffy wuffy. |
||
295 | loadLanguage('Login'); |
||
296 | |||
297 | // Check they entered something... |
||
298 | if (empty($_REQUEST['uid'])) |
||
299 | fatal_lang_error('username_no_exist', false); |
||
300 | |||
301 | // Get the stuff.... |
||
302 | $request = $smcFunc['db_query']('', ' |
||
303 | SELECT id_member, real_name, member_name, secret_question |
||
304 | FROM {db_prefix}members |
||
305 | WHERE id_member = {int:id_member} |
||
306 | LIMIT 1', |
||
307 | array( |
||
308 | 'id_member' => (int) $_REQUEST['uid'], |
||
309 | ) |
||
310 | ); |
||
311 | if ($smcFunc['db_num_rows']($request) == 0) |
||
312 | fatal_lang_error('username_no_exist', false); |
||
313 | |||
314 | $row = $smcFunc['db_fetch_assoc']($request); |
||
315 | $smcFunc['db_free_result']($request); |
||
316 | |||
317 | // If there is NO secret question - then throw an error. |
||
318 | if (trim($row['secret_question']) == '') |
||
319 | fatal_lang_error('registration_no_secret_question', false); |
||
320 | |||
321 | // Ask for the answer... |
||
322 | $context['remind_user'] = $row['id_member']; |
||
323 | $context['remind_type'] = ''; |
||
324 | $context['secret_question'] = $row['secret_question']; |
||
325 | |||
326 | $context['sub_template'] = 'ask'; |
||
327 | createToken('remind-sai'); |
||
328 | loadJavaScriptFile('register.js', array('defer' => false, 'minimize' => true), 'smf_register'); |
||
329 | } |
||
330 | |||
331 | /** |
||
332 | * Validates the secret answer input by the user |
||
333 | */ |
||
334 | function SecretAnswer2() |
||
335 | { |
||
336 | global $txt, $context, $modSettings, $smcFunc, $sourcedir; |
||
337 | |||
338 | checkSession(); |
||
339 | validateToken('remind-sai'); |
||
340 | |||
341 | // Hacker? How did you get this far without an email or username? |
||
342 | if (empty($_REQUEST['uid'])) |
||
343 | fatal_lang_error('username_no_exist', false); |
||
344 | |||
345 | loadLanguage('Login'); |
||
346 | |||
347 | // Get the information from the database. |
||
348 | $request = $smcFunc['db_query']('', ' |
||
349 | SELECT id_member, real_name, member_name, secret_answer, secret_question, email_address |
||
350 | FROM {db_prefix}members |
||
351 | WHERE id_member = {int:id_member} |
||
352 | LIMIT 1', |
||
353 | array( |
||
354 | 'id_member' => $_REQUEST['uid'], |
||
355 | ) |
||
356 | ); |
||
357 | if ($smcFunc['db_num_rows']($request) == 0) |
||
358 | fatal_lang_error('username_no_exist', false); |
||
359 | |||
360 | $row = $smcFunc['db_fetch_assoc']($request); |
||
361 | $smcFunc['db_free_result']($request); |
||
362 | |||
363 | /* |
||
364 | * Check if the secret answer is correct. |
||
365 | * In 2.1 this was changed to use hash_(verify_)passsword, same as the password. The length of the hash is 60 characters. |
||
366 | * Prior to 2.1 this was a simple md5. The length of the hash is 32 characters. |
||
367 | * For compatibility with older answers, we still check if a match occurs on md5. As there is a difference in the hash lengths, there isn't a possiblity of a cross match between the hashes. This will ensure in future answer updates will prevent md5 methods from working. |
||
368 | */ |
||
369 | if ($row['secret_question'] == '' || $row['secret_answer'] == '' || (!hash_verify_password($row['member_name'], $_POST['secret_answer'], $row['secret_answer']) && md5($_POST['secret_answer']) != $row['secret_answer'])) |
||
370 | { |
||
371 | log_error(sprintf($txt['reminder_error'], $row['member_name']), 'user'); |
||
372 | fatal_lang_error('incorrect_answer', false); |
||
373 | } |
||
374 | |||
375 | // You can't use a blank one! |
||
376 | if (strlen(trim($_POST['passwrd1'])) === 0) |
||
377 | fatal_lang_error('no_password', false); |
||
378 | |||
379 | // They have to be the same too. |
||
380 | if ($_POST['passwrd1'] != $_POST['passwrd2']) |
||
381 | fatal_lang_error('passwords_dont_match', false); |
||
382 | |||
383 | // Make sure they have a strong enough password. |
||
384 | require_once($sourcedir . '/Subs-Auth.php'); |
||
385 | $passwordError = validatePassword($_POST['passwrd1'], $row['member_name'], array($row['email_address'])); |
||
386 | |||
387 | // Invalid? |
||
388 | if ($passwordError != null) |
||
389 | if ($passwordError == 'short') |
||
390 | fatal_lang_error('profile_error_password_' . $passwordError, false, array(empty($modSettings['password_strength']) ? 4 : 8)); |
||
391 | else |
||
392 | fatal_lang_error('profile_error_password_' . $passwordError, false); |
||
393 | |||
394 | // Alright, so long as 'yer sure. |
||
395 | updateMemberData($row['id_member'], array('passwd' => hash_password($row['member_name'], $_POST['passwrd1']))); |
||
396 | |||
397 | call_integration_hook('integrate_reset_pass', array($row['member_name'], $row['member_name'], $_POST['passwrd1'])); |
||
398 | |||
399 | // Tell them it went fine. |
||
400 | loadTemplate('Login'); |
||
401 | $context += array( |
||
402 | 'page_title' => $txt['reminder_password_set'], |
||
403 | 'sub_template' => 'login', |
||
404 | 'default_username' => $row['member_name'], |
||
405 | 'default_password' => $_POST['passwrd1'], |
||
406 | 'never_expire' => false, |
||
407 | 'description' => $txt['reminder_password_set'] |
||
408 | ); |
||
409 | |||
410 | createToken('login'); |
||
411 | } |
||
412 | |||
413 | ?> |