function send_reset_confirmation_request($name) { global $sitename; $rs = safe_row('email, nonce', 'txp_users', "name = '" . doSlash($name) . "'"); if ($rs) { extract($rs); $confirm = bin2hex(pack('H*', substr(md5($nonce), 0, 10)) . $name); $message = gTxt('greeting') . ' ' . $name . ',' . n . n . gTxt('password_reset_confirmation') . ': ' . n . hu . 'textpattern/index.php?confirm=' . $confirm; if (txpMail($email, "[{$sitename}] " . gTxt('password_reset_confirmation_request'), $message)) { return gTxt('password_reset_confirmation_request_sent'); } else { return gTxt('could_not_mail'); } } else { return gTxt('unknown_author', array('{name}' => htmlspecialchars($name))); } }
function send_reset_confirmation_request($name) { global $sitename; $rs = safe_row('email, nonce', 'txp_users', "name = '" . doSlash($name) . "'"); if ($rs) { extract($rs); $confirm = bin2hex(pack('H*', substr(md5($nonce), 0, 10)) . $name); $message = gTxt('greeting') . ' ' . $name . ',' . n . n . gTxt('password_reset_confirmation') . ': ' . n . hu . 'textpattern/index.php?confirm=' . $confirm; if (txpMail($email, "[{$sitename}] " . gTxt('password_reset_confirmation_request'), $message)) { return gTxt('password_reset_confirmation_request_sent'); } else { return array(gTxt('could_not_mail'), E_ERROR); } } else { // Though 'unknown_author' could be thrown, send generic 'request_sent' message // instead so that (non-)existence of account names are not leaked return gTxt('password_reset_confirmation_request_sent'); } }
function mail_comment($message, $cname, $cemail, $cweb, $parentid, $discussid) { global $sitename; $parentid = assert_int($parentid); $discussid = assert_int($discussid); $article = safe_row("Section, Posted, ID, url_title, AuthorID, Title", "textpattern", "ID = {$parentid}"); extract($article); extract(safe_row("RealName, email", "txp_users", "name = '" . doSlash($AuthorID) . "'")); $evaluator =& get_comment_evaluator(); $out = gTxt('greeting') . " {$RealName}," . n . n; $out .= str_replace('{title}', $Title, gTxt('comment_recorded')) . n; $out .= permlinkurl_id($parentid) . n; if (has_privs('discuss', $AuthorID)) { $out .= hu . 'textpattern/index.php?event=discuss&step=discuss_edit&discussid=' . $discussid . n; } $out .= gTxt('status') . ": " . $evaluator->get_result('text') . '. ' . implode(',', $evaluator->get_result_message()) . n; $out .= n; $out .= gTxt('comment_name') . ": {$cname}" . n; $out .= gTxt('comment_email') . ": {$cemail}" . n; $out .= gTxt('comment_web') . ": {$cweb}" . n; $out .= gTxt('comment_comment') . ": {$message}"; $subject = strtr(gTxt('comment_received'), array('{site}' => $sitename, '{title}' => $Title)); $success = txpMail($email, $subject, $out, $cemail); }
function send_new_password($NewPass, $themail, $name) { global $txp_user, $sitename; $message = gTxt('greeting') . ' ' . $name . ',' . "\r\n" . gTxt('your_password_is') . ': ' . $NewPass . "\r\n" . "\r\n" . gTxt('log_in_at') . ' ' . hu . 'textpattern/index.php'; return txpMail($themail, "[{$sitename}] " . gTxt('your_new_password'), $message); }
function mail_comment($message, $cname, $cemail, $cweb, $parentid) { global $sitename; extract(safe_row("AuthorID,Title", "textpattern", "ID = '{$parentid}'")); extract(safe_row("RealName, email", "txp_users", "name = '" . doSlash($AuthorID) . "'")); $out = gTxt('greeting') . " {$RealName},\r\n\r\n"; $out .= str_replace('{title}', $Title, gTxt('comment_recorded') . "\r\n\r\n"); $out .= gTxt('comment_name') . ": {$cname}\r\n"; $out .= gTxt('comment_email') . ": {$cemail}\r\n"; $out .= gTxt('comment_web') . ": {$cweb}\r\n"; $out .= gTxt('comment_comment') . ": {$message}"; $subject = strtr(gTxt('comment_received'), array('{site}' => $sitename, '{title}' => $Title)); $success = txpMail($email, $subject, $out, $cemail); }
/** * Sends a password reset link to a user's email address. * * This function will return a success message even when the specified user * doesn't exist. Though an error message could be thrown when a user isn't * found, this is done due to security, which prevents the function from * leaking existing account names. * * @param string $name The login name * @return string A localized message string * @see send_new_password() * @see reset_author_pass() * @example * echo send_reset_confirmation_request('username'); */ function send_reset_confirmation_request($name) { global $sitename; $expiryTimestamp = time() + 60 * RESET_EXPIRY_MINUTES; $expiry = strftime('%Y-%m-%d %H:%M:%S', $expiryTimestamp); $rs = safe_query("SELECT\n txp_users.user_id, txp_users.email,\n txp_users.nonce, txp_users.pass,\n txp_token.type\n FROM " . safe_pfx('txp_users') . " txp_users\n LEFT JOIN " . safe_pfx('txp_token') . " txp_token\n ON txp_users.user_id = txp_token.reference_id\n WHERE txp_users.name = '" . doSlash($name) . "'\n AND TIMESTAMPDIFF(SECOND, txp_token.expires, '" . $expiry . "') > " . 60 * RESET_RATE_LIMIT_MINUTES . "\n AND txp_token.type = 'password_reset'"); $row = nextRow($rs); if ($row) { extract($row); $uid = assert_int($user_id); // The selector becomes an indirect reference to the txp_users row, // which does not leak information. $selector = Txp::get('\\Textpattern\\Password\\Random')->generate(12); $expiryYear = safe_strftime('%Y', $expiryTimestamp); $expiryMonth = safe_strftime('%B', $expiryTimestamp); $expiryDay = safe_strftime('%Oe', $expiryTimestamp); $expiryTime = safe_strftime('%H:%M', $expiryTimestamp); // Use a hash of the nonce, selector and password. // This ensures that confirmation requests expire automatically when: // a) The person next logs in, or // b) They successfully change their password (usually as a result of this reset request) // Using the selector in the hash just injects randomness, otherwise two requests // back-to-back would generate the same confirmation code. // Old requests for the same user id are purged every time a new request is made. $token = bin2hex(pack('H*', substr(hash(HASHING_ALGORITHM, $nonce . $selector . $pass), 0, SALT_LENGTH))); $confirm = $token . $selector; // Remove any previous reset tokens and insert the new one. safe_delete("txp_token", "reference_id = {$uid} AND type = 'password_reset'"); safe_insert("txp_token", "reference_id = {$uid},\n type = 'password_reset',\n selector = '" . doSlash($selector) . "',\n token = '" . doSlash($token) . "',\n expires = '" . doSlash($expiry) . "'\n "); $message = gTxt('salutation', array('{name}' => $name)) . n . n . gTxt('password_reset_confirmation') . n . hu . 'textpattern/index.php?confirm=' . $confirm . n . n . gTxt('link_expires', array('{year}' => $expiryYear, '{month}' => $expiryMonth, '{day}' => $expiryDay, '{time}' => $expiryTime)); if (txpMail($email, "[{$sitename}] " . gTxt('password_reset_confirmation_request'), $message)) { return gTxt('password_reset_confirmation_request_sent'); } else { return array(gTxt('could_not_mail'), E_ERROR); } } else { // Though 'unknown_author' could be thrown, send generic 'request_sent' // message instead so that (non-)existence of account names are not leaked. // Since this is a short circuit, there's a possibility of a timing attack // revealing the existence of an account, which we could defend against // to some degree. return gTxt('password_reset_confirmation_request_sent'); } }
function send_customer_password($RealName, $name, $email, $password) { global $sitename; $message = gTxt('greeting') . ' ' . $RealName . ',' . "\r\n" . "\r\n" . gTxt('you_have_been_registered') . ' ' . $sitename . "\r\n" . "\r\n" . gTxt('your_login_is') . ': ' . $name . "\r\n" . gTxt('your_password_is') . ': ' . $password . "\r\n"; //"\r\n"."\r\n".gTxt('log_in_at').': '.hu.'textpattern/index.php'; return txpMail($email, "[{$sitename}] " . gTxt('your_login_info'), $message); }
function mail_comment($message, $cname, $cemail, $cweb, $parentid, $discussid) { global $sitename; $article = safe_row("Section, Posted, ID, url_title, AuthorID, Title", "textpattern", "ID = '{$parentid}'"); extract($article); extract(safe_row("RealName, email", "txp_users", "name = '" . doSlash($AuthorID) . "'")); $out = gTxt('greeting') . " {$RealName},\r\n\r\n"; $out .= str_replace('{title}', $Title, gTxt('comment_recorded')) . "\r\n"; $out .= permlinkurl_id($parentid) . "\r\n"; if (has_privs('discuss', $AuthorID)) { $out .= hu . 'textpattern/?event=discuss&step=discuss_edit&discussid=' . $discussid . "\r\n"; } $out .= "\r\n"; $out .= gTxt('comment_name') . ": {$cname}\r\n"; $out .= gTxt('comment_email') . ": {$cemail}\r\n"; $out .= gTxt('comment_web') . ": {$cweb}\r\n"; $out .= gTxt('comment_comment') . ": {$message}"; $subject = strtr(gTxt('comment_received'), array('{site}' => $sitename, '{title}' => $Title)); $success = txpMail($email, $subject, $out, $cemail); }
/** * Validates the sent login form and creates a session. * * During the reset request procedure, it is conceivable to verify the * token as soon as it's presented in the URL, but that would: * a) require refactoring code similarities in both p_confirm and p_alter branches * b) require some way (e.g. an Exception) to signal back to doLoginForm() that * the token is bogus so the 'change your password' form is not displayed. * c) leak information about the validity of a token, thus allowing rapid brute-force * attempts. * * The inconvenience of a real user following an expired token and being told so * after they've set a password is a small price to pay for the improved security * and reduction of attack surface that validating after submission affords. * * @todo Could the checks be done via a (reusable) Validator()? * * @return string A localised feedback message * @see doLoginForm() */ function doTxpValidate() { global $logout, $txp_user; $p_userid = ps('p_userid'); $p_password = ps('p_password'); $p_reset = ps('p_reset'); $p_alter = ps('p_alter'); $p_set = ps('p_set'); $stay = ps('stay'); $p_confirm = gps('confirm'); $logout = gps('logout'); $message = ''; $pub_path = preg_replace('|//$|', '/', rhu . '/'); if (cs('txp_login') && strpos(cs('txp_login'), ',')) { $txp_login = explode(',', cs('txp_login')); $c_hash = end($txp_login); $c_userid = join(',', array_slice($txp_login, 0, -1)); } else { $c_hash = ''; $c_userid = ''; } if ($logout) { setcookie('txp_login', '', time() - 3600); setcookie('txp_login_public', '', time() - 3600, $pub_path); } if ($c_userid && strlen($c_hash) === 32) { // Cookie exists. // @todo Improve security by using a better nonce/salt mechanism. md5 and uniqid are bad. $r = safe_row("name, nonce", 'txp_users', "name = '" . doSlash($c_userid) . "' AND last_access > DATE_SUB(NOW(), INTERVAL 30 DAY)"); if ($r && $r['nonce'] && $r['nonce'] === md5($c_userid . pack('H*', $c_hash))) { // Cookie is good. if ($logout) { // Destroy nonce. safe_update('txp_users', "nonce = '" . doSlash(md5(uniqid(mt_rand(), true))) . "'", "name = '" . doSlash($c_userid) . "'"); } else { // Create $txp_user. $txp_user = $r['name']; } return $message; } else { txp_status_header('401 Your session has expired'); setcookie('txp_login', $c_userid, time() + 3600 * 24 * 365); setcookie('txp_login_public', '', time() - 3600, $pub_path); $message = array(gTxt('bad_cookie'), E_ERROR); } } elseif ($p_userid && $p_password) { // Incoming login vars. $name = txp_validate($p_userid, $p_password); if ($name !== false) { $c_hash = md5(uniqid(mt_rand(), true)); $nonce = md5($name . pack('H*', $c_hash)); safe_update('txp_users', "nonce = '" . doSlash($nonce) . "'", "name = '" . doSlash($name) . "'"); setcookie('txp_login', $name . ',' . $c_hash, $stay ? time() + 3600 * 24 * 365 : 0, null, null, null, LOGIN_COOKIE_HTTP_ONLY); setcookie('txp_login_public', substr(md5($nonce), -10) . $name, $stay ? time() + 3600 * 24 * 30 : 0, $pub_path); // Login is good, create $txp_user. $txp_user = $name; return ''; } else { sleep(3); txp_status_header('401 Could not log in with that username/password'); $message = array(gTxt('could_not_log_in'), E_ERROR); } } elseif ($p_reset) { // Reset request. sleep(3); include_once txpath . '/lib/txplib_admin.php'; $message = $p_userid ? send_reset_confirmation_request($p_userid) : ''; } elseif ($p_alter || $p_set) { // Password change/set confirmation. sleep(3); global $sitename; $pass = ps('p_password'); $type = $p_alter ? 'password_reset' : 'account_activation'; if (trim($pass) === '') { $message = array(gTxt('password_required'), E_ERROR); } else { $hash = gps('hash'); $selector = substr($hash, SALT_LENGTH); $tokenInfo = safe_row("reference_id, token, expires", 'txp_token', "selector = '" . doSlash($selector) . "' AND type='{$type}'"); if ($tokenInfo) { if (strtotime($tokenInfo['expires']) <= time()) { $message = array(gTxt('token_expired'), E_ERROR); } else { $uid = assert_int($tokenInfo['reference_id']); $row = safe_row("name, email, nonce, pass AS old_pass", 'txp_users', "user_id = {$uid}"); if ($row && $row['nonce'] && $hash === bin2hex(pack('H*', substr(hash(HASHING_ALGORITHM, $row['nonce'] . $selector . $row['old_pass']), 0, SALT_LENGTH))) . $selector) { if (change_user_password($row['name'], $pass)) { $body = gTxt('salutation', array('{name}' => $row['name'])) . n . n . ($p_alter ? gTxt('password_change_confirmation') : gTxt('password_set_confirmation') . n . n . gTxt('log_in_at') . ': ' . hu . 'textpattern/index.php'); $message = $p_alter ? gTxt('password_changed') : gTxt('password_set'); txpMail($row['email'], "[{$sitename}] " . $message, $body); // Invalidate all tokens in the wild for this user. safe_delete("txp_token", "reference_id = {$uid} AND type IN ('password_reset', 'account_activation')"); } } else { $message = array(gTxt('invalid_token'), E_ERROR); } } } else { $message = array(gTxt('invalid_token'), E_ERROR); } } } $txp_user = ''; return $message; }
function clone_for_translation() { $has_privs = has_privs('l10n.clone'); if (!$has_privs) { return; } # User cannot clone articles. $vars = array('rendition'); extract(gpsa($vars)); $rendition = (int) $rendition; $langs = MLPLanguageHandler::get_site_langs(); $clone_to = array(); foreach ($langs as $lang) { $clone = $lang === gps($lang); if ($clone) { $new_author = gps($lang . '-AuthorID'); $clone_to[$lang] = $new_author; } } if (count($clone_to) < 1) { $this->parent->message = gTxt('l10n-no_langs_selected'); $_POST['step'] = 'start_clone'; return; } # # Prepare the source rendition data... # $source = safe_row('*', 'textpattern', "`ID`={$rendition}"); $article_id = (int) $source[L10N_COL_GROUP]; # # Create the articles, substituting new authors and status as needed... # $notify = array(); # For email notices. foreach ($clone_to as $lang => $new_author) { $rendition_id = $this->_clone_rendition($source, $article_id, $lang, $new_author); # Now we know rendition & article IDs, store against author for email notification... $language = MLPLanguageHandler::get_native_name_of_lang($lang); $notify[$new_author][$lang] = array('id' => "{$rendition_id}", 'title' => $source['Title'], 'language' => $language); } # # Send the notifications? # $send_notifications = '1' == $this->pref('l10n-send_notifications') ? true : false; $notify_self = '1' == $this->pref('l10n-send_notice_to_self') ? true : false; if ($send_notifications) { global $sitename, $siteurl, $txp_user; extract(safe_row('RealName AS txp_username,email AS replyto', 'txp_users', "name='{$txp_user}'")); foreach ($notify as $new_user => $list) { # # Skip if no articles... # $count = count($list); if ($count < 1) { continue; } # # Skip if users are the same and no notifications are to be sent in that case... # $same = $new_user == $txp_user; if ($same and !$notify_self) { continue; } # # Construct a list of links to the renditions assigned to this user... # $links = array(); foreach ($list as $lang => $record) { extract($record); $msg = gTxt('title') . ": \"{$title}\"\r\n"; $msg .= gTxt('l10n-xlate_to') . "{$language} [{$lang}].\r\n"; $msg .= "http://{$siteurl}/textpattern/index.php?event=article&step=edit&ID={$id}\r\n"; $links[] = $msg; } extract(safe_row('RealName AS new_user,email', 'txp_users', "name='{$new_user}'")); $s = $count === 1 ? '' : 's'; $subs = array('{sitename}' => $sitename, '{count}' => $count, '{s}' => $s, '{txp_username}' => $txp_username); if ($same) { $body = gTxt('l10n-email_body_self', $subs); } else { $body = gTxt('l10n-email_body_other', $subs); } $body .= join("\r\n", $links) . "\r\n" . gTxt('l10n-email_end', $subs); $subject = gTxt('l10n-email_xfer_subject', $subs); @txpMail($email, $subject, $body, $replyto); } } }
function _l10n_changeauthor_notify_routine() { global $l10n_view; # Permissions for email... $send_notifications = '1' == $l10n_view->pref('l10n-send_notifications') ? true : false; $on_changeauthor = '1' == $l10n_view->pref('l10n-send_notice_on_changeauthor') ? true : false; $notify_self = '1' == $l10n_view->pref('l10n-send_notice_to_self') ? true : false; if (!$send_notifications or !$on_changeauthor) { return false; } global $statuses, $sitename, $siteurl, $txp_user; $new_user = gps('AuthorID'); $selected = gps('selected'); $links = array(); $same = $new_user == $txp_user; if (empty($new_user)) { return; } if (!$same or $notify_self) { if ($selected and !empty($selected)) { foreach ($selected as $id) { # # Make a link to the article... # $row = safe_row('Title , ' . L10N_COL_LANG . ' , `' . L10N_COL_GROUP . '` , Status', 'textpattern', "`ID`='{$id}'"); extract($row); $lang = MLPLanguageHandler::get_native_name_of_lang($row[L10N_COL_LANG]); $status = $statuses[$Status]; $msg = gTxt('title') . ": \"{$Title}\"\r\n" . gTxt('status') . ": {$status} , " . gTxt('language') . ": {$lang} [" . $row[L10N_COL_LANG] . '] , ' . gTxt('group') . ': ' . $row[L10N_COL_GROUP] . ".\r\n"; $msg .= "http://{$siteurl}/textpattern/index.php?event=article&step=edit&ID={$id}\r\n"; $links[] = $msg; } } extract(safe_row('RealName AS txp_username,email AS replyto', 'txp_users', "name='{$txp_user}'")); extract(safe_row('RealName AS new_user,email', 'txp_users', "name='{$new_user}'")); $count = count($links); $s = $count === 1 ? '' : 's'; $subs = array('{sitename}' => $sitename, '{count}' => $count, '{s}' => $s, '{txp_username}' => $txp_username); if ($same) { $body = gTxt('l10n-email_body_self', $subs); } else { $body = gTxt('l10n-email_body_other', $subs); } $body .= join("\r\n", $links) . "\r\n\r\n" . gTxt('thanks') . "\r\n--\r\n{$txp_username}."; $subject = gTxt('l10n-email_xfer_subject', $subs); $ok = @txpMail($email, $subject, $body, $replyto); } }
function send_new_password($password, $email, $name) { global $txp_user, $sitename; if (empty($name)) { $name = $txp_user; } $message = gTxt('greeting') . ' ' . $name . ',' . "\r\n" . "\r\n" . gTxt('your_password_is') . ': ' . $password . "\r\n" . "\r\n" . gTxt('log_in_at') . ': ' . hu . 'textpattern/index.php'; return txpMail($email, "[{$sitename}] " . gTxt('your_new_password'), $message); }