diff --git a/includes/menu.inc b/includes/menu.inc
index 77719a33eff58e8f3ea352264150018f03bd9b03..9af697d4cc4f9ecf325768a1a1f447f5d234d6b7 100644
--- a/includes/menu.inc
+++ b/includes/menu.inc
@@ -238,6 +238,11 @@
  */
 define('MENU_SITE_OFFLINE', 4);
 
+/**
+ * Internal menu status code -- Everything is working fine.
+ */
+define('MENU_SITE_ONLINE', 5);
+
 /**
  * @} End of "Menu status codes".
  */
@@ -447,10 +452,17 @@ function menu_get_item($path = NULL, $router_item = NULL) {
  *   the result to the caller (FALSE).
  */
 function menu_execute_active_handler($path = NULL, $deliver = TRUE) {
-  if (_menu_site_is_offline()) {
-    $page_callback_result = MENU_SITE_OFFLINE;
-  }
-  else {
+  // Check if site is offline.
+  $page_callback_result = _menu_site_is_offline() ? MENU_SITE_OFFLINE : MENU_SITE_ONLINE;
+
+  // Allow other modules to change the site status but not the path because that
+  // would not change the global variable. hook_url_inbound_alter() can be used
+  // to change the path. Code later will not use the $read_only_path variable.
+  $read_only_path = !empty($path) ? $path : $_GET['q'];
+  drupal_alter('menu_site_status', $page_callback_result, $read_only_path);
+
+  // Only continue if the site status is not set.
+  if ($page_callback_result == MENU_SITE_ONLINE) {
     // Rebuild if we know it's needed, or if the menu masks are missing which
     // occurs rarely, likely due to a race condition of multiple rebuilds.
     if (variable_get('menu_rebuild_needed', FALSE) || !variable_get('menu_masks', array())) {
@@ -3387,15 +3399,7 @@ function _menu_site_is_offline($check_only = FALSE) {
       }
     }
     else {
-      // Anonymous users get a FALSE at the login prompt, TRUE otherwise.
-      if (user_is_anonymous()) {
-        return ($_GET['q'] != 'user' && $_GET['q'] != 'user/login');
-      }
-      // Logged in users are unprivileged here, so they are logged out.
-      if (!$check_only) {
-        require_once DRUPAL_ROOT . '/' . drupal_get_path('module', 'user') . '/user.pages.inc';
-        user_logout();
-      }
+      return TRUE;
     }
   }
   return FALSE;
diff --git a/modules/openid/openid.module b/modules/openid/openid.module
index 9a6e7edfb706e6ed3cc8f4a71718515920f128a5..99d37cf4c6623d2eb50baa8ce63e6b8abd204e80 100644
--- a/modules/openid/openid.module
+++ b/modules/openid/openid.module
@@ -38,6 +38,16 @@ function openid_menu() {
   return $items;
 }
 
+/**
+ * Implements hook_menu_site_status_alter().
+ */
+function openid_menu_site_status_alter(&$menu_site_status, $path) {
+  // Allow access to openid/authenticate even if site is in offline mode.
+  if ($menu_site_status == MENU_SITE_OFFLINE && user_is_anonymous() && $path == 'openid/authenticate') {
+    $menu_site_status = MENU_SITE_ONLINE;
+  }
+}
+
 /**
  * Implements hook_help().
  */
diff --git a/modules/openid/openid.test b/modules/openid/openid.test
index cf90161fd7a5dd496523a1484b64e2f7d2a593cc..68313ae7ea920e12ea2bbe56cdba9e6d5ff94338 100644
--- a/modules/openid/openid.test
+++ b/modules/openid/openid.test
@@ -146,6 +146,38 @@ class OpenIDFunctionalTestCase extends OpenIDWebTestCase {
     $this->assertResponse(200);
   }
 
+  /**
+   * Test login using OpenID during maintenance mode.
+   */
+  function testLoginMaintenanceMode() {
+    $this->web_user = $this->drupalCreateUser(array('access site in maintenance mode'));
+    $this->drupalLogin($this->web_user);
+
+    // Use a User-supplied Identity that is the URL of an XRDS document.
+    $identity = url('openid-test/yadis/xrds', array('absolute' => TRUE));
+    $this->addIdentity($identity);
+    $this->drupalLogout();
+
+    // Enable maintenance mode.
+    variable_set('maintenance_mode', 1);
+
+    // Test logging in via the user/login page while the site is offline.
+    $edit = array('openid_identifier' => $identity);
+    $this->drupalPost('user/login', $edit, t('Log in'));
+
+    // Check we are on the OpenID redirect form.
+    $this->assertTitle(t('OpenID redirect'), t('OpenID redirect page was displayed.'));
+
+    // Submit form to the OpenID Provider Endpoint.
+    $this->drupalPost(NULL, array(), t('Send'));
+
+    $this->assertLink($this->web_user->name, 0, t('User was logged in.'));
+
+    // Verify user was redirected away from user/login to an accessible page.
+    $this->assertText(t('Operating in maintenance mode.'));
+    $this->assertResponse(200);
+  }
+
   /**
    * Test deleting an OpenID identity from a user's profile.
    */
diff --git a/modules/openid/tests/openid_test.module b/modules/openid/tests/openid_test.module
index 261a1aa1ef11221da5d6499da25b6d63543bb2ea..5beb5fa411e486be4324f777ebe32d27a1359936 100644
--- a/modules/openid/tests/openid_test.module
+++ b/modules/openid/tests/openid_test.module
@@ -64,6 +64,16 @@ function openid_test_menu() {
   return $items;
 }
 
+/**
+ * Implements hook_menu_site_status_alter().
+ */
+function openid_test_menu_site_status_alter(&$menu_site_status, $path) {
+  // Allow access to openid endpoint and identity even in offline mode.
+  if ($menu_site_status == MENU_SITE_OFFLINE && user_is_anonymous() && in_array($path, array('openid-test/yadis/xrds', 'openid-test/endpoint'))) {
+    $menu_site_status = MENU_SITE_ONLINE;
+  }
+}
+
 /**
  * Menu callback; XRDS document that references the OP Endpoint URL.
  */
diff --git a/modules/simpletest/tests/menu.test b/modules/simpletest/tests/menu.test
index 0e6da766b1b1bb8b185aa991797a08da632ec8fa..fdf529733bb87ca66e1574532581c35f4de5784f 100644
--- a/modules/simpletest/tests/menu.test
+++ b/modules/simpletest/tests/menu.test
@@ -90,6 +90,39 @@ class MenuRouterTestCase extends DrupalWebTestCase {
     $this->assertRaw('seven/style.css', t("The administrative theme's CSS appears on the page."));
   }
 
+  /**
+   * Make sure the maintenance mode can be bypassed using hook_menu_site_status_alter().
+   *
+   * @see hook_menu_site_status_alter().
+   */
+  function testMaintenanceModeLoginPaths() {
+    variable_set('maintenance_mode', TRUE);
+
+    $offline_message = t('@site is currently under maintenance. We should be back shortly. Thank you for your patience.', array('@site' => variable_get('site_name', 'Drupal')));
+    $this->drupalLogout();
+    $this->drupalGet('node');
+    $this->assertText($offline_message);
+    $this->drupalGet('menu_login_callback');
+    $this->assertText('This is menu_login_callback().', t('Maintenance mode can be bypassed through hook_login_paths().'));
+  }
+
+  /**
+   * Test that an authenticated user hitting 'user/login' gets redirected to
+   * 'user' and 'user/register' gets redirected to the user edit page.
+   */
+  function testAuthUserUserLogin() {
+    $loggedInUser = $this->drupalCreateUser(array());
+    $this->drupalLogin($loggedInUser);
+
+    $this->DrupalGet('user/login');
+    // Check that we got to 'user'.
+    $this->assertTrue($this->url == url('user', array('absolute' => TRUE)), t("Logged-in user redirected to q=user on accessing q=user/login"));
+
+    // user/register should redirect to user/UID/edit.
+    $this->DrupalGet('user/register');
+    $this->assertTrue($this->url == url('user/' . $this->loggedInUser->uid . '/edit', array('absolute' => TRUE)), t("Logged-in user redirected to q=user/UID/edit on accessing q=user/register"));
+  }
+
   /**
    * Test the theme callback when it is set to use an optional theme.
    */
@@ -491,4 +524,3 @@ class MenuTreeDataTestCase extends DrupalUnitTestCase {
     return $this->assert($link1['mlid'] == $link2['mlid'], $message ? $message : t('First link is identical to second link'));
   }
 }
-
diff --git a/modules/simpletest/tests/menu_test.module b/modules/simpletest/tests/menu_test.module
index ee8f2ea1b7cb42ac8e4f0b297ef1e3bead4724fd..8bab8a208cb8d7530683cbb8d0fd4a7d90ec7880 100644
--- a/modules/simpletest/tests/menu_test.module
+++ b/modules/simpletest/tests/menu_test.module
@@ -189,6 +189,12 @@ function menu_test_menu() {
     'type' => MENU_LOCAL_TASK,
   );
 
+  $items['menu_login_callback'] = array(
+    'title' => 'Used as a login path',
+    'page callback' => 'menu_login_callback',
+    'access callback' => TRUE,
+  );
+
   return $items;
 }
 
@@ -329,3 +335,20 @@ function menu_test_static_variable($value = NULL) {
   }
   return $variable;
 }
