$cid, 'username' => $username, 'realname' => $realname, 'email' => $email ); serendipity_insertPermalink($data, 'author'); return $cid; } /** * Delete an author account * * (Note, this function does not delete entries by an author) * * @access public * @param int The author ID to delete * @return boolean True on success, false on error or unsufficient privileges */ function serendipity_deleteAuthor($authorid) { global $serendipity; if (!serendipity_checkPermission('adminUsersDelete')) { return false; } if (serendipity_db_query("DELETE FROM {$serendipity['dbPrefix']}authors WHERE authorid=" . (int)$authorid)) { serendipity_db_query("DELETE FROM {$serendipity['dbPrefix']}permalinks WHERE entry_id=" . (int)$authorid ." and type='author'"); } return true; } /** * Removes a configuration value from the Serendipity Configuration * * Global config items have the authorid 0, author-specific configuration items have the corresponding authorid. * * @access public * @param string The name of the configuration value * @param int The ID of the owner of the config value (0: global) * @return null */ function serendipity_remove_config_var($name, $authorid = 0) { global $serendipity; serendipity_db_query("DELETE FROM {$serendipity['dbPrefix']}config where name='" . serendipity_db_escape_string($name) . "' AND authorid = " . (int)$authorid); } /** * Sets a configuration value for the Serendipity Configuration * * Global config items have the authorid 0, author-specific configuration items have the corresponding authorid. * * @access public * @param string The name of the configuration value * @param string The value of the configuration item * @param int The ID of the owner of the config value (0: global) * @return null */ function serendipity_set_config_var($name, $val, $authorid = 0) { global $serendipity; serendipity_db_query("DELETE FROM {$serendipity['dbPrefix']}config where name='" . serendipity_db_escape_string($name) . "' AND authorid = " . (int)$authorid); if ($name == 'password' || $name == 'check_password') { return; } $r = serendipity_db_insert('config', array('name' => $name, 'value' => $val, 'authorid' => $authorid)); if ($authorid === 0 || $authorid === $serendipity['authorid']) { if ($val === 'false') { $serendipity[$name] = false; } else { $serendipity[$name] = $val; } } if (is_string($r)) { echo $r; } } /** * Retrieve a global configuration value for a specific item of the current Serendipity Configuration * * @access public * @param string The name of the configuration value * @param string The default value of a configuration item, if not found in the Database * @param boolean If set to true, the default value of a configuration item will be returned if the item is set, but empty. If false, an empty configuration value will be returned empty. This is required for getting default values if you do not want to store/allow empty config values. * @return string The configuration value content */ function serendipity_get_config_var($name, $defval = false, $empty = false) { global $serendipity; if (isset($serendipity[$name])) { if ($empty && gettype($serendipity[$name]) == 'string' && $serendipity[$name] === '') { return $defval; } else { return $serendipity[$name]; } } else { return $defval; } } /** * Retrieve an author-specific configuration value for an item of the Serendipity Configuration stored in the DB * * Despite the serendipity_get_config_var() function, this will retrieve author-specific values straight from the Database. * * @access public * @param string The name of the configuration value * @param int The ID of the owner of the config value (0: global) * @param string The default value of a configuration option, if not set in the DB * @return string The configuration value content */ function serendipity_get_user_config_var($name, $authorid, $default = '') { global $serendipity; $author_sql = ''; if (!empty($authorid)) { $author_sql = "authorid = " . (int)$authorid . " AND "; } elseif (isset($serendipity[$name])) { return $serendipity[$name]; } $r = serendipity_db_query("SELECT value FROM {$serendipity['dbPrefix']}config WHERE $author_sql name = '" . $name . "' LIMIT 1", true); if (is_array($r)) { return $r[0]; } else { return $default; } } /** * Retrieves an author-specific account value * * This retrieves specific account data from the user configuration, not from the serendipity configuration. * * @access public * @param string The name of the configuration value * @param int The ID of the author to fetch the configuration for * @param string The default value of a configuration option, if not set in the DB * @return string The configuration value content */ function serendipity_get_user_var($name, $authorid, $default) { global $serendipity; $r = serendipity_db_query("SELECT $name FROM {$serendipity['dbPrefix']}authors WHERE authorid = " . (int)$authorid, true); if (is_array($r)) { return $r[0]; } else { return $default; } } /** * Updates data from the author-specific account * * This sets the personal account data of a serendipity user within the 'authors' DB table * * @access public * @param string The name of the configuration value * @param string The content of the configuration value * @param int The ID of the author to set the configuration for * @param boolean If set to true, the stored config value will be imported to the Session/current config of the user. This is applied for example when you change your own user's preferences and want it to be immediately reflected in the interface. * @return null */ function serendipity_set_user_var($name, $val, $authorid, $copy_to_s9y = true) { global $serendipity; // When inserting a DB value, this array maps the new values to the corresponding s9y variables static $user_map_array = array( 'username' => 'serendipityUser', 'email' => 'serendipityEmail', 'userlevel' => 'serendipityUserlevel' ); // Special case for inserting a password switch($name) { case 'check_password': //Skip this field. It doesn't need to be stored. return; case 'password': if (empty($val)) { return; } $val = serendipity_hash($val); $copy_to_s9y = false; break; case 'right_publish': case 'mail_comments': case 'mail_trackbacks': $val = (serendipity_db_bool($val) ? 1 : '0'); break; } serendipity_db_query("UPDATE {$serendipity['dbPrefix']}authors SET $name = '" . serendipity_db_escape_string($val) . "' WHERE authorid = " . (int)$authorid); if ($copy_to_s9y) { if (isset($user_map_array[$name])) { $key = $user_map_array[$name]; } else { $key = 'serendipity' . ucfirst($name); } $_SESSION[$key] = $serendipity[$key] = $val; } } /** * Gets the full filename and path of a template/style/theme file * * The returned full path is depending on the second parameter, where you can either fetch a HTTP path, or a realpath. * The file is searched in the current template, and if it is not found there, it is returned from the default template. * * @access public * @param string The filename to search for in the selected template * @param string The path selector that tells whether to return a HTTP or realpath * @return string The full path+filename to the requested file */ function serendipity_getTemplateFile($file, $key = 'serendipityHTTPPath') { global $serendipity; $directories = array(); $directories[] = isset($serendipity['template']) ? $serendipity['template'] . '/' : ''; if (isset($serendipity['template_engine']) && (stristr($file, 'admin/') === false || $serendipity['template_engine'] != 'default')) { $directories[] = $serendipity['template_engine'] . '/'; } $directories[] = $serendipity['defaultTemplate'] .'/'; $directories[] = 'default/'; foreach ($directories as $directory) { $templateFile = $serendipity['templatePath'] . $directory . $file; if (file_exists($serendipity['serendipityPath'] . $templateFile)) { return $serendipity[$key] . $templateFile; } } if (preg_match('@\.(tpl|css|php)@i', $file) && !stristr($file, 'plugin')) { return $file; } return false; } /** * Loads all configuration values and imports them to the $serendipity array * * This function may be called twice - once for the global config and once for * user-specific config * * @access public * @param int The Authorid to fetch the configuration from (0: global) * @return null */ function serendipity_load_configuration($author = null) { global $serendipity; static $config_loaded = array(); if (isset($config_loaded[$author])) { return true; } if (!empty($author)) { // Replace default configuration directives with user-relevant data $rows =& serendipity_db_query("SELECT name,value FROM {$serendipity['dbPrefix']}config WHERE authorid = '". (int)$author ."'"); } else { // Only get default variables, user-independent (frontend) $rows =& serendipity_db_query("SELECT name, value FROM {$serendipity['dbPrefix']}config WHERE authorid = 0"); } if (is_array($rows)) { foreach ($rows as $row) { // Convert 'true' and 'false' into booleans $serendipity[$row['name']] = serendipity_get_bool($row['value']); } } $config_loaded[$author] = true; // Store default language $serendipity['default_lang'] = $serendipity['lang']; } /** * Perform logout functions (destroys session data) * * @access public * @return null */ function serendipity_logout() { $_SESSION['serendipityAuthedUser'] = false; serendipity_session_destroy(); serendipity_deleteCookie('author_information'); serendipity_deleteCookie('author_token'); } /** * Destroys a session, keeps important stuff intact. * @access public * @return null */ function serendipity_session_destroy() { $no_smarty = $_SESSION['no_smarty']; @session_destroy(); session_regenerate_id(); session_start(); $_SESSION['SERVER_GENERATED_SID'] = true; $_SESSION['no_smarty'] = $no_smarty; } /** * Perform login to Serendipity * * @access public * @param boolean If set to true, external plugins will be queried for getting a login * @return boolean Return true, if the user is logged in. False if not. */ function serendipity_login($use_external = true) { global $serendipity; if (serendipity_authenticate_author('', '', false, $use_external)) { #The session has this data already #we previously just checked the value of $_SESSION['serendipityAuthedUser'] but #we need the authorid still, so call serendipity_authenticate_author with blank #params return true; } // First try login via POST data. If true, the userinformation will be stored in a cookie (optionally) if (serendipity_authenticate_author($serendipity['POST']['user'], $serendipity['POST']['pass'], false, $use_external)) { if (empty($serendipity['POST']['auto'])) { serendipity_deleteCookie('author_information'); serendipity_deleteCookie('author_information_iv'); return false; } else { serendipity_issueAutologin( array('username' => $serendipity['POST']['user'], 'password' => $serendipity['POST']['pass'] ) ); return true; } // Now try login via COOKIE data } elseif (isset($serendipity['COOKIE']['author_information'])) { $cookie = serendipity_checkAutologin($serendipity['COOKIE']['author_information'], $serendipity['COOKIE']['author_information_iv']); if (is_array($cookie) && serendipity_authenticate_author($cookie['username'], $cookie['password'], false, $use_external)) { return true; } else { serendipity_deleteCookie('author_information'); serendipity_deleteCookie('author_information_iv'); return false; } } } /** * Issue a new auto login cookie * @param array The input data */ function serendipity_issueAutologin($array) { global $serendipity; $package = serialize($array); if (function_exists('mcrypt_encrypt')) { // Secure the package data when being stored inside the Database $iv = mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_BLOWFISH, MCRYPT_MODE_CBC), MCRYPT_RAND); $key = base64_encode($iv); $package = mcrypt_encrypt(MCRYPT_BLOWFISH, $key, $package, MCRYPT_MODE_CBC, $iv); serendipity_setCookie('author_information_iv', $key); } $package = base64_encode($package); $rnd = md5(uniqid(time(), true) . $_SERVER['REMOTE_ADDR']); // Delete possible current cookie. Also delete any autologin keys that smell like 3-week-old, dead fish. if (stristr($serendipity['dbType'], 'sqlite')) { $cast = "name"; } else { // Adds explicits casting for mysql, postgresql and others. $cast = "cast(name as integer)"; } serendipity_db_query("DELETE FROM {$serendipity['dbPrefix']}options WHERE okey = 'l_" . serendipity_db_escape_string($serendipity['COOKIE']['author_information']) . "' OR (okey LIKE 'l_%' AND $cast < " . (time() - 1814400) . ")"); // Issue new autologin cookie serendipity_db_query("INSERT INTO {$serendipity['dbPrefix']}options (name, value, okey) VALUES ('" . time() . "', '" . serendipity_db_escape_string($package) . "', 'l_" . $rnd . "')"); serendipity_setCookie('author_information', $rnd); } /** * Checks a new auto login cookie * @param array The input data */ function serendipity_checkAutologin($ident, $iv) { global $serendipity; // Fetch login data from DB $autologin =& serendipity_db_query("SELECT * FROM {$serendipity['dbPrefix']}options WHERE okey = 'l_" . serendipity_db_escape_string($ident) . "' LIMIT 1", true, 'assoc'); if (!is_array($autologin)) { return false; } if (function_exists('mcrypt_decrypt') && !empty($iv)) { $key = $iv; $iv = base64_decode($iv); $cookie = unserialize(mcrypt_decrypt(MCRYPT_BLOWFISH, $key, base64_decode($autologin['value']), MCRYPT_MODE_CBC, $iv)); } else { $cookie = unserialize(base64_decode($autologin['value'])); } if ($autologin['name'] < (time()-86400)) { // Issued autologin cookie has been issued more than 1 day ago. Re-Issue new cookie, invalidate old one to prevent abuse if ($serendipity['expose_s9y']) serendipity_header('X-ReIssue-Cookie: +' . (time() - $autologin['name']) . 's'); serendipity_issueAutologin($cookie); } return $cookie; } /** * Set a session cookie which can identify a user accross http/https boundaries */ function serendipity_setAuthorToken() { $hash = sha1(uniqid(rand(), true)); serendipity_setCookie('author_token', $hash); $_SESSION['author_token'] = $hash; } /** * Perform user authentication routine * * If a user is already authenticated via session data, this bypasses some routines. * After a user has ben authenticated, several SESSION variables ar set. * If the authentication fails, the session is destroyed. * * @access public * @param string The username to check * @param string The password to check (may contain plaintext or MD5 hash) * @param boolean Indicates whether the input password is already in MD5 format (TRUE) or not (FALSE). * @param boolean Indicates whether to query external plugins for authentication * @return boolean True on success, False on error */ function serendipity_authenticate_author($username = '', $password = '', $is_hashed = false, $use_external = true) { global $serendipity; static $debug = false; static $debugc = 0; if ($debug) { $fp = fopen('login.log', 'a'); flock($fp, LOCK_EX); $debugc++; fwrite($fp, date('Y-m-d H:i') . ' - #' . $debugc . ' Login init [' . $username . ',' . $password . ',' . (int)$is_hashed . ',' . (int)$use_external . ']' . ' (' . $_SERVER['REMOTE_ADDR'] . ',' . $_SERVER['REQUEST_URI'] . ', ' . session_id() . ')' . "\n"); } if (isset($_SESSION['serendipityUser']) && isset($_SESSION['serendipityPassword']) && isset($_SESSION['serendipityAuthedUser']) && $_SESSION['serendipityAuthedUser'] == true) { $username = $_SESSION['serendipityUser']; $password = $_SESSION['serendipityPassword']; // For safety reasons when multiple blogs are installed on the same host, we need to check the current author each time to not let him log into a different blog with the same sessiondata #$is_hashed = true; if ($debug) fwrite($fp, date('Y-m-d H:i') . ' - Recall from session: ' . $username . ':' . $password . "\n"); } if ($debug) fwrite($fp, date('Y-m-d H:i') . ' - Login ext check' . "\n"); $is_authenticated = false; serendipity_plugin_api::hook_event('backend_login', $is_authenticated, NULL); if ($is_authenticated) { return true; } if ($debug) fwrite($fp, date('Y-m-d H:i') . ' - Login username check:' . $username . "\n"); if ($username != '') { if ($use_external) { serendipity_plugin_api::hook_event('backend_auth', $is_hashed, array('username' => $username, 'password' => $password)); } $query = "SELECT DISTINCT email, password, realname, authorid, userlevel, right_publish, hashtype FROM {$serendipity['dbPrefix']}authors WHERE username = '" . serendipity_db_escape_string($username) . "'"; if ($debug) fwrite($fp, date('Y-m-d H:i') . ' - Login check (' . serialize($is_hashed) . ', ' . $_SESSION['serendipityPassword'] . '):' . $query . "\n"); $rows =& serendipity_db_query($query, false, 'assoc'); if (is_array($rows)) { foreach($rows AS $row) { if ($is_valid_user) continue; $is_valid_user = false; // Old MD5 hashing routine. Will convert user. if (empty($row['hashtype']) || $row['hashtype'] == 0) { if (isset($serendipity['hashkey']) && (time() - $serendipity['hashkey']) >= 15768000) { die('You can no longer login with an old-style MD5 hash to prevent MD5-Hostage abuse. Please ask the Administrator to set you a new password.'); } if ( ($is_hashed === false && (string)$row['password'] === (string)md5($password)) || ($is_hashed !== false && (string)$row['password'] === (string)$password) ) { serendipity_db_query("UPDATE {$serendipity['dbPrefix']}authors SET password = '" . ($is_hashed === false ? serendipity_hash($password) : $password) . "', hashtype = 1 WHERE authorid = '" . $row['authorid'] . "'"); if ($debug) fwrite($fp, date('Y-m-d H:i') . ' - Migrated user:' . $row['username'] . "\n"); $is_valid_user = true; } else { continue; } } else { if ( ($is_hashed === false && (string)$row['password'] === (string)serendipity_hash($password)) || ($is_hashed !== false && (string)$row['password'] === (string)$password) ) { $is_valid_user = true; if ($debug) fwrite($fp, date('Y-m-d H:i') . ' - Validated ' . $row['password'] . ' == ' . ($is_hashed === false ? 'unhash:' . serendipity_hash($password) : 'hash:' . $password) . "\n"); } else { if ($debug) fwrite($fp, date('Y-m-d H:i') . ' - INValidated ' . $row['password'] . ' == ' . ($is_hashed === false ? 'unhash:' . serendipity_hash($password) : 'hash:' . $password) . "\n"); continue; } } // This code is only reached, if the password before is valid. if ($is_valid_user) { if ($debug) fwrite($fp, date('Y-m-d H:i') . ' [sid:' . session_id() . '] - Success.' . "\n"); serendipity_setCookie('old_session', session_id(), false); if (!$is_hashed) { serendipity_setAuthorToken(); $_SESSION['serendipityPassword'] = $serendipity['serendipityPassword'] = $password; } $_SESSION['serendipityUser'] = $serendipity['serendipityUser'] = $username; $_SESSION['serendipityRealname'] = $serendipity['serendipityRealname'] = $row['realname']; $_SESSION['serendipityEmail'] = $serendipity['serendipityEmail'] = $row['email']; $_SESSION['serendipityAuthorid'] = $serendipity['authorid'] = $row['authorid']; $_SESSION['serendipityUserlevel'] = $serendipity['serendipityUserlevel'] = $row['userlevel']; $_SESSION['serendipityAuthedUser'] = $serendipity['serendipityAuthedUser'] = true; $_SESSION['serendipityRightPublish']= $serendipity['serendipityRightPublish'] = $row['right_publish']; $_SESSION['serendipityHashType'] = $serendipity['serendipityHashType'] = $row['hashtype']; serendipity_load_configuration($serendipity['authorid']); serendipity_setCookie('userDefLang', $serendipity['lang'], false); return true; } } } // Only reached, when proper login did not yet return true. if ($debug) fwrite($fp, date('Y-m-d H:i') . ' - FAIL.' . "\n"); $_SESSION['serendipityAuthedUser'] = false; serendipity_session_destroy(); } if ($debug) { fwrite($fp, date('Y-m-d H:i') . ' [sid:' . session_id() . '] - Uninit' . "\n"); fclose($fp); } return false; } /** * Check if a user is logged in * * @access public * @return boolean TRUE when logged in, FALSE when not. */ function serendipity_userLoggedIn() { if ($_SESSION['serendipityAuthedUser'] === true && IS_installed) { return true; } else { return false; } } /** * A clone of an ifsetor() function to set a variable conditional on if the target already exists * * The function sets the contents of $source into the $target variable, but only if $target is not yet set. Eases up some if...else logic or multiple ternary operators * * @access public * @param mixed Source variable that should be set into the target variable (reference call!) * @param mixed Target variable, that should get the contents of the source variable (reference call!) * @return boolean True, when $target was not yet set and has been altered. False when no changes where made. */ function serendipity_restoreVar(&$source, &$target) { global $serendipity; if (isset($source) && !isset($target)) { $target = $source; return true; } return false; } /** * Echo Javascript code to set a cookie variable * * This function is useful if your HTTP headers were already sent, but you still want to set a cookie * Note that contents are echo'd, not return'd. * * @access public * @param string The name of the cookie variable * @param string The contents of the cookie variable * @return null */ function serendipity_JSsetCookie($name, $value) { $name = htmlentities($name); $value = urlencode($value); echo '' . "\n"; } /** * Set a Cookie via HTTP calls, and update $_COOKIE plus $serendipity['COOKIE'] array. * * @access public * @param string The name of the cookie variable * @param string The contents of the cookie variable * @return null */ function serendipity_setCookie($name, $value, $securebyprot = true) { global $serendipity; $host = $_SERVER['HTTP_HOST']; if ($securebyprot) { $secure = (strtolower($_SERVER['HTTPS']) == 'on') ? true : false; if ($pos = strpos($host, ":")) { $host = substr($host, 0, $pos); } } else { $secure = false; } // If HTTP-Hosts like "localhost" are used, current browsers reject cookies. // In this case, we disregard the HTTP host to be able to set that cookie. if (substr_count($host, '.') < 1) { $host = ''; } setcookie("serendipity[$name]", $value, time()+60*60*24*30, $serendipity['serendipityHTTPPath'], $host, $secure); $_COOKIE[$name] = $value; $serendipity['COOKIE'][$name] = $value; } /** * Deletes an existing cookie value * * LONG * * @access public * @param string Name of the cookie to delete * @return */ function serendipity_deleteCookie($name) { global $serendipity; $host = $_SERVER['HTTP_HOST']; if ($pos = strpos($host, ":")) { $host = substr($host, 0, $pos); } // If HTTP-Hosts like "localhost" are used, current browsers reject cookies. // In this case, we disregard the HTTP host to be able to set that cookie. if (substr_count($host, '.') < 1) { $host = ''; } setcookie("serendipity[$name]", '', time()-4000, $serendipity['serendipityHTTPPath'], $host); unset($_COOKIE[$name]); unset($serendipity['COOKIE'][$name]); } /** * Performs a check whether an iframe for the admin section shall be emitted * * The iframe is used for previewing an entry with the stylesheet of the frontend. * It fetches its data from the session input data. * * @access private * @return boolean True, if iframe was requested, false if not. */ function serendipity_is_iframe() { global $serendipity; if ($serendipity['GET']['is_iframe'] && is_array($_SESSION['save_entry'])) { include_once S9Y_INCLUDE_PATH . 'include/functions_entries_admin.inc.php'; // An iframe may NOT contain and tags, that's why we emit different headers here than on serendipity_admin.php // We need to restore GET/POST variables to that depending plugins inside the iframe // can still fetch all that variables; and we also tighten security by not allowing // to pass any different GET/POST variables to our iframe. $iframe_mode = $serendipity['GET']['iframe_mode']; $serendipity['POST'] = &$_SESSION['save_entry_POST']; $serendipity['GET'] = &$_SESSION['save_entry_POST']; // GET-Vars are the same as POST to ensure compatibility. ignore_user_abort(true); serendipity_iframe($_SESSION['save_entry'], $iframe_mode, true); return true; } return false; } /** * Prints the content of the iframe. * * Called by serendipity_is_iframe, when preview is requested. Fetches data from session. * An iframe is used so that a single s9y page must not timeout on intensive operations, * and so that the frontend stylesheet can be embedded without screwing up the backend. * * @access private * @see serendipity_is_iframe() * @param mixed The entry array (comes from session variable) * @param string Indicates whether an entry is previewed or saved. Save performs XML-RPC calls. * @param boolean Use smarty templating? * @return boolean Indicates whether iframe data was printed */ function serendipity_iframe(&$entry, $mode = null, $use_smarty = true) { global $serendipity; if (empty($mode) || !is_array($entry)) { return false; } if ($use_smarty) { $serendipity['smarty_raw_mode'] = true; // Force output of Smarty stuff in the backend $serendipity['smarty_preview'] = true; serendipity_smarty_init(); $serendipity['smarty']->assign('is_preview', true); ob_start(); } $show = false; switch ($mode) { case 'save': echo '
'; $res = serendipity_updertEntry($entry); if (is_string($res)) { echo '