function getNewConfirmUrl($userid, $handle) { require_once QA_INCLUDE_DIR . 'db/users.php'; $emailcode = qa_db_user_rand_emailcode(); qa_db_user_set($userid, 'emailcode', $emailcode); return "http://" . $_SERVER['HTTP_HOST'] . "/bmf/verify.php?c=" . $emailcode . "&u=" . $handle; }
function qa_complete_reset_user($userid) { require_once QA_INCLUDE_DIR . 'qa-util-string.php'; require_once QA_INCLUDE_DIR . 'qa-app-options.php'; require_once QA_INCLUDE_DIR . 'qa-app-emails.php'; require_once QA_INCLUDE_DIR . 'qa-app-cookies.php'; require_once QA_INCLUDE_DIR . 'qa-db-selects.php'; $password = qa_random_alphanum(max(QA_MIN_PASSWORD_LEN, QA_NEW_PASSWORD_LEN)); $userinfo = qa_db_select_with_pending(qa_db_user_account_selectspec($userid, true)); if (!qw_send_notification($userid, $userinfo['email'], $userinfo['handle'], qa_lang('emails/new_password_subject'), nl2br(qa_lang('emails/new_password_body')), array('^password' => $password, '^url' => qa_opt('site_url')))) { qa_fatal_error('Could not send new password - password not reset'); } qa_db_user_set_password($userid, $password); // do this last, to be safe qa_db_user_set($userid, 'emailcode', ''); // so can't be reused qa_report_event('u_reset', $userid, $userinfo['handle'], qa_cookie_get(), array('email' => $userinfo['email'])); }
function qa_set_logged_in_user($userid, $handle = '', $remember = false, $source = null) { if (qa_to_override(__FUNCTION__)) { $args = func_get_args(); return qa_call_override(__FUNCTION__, $args); } require_once QA_INCLUDE_DIR . 'app/cookies.php'; qa_start_session(); if (isset($userid)) { qa_set_session_user($userid, $source); // PHP sessions time out too quickly on the server side, so we also set a cookie as backup. // Logging in from a second browser will make the previous browser's 'Remember me' no longer // work - I'm not sure if this is the right behavior - could see it either way. require_once QA_INCLUDE_DIR . 'db/selects.php'; $userinfo = qa_db_single_select(qa_db_user_account_selectspec($userid, true)); // if we have logged in before, and are logging in the same way as before, we don't need to change the sessioncode/source // this means it will be possible to automatically log in (via cookies) to the same account from more than one browser if (empty($userinfo['sessioncode']) || $source !== $userinfo['sessionsource']) { $sessioncode = qa_db_user_rand_sessioncode(); qa_db_user_set($userid, 'sessioncode', $sessioncode); qa_db_user_set($userid, 'sessionsource', $source); } else { $sessioncode = $userinfo['sessioncode']; } qa_db_user_logged_in($userid, qa_remote_ip_address()); qa_set_session_cookie($handle, $sessioncode, $remember); qa_report_event('u_login', $userid, $userinfo['handle'], qa_cookie_get()); } else { $olduserid = qa_get_logged_in_userid(); $oldhandle = qa_get_logged_in_handle(); qa_clear_session_cookie(); qa_clear_session_user(); qa_report_event('u_logout', $olduserid, $oldhandle, qa_cookie_get()); } }
function qa_mailing_send_one($userid, $handle, $email, $emailcode) { require_once QA_INCLUDE_DIR . 'qa-app-emails.php'; require_once QA_INCLUDE_DIR . 'qa-db-users.php'; if (!strlen(trim($emailcode))) { $emailcode = qa_db_user_rand_emailcode(); qa_db_user_set($userid, 'emailcode', $emailcode); } $unsubscribeurl = qa_path_absolute('unsubscribe', array('c' => $emailcode, 'u' => $handle)); return qa_send_email(array('fromemail' => qa_opt('mailing_from_email'), 'fromname' => qa_opt('mailing_from_name'), 'toemail' => $email, 'toname' => $handle, 'subject' => qa_opt('mailing_subject'), 'body' => trim(qa_opt('mailing_body')) . "\n\n\n" . qa_lang('users/unsubscribe') . ' ' . $unsubscribeurl, 'html' => false)); }
if (!qa_check_form_security_code('password', qa_post_text('code'))) { $errors['page'] = qa_lang_html('misc/form_security_again'); } else { $errors = array(); if ($haspassword && strtolower(qa_db_calc_passcheck($inoldpassword, $useraccount['passsalt'])) != strtolower($useraccount['passcheck'])) { $errors['oldpassword'] = qa_lang('users/password_wrong'); } $useraccount['password'] = $inoldpassword; $errors = $errors + qa_password_validate($innewpassword1, $useraccount); // array union if ($innewpassword1 != $innewpassword2) { $errors['newpassword2'] = qa_lang('users/password_mismatch'); } if (empty($errors)) { qa_db_user_set_password($userid, $innewpassword1); qa_db_user_set($userid, 'sessioncode', ''); // stop old 'Remember me' style logins from still working qa_set_logged_in_user($userid, $useraccount['handle'], false, $useraccount['sessionsource']); // reinstate this specific session qa_report_event('u_password', $userid, $useraccount['handle'], qa_cookie_get()); qa_redirect('account', array('state' => 'password-changed')); } } } // Prepare content for theme $qa_content = qa_content_prepare(); $qa_content['title'] = qa_lang_html('profile/my_account_title'); $qa_content['error'] = @$errors['page']; $qa_content['form_profile'] = array('tags' => 'enctype="multipart/form-data" method="post" action="' . qa_self_html() . '"', 'style' => 'wide', 'fields' => array('duration' => array('type' => 'static', 'label' => qa_lang_html('users/member_for'), 'value' => qa_time_to_string(qa_opt('db_time') - $useraccount['created'])), 'type' => array('type' => 'static', 'label' => qa_lang_html('users/member_type'), 'value' => qa_html(qa_user_level_string($useraccount['level'])), 'note' => $isblocked ? qa_lang_html('users/user_blocked') : null), 'handle' => array('label' => qa_lang_html('users/handle_label'), 'tags' => 'name="handle"', 'value' => qa_html(isset($inhandle) ? $inhandle : $useraccount['handle']), 'error' => qa_html(@$errors['handle']), 'type' => $changehandle && !$isblocked ? 'text' : 'static'), 'email' => array('label' => qa_lang_html('users/email_label'), 'tags' => 'name="email"', 'value' => qa_html(isset($inemail) ? $inemail : $useraccount['email']), 'error' => isset($errors['email']) ? qa_html($errors['email']) : ($doconfirms && !$isconfirmed ? qa_insert_login_links(qa_lang_html('users/email_please_confirm')) : null), 'type' => $isblocked ? 'static' : 'text'), 'messages' => array('label' => qa_lang_html('users/private_messages'), 'tags' => 'name="messages"', 'type' => 'checkbox', 'value' => !($useraccount['flags'] & QA_USER_FLAGS_NO_MESSAGES), 'note' => qa_lang_html('users/private_messages_explanation')), 'wall' => array('label' => qa_lang_html('users/wall_posts'), 'tags' => 'name="wall"', 'type' => 'checkbox', 'value' => !($useraccount['flags'] & QA_USER_FLAGS_NO_WALL_POSTS), 'note' => qa_lang_html('users/wall_posts_explanation')), 'mailings' => array('label' => qa_lang_html('users/mass_mailings'), 'tags' => 'name="mailings"', 'type' => 'checkbox', 'value' => !($useraccount['flags'] & QA_USER_FLAGS_NO_MAILINGS), 'note' => qa_lang_html('users/mass_mailings_explanation')), 'avatar' => null), 'buttons' => array('save' => array('tags' => 'onclick="qa_show_waiting_after(this, false);"', 'label' => qa_lang_html('users/save_profile'))), 'hidden' => array('dosaveprofile' => '1', 'code' => qa_get_form_security_code('account'))); if (qa_get_state() == 'profile-saved') { $qa_content['form_profile']['ok'] = qa_lang_html('users/profile_saved');
} foreach ($userfields as $userfield) { if (!isset($errors[$userfield['fieldid']])) { qa_db_user_profile_set($userid, $userfield['title'], $inprofile[$userfield['fieldid']]); } } if (count($errors)) { $userediting = true; } qa_report_event('u_edit', $loginuserid, qa_get_logged_in_handle(), qa_cookie_get(), array('userid' => $userid, 'handle' => $useraccount['handle'])); } if (isset($maxlevelassign)) { $inlevel = min($maxlevelassign, (int) qa_post_text('level')); // constrain based on maximum permitted to prevent simple browser-based attack if ($inlevel != $useraccount['level']) { qa_db_user_set($userid, 'level', $inlevel); qa_report_event('u_level', $loginuserid, qa_get_logged_in_handle(), qa_cookie_get(), array('userid' => $userid, 'handle' => $useraccount['handle'], 'level' => $inlevel, 'oldlevel' => $useraccount['level'])); } } if (empty($errors)) { qa_redirect(qa_request()); } list($useraccount, $userprofile) = qa_db_select_with_pending(qa_db_user_account_selectspec($userid, true), qa_db_user_profile_selectspec($userid, true)); } } if (isset($maxlevelassign) && $useraccount['level'] < QA_USER_LEVEL_MODERATOR) { if (qa_clicked('doblock')) { require_once QA_INCLUDE_DIR . 'qa-db-users.php'; qa_db_user_set_flag($userid, QA_USER_FLAGS_USER_BLOCKED, true); qa_report_event('u_block', $loginuserid, qa_get_logged_in_handle(), qa_cookie_get(), array('userid' => $userid, 'handle' => $useraccount['handle'])); qa_redirect(qa_request());
require_once QA_INCLUDE_DIR . 'qa-app-blobs.php'; qa_db_user_set($userid, 'avatarblobid', null); qa_db_user_set($userid, 'avatarwidth', null); qa_db_user_set($userid, 'avatarheight', null); qa_delete_blob($useraccount['avatarblobid']); } } if ($fieldseditable) { $filterhandle = $handle; // we're not filtering the handle... $errors = qa_handle_email_filter($filterhandle, $inemail, $useraccount); unset($errors['handle']); // ...and we don't care about any errors in it if (!isset($errors['email'])) { if ($inemail != $useraccount['email']) { qa_db_user_set($userid, 'email', $inemail); qa_db_user_set_flag($userid, QA_USER_FLAGS_EMAIL_CONFIRMED, false); } } if (count($inprofile)) { $filtermodules = qa_load_modules_with('filter', 'filter_profile'); foreach ($filtermodules as $filtermodule) { $filtermodule->filter_profile($inprofile, $errors, $useraccount, $userprofile); } } foreach ($userfields as $userfield) { if (!isset($errors[$userfield['fieldid']])) { qa_db_user_profile_set($userid, $userfield['title'], $inprofile[$userfield['fieldid']]); } } if (count($errors)) {
function check_merge(&$useraccount, &$mylogins, $tolink) { global $qa_cached_logged_in_user, $qa_logged_in_userid_checked; $userid = $findid = $useraccount['userid']; $findemail = $useraccount['oemail']; // considering this is an openid user, so use the openid email if (empty($findemail)) { $findemail = $useraccount['email']; // fallback } if ($tolink) { // user is logged in with $userid but wants to merge $findid $findemail = null; $findid = $tolink['userid']; } else { if (qa_get('confirm') == 2 || qa_post_text('confirm') == 2) { // bogus confirm page, stop right here qa_redirect('logins'); } } // find other un-linked accounts with the same email $otherlogins = qa_db_user_login_find_other__open($findid, $findemail, $userid); if (qa_clicked('domerge') && !empty($otherlogins)) { // if cancel was requested, just redirect if ($_POST['domerge'] == 0) { $tourl = qa_post_text('to'); if (!empty($tourl)) { qa_redirect($tourl); } else { qa_redirect($tolink ? 'logins' : ''); } } // a request to merge (link) multiple accounts was made require_once QA_INCLUDE_DIR . 'qa-app-users-edit.php'; $recompute = false; $email = null; $baseid = $_POST["base{$_POST['domerge']}"]; // POST[base1] or POST[base2] // see which account was selected, if any if ($baseid != 0) { // just in case foreach ($otherlogins as $login) { // see if this is the currently logged in account $loginid = $login['details']['userid']; $is_current = $loginid == $userid; // see if this user was selected for merge if (isset($_POST["user_{$loginid}"]) || $is_current) { if ($baseid != $loginid) { // this account should be deleted as it's different from the selected base id if (!empty($login['logins'])) { // update all associated logins qa_db_user_login_sync(true); qa_db_user_login_replace_userid__open($loginid, $baseid); qa_db_user_login_sync(false); } // delete old user but keep the email qa_delete_user($loginid); $recompute = true; if (empty($email)) { $email = $login['details']['email']; } if (empty($email)) { $email = $login['details']['oemail']; } } } } } // recompute the stats, if needed if ($recompute) { require_once QA_INCLUDE_DIR . 'qa-db-points.php'; qa_db_userpointscount_update(); // check if the current account has been deleted if ($userid != $baseid) { $oldsrc = $useraccount['sessionsource']; qa_set_logged_in_user($baseid, $useraccount['handle'], false, $oldsrc); $useraccount = qa_db_user_find_by_id__open($baseid); $userid = $baseid; // clear some cached data qa_db_flush_pending_result('loggedinuser'); $qa_logged_in_userid_checked = false; unset($qa_cached_logged_in_user); } // also check the email address on the remaining user account if (empty($useraccount['email']) && !empty($email)) { // update the account if the email address is not used anymore $emailusers = qa_db_user_find_by_email($email); if (count($emailusers) == 0) { qa_db_user_set($userid, 'email', $email); $useraccount['email'] = $email; // to show on the page } } } $conf = qa_post_text('confirm'); $tourl = qa_post_text('to'); if ($conf) { $tourl = qa_post_text('to'); if (!empty($tourl)) { qa_redirect($tourl); } else { qa_redirect($tolink ? 'logins' : ''); } } // update the arrays $otherlogins = qa_db_user_login_find_other__open($userid, $findemail); $mylogins = qa_db_user_login_find_mine__open($userid); } // remove the current user id unset($otherlogins[$userid]); return $otherlogins; }
function qa_set_user_avatar($userid, $imagedata, $oldblobid = null) { if (qa_to_override(__FUNCTION__)) { $args = func_get_args(); return qa_call_override(__FUNCTION__, $args); } require_once QA_INCLUDE_DIR . 'qa-util-image.php'; $imagedata = qa_image_constrain_data($imagedata, $width, $height, qa_opt('avatar_store_size')); if (isset($imagedata)) { require_once QA_INCLUDE_DIR . 'qa-db-blobs.php'; $newblobid = qa_db_blob_create($imagedata, 'jpeg', null, $userid, null, qa_remote_ip_address()); if (isset($newblobid)) { qa_db_user_set($userid, 'avatarblobid', $newblobid); qa_db_user_set($userid, 'avatarwidth', $width); qa_db_user_set($userid, 'avatarheight', $height); qa_db_user_set_flag($userid, QA_USER_FLAGS_SHOW_AVATAR, true); qa_db_user_set_flag($userid, QA_USER_FLAGS_SHOW_GRAVATAR, false); if (isset($oldblobid)) { qa_db_blob_delete($oldblobid); } return true; } } return false; }
if (qa_clicked('dochangepassword')) { require_once QA_INCLUDE_DIR . 'qa-app-users-edit.php'; $inoldpassword = qa_post_text('oldpassword'); $innewpassword1 = qa_post_text('newpassword1'); $innewpassword2 = qa_post_text('newpassword2'); $errors = array(); if ($haspassword && strtolower(qa_db_calc_passcheck($inoldpassword, $useraccount['passsalt'])) != strtolower($useraccount['passcheck'])) { $errors['oldpassword'] = qa_lang_html('users/password_wrong'); } $errors = array_merge($errors, qa_password_validate($innewpassword1)); if ($innewpassword1 != $innewpassword2) { $errors['newpassword2'] = qa_lang_html('users/password_mismatch'); } if (empty($errors)) { qa_db_user_set_password($qa_login_userid, $innewpassword1); qa_db_user_set($qa_login_userid, 'sessioncode', ''); // stop old 'Remember me' style logins from still working qa_set_logged_in_user($qa_login_userid, $useraccount['handle'], false, $useraccount['sessionsource']); // reinstate this specific session qa_report_event('u_password', $qa_login_userid, $useraccount['handle'], $qa_cookieid); qa_redirect('account', array('state' => 'password-changed')); } } // Prepare content for theme $qa_content = qa_content_prepare(); $qa_content['title'] = qa_lang_html('profile/my_account_title'); $qa_content['form_profile'] = array('tags' => 'ENCTYPE="multipart/form-data" METHOD="POST" ACTION="' . qa_self_html() . '"', 'style' => 'wide', 'fields' => array('duration' => array('type' => 'static', 'label' => qa_lang_html('users/member_for'), 'value' => qa_time_to_string(qa_opt('db_time') - $useraccount['created'])), 'type' => array('type' => 'static', 'label' => qa_lang_html('users/member_type'), 'value' => qa_html(qa_user_level_string($useraccount['level']))), 'handle' => array('label' => qa_lang_html('users/handle_label'), 'tags' => 'NAME="handle"', 'value' => qa_html(isset($inhandle) ? $inhandle : $useraccount['handle']), 'error' => qa_html(@$errors['handle']), 'type' => $changehandle ? 'text' : 'static'), 'email' => array('label' => qa_lang_html('users/email_label'), 'tags' => 'NAME="email"', 'value' => qa_html(isset($inemail) ? $inemail : $useraccount['email']), 'error' => isset($errors['email']) ? qa_html($errors['email']) : ($doconfirms && !$isconfirmed ? qa_insert_login_links(qa_lang_html('users/email_please_confirm')) : null)), 'notify_annoucements' => array('type' => 'checkbox', 'label' => qa_lang_html('users/notify_announcements_label'), 'tags' => 'NAME="notify_an"', 'value' => !($useraccount['flags'] & QA_USER_FLAGS_NOTIFY_ANNOUNCEMENTS), 'note' => qa_lang_html('users/notify_annoucements_explanation')), 'notify_questions' => array('type' => 'checkbox', 'label' => qa_lang_html('users/notify_questions_label'), 'tags' => 'NAME="notify_q"', 'value' => !($useraccount['flags'] & QA_USER_FLAGS_NOTIFY_QUESTIONS), 'note' => qa_lang_html('users/notify_questions_explanation')), 'notify_answers' => array('type' => 'checkbox', 'label' => qa_lang_html('users/notify_answers_label'), 'tags' => 'NAME="notify_a"', 'value' => !($useraccount['flags'] & QA_USER_FLAGS_NOTIFY_ANSWERS), 'note' => qa_lang_html('users/notify_answers_explanation')), 'messages' => array('label' => qa_lang_html('users/private_messages'), 'tags' => 'NAME="messages"', 'type' => 'checkbox', 'value' => !($useraccount['flags'] & QA_USER_FLAGS_NO_MESSAGES), 'note' => qa_lang_html('users/private_messages_explanation')), 'avatar' => null), 'buttons' => array('save' => array('label' => qa_lang_html('users/save_profile'))), 'hidden' => array('dosaveprofile' => '1')); if ($qa_state == 'profile-saved') { $qa_content['form_profile']['ok'] = qa_lang_html('users/profile_saved'); } if (!qa_opt('allow_private_messages')) {
function qa_set_logged_in_user($userid, $handle = '', $remember = false, $source = null, $categoryid = null) { require_once QA_INCLUDE_DIR . 'qa-app-cookies.php'; qa_start_session(); $suffix = qa_session_var_suffix(); if (isset($userid)) { $_SESSION['qa_session_userid_' . $suffix] = $userid; $_SESSION['qa_session_source_' . $suffix] = $source; $_SESSION['qa_session_verify_' . $suffix] = qa_session_verify_code($userid); $_SESSION['mp_session_category_id_' . $suffix] = $categoryid; // prevents one account on a shared server being able to create a log in a user to Q2A on another account on same server // PHP sessions time out too quickly on the server side, so we also set a cookie as backup. // Logging in from a second browser will make the previous browser's 'Remember me' no longer // work - I'm not sure if this is the right behavior - could see it either way. require_once QA_INCLUDE_DIR . 'qa-db-selects.php'; $userinfo = qa_db_single_select(qa_db_user_account_selectspec($userid, true)); // if we have logged in before, and are logging in the same way as before, we don't need to change the sessioncode/source // this means it will be possible to automatically log in (via cookies) to the same account from more than one browser if (empty($userinfo['sessioncode']) || $source !== $userinfo['sessionsource']) { $sessioncode = qa_db_user_rand_sessioncode(); qa_db_user_set($userid, 'sessioncode', $sessioncode); qa_db_user_set($userid, 'sessionsource', $source); } else { $sessioncode = $userinfo['sessioncode']; } qa_db_user_logged_in($userid, qa_remote_ip_address()); //qa_set_session_cookie($handle, $sessioncode, $remember); qa_set_session_cookie($handle, $sessioncode, $remember, $categoryid); qa_report_event('u_login', $userid, $userinfo['handle'], qa_cookie_get()); } else { $olduserid = qa_get_logged_in_userid(); $oldhandle = qa_get_logged_in_handle(); qa_clear_session_cookie(); unset($_SESSION['qa_session_userid_' . $suffix]); unset($_SESSION['qa_session_source_' . $suffix]); unset($_SESSION['qa_session_verify_' . $suffix]); unset($_SESSION['mp_session_category_id_' . $suffix]); qa_report_event('u_logout', $olduserid, $oldhandle, qa_cookie_get()); } }
/** * Overrides the default mechanism of logging in from external sources. * * Adds a different way of tracking the sessions and performs some * additional tasks when creating an user account (setting new fields, * extra checks, etc). */ function qa_log_in_external_user($source, $identifier, $fields) { require_once QA_INCLUDE_DIR . 'qa-db-users.php'; $remember = qa_opt('open_login_remember') ? true : false; $users = qa_db_user_login_find($source, $identifier); $countusers = count($users); if ($countusers > 1) { qa_fatal_error('External login mapped to more than one user'); } // should never happen /* * To allow for more than one account from the same openid/openauth provider to be * linked to an Q2A user, we need to override the way session source is stored * Supposing userid 01 is linked to 2 yahoo accounts, the session source will be * something like 'yahoo-xyz' when logging in with the first yahoo account and * 'yahoo-xyt' when logging in with the other. */ $aggsource = qa_open_login_get_new_source($source, $identifier); // prepare some data if (empty($fields['handle'])) { $ohandle = ucfirst($source); } else { $ohandle = preg_replace('/[\\@\\+\\/]/', ' ', $fields['handle']); } $oemail = null; if (strlen(@$fields['email']) && $fields['confirmed']) { // only if email is confirmed $oemail = $fields['email']; } if ($countusers) { // user exists so log them in //always update email and handle if ($oemail) { qa_db_user_login_set__open($source, $identifier, 'oemail', $oemail); } qa_db_user_login_set__open($source, $identifier, 'ohandle', $ohandle); qa_set_logged_in_user($users[0]['userid'], $users[0]['handle'], $remember, $aggsource); } else { // create and log in user require_once QA_INCLUDE_DIR . 'qa-app-users-edit.php'; qa_db_user_login_sync(true); $users = qa_db_user_login_find($source, $identifier); // check again after table is locked if (count($users) == 1) { //always update email and handle if ($oemail) { qa_db_user_login_set__open($source, $identifier, 'oemail', $oemail); } qa_db_user_login_set__open($source, $identifier, 'ohandle', $ohandle); qa_db_user_login_sync(false); qa_set_logged_in_user($users[0]['userid'], $users[0]['handle'], $remember, $aggsource); } else { $handle = qa_handle_make_valid(@$fields['handle']); // check if email address already exists $emailusers = array(); if (strlen(@$fields['email']) && $fields['confirmed']) { // only if email is confirmed $emailusers = qa_db_user_find_by_email_or_oemail__open($fields['email']); if (count($emailusers)) { // unset regular email to prevent duplicates unset($fields['email']); } } $userid = qa_create_new_user((string) @$fields['email'], null, $handle, isset($fields['level']) ? $fields['level'] : QA_USER_LEVEL_BASIC, @$fields['confirmed']); qa_db_user_set($userid, 'oemail', $oemail); qa_db_user_login_add($userid, $source, $identifier); qa_db_user_login_set__open($source, $identifier, 'oemail', $oemail); qa_db_user_login_set__open($source, $identifier, 'ohandle', $ohandle); qa_db_user_login_sync(false); $profilefields = array('name', 'location', 'website', 'about'); foreach ($profilefields as $fieldname) { if (strlen(@$fields[$fieldname])) { qa_db_user_profile_set($userid, $fieldname, $fields[$fieldname]); } } if (strlen(@$fields['avatar'])) { qa_set_user_avatar($userid, $fields['avatar']); } qa_set_logged_in_user($userid, $handle, $remember, $aggsource); return count($emailusers); } } return 0; }