/** main program for visitors * * this routine is called from /index.php. It is the main program for visitors. * * @return void page sent to the browser * @todo cleanup login/logout-code */ function main_index() { global $USER; global $CFG; global $LANGUAGE; /** initialise the program, setup database, read configuration, etc. */ require_once $CFG->progdir . '/init.php'; initialise(); was_version_check(); // this never returns if versions don't match // TODO: cleanup like in main_admin() // handle login/logout/continuation so we quickly find out which user is calling if (isset($_GET['logout'])) { /** loginlib.php contains both login- and logout-routines */ require_once $CFG->progdir . '/lib/loginlib.php'; was_logout(); // may or may not return here } elseif (isset($_GET['login'])) { /** loginlib.php contains both login- and logout-routines */ require_once $CFG->progdir . '/lib/loginlib.php'; was_login(magic_unquote($_GET['login'])); // may or may not return here } elseif (isset($_COOKIE[$CFG->session_name])) { /** dbsessionlib.php contains our own database based session handler */ require_once $CFG->progdir . '/lib/dbsessionlib.php'; dbsession_setup($CFG->session_name); if (dbsession_exists(magic_unquote($_COOKIE[$CFG->session_name]))) { session_start(); } } // At this point we either have a valid session with a logged-in user // (indicated via existence of $_SESSION) or we are dealing with an anonymous // visitor with non-existing $_SESSION. Keep track of the number of calls // this user makes (may be logged lateron on logout). if (isset($_SESSION)) { if (!isset($_SESSION['session_counter'])) { // first time after login, record start time of session $_SESSION['session_counter'] = 1; $_SESSION['session_start'] = strftime("%Y-%m-%d %T"); } else { $_SESSION['session_counter']++; } } // Now is the time to create a USER object, even when the visitor is just a passerby // because we can then determine easily if a visitor is allowed certain things, e.g. // view a protected area or something /** useraccount.class.php is used to define the USER object */ require_once $CFG->progdir . '/lib/useraccount.class.php'; if (isset($_SESSION) && isset($_SESSION['user_id'])) { $USER = new Useraccount($_SESSION['user_id']); $USER->is_logged_in = TRUE; $_SESSION['language_key'] = $LANGUAGE->get_current_language(); // remember language set via _GET or otherwise } else { $USER = new Useraccount(); $USER->is_logged_in = FALSE; } // Check for the special preview-mode // This allows a webmaster to preview a page in the correct environment (theme) // even when the page is under embargo. Note that the node_id and area_id are // retrieved from the session; the user only has a cryptic preview-code. // See pagemanagerlib.php for more information (function task_page_preview()). $in_preview_mode = FALSE; if ($USER->is_logged_in) { $preview_code_from_url = get_parameter_string('preview'); if (!is_null($preview_code_from_url) && isset($_SESSION['preview_salt']) && isset($_SESSION['preview_node'])) { $hash = md5($_SESSION['preview_salt'] . $_SESSION['preview_node']); if ($hash === $preview_code_from_url) { $node_id = intval($_SESSION['preview_node']); $area_id = intval($_SESSION['preview_area']); $area = db_select_single_record('areas', '*', array('area_id' => $area_id)); if ($area === FALSE) { logger("Fatal error 070: cannot preview node '{$node_id}' in area '{$area_id}'"); error_exit('070'); } else { $tree = tree_build($area_id); $in_preview_mode = TRUE; } } } } if ($in_preview_mode == FALSE) { $requested_area = get_requested_area(); $requested_node = get_requested_node(); $req_area_str = is_null($requested_area) ? "NULL" : strval($requested_area); $req_node_str = is_null($requested_node) ? "NULL" : strval($requested_node); if (($area = calculate_area($requested_area, $requested_node)) === FALSE) { logger("Fatal error 080: no valid area (request: area='{$req_area_str}', node='{$req_node_str}')"); error_exit('080'); // no such area } $area_id = intval($area['area_id']); // If $USER has no permission to view area $area_id, we simply bail out. // Rationale: if the user is genuine, she knows about logging in first. // If the user is NOT logged in and tries to view a protected area, I'd consider // it malicious, and in that case I won't even confirm the existence of // the requested area. (If a cracker simply tries areas 0,1,.. and sometimes is greeted // with 'please enter credentials' and sometimes with 'area does not exist', this // provides information to the cracker. I don't want that). Note that the error code // is the same as the one for non-existing area. In other words: for an unauthorised // visitor an existing private area is just as non-existent as a non-existing public area. if (db_bool_is(TRUE, $area['is_private']) && !$USER->has_intranet_permissions(ACL_ROLE_INTRANET_ACCESS, $area_id)) { logger(sprintf("Fatal error 080: no view permissions for area '%d' (request: area='%s', node='%s')", $area_id, $req_area_str, $req_node_str)); error_exit('080'); // no such area } // still here? // then we've got a valid $area_id and corresponding $area record. // now we need to figure out which $node_id to use $tree = tree_build($area_id); if (($node_id = calculate_node_id($tree, $area_id, $requested_node)) === FALSE) { logger(sprintf("Fatal error 080: no valid node within area '%d' (request: area='%s', node='%s')", $area_id, $req_area_str, $req_node_str)); error_exit('080'); // no such area } } // At this point we have the following in our hands // - a valid $area_id // - a valid $node_id // - the complete tree from area $area_id in $tree // - the area record from database in $area // - the node record from database in $tree[$node_id]['record'] // - a flag that signals preview mode in $in_preview_mode // We are on our way to generate a full page with content and all, // but otoh we MIGHT be in the middle of a redirect, so we may have to // leave without showing anything at all... if (!empty($tree[$node_id]['record']['link_href'])) { update_statistics($node_id); if (isset($_SESSION)) { session_write_close(); } redirect_and_exit(htmlspecialchars($tree[$node_id]['record']['link_href'])); // exit; redirect_and_exit() never returns } /** themelib contains the theme factory */ require_once $CFG->progdir . '/lib/themelib.php'; // And now we know about the $area, we can carry on determining which $theme to use. // $theme = theme_factory($area['theme_id'], $area_id, $node_id); if ($theme === FALSE) { logger("Fatal error 090: cannot setup theme '{$area['theme_id']}' in area '{$area_id}'"); error_exit('090'); } // Tell the theme about the preview mode $theme->set_preview_mode($in_preview_mode); // Now all we need to do is let the module connected to node $node_id generate output $module_id = $tree[$node_id]['record']['module_id']; module_view($theme, $area_id, $node_id, $module_id); // Remember this visitor update_statistics($node_id); // Finally, send output to user $theme->send_output(); if (isset($_SESSION)) { session_write_close(); } // done! exit; }
/** main program for serving files * * this routine is called from /file.php. * * This routine is responsible for serving files to the visitor. * These files are stored in a (virtual) file hierarchy that looks * like this. * * <pre> * /areas/areaname * /another * /stillmore * ... * /users/username * /another * /stillmore * ... * /groups/groupname * /another * /stillmore * ... * /websiteatschool/program * /manual * /languages * </pre> * * This structure maps to the real file system as follows. The (virtual) * directories /areas, /users and /groups correspond to the fysical * directories {$CFG->datadir}/areas, {$CFG->datadir}/users and * {$CFG->datadir}/groups respectively. The subdirectories correspond to * a (unique) area, user or group and serve as a file repository for that * area, user or group. * * The (virtual) top-level directory /websiteatschool is a special case. * It is used to serve the currently running website program code and the * user-defined translations of active languages. * * Before any file is transmitted to the visitor the access privileges * are checked. The following rules apply. * * Access control for the /areas subdirectory * * - an area must be active before any files are served * - the visitor must have access to the private area if files are to be served * - non-existing files yield a 404 Not Found error * - non-existing areas also yield a 404 Not Found error * - if the visitor has no access to the private area, also a 404 Not Found error is returned * * Access control for /users and /groups * * - a user/group must be active before any files are served * - non-existing users/groups yield 404 Not Found * - non-existing files in existing directories also yield 404 Not Found * * Access control for /websiteatschool * * - there is no limit on downloading the currently active program code or user-defined translations of active languages * * Note: * The check on '..' in the requested filename would be inconclusive if the $path * is encoded in invalid UTF-8: the overlong sequence 2F C0 AE 2E 2F eventually * yields 2F 2E 2E 2F or '/../'. Reference: RFC3629 section 10. However, we use * the filename processed with get_requested_filename() which already checks for * utf8 validity, which rules out the trick with overlong sequences. * * @return void file sent to the browser OR 404 not found on error */ function main_file() { global $USER; global $CFG; global $WAS_SCRIPT_NAME; global $LANGUAGE; /** initialise the program, setup database, read configuration, etc. */ require_once $CFG->progdir . '/init.php'; initialise(); was_version_check(); // this never returns if versions don't match /** utility routines for manipulating files */ require_once $CFG->progdir . '/lib/filelib.php'; $filename = get_requested_filename(); if (is_null($filename)) { error_exit404(); } // 0 -- is the visitor logged in if (isset($_COOKIE[$CFG->session_name])) { /** dbsessionlib.php contains our own database based session handler */ require_once $CFG->progdir . '/lib/dbsessionlib.php'; dbsession_setup($CFG->session_name); if (dbsession_exists(magic_unquote($_COOKIE[$CFG->session_name]))) { session_start(); if (!isset($_SESSION['session_counter'])) { // first time after login, record start time of session $_SESSION['session_counter'] = 1; $_SESSION['session_start'] = strftime("%Y-%m-%d %T"); } else { $_SESSION['session_counter']++; } } } /** useraccount.class.php is used to define the USER object */ require_once $CFG->progdir . '/lib/useraccount.class.php'; if (isset($_SESSION) && isset($_SESSION['user_id'])) { $USER = new Useraccount($_SESSION['user_id']); $USER->is_logged_in = TRUE; $_SESSION['language_key'] = $LANGUAGE->get_current_language(); // remember language set via _GET or otherwise session_write_close(); // we no longer need this here, everything relevant is now in $USER } else { $USER = new Useraccount(); $USER->is_logged_in = FALSE; } // // 1 -- does the visitor want to download the source code // $path_components = explode('/', trim(strtr($filename, '\\', '/'), '/')); if (strtolower($path_components[0]) == 'websiteatschool') { $source = isset($path_components[1]) ? strtolower($path_components[1]) : 'program'; download_source($source); exit; } // // 2 -- no source code requested. check out regular files // $path = '/' . implode('/', $path_components); // 2A -- always disallow attempts to escape from tree via parent directory tricks if (in_array('..', $path_components)) { logger(sprintf("%s(): access denied for file '%s': no tricks with '/../': return 404 Not Found", __FUNCTION__, $path), WLOG_DEBUG); error_exit404($path); } // 2B -- check the 1st and 2nd component of the requested file switch ($path_components[0]) { case 'areas': $area_path = isset($path_components[1]) ? $path_components[1] : ''; $fields = array('area_id', 'is_private'); $where = array('is_active' => TRUE, 'path' => $area_path); $table = 'areas'; if (($record = db_select_single_record($table, $fields, $where)) === FALSE) { logger(sprintf("%s(): access denied for file '%s': non-existing or inactive area: return 404 Not Found", __FUNCTION__, $path), WLOG_DEBUG); error_exit404($path); } $area_id = intval($record['area_id']); if (db_bool_is(TRUE, $record['is_private']) && !$USER->has_intranet_permissions(ACL_ROLE_INTRANET_ACCESS, $area_id)) { logger(sprintf("%s(): access denied for file '%s' in private area '%d': return 404 Not Found", __FUNCTION__, $path, $area_id), WLOG_DEBUG); error_exit404($path); } break; case 'users': $user_path = isset($path_components[1]) ? $path_components[1] : ''; $fields = array('user_id'); $where = array('path' => $user_path, 'is_active' => TRUE); $table = 'users'; if (($record = db_select_single_record($table, $fields, $where)) === FALSE) { logger(sprintf("%s(): access denied for file '%s': non-existing or inactive user: return 404 Not Found", __FUNCTION__, $path), WLOG_DEBUG); error_exit404($path); } break; case 'groups': $group_path = isset($path_components[1]) ? $path_components[1] : ''; $fields = array('group_id'); $where = array('path' => $group_path, 'is_active' => TRUE); $table = 'groups'; if (($record = db_select_single_record($table, $fields, $where)) === FALSE) { logger(sprintf("%s(): access denied for file '%s': non-existing or inactive group: return 404 Not Found", __FUNCTION__, $path), WLOG_DEBUG); error_exit404($path); } break; default: logger(sprintf("%s(): access denied for file '%s': subdirectory '%s' not recognised: return 404 Not Found", __FUNCTION__, $path, $path_components[0]), WLOG_DEBUG); error_exit404($path); break; } // 2C -- still here? 1st and 2nd components are good but does the file exist? if (!is_file($CFG->datadir . $path)) { logger(sprintf("%s(): access denied for file '%s': file does not exist: return 404 Not Found", __FUNCTION__, $path), WLOG_DEBUG); error_exit404($path); } // // At this point we confident that the file exists within the data directory and also that // the visitor is allowed access to the file. Now send the file to the visitor. // $name = basename($path); if (($bytes_sent = send_file_from_datadir($path, $name)) === FALSE) { logger(sprintf("Failed to send '%s' using filename '%s'", $path, $name)); $retval = FALSE; } else { logger(sprintf("Success sending '%s' using filename '%s', size = %d bytes", $path, $name, $bytes_sent), WLOG_DEBUG); $retval = TRUE; } exit; }
/** continue the session from the previous call OR exit * * This tries to resume the session that was initiated before * (when the user logged in succesfully). If the session cannot * be resumed, we logout the user, show the login screen and exit. * In other words: this routine guarantees that a valid session * exists if and when this routine returns. If the routine returns, * it returns the user_id. * * @return void|int on success the $user_id is returned else a logout + exit is forced * @uses $CFG; * @uses dbsessionlib.php */ function admin_continue_session() { global $CFG; /** dbsessionlib.php contains our own database based session handler */ require_once $CFG->progdir . '/lib/dbsessionlib.php'; dbsession_setup($CFG->session_name); if (dbsession_exists($_COOKIE[$CFG->session_name])) { session_start(); } if (!isset($_SESSION)) { admin_logout_and_exit(); } else { if (!isset($_SESSION['session_counter'])) { // first time after login, record start time of session $_SESSION['session_counter'] = 1; $_SESSION['session_start'] = strftime("%Y-%m-%d %T"); } else { $_SESSION['session_counter']++; } $user_id = $_SESSION['user_id']; } return $user_id; }
/** end a session (logout the user) and maybe redirect * * This routine ends the current session if it exists (as indicated by the * cookie presented by the user's browser). An empty value is sent * to the browser (effectively deleting the cookie) and also * the session is ended. The routine ends either with * showing a generic login dialog OR a redirection to * a user-defined page. * * Note that as a rule this routine does NOT return but instead calls exit(). * However, there are cases where this routine DOES return, notably when * no session appears to be established (no cookie submitted by the browser * or a non-existing/expired session). If the routine does return, the status * is equivalent to a logged out user; no session exists so the user simply * should not be logged in. * * @return void this function sometimes never returns * @uses $CFG * @uses dbsession_setup() */ function was_logout() { global $CFG; if (isset($_COOKIE[$CFG->session_name])) { /** install our own database based session handler */ require_once $CFG->progdir . '/lib/dbsessionlib.php'; dbsession_setup($CFG->session_name); if (dbsession_exists($_COOKIE[$CFG->session_name])) { session_start(); $redirect = isset($_SESSION['redirect']) ? $_SESSION['redirect'] : ''; $user_id = $_SESSION['user_id']; $logmessage = 'logout: \'' . $_SESSION['username'] . '\' (' . $user_id . '): success'; $logmessage .= ' (session started ' . $_SESSION['session_start'] . ', count=' . $_SESSION['session_counter'] . ')'; $_SESSION = array(); // get rid of all session variables /* kill the session/cookie in the user's browser (with the correct cookie parameters)... */ $a = session_get_cookie_params(); setcookie($CFG->session_name, '', time() - 86400, $a['path'], $a['domain'], $a['secure']); /* ...and also in the sessions table */ session_destroy(); /* log the event */ logger($logmessage, WLOG_INFO, $user_id); if (!empty($redirect)) { header('Location: ' . $redirect); echo '<a href="' . $redirect . '">' . htmlspecialchars($redirect) . '</a>'; } else { show_login(LOGIN_PROCEDURE_NORMAL, t('logout_successful', 'loginlib')); } } else { /* kill the cookie in the user's browser even when the session does no longer exist in database */ $a = session_get_cookie_params(); setcookie($CFG->session_name, '', time() - 86400, $a['path'], $a['domain'], $a['secure']); logger('logout: reset user\'s cookie even without corresponding session record in database', WLOG_DEBUG); /* since we don't know where to go next (no redirect), we'll go to login screen #1 */ show_login(LOGIN_PROCEDURE_NORMAL, t('logout_forced', 'loginlib')); } exit; } else { // nothing because no cookie was/is set logger('logout: nothing to do'); } }