'Secure Site', 'description' => 'Enables HTTP Auth security or an HTML form to restrict site access.', 'page callback' => 'drupal_get_form', 'page arguments' => array('securesite_admin_settings'), 'access arguments' => array('administer site configuration'), 'file' => 'securesite.admin.inc', ); return $items; } /** * Implementation of hook_boot() * * This is where Secure Site does most of its processing * * Note: When a user is logged in, but doesn't have the 'access secured pages' * permission, they get a normal Access Denied message */ function securesite_boot() { global $user, $base_path; $securesite_enabled = variable_get('securesite_enabled', SECURESITE_DISABLED); $guest_name = variable_get('securesite_guest_name', ''); $guest_pass = variable_get('securesite_guest_pass', ''); // Step #1: Process conditions that bypass Secure Site authentication if (!$securesite_enabled // Secure Site isn't enabled || ($securesite_enabled == SECURESITE_AUTH_ALT) // Old auth method, deprecated in 5.x-1.4 || (php_sapi_name() == 'cli') // Running PHP from the command line, such as when using Drush || (request_uri() == $base_path .'cron.php') // Accessing cron page || ($user->uid == 1) // Site administrator || ((!empty($guest_name)) // Guest access is allowed and guest has previously logged in && (isset($_SESSION['securesite_guest']) ? $_SESSION['securesite_guest'] : ''))) { return; } // Do a full bootstrap since Secure Site uses many functions throughout Core, // such as path.inc and user.module functions, t(), and theme() drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL); // This has to be after a full bootstrap if ((_securesite_filter_check(isset($_GET['q']) ? $_GET['q'] : '')) // User accessing a page in the bypass list || ($user->uid && user_access('access secured pages'))) { // User is logged in and has privileges to access secured pages return; } // Prevent a login/logout loop by redirecting off the logout page if (strpos(request_uri(), $base_path .'logout') === 0) { drupal_goto(''); } // Step #2: Process password resets if (strpos(request_uri(), $base_path .'user/reset/') === 0) { $args = explode('/', $_GET['q']); // The password reset function doesn't work well if it doesn't have all the // required parameters or if the UID parameter isn't valid if (count($args) >= 5 && user_load(array('uid' => $args[2], 'status' => 1))) { // Sanity-checking complete, now let user_pass_reset() and the menu // system handle it return; } else { drupal_set_message(t('You have tried to use an invalid one-time login link. Please request a new one using the form below.'), 'error'); } } // Step #3: Set up variables if ($securesite_enabled == SECURESITE_FORM && !empty($_POST['edit'])) { $edit = $_POST['edit']; } elseif ($securesite_enabled == SECURESITE_AUTH) { // PHP in CGI mode work-arounds. Sometimes, "REDIRECT_" prefixes $_SERVER // variables. See http://www.php.net/reserved.variables if (!empty($_SERVER['REDIRECT_HTTP_AUTHORIZATION']) && empty($_SERVER['HTTP_AUTHORIZATION'])) { $_SERVER['HTTP_AUTHORIZATION'] = $_SERVER['REDIRECT_HTTP_AUTHORIZATION']; } // Auth variables set via Rewrite rules need to be decoded // See http://www.php.net/manual/en/features.http-auth.php#76708 if (!empty($_SERVER['HTTP_AUTHORIZATION'])) { list($_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW']) = explode(':', base64_decode(substr($_SERVER['HTTP_AUTHORIZATION'], 6))); } // Process username and password normally. The correct $_SERVER variables // are now set if PHP is run in CGI mode if (isset($_SERVER['PHP_AUTH_USER'])) { $edit['name'] = $_SERVER['PHP_AUTH_USER']; } if (isset($_SERVER['PHP_AUTH_PW'])) { $edit['pass'] = $_SERVER['PHP_AUTH_PW']; } } // Step #4: If needed, ask user for credentials if ((empty($edit['name']) || empty($edit['pass'])) && ($user->uid == 0)) { _securesite_user_auth(); } // Step #5: Check if user is a guest and log them in if they are if (!empty($guest_name) && !empty($guest_pass) && ($guest_name == $edit['name']) && ($guest_pass == $edit['pass'])) { // Mark this session to prevent re-login (note: guests can't logout) $_SESSION['securesite_guest'] = TRUE; $_SESSION['securesite_login'] = TRUE; // Redirect to prevent some caching problems drupal_goto($_GET['q']); } unset($_SESSION['securesite_guest']); // If not a guest, make sure to clear the guest session // Step #6: Check user's credentials // The LDAP auth module can't use the regular external user login system, so // we have to call its login function directly if (function_exists('_ldapauth_user_authenticate')) { $account = _ldapauth_user_authenticate($edit['name'], $edit['pass']); } else { $account = user_authenticate(array('name' => $edit['name'], 'pass' => $edit['pass'])); } // Step #7: Process login attempt if ((isset($account->uid) ? $account->uid : FALSE) && user_access('access secured pages', $account)) { // Mark the session so Secure Site will be triggered on logout $_SESSION['securesite_login'] = TRUE; // Redirect to prevent some caching problems drupal_goto($_GET['q']); } else { // Login failed if (!empty($edit['name'])) { watchdog('user', 'Login attempt failed for %user.', array('%user' => $edit['name'])); } else { watchdog('user', 'Login attempt failed for anonymous user.', array('%user' => $edit['name'])); } _securesite_user_auth(); } } /** * Implementation of hook_user() * * When users logout, show the HTTP Auth dialog to make sure the HTTP Auth * credentials are cleared * * @see _securesite_user_auth() */ function securesite_user($op, &$edit, &$user) { if (($op == 'logout') && (variable_get('securesite_enabled', SECURESITE_DISABLED) == SECURESITE_AUTH) && ((isset($_SESSION['securesite_login']) ? $_SESSION['securesite_login'] : FALSE))) { // Load the anonymous user $user = drupal_anonymous_user(); // Clear stored credentials _securesite_user_auth(); } } /** * Display authentication dialog and send password reset mails */ function _securesite_user_auth() { global $base_url; module_load_include('inc', 'securesite'); $securesite_enabled = variable_get('securesite_enabled', SECURESITE_DISABLED); $content = ''; // Step #1: Check if the user attempted to submit the login form. If so, // getting here means they didn't enter their info correctly if (isset($_POST['securesite_login_form'])) { drupal_set_message(t('Unrecognized username and/or password.'), 'error'); } // Step #2: Check if the user attempted to submit the password request form. // If so, check if we have information for the name/mail they entered and // send it if we do if (isset($_POST['securesite_request_form']) && isset($_POST['edit'])) { _securesite_password_reset($_POST['edit']); } // Get content for dialog if ($securesite_enabled == SECURESITE_FORM) { $content .= _securesite_login_form(); } $content .= _securesite_request_form(); // Step #3: If using HTTP Auth, send the appropriate headers, but only if the // user isn't logged in and they haven't just submitted the password reset or // login forms if (($securesite_enabled == SECURESITE_AUTH) && empty($_POST['securesite_request_form']) && empty($_POST['securesite_login_form'])) { $realm = variable_get('securesite_realm', variable_get('site_name', 'Drupal')); // If not on the home page of the site, Opera will not show the auth dialog // the first time after logout. It will show the page displayed before // logging out. Reloading will cause the dialog to display. Safari // doesn't seem show the login/password request form when cancelling the // auth dialog no matter what $browsers = array('msie', 'opera', 'safari'); $user_agent = (isset($_SERVER['HTTP_USER_AGENT']) ? strtolower($_SERVER['HTTP_USER_AGENT']) : ''); foreach ($browsers as $browser) { if (strpos($user_agent, $browser) !== FALSE) { $realm .= ' - '. mt_rand(10, 999); break; } } header('WWW-Authenticate: Basic realm="'. $realm .'"'); header('HTTP/1.0 401 Unauthorized'); } // Step #4: Show the login form and/or password request form if user cancels // HTTP Auth dialog _securesite_dialog_page($content); module_invoke_all('exit', request_uri()); session_write_close(); exit(); } /** * Check if pages should bypass Secure Site * * @param $path * String containing the path to be filtered * * @return * TRUE if Secure Site should be bypassed */ function _securesite_filter_check($path) { // Don't allow empty paths if (empty($path)) { return FALSE; } // Fetch paths to ignore $pages = variable_get('securesite_filter_pages', ''); // Check if the current path matches the list defined by the admin $alias = drupal_get_path_alias($path); $regexp = '/^('. preg_replace(array('/(\r\n?|\n)/', '/\\\\\*/', '/(^|\|)\\\\($|\|)/'), array('|', '.*', '\1'. preg_quote(variable_get('site_frontpage', 'node'), '/') .'\2'), preg_quote($pages, '/')) .')$/'; $page_match = preg_match($regexp, $alias); // Check normal paths if the alias lookup fails to match if (!$page_match) { $alias = drupal_get_normal_path($path); $page_match = preg_match($regexp, $alias); } // Whitelist or blacklist? if (variable_get('securesite_filter_pages_type', SECURESITE_WHITELIST) == !$page_match) { return TRUE; } return FALSE; } /** * Implementation of hook_mail() */ function securesite_mail($key, &$message, $params) { // Ignoring $key for now, since there's only one type of mail sent by Secure Site $message['subject'] = $params['subject']; $message['body'] = $params['body']; }