1<?php 2# Copyright (c) 2003-2005, Jannis Hermanns (on behalf the Serendipity Developer Team) 3# All rights reserved. See LICENSE file for licensing details 4 5if (IN_serendipity !== true) { 6 die ("Don't hack!"); 7} 8 9/** 10 * Adds a new author account 11 * 12 * @access public 13 * @param string New username 14 * @param string New password 15 * @param string The realname of the user 16 * @param string The email address of the user 17 * @param int The userlevel of a user 18 * @return int The new user ID of the added author 19 */ 20function serendipity_addAuthor($username, $password, $realname, $email, $userlevel=0, $hashtype=2) { 21 global $serendipity; 22 $password = serendipity_hash($password); 23 $query = "INSERT INTO {$serendipity['dbPrefix']}authors (username, password, realname, email, userlevel, hashtype) 24 VALUES ('" . serendipity_db_escape_string($username) . "', 25 '" . serendipity_db_escape_String($password) . "', 26 '" . serendipity_db_escape_String($realname) . "', 27 '" . serendipity_db_escape_String($email) . "', 28 '" . serendipity_db_escape_String($userlevel) . "', 29 '" . serendipity_db_escape_String($hashtype) . "' 30 )"; 31 serendipity_db_query($query); 32 $cid = serendipity_db_insert_id('authors', 'authorid'); 33 34 $data = array( 35 'authorid' => $cid, 36 'username' => $username, 37 'realname' => $realname, 38 'email' => $email 39 ); 40 41 serendipity_insertPermalink($data, 'author'); 42 return $cid; 43} 44 45/** 46 * Delete an author account 47 * 48 * (Note, this function does not delete entries by an author) 49 * 50 * @access public 51 * @param int The author ID to delete 52 * @return boolean True on success, false on error or unsufficient privileges 53 */ 54function serendipity_deleteAuthor($authorid) { 55 global $serendipity; 56 57 if (!serendipity_checkPermission('adminUsersDelete')) { 58 return false; 59 } 60 61 if (serendipity_db_query("DELETE FROM {$serendipity['dbPrefix']}authors WHERE authorid=" . (int)$authorid)) { 62 serendipity_db_query("DELETE FROM {$serendipity['dbPrefix']}permalinks WHERE entry_id=" . (int)$authorid ." and type='author'"); 63 } 64 return true; 65} 66 67/** 68 * Removes a configuration value from the Serendipity Configuration 69 * 70 * Global config items have the authorid 0, author-specific configuration items have the corresponding authorid. 71 * 72 * @access public 73 * @param string The name of the configuration value 74 * @param int The ID of the owner of the config value (0: global) 75 * @return null 76 */ 77function serendipity_remove_config_var($name, $authorid = 0) { 78 global $serendipity; 79 serendipity_db_query("DELETE FROM {$serendipity['dbPrefix']}config where name='" . serendipity_db_escape_string($name) . "' AND authorid = " . (int)$authorid); 80} 81 82/** 83 * Sets a configuration value for the Serendipity Configuration 84 * 85 * Global config items have the authorid 0, author-specific configuration items have the corresponding authorid. 86 * 87 * @access public 88 * @param string The name of the configuration value 89 * @param string The value of the configuration item 90 * @param int The ID of the owner of the config value (0: global) 91 */ 92function serendipity_set_config_var($name, $val, $authorid = 0) { 93 global $serendipity; 94 95 serendipity_db_query("DELETE FROM {$serendipity['dbPrefix']}config where name='" . serendipity_db_escape_string($name) . "' AND authorid = " . (int)$authorid); 96 97 if ($name == 'password' || $name == 'check_password') { 98 return; 99 } 100 101 $r = serendipity_db_insert('config', array('name' => $name, 'value' => $val, 'authorid' => $authorid)); 102 103 if ($authorid === 0 || $authorid === $serendipity['authorid']) { 104 if ($val === 'false') { 105 $serendipity[$name] = false; 106 } else { 107 $serendipity[$name] = $val; 108 } 109 } 110 111 if (is_string($r)) { 112 # if $r is a string, it is the error-message from the insert 113 echo $r; 114 } 115} 116 117/** 118 * Retrieve a global configuration value for a specific item of the current Serendipity Configuration 119 * 120 * @access public 121 * @param string The name of the configuration value 122 * @param string The default value of a configuration item, if not found in the Database 123 * @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. 124 * @return string The configuration value content 125 */ 126function serendipity_get_config_var($name, $defval = false, $empty = false) { 127 global $serendipity; 128 if (isset($serendipity[$name])) { 129 if ($empty && gettype($serendipity[$name]) == 'string' && $serendipity[$name] === '') { 130 return $defval; 131 } else { 132 return $serendipity[$name]; 133 } 134 } else { 135 return $defval; 136 } 137} 138 139/** 140 * Retrieve an author-specific configuration value for an item of the Serendipity Configuration stored in the DB 141 * 142 * Despite the serendipity_get_config_var() function, this will retrieve author-specific values straight from the Database. 143 * 144 * @access public 145 * @param string The name of the configuration value 146 * @param int The ID of the owner of the config value (0: global) 147 * @param string The default value of a configuration option, if not set in the DB 148 * @return string The configuration value content 149 */ 150function serendipity_get_user_config_var($name, $authorid, $default = '') { 151 global $serendipity; 152 153 $author_sql = ''; 154 if (!empty($authorid)) { 155 $author_sql = "authorid = " . (int)$authorid . " AND "; 156 } elseif (isset($serendipity[$name])) { 157 return $serendipity[$name]; 158 } 159 160 $r = serendipity_db_query("SELECT value FROM {$serendipity['dbPrefix']}config WHERE $author_sql name = '" . $name . "' LIMIT 1", true); 161 162 if (is_array($r)) { 163 return $r[0]; 164 } else { 165 return $default; 166 } 167} 168 169/** 170 * Retrieves an author-specific account value 171 * 172 * This retrieves specific account data from the user configuration, not from the serendipity configuration. 173 * 174 * @access public 175 * @param string The name of the configuration value 176 * @param int The ID of the author to fetch the configuration for 177 * @param string The default value of a configuration option, if not set in the DB 178 * @return string The configuration value content 179 */ 180function serendipity_get_user_var($name, $authorid, $default) { 181 global $serendipity; 182 183 $r = serendipity_db_query("SELECT $name FROM {$serendipity['dbPrefix']}authors WHERE authorid = " . (int)$authorid, true); 184 185 if (is_array($r)) { 186 return $r[0]; 187 } else { 188 return $default; 189 } 190} 191 192/** 193 * Updates data from the author-specific account 194 * 195 * This sets the personal account data of a serendipity user within the 'authors' DB table 196 * 197 * @access public 198 * @param string The name of the configuration value 199 * @param string The content of the configuration value 200 * @param int The ID of the author to set the configuration for 201 * @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. 202 * @return null 203 */ 204function serendipity_set_user_var($name, $val, $authorid, $copy_to_s9y = true) { 205 global $serendipity; 206 207 // When inserting a DB value, this array maps the new values to the corresponding s9y variables 208 static $user_map_array = array( 209 'username' => 'serendipityUser', 210 'email' => 'serendipityEmail', 211 'userlevel' => 'serendipityUserlevel' 212 ); 213 214 // Special case for inserting a password 215 switch($name) { 216 case 'check_password': 217 //Skip this field. It doesn't need to be stored. 218 return; 219 case 'password': 220 if (empty($val)) { 221 return; 222 } 223 224 $val = serendipity_hash($val); 225 $copy_to_s9y = false; 226 break; 227 228 case 'right_publish': 229 case 'mail_comments': 230 case 'mail_trackbacks': 231 $val = (serendipity_db_bool($val) ? 1 : '0'); 232 break; 233 } 234 235 serendipity_db_query("UPDATE {$serendipity['dbPrefix']}authors SET $name = '" . serendipity_db_escape_string($val) . "' WHERE authorid = " . (int)$authorid); 236 237 if ($copy_to_s9y) { 238 if (isset($user_map_array[$name])) { 239 $key = $user_map_array[$name]; 240 } else { 241 $key = 'serendipity' . ucfirst($name); 242 } 243 244 $_SESSION[$key] = $serendipity[$key] = $val; 245 } 246} 247 248/** 249 * Gets the full filename and path of a template/style/theme file 250 * 251 * The returned full path is depending on the second parameter, where you can either fetch a HTTP path, or a realpath. 252 * The file is searched in the current template, and if it is not found there, it is returned from the default template. 253 * 254 * @access public 255 * @param string The filename to search for in the selected template 256 * @param string The path selector that tells whether to return a HTTP or realpath 257 * @param bool Enable to include frontend template fallback chaining (used for wysiwyg Editor custom config files, emoticons, etc) 258 * @return string The full path+filename to the requested file 259 */ 260function serendipity_getTemplateFile($file, $key = 'serendipityHTTPPath', $force_frontend_fallback = false) { 261 global $serendipity; 262 263 $directories = array(); 264 if ((! defined('IN_serendipity_admin')) || $force_frontend_fallback) { 265 $directories[] = $serendipity['template'] . '/'; # In the frontend or when forced (=preview_iframe.tpl), use the frontend theme 266 } else { 267 $directories[] = $serendipity['template_backend'] . '/'; # Since 2.0 s9y can have a independent backend theme 268 } 269 $directories[] = $serendipity['template_engine'] . '/'; # themes can set an engine, which will be used if they do not have the file 270 $directories[] = $serendipity['defaultTemplate'] .'/'; # the default theme is the last place we will look in, serving as pure fallback 271 $directories = array_unique($directories); # save performance by not checking for file existence multiple times in the same directory 272 273 foreach ($directories as $directory) { 274 $templateFile = $serendipity['templatePath'] . $directory . $file; 275 if (file_exists($serendipity['serendipityPath'] . $templateFile)) { 276 return $serendipity[$key] . $templateFile; 277 } 278 279 if (file_exists($serendipity['serendipityPath'] . $templateFile . ".tpl")) { 280 # catch *.tpl files serving as template, used by the backend for serendipity_editor.js.tpl 281 return $serendipity['baseURL'] . 'index.php?/plugin/' . $file; 282 } 283 } 284 285 return false; 286} 287 288/** 289 * Loads all configuration values and imports them to the $serendipity array 290 * 291 * This function may be called twice - once for the global config and once for 292 * user-specific config 293 * 294 * @access public 295 * @param int The Authorid to fetch the configuration from (0: global) 296 * @return null 297 */ 298function serendipity_load_configuration($author = null) { 299 global $serendipity; 300 static $config_loaded = array(); 301 302 if (isset($config_loaded[$author])) { 303 return true; 304 } 305 306 if (!empty($author)) { 307 // Replace default configuration directives with user-relevant data 308 $rows =& serendipity_db_query("SELECT name,value 309 FROM {$serendipity['dbPrefix']}config 310 WHERE authorid = '". (int)$author ."'"); 311 } else { 312 // Only get default variables, user-independent (frontend) 313 $rows =& serendipity_db_query("SELECT name, value 314 FROM {$serendipity['dbPrefix']}config 315 WHERE authorid = 0"); 316 } 317 318 if (is_array($rows)) { 319 foreach ($rows as $row) { 320 // Convert 'true' and 'false' into booleans 321 $serendipity[$row['name']] = serendipity_get_bool($row['value']); 322 } 323 } 324 $config_loaded[$author] = true; 325 326 // Set baseURL to defaultBaseURL 327 if ((empty($author) || empty($serendipity['baseURL'])) && isset($serendipity['defaultBaseURL'])) { 328 $serendipity['baseURL'] = $serendipity['defaultBaseURL']; 329 } 330 331 // Store default language 332 $serendipity['default_lang'] = $serendipity['lang']; 333} 334 335/** 336 * Perform logout functions (destroys session data) 337 * 338 * @access public 339 * @return null 340 */ 341function serendipity_logout() { 342 $_SESSION['serendipityAuthedUser'] = false; 343 serendipity_session_destroy(); 344 serendipity_deleteCookie('author_username'); 345 serendipity_deleteCookie('author_autologintoken'); 346 serendipity_deleteCookie('author_token'); 347} 348 349/** 350 * Destroys a session, keeps important stuff intact. 351 * @access public 352 * @return null 353 */ 354function serendipity_session_destroy() { 355 $no_smarty = $_SESSION['no_smarty']; 356 @session_destroy(); 357 session_start(); 358 session_regenerate_id(); 359 360 $_SESSION['SERVER_GENERATED_SID'] = true; 361 $_SESSION['no_smarty'] = $no_smarty; 362} 363 364/** 365 * Perform login to Serendipity 366 * 367 * @access public 368 * @param boolean If set to true, external plugins will be queried for getting a login 369 * @return boolean Return true, if the user is logged in. False if not. 370 */ 371function serendipity_login($use_external = true) { 372 global $serendipity; 373 374 if (serendipity_authenticate_author('', '', false, $use_external)) { 375 #The session has this data already 376 #we previously just checked the value of $_SESSION['serendipityAuthedUser'] but 377 #we need the authorid still, so call serendipity_authenticate_author with blank 378 #params 379 return true; 380 } 381 382 // First try login via POST data. If true, the userinformation will be stored in a cookie (optionally) 383 if (serendipity_authenticate_author($serendipity['POST']['user'], $serendipity['POST']['pass'], false, $use_external)) { 384 if (empty($serendipity['POST']['auto'])) { 385 serendipity_deleteCookie('author_information'); 386 return false; 387 } else { 388 serendipity_issueAutologin($serendipity['POST']['user']); 389 return true; 390 } 391 // Now try login via COOKIE data 392 } elseif (isset($serendipity['COOKIE']['author_username'])) { 393 $user = $serendipity['COOKIE']['author_username']; 394 $valid_logintoken = serendipity_checkAutologin($user); 395 if ($valid_logintoken === true) { 396 // if we do not tie down the session gere it will be recreated on every page reload, which will fuck op the form token system. That's why we need to load all data that makes the session stick. That's why we call setAuthorToken here. 397 serendipity_setAuthorToken(); 398 serendipity_load_userdata($user); 399 return true; 400 } else { 401 serendipity_deleteCookie('author_username'); 402 serendipity_deleteCookie('author_autologintoken'); 403 return false; 404 } 405 } 406 407 $data = array('ext' => $use_external, 'mode' => 2, 'user' => $serendipity['POST']['user'], 'pass' => $serendipity['POST']['pass']); 408 serendipity_plugin_api::hook_event('backend_loginfail', $data); 409} 410 411/** 412 * Issue a new auto login cookie. For that we store in the options table (name, value, okey) the values (autologin_$username, a random token, the current time). 413 * @param String The username 414 */ 415function serendipity_issueAutologin($user) { 416 global $serendipity; 417 418 try { 419 // TODO: Add https://github.com/paragonie/random_compat for PHP < 7.0 420 $random_string = random_bytes(32); 421 } catch (TypeError $e) { 422 // Well, it's an integer, so this IS unexpected. 423 die("An unexpected error has occurred"); 424 } catch (Error $e) { 425 // This is also unexpected because 32 is a reasonable integer. 426 die("An unexpected error has occurred"); 427 } catch (Exception $e) { 428 // If you get this message, the CSPRNG failed hard. 429 die("Could not generate a random string. Is our OS secure?"); 430 } 431 432 $rnd = bin2hex($random_string); 433 434 435 // Delete possible current cookie. Also delete any autologin keys that smell like 3-week-old, dead fish. 436 if (stristr($serendipity['dbType'], 'sqlite')) { 437 $cast = "okey"; 438 } elseif (stristr($serendipity['dbType'], 'mysqli')) { 439 // Adds explicit casting for mysql. 440 $cast = "cast(okey as unsigned)"; 441 } else { 442 // Adds explicit casting for postgresql and others. 443 $cast = "cast(okey as integer)"; 444 } 445 446 serendipity_db_query("DELETE FROM {$serendipity['dbPrefix']}options 447 WHERE name = 'autologin_" . serendipity_db_escape_string($user) . "' 448 OR (name LIKE 'autologin_%' AND $cast < " . (time() - 1814400) . ")"); 449 450 // Issue new autologin cookie 451 serendipity_db_query("INSERT INTO {$serendipity['dbPrefix']}options (name, value, okey) VALUES ('autologin_" . serendipity_db_escape_string($user) . "', '" . $rnd . "', '" . time() . "')"); 452 serendipity_setCookie('author_autologintoken', $rnd, true, false, true); 453 serendipity_setCookie('author_username', $user); 454} 455 456/** 457 * Checks a new auto login cookie, by seeing whether the token stored in the cookie is the same as the one stored in the database 458 * @param String The username 459 * @returns bool true if valid, false if not 460 */ 461function serendipity_checkAutologin($user) { 462 global $serendipity; 463 464 if (stristr($serendipity['dbType'], 'sqlite')) { 465 $cast = "okey"; 466 } elseif (stristr($serendipity['dbType'], 'mysqli')) { 467 // Adds explicit casting for mysql. 468 $cast = "cast(okey as unsigned)"; 469 } else { 470 // Adds explicit casting for postgresql and others. 471 $cast = "cast(okey as integer)"; 472 } 473 474 // Fetch autologin data from DB 475 $autologin_stored = serendipity_db_query("SELECT name, value, okey FROM {$serendipity['dbPrefix']}options WHERE name = 'autologin_" . serendipity_db_escape_string($user) . "' AND $cast > " . (time() - 1814400) . " LIMIT 1", true, 'assoc'); 476 477 if (!is_array($autologin_stored)) { 478 return false; 479 } 480 481 if ($serendipity['COOKIE']['author_autologintoken'] !== $autologin_stored['value']) { 482 return false; 483 } 484 485 if ($autologin_stored['okey'] < (time()-86400)) { 486 // Issued autologin cookie has been issued more than 1 day ago. Re-Issue new cookie, invalidate old one to prevent abuse 487 if ($serendipity['expose_s9y']) serendipity_header('X-ReIssue-Cookie: +' . (time() - $autologin_stored['okey']) . 's'); 488 serendipity_issueAutologin($user); 489 } 490 491 return true; 492} 493 494/** 495 * Set a session cookie which can identify a user across http/https boundaries 496 */ 497function serendipity_setAuthorToken() { 498 $hash = sha1(uniqid(rand(), true)); 499 serendipity_setCookie('author_token', $hash); 500 $_SESSION['author_token'] = $hash; 501} 502 503/** 504 * Perform user authentication routine 505 * 506 * If a user is already authenticated via session data, this bypasses some routines. 507 * After a user has ben authenticated, several SESSION variables ar set. 508 * If the authentication fails, the session is destroyed. 509 * 510 * @access public 511 * @param string The username to check 512 * @param string The password to check (may contain plaintext or MD5 hash) 513 * @param boolean Indicates whether the input password is already in MD5 format (TRUE) or not (FALSE). 514 * @param boolean Indicates whether to query external plugins for authentication 515 * @return boolean True on success, False on error 516 */ 517function serendipity_authenticate_author($username = '', $password = '', $is_hashed = false, $use_external = true) { 518 global $serendipity; 519 static $debug = false; 520 static $debugc = 0; 521 522 if ($debug) { 523 $fp = fopen('login.log', 'a'); 524 flock($fp, LOCK_EX); 525 $debugc++; 526 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"); 527 } 528 if (isset($_SESSION['serendipityUser']) && isset($_SESSION['serendipityPassword']) && isset($_SESSION['serendipityAuthedUser']) && $_SESSION['serendipityAuthedUser'] == true) { 529 $username = $_SESSION['serendipityUser']; 530 $password = $_SESSION['serendipityPassword']; 531 // 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 532 #$is_hashed = true; 533 if ($debug) fwrite($fp, date('Y-m-d H:i') . ' - Recall from session: ' . $username . ':' . $password . "\n"); 534 } 535 536 if ($debug) fwrite($fp, date('Y-m-d H:i') . ' - Login ext check' . "\n"); 537 $is_authenticated = false; 538 serendipity_plugin_api::hook_event('backend_login', $is_authenticated, NULL); 539 if ($is_authenticated) { 540 return true; 541 } 542 543 if ($debug) fwrite($fp, date('Y-m-d H:i') . ' - Login username check:' . $username . "\n"); 544 if (!empty($username) && is_string($username)) { 545 if ($use_external) { 546 serendipity_plugin_api::hook_event('backend_auth', $is_hashed, array('username' => $username, 'password' => $password)); 547 } 548 549 $query = "SELECT DISTINCT 550 email, password, realname, authorid, userlevel, right_publish, hashtype 551 FROM 552 {$serendipity['dbPrefix']}authors 553 WHERE 554 username = '" . serendipity_db_escape_string($username) . "'"; 555 if ($debug) fwrite($fp, date('Y-m-d H:i') . ' - Login check (' . serialize($is_hashed) . ', ' . $_SESSION['serendipityPassword'] . '):' . $query . "\n"); 556 557 $rows =& serendipity_db_query($query, false, 'assoc'); 558 if (is_array($rows)) { 559 foreach($rows AS $row) { 560 if ($is_valid_user) continue; 561 $is_valid_user = false; 562 563 if (empty($row['hashtype']) || $row['hashtype'] == 0) { 564 // Old MD5 hashing routine. Will convert user. 565 if (isset($serendipity['hashkey']) && (time() - $serendipity['hashkey']) >= 15768000) { 566 die('You can no longer login with an old-style MD5 hash to prevent MD5-Hostage abuse. 567 Please ask the Administrator to set you a new password.'); 568 } 569 570 if ( ($is_hashed === false && (string)$row['password'] === (string)md5($password)) || 571 ($is_hashed !== false && (string)$row['password'] === (string)$password) ) { 572 573 serendipity_db_query("UPDATE {$serendipity['dbPrefix']}authors 574 SET password = '" . ($is_hashed === false ? serendipity_hash($password) : $password) . "', 575 hashtype = 1 576 WHERE authorid = '" . $row['authorid'] . "'"); 577 if ($debug) fwrite($fp, date('Y-m-d H:i') . ' - Migrated user:' . $row['username'] . "\n"); 578 $is_valid_user = true; 579 } else { 580 continue; 581 } 582 } else { 583 if ($row['hashtype'] == 1) { 584 // Old sha1 hashing routine. Will convert user. 585 if ( ($is_hashed === false && (string)$row['password'] === (string)serendipity_sha1_hash($password)) || 586 ($is_hashed !== false && (string)$row['password'] === (string)$password) ) { 587 588 serendipity_db_query("UPDATE {$serendipity['dbPrefix']}authors 589 SET password = '" . ($is_hashed === false ? serendipity_hash($password) : $password) . "', 590 hashtype = 2 591 WHERE authorid = '" . $row['authorid'] . "'"); 592 if ($debug) fwrite($fp, date('Y-m-d H:i') . ' - Migrated user:' . $row['username'] . "\n"); 593 $is_valid_user = true; 594 } else { 595 continue; 596 } 597 } else { 598 if ( ($is_hashed === false && password_verify((string)$password, $row['password'])) || 599 ($is_hashed !== false && (string)$row['password'] === (string)$password) ) { 600 601 $is_valid_user = true; 602 if ($debug) fwrite($fp, date('Y-m-d H:i') . ' - Validated ' . $row['password'] . ' == ' . ($is_hashed === false ? 'unhash:' . serendipity_hash($password) : 'hash:' . $password) . "\n"); 603 } else { 604 if ($debug) fwrite($fp, date('Y-m-d H:i') . ' - INValidated ' . $row['password'] . ' == ' . ($is_hashed === false ? 'unhash:' . serendipity_hash($password) : 'hash:' . $password) . "\n"); 605 continue; 606 } 607 } 608 } 609 610 // This code is only reached if the password before is valid. 611 if ($is_valid_user) { 612 if ($debug) fwrite($fp, date('Y-m-d H:i') . ' [sid:' . session_id() . '] - Success.' . "\n"); 613 serendipity_setCookie('old_session', session_id(), false); 614 if (!$is_hashed) { 615 serendipity_setAuthorToken(); 616 $_SESSION['serendipityPassword'] = $serendipity['serendipityPassword'] = $password; 617 } 618 619 return serendipity_load_userdata($username); 620 621 622 } 623 } 624 } 625 626 // Only reached, when proper login did not yet return true. 627 if ($debug) fwrite($fp, date('Y-m-d H:i') . ' - FAIL.' . "\n"); 628 629 $_SESSION['serendipityAuthedUser'] = false; 630 serendipity_session_destroy(); 631 } 632 633 if ($debug) { 634 fwrite($fp, date('Y-m-d H:i') . ' [sid:' . session_id() . '] - Uninit' . "\n"); 635 fclose($fp); 636 } 637 638 return false; 639} 640 641function serendipity_load_userdata($username) { 642 global $serendipity; 643 644 $query = "SELECT DISTINCT 645 email, password, realname, authorid, userlevel, right_publish, hashtype 646 FROM 647 {$serendipity['dbPrefix']}authors 648 WHERE 649 username = '" . serendipity_db_escape_string($username) . "'"; 650 651 $rows = serendipity_db_query($query, false, 'assoc'); 652 $row = $rows[0]; 653 654 $_SESSION['serendipityUser'] = $serendipity['serendipityUser'] = $username; 655 $_SESSION['serendipityRealname'] = $serendipity['serendipityRealname'] = $row['realname']; 656 $_SESSION['serendipityEmail'] = $serendipity['serendipityEmail'] = $row['email']; 657 $_SESSION['serendipityAuthorid'] = $serendipity['authorid'] = $row['authorid']; 658 $_SESSION['serendipityUserlevel'] = $serendipity['serendipityUserlevel'] = $row['userlevel']; 659 $_SESSION['serendipityAuthedUser'] = $serendipity['serendipityAuthedUser'] = true; 660 $_SESSION['serendipityRightPublish'] = $serendipity['serendipityRightPublish'] = $row['right_publish']; 661 $_SESSION['serendipityHashType'] = $serendipity['serendipityHashType'] = $row['hashtype']; 662 663 serendipity_load_configuration($serendipity['authorid']); 664 serendipity_setCookie('userDefLang', $serendipity['lang'], false); 665 return true; 666} 667 668/** 669 * Check if a user is logged in 670 * 671 * @access public 672 * @return boolean TRUE when logged in, FALSE when not. 673 */ 674function serendipity_userLoggedIn() { 675 if ($_SESSION['serendipityAuthedUser'] === true && IS_installed) { 676 return true; 677 } else { 678 return false; 679 } 680} 681 682/** 683 * A clone of an ifsetor() function to set a variable conditional on if the target already exists 684 * 685 * 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 686 * 687 * @access public 688 * @param mixed Source variable that should be set into the target variable (reference call!) 689 * @param mixed Target variable, that should get the contents of the source variable (reference call!) 690 * @return boolean True, when $target was not yet set and has been altered. False when no changes where made. 691 */ 692function serendipity_restoreVar(&$source, &$target) { 693 global $serendipity; 694 695 if (isset($source) && !isset($target)) { 696 $target = $source; 697 return true; 698 } 699 700 return false; 701} 702 703/** 704 * Set a Cookie via HTTP calls, and update $_COOKIE plus $serendipity['COOKIE'] array. 705 * 706 * @access public 707 * @param string The name of the cookie variable 708 * @param string The contents of the cookie variable 709 * @param int Cookie validity (unix timestamp) 710 * @return null 711 */ 712function serendipity_setCookie($name, $value, $securebyprot = true, $custom_timeout = false, $httpOnly = false) { 713 global $serendipity; 714 715 $host = $_SERVER['HTTP_HOST']; 716 if ($securebyprot) { 717 $secure = (strtolower($_SERVER['HTTPS']) == 'on') ? true : false; 718 if ($pos = strpos($host, ":")) { 719 $host = substr($host, 0, $pos); 720 } 721 } else { 722 $secure = false; 723 } 724 725 // If HTTP-Hosts like "localhost" are used, current browsers reject cookies. 726 // In this case, we disregard the HTTP host to be able to set that cookie. 727 if (substr_count($host, '.') < 1) { 728 $host = ''; 729 } 730 731 if ($custom_timeout === false) { 732 $custom_timeout = time() + 60*60*24*30; 733 } 734 735 setcookie("serendipity[$name]", $value, $custom_timeout, $serendipity['serendipityHTTPPath'], $host, $secure, $httpOnly); 736 $_COOKIE[$name] = $value; 737 $serendipity['COOKIE'][$name] = $value; 738} 739 740/** 741 * Echo Javascript code to set a cookie variable 742 * 743 * This function is useful if your HTTP headers were already sent, but you still want to set a cookie 744 * Note that contents are echo'd, not return'd. Can be used by plugins. 745 * 746 * @access public 747 * @param string The name of the cookie variable 748 * @param string The contents of the cookie variable 749 * @return null 750 */ 751function serendipity_JSsetCookie($name, $value) { 752 $name = serendipity_entities($name); 753 $value = urlencode($value); 754 755 echo '<script type="text/javascript">serendipity.SetCookie("' . $name . '", unescape("' . $value . '"))</script>' . "\n"; 756} 757 758 759/** 760 * Deletes an existing cookie value 761 * 762 * LONG 763 * 764 * @access public 765 * @param string Name of the cookie to delete 766 * @return 767 */ 768function serendipity_deleteCookie($name) { 769 global $serendipity; 770 771 $host = $_SERVER['HTTP_HOST']; 772 if ($pos = strpos($host, ":")) { 773 $host = substr($host, 0, $pos); 774 } 775 776 // If HTTP-Hosts like "localhost" are used, current browsers reject cookies. 777 // In this case, we disregard the HTTP host to be able to set that cookie. 778 if (substr_count($host, '.') < 1) { 779 $host = ''; 780 } 781 782 setcookie("serendipity[$name]", '', time()-4000, $serendipity['serendipityHTTPPath'], $host); 783 unset($_COOKIE[$name]); 784 unset($serendipity['COOKIE'][$name]); 785} 786 787/** 788 * Performs a check whether an iframe for the admin section shall be emitted 789 * 790 * The iframe is used for previewing an entry with the stylesheet of the frontend. 791 * It fetches its data from the session input data. 792 * 793 * @access private 794 * @return boolean True, if iframe was requested, false if not. 795 */ 796function serendipity_is_iframe() { 797 global $serendipity; 798 799 if ($serendipity['GET']['is_iframe'] && is_array($_SESSION['save_entry'])) { 800 if (!is_object($serendipity['smarty'])) { 801 // We need smarty also in the iframe to load a template's config.inc.php and register possible event hooks. 802 serendipity_smarty_init(); 803 } 804 return true; 805 } 806 return false; 807} 808 809/** 810 * Prints the content of the iframe. 811 * 812 * Called by serendipity_is_iframe, when preview is requested. Fetches data from session. 813 * An iframe is used so that a single s9y page must not timeout on intensive operations, 814 * and so that the frontend stylesheet can be embedded without screwing up the backend. 815 * 816 * @access private 817 * @see serendipity_is_iframe() 818 * @param mixed The entry array (comes from session variable) 819 * @param string Indicates whether an entry is previewed or saved. Save performs XML-RPC calls. 820 * @param boolean Use smarty templating? 821 * @return boolean Indicates whether iframe data was printed 822 */ 823function serendipity_iframe(&$entry, $mode = null) { 824 global $serendipity; 825 826 if (empty($mode) || !is_array($entry)) { 827 return false; 828 } 829 830 831 $data = array(); 832 $data['is_preview'] = true; 833 $data['mode'] = $mode; 834 835 switch ($mode) { 836 case 'save': 837 ob_start(); 838 $res = serendipity_updertEntry($entry); 839 $data['updertHooks'] = ob_get_contents(); 840 ob_end_clean(); 841 if (is_string($res)) { 842 $data['res'] = $res; 843 } 844 if (!empty($serendipity['lastSavedEntry'])) { 845 $data['lastSavedEntry'] = $serendipity['lastSavedEntry']; 846 } 847 $data['entrylink'] = serendipity_archiveURL($res, $entry['title'], 'serendipityHTTPPath', true, array('timestamp' => $entry['timestamp'])); 848 break; 849 850 case 'preview': 851 $data['preview'] = serendipity_printEntries(array($entry), ($entry['extended'] != '' ? 1 : 0), true); 852 break; 853 } 854 return serendipity_smarty_show('preview_iframe.tpl', $data); 855} 856 857/** 858 * Creates the necessary session data to be used by later iframe calls 859 * 860 * This function emits the actual <iframe> call. 861 * 862 * @access private 863 * @see serendipity_is_iframe() 864 * @param string Indicates whether an entry is previewed or saved. Save performs XML-RPC calls. 865 * @param mixed The entry array (comes from HTTP POST request) 866 * @return boolean Indicates whether iframe data was stored 867 */ 868function serendipity_iframe_create($mode, &$entry) { 869 global $serendipity; 870 871 if (!empty($serendipity['POST']['no_save'])) { 872 return true; 873 } 874 875 if (!serendipity_checkFormToken()) { 876 return false; 877 } 878 879 $_SESSION['save_entry'] = $entry; 880 $_SESSION['save_entry_POST'] = $serendipity['POST']; 881 882 $attr = ''; 883 switch($mode) { 884 case 'save': 885 $attr = ' height="100" '; 886 break; 887 888 case 'preview': 889 $attr = ' height="300" '; 890 break; 891 } 892 893 return '<iframe src="serendipity_admin.php?serendipity[is_iframe]=true&serendipity[iframe_mode]=' . $mode . '" id="serendipity_iframe" name="serendipity_iframe" ' . $attr . ' width="100%" frameborder="0" marginwidth="0" marginheight="0" scrolling="auto" title="Serendipity">' 894 . IFRAME_WARNING 895 . '</iframe>'; 896} 897 898/** 899 * Pre-Checks certain server environments to indicate available options when installing Serendipity 900 * 901 * @access public 902 * @param string The name of the configuration option that needs to be checked for environmental data. 903 * @return array Returns the array of available options for the requested config option 904 */ 905function serendipity_probeInstallation($item) { 906 global $serendipity; 907 $res = NULL; 908 909 switch ( $item ) { 910 case 'dbType' : 911 $res = array(); 912 if (extension_loaded('mysql')) { 913 $res['mysql'] = 'MySQL'; 914 } 915 if (extension_loaded('PDO') && 916 in_array('pgsql', PDO::getAvailableDrivers())) { 917 $res['pdo-postgres'] = 'PDO::PostgreSQL'; 918 } 919 920 if (extension_loaded('PDO') && 921 in_array('sqlite', PDO::getAvailableDrivers())) { 922 $res['pdo-sqlite'] = 'PDO::SQLite'; 923 $has_pdo = true; 924 } else { 925 $has_pdo = false; 926 } 927 928 if (extension_loaded('pgsql')) { 929 $res['postgres'] = 'PostgreSQL'; 930 } 931 if (extension_loaded('mysqli')) { 932 $res['mysqli'] = 'MySQLi'; 933 } 934 if (extension_loaded('sqlite') && function_exists('sqlite_open')) { 935 $res['sqlite'] = 'SQLite'; 936 } 937 if (extension_loaded('SQLITE3') && function_exists('sqlite3_open')) { 938 $res['sqlite3'] = 'SQLite3'; 939 } 940 if (class_exists('SQLite3')) { 941 if ($has_pdo) { 942 $res['sqlite3oo'] = 'SQLite3 (OO) (Preferably use PDO-SQlite!)'; 943 } else { 944 $res['sqlite3oo'] = 'SQLite3 (OO)'; 945 } 946 } 947 if (function_exists('sqlrcon_alloc')) { 948 $res['sqlrelay'] = 'SQLRelay'; 949 } 950 break; 951 952 case 'rewrite' : 953 $res = array(); 954 $res['none'] = 'Disable URL Rewriting'; 955 $res['errordocs'] = 'Use Apache errorhandling'; 956 if( !function_exists('apache_get_modules') || in_array('mod_rewrite', apache_get_modules()) ) { 957 $res['rewrite'] = 'Use Apache mod_rewrite'; 958 } 959 if( !function_exists('apache_get_modules') || in_array('mod_rewrite', apache_get_modules()) ) { 960 $res['rewrite2'] = 'Use Apache mod_rewrite (for 1&1 and problematic servers)'; 961 } 962 963 break; 964 } 965 966 return $res; 967} 968 969/** 970 * Sets a HTTP header 971 * 972 * @access public 973 * @param string The HTTP header to set 974 * @return null 975 */ 976function serendipity_header($header) { 977 if (!headers_sent()) { 978 header($header); 979 } 980} 981 982/** 983 * Gets the currently selected language. Either from the browser, or the personal configuration, or the global configuration. 984 * 985 * This function also sets HTTP Headers and cookies to contain the language for follow-up requests 986 * TODO: 987 * This previously was handled inside a plugin with an event hook, but caching 988 * the event plugins that early in sequence created trouble with plugins not 989 * having loaded the right language. 990 * Find a way to let plugins hook into that sequence :-) 991 * 992 * @access public 993 * @return string Returns the name of the selected language. 994 */ 995function serendipity_getSessionLanguage() { 996 global $serendipity; 997 998 // DISABLE THIS! 999/* 1000 if ($_SESSION['serendipityAuthedUser']) { 1001 serendipity_header('X-Serendipity-InterfaceLangSource: Database'); 1002 return $serendipity['lang']; 1003 } 1004*/ 1005 if (isset($serendipity['lang']) && !isset($serendipity['languages'][$serendipity['lang']])) { 1006 $serendipity['lang'] = $serendipity['autolang']; 1007 } 1008 1009 if (isset($_REQUEST['user_language']) && (!empty($serendipity['languages'][$_REQUEST['user_language']])) && !headers_sent()) { 1010 serendipity_setCookie('serendipityLanguage', $_REQUEST['user_language'], false); 1011 } 1012 1013 if (isset($serendipity['COOKIE']['serendipityLanguage'])) { 1014 if ($serendipity['expose_s9y']) serendipity_header('X-Serendipity-InterfaceLangSource: Cookie'); 1015 $lang = $serendipity['COOKIE']['serendipityLanguage']; 1016 } elseif (isset($serendipity['GET']['lang_selected']) && !empty($serendipity['languages'][$serendipity['GET']['lang_selected']])) { 1017 if ($serendipity['expose_s9y']) serendipity_header('X-Serendipity-InterfaceLangSource: GET'); 1018 $lang = $serendipity['GET']['lang_selected']; 1019 } elseif (serendipity_db_bool($serendipity['lang_content_negotiation'])) { 1020 if ($serendipity['expose_s9y']) serendipity_header('X-Serendipity-InterfaceLangSource: Content-Negotiation'); 1021 $lang = serendipity_detectLang(); 1022 } 1023 1024 if (isset($lang)) { 1025 $serendipity['detected_lang'] = $lang; 1026 } else { 1027 if (! empty($_SESSION['serendipityLanguage'])) { 1028 $lang = $_SESSION['serendipityLanguage']; 1029 } else { 1030 if (isset($serendipity['COOKIE']['userDefLang']) && ! empty($serendipity['COOKIE']['userDefLang'])) { 1031 $lang = $serendipity['COOKIE']['userDefLang']; 1032 } else { 1033 if ($serendipity['lang']) { 1034 $lang = $serendipity['lang']; 1035 } else { 1036 // serendipity_load_configuration() failed to load language, go default 1037 $lang = $serendipity['autolang']; 1038 } 1039 } 1040 } 1041 $serendipity['detected_lang'] = null; 1042 } 1043 1044 if (!isset($serendipity['languages'][$lang])) { 1045 $serendipity['detected_lang'] = null; 1046 return $serendipity['lang']; 1047 } else { 1048 $_SESSION['serendipityLanguage'] = $lang; 1049 if (!is_null($serendipity['detected_lang'])) { 1050 if ($serendipity['expose_s9y']) serendipity_header('X-Serendipity-InterfaceLang: ' . $lang); 1051 } 1052 } 1053 1054 return $lang; 1055} 1056 1057/** 1058 * Gets the selected language from personal configuration if needed 1059 * 1060 * This function also sets HTTP Headers and cookies to contain the language for follow-up requests 1061 * 1062 * @access public 1063 * @return string Returns the name of the selected language. 1064 */ 1065function serendipity_getPostAuthSessionLanguage() { 1066 global $serendipity; 1067 1068 if (! is_null($serendipity['detected_lang'])) { 1069 return $serendipity['detected_lang']; 1070 } 1071 1072 if ($_SESSION['serendipityAuthedUser']) { 1073 if ($serendipity['expose_s9y']) serendipity_header('X-Serendipity-InterfaceLangSource: Database'); 1074 $lang = $serendipity['lang']; 1075 } else { 1076 $lang = (isset($_SESSION['serendipityLanguage'])) ? $_SESSION['serendipityLanguage'] : $serendipity['lang']; 1077 } 1078 1079 if (!isset($serendipity['languages'][$lang])) { 1080 $lang = $serendipity['lang']; 1081 } 1082 1083 $_SESSION['serendipityLanguage'] = $lang; 1084 1085 if ($serendipity['expose_s9y']) serendipity_header('X-Serendipity-InterfaceLang: ' . $lang); 1086 1087 if ($lang != $serendipity['lang']) { 1088 $serendipity['content_lang'] = $lang; 1089 } 1090 1091 return $lang; 1092} 1093 1094/** 1095 * Retrieves an array of applying permissions to an author 1096 * 1097 * The privileges of each group an author is a member of are aggreated 1098 * and stored in a larger array. So both memberships and all aplying 1099 * privileges are returned. 1100 * 1101 * @access public 1102 * @param int The ID of the author to fetch permissions/group memberships for 1103 * @return array Multi-dimensional associative array which holds a 'membership' and permission name data 1104 */ 1105function &serendipity_getPermissions($authorid) { 1106 global $serendipity; 1107 1108 // Get group information 1109 $groups =& serendipity_db_query("SELECT ag.groupid, g.name, gc.property, gc.value 1110 FROM {$serendipity['dbPrefix']}authorgroups AS ag 1111 LEFT OUTER JOIN {$serendipity['dbPrefix']}groups AS g 1112 ON ag.groupid = g.id 1113 LEFT OUTER JOIN {$serendipity['dbPrefix']}groupconfig AS gc 1114 ON gc.id = g.id 1115 WHERE ag.authorid = " . (int)$authorid); 1116 $perm = array('membership' => array()); 1117 if (is_array($groups)) { 1118 foreach($groups AS $group) { 1119 $perm['membership'][$group['groupid']] = $group['groupid']; 1120 $perm[$group['groupid']][$group['property']] = $group['value']; 1121 } 1122 } 1123 return $perm; 1124} 1125 1126/** 1127 * Returns the list of available internal Serendipity permission field names 1128 * 1129 * This function also mapps which function was available to which userleves in older 1130 * Serendipity versions. Thus if an author does not have a certain privilege he should 1131 * have because of his userlevel, this can be reverse-mapped. 1132 * 1133 * @access public 1134 * @return array Multi-dimensional associative array which the list of all permission items plus their userlevel associations 1135 */ 1136function serendipity_getPermissionNames() { 1137 return array( 1138 'personalConfiguration' 1139 => array(USERLEVEL_ADMIN, USERLEVEL_CHIEF, USERLEVEL_EDITOR), 1140 'personalConfigurationUserlevel' 1141 => array(USERLEVEL_ADMIN, USERLEVEL_CHIEF), 1142 'personalConfigurationNoCreate' 1143 => array(USERLEVEL_ADMIN, USERLEVEL_CHIEF), 1144 'personalConfigurationRightPublish' 1145 => array(USERLEVEL_ADMIN, USERLEVEL_CHIEF), 1146 'siteConfiguration' 1147 => array(USERLEVEL_ADMIN), 1148 'blogConfiguration' 1149 => array(USERLEVEL_ADMIN, USERLEVEL_CHIEF), 1150 1151 'adminEntries' 1152 => array(USERLEVEL_ADMIN, USERLEVEL_CHIEF, USERLEVEL_EDITOR), 1153 'adminEntriesMaintainOthers' 1154 => array(USERLEVEL_ADMIN, USERLEVEL_CHIEF), 1155 1156 'adminImport' 1157 => array(USERLEVEL_ADMIN, USERLEVEL_CHIEF), 1158 1159 'adminCategories' 1160 => array(USERLEVEL_ADMIN, USERLEVEL_CHIEF, USERLEVEL_EDITOR), 1161 'adminCategoriesMaintainOthers' 1162 => array(USERLEVEL_ADMIN, USERLEVEL_CHIEF), 1163 'adminCategoriesDelete' 1164 => array(USERLEVEL_ADMIN, USERLEVEL_CHIEF), 1165 1166 'adminUsers' 1167 => array(USERLEVEL_ADMIN, USERLEVEL_CHIEF), 1168 'adminUsersDelete' 1169 => array(USERLEVEL_ADMIN, USERLEVEL_CHIEF), 1170 'adminUsersEditUserlevel' 1171 => array(USERLEVEL_ADMIN, USERLEVEL_CHIEF), 1172 'adminUsersMaintainSame' 1173 => array(USERLEVEL_ADMIN, USERLEVEL_CHIEF), 1174 'adminUsersMaintainOthers' 1175 => array(USERLEVEL_ADMIN), 1176 'adminUsersCreateNew' 1177 => array(USERLEVEL_ADMIN, USERLEVEL_CHIEF), 1178 'adminUsersGroups' 1179 => array(USERLEVEL_ADMIN, USERLEVEL_CHIEF), 1180 1181 'adminPlugins' 1182 => array(USERLEVEL_ADMIN, USERLEVEL_CHIEF), 1183 'adminPluginsMaintainOthers' 1184 => array(USERLEVEL_ADMIN), 1185 1186 'adminImages' 1187 => array(USERLEVEL_ADMIN, USERLEVEL_CHIEF, USERLEVEL_EDITOR), 1188 'adminImagesDirectories' 1189 => array(USERLEVEL_ADMIN, USERLEVEL_CHIEF), 1190 'adminImagesAdd' 1191 => array(USERLEVEL_ADMIN, USERLEVEL_CHIEF, USERLEVEL_EDITOR), 1192 'adminImagesDelete' 1193 => array(USERLEVEL_ADMIN, USERLEVEL_CHIEF, USERLEVEL_EDITOR), 1194 'adminImagesMaintainOthers' 1195 => array(USERLEVEL_ADMIN, USERLEVEL_CHIEF), 1196 'adminImagesViewOthers' 1197 => array(USERLEVEL_ADMIN, USERLEVEL_CHIEF, USERLEVEL_EDITOR), 1198 'adminImagesView' 1199 => array(USERLEVEL_ADMIN, USERLEVEL_CHIEF, USERLEVEL_EDITOR), 1200 'adminImagesSync' 1201 => array(USERLEVEL_ADMIN, USERLEVEL_CHIEF), 1202 1203 'adminComments' 1204 => array(USERLEVEL_ADMIN, USERLEVEL_CHIEF), 1205 1206 'adminTemplates' 1207 => array(USERLEVEL_ADMIN, USERLEVEL_CHIEF), 1208 1209 'hiddenGroup' 1210 => array(-1) 1211 ); 1212} 1213 1214/** 1215 * Checks if a permission is granted to a specific author 1216 * 1217 * This function caches all permission chacks in static function variables to not 1218 * fetch all permissions time and again. 1219 * The permission checks are performed against the values of each group. If a privilege 1220 * is set in one of the groups the author is a user of, the function returns true. 1221 * If a privilege is not set, the userlevel of an author is checked to act for backwards-compatibility. 1222 * 1223 * @access public 1224 * @see serendipity_getPermissionNames() 1225 * @param string The name of the permission to check 1226 * @param int The authorid for which the permission check should be performed 1227 * @param boolean If set to true, all groups that the requested author is a user of will be returned. This bypasses the permission check and mainly acts as a mean to return cached permissions, since those variables are only available within this function. 1228 * @return mixed Either returns true if a permission check is performed, or return an array of group memberships. Depends on the $returnMyGroups variable. 1229 */ 1230function serendipity_checkPermission($permName, $authorid = null, $returnMyGroups = false) { 1231 global $serendipity; 1232 1233 // Define old serendipity permissions 1234 static $permissions = null; 1235 static $group = null; 1236 1237 if (IS_installed !== true) { 1238 return true; 1239 } 1240 1241 if ($permissions === null) { 1242 $permissions = serendipity_getPermissionNames(); 1243 } 1244 1245 if ($group === null) { 1246 $group = array(); 1247 } 1248 1249 if ($authorid === null) { 1250 $authorid = $serendipity['authorid']; 1251 } 1252 1253 if (!isset($group[$authorid])) { 1254 $group[$authorid] = serendipity_getPermissions($authorid); 1255 } 1256 1257 if ($returnMyGroups) { 1258 if ($returnMyGroups === 'all') { 1259 return $group[$authorid]; 1260 } else { 1261 return $group[$authorid]['membership']; 1262 } 1263 } 1264 1265 if ($authorid == $serendipity['authorid'] && $serendipity['no_create']) { 1266 // This no_create user privilege overrides other permissions. 1267 return false; 1268 } 1269 1270 $return = true; 1271 1272 foreach($group[$authorid] AS $item) { 1273 if (!isset($item[$permName])) { 1274 continue; 1275 } 1276 1277 if ($item[$permName] === 'true') { 1278 return true; 1279 } else { 1280 $return = false; 1281 } 1282 } 1283 1284 // If the function did not yet return it means there's a check for a permission which is not defined anywhere. 1285 // Let's use a backwards compatible way. 1286 if ($return && isset($permissions[$permName]) && in_array($serendipity['serendipityUserlevel'], $permissions[$permName])) { 1287 return true; 1288 } 1289 1290 return false; 1291} 1292 1293/** 1294 * Update author group membership(s) 1295 * 1296 * @access public 1297 * @param array The array of groups the author should be a member of. All memberships that were present before and not contained in this array will be removed. 1298 * @param int The ID of the author to update 1299 * @param boolean If set to true, the groups can only be updated if the user has the adminUsersMaintainOthers privilege. If set to false, group memberships will be changeable for any user. 1300 * @return 1301 */ 1302function serendipity_updateGroups($groups, $authorid, $apply_acl = true) { 1303 global $serendipity; 1304 1305 if ($apply_acl && !serendipity_checkPermission('adminUsersMaintainOthers')) { 1306 return false; 1307 } 1308 1309 serendipity_db_query("DELETE FROM {$serendipity['dbPrefix']}authorgroups WHERE authorid = " . (int)$authorid); 1310 1311 foreach($groups AS $group) { 1312 serendipity_db_query("INSERT INTO {$serendipity['dbPrefix']}authorgroups (authorid, groupid) VALUES (" . (int)$authorid . ", " . (int)$group . ")"); 1313 } 1314 return true; 1315} 1316 1317/** 1318 * Returns all authorgroups that are available 1319 * 1320 * If a groupname is prefixed with "USERLEVEL_" then the constant of that name is used to 1321 * return the name of the group. This allows inserting the special groups Chief Editor, Editor 1322 * and admin and still being able to use multilingual names for these groups. 1323 * 1324 * @access public 1325 * @param int If set to an author ID value, only groups are fetched that this author is a member of. If set to false, all groups are returned, also those that the current user has no access to. 1326 * @return array An associative array of group names. 1327 */ 1328function &serendipity_getAllGroups($apply_ACL_user = false) { 1329 global $serendipity; 1330 1331 if ($apply_ACL_user) { 1332 $groups =& serendipity_db_query("SELECT g.id AS confkey, 1333 g.name AS confvalue, 1334 g.id AS id, 1335 g.name AS name 1336 FROM {$serendipity['dbPrefix']}authorgroups AS ag 1337 LEFT OUTER JOIN {$serendipity['dbPrefix']}groups AS g 1338 ON g.id = ag.groupid 1339 WHERE ag.authorid = " . (int)$apply_ACL_user . " 1340 ORDER BY g.name", false, 'assoc'); 1341 } else { 1342 $groups =& serendipity_db_query("SELECT g.id AS confkey, 1343 g.name AS confvalue, 1344 g.id AS id, 1345 g.name AS name 1346 FROM {$serendipity['dbPrefix']}groups AS g 1347 ORDER BY g.name", false, 'assoc'); 1348 } 1349 if (is_array($groups)) { 1350 foreach ($groups as $k => $v) { 1351 if ('USERLEVEL_' == substr($v['confvalue'], 0, 10)) { 1352 $groups[$k]['confvalue'] = $groups[$k]['name'] = constant($v['confvalue']); 1353 } 1354 } 1355 } 1356 return $groups; 1357} 1358 1359/** 1360 * Fetch the permissions of a certain group 1361 * 1362 * @access public 1363 * @param int The ID of the group that the permissions are fetched for 1364 * @return array The associative array of permissions of a group. 1365 */ 1366function &serendipity_fetchGroup($groupid) { 1367 global $serendipity; 1368 1369 $conf = array(); 1370 $groups =& serendipity_db_query("SELECT g.id AS confkey, 1371 g.name AS confvalue, 1372 g.id AS id, 1373 g.name AS name, 1374 1375 gc.property AS property, 1376 gc.value AS value 1377 FROM {$serendipity['dbPrefix']}groups AS g 1378 LEFT OUTER JOIN {$serendipity['dbPrefix']}groupconfig AS gc 1379 ON g.id = gc.id 1380 WHERE g.id = " . (int)$groupid, false, 'assoc'); 1381 1382 if (is_array($groups)) { 1383 foreach($groups AS $group) { 1384 $conf[$group['property']] = $group['value']; 1385 } 1386 } 1387 1388 // The following are unique 1389 $conf['name'] = $groups[0]['name']; 1390 $conf['id'] = $groups[0]['id']; 1391 $conf['confkey'] = $groups[0]['confkey']; 1392 $conf['confvalue'] = $groups[0]['confvalue']; 1393 1394 return $conf; 1395} 1396 1397/** 1398 * Gets all groups a user is a member of 1399 * 1400 * @access public 1401 * @param int The authorid to fetch groups for 1402 * @param boolean Indicate whether the original multi-dimensional DB result array shall be returned (FALSE) or if the array shall be flattened to be 1-dimensional (TRUE). 1403 * @return 1404 */ 1405function &serendipity_getGroups($authorid, $sequence = false) { 1406 global $serendipity; 1407 1408 $_groups =& serendipity_db_query("SELECT g.id AS confkey, 1409 g.name AS confvalue, 1410 g.id AS id, 1411 g.name AS name 1412 FROM {$serendipity['dbPrefix']}authorgroups AS ag 1413 LEFT OUTER JOIN {$serendipity['dbPrefix']}groups AS g 1414 ON g.id = ag.groupid 1415 WHERE ag.authorid = " . (int)$authorid, false, 'assoc'); 1416 if (!is_array($_groups)) { 1417 $groups = array(); 1418 } else { 1419 $groups =& $_groups; 1420 } 1421 1422 if ($sequence) { 1423 $rgroups = array(); 1424 foreach($groups AS $grouprow) { 1425 $rgroups[] = $grouprow['confkey']; 1426 } 1427 } else { 1428 $rgroups =& $groups; 1429 } 1430 1431 return $rgroups; 1432} 1433 1434/** 1435 * Gets all author IDs of a specific group 1436 * 1437 * @access public 1438 * @param int The ID of the group to fetch the authors of 1439 * @return array The associative array of author IDs and names 1440 */ 1441function &serendipity_getGroupUsers($groupid) { 1442 global $serendipity; 1443 1444 $groups =& serendipity_db_query("SELECT g.name AS name, 1445 a.realname AS author, 1446 a.authorid AS id 1447 FROM {$serendipity['dbPrefix']}authorgroups AS ag 1448 LEFT OUTER JOIN {$serendipity['dbPrefix']}groups AS g 1449 ON g.id = ag.groupid 1450 LEFT OUTER JOIN {$serendipity['dbPrefix']}authors AS a 1451 ON ag.authorid = a.authorid 1452 WHERE ag.groupid = " . (int)$groupid, false, 'assoc'); 1453 return $groups; 1454} 1455 1456/** 1457 * Deletes a specific author group by ID 1458 * 1459 * @access public 1460 * @param int The group ID to delete 1461 * @return boolean Return true if group could be deleted, false if unsufficient privileges. 1462 */ 1463function serendipity_deleteGroup($groupid) { 1464 global $serendipity; 1465 1466 if (!serendipity_checkPermission('adminUsersGroups')) { 1467 return false; 1468 } 1469 1470 if (!serendipity_checkPermission('adminUsersMaintainOthers')) { 1471 // Only groups should be accessible where a user has access rights. 1472 $my_groups = serendipity_getGroups($serendipity['authorid'], true); 1473 if (!in_array($groupid, $my_groups)) { 1474 return false; 1475 } 1476 } 1477 1478 serendipity_db_query("DELETE FROM {$serendipity['dbPrefix']}groups WHERE id = " . (int)$groupid); 1479 serendipity_db_query("DELETE FROM {$serendipity['dbPrefix']}authorgroups WHERE groupid = " . (int)$groupid); 1480 1481 return true; 1482} 1483 1484/** 1485 * Creates a new author group 1486 * 1487 * @access public 1488 * @param string The name of the new group 1489 * @return int The id of the created group 1490 */ 1491function serendipity_addGroup($name) { 1492 global $serendipity; 1493 1494 serendipity_db_query("INSERT INTO {$serendipity['dbPrefix']}groups (name) VALUES ('" . serendipity_db_escape_string($name) . "')"); 1495 $gid = serendipity_db_insert_id('groups', 'id'); 1496 1497 return $gid; 1498} 1499 1500/** 1501 * Returns a list of all existing permission names. 1502 * 1503 * Additional plugins might insert specific properties into the groupconfig database to 1504 * handle their own privileges. This call returns an array of all available permission names 1505 * so that it can be intersected with the list of internal permission names (serendipity_getPermissionNames()) 1506 * and the be distincted. 1507 * 1508 * @access public 1509 * @see serendipity_getPermissionNames() 1510 * @return array associative array of all available permission names 1511 */ 1512function &serendipity_getDBPermissionNames() { 1513 global $serendipity; 1514 1515 $config =& serendipity_db_query("SELECT property FROM {$serendipity['dbPrefix']}groupconfig GROUP BY property ORDER BY property", false, 'assoc'); 1516 1517 return $config; 1518} 1519 1520/** 1521 * Gets the list of all Permissions and merges the two arrays 1522 * 1523 * The first call will fetch all existing permission names of the database. Then it will 1524 * fetch the list of defined internal permission names, which has an array with extra information 1525 * (like userlevel). This array will be merged with those permission names only found in the 1526 * database. The returned array will then hold as much information about permission names as is 1527 * available. 1528 * TODO Might need further pushing and/or an event hook so that external plugins using the 1529 * permission system can inject specific information into the array 1530 * 1531 * @access public 1532 * @see serendipity_getPermissionNames() 1533 * @see serendipity_getDBPermissionNames() 1534 * @return array Returns the array with all information about all permission names 1535 */ 1536function &serendipity_getAllPermissionNames() { 1537 global $serendipity; 1538 1539 $DBperms =& serendipity_getDBPermissionNames(); 1540 $perms = serendipity_getPermissionNames(); 1541 1542 foreach($DBperms AS $perm) { 1543 if (!isset($perms[$perm['property']])) { 1544 $perms[$perm['property']] = array(); 1545 } 1546 } 1547 1548 return $perms; 1549} 1550 1551/** 1552 * Checks if two users are members of the same group 1553 * 1554 * This function will retrieve all group memeberships of a 1555 * foreign user ($checkuser) and yourself ($myself). Then it 1556 * will check if there is any group membership that those 1557 * two users have in common. 1558 * It can be used for detecting if a different author should 1559 * be allowed to access your entries, because he's in the same 1560 * group, for example. 1561 * 1562 * @access public 1563 * @param int ID of the first author to check group memberships 1564 * @param int ID of the second author to check group memberships 1565 * @return boolea True if a membership intersects, false if not 1566 */ 1567function serendipity_intersectGroup($checkuser = null, $myself = null) { 1568 global $serendipity; 1569 1570 if ($myself === null) { 1571 $myself = $serendipity['authorid']; 1572 } 1573 1574 $my_groups = serendipity_getGroups($myself, true); 1575 $his_groups = serendipity_getGroups($checkuser, true); 1576 1577 foreach($his_groups AS $his_group) { 1578 if (in_array($his_group, $my_groups)) { 1579 return true; 1580 } 1581 } 1582 1583 return false; 1584} 1585 1586/** 1587 * Updates the configuration of permissions of a specific group 1588 * 1589 * This function ensures that a group can only be updated from users that have permissions to do so. 1590 * @access public 1591 * @param int The ID of the group to update 1592 * @param array The associative array of permission names 1593 * @param array The associative array of new values for the permissions. Needs the same associative keys like the $perms array. 1594 * @param bool Indicates if an all new privilege should be inserted (true) or if an existing privilege is going to be checked 1595 * @param array The associative array of plugin permission names 1596 * @param array The associative array of plugin permission hooks 1597 * @return true 1598 */ 1599function serendipity_updateGroupConfig($groupid, &$perms, &$values, $isNewPriv = false, $forbidden_plugins = null, $forbidden_hooks = null) { 1600 global $serendipity; 1601 1602 if (!serendipity_checkPermission('adminUsersGroups')) { 1603 return false; 1604 } 1605 1606 if (!serendipity_checkPermission('adminUsersMaintainOthers')) { 1607 // Only groups should be accessible where a user has access rights. 1608 $my_groups = serendipity_getGroups($serendipity['authorid'], true); 1609 if (!in_array($groupid, $my_groups)) { 1610 return false; 1611 } 1612 } 1613 1614 $storage =& serendipity_fetchGroup($groupid); 1615 1616 serendipity_db_query("DELETE FROM {$serendipity['dbPrefix']}groupconfig WHERE id = " . (int)$groupid); 1617 foreach ($perms AS $perm => $userlevels) { 1618 if (substr($perm, 0, 2) == 'f_') { 1619 continue; 1620 } 1621 1622 if (isset($values[$perm]) && $values[$perm] == 'true') { 1623 $value = 'true'; 1624 } elseif (isset($values[$perm]) && $values[$perm] === 'false') { 1625 $value = 'false'; 1626 } elseif (isset($values[$perm])) { 1627 $value = $values[$perm]; 1628 } else { 1629 $value = 'false'; 1630 } 1631 1632 if ($isNewPriv == false && !serendipity_checkPermission($perm) && $perm != 'hiddenGroup') { 1633 if (!isset($storage[$perm])) { 1634 $value = 'false'; 1635 } else { 1636 $value = $storage[$perm]; 1637 } 1638 } 1639 1640 serendipity_db_query( 1641 sprintf("INSERT INTO {$serendipity['dbPrefix']}groupconfig (id, property, value) VALUES (%d, '%s', '%s')", 1642 (int)$groupid, 1643 serendipity_db_escape_string($perm), 1644 serendipity_db_escape_string($value) 1645 ) 1646 ); 1647 } 1648 1649 if (is_array($forbidden_plugins)) { 1650 foreach($forbidden_plugins AS $plugid) { 1651 serendipity_db_query( 1652 sprintf("INSERT INTO {$serendipity['dbPrefix']}groupconfig (id, property, value) VALUES (%d, '%s', 'true')", 1653 (int)$groupid, 1654 serendipity_db_escape_string('f_' . urldecode($plugid)) 1655 ) 1656 ); 1657 } 1658 } 1659 1660 if (is_array($forbidden_hooks)) { 1661 foreach($forbidden_hooks AS $hook) { 1662 serendipity_db_query( 1663 sprintf("INSERT INTO {$serendipity['dbPrefix']}groupconfig (id, property, value) VALUES (%d, '%s', 'true')", 1664 (int)$groupid, 1665 serendipity_db_escape_string('f_' . urldecode($hook)) 1666 ) 1667 ); 1668 } 1669 } 1670 1671 serendipity_db_query("UPDATE {$serendipity['dbPrefix']}groups SET name = '" . serendipity_db_escape_string($values['name']) . "' WHERE id = " . (int)$groupid); 1672 1673 if (is_array($values['members'])) { 1674 serendipity_db_query("DELETE FROM {$serendipity['dbPrefix']}authorgroups WHERE groupid = " . (int)$groupid); 1675 foreach($values['members'] AS $member) { 1676 serendipity_db_query( 1677 sprintf("INSERT INTO {$serendipity['dbPrefix']}authorgroups (groupid, authorid) VALUES (%d, %d)", 1678 (int)$groupid, 1679 (int)$member 1680 ) 1681 ); 1682 } 1683 } 1684 1685 return true; 1686} 1687 1688/** 1689 * Adds a default internal group (Editor, Chief Editor, Admin) 1690 * 1691 * @access public 1692 * @param string The name of the group to insert 1693 * @param int The userlevel that represents this group (0|1|255 for Editor/Chief/Admin). 1694 * @return true 1695 */ 1696function serendipity_addDefaultGroup($name, $level) { 1697 global $serendipity; 1698 1699 static $perms = null; 1700 if ($perms === null) { 1701 $perms = serendipity_getPermissionNames(); 1702 } 1703 1704 serendipity_db_query("INSERT INTO {$serendipity['dbPrefix']}groups (name) VALUES ('" . serendipity_db_escape_string($name) . "')"); 1705 $gid = (int)serendipity_db_insert_id('groups', 'id'); 1706 serendipity_db_query("INSERT INTO {$serendipity['dbPrefix']}groupconfig (id, property, value) VALUES ($gid, 'userlevel', '" . (int)$level . "')"); 1707 1708 $authors = serendipity_db_query("SELECT * FROM {$serendipity['dbPrefix']}authors WHERE userlevel = " . (int)$level); 1709 1710 if (is_array($authors)) { 1711 foreach($authors AS $author) { 1712 serendipity_db_query("INSERT INTO {$serendipity['dbPrefix']}authorgroups (authorid, groupid) VALUES ('{$author['authorid']}', '$gid')"); 1713 } 1714 } 1715 1716 foreach($perms AS $permName => $permArray) { 1717 if (in_array($level, $permArray)) { 1718 serendipity_db_query("INSERT INTO {$serendipity['dbPrefix']}groupconfig (id, property, value) VALUES ($gid, '" . serendipity_db_escape_string($permName) . "', 'true')"); 1719 } else { 1720 serendipity_db_query("INSERT INTO {$serendipity['dbPrefix']}groupconfig (id, property, value) VALUES ($gid, '" . serendipity_db_escape_string($permName) . "', 'false')"); 1721 } 1722 } 1723 1724 return true; 1725} 1726 1727/** 1728 * Allow access to a specific item (category or entry) for a specific usergroup 1729 * 1730 * ACL are Access Control Lists. They indicate which read/write permissions a 1731 * specific item has for specific usergroups. 1732 * An artifact in terms of Serendipity can be either a category or an entry, or 1733 * anything beyond that for future compatibility. 1734 * This function sets up the ACLs. 1735 * 1736 * @access public 1737 * @param int The ID of the artifact to set the access 1738 * @param string The type of an artifact (category|entry) 1739 * @param string The type of access to grant (read|write) 1740 * @param array The ID of the group to grant access to 1741 * @param string A variable option for an artifact 1742 * @return boolean True if ACL was applied, false if not. 1743 */ 1744function serendipity_ACLGrant($artifact_id, $artifact_type, $artifact_mode, $groups, $artifact_index = '') { 1745 global $serendipity; 1746 1747 if (empty($groups) || !is_array($groups)) { 1748 return false; 1749 } 1750 1751 // Delete all old existing relations. 1752 serendipity_db_query("DELETE FROM {$serendipity['dbPrefix']}access 1753 WHERE artifact_id = " . (int)$artifact_id . " 1754 AND artifact_type = '" . serendipity_db_escape_string($artifact_type) . "' 1755 AND artifact_mode = '" . serendipity_db_escape_string($artifact_mode) . "' 1756 AND artifact_index = '" . serendipity_db_escape_string($artifact_index) . "'"); 1757 1758 $data = array( 1759 'artifact_id' => (int)$artifact_id, 1760 'artifact_type' => $artifact_type, 1761 'artifact_mode' => $artifact_mode, 1762 'artifact_index' => $artifact_index 1763 ); 1764 1765 if (count($data) < 1) { 1766 return true; 1767 } 1768 1769 foreach($groups AS $group) { 1770 $data['groupid'] = $group; 1771 serendipity_db_insert('access', $data); 1772 } 1773 1774 return true; 1775} 1776 1777/** 1778 * Checks if a specific item (category or entry) can be accessed by a specific usergroup 1779 * 1780 * ACL are Access Control Lists. They indicate which read/write permissions a 1781 * specific item has for specific usergroups. 1782 * An artifact in terms of Serendipity can be either a category or an entry, or 1783 * anything beyond that for future compatibility. 1784 * This function retrieves the ACLs. 1785 * 1786 * @access public 1787 * @param int The ID of the artifact to set the access 1788 * @param string The type of an artifact (category|entry) 1789 * @param string The type of access to check for (read|write) 1790 * @param string A variable option for an artifact 1791 * @return array Returns an array of all groups that are allowed for this kind of access. You can then check if you are the member of any of the groups returned here. 1792 */ 1793function serendipity_ACLGet($artifact_id, $artifact_type, $artifact_mode, $artifact_index = '') { 1794 global $serendipity; 1795 1796 $sql = "SELECT groupid, artifact_index FROM {$serendipity['dbPrefix']}access 1797 WHERE artifact_type = '" . serendipity_db_escape_string($artifact_type) . "' 1798 AND artifact_id = '" . (int)$artifact_id . "' 1799 AND artifact_mode = '" . serendipity_db_escape_string($artifact_mode) . "' 1800 AND artifact_index = '" . serendipity_db_escape_string($artifact_index) . "'"; 1801 $rows =& serendipity_db_query($sql, false, 'assoc'); 1802 1803 if (!is_array($rows)) { 1804 return false; 1805 } 1806 1807 $acl = array(); 1808 foreach($rows AS $row) { 1809 $acl[$row['groupid']] = $row['artifact_index']; 1810 } 1811 1812 return $acl; 1813} 1814 1815/** 1816 * Checks if a specific item (category or entry) can be accessed by a specific Author 1817 * 1818 * ACL are Access Control Lists. They indicate which read/write permissions a 1819 * specific item has for specific usergroups. 1820 * An artifact in terms of Serendipity can be either a category or an entry, or 1821 * anything beyond that for future compatibility. 1822 * This function retrieves the ACLs for a specific user. 1823 * 1824 * @access public 1825 * @param int The ID of the author to check against. 1826 * @param int The ID of the artifact to set the access 1827 * @param string The type of an artifact ('category', more to come) 1828 * @param string The type of access to check for (read|write) 1829 * @return boolean Returns true, if the author has access to this artifact. False if not. 1830 */ 1831function serendipity_ACLCheck($authorid, $artifact_id, $artifact_type, $artifact_mode) { 1832 global $serendipity; 1833 1834 $artifact_sql = array(); 1835 1836 // TODO: If more artifact_types are available, the JOIN needs to be edited so that the first AND portion is not required, and the join is fully made on that conditiion. 1837 switch($artifact_type) { 1838 default: 1839 case 'category': 1840 $artifact_sql['unique']= "atf.categoryid"; 1841 $artifact_sql['cond'] = "atf.categoryid = " . (int)$artifact_id; 1842 $artifact_sql['where'] = " ag.groupid = a.groupid 1843 OR a.groupid = 0 1844 OR (a.artifact_type IS NULL AND (atf.authorid = " . (int)$authorid . " OR atf.authorid = 0 OR atf.authorid IS NULL))"; 1845 $artifact_sql['table'] = 'category'; 1846 } 1847 1848 $sql = "SELECT {$artifact_sql['unique']} AS result 1849 FROM {$serendipity['dbPrefix']}{$artifact_sql['table']} AS atf 1850 LEFT OUTER JOIN {$serendipity['dbPrefix']}authorgroups AS ag 1851 ON ag.authorid = ". (int)$authorid . " 1852 LEFT OUTER JOIN {$serendipity['dbPrefix']}access AS a 1853 ON ( a.artifact_type = '" . serendipity_db_escape_string($artifact_type) . "' 1854 AND a.artifact_id = " . (int)$artifact_id . " 1855 AND a.artifact_mode = '" . serendipity_db_escape_string($artifact_mode) . "' 1856 ) 1857 1858 WHERE {$artifact_sql['cond']} 1859 AND ( {$artifact_sql['where']} ) 1860 GROUP BY result"; 1861 1862 $res =& serendipity_db_query($sql, true, 'assoc'); 1863 if (is_array($res) && !empty($res['result'])) { 1864 return true; 1865 } 1866 1867 return false; 1868} 1869 1870/** 1871 * Prepares a SQL statement to be used in queries that should be ACL restricted. 1872 * 1873 * ACL are Access Control Lists. They indicate which read/write permissions a 1874 * specific item has for specific usergroups. 1875 * An artifact in terms of Serendipity can be either a category or an entry, or 1876 * anything beyond that for future compatibility. 1877 * This function evaluates and applies the SQL statements required. 1878 * It is currently only written for retrieving Category ACLs. 1879 * All of the SQL code that will be used in serendipity_fetchEntries will be stored 1880 * within the referenced $cond array. 1881 * 1882 * @access private 1883 * @param array Associative array that holds the SQL part array to be used in other functions like serendipity_fetchEntries() 1884 * @param boolean Some queries do not need to joins categories. When ACLs need to be applied, this column is required, so if $append_category is set to true it will perform this missing JOIN. 1885 * @param string The ACL type ('category', 'directory') 1886 * @param string ACL mode 1887 * @return true True if ACLs were applied, false if not. 1888 */ 1889function serendipity_ACL_SQL(&$cond, $append_category = false, $type = 'category', $mode = 'read') { 1890 global $serendipity; 1891 1892 // A global configuration item controls whether the blog should apply ACLs or not! 1893 if (!isset($serendipity['enableACL']) || $serendipity['enableACL'] == true) { 1894 1895 // If the user is logged in, we retrieve his authorid for the upcoming checks 1896 if ($_SESSION['serendipityAuthedUser'] === true) { 1897 $read_id = (int)$serendipity['authorid']; 1898 $read_id_sql = 'acl_a.groupid OR acl_acc.groupid = 0'; 1899 } else { 1900 // "0" as category property counts as "anonymous viewers" 1901 $read_id = 0; 1902 $read_id_sql = 0; 1903 } 1904 1905 if ($append_category) { 1906 if ($append_category !== 'limited') { 1907 $cond['joins'] .= " LEFT JOIN {$serendipity['dbPrefix']}entrycat ec 1908 ON e.id = ec.entryid"; 1909 } 1910 1911 $cond['joins'] .= " LEFT JOIN {$serendipity['dbPrefix']}category c 1912 ON ec.categoryid = c.categoryid"; 1913 } 1914 1915 switch($type) { 1916 case 'directory': 1917 $sql_artifact_column = 'i.path IS NULL OR 1918 acl_acc.groupid IS NULL'; 1919 $sql_artifact = 'AND acl_acc.artifact_index = i.path'; 1920 break; 1921 1922 case 'category': 1923 $sql_artifact_column = 'c.categoryid IS NULL'; 1924 $sql_artifact = 'AND acl_acc.artifact_id = c.categoryid'; 1925 break; 1926 } 1927 1928 $cond['joins'] .= " LEFT JOIN {$serendipity['dbPrefix']}authorgroups AS acl_a 1929 ON acl_a.authorid = " . $read_id . " 1930 LEFT JOIN {$serendipity['dbPrefix']}access AS acl_acc 1931 ON ( acl_acc.artifact_mode = '" . $mode . "' 1932 AND acl_acc.artifact_type = '" . $type . "' 1933 " . $sql_artifact . " 1934 )"; 1935 1936 if (empty($cond['and'])) { 1937 $cond['and'] .= ' WHERE '; 1938 } else { 1939 $cond['and'] .= ' AND '; 1940 } 1941 1942 // When in Admin-Mode, apply readership permissions. 1943 $cond['and'] .= " ( 1944 " . $sql_artifact_column . " 1945 OR ( acl_acc.groupid = " . $read_id_sql . ") 1946 OR ( acl_acc.artifact_id IS NULL 1947 " . (isset($serendipity['GET']['adminModule']) && 1948 $serendipity['GET']['adminModule'] == 'entries' && 1949 !serendipity_checkPermission('adminEntriesMaintainOthers') 1950 ? "AND (c.authorid IS NULL OR c.authorid = 0 OR c.authorid = " . $read_id . ")" 1951 : "") . " 1952 ) 1953 )"; 1954 return true; 1955 } 1956 1957 return false; 1958} 1959 1960/** 1961 * Check for Cross-Site-Request-Forgery attacks because of missing HTTP Referer 1962 * 1963 * http://de.wikipedia.org/wiki/XSRF 1964 * This function checks the HTTP referer, and if it is part of the current Admin panel. 1965 * 1966 * @access public 1967 * @return Returns true if XSRF was detected, false if not. The script should abort, if TRUE is returned. 1968 */ 1969function serendipity_checkXSRF() { 1970 global $serendipity; 1971 1972 // If no module was requested, the user has just logged in and no action will be performed. 1973 if (empty($serendipity['GET']['adminModule'])) { 1974 return false; 1975 } 1976 1977 // The referrer was empty. Deny access. 1978 if (empty($_SERVER['HTTP_REFERER'])) { 1979 echo serendipity_reportXSRF(1, true, true); 1980 return false; 1981 } 1982 1983 // Parse the Referrer host. Abort if not parseable. 1984 $hostinfo = @parse_url($_SERVER['HTTP_REFERER']); 1985 if (!is_array($hostinfo)) { 1986 echo serendipity_reportXSRF(2, true, true); 1987 return true; 1988 } 1989 1990 // Get the server against we will perform the XSRF check. 1991 $server = ''; 1992 if (empty($_SERVER['HTTP_HOST'])) { 1993 $myhost = @parse_url($serendipity['baseURL']); 1994 if (is_array($myhost)) { 1995 $server = $myhost['host']; 1996 } 1997 } else { 1998 $server = $_SERVER['HTTP_HOST']; 1999 } 2000 2001 // If the current server is different than the referred server, deny access. 2002 if ($hostinfo['host'] != $server) { 2003 echo serendipity_reportXSRF(3, true, true); 2004 return true; 2005 } 2006 return false; 2007} 2008 2009/** 2010 * Report a XSRF attempt to the Serendipity Interface 2011 * 2012 * http://de.wikipedia.org/wiki/XSRF 2013 * 2014 * LONG 2015 * 2016 * @access public 2017 * @see serendipity_checkXSRF() 2018 * @param string The type of XSRF check that got hit. Used for CSS formatting. 2019 * @param boolean If true, the XSRF error should be fatal 2020 * @param boolean If true, tell Serendipity to check the $serendipity['referrerXSRF'] config option to decide if an error should be reported or not. 2021 * @return string Returns the HTML error report 2022 */ 2023function serendipity_reportXSRF($type = 0, $reset = true, $use_config = false) { 2024 global $serendipity; 2025 2026 // Set this in your serendipity_config_local.inc.php if you want HTTP Referrer blocking: 2027 // $serendipity['referrerXSRF'] = true; 2028 2029 $string = '<div class="msg_error XSRF_' . $type . '"><span class="icon-attention" aria-hidden="true"></span> ' . ERROR_XSRF . '</div>'; 2030 if ($reset) { 2031 // Config key "referrerXSRF" can be set to enable blocking based on HTTP Referrer. Recommended for Paranoia. 2032 if (($use_config && isset($serendipity['referrerXSRF']) && $serendipity['referrerXSRF']) || $use_config === false) { 2033 $serendipity['GET']['adminModule'] = ''; 2034 } else { 2035 // Paranoia not enabled. Do not report XSRF. 2036 $string = ''; 2037 } 2038 } 2039 return $string; 2040} 2041 2042/** 2043 * Prevent XSRF attacks by checking for a form token 2044 * 2045 * http://de.wikipedia.org/wiki/XSRF 2046 * 2047 * This function checks, if a valid Form token was posted to the site. 2048 * 2049 * @access public 2050 * @see serendipity_setFormToken() 2051 * @return boolean Returns true, if XSRF attempt was found and the token was missing 2052 */ 2053function serendipity_checkFormToken($output = true) { 2054 global $serendipity; 2055 $token = ''; 2056 if (!empty($serendipity['POST']['token'])) { 2057 $token = $serendipity['POST']['token']; 2058 } elseif (!empty($serendipity['GET']['token'])) { 2059 $token = $serendipity['GET']['token']; 2060 } 2061 2062 if (empty($token)) { 2063 if ($output) echo serendipity_reportXSRF('token', false); 2064 return false; 2065 } 2066 2067 if ($token != md5(session_id()) && 2068 $token != md5($serendipity['COOKIE']['old_session'])) { 2069 if ($output) echo serendipity_reportXSRF('token', false); 2070 return false; 2071 } 2072 2073 return true; 2074} 2075 2076/** 2077 * Prevent XSRF attacks by setting a form token within HTTP Forms 2078 * 2079 * http://de.wikipedia.org/wiki/XSRF 2080 * 2081 * By inserting a unique Form token that holds the session id, all requests 2082 * to serendipity HTTP forms can only be processed if the token is present. 2083 * This effectively makes XSRF attacks impossible. Only bundled with XSS 2084 * attacks it can be bypassed. 2085 * 2086 * 'form' type tokens can be embedded within the <form> script. 2087 * 'url' type token can be embedded within HTTP GET calls. 2088 * 2089 * @access public 2090 * @param string The type of token to return (form|url|plain) 2091 * @return string Returns the form token to be used in your functions 2092 */ 2093function serendipity_setFormToken($type = 'form') { 2094 global $serendipity; 2095 2096 if ($type == 'form') { 2097 return '<input type="hidden" name="serendipity[token]" value="' . md5(session_id()) . '" />'."\n"; 2098 } elseif ($type == 'url') { 2099 return 'serendipity[token]=' . md5(session_id()); 2100 } else { 2101 return md5(session_id()); 2102 } 2103} 2104 2105/** 2106 * Load available/configured options for a specific theme (through config.inc.php of a template directory) 2107 * into an array. 2108 * 2109 * @param array Referenced variable coming from the config.inc.php file, where the config values will be stored in 2110 * @param boolean Use true boolean mode in array $template_config in the config.inc.php file 2111 * @return array Final return array with default values 2112 */ 2113function &serendipity_loadThemeOptions(&$template_config, $okey = '', $bc_bool = false) { 2114 global $serendipity; 2115 2116 if (empty($okey)) { 2117 $okey = $serendipity['template']; 2118 } 2119 2120 $_template_vars =& serendipity_db_query("SELECT name, value FROM {$serendipity['dbPrefix']}options 2121 WHERE okey = 't_" . serendipity_db_escape_string($okey) . "' 2122 OR okey = 't_global'", false, 'assoc', false, 'name', 'value'); 2123 if (!is_array($_template_vars)) { 2124 $template_vars = array(); 2125 } else { 2126 $template_vars =& $_template_vars; 2127 } 2128 2129 foreach($template_config AS $key => $item) { 2130 if (!isset($template_vars[$item['var']])) { 2131 $template_vars[$item['var']] = $item['default']; 2132 } 2133 } 2134 if($bc_bool) { 2135 foreach($template_vars AS $k => $i) { 2136 if ($i == 'true' || $i == 'false') { 2137 $template_vars[$k] = serendipity_db_bool($i); 2138 } 2139 } 2140 //reset smarty compiled template ? 2141 } 2142 return $template_vars; 2143} 2144 2145/** 2146 * Load global available/configured options for a specific theme 2147 * into an array. 2148 * 2149 * @param array Referenced variable coming from the config.inc.php file, where the config values will be stored in 2150 * @param array Current template configuration 2151 * @return array Final return array with default values 2152 */ 2153function serendipity_loadGlobalThemeOptions(&$template_config, &$template_loaded_config, $supported = array()) { 2154 global $serendipity; 2155 2156 if ($supported['navigation']) { 2157 $navlinks = array(); 2158 2159 $conf_amount = array( 2160 'var' => 'amount', 2161 'name' => NAVLINK_AMOUNT, 2162 'type' => 'string', 2163 'default' => '5', 2164 'scope' => 'global' 2165 ); 2166 2167 // This always needs to be present, if not it could happen that the template options do have an older version of this variable 2168 $template_config[] = $conf_amount; 2169 2170 if (!isset($template_loaded_config['amount']) || empty($template_loaded_config['amount'])) { 2171 $template_loaded_config['amount'] = $conf_amount['default']; 2172 } 2173 2174 // Check if we are currently inside the admin interface. 2175 if ($serendipity['POST']['adminModule'] == 'templates' && $serendipity['POST']['adminAction'] == 'configure' && !empty($serendipity['POST']['template']['amount'])) { 2176 $template_loaded_config['amount'] = (int)$serendipity['POST']['template']['amount']; 2177 } 2178 2179 for ($i = 0; $i < $template_loaded_config['amount']; $i++) { 2180 $navlinks[] = array( 2181 'title' => $template_loaded_config['navlink' . $i . 'text'], 2182 'href' => $template_loaded_config['navlink' . $i . 'url'] 2183 ); 2184 2185 $template_config[] = array( 2186 'var' => 'navlink' . $i . 'text', 2187 'name' => NAV_LINK_TEXT . ' #' . ($i+1), 2188 'type' => 'string', 2189 'default' => 'Link #' . ($i+1), 2190 'scope' => 'global' 2191 ); 2192 $template_config[] = array( 2193 'var' => 'navlink' . $i . 'url', 2194 'name' => NAV_LINK_URL . ' #' . ($i+1), 2195 'type' => 'string', 2196 'default' => '#', 2197 'scope' => 'global' 2198 ); 2199 } 2200 2201 $serendipity['smarty']->assignByRef('navlinks', $navlinks); 2202 } 2203 2204 // Forward thinking. ;-) 2205 serendipity_plugin_api::hook_event('backend_templates_globalthemeoptions', $template_config, $supported); 2206} 2207 2208 2209 2210/** 2211 * Check if a member of a group has permissions to execute a plugin 2212 * 2213 * @param string Pluginname 2214 * @param int ID of the group of which the members should be checked 2215 * @return boolean 2216 */ 2217function serendipity_hasPluginPermissions($plugin, $groupid = null) { 2218 static $forbidden = null; 2219 global $serendipity; 2220 2221 if (empty($serendipity['authorid'])) { 2222 return true; 2223 } 2224 2225 if ($forbidden === null || ($groupid !== null && !isset($forbidden[$groupid]))) { 2226 $forbidden = array(); 2227 2228 if ($groupid === null) { 2229 $groups = serendipity_checkPermission(null, null, 'all'); 2230 } else { 2231 $groups = array($groupid => serendipity_fetchGroup($groupid)); 2232 } 2233 2234 foreach($groups AS $idx => $group) { 2235 if ($idx == 'membership') { 2236 continue; 2237 } 2238 foreach($group AS $key => $val) { 2239 if (substr($key, 0, 2) == 'f_') { 2240 $forbidden[$groupid][$key] = true; 2241 } 2242 } 2243 } 2244 } 2245 2246 if (isset($forbidden[$groupid]['f_' . $plugin])) { 2247 return false; 2248 } else { 2249 return true; 2250 } 2251} 2252 2253/** 2254 * Return the bcrypt hash of a value 2255 * 2256 * @param string The string to hash 2257 * @return string The hashed string 2258 */ 2259function serendipity_hash($string) { 2260 return password_hash($string, PASSWORD_BCRYPT); 2261} 2262 2263/** 2264 * Return the SHA1 (with pre-hash) of a value 2265 * 2266 * @param string The string to hash 2267 * @return string The hashed string 2268 */ 2269function serendipity_sha1_hash($string) { 2270 global $serendipity; 2271 2272 if (empty($serendipity['hashkey'])) { 2273 serendipity_set_config_var('hashkey', time(), 0); 2274 } 2275 2276 return sha1($serendipity['hashkey'] . $string); 2277} 2278 2279/** 2280 * Backwards-compatibility to recognize old-style md5 passwords to allow migration 2281 * 2282 * @param string The string to hash 2283 * @param string Either SHA1 or MD5 hash, depending on value 2284 */ 2285function serendipity_passwordhash($cleartext_password) { 2286 global $serendipity; 2287 2288 if ($_SESSION['serendipityHashType'] > 0) { 2289 return serendipity_hash($cleartext_password); 2290 } else { 2291 return md5($cleartext_password); 2292 } 2293} 2294 2295/* vim: set sts=4 ts=4 expandtab : */ 2296