+
+/**
+ * Implements hook_menu_site_status_alter().
+ */
+function menu_test_menu_site_status_alter(&$menu_site_status, $path) {
+  // Allow access to ?q=menu_login_callback even if in maintenance mode.
+  if ($menu_site_status == MENU_SITE_OFFLINE && $path == 'menu_login_callback') {
+    $menu_site_status = MENU_SITE_ONLINE;
+  }
+}
+
+/**
+ * Menu callback to be used as a login path.
+ */
+function menu_login_callback() {
+  return 'This is menu_login_callback().';
+}
diff --git a/modules/system/system.api.php b/modules/system/system.api.php
index 497eb91557fcf07f17994576352a8564e9daebf4..f2616ec13c37a99fb0f656a6c6d3846d2f9404cd 100644
--- a/modules/system/system.api.php
+++ b/modules/system/system.api.php
@@ -3960,6 +3960,31 @@ function hook_filetransfer_backends() {
   return $backends;
 }
 
+/**
+ * Control site status before menu dispatching.
+ *
+ * The hook is called after checking whether the site is offline but before 
+ * the current router item is retrieved and executed by
+ * menu_execute_active_handler(). If the site is in offline mode,
+ * $menu_site_status is set to MENU_SITE_OFFLINE.
+ *
+ * @param $menu_site_status
+ *   Supported values are MENU_SITE_OFFLINE, MENU_ACCESS_DENIED,
+ *   MENU_NOT_FOUND and MENU_SITE_ONLINE. Any other value than
+ *   MENU_SITE_ONLINE will skip the default menu handling system and be passed
+ *   for delivery to drupal_deliver_page() with a NULL
+ *   $default_delivery_callback.
+ * @param $path
+ *   Contains the system path that is going to be loaded. This is read only,
+ *   use hook_url_inbound_alter() to change the path.
+ */
+function hook_menu_site_status_alter(&$menu_site_status, $path) {
+  // Allow access to my_module/authentication even if site is in offline mode.
+  if ($menu_site_status == MENU_SITE_OFFLINE && user_is_anonymous() && $path == 'my_module/authentication') {
+    $menu_site_status = MENU_SITE_ONLINE;
+  }
+}
+
 /**
  * @} End of "addtogroup hooks".
  */
