diff --git a/modules/negotiate/dictionaries/negotiate.definition.json b/modules/negotiate/dictionaries/negotiate.definition.json
new file mode 100644
index 0000000000000000000000000000000000000000..aed1d4aa98a28b7f41fdf880edc6e9473e198196
--- /dev/null
+++ b/modules/negotiate/dictionaries/negotiate.definition.json
@@ -0,0 +1,17 @@
+{
+	"disable_title": {
+		"en": "Turned off ability to use automatic login for this browser"
+	},
+	"disable_info_pre": {
+		"en": "<p>By accessing this page, you have turned off the ability to use automatic login for this browser.<\/p><p>To turn it on again, please visit <a href='URL'>this page<\/a>.<\/p>"
+	},
+	"enable_title": {
+		"en": "Turned on the ability to use automatic login for this browser"
+	},
+	"enable_info_pre": {
+		"en": "<p>By accessing this page, you have turned on the ability to use automatic login for this browser.<\/p><p>To turn it off again, please visit <a href='URL'>this page<\/a>.<\/p>"
+	},
+	"info_post": {
+		"en": "<h3>What is automatic login<\/h3><p>Automatic login allows you to log in without being asked for username and password:<ul><li>if the computer is in the Kerberos domain<\/li><li>using a supported browser (IE and some others depending on installation options)<\/li><li>on a supported operating system (Windows, Mac)<\/li><\/ul><\/p>"
+	}
+}
diff --git a/modules/negotiate/dictionaries/negotiate.translation.json b/modules/negotiate/dictionaries/negotiate.translation.json
new file mode 100644
index 0000000000000000000000000000000000000000..01a7323aa94e0a4b6208713d6afa7ea7796fbc7f
--- /dev/null
+++ b/modules/negotiate/dictionaries/negotiate.translation.json
@@ -0,0 +1,22 @@
+{
+	"disable_title": {
+		"no": "Skrudd av mulighet for automatisk innlogging for denne nettleseren",
+		"nn": "Skrudd av moglegheit for automatisk innlogging for denne nettlesaren"
+	},
+	"disable_info_pre": {
+		"no": "<p>Ved \u00e5 g\u00e5 til denne siden har du skrudd av muligheten for automatisk innlogging for denne nettleseren.<\/p><p>For \u00e5 skru p\u00e5 igjen muligheten, kan du g\u00e5 til <a href='URL'>denne siden<\/a>.<\/p>",
+		"nn": "<p>Ved \u00e5 g\u00e5 til denne siden har du skrudd av moglegheit for automatisk innlogging for denne nettlesaren.<\/p><p>For \u00e5 skru p\u00e5 igjen moglegheita, kan du g\u00e5 til <a href='URL'>denne siden<\/a>.<\/p>"
+	},
+	"enable_title": {
+		"no": "Skrudd p\u00e5 mulighet for automatisk innlogging for denne nettleseren",
+		"nn": "Skrudd p\u00e5 moglegheit for automatisk innlogging for denne nettlesaren"
+	},
+	"enable_info_pre": {
+		"no": "<p>Ved \u00e5 g\u00e5 til denne siden har du skrudd p\u00e5 muligheten for automatisk innlogging for denne nettleseren.<\/p><p>For \u00e5 skru av igjen muligheten, kan du g\u00e5 til <a href='URL'>denne siden<\/a>.<\/p>",
+		"nn": "<p>Ved \u00e5 g\u00e5 til denne siden har du skrudd p\u00e5 moglegheit for automatisk innlogging for denne nettlesaren.<\/p><p>For \u00e5 skru av igjen moglegheita, kan du g\u00e5 til <a href='URL'>denne siden<\/a>.<\/p>"
+	},
+	"info_post": {
+		"no": "<h3>Hva er automatisk innlogging<\/h3><\/p><p>Automatisk innlogging gj\u00f8r at man kan logge inn uten \u00e5 bli bedt om brukernavn og passord i noen tilfeller:<ul><li>hvis maskinen er i Kerberos domenet<\/li><li>og hvis man bruker en st\u00f8ttet nettleser (IE og noen flere avhengig av oppsett)<\/li><li>p\u00e5 et st\u00f8ttet operativsystem (Windows, Mac)<\/li><\/ul><\/p>",
+		"nn": "<h3>Kva er automatisk innlogging<\/h3><\/p><p>Automatisk innlogging gj\u00f8r at man kan logge inn uten \u00e5 bli bedt om brukernavn og passord i noen tilfeller:<ul><li>hvis maskinen er i Kerberos domenet<\/li><li>og hvis man bruker en st\u00f8ttet nettleser (IE og noen flere avhengig av oppsett)<\/li><li>p\u00e5 et st\u00f8ttet operativsystem (Windows, Mac)<\/li><\/ul><\/p>"
+	}
+}
diff --git a/modules/negotiate/docs/negotiate.txt b/modules/negotiate/docs/negotiate.txt
new file mode 100644
index 0000000000000000000000000000000000000000..62ae6f52c3e4736120940f5c244611a09972e2e5
--- /dev/null
+++ b/modules/negotiate/docs/negotiate.txt
@@ -0,0 +1,279 @@
+Negotiate module
+================
+
+The Negotiate module implements Microsofts Kerberos SPNEGO mechanism.
+It is intended to only support Kerberos and not NTLM which RFC4559
+implements.
+
+`negotiate:Negotiate`
+: Authenticates users via HTTP authentication
+
+`negotiate:Negotiate`
+---------------------
+
+Negotiate implements the following mechanics:
+
+ * Initiate HTTP_AUTHN with the client
+ * Authorize user against a LDAP directory
+ * Collect metadata from LDAP directory
+ * Fall back to other SimpleSamlPhp module for any client/user that
+   fails to authenticate in the Negotiate module
+ * Check only clients from a certain subnet
+ * Supports enabling/disabling a client
+
+In effect this module aims to extend the Microsoft AD SSO session to
+the SAML IdP. (Or any other Kerberos domain) It doesn't work like this
+of course but for the user the client is automatically authenticated
+when an SP sends the client to the IdP. In reality Negotiate
+authenticates the user via SPNEGO and issues a separate SAML session.
+The Kerberos session against the Authentication Server is completely
+separate from the SAML session with the IdP. The only time the
+Kerberos session affects the SAML session is at authN at the IdP.
+
+The module is meant to supplement existing auth modules and not
+replace them. Users do not always log in on the IdP from a machine in
+the Windows domain (or another Kerberos domain) and from their own
+domain accounts. A fallback mechanism must be supplemented.
+
+The Kerberos TGS can be issued for a wide variety of accounts so an
+authoriation backend via LDAP is needed. If the search, with filters,
+fails, the fallback in invoked. This to prevent kiosk accounts and the
+likes to get faulty SAML sessions.
+
+The subnet is required to prevent excess attempts to authenticate via
+Kerberos for clients that always will fail. Worst case scenario the
+browser will prompt the user for u/p in a popup box that will always
+fail. Only when the user clicks cancel the proper login process will
+continue. This is handled through the body of the 401 message the
+client recieves with the Negotiate request. In the body a URL to the
+fallback mechanism is supplied and Javascript is used to redirect the
+client.
+
+All configuration is handled in authsources.php:
+
+     'weblogin' => array(
+             'negotiate:Negotiate',
+             'keytab' => '/path/to/keytab-file',
+             'fallback' => 'ldap',
+             'hostname' => 'ldap.example.com',
+             'base' => 'cn=people,dc=example,dc=com',
+             'adminUser' => 'cn=idp-fallback,cn=services,dc=example,dc=com',
+             'adminPassword' => 'VerySecretPassphraseHush'
+     ),
+     'ldap' => array(
+             'ldap:LDAP',
+             'hostname' => 'ldap.example.com',
+             'enable_tls' => TRUE,
+             'dnpattern' => 'uid=%username%,cn=people,dc=example,dc=com',
+             'search.enable' => FALSE
+     ),
+
+
+
+`php_krb5`
+++++++++++
+
+The processing involving the actual Kerberos ticket handling is done
+by php_krb5. The package is not yet labeled stable but has worked well
+during testing.
+
+NOTE! php_krb5 hardcodes the service name in the keytab file to 'HTTP'
+as of php_krb5-1.0rc2. To change this you need to edit the module code.
+Be wary of how much space is allocated to the string in
+negotiate_auth.c:101.
+
+Depending on you apache config you may need a rewrite rule to allow
+php_krb5 to read the HTTP_AUTHORIZATION header:
+
+     RewriteEngine on
+     RewriteCond %{HTTP:Authorization}  !^$
+     RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization},L]
+
+
+Test the Kerberos setup with the following script:
+
+     <?php
+     if(!extension_loaded('krb5')) {
+             die('KRB5 Extension not installed');
+     }
+
+     if(!empty($_SERVER['HTTP_AUTHORIZATION'])) {
+            list($mech, $data) = explode(' ', $_SERVER['HTTP_AUTHORIZATION']);
+             if(strtolower($mech) == 'basic') {
+                     echo "Client sent basic";
+                     die('Unsupported request');
+             } else if(strtolower($mech) != 'negotiate') {
+                     echo "Couldn't find negotiate";
+                     die('Unsupported request');
+             }
+             $auth = new KRB5NegotiateAuth('/path/to/keytab');
+             $reply = '';
+             if($reply = $auth->doAuthentication()) {
+                     header('HTTP/1.1 200 Success');
+                     echo 'Success - authenticated as ' . $auth->getAuthenticatedUser() . '<br>';
+             } else {
+                     echo 'Failed to authN.';
+                     die();
+             }
+     } else {
+             header('HTTP/1.1 401 Unauthorized');
+             header('WWW-Authenticate: Negotiate',false);
+             echo 'Not authenticated. No HTTP_AUTHORIZATION available.';
+             echo 'Check headers sent by the browser and verify that ';
+             echo 'apache passes them to PHP';
+     }
+     ?>
+
+
+
+`LDAP`
+++++++
+
+LDAP is used to verify the user due to the lack of metadata in
+Kerberos. A domain can contain lots of kiosk users, non-personal
+accounts and the likes. The LDAP lookup will authorize and fetch
+attributes as defined by SimpleSamlPhp metadata.
+
+'hostname', 'enable_tls', 'debugLDAP', 'timeout' and 'base' are
+self-explanatory. Read the documentation of the LDAP auth module for
+more information. 'attr' is the attribute that will be used to look up
+user objects in the directory after extracting it from the Kerberos
+session. Default is 'uid'.
+
+For LDAP directories with restricted access to objects or attributes
+Negotiate implements 'adminUser' and 'adminPassword'. adminUser must
+be a DN to an object with access to search for all relevant user
+objects and to look up attributes needed by the SP.
+
+
+`Subnet filtering`
+++++++++++++++++++
+
+Subnet is meant to filter which clients you subject to the
+WWW-Authenticate request.
+
+Syntax is:
+
+     'subnet' => array('127.0.0.0/16','192.168.0.0/16'),
+
+Browsers, especially IE, behave erratically when they encounter a
+WWW-Authenticate from the webserver. Included in RFC4559 Negotiate is
+NTLM authentication which IE seems prone to fall back to under various
+conditions. This triggers a popup login box which defeats the whole
+purpose of this module.
+
+TBD: Replace or supplement with LDAP lookups in the domain. Machines
+currently in the domain should be the only ones that are promted with
+WWW-Authenticate: Negotiate.
+
+
+`Enabling/disabling Negotiate from a web browser`
++++++++++++++++++++++++++++++++++++++++++++++++++
+
+Included in Negotiate are semi-static web pages for enabling and
+disabling Negotiate for any given client. The pages simple set/deletes
+a cookie that Negotiate will look for when a client attempts AuthN.
+The help text in the JSON files should be locally overwritten to fully
+explain which clients are accepted by Negotiate.
+
+
+`Logout/Login loop and reauthenticating`
+++++++++++++++++++++++++++++++++++++++++
+
+Due to the automatic AuthN of certain clients and how SPs will
+automatically redirect clients to the IdP when clients try to access
+restricted content, a session variable has been put into Negotiate. This
+variable makes sure Negotiate doesn't reautenticate a recently logged
+out user. The consequence of this is that the user will be presented
+with the login mechanism of the fallback module specified in Negotiate
+config.
+
+SimpleSamlPhp offers no decent way of adding hooks or piggyback this
+information to the fallback module. In future releases one might add a
+box of information to the user explaining what's happening.
+
+One can add this bit of code to the template in the fallback AuthN
+module:
+
+// This should be placed in your www script
+$nego_session = FALSE;
+$nego_perm = FALSE;
+$nego_retry = NULL;
+if (array_key_exists('negotiate:authId', $state)) {
+    $nego = SimpleSAML_Auth_Source::getById($state['negotiate:authId']);
+    $mask = $nego->checkMask();
+    $disabled = $nego->spDisabledInMetadata($spMetadata);
+    $session_disabled = $session->getData('negotiate:disable', 'session');
+    if ($mask and !$disabled) {
+        if(array_key_exists('NEGOTIATE_AUTOLOGIN_DISABLE_PERMANENT', $_COOKIE) &&
+           $_COOKIE['NEGOTIATE_AUTOLOGIN_DISABLE_PERMANENT'] == 'True') {
+            $nego_perm = TRUE;
+        } elseif ($session_disabled) {
+            $retryState = SimpleSAML_Auth_State::cloneState($state);
+            unset($retryState[SimpleSAML_Auth_State::ID]);
+            $nego_retry = SimpleSAML_Auth_State::saveState($retryState, 'sspmod_negotiate_Auth_Source_Negotiate.StageId');
+            $nego_session = TRUE;
+        }
+    }
+}
+
+// This should reside in your template
+if($this->data['nego']['disable_perm']) {
+    echo '<span id="login-extra-info-uio.no" class="login-extra-info">'
+          . '<span class="login-extra-info-divider"></span>'
+          . $this->t('{feide:login:login_uio_negotiate_disabled_permanent_info}')
+          . '</span>';
+} elseif($this->data['nego']['disable_session']) {
+     echo '<span id="login-extra-info-uio.no" class="login-extra-info">'
+          . '<span class="login-extra-info-divider"></span>'
+          . $this->t('{feide:login:login_uio_negotiate_disabled_session_info}')
+          . '<br><a href="'.SimpleSAML_Module::getModuleURL('negotiate/retry.php', array('AuthState' => $this->data['nego']['retry_id'])).'">'
+          . $this->t('{feide:login:login_uio_negotiate_disabled_session_info_link}')
+          . '</a>'
+          . '</span>';
+}
+
+The above may or may not work right out of the box for you but it is
+the gist of it. By looking at the state variable, cookie and checking
+for filters and the likes, only clients that are subjected to
+Negotiate should get the help text.
+
+Note that with Negotiate there is also a small script to allow the
+user to re-authenticate with Negotiate after being sent to the
+fallback mechanism due to the session cookie. In the example above you
+can see the construction of the URL. The cloning of the current state
+is necessary for retry.php to load a state without triggering a
+security check in SSP's state handling library. If you omit this and
+pass on the original state you will see a warning in the log like
+this:
+
+    Sep 27 13:47:36 simplesamlphp WARNING [b99e6131ee] Wrong stage in state. Was 'foo', should be 'sspmod_negotiate_Auth_Source_Negotiate.StageId'.
+
+It will work as loadState will take controll and call
+Negotiate->authenticate() but remaining code in retry.php will be
+discarded. Other side-effects may occur.
+
+
+`Clients`
++++++++++
+
+* Internet Explorer
+
+YMMV but generally you need to have your IdP defined in "Internet
+Options" -> "Security" -> "Local intranet" -> "Sites" -> "Advanced".
+You also need "Internet Options" -> "Advanced" -> "Security" -> Enable
+Integrated Windows Authentication" enabled.
+
+* Firefox
+
+Open "about:config". Locate "network.auth.use-sspi" and verify that
+this is true (on a Windows machine). Next locate
+"network.negotiate-auth.trusted-uris" and insert your IdP.
+
+* Safari
+
+TODO
+
+* Chrome
+
+TODO
diff --git a/modules/negotiate/lib/Auth/Source/Negotiate.php b/modules/negotiate/lib/Auth/Source/Negotiate.php
new file mode 100644
index 0000000000000000000000000000000000000000..d2f49792b5c7d9858c4047ab6259f3ed48d66914
--- /dev/null
+++ b/modules/negotiate/lib/Auth/Source/Negotiate.php
@@ -0,0 +1,321 @@
+<?php
+
+/**
+ * The Negotiate module. Allows for password-less, secure login by
+ * Kerberos and Negotiate.
+ *
+ * @author Mathias Meisfjordskar, University of Oslo.
+ * 	   <mathias.meisfjordskar@usit.uio.no>
+ * @package simpleSAMLphp
+ * @version $Id$
+ */
+
+class sspmod_negotiate_Auth_Source_Negotiate extends SimpleSAML_Auth_Source {
+
+	// Constants used in the module
+	const STAGEID = 'sspmod_negotiate_Auth_Source_Negotiate.StageId';
+
+	private $config;
+	protected $ldap = NULL;
+
+	/**
+	 * Constructor for this authentication source.
+	 *
+	 * @param array $info	 Information about this authentication source.
+	 * @param array $config	 Configuration.
+	 */
+	public function __construct($info, $config) {
+		assert('is_array($info)');
+		assert('is_array($config)');
+
+		if(!extension_loaded('krb5'))
+			throw new Exception('KRB5 Extension not installed');
+
+		// Call the parent constructor first, as required by the interface.
+		parent::__construct($info, $config);
+
+		$config = SimpleSAML_Configuration::loadFromArray($config);;
+
+		$this->backend = $config->getString('fallback');
+		$this->hostname = $config->getString('hostname');
+		$this->enableTLS = $config->getBoolean('enable_tls', FALSE);
+		$this->debugLDAP = $config->getBoolean('debugLDAP', FALSE);
+		$this->timeout = $config->getValue('timeout', 30);
+		$this->keytab = $config->getString('keytab');
+		$this->base = $config->getString('base');
+		$this->attr = $config->getString('attr', 'uid');
+		$this->subnet = $config->getArray('subnet', NULL);
+		$this->admin_user = $config->getString('adminUser', NULL);
+		$this->admin_pw = $config->getString('adminPassword', NULL);
+
+	}
+
+	/**
+	 * The inner workings of the module.
+	 *
+	 * Checks to see if client is in the defined subnets (if
+	 * defined in config). Sends the client a 401 Negotiate and
+	 * responds to the result. If the client fails to provide a
+	 * proper Kerberos ticket, the login process is handed over to
+	 * the 'fallback' module defined in the config.
+	 *
+	 * LDAP is used as a user metadata source.
+	 *
+	 * @param array &$state	 Information about the current authentication.
+	 */
+	public function authenticate(&$state) {
+		assert('is_array($state)');
+
+		// Set the default backend to config
+		$state['LogoutState'] = array(
+			'negotiate:backend' => $this->backend,
+			);
+		$state['negotiate:authId'] = $this->authId;
+
+
+		// Check for disabled SPs. The disable flag is store in the SP
+		// metadata.
+		if (array_key_exists('SPMetadata', $state) and $this->spDisabledInMetadata($state['SPMetadata']))
+			$this->fallBack($state);
+		// Go straight to fallback if Negotiate is disabled or if you are
+		// sent back to the IdP directly from the SP after having logged out
+		$session = SimpleSAML_Session::getInstance();
+		$disabled = $session->getData('negotiate:disable', 'session');
+
+		if ($disabled or
+		    (!empty($_COOKIE['NEGOTIATE_AUTOLOGIN_DISABLE_PERMANENT']) and
+		     $_COOKIE['NEGOTIATE_AUTOLOGIN_DISABLE_PERMANENT'] == 'True')) {
+			SimpleSAML_Logger::debug('Negotiate - session disabled. falling back');
+			$this->fallBack($state);
+			// Never executed
+			assert('FALSE');
+		}
+		$mask = $this->checkMask();
+		if (!$mask) {
+			$this->fallBack($state);
+			// Never executed
+			assert('FALSE');
+		}
+
+		SimpleSAML_Logger::debug('Negotiate - authenticate(): looking for Negotate');
+		if (!empty($_SERVER['HTTP_AUTHORIZATION'])) {
+			SimpleSAML_Logger::debug('Negotiate - authenticate(): Negotate found');
+			$this->ldap = new SimpleSAML_Auth_LDAP($this->hostname, $this->enableTLS, $this->debugLDAP, $this->timeout);
+
+			list($mech, $data) = explode(' ', $_SERVER['HTTP_AUTHORIZATION'],2);
+			if(strtolower($mech) == 'basic')
+				SimpleSAML_Logger::debug('Negotiate - authenticate(): Basic found. Skipping.');
+			else if(strtolower($mech) != 'negotiate')
+				SimpleSAML_Logger::debug('Negotiate - authenticate(): No "Negotiate" found. Skipping.');
+
+			$auth = new KRB5NegotiateAuth($this->keytab);
+			// Atempt Kerberos authentication
+			try {
+				$reply = $auth->doAuthentication();
+			} catch (Exception $e) {
+				SimpleSAML_Logger::error('Negotiate - authenticate(): doAuthentication() exception: '. $e->getMessage());
+				$reply = NULL;
+			}
+
+			if($reply) {
+				// Success. Krb TGS recieved.
+				$user = $auth->getAuthenticatedUser();
+				SimpleSAML_Logger::info('Negotiate - authenticate(): '. $user . ' authenticated.');
+				$lookup = $this->lookupUserData($user);
+				if ($lookup) {
+					$state['Attributes'] = $lookup;
+					// Override the backend so logout will know what to look for.
+					$state['LogoutState'] = array(
+						'negotiate:backend' => NULL,
+						);
+					SimpleSAML_Logger::info('Negotiate - authenticate(): '. $user . ' authorized.');
+					SimpleSAML_Auth_Source::completeAuth($state);
+					// Never reached.
+					assert('FALSE');
+				}
+			} else {
+				// Some error in the recieved ticket. Expired?
+				SimpleSAML_Logger::info('Negotiate - authenticate(): Kerberos authN failed. Skipping.');
+			}
+		} else {
+			// No auth token. Send it.
+			SimpleSAML_Logger::debug('Negotiate - authenticate(): Sending Negotiate.');
+			// Save the $state array, so that we can restore if after a redirect
+			SimpleSAML_Logger::debug('Negotiate - fallback: '.$state['LogoutState']['negotiate:backend']);
+			$id = SimpleSAML_Auth_State::saveState($state, self::STAGEID);
+			$params = array('AuthState' => $id);
+
+			$this->sendNegotiate($params);
+			exit;
+		}
+
+		SimpleSAML_Logger::info('Negotiate - authenticate(): Client failed Negotiate. Falling back');
+		$this->fallBack($state);
+		/* The previous function never returns, so this code is never
+		   executed */
+		assert('FALSE');
+	}
+
+	public function spDisabledInMetadata($spMetadata) {
+		if (array_key_exists('negotiate:disable', $spMetadata)) {
+			if ($spMetadata['negotiate:disable'] == TRUE) {
+				SimpleSAML_Logger::debug('Negotiate - SP disabled. falling back');
+				return True;
+				// Never executed
+				assert('FALSE');
+			} else {
+				SimpleSAML_Logger::debug('Negotiate - SP disable flag found but set to FALSE');
+			}
+		} else {
+			SimpleSAML_Logger::debug('Negotiate - SP disable flag not found');
+		}
+		return False;
+	}
+
+	/**
+	 * checkMask() looks up the subnet config option and verifies
+	 * that the client is within that range.
+	 *
+	 * Will return TRUE if no subnet option is configured.
+	 *
+	 * @return boolean
+	 */
+        public function checkMask() {
+		// No subnet means all clients are accepted.
+		if ($this->subnet === NULL)
+			return TRUE;
+		$ip = $_SERVER['REMOTE_ADDR'];
+		foreach ($this->subnet as $cidr) {
+			$ret = SimpleSAML_Utilities::ipCIDRcheck($cidr);
+			if ($ret) {
+				SimpleSAML_Logger::debug('Negotiate: Client "'.$ip.'" matched subnet.');
+				return TRUE;
+			}
+		}
+		SimpleSAML_Logger::debug('Negotiate: Client "'.$ip.'" did not match subnet.');
+		return FALSE;
+	}
+
+	/**
+	 * Send the actual headers and body of the 401. Embedded in
+	 * the body is a post that is triggered by JS if the client
+	 * wants to show the 401 message.
+	 *
+	 * @param array $params additional parameters to the URL in
+	 *			the URL in the body
+	 */
+	protected function sendNegotiate($params) {
+		$url = SimpleSAML_Module::getModuleURL('negotiate/backend.php', $params);
+
+		header('HTTP/1.1 401 Unauthorized');
+		header('WWW-Authenticate: Negotiate',false);
+		echo '
+<html>
+<head>
+<script type="text/javascript">window.location = '.json_encode(htmlspecialchars($url)).'</script>
+<title>Redirect to login</title>
+</head>
+<body>
+Your browser seems to have Javascript disabled. Please
+click <a href="'.htmlspecialchars($url).'">here</a>.
+</body>
+</html> ';
+
+	}
+
+	/**
+	 * Passes control of the login process to a different module.
+	 *
+	 * @param string $state	 Information about the current authentication.
+	 */
+	public static function fallBack(&$state) {
+		$authId = $state['LogoutState']['negotiate:backend'];
+
+		if ($authId === NULL) {
+			$msg = "This code should never be reached.";
+			throw new SimpleSAML_Error_AuthSource($msg);
+		}
+		$source = SimpleSAML_Auth_Source::getById($authId);
+
+		try {
+			$source->authenticate($state);
+		} catch (SimpleSAML_Error_Exception $e) {
+			SimpleSAML_Auth_State::throwException($state, $e);
+		} catch (Exception $e) {
+			$e = new SimpleSAML_Error_UnserializableException($e);
+			SimpleSAML_Auth_State::throwException($state, $e);
+		}
+		// fallBack never returns after loginCompleted()
+		SimpleSAML_Logger::debug('Negotiate: backend returned');
+		self::loginCompleted($state);
+	}
+
+	/**
+	 * Strips away the realm of the Kerberos identifier, looks up
+	 * what attributes to fetch from SP metadata and searches the
+	 * directory.
+	 *
+	 * @param string $user	The Kerberos user identifier
+	 * @return string	The DN to the user or NULL if not found
+	 */
+	protected function lookupUserData($user) {
+		// Kerberos usernames include realm. Strip that away.
+		$pos = strpos($user, '@');
+		if ($pos === false)
+			return NULL;
+		$uid = substr($user, 0, $pos);
+
+		$this->adminBind();
+		try {
+			$dn = $this->ldap->searchfordn($this->base, $this->attr, $uid);
+			return $this->ldap->getAttributes($dn);
+		} catch (SimpleSAML_Error_Exception $e) {
+			SimpleSAML_Logger::debug('Negotiate - ldap lookup failed: '. $e);
+			return NULL;
+		}
+	}
+
+	/**
+	 * Elevates the LDAP connection to allow restricted lookups if
+	 * so configured. Does nothing if not.
+	 */
+	protected function adminBind() {
+		if ($this->admin_user === NULL) {
+			// No admin user.
+			return;
+		}
+		SimpleSAML_Logger::debug('Negotiate - authenticate(): Binding as system user ' . var_export($this->admin_user, TRUE));
+
+		if(!$this->ldap->bind($this->admin_user, $this->admin_pw)){
+			$msg = 'Unable to authenticate system user (LDAP_INVALID_CREDENTIALS) ' . var_export($this->admin_user, TRUE);
+			SimpleSAML_Logger::error('Negotiate - authenticate(): ' . $msg);
+			throw new SimpleSAML_Error_AuthSource($msg);
+		}
+	}
+
+	/**
+	 * Log out from this authentication source.
+	 *
+	 * This method either logs the user out from Negotiate or passes the
+	 * logout call to the fallback module.
+	 *
+	 * @param array &$state	 Information about the current logout operation.
+	 */
+	public function logout(&$state) {
+		assert('is_array($state)');
+		/* Get the source that was used to authenticate */
+		$authId = $state['negotiate:backend'];
+		SimpleSAML_Logger::debug('Negotiate - logout has the following authId: "'.$authId.'"');
+
+		if ($authId === NULL) {
+			$session = SimpleSAML_Session::getInstance();
+			$session->setData('negotiate:disable', 'session', TRUE, 24*60*60);
+			parent::logout($state);
+		} else {
+			$source = SimpleSAML_Auth_Source::getById($authId);
+			$source->logout($state);
+		}
+	}
+
+}
+
diff --git a/modules/negotiate/templates/disable.php b/modules/negotiate/templates/disable.php
new file mode 100644
index 0000000000000000000000000000000000000000..acb8480ae764f6861687bae83818bd6c00e05c11
--- /dev/null
+++ b/modules/negotiate/templates/disable.php
@@ -0,0 +1,21 @@
+<?php
+
+/**
+ *
+ *
+ * @author Mathias Meisfjordskar, University of Oslo.
+ *         <mathias.meisfjordskar@usit.uio.no>
+ * @package simpleSAMLphp
+ * @version $Id$
+ */
+$this->includeAtTemplateBase('includes/header.php');
+?>
+<h1><?php echo $this->t('{negotiate:negotiate:disable_title}'); ?></h1>
+<?php
+$url = SimpleSAML_Module::getModuleURL('negotiate/enable.php');
+?>
+<?php echo $this->t('{negotiate:negotiate:disable_info_pre}', array('URL' => htmlspecialchars($url))); ?>
+
+<?php echo $this->t('{negotiate:negotiate:info_post}'); ?>
+
+<?php $this->includeAtTemplateBase('includes/footer.php'); ?>
diff --git a/modules/negotiate/templates/enable.php b/modules/negotiate/templates/enable.php
new file mode 100644
index 0000000000000000000000000000000000000000..05caeb996e47c2a8a15026495d3c982d56f7ea3d
--- /dev/null
+++ b/modules/negotiate/templates/enable.php
@@ -0,0 +1,22 @@
+<?php
+
+/**
+ *
+ *
+ * @author Mathias Meisfjordskar, University of Oslo.
+ *         <mathias.meisfjordskar@usit.uio.no>
+ * @package simpleSAMLphp
+ * @version $Id$
+ */
+$this->includeAtTemplateBase('includes/header.php');
+?>
+<h1><?php echo $this->t('{negotiate:negotiate:enable_title}'); ?></h1>
+
+<?php
+$url = SimpleSAML_Module::getModuleURL('negotiate/disable.php');
+?>
+<?php echo $this->t('{negotiate:negotiate:enable_info_pre}', array('URL' => htmlspecialchars($url))); ?>
+
+<?php echo $this->t('{negotiate:negotiate:info_post}'); ?>
+
+<?php $this->includeAtTemplateBase('includes/footer.php'); ?>
diff --git a/modules/negotiate/www/backend.php b/modules/negotiate/www/backend.php
new file mode 100644
index 0000000000000000000000000000000000000000..f9d3622296bcf95b3ac7fcd170ddc782a8c9e1b3
--- /dev/null
+++ b/modules/negotiate/www/backend.php
@@ -0,0 +1,18 @@
+<?php
+
+/**
+ * Provide a URL for the module to statically link to.
+ *
+ * @author Mathias Meisfjordskar, University of Oslo.
+ *         <mathias.meisfjordskar@usit.uio.no>
+ * @package simpleSAMLphp
+ * @version $Id$
+ */
+
+$authStateId = $_REQUEST['AuthState'];
+$state = SimpleSAML_Auth_State::loadState($authStateId, sspmod_negotiate_Auth_Source_Negotiate::STAGEID);
+SimpleSAML_Logger::debug('backend - fallback: '.$state['LogoutState']['negotiate:backend']);
+
+sspmod_negotiate_Auth_Source_Negotiate::fallBack($state);
+
+exit;
diff --git a/modules/negotiate/www/disable.php b/modules/negotiate/www/disable.php
new file mode 100644
index 0000000000000000000000000000000000000000..021fb7bc400313dc1478def7f58c622895d04fd5
--- /dev/null
+++ b/modules/negotiate/www/disable.php
@@ -0,0 +1,17 @@
+<?php
+
+/**
+ *
+ *
+ * @author Mathias Meisfjordskar, University of Oslo.
+ *         <mathias.meisfjordskar@usit.uio.no>
+ * @package simpleSAMLphp
+ * @version $Id$
+ */
+
+$globalConfig = SimpleSAML_Configuration::getInstance();
+setcookie('NEGOTIATE_AUTOLOGIN_DISABLE_PERMANENT', 'True', mktime(0,0,0,1,1,2038), '/', SimpleSAML_Utilities::getSelfHost(), FALSE, TRUE);
+$session = SimpleSAML_Session::getInstance();
+$session->setData('negotiate:disable', 'session', FALSE, 24*60*60);
+$t = new SimpleSAML_XHTML_Template($globalConfig, 'negotiate:disable.php');
+$t->show();
diff --git a/modules/negotiate/www/enable.php b/modules/negotiate/www/enable.php
new file mode 100644
index 0000000000000000000000000000000000000000..340b9dcd00b9e8118b95c6d506a323b9527eb8f5
--- /dev/null
+++ b/modules/negotiate/www/enable.php
@@ -0,0 +1,17 @@
+<?php
+
+/**
+ *
+ *
+ * @author Mathias Meisfjordskar, University of Oslo.
+ *         <mathias.meisfjordskar@usit.uio.no>
+ * @package simpleSAMLphp
+ * @version $Id$
+ */
+
+$globalConfig = SimpleSAML_Configuration::getInstance();
+setcookie('NEGOTIATE_AUTOLOGIN_DISABLE_PERMANENT', 'False', time() - 3600, '/', SimpleSAML_Utilities::getSelfHost(), FALSE, TRUE);
+$session = SimpleSAML_Session::getInstance();
+$session->setData('negotiate:disable', 'session', FALSE, 24*60*60);
+$t = new SimpleSAML_XHTML_Template($globalConfig, 'negotiate:enable.php');
+$t->show();
diff --git a/modules/negotiate/www/retry.php b/modules/negotiate/www/retry.php
new file mode 100644
index 0000000000000000000000000000000000000000..2d12eab97ebe92777c8108b067a8aed5e1472a4b
--- /dev/null
+++ b/modules/negotiate/www/retry.php
@@ -0,0 +1,32 @@
+<?php
+
+/**
+ *
+ *
+ * @author Mathias Meisfjordskar, University of Oslo.
+ *         <mathias.meisfjordskar@usit.uio.no>
+ * @package simpleSAMLphp
+ * @version $Id$
+ */
+
+$authStateId = $_REQUEST['AuthState'];
+$state = SimpleSAML_Auth_State::loadState($authStateId, sspmod_negotiate_Auth_Source_Negotiate::STAGEID);
+
+$metadata = SimpleSAML_Metadata_MetaDataStorageHandler::getMetadataHandler();
+$idpid = $metadata->getMetaDataCurrentEntityID('saml20-idp-hosted', 'metaindex');
+$idpmeta = $metadata->getMetaData($idpid, 'saml20-idp-hosted');
+
+if (isset($idpmeta['auth'])) {
+	$source = SimpleSAML_Auth_Source::getById($idpmeta['auth']);
+	if ($source === NULL)
+		throw new SimpleSAML_Error_BadRequest('Invalid AuthId "' . $idpmeta['auth'] . '" - not found.');
+
+	$session = SimpleSAML_Session::getInstance();
+	$session->setData('negotiate:disable', 'session', FALSE, 24*60*60);
+	SimpleSAML_Logger::debug('Negotiate(retry) - session enabled, retrying.');
+	$source->authenticate($state);
+	assert('FALSE');
+} else {
+	SimpleSAML_Logger::error('Negotiate - retry - no "auth" parameter found in IdP metadata.');
+	assert('FALSE');
+}