diff --git includes/menu.inc includes/menu.inc
index b1d56ba..3cd4b03 100644
--- includes/menu.inc
+++ includes/menu.inc
@@ -3336,7 +3336,8 @@ 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');
+        $login_paths = module_invoke_all('login_paths');
+        return !in_array($_GET['q'], $login_paths);
       }
       // Logged in users are unprivileged here, so they are logged out.
       if (!$check_only) {
diff --git modules/openid/openid.module modules/openid/openid.module
index d63ee56..0831f48 100644
--- modules/openid/openid.module
+++ modules/openid/openid.module
@@ -39,6 +39,13 @@ function openid_menu() {
 }
 
 /**
+ * Implements hook_login_paths().
+ */
+function openid_login_paths() {
+  return array('openid/authenticate');
+}
+
+/**
  * Implements hook_help().
  */
 function openid_help($path, $arg) {
diff --git modules/simpletest/drupal_web_test_case.php modules/simpletest/drupal_web_test_case.php
index b2abe90..98a01ea 100644
--- modules/simpletest/drupal_web_test_case.php
+++ modules/simpletest/drupal_web_test_case.php
@@ -1077,10 +1077,11 @@ class DrupalWebTestCase extends DrupalTestCase {
    * Logs a user out of the internal browser, then check the login page to confirm logout.
    */
   protected function drupalLogout() {
-    // Make a request to the logout page, and redirect to the user page, the
+    // Make a request to the logout page, and then to the user page, the
     // idea being if you were properly logged out you should be seeing a login
     // screen.
-    $this->drupalGet('user/logout', array('query' => array('destination' => 'user')));
+    $this->drupalGet('user/logout');
+    $this->drupalGet('user');
     $pass = $this->assertField('name', t('Username field found.'), t('Logout'));
     $pass = $pass && $this->assertField('pass', t('Password field found.'), t('Logout'));
 
diff --git modules/simpletest/tests/menu.test modules/simpletest/tests/menu.test
index 53ee3b7..b2842f2 100644
--- modules/simpletest/tests/menu.test
+++ modules/simpletest/tests/menu.test
@@ -82,6 +82,22 @@ class MenuRouterTestCase extends DrupalWebTestCase {
   }
 
   /**
+   * Make sure the maintenance mode can be bypassed using hook_login_paths().
+   *
+   * @see menu_test_login_paths().
+   */
+  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 the theme callback when it is set to use an optional theme.
    */
   function testThemeCallbackOptionalTheme() {
@@ -482,4 +498,3 @@ class MenuTreeDataTestCase extends DrupalUnitTestCase {
     return $this->assert($link1['mlid'] == $link2['mlid'], $message ? $message : t('First link is identical to second link'));
   }
 }
-
diff --git modules/simpletest/tests/menu_test.module modules/simpletest/tests/menu_test.module
index a51d9fc..50a7fa6 100644
--- modules/simpletest/tests/menu_test.module
+++ modules/simpletest/tests/menu_test.module
@@ -173,6 +173,12 @@ function menu_test_menu() {
     'context' => MENU_CONTEXT_NONE,
   );
 
+  $items['menu_login_callback'] = array(
+    'title' => 'Used as a login path',
+    'page callback' => 'menu_login_callback',
+    'access callback' => TRUE,
+  );
+
   return $items;
 }
 
@@ -313,3 +319,17 @@ function menu_test_static_variable($value = NULL) {
   }
   return $variable;
 }
+
+/**
+ * Implements hook_login_paths().
+ */
+function menu_test_login_paths() {
+  return array('menu_login_callback');
+}
+
+/**
+ * Menu callback to be used as a login path.
+ */
+function menu_login_callback() {
+  return 'This is menu_login_callback().';
+}
diff --git modules/system/system.api.php modules/system/system.api.php
index c9d1cfc..e3d5ca9 100644
--- modules/system/system.api.php
+++ modules/system/system.api.php
@@ -3218,6 +3218,23 @@ function hook_countries_alter(&$countries) {
   // Quebec has seceded from Canada. Add to country list.
   $countries['QC'] = 'Quebec';
 }
+
+/**
+ * Define paths as necessary for user login.
+ *
+ * The hook is only called when the site is in maintenance mode to decide
+ * whether the path should be accessible for anonymous users in maintenance
+ * mode.
+ *
+ * @return
+ *   An array of Drupal paths which should be accessible for anonymous users
+ *   even in maintenance mode. Core defines user, user/login and
+ *   openid/authenticate.
+ */
+function hook_login_paths() {
+  return array('mymodule/authenticate');
+}
+
 /**
  * @} End of "addtogroup hooks".
  */
diff --git modules/user/user.module modules/user/user.module
index 4d4acc2..494bc8d 100644
--- modules/user/user.module
+++ modules/user/user.module
@@ -1683,6 +1683,13 @@ function user_menu() {
 }
 
 /**
+ * Implements hook_login_paths().
+ */
+function user_login_paths() {
+  return array('user', 'user/login');
+}
+
+/**
  * Implements hook_init().
  */
 function user_init() {