diff --git a/modules/system/system.test b/modules/system/system.test
index 7d6619802e15519bebf3161fc01f44f087109e69..e456e3744edcf60deb0a4cb076ad06524c329424 100644
--- a/modules/system/system.test
+++ b/modules/system/system.test
@@ -770,8 +770,6 @@ class SiteMaintenanceTestCase extends DrupalWebTestCase {
     $this->assertText($offline_message);
     $this->drupalGet('user/register');
     $this->assertText($offline_message);
-    $this->drupalGet('user/password');
-    $this->assertText($offline_message);
 
     // Verify that user is able to log in.
     $this->drupalGet('user');
@@ -804,6 +802,23 @@ class SiteMaintenanceTestCase extends DrupalWebTestCase {
     $this->drupalLogout();
     $this->drupalGet('');
     $this->assertRaw($offline_message, t('Found the site offline message.'));
+
+    // Verify that custom site offline message is not displayed on user/password.
+    $this->drupalGet('user/password');
+    $this->assertText(t('Username or e-mail address'), t('Anonymous users can access user/password'));
+
+    // Submit password reset form.
+    $edit = array(
+      'name' => $this->user->name,
+    );
+    $this->drupalPost('user/password', $edit, t('E-mail new password'));
+    $mails = $this->drupalGetMails();
+    $start = strpos($mails[0]['body'], 'user/reset/'. $this->user->uid);
+    $path = substr($mails[0]['body'], $start, 66 + strlen($this->user->uid));
+
+    // Log in with temporary login link.
+    $this->drupalPost($path, array(), t('Log in'));
+    $this->assertText($user_message);
   }
 }
 
diff --git a/modules/user/user.module b/modules/user/user.module
index 0fa22cb09f04152c8f2acb636223ce29219fdc23..226f2f152691bff2097c927fddf3d4cf584e0fb3 100644
--- a/modules/user/user.module
+++ b/modules/user/user.module
@@ -1740,6 +1740,48 @@ function user_menu() {
   return $items;
 }
 
+/**
+ * Implements hook_menu_site_status_alter().
+ */
+function user_menu_site_status_alter(&$menu_site_status, $path) {
+  if ($menu_site_status == MENU_SITE_OFFLINE) {
+    // If the site is offline, log out unprivileged users.
+    if (user_is_logged_in() && !user_access('access site in maintenance mode')) {
+      module_load_include('pages.inc', 'user', 'user');
+      user_logout();
+    }
+
+    if (user_is_anonymous()) {
+      switch ($path) {
+        case 'user':
+          // Forward anonymous user to login page.
+          drupal_goto('user/login');
+        case 'user/login':
+        case 'user/password':
+          // Disable offline mode.
+          $menu_site_status = MENU_SITE_ONLINE;
+          break;
+        default:
+          if (strpos($path, 'user/reset/') === 0) {
+            // Disable offline mode.
+            $menu_site_status = MENU_SITE_ONLINE;
+          }
+          break;
+      }
+    }
+  }
+  if (user_is_logged_in()) {
+    if ($path == 'user/login') {
+      // If user is logged in, redirect to 'user' instead of giving 403.
+      drupal_goto('user');
+    }
+    if ($path == 'user/register') {
+      // Authenticated user should be redirected to user edit page.
+      drupal_goto('user/' . $GLOBALS['user']->uid . '/edit');
+    }
+  }
+}
+
 /**
  * Implements hook_init().
  */