diff --git a/config-templates/config.php b/config-templates/config.php
index 9df78a4a22a550b8843758ac6197710b310753b8..9136e0545c970f1f313c273a2db96ccc79032ddb 100644
--- a/config-templates/config.php
+++ b/config-templates/config.php
@@ -240,6 +240,12 @@ $config = array (
 	 */
 	'session.cookie.secure' => FALSE,
 
+	/*
+	 * When set to FALSE fallback to transient session on session initialization
+	 * failure, throw exception otherwise.
+	 */
+	'session.disable_fallback' => FALSE,
+
 	/*
 	 * Enable secure POST from HTTPS to HTTP.
 	 *
diff --git a/docs/simplesamlphp-changelog.txt b/docs/simplesamlphp-changelog.txt
index ea17b82ea557585bbac5be584de42c9f622e468c..62554114eb435fb56f5c2f4428ad848226e57a49 100644
--- a/docs/simplesamlphp-changelog.txt
+++ b/docs/simplesamlphp-changelog.txt
@@ -8,7 +8,7 @@ See the upgrade notes for specific information about upgrading.
 
 ## Version 1.9
 
-Released 2012-04-XX.
+Released 2012-05-XX.
 
   * Restructure error templates to share a common base template.
   * Warnings about URL length limits from Suhosin PHP extension.
@@ -53,6 +53,14 @@ Released 2012-04-XX.
   * Fix invalid HTML for login pages where username is set.
   * Remove unecessary check for PHP version >= 5.2 when setting cookies.
   * Better error message when a module is missing a default-enable or default-disable file.
+  * Support for validating RSA-SHA256 signatures.
+
+### `aselect`
+
+  * New module that replaces the previous module.
+  * Better error handling.
+  * Support for request signing.
+  * Loses support for A-Select Cross.
 
 ### `authcrypt`
 
@@ -135,6 +143,10 @@ Released 2012-04-XX.
   * Update to latest version of the OpenID library.
   * Support for sending authentication requests via GET requests (with the prefer_http_redirect option).
 
+### `radius`
+
+  * Support for setting the "NAS-Identifier" attribute.
+
 ### `saml`
 
   * Preserve ID-attributes on elements during signing. (Makes it possible to change the binding for some messages.)
@@ -153,11 +165,14 @@ Released 2012-04-XX.
   * IdP: Add `saml:AllowCreate` to the state array. This makes it possible to access this parameter from authentication processing filters.
   * IdP: Sign the artifact response message.
   * IdP: Allow the "host" metadata option to include more than one path element.
+  * IdP: Support for generating metadata with MDUI extension elements.
   * SP: Use the discojuice-module as a discovery service if it is enabled.
   * SP: Add `saml:idp`-parameter to trigger login to a specific IdP to as_login.php.
   * SP: Do not display error on duplicate response when we have a valid session.
   * SP: Fix for logout after IdP initiated authentication.
   * SP: Fix handling of authentication response without a saml:Issuer element.
+  * SP: Support for specifying required attributes in metadata.
+  * SP: Support for limiting the AssertionConsumerService endpoints listed in metadata.
   * `saml:PersistentNameID`: Fail when the user has more than one value in the user ID attribute.
   * `saml:SQLPersistentNameID`: Persistent NameID stored in a SQL database.
   * `saml:AuthnContextClassRef`: New filter to set the AuthnContextClassRef in responses.
diff --git a/docs/simplesamlphp-metadata-extensions-attributes.txt b/docs/simplesamlphp-metadata-extensions-attributes.txt
new file mode 100644
index 0000000000000000000000000000000000000000..fb9dad61a7587546420887dea9045b25b810feac
--- /dev/null
+++ b/docs/simplesamlphp-metadata-extensions-attributes.txt
@@ -0,0 +1,113 @@
+SAML V2.0 Metadata Extensions for Login and Discovery User Interface
+=============================
+
+<!--
+	This file is written in Markdown syntax.
+	For more information about how to use the Markdown syntax, read here:
+	http://daringfireball.net/projects/markdown/syntax
+-->
+
+  * Version: `$Id:$`
+  * Author: Timothy Ace [tace@synacor.com](mailto:tace@synacor.com)
+
+<!-- {{TOC}} -->
+
+This is a reference for the SimpleSAMLphp implemenation of the [SAML
+V2.0 Attribute Extensions](http://docs.oasis-open.org/security/saml/Post2.0/sstc-saml-attribute-ext.pdf)
+defined by OASIS.
+
+The `metadata/saml20-idp-hosted.php` entries are used to define the
+metadata extension items. An example of this is:
+
+    <?php
+    $metadata['entity-id-1'] = array(
+        /* ... */
+		'EntityAttributes' => array(
+			'urn:simplesamlphp:v1:simplesamlphp' => array('is', 'really', 'cool'),
+			'{urn:simplesamlphp:v1}foo'          => array('bar'),
+		),
+        /* ... */
+    );
+
+The OASIS specification primarily defines how to include arbitrary
+`Attribute` and `Assertion` elements within the metadata for an IdP.
+
+*Note*: SimpleSAMLphp does not support `Assertion` elements within the
+metadata at this time.
+
+Defining Attributes
+--------------
+
+The `EntityAttributes` key is used to define the attributes in the
+metadata. Each item in the `EntityAttributes` array defines a new
+`<Attribute>` item in the metadata. The value for each key must be an
+array. Each item in this array produces a separte `<AttributeValue>`
+element within the `<Attribute>` element.
+
+		'EntityAttributes' => array(
+			'urn:simplesamlphp:v1:simplesamlphp' => array('is', 'really', 'cool'),
+		),
+
+This generates:
+
+      <saml:Attribute xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" Name="urn:simplesamlphp:v1:simplesamlphp" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
+        <saml:AttributeValue xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xs="http://www.w3.org/2001/XMLSchema" xsi:type="xs:string">is</saml:AttributeValue>
+        <saml:AttributeValue xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xs="http://www.w3.org/2001/XMLSchema" xsi:type="xs:string">really</saml:AttributeValue>
+        <saml:AttributeValue xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xs="http://www.w3.org/2001/XMLSchema" xsi:type="xs:string">cool</saml:AttributeValue>
+      </saml:Attribute>
+
+Each `<Attribute>` element requires a `NameFormat` attribute. This is
+specified using curly braces at the beginning of the key name:
+
+		'EntityAttributes' => array(
+			'{urn:simplesamlphp:v1}foo' => array('bar'),
+		),
+
+This generates:
+
+      <saml:Attribute xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" Name="foo" NameFormat="urn:simplesamlphp:v1">
+        <saml:AttributeValue xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xs="http://www.w3.org/2001/XMLSchema" xsi:type="xs:string">bar</saml:AttributeValue>
+      </saml:Attribute>
+
+When the curly braces are omitted, the NameFormat is automatically set
+to "urn:oasis:names:tc:SAML:2.0:attrname-format:uri".
+
+Generated XML Metadata Examples
+----------------
+
+If given the following configuration...
+
+    $metadata['https://www.example.com/saml/saml2/idp/metadata.php'] = array(
+        'host' => 'www.example.com',
+        'certificate' => 'server.crt',
+        'privatekey' => 'server.pem',
+        'auth' => 'example-userpass',
+
+		'EntityAttributes' => array(
+			'urn:simplesamlphp:v1:simplesamlphp' => array('is', 'really', 'cool'),
+			'{urn:simplesamlphp:v1}foo'          => array('bar'),
+		),
+	);
+
+... will generate the following XML metadata:
+
+	<?xml version="1.0"?>
+	<md:EntityDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" xmlns:mdattr="urn:oasis:names:tc:SAML:metadata:attribute" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mdui="urn:oasis:names:tc:SAML:metadata:ui" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" entityID="https://www.example.com/saml/saml2/idp/metadata.php">
+	  <md:Extensions>
+		<mdattr:EntityAttributes xmlns:mdattr="urn:oasis:names:tc:SAML:metadata:attribute">
+		  <saml:Attribute xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" Name="urn:simplesamlphp:v1:simplesamlphp" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
+			<saml:AttributeValue xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xs="http://www.w3.org/2001/XMLSchema" xsi:type="xs:string">is</saml:AttributeValue>
+			<saml:AttributeValue xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xs="http://www.w3.org/2001/XMLSchema" xsi:type="xs:string">really</saml:AttributeValue>
+			<saml:AttributeValue xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xs="http://www.w3.org/2001/XMLSchema" xsi:type="xs:string">cool</saml:AttributeValue>
+		  </saml:Attribute>
+		  <saml:Attribute xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" Name="foo" NameFormat="urn:simplesamlphp:v1">
+			<saml:AttributeValue xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xs="http://www.w3.org/2001/XMLSchema" xsi:type="xs:string">bar</saml:AttributeValue>
+		  </saml:Attribute>
+		</mdattr:EntityAttributes>
+	  </md:Extensions>
+	  <md:IDPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
+		<md:KeyDescriptor use="signing">
+		  <ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
+			<ds:X509Data>
+            ...
+
diff --git a/docs/simplesamlphp-metadata-extensions-ui.txt b/docs/simplesamlphp-metadata-extensions-ui.txt
new file mode 100644
index 0000000000000000000000000000000000000000..7585a32cd66e59a8e7bb0ba84d3bfd2f7920e2e4
--- /dev/null
+++ b/docs/simplesamlphp-metadata-extensions-ui.txt
@@ -0,0 +1,265 @@
+SAML V2.0 Metadata Extensions for Login and Discovery User Interface
+=============================
+
+<!--
+    This file is written in Markdown syntax.
+    For more information about how to use the Markdown syntax, read here:
+    http://daringfireball.net/projects/markdown/syntax
+-->
+
+  * Version: `$Id:$`
+  * Author: Timothy Ace [tace@synacor.com](mailto:tace@synacor.com)
+
+<!-- {{TOC}} -->
+
+This is a reference for the SimpleSAMLphp implemenation of the [SAML
+V2.0 Metadata Extensions for Login and Discovery User Interface](http://docs.oasis-open.org/security/saml/Post2.0/sstc-saml-metadata-ui/v1.0/sstc-saml-metadata-ui-v1.0.pdf)
+defined by OASIS.
+
+The `metadata/saml20-idp-hosted.php` entries are used to define the
+metadata extension items. An example of this is:
+
+    <?php
+    $metadata['entity-id-1'] = array(
+        /* ... */
+        'UIInfo' => array(
+            'DisplayName' => array(
+                'en' => 'English name',
+                'es' => 'Nombre en Español',
+            ),
+            'Description' => array(
+                'en' => 'English description',
+                'es' => 'Descripción en Español',
+            ),
+            'InformationURL' => array(
+                'en' => 'http://example.com/info/en',
+                'es' => 'http://example.com/info/es',
+            ),
+            'PrivacyStatementURL' => array(
+                'en' => 'http://example.com/privacy/en',
+                'es' => 'http://example.com/privacy/es',
+            ),
+            'Keywords' => array(
+                'en' => array('communication', 'federated session'),
+                'es' => array('comunicación', 'sesión federated'),
+            ),
+            'Logo' => array(
+                array(
+                    'url'    => 'http://example.com/logo1.png',
+                    'height' => 200,
+                    'width'  => 400,
+                    'lang'   => 'en',
+                ),
+                array(
+                    'url'    => 'http://example.com/logo2.png',
+                    'height' => 201,
+                    'width'  => 401,
+                ),
+            ),
+        ),
+        'DiscoHints' => array(
+            'IPHint'          => array('130.59.0.0/16', '2001:620::0/96'),
+            'DomainHint'      => array('example.com', 'www.example.com'),
+            'GeolocationHint' => array('geo:47.37328,8.531126', 'geo:19.34343,12.342514'),
+        ),
+        /* ... */
+    );
+
+The OASIS specification primarily defines how an IdP can communicate
+metadata related to IdP discovery. There are two different types of
+extensions defined. There are the `<mdui:UIInfo>`elements that define
+how an IdP should be displayed and there are the `<mdui:DiscoHints>`
+elements that define when an IdP should be choosen/displayed.
+
+UIInfo Items
+--------------
+
+These elements are used for IdP discovery to determine what to display
+about an IdP. These properties are all children of the `UIInfo` key.
+
+*Note*: Most elements are localized strings that specify the language
+using the array key as the language-code:
+
+            'DisplayName' => array(
+                'en' => 'English name',
+                'es' => 'Nombre en Español',
+            ),
+
+`DisplayName`
+:   The localized list of names for this IdP
+
+            'DisplayName' => array(
+                'en' => 'English name',
+                'es' => 'Nombre en Español',
+            ),
+
+`Description`
+:   The localized list of statements used to decribe this IdP
+
+            'Description' => array(
+                'en' => 'English description',
+                'es' => 'Descripción en Español',
+            ),
+
+`InformationURL`
+:   A localized list of URLs where more information about the IdP is
+    located.
+
+            'InformationURL' => array(
+                'en' => 'http://example.com/info/en',
+                'es' => 'http://example.com/info/es',
+            ),
+
+`PrivacyStatementURL`
+:   A localized list of URLs where the IdP's privacy statement is
+    located.
+
+            'PrivacyStatementURL' => array(
+                'en' => 'http://example.com/privacy/en',
+                'es' => 'http://example.com/privacy/es',
+            ),
+
+`Keywords`
+:   A localized list of keywords used to describe the IdP
+
+            'Keywords' => array(
+                'en' => array('communication', 'federated session'),
+                'es' => array('comunicación', 'sesión federated'),
+            ),
+
+:   *Note*: The `+` (plus) character is forbidden by specification from
+    being part of a Keyword.
+
+`Logo`
+:   The logos used to represent the IdP
+
+            'Logo' => array(
+                array(
+                    'url'    => 'http://example.com/logo1.png',
+                    'height' => 200,
+                    'width'  => 400,
+                    'lang'   => 'en',
+                ),
+                array(
+                    'url'    => 'http://example.com/logo2.png',
+                    'height' => 201,
+                    'width'  => 401,
+                ),
+            ),
+
+:   An optional `lang` key containing a language-code is supported for
+    localized Logos.
+
+DiscoHints Items
+--------------
+
+These elements are used for IdP discovery to determine when to choose or
+present an IdP. These properties are all children of the `DiscoHints`
+key.
+
+`IPHint`
+:   This is a list of both IPv4 and IPv6 addresses in CIDR notation
+    services by or associated with this entity.
+
+            'IPHint' => array('130.59.0.0/16', '2001:620::0/96'),
+
+`DomainHint`
+:   This specifies a list of domain names serviced by or associated with
+    this entity.
+
+            'DomainHint' => array('example.com', 'www.example.com'),
+
+`GeolocationHint`
+:   This specifies a list of geographic coordinates associated with, or
+    serviced by, the entity. Coordinates are given in URI form using the
+    geo URI scheme [RFC5870](http://www.ietf.org/rfc/rfc5870.txt).
+
+            'GeolocationHint' => array('geo:47.37328,8.531126', 'geo:19.34343,12.342514'),
+
+
+Generated XML Metadata Examples
+----------------
+
+If given the following configuration...
+
+    $metadata['https://www.example.com/saml/saml2/idp/metadata.php'] = array(
+        'host' => 'www.example.com',
+        'certificate' => 'server.crt',
+        'privatekey' => 'server.pem',
+        'auth' => 'example-userpass',
+
+        'UIInfo' => array(
+            'DisplayName' => array(
+                'en' => 'English name',
+                'es' => 'Nombre en Español',
+            ),
+            'Description' => array(
+                'en' => 'English description',
+                'es' => 'Descripción en Español',
+            ),
+            'InformationURL' => array(
+                'en' => 'http://example.com/info/en',
+                'es' => 'http://example.com/info/es',
+            ),
+            'PrivacyStatementURL' => array(
+                'en' => 'http://example.com/privacy/en',
+                'es' => 'http://example.com/privacy/es',
+            ),
+            'Keywords' => array(
+                'en' => array('communication', 'federated session'),
+                'es' => array('comunicación', 'sesión federated'),
+            ),
+            'Logo' => array(
+                array(
+                    'url'    => 'http://example.com/logo1.png',
+                    'height' => 200,
+                    'width'  => 400,
+                ),
+                array(
+                    'url'    => 'http://example.com/logo2.png',
+                    'height' => 201,
+                    'width'  => 401,
+                ),
+            ),
+        ),
+        'DiscoHints' => array(
+            'IPHint'          => array('130.59.0.0/16', '2001:620::0/96'),
+            'DomainHint'      => array('example.com', 'www.example.com'),
+            'GeolocationHint' => array('geo:47.37328,8.531126', 'geo:19.34343,12.342514'),
+        ),
+    );
+
+... will generate the following XML metadata:
+
+    <?xml version="1.0"?>
+    <md:EntityDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" xmlns:mdattr="urn:oasis:names:tc:SAML:metadata:attribute" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mdui="urn:oasis:names:tc:SAML:metadata:ui" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" entityID="https://www.example.com/saml/saml2/idp/metadata.php">
+      <md:IDPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
+        <md:Extensions>
+          <mdui:UIInfo xmlns:mdui="urn:oasis:names:tc:SAML:metadata:ui">
+            <mdui:DisplayName xml:lang="en">English name</mdui:DisplayName>
+            <mdui:DisplayName xml:lang="es">Nombre en Espa&#xF1;ol</mdui:DisplayName>
+            <mdui:Description xml:lang="en">English description</mdui:Description>
+            <mdui:Description xml:lang="es">Descripci&#xF3;n en Espa&#xF1;ol</mdui:Description>
+            <mdui:InformationURL xml:lang="en">http://example.com/info/en</mdui:InformationURL>
+            <mdui:InformationURL xml:lang="es">http://example.com/info/es</mdui:InformationURL>
+            <mdui:PrivacyStatementURL xml:lang="en">http://example.com/privacy/en</mdui:PrivacyStatementURL>
+            <mdui:PrivacyStatementURL xml:lang="es">http://example.com/privacy/es</mdui:PrivacyStatementURL>
+            <mdui:Keywords xml:lang="en">communication federated+session</mdui:Keywords>
+            <mdui:Keywords xml:lang="es">comunicaci&#xF3;n sesi&#xF3;n+federated</mdui:Keywords>
+            <mdui:Logo width="400" height="200" xml:lang="en">http://example.com/logo1.png</mdui:Logo>
+            <mdui:Logo width="401" height="201">http://example.com/logo2.png</mdui:Logo>
+          </mdui:UIInfo>
+          <mdui:DiscoHints xmlns:mdui="urn:oasis:names:tc:SAML:metadata:ui">
+            <mdui:IPHint>130.59.0.0/16</mdui:IPHint>
+            <mdui:IPHint>2001:620::0/96</mdui:IPHint>
+            <mdui:DomainHint>example.com</mdui:DomainHint>
+            <mdui:DomainHint>www.example.com</mdui:DomainHint>
+            <mdui:GeolocationHint>geo:47.37328,8.531126</mdui:GeolocationHint>
+            <mdui:GeolocationHint>geo:19.34343,12.342514</mdui:GeolocationHint>
+          </mdui:DiscoHints>
+        </md:Extensions>
+        <md:KeyDescriptor use="signing">
+          <ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
+            <ds:X509Data>
+            ...
+
diff --git a/docs/simplesamlphp-upgrade-notes-1.9.txt b/docs/simplesamlphp-upgrade-notes-1.9.txt
index 865abdd2923463ec638cc62e0d2b07c701c8194b..3f655b45e9da1e726de4c7b8c6568a338d1a2634 100644
--- a/docs/simplesamlphp-upgrade-notes-1.9.txt
+++ b/docs/simplesamlphp-upgrade-notes-1.9.txt
@@ -7,3 +7,4 @@ Upgrade notes for simpleSAMLphp 1.9
   * Access permissions of generated files are now restricted to the current user.
   * The code to set cookies now requires PHP version >= 5.2. (PHP version 5.2.0 or newer has been the only supported version for a while, but it has in some cases been possible to run simpleSAMLphp with older versions.)
   * It used to be possible to set an array of endpoints for the SingleSignOnService in `saml20-idp-hosted.php`. That is no longer supported.
+  * The `aselect` module has been replaced with a new module. The new module gives us better error handling and support for request signing, but we lose support for A-Select Cross.
diff --git a/lib/SAML2/XML/md/Extensions.php b/lib/SAML2/XML/md/Extensions.php
index 70d7d1cfdada248f4af09c1c473457ceb1d18c5e..d3d237acc2a25e10d8569aae5148a64d0f833a3c 100644
--- a/lib/SAML2/XML/md/Extensions.php
+++ b/lib/SAML2/XML/md/Extensions.php
@@ -24,6 +24,10 @@ class SAML2_XML_md_Extensions {
 				$ret[] = new SAML2_XML_mdattr_EntityAttributes($node);
 			} elseif ($node->namespaceURI === SAML2_XML_mdrpi_Common::NS_MDRPI && $node->localName === 'PublicationInfo') {
 				$ret[] = new SAML2_XML_mdrpi_PublicationInfo($node);
+			} elseif ($node->namespaceURI === SAML2_XML_mdui_UIInfo::NS && $node->localName === 'UIInfo') {
+				$ret[] = new SAML2_XML_mdui_UIInfo($node);
+			} elseif ($node->namespaceURI === SAML2_XML_mdui_DiscoHints::NS && $node->localName === 'DiscoHints') {
+				$ret[] = new SAML2_XML_mdui_DiscoHints($node);
 			} else {
 				$ret[] = new SAML2_XML_Chunk($node);
 			}
diff --git a/lib/SAML2/XML/mdui/DiscoHints.php b/lib/SAML2/XML/mdui/DiscoHints.php
new file mode 100644
index 0000000000000000000000000000000000000000..8ff510d96fb50a98f6994698248a9ee54bbea46e
--- /dev/null
+++ b/lib/SAML2/XML/mdui/DiscoHints.php
@@ -0,0 +1,106 @@
+<?php
+
+/**
+ * Class for handling the metadata extensions for login and discovery user interface
+ *
+ * @link: http://docs.oasis-open.org/security/saml/Post2.0/sstc-saml-metadata-ui/v1.0/sstc-saml-metadata-ui-v1.0.pdf
+ * @package simpleSAMLphp
+ * @version $Id$
+ */
+class SAML2_XML_mdui_DiscoHints {
+
+	/**
+	 * The namespace used for the DiscoHints extension.
+	 */
+	const NS = 'urn:oasis:names:tc:SAML:metadata:ui';
+
+
+	/**
+	 * Array with child elements.
+	 *
+	 * The elements can be any of the other SAML2_XML_mdui_* elements.
+	 *
+	 * @var array
+	 */
+	public $children = array();
+
+
+	/**
+	 * The IPHint, as an array of strings.
+	 *
+	 * @var array
+	 */
+	public $IPHint = array();
+
+
+	/**
+	 * The DomainHint, as an array of strings.
+	 *
+	 * @var array
+	 */
+	public $DomainHint = array();
+
+
+	/**
+	 * The GeolocationHint, as an array of strings.
+	 *
+	 * @var array
+	 */
+	public $GeolocationHint = array();
+
+	/**
+	 * Create a DiscoHints element.
+	 *
+	 * @param DOMElement|NULL $xml  The XML element we should load.
+	 */
+	public function __construct(DOMElement $xml = NULL) {
+
+		if ($xml === NULL) {
+			return;
+		}
+
+		$this->IPHint =          SAML2_Utils::extractStrings($xml, self::NS, 'IPHint');
+		$this->DomainHint =      SAML2_Utils::extractStrings($xml, self::NS, 'DomainHint');
+		$this->GeolocationHint = SAML2_Utils::extractStrings($xml, self::NS, 'GeolocationHint');
+
+		foreach (SAML2_Utils::xpQuery($xml, "./*[namespace-uri()!='".self::NS."']") as $node) {
+			$this->children[] = new SAML2_XML_Chunk($node);
+		}
+	}
+
+
+	/**
+	 * Convert this DiscoHints to XML.
+	 *
+	 * @param DOMElement $parent  The element we should append to.
+	 */
+	public function toXML(DOMElement $parent) {
+		assert('is_array($this->IPHint)');
+		assert('is_array($this->DomainHint)');
+		assert('is_array($this->GeolocationHint)');
+		assert('is_array($this->children)');
+
+		if (!empty($this->IPHint)
+		 || !empty($this->DomainHint)
+		 || !empty($this->GeolocationHint)
+		 || !empty($this->children)) {
+			$doc = $parent->ownerDocument;
+
+			$e = $doc->createElementNS(self::NS, 'mdui:DiscoHints');
+			$parent->appendChild($e);
+
+			if (!empty($this->children)) {
+				foreach ($this->children as $child) {
+					$child->toXML($e);
+				}
+			}
+
+			SAML2_Utils::addStrings($e, self::NS, 'mdui:IPHint',          FALSE, $this->IPHint);
+			SAML2_Utils::addStrings($e, self::NS, 'mdui:DomainHint',      FALSE, $this->DomainHint);
+			SAML2_Utils::addStrings($e, self::NS, 'mdui:GeolocationHint', FALSE, $this->GeolocationHint);
+
+			return $e;
+		}
+	}
+
+}
diff --git a/lib/SAML2/XML/mdui/Keywords.php b/lib/SAML2/XML/mdui/Keywords.php
new file mode 100644
index 0000000000000000000000000000000000000000..0cc2893ea8629fa0dc0601570a25d06c7cfecbba
--- /dev/null
+++ b/lib/SAML2/XML/mdui/Keywords.php
@@ -0,0 +1,79 @@
+<?php
+
+/**
+ * Class for handling the Keywords metadata extensions for login and discovery user interface
+ *
+ * @link: http://docs.oasis-open.org/security/saml/Post2.0/sstc-saml-metadata-ui/v1.0/sstc-saml-metadata-ui-v1.0.pdf
+ * @package simpleSAMLphp
+ * @version $Id$
+ */
+class SAML2_XML_mdui_Keywords {
+
+	/**
+	 * The keywords of this item.
+	 *
+	 * @var string
+	 */
+	public $Keywords;
+
+
+	/**
+	 * The language of this item.
+	 *
+	 * @var string
+	 */
+	public $lang;
+
+
+	/**
+	 * Initialize a Keywords.
+	 *
+	 * @param DOMElement|NULL $xml  The XML element we should load.
+	 */
+	public function __construct(DOMElement $xml = NULL) {
+
+		if ($xml === NULL) {
+			return;
+		}
+
+		if (!$xml->hasAttribute('xml:lang')) {
+			throw new Exception('Missing lang on Keywords.');
+		}
+		if (!is_string($xml->textContent) || !strlen($xml->textContent)) {
+			throw new Exception('Missing value for Keywords.');
+		}
+		$this->Keywords = array();
+		foreach (explode(' ', $xml->textContent) as $keyword) {
+			$this->Keywords[] = str_replace('+', ' ', $keyword);
+		}
+		$this->lang = $xml->getAttribute('xml:lang');
+	}
+
+
+	/**
+	 * Convert this Keywords to XML.
+	 *
+	 * @param DOMElement $parent  The element we should append this Keywords to.
+	 */
+	public function toXML(DOMElement $parent) {
+		assert('is_string($this->lang)');
+		assert('is_array($this->Keywords)');
+
+		$doc = $parent->ownerDocument;
+
+		$e = $doc->createElementNS(SAML2_XML_mdui_UIInfo::NS, 'mdui:Keywords');
+		$e->setAttribute('xml:lang', $this->lang);
+		$e->nodeValue = '';
+		foreach ($this->Keywords as $keyword) {
+			if (strpos($keyword, "+") !== false) {
+				throw new Exception('Keywords may not contain a "+" character.');
+			}
+			$e->nodeValue .= str_replace(' ', '+', $keyword) . ' ';
+		}
+		$e->nodeValue = rtrim($e->nodeValue);
+		$parent->appendChild($e);
+
+		return $e;
+	}
+
+}
diff --git a/lib/SAML2/XML/mdui/Logo.php b/lib/SAML2/XML/mdui/Logo.php
new file mode 100644
index 0000000000000000000000000000000000000000..1e327a1db0995f9b58c8bb615a61d24a3fb09eb7
--- /dev/null
+++ b/lib/SAML2/XML/mdui/Logo.php
@@ -0,0 +1,94 @@
+<?php
+
+/**
+ * Class for handling the Logo metadata extensions for login and discovery user interface
+ *
+ * @link: http://docs.oasis-open.org/security/saml/Post2.0/sstc-saml-metadata-ui/v1.0/sstc-saml-metadata-ui-v1.0.pdf
+ * @package simpleSAMLphp
+ * @version $Id$
+ */
+class SAML2_XML_mdui_Logo {
+
+	/**
+	 * The url of this logo.
+	 *
+	 * @var string
+	 */
+	public $url;
+
+
+	/**
+	 * The width of this logo.
+	 *
+	 * @var string
+	 */
+	public $width;
+
+
+	/**
+	 * The height of this logo.
+	 *
+	 * @var string
+	 */
+	public $height;
+
+	/**
+	 * The language of this item.
+	 *
+	 * @var string
+	 */
+	public $lang;
+
+
+	/**
+	 * Initialize a Logo.
+	 *
+	 * @param DOMElement|NULL $xml  The XML element we should load.
+	 */
+	public function __construct(DOMElement $xml = NULL) {
+
+		if ($xml === NULL) {
+			return;
+		}
+
+		if (!$xml->hasAttribute('width')) {
+			throw new Exception('Missing width of Logo.');
+		}
+		if (!$xml->hasAttribute('height')) {
+			throw new Exception('Missing height of Logo.');
+		}
+		if (!is_string($xml->textContent) || !strlen($xml->textContent)) {
+			throw new Exception('Missing url value for Logo.');
+		}
+		$this->url = $xml->textContent;
+		$this->width = (int)$xml->getAttribute('width');
+		$this->height = (int)$xml->getAttribute('height');
+		$this->lang = $xml->hasAttribute('xml:lang') ? $xml->getAttribute('xml:lang') : NULL;
+	}
+
+
+	/**
+	 * Convert this Logo to XML.
+	 *
+	 * @param DOMElement $parent  The element we should append this Logo to.
+	 */
+	public function toXML(DOMElement $parent) {
+		assert('is_int($this->width)');
+		assert('is_int($this->height)');
+		assert('is_string($this->url)');
+
+		$doc = $parent->ownerDocument;
+
+		$e = $doc->createElementNS(SAML2_XML_mdui_UIInfo::NS, 'mdui:Logo');
+		$e->nodeValue = $this->url;
+		$e->setAttribute('width', (int)$this->width);
+		$e->setAttribute('height', (int)$this->height);
+		if (isset($this->lang)) {
+			$e->setAttribute('xml:lang', $this->lang);
+		}
+		$parent->appendChild($e);
+
+		return $e;
+	}
+
+}
diff --git a/lib/SAML2/XML/mdui/UIInfo.php b/lib/SAML2/XML/mdui/UIInfo.php
new file mode 100644
index 0000000000000000000000000000000000000000..2023abe73884aa2d68b20858c65531cff5e0b60f
--- /dev/null
+++ b/lib/SAML2/XML/mdui/UIInfo.php
@@ -0,0 +1,154 @@
+<?php
+
+/**
+ * Class for handling the metadata extensions for login and discovery user interface
+ *
+ * @link: http://docs.oasis-open.org/security/saml/Post2.0/sstc-saml-metadata-ui/v1.0/sstc-saml-metadata-ui-v1.0.pdf
+ * @package simpleSAMLphp
+ * @version $Id$
+ */
+class SAML2_XML_mdui_UIInfo {
+
+	/**
+	 * The namespace used for the UIInfo extension.
+	 */
+	const NS = 'urn:oasis:names:tc:SAML:metadata:ui';
+
+
+	/**
+	 * Array with child elements.
+	 *
+	 * The elements can be any of the other SAML2_XML_mdui_* elements.
+	 *
+	 * @var array
+	 */
+	public $children = array();
+
+	/**
+	 * The DisplayName, as an array of language => translation.
+	 *
+	 * @var array
+	 */
+	public $DisplayName = array();
+
+	/**
+	 * The Description, as an array of language => translation.
+	 *
+	 * @var array
+	 */
+	public $Description = array();
+
+	/**
+	 * The InformationURL, as an array of language => url.
+	 *
+	 * @var array
+	 */
+	public $InformationURL = array();
+
+	/**
+	 * The PrivacyStatementURL, as an array of language => url.
+	 *
+	 * @var array
+	 */
+	public $PrivacyStatementURL = array();
+
+	/**
+	 * The Keywords, as an array of language => array of strings.
+	 *
+	 * @var array
+	 */
+	public $Keywords = array();
+
+	/**
+	 * The Logo, as an array of associative arrays containing url, width, height, and optional lang.
+	 *
+	 * @var array
+	 */
+	public $Logo = array();
+
+
+	/**
+	 * Create a UIInfo element.
+	 *
+	 * @param DOMElement|NULL $xml  The XML element we should load.
+	 */
+	public function __construct(DOMElement $xml = NULL) {
+
+		if ($xml === NULL) {
+			return;
+		}
+
+		$this->DisplayName         = SAML2_Utils::extractLocalizedStrings($xml, self::NS, 'DisplayName');
+		$this->Description         = SAML2_Utils::extractLocalizedStrings($xml, self::NS, 'Description');
+		$this->InformationURL      = SAML2_Utils::extractLocalizedStrings($xml, self::NS, 'InformationURL');
+		$this->PrivacyStatementURL = SAML2_Utils::extractLocalizedStrings($xml, self::NS, 'PrivacyStatementURL');
+
+		foreach (SAML2_Utils::xpQuery($xml, './*') as $node) {
+			if ($node->namespaceURI === self::NS) {
+				switch ($node->localName) {
+					case 'Keywords':
+						$this->Keywords[] = new SAML2_XML_mdui_Keywords($node);
+					break;
+					case 'Logo':
+						$this->Logo[] = new SAML2_XML_mdui_Logo($node);
+					break;
+				}
+			} else {
+				$this->children[] = new SAML2_XML_Chunk($node);
+			}
+		}
+	}
+
+
+	/**
+	 * Convert this UIInfo to XML.
+	 *
+	 * @param DOMElement $parent  The element we should append to.
+	 */
+	public function toXML(DOMElement $parent) {
+		assert('is_array($this->DisplayName)');
+		assert('is_array($this->InformationURL)');
+		assert('is_array($this->PrivacyStatementURL)');
+		assert('is_array($this->Keywords)');
+		assert('is_array($this->Logo)');
+		assert('is_array($this->children)');
+
+		if (!empty($this->DisplayName)
+		 || !empty($this->Description)
+		 || !empty($this->InformationURL)
+		 || !empty($this->PrivacyStatementURL)
+		 || !empty($this->Keywords)
+		 || !empty($this->Logo)
+		 || !empty($this->children)) {
+			$doc = $parent->ownerDocument;
+
+			$e = $doc->createElementNS(self::NS, 'mdui:UIInfo');
+			$parent->appendChild($e);
+
+			SAML2_Utils::addStrings($e, self::NS, 'mdui:DisplayName',         TRUE, $this->DisplayName);
+			SAML2_Utils::addStrings($e, self::NS, 'mdui:Description',         TRUE, $this->Description);
+			SAML2_Utils::addStrings($e, self::NS, 'mdui:InformationURL',      TRUE, $this->InformationURL);
+			SAML2_Utils::addStrings($e, self::NS, 'mdui:PrivacyStatementURL', TRUE, $this->PrivacyStatementURL);
+
+			if (!empty($this->Keywords)) {
+				foreach ($this->Keywords as $child) {
+					$child->toXML($e);
+				}
+			}
+
+			if (!empty($this->Logo)) {
+				foreach ($this->Logo as $child) {
+					$child->toXML($e);
+				}
+			}
+
+			if (!empty($this->children)) {
+				foreach ($this->children as $child) {
+					$child->toXML($e);
+				}
+			}
+		}
+		return $e;
+	}
+
+}
diff --git a/lib/SimpleSAML/Metadata/SAMLBuilder.php b/lib/SimpleSAML/Metadata/SAMLBuilder.php
index 3373d0f64987df7419d88c73ae0a84efd071dda8..5d21c78cc416f552c6693b9e3ea704f319f08164 100644
--- a/lib/SimpleSAML/Metadata/SAMLBuilder.php
+++ b/lib/SimpleSAML/Metadata/SAMLBuilder.php
@@ -118,6 +118,88 @@ class SimpleSAML_Metadata_SAMLBuilder {
 				$e->Extensions[] = $s;
 			}
 		}
+
+		if ($metadata->hasValue('EntityAttributes')) {
+			$ea = new SAML2_XML_mdattr_EntityAttributes();
+			foreach ($metadata->getArray('EntityAttributes') as $attributeName => $attributeValues) {
+				$a = new SAML2_XML_saml_Attribute();
+				$a->Name = $attributeName;
+				$a->NameFormat = 'urn:oasis:names:tc:SAML:2.0:attrname-format:uri';
+
+				// Attribute names that is not URI is prefixed as this: '{nameformat}name'
+				if (preg_match('/^\{(.*?)\}(.*)$/', $attributeName, $matches)) {
+					$a->Name = $matches[2];
+					$nameFormat = $matches[1];
+					if ($nameFormat !== SAML2_Const::NAMEFORMAT_UNSPECIFIED) {
+						$a->NameFormat = $nameFormat;
+					}
+				}
+				foreach ($attributeValues as $attributeValue) {
+					$a->AttributeValue[] = new SAML2_XML_saml_AttributeValue($attributeValue);
+				}
+				$ea->children[] = $a;
+			}
+			$this->entityDescriptor->Extensions[] = $ea;
+		}
+
+		if ($metadata->hasValue('UIInfo')) {
+			$ui = new SAML2_XML_mdui_UIInfo();
+			foreach ($metadata->getArray('UIInfo') as $uiName => $uiValues) {
+				switch ($uiName) {
+					case 'DisplayName':
+						$ui->DisplayName = $uiValues;
+					break;
+					case 'Description':
+						$ui->Description = $uiValues;
+					break;
+					case 'InformationURL':
+						$ui->InformationURL = $uiValues;
+					break;
+					case 'PrivacyStatementURL':
+						$ui->PrivacyStatementURL = $uiValues;
+					break;
+					case 'Keywords':
+						foreach ($uiValues as $lang => $keywords) {
+							$uiItem = new SAML2_XML_mdui_Keywords();
+							$uiItem->lang = $lang;
+							$uiItem->Keywords = $keywords;
+							$ui->Keywords[] = $uiItem;
+						}
+					break;
+					case 'Logo':
+						foreach ($uiValues as $logo) {
+							$uiItem = new SAML2_XML_mdui_Logo();
+							$uiItem->url    = $logo['url'];
+							$uiItem->width  = $logo['width'];
+							$uiItem->height = $logo['height'];
+							if (isset($logo['lang'])) {
+								$uiItem->lang = $logo['lang'];
+							}
+							$ui->Logo[] = $uiItem;
+						}
+					break;
+				}
+			}
+			$e->Extensions[] = $ui;
+		}
+
+		if ($metadata->hasValue('DiscoHints')) {
+			$dh = new SAML2_XML_mdui_DiscoHints();
+			foreach ($metadata->getArray('DiscoHints') as $dhName => $dhValues) {
+				switch ($dhName) {
+					case 'IPHint':
+						$dh->IPHint = $dhValues;
+					break;
+					case 'DomainHint':
+						$dh->DomainHint = $dhValues;
+					break;
+					case 'GeolocationHint':
+						$dh->GeolocationHint = $dhValues;
+					break;
+				}
+			}
+			$e->Extensions[] = $dh;
+		}
 	}
 
 
diff --git a/lib/SimpleSAML/Metadata/SAMLParser.php b/lib/SimpleSAML/Metadata/SAMLParser.php
index f5e089d5568d3ff767a4af6cd8bd129a37bca638..8822451abd3cd5cbf76bcdfbf0d1d16953718e3f 100644
--- a/lib/SimpleSAML/Metadata/SAMLParser.php
+++ b/lib/SimpleSAML/Metadata/SAMLParser.php
@@ -97,6 +97,8 @@ class SimpleSAML_Metadata_SAMLParser {
 	private $entityAttributes;
 	private $attributes;
 	private $tags;
+	private $uiInfo;
+	private $discoHints;
 
 
 	/**
@@ -427,7 +429,14 @@ class SimpleSAML_Metadata_SAMLParser {
 		if (!empty($this->entityAttributes)) {
 			$metadata['EntityAttributes'] = $this->entityAttributes;
 		}
-		
+
+		if (!empty($roleDescriptor['UIInfo'])) {
+			$metadata['UIInfo'] = $roleDescriptor['UIInfo'];
+		}
+
+		if (!empty($roleDescriptor['DiscoHints'])) {
+			$metadata['DiscoHints'] = $roleDescriptor['DiscoHints'];
+		}
 	}
 
 
@@ -739,6 +748,8 @@ class SimpleSAML_Metadata_SAMLParser {
 		$ret['scope'] = $ext['scope'];
 		$ret['tags'] = $ext['tags'];
 		$ret['EntityAttributes'] = $ext['EntityAttributes'];
+		$ret['UIInfo'] = $ext['UIInfo'];
+		$ret['DiscoHints'] = $ext['DiscoHints'];
 
 		return $ret;
 	}
@@ -861,6 +872,8 @@ class SimpleSAML_Metadata_SAMLParser {
 			'scope' => array(),
 			'tags' => array(),
 			'EntityAttributes' => array(),
+			'UIInfo' => array(),
+			'DiscoHints' => array(),
 		);
 
 		foreach ($element->Extensions as $e) {
@@ -869,7 +882,7 @@ class SimpleSAML_Metadata_SAMLParser {
 				$ret['scope'][] = $e->scope;
 				continue;
 			}
-			
+
 			// Entity Attributes are only allowed at entity level extensions
 			// and not at RoleDescriptor level
 			if ($element instanceof SAML2_XML_md_EntityDescriptor) {
@@ -901,7 +914,52 @@ class SimpleSAML_Metadata_SAMLParser {
 					}
 				}				
 			}
-			
+
+			// UIInfo elements are only allowed at RoleDescriptor level extensions
+			if ($element instanceof SAML2_XML_md_RoleDescriptor) {
+
+				if ($e instanceof SAML2_XML_mdui_UIInfo) {
+
+					$ret['UIInfo']['DisplayName']         = $e->DisplayName;
+					$ret['UIInfo']['Description']         = $e->Description;
+					$ret['UIInfo']['InformationURL']      = $e->InformationURL;
+					$ret['UIInfo']['PrivacyStatementURL'] = $e->PrivacyStatementURL;
+
+					foreach($e->Keywords as $uiItem) {
+						if (!($uiItem instanceof SAML2_XML_mdui_Keywords)
+						 || empty($uiItem->Keywords)
+						 || empty($uiItem->lang))
+							continue;
+						$ret['UIInfo']['Keywords'][$uiItem->lang] = $uiItem->Keywords;
+					}
+					foreach($e->Logo as $uiItem) {
+						if (!($uiItem instanceof SAML2_XML_mdui_Logo)
+						 || empty($uiItem->url)
+						 || empty($uiItem->height)
+						 || empty($uiItem->width))
+							continue;
+						$logo = array(
+							'url'    => $uiItem->url,
+							'height' => $uiItem->height,
+							'width'  => $uiItem->width,
+						);
+						if (!empty($uiItem->Lang)) {
+							$logo['lang'] = $uiItem->lang;
+						}
+						$ret['UIInfo']['Logo'][] = $logo;
+					}
+				}
+			}
+
+			// DiscoHints elements are only allowed at IDPSSODescriptor level extensions
+			if ($element instanceof SAML2_XML_md_IDPSSODescriptor) {
+
+				if ($e instanceof SAML2_XML_mdui_DiscoHints) {
+					$ret['DiscoHints']['IPHint']          = $e->IPHint;
+					$ret['DiscoHints']['DomainHint']      = $e->DomainHint;
+					$ret['DiscoHints']['GeolocationHint'] = $e->GeolocationHint;
+				}
+			}
 
 
 			if (!($e instanceof SAML2_XML_Chunk)) {
diff --git a/lib/SimpleSAML/Session.php b/lib/SimpleSAML/Session.php
index 49339247ff39250c8c21c419ff3db77c443561a1..74390edfdd03a57702b4828602336005d498b10a 100644
--- a/lib/SimpleSAML/Session.php
+++ b/lib/SimpleSAML/Session.php
@@ -45,6 +45,14 @@ class SimpleSAML_Session {
 	private $sessionId;
 
 
+	/**
+	 * Transient session flag.
+	 *
+	 * @var boolean|FALSE
+	 */
+	private $transient = FALSE;
+
+
 	/**
 	 * The track id is a new random unique identifier that is generate for each session.
 	 * This is used in the debug logs and error messages to easily track more information
@@ -150,6 +158,7 @@ class SimpleSAML_Session {
 
 		if ($transient) {
 			$this->trackid = 'XXXXXXXXXX';
+			$this->transient = TRUE;
 			return;
 		}
 
@@ -249,14 +258,21 @@ class SimpleSAML_Session {
 		try {
 			self::$instance = self::getSession();
 		} catch (Exception $e) {
+			/* For some reason, we were unable to initialize this session. Use a transient session instead. */
+			self::useTransientSession();
+
+			$globalConfig = SimpleSAML_Configuration::getInstance();
+			if ($globalConfig->getBoolean('session.disable_fallback', FALSE) === TRUE) {
+				throw $e;
+			}
+
 			if ($e instanceof SimpleSAML_Error_Exception) {
 				SimpleSAML_Logger::error('Error loading session:');
 				$e->logError();
 			} else {
 				SimpleSAML_Logger::error('Error loading session: ' . $e->getMessage());
 			}
-			/* For some reason, we were unable to initialize this session. Use a transient session instead. */
-			self::useTransientSession();
+
 			return self::$instance;
 		}
 
@@ -299,6 +315,16 @@ class SimpleSAML_Session {
 	}
 
 
+	/**
+	 * Retrieve if session is transient.
+	 *
+	 * @return boolean  The session transient flag.
+	 */
+	public function isTransient() {
+		return $this->transient;
+	}
+
+
 	/**
 	 * Get a unique ID that will be permanent for this session.
 	 * Used for debugging and tracing log files related to a session.
diff --git a/lib/SimpleSAML/Utilities.php b/lib/SimpleSAML/Utilities.php
index f2c7144460fa363987982af71d971a53c6ee8e74..e6c80c4b3e01e8aa00a301daf31245aa55c8f663 100644
--- a/lib/SimpleSAML/Utilities.php
+++ b/lib/SimpleSAML/Utilities.php
@@ -128,7 +128,11 @@ class SimpleSAML_Utilities {
 	 */
 	private static function getServerPort() {
 
-		$portnumber = $_SERVER["SERVER_PORT"];
+		if (isset($_SERVER["SERVER_PORT"])) {
+			$portnumber = $_SERVER["SERVER_PORT"];
+		} else {
+			$portnumber = 80;
+		}
 		$port = ':' . $portnumber;
 
 		if (self::getServerHTTPS()) {
@@ -2179,19 +2183,24 @@ class SimpleSAML_Utilities {
 		// Data and headers.
 		if ($getHeaders) {
 
-			$headers = array();
-
-			foreach($http_response_header as $h) {
-				if(preg_match('@^HTTP/1\.[01]\s+\d{3}\s+@', $h)) {
-					$headers = array(); // reset
-					$headers[0] = $h;
-					continue;
-				}
-				$bits = explode(':', $h, 2);
-				if(count($bits) === 2) {
-					$headers[strtolower($bits[0])] = trim($bits[1]);
+			if (isset($http_response_header)) {
+				$headers = array();
+				foreach($http_response_header as $h) {
+					if(preg_match('@^HTTP/1\.[01]\s+\d{3}\s+@', $h)) {
+						$headers = array(); // reset
+						$headers[0] = $h;
+						continue;
+					}
+					$bits = explode(':', $h, 2);
+					if(count($bits) === 2) {
+						$headers[strtolower($bits[0])] = trim($bits[1]);
+					}
 				}
+			} else {
+				/* No HTTP headers - probably a different protocol, e.g. file. */
+				$headers = NULL;
 			}
+
 			return array($data, $headers);
 		}
 
diff --git a/lib/SimpleSAML/XHTML/EMail.php b/lib/SimpleSAML/XHTML/EMail.php
index 4c5385e5ef2d1ee1d907add08fe93ed421279b5c..f167413895ef22f6a1b9da5421d09f6768aa8d77 100644
--- a/lib/SimpleSAML/XHTML/EMail.php
+++ b/lib/SimpleSAML/XHTML/EMail.php
@@ -90,7 +90,7 @@ Content-Transfer-Encoding: 8bit
 
 --simplesamlphp-' . $random_hash . '--
 ';
-		$headers = join("\r\n", $this->headers);
+		$headers = implode("\n", $this->headers);
 
 		$mail_sent = @mail($this->to, $this->subject, $message, $headers);
 		SimpleSAML_Logger::debug('Email: Sending e-mail to [' . $this->to . '] : ' . ($mail_sent ? 'OK' : 'Failed'));
diff --git a/modules/aselect/docs/aselect.txt b/modules/aselect/docs/aselect.txt
index 9e3f91cb1c70b56ade5840c7737923695470a911..756798e12fc3a23eb183a483b7e9f1eba29d816d 100644
--- a/modules/aselect/docs/aselect.txt
+++ b/modules/aselect/docs/aselect.txt
@@ -1,64 +1,44 @@
-Using the A-Select authentication source with simpleSAMLphp
-===========================================================
+A-Select module for simpleSAMLphp
+---------------------------------
 
-This authentication source for A-Select is based on the a-select
-handler by Hans Zandbelt. The original source combines the possibility
-to use an A-Select server as an authentication source and the possibility
-to use simpleSAMLphp as an A-Select server. This module only acts as a
-authentication source. Signing is not (yet) supported.
+This module allows one to use an A-Select server as authentication
+source for simpleSAMLphp.
 
-The structure applied follows the structure of the CAS authentication source.
+The module supports the A-Select protocol, including signing of
+requests. Not supported is A-Select Cross.
 
+Usage:
 
-Setting up the A-Select authentication module
-----------------------------------------------
+Enable the module if not already enabled:
+$ touch modules/aselect/enabled
 
-The first thing you need to do is to enable the aselect module:
+In config/authsources.php, configure your A-Selectserver as an
+authentication source. The following is an example for a source
+named 'aselect':
 
-    touch modules/aselect/enable
-
-The A-Select authentication module has two modes of operation.
-
-1. The module can act to the A-Select server as an application.
-
-Configuration in A-Select:
-
-        <application id="app1" level="30">
-            <attribute_policy>policyA</attribute_policy>
-            <forced_authenticate>false</forced_authenticate>
-        </application>
-
-Configuration in authsources.php
-
-    'example-aselect' => array(
+    'aselect' => array(
         'aselect:aselect',
-        'serverurl'            => 'http://a-select.dev.han.nl:8080/aselectserver/server',
-        'serverid'             => 'hanaselect',
-        'type'                 => 'app',                   # type = app/cross
-        'app_id'               => 'app1',                  # only if type = app
+        'app_id' => 'simplesamlphp',
+        'server_id' => 'sso.example.com',
+        'server_url' => 'https://test.sso.example.com/server',
+        'private_key' => 'file:///etc/ssl/private/aselect.key'
     ),
 
-2. The module can act to the A-Select server as cross A-Select.
+The parameters:
+- app_id: the application I for simpleSAMLphp as configured in
+  your A-Select server;
+- server_id: the A-Select server ID as configured in your
+  A-Select server;
+- server_url: the URL for your A-Selectserver, usually ends in
+  '/server/.
+- private_key: the key you want to use for signing requests.
+  If you're really sure you do not want request signing, you
+  can set this option to a null value.
+Options 'serverurl' and 'serverid' (without underscore) are
+supported for backwards compatibility.
 
-Configuration in A-Select:
-
-    <cross_aselect>
-        <local_servers require_signing="false">
-            <organization id="simpleSAMLphp" server="sso.testorg.com" attribute_policy="policyA">
-            </organization>
-        </local_servers>
-    </cross_aselect>
-
-
-Configuration in authsources.php
-
-    'example-aselect' => array(
-        'aselect:aselect',
-        'serverurl'            => 'http://a-select.dev.han.nl:8080/aselectserver/server',
-        'serverid'             => 'hanaselect',
-        'type'                 => 'cross',                 # type = app/cross
-        'local_organization'   => 'simpleSAMLphp',         # only if type = cross
-        'required_level'       => 10,                      # only if type = cross, defaults to 10
-    ),
+Author: Wessel Dankers <wsl@uvt.nl>
 
+Copyright: © 2011,2012 Tilburg University (http://www.tilburguniversity.edu)
 
+License: GPL version 3 or any later version.
diff --git a/modules/aselect/lib/Auth/Source/aselect.php b/modules/aselect/lib/Auth/Source/aselect.php
index 2cba46470614d2277d8ccc65bb4536a501cade3d..c7cb88d2f789b7a370a6c8924c760dc4ee8bf531 100644
--- a/modules/aselect/lib/Auth/Source/aselect.php
+++ b/modules/aselect/lib/Auth/Source/aselect.php
@@ -1,30 +1,15 @@
 <?php
 
 /**
- * A-Select authentication source.
+ * Authentication module which acts as an A-Select client
  *
- * Based on www/aselect/handler.php by Hans Zandbelt, SURFnet BV. <hans.zandbelt@surfnet.nl>
- *
- * @author Patrick Honing, Hogeschool van Arnhem en Nijmegen. <Patrick.Honing@han.nl>
- * @package simpleSAMLphp
- * @version $Id$
+ * @author Wessel Dankers, Tilburg University
  */
 class sspmod_aselect_Auth_Source_aselect extends SimpleSAML_Auth_Source {
-
-	/**
-	 * The string used to identify our states.
-	 */
-	const STAGE_INIT = 'aselect:init';
-
-	/**
-	 * The key of the AuthId field in the state.
-	 */
-	const AUTHID = 'aselect:AuthId';
-
-	/**
-	 * @var array with aselect configuration
-	 */
-	private $asconfig;
+	private $app_id = 'simplesamlphp';
+	private $server_id;
+	private $server_url;
+	private $private_key;
 
 	/**
 	 * Constructor for this authentication source.
@@ -33,142 +18,186 @@ class sspmod_aselect_Auth_Source_aselect extends SimpleSAML_Auth_Source {
 	 * @param array $config  Configuration.
 	 */
 	public function __construct($info, $config) {
-		assert('is_array($info)');
-		assert('is_array($config)');
-
 		/* Call the parent constructor first, as required by the interface. */
 		parent::__construct($info, $config);
 
-		if (!array_key_exists('serverurl', $config)) throw new Exception('aselect serverurl not specified');
-		$this->asconfig['serverurl'] = $config['serverurl'];
+		$cfg = SimpleSAML_Configuration::loadFromArray($config,
+			'Authentication source ' . var_export($this->authId, true));
 
-		if (!array_key_exists('serverid', $config)) throw new Exception('aselect serverid not specified');
-		$this->asconfig['serverid'] = $config['serverid'];
+		$cfg->getValueValidate('type', array('app'), 'app');
+		$this->app_id = $cfg->getString('app_id');
+		$this->private_key = $cfg->getString('private_key', null);
 
-		if (!array_key_exists('type', $config)) throw new Exception('aselect type not specified');
-		$this->asconfig['type'] = $config['type'];
+		// accept these arguments with '_' for consistency
+		// accept these arguments without '_' for backwards compatibility
+		$this->server_id = $cfg->getString('serverid', null);
+		if($this->server_id === null)
+			$this->server_id = $cfg->getString('server_id');
 
-		if ($this->asconfig['type'] == 'app') {
-			if (!array_key_exists('app_id', $config)) throw new Exception('aselect app_id not specified');
-			$this->asconfig['app_id'] = $config['app_id'];
-		} elseif($this->asconfig['type'] == 'cross') {
-			if (!array_key_exists('local_organization', $config)) throw new Exception('aselect local_organization not specified');
-			$this->asconfig['local_organization'] = $config['local_organization'];
+		$this->server_url = $cfg->getString('serverurl', null);
+		if($this->server_url === null)
+			$this->server_url = $cfg->getString('server_url');
+	}
 
-			$this->asconfig['required_level'] = (array_key_exists('required_level', $config)) ? $config['required_level'] : 10;
-		} else {
-			throw new Exception('aselect type need to be either app or cross');
-		}
+	/**
+	 * Initiate authentication.
+	 *
+	 * @param array &$state  Information about the current authentication.
+	 */
+	public function authenticate(&$state) {
+		$state['aselect::authid'] = $this->authId;
+		$id = SimpleSAML_Auth_State::saveState($state, 'aselect:login', true);
+
+		try {
+			$app_url = SimpleSAML_Module::getModuleURL('aselect/credentials.php', array('ssp_state' => $id));
+			$as_url = $this->request_authentication($app_url);
 
+			SimpleSAML_Utilities::redirect($as_url);
+		} catch(Exception $e) {
+			// attach the exception to the state
+			SimpleSAML_Auth_State::throwException($state, $e);
+		}
 	}
 
+	/**
+	 * Sign a string using the configured private key
+	 *
+	 * @param string $str  The string to calculate a signature for
+	 */
+	private function base64_signature($str) {
+		$key = openssl_pkey_get_private($this->private_key);
+		if($key === false)
+			throw new SimpleSAML_Error_Exception("Unable to load private key: ".openssl_error_string());
+		if(!openssl_sign($str, $sig, $key))
+			throw new SimpleSAML_Error_Exception("Unable to create signature: ".openssl_error_string());
+		openssl_pkey_free($key);
+		return base64_encode($sig);
+	}
 
-	// helper function for sending a non-browser request to a remote server
-	function as_call($url) {
-		$ch = curl_init();
-		curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
-		curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
-		curl_setopt($ch, CURLOPT_URL, $url);
-		$result = curl_exec($ch);
-		$error = curl_error($ch);
-		curl_close($ch);
-		if ($result == FALSE) {
-			throw new Exception('Request on remote server failed: ' . $error);
-		}
-		$parms = array();
-		foreach (explode('&', $result) as $parm) {
-			$tuple = explode('=', $parm);
-			$parms[urldecode($tuple[0])] = urldecode($tuple[1]);
-		}
-		if ($parms['result_code'] != '0000') {
-			throw new Exception('Request on remote server returned error: ' . $result);
+	/**
+	 * Parse a base64 encoded attribute blob. Can't use parse_str() because it
+	 * may contain multi-valued attributes.
+	 *
+	 * @param string $base64  The base64 string to decode.
+	 */
+	private static function decode_attributes($base64) {
+		$blob = base64_decode($base64, true);
+		if($blob === false)
+			throw new SimpleSAML_Error_Exception("Attributes parameter base64 malformed");
+		$pairs = explode('&', $blob);
+		$ret = array();
+		foreach($pairs as $pair) {
+			$keyval = explode('=', $pair, 2);
+			if(count($keyval) < 2)
+				throw new SimpleSAML_Error_Exception("Missing value in attributes parameter");
+			$key = urldecode($keyval[0]);
+			$val = urldecode($keyval[1]);
+			$ret[$key][] = $val;
 		}
-		return $parms;
+		return $ret;
 	}
 
+	/**
+	 * Default options for curl invocations.
+	 */
+	private static $curl_options = array(
+		CURLOPT_BINARYTRANSFER => true,
+		CURLOPT_FAILONERROR => true,
+		CURLOPT_RETURNTRANSFER => true,
+		CURLOPT_CONNECTTIMEOUT => 1,
+		CURLOPT_TIMEOUT => 5,
+		CURLOPT_USERAGENT => "simpleSAMLphp",
+	);
 
 	/**
-	 * Log-in using A-Select
+	 * Create a (possibly signed) URL to contact the A-Select server.
 	 *
-	 * @param array &$state  Information about the current authentication.
+	 * @param string $request    The name of the request (authenticate / verify_credentials).
+	 * @param array $parameters  The parameters to pass for this request.
 	 */
-	public function authenticate(&$state) {
-		assert('is_array($state)');
-
-		/* We are going to need the authId in order to retrieve this authentication source later. */
-		$state[self::AUTHID] = $this->authId;
-
-		$stateID = SimpleSAML_Auth_State::saveState($state, self::STAGE_INIT);
-
-		$serviceUrl = SimpleSAML_Module::getModuleURL('aselect/linkback.php', array('stateID' => $stateID));
-
-		if ($this->asconfig['type'] == 'app') {
-			$params = array(
-				'request'               => 'authenticate',
-				'a-select-server'       => $this->asconfig['serverid'],
-				'app_id'                => $this->asconfig['app_id'],
-				'app_url'               => $serviceUrl,
-			);
-		} else { // type = cross
-			$params = array(
-				'request'               => 'authenticate',
-				'a-select-server'       => $this->asconfig['serverid'],
-				'local_organization'    => $this->asconfig['local_organization'],
-				'required_level'        => $this->asconfig['required_level'],
-				'local_as_url'          => $serviceUrl,
-
-			);
+	private function create_aselect_url($request, $parameters) {
+		$parameters['request'] = $request;
+		$parameters['a-select-server'] = $this->server_id;
+		if(!is_null($this->private_key)) {
+			$signable = '';
+			foreach(array('a-select-server', 'app_id', 'app_url', 'aselect_credentials', 'rid') as $p)
+				if(array_key_exists($p, $parameters))
+					$signable .= $parameters[$p];
+			$parameters['signature'] = $this->base64_signature($signable);
 		}
-		$url = SimpleSAML_Utilities::addURLparameter($this->asconfig['serverurl'],$params);
+		return SimpleSAML_Utilities::addURLparameter($this->server_url, $parameters);
+	}
+
+	/**
+	 * Contact the A-Select server and return the result as an associative array.
+	 *
+	 * @param string $request    The name of the request (authenticate / verify_credentials).
+	 * @param array $parameters  The parameters to pass for this request.
+	 */
+	private function call_aselect($request, $parameters) {
+		$url = $this->create_aselect_url($request, $parameters);
+
+		$curl = curl_init($url);
+		if($curl === false)
+			throw new SimpleSAML_Error_Exception("Unable to create CURL handle");
+
+		if(!curl_setopt_array($curl, self::$curl_options))
+			throw new SimpleSAML_Error_Exception("Unable to set CURL options: ".curl_error($curl));
+
+		$str = curl_exec($curl);
+		$err = curl_error($curl);
+
+		curl_close($curl);
+
+		if($str === false)
+			throw new SimpleSAML_Error_Exception("Unable to retrieve URL: $error");
 
-		$parm = $this->as_call($url);
+		parse_str($str, $res);
 
-		SimpleSAML_Utilities::redirect(
-			$parm['as_url'],
-			array(
-				'rid'               => $parm['rid'],
-				'a-select-server'   => $this->asconfig['serverid'],
-			)
-		);
+		// message is only available with some A-Select server implementations
+		if($res['result_code'] != '0000')
+			if(array_key_exists('message', $res))
+				throw new SimpleSAML_Error_Exception("Unable to contact SSO service: result_code=".$res['result_code']." message=".$res['message']);
+			else
+				throw new SimpleSAML_Error_Exception("Unable to contact SSO service: result_code=".$res['result_code']);
+		unset($res['result_code']);
+
+		return $res;
 	}
 
-	public function finalStep(&$state) {
-		$credentials = $state['aselect:credentials'];
-		$rid = $state['aselect:rid'];
-		assert('isset($credentials)');
-		assert('isset($rid)');
-
-		$params = array(
-			'request'               => 'verify_credentials',
-			'rid'                   => $rid,
-			'a-select-server'       => $this->asconfig['serverid'],
-			'aselect_credentials'   => $credentials,
-		);
-		if ($this->asconfig['type'] == 'cross') {
-			$params['local_organization'] = $this->asconfig['local_organization'];
-		}
+	/**
+	 * Initiate authentication. Returns a URL to redirect the user to.
+	 *
+	 * @param string $app_url  The SSP URL to return to after authenticating (similar to an ACS).
+	 */
+	public function request_authentication($app_url) {
+		$res = $this->call_aselect('authenticate',
+			array('app_id' => $this->app_id, 'app_url' => $app_url));
 
-		$url = SimpleSAML_Utilities::addURLparameter($this->asconfig['serverurl'], $params);
-
-		$parms = $this->as_call($url);
-		$attributes = array('uid' => array($parms['uid']));
-
-		if (array_key_exists('attributes', $parms)) {
-			$decoded = base64_decode($parms['attributes']);
-			foreach (explode('&', $decoded) as $parm) {
-				$tuple = explode('=', $parm);
-				$name = urldecode($tuple[0]);
-				if (preg_match('/\[\]$/',$name)) {
-					$name = substr($name, 0 ,-2);
-				}
-				if (!array_key_exists($name, $attributes)) {
-					$attributes[$name] = array();
-				}
-				$attributes[$name][] = urldecode($tuple[1]);
-			}
-		}
-		$state['Attributes'] = $attributes;
+		$as_url = $res['as_url'];
+		unset($res['as_url']);
+
+		return SimpleSAML_Utilities::addURLparameter($as_url, $res);
+	}
+
+	/**
+	 * Verify the credentials upon return from the A-Select server. Returns an associative array
+	 * with the information given by the A-Select server. Any attributes are pre-parsed.
+	 *
+	 * @param string $server_id    The A-Select server ID as passed by the client
+	 * @param string $credentials  The credentials as passed by the client
+	 * @param string $rid          The request ID as passed by the client
+	 */
+	public function verify_credentials($server_id, $credentials, $rid) {
+		if($server_id != $this->server_id)
+			throw new SimpleSAML_Error_Exception("Acquired server ID ($server_id) does not match configured server ID ($this->server_id)");
+
+		$res = $this->call_aselect('verify_credentials',
+			array('aselect_credentials' => $credentials, 'rid' => $rid));
+
+		if(array_key_exists('attributes', $res))
+			$res['attributes'] = self::decode_attributes($res['attributes']);
 
-		SimpleSAML_Auth_Source::completeAuth($state);
+		return $res;
 	}
 }
diff --git a/modules/aselect/www/credentials.php b/modules/aselect/www/credentials.php
new file mode 100644
index 0000000000000000000000000000000000000000..3d3b8cba1204a903c768285f831c544bc253a113
--- /dev/null
+++ b/modules/aselect/www/credentials.php
@@ -0,0 +1,47 @@
+<?php
+
+/**
+ * Check the credentials that the user got from the A-Select server.
+ * This function is called after the user returns from the A-Select server.
+ *
+ * @author Wessel Dankers, Tilburg University
+ */
+function check_credentials() {
+	$state = SimpleSAML_Auth_State::loadState($_REQUEST['ssp_state'], 'aselect:login');
+
+	if(!array_key_exists('a-select-server', $_REQUEST))
+		SimpleSAML_Auth_State::throwException($state, new SimpleSAML_Error_Exception("Missing a-select-server parameter"));
+	$server_id = $_REQUEST['a-select-server'];
+
+	if(!array_key_exists('aselect_credentials', $_REQUEST))
+		SimpleSAML_Auth_State::throwException($state, new SimpleSAML_Error_Exception("Missing aselect_credentials parameter"));
+	$credentials = $_REQUEST['aselect_credentials'];
+
+	if(!array_key_exists('rid', $_REQUEST))
+		SimpleSAML_Auth_State::throwException($state, new SimpleSAML_Error_Exception("Missing rid parameter"));
+	$rid = $_REQUEST['rid'];
+
+	try {
+		if(!array_key_exists('aselect::authid', $state))
+			throw new SimpleSAML_Error_Exception("ASelect authentication source missing in state");
+		$authid = $state['aselect::authid'];
+		$aselect = SimpleSAML_Auth_Source::getById($authid);
+		if(is_null($aselect))
+			throw new SimpleSAML_Error_Exception("Could not find authentication source with id $authid");
+		$creds = $aselect->verify_credentials($server_id, $credentials, $rid);
+
+		if(array_key_exists('attributes', $creds)) {
+			$state['Attributes'] = $creds['attributes'];
+		} else {
+			$res = $creds['res'];
+			$state['Attributes'] = array('uid' => array($res['uid']), 'organization' => array($res['organization']));
+		}
+	} catch(Exception $e) {
+		SimpleSAML_Auth_State::throwException($state, $e);
+	}
+
+	SimpleSAML_Auth_Source::completeAuth($state);
+	SimpleSAML_Auth_State::throwException($state, new SimpleSAML_Error_Exception("Internal error in A-Select component"));
+}
+
+check_credentials();
diff --git a/modules/aselect/www/linkback.php b/modules/aselect/www/linkback.php
index 1f3efba228777d941f009f22ce31942e48689ea2..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 100644
--- a/modules/aselect/www/linkback.php
+++ b/modules/aselect/www/linkback.php
@@ -1,36 +0,0 @@
-<?php
-
-/**
- * Handle linkback() response from A-Select.
- */
-
-
-if (!isset($_GET['stateID'])) {
-	throw new SimpleSAML_Error_BadRequest('Missing stateID parameter.');
-}
-$stateId = (string)$_GET['stateID'];
-
-if (!isset($_GET['aselect_credentials'])) {
-	throw new SimpleSAML_Error_BadRequest('Missing aselect_credentials parameter.');
-}
-if (!isset($_GET['rid'])) {
-	throw new SimpleSAML_Error_BadRequest('Missing ridparameter.');
-}
-
-
-$state = SimpleSAML_Auth_State::loadState($stateId, sspmod_aselect_Auth_Source_aselect::STAGE_INIT);
-$state['aselect:credentials'] = $_GET['aselect_credentials'];
-$state['aselect:rid'] = $_GET['rid'];
-
-
-/* Find authentication source. */
-assert('array_key_exists(sspmod_aselect_Auth_Source_aselect::AUTHID, $state)');
-$sourceId = $state[sspmod_aselect_Auth_Source_aselect::AUTHID];
-
-$source = SimpleSAML_Auth_Source::getById($sourceId);
-if ($source === NULL) {
-	throw new Exception('Could not find authentication source with id ' . $sourceId);
-}
-
-$source->finalStep($state);
-
diff --git a/modules/discojuice/config-templates/discojuice.php b/modules/discojuice/config-templates/discojuice.php
new file mode 100644
index 0000000000000000000000000000000000000000..2f1e0e31e48621b0c2fdb6dbcc2a8d8c0eb9a570
--- /dev/null
+++ b/modules/discojuice/config-templates/discojuice.php
@@ -0,0 +1,42 @@
+<?php
+
+/**
+ * This is the configuration file for the DiscoJuice.
+ */
+
+$config = array(
+
+	// A human readable name describing the Service Provider
+	'name' => 'Service',
+	
+	/* A set of prepared metadata feeds from discojuice.org
+	 * You may visit
+	 * 		https://static.discojuice.org/feeds/
+	 *
+	 * to review the available feed identifiers.
+	 * You may choose to not use any of the provider feed, by setting this to an 
+	 * empty array: array()
+	 */
+	'feeds' => array('edugain'),
+	
+	/*
+	 * You may provide additional feeds
+	 */
+	'additionalFeeds' => array(
+	),
+	
+	/*
+	 * If you set this value to true, the module will contact discojuice.org to read and write cookies.
+	 * If you enable this, you will also need to get your host accepted in the access control list of 
+	 * discojuice.org
+	 *
+	 * The response url of your service, similar to:
+	 *
+	 *		https://sp.example.org/simplesaml/module.php/discojuice/response.html	
+	 *
+	 * will need to be registered at discojuice.org. If your response url is already registered in the metadata
+	 * of one of the federation feeds at discojuice.org, you should already have access.
+	 */
+	'enableCentralStorage' => false,
+	
+);
\ No newline at end of file
diff --git a/modules/discojuice/default-disable b/modules/discojuice/default-disable
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/modules/discojuice/templates/central.tpl.php b/modules/discojuice/templates/central.tpl.php
new file mode 100644
index 0000000000000000000000000000000000000000..8067e168041f104f291c21cba5600f5d9e0e2401
--- /dev/null
+++ b/modules/discojuice/templates/central.tpl.php
@@ -0,0 +1,80 @@
+<?php
+
+header('P3P:CP="IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT"');
+
+?><!DOCTYPE html>
+<html lang="en">
+<head>
+	<meta charset="utf-8" />
+	<title>Select Your Login Provider</title>
+
+	<!-- JQuery hosted by Google -->
+	<script src="//ajax.googleapis.com/ajax/libs/jquery/1.6.4/jquery.min.js" type="text/javascript"></script>
+
+	<!-- DiscoJuice hosted by UNINETT at discojuice.org -->
+	<script type="text/javascript" src="https://engine.discojuice.org/discojuice-stable.min.js"></script>
+	<link rel="stylesheet" type="text/css" href="https://static.discojuice.org/css/discojuice.css" />
+
+	<style type="text/css">
+		body {
+			text-align: center;
+		}
+		div.discojuice {
+			text-align: left;
+			position: relative;
+			width: 600px;
+			margin-right: auto;
+			margin-left: auto;
+		}
+	</style>
+
+	<script type="text/javascript">
+
+<?php
+
+	echo '
+		$("document").ready(function() {
+			var djc = DiscoJuice.Hosted.getConfig(' . 
+				json_encode($this->data['hostedConfig'][0]) . "," .
+				json_encode($this->data['hostedConfig'][1]) . "," . 
+				json_encode($this->data['hostedConfig'][2]) .  "," .
+				json_encode($this->data['hostedConfig'][3]) .  "," .
+				json_encode($this->data['hostedConfig'][4]) .
+			');';
+
+	// echo "	djc.country = false;\n";
+	// echo "	djc.showLocationInfo = false;\n";
+	
+	if (!$this->data['enableCentralStorage']) {
+		echo "	delete djc.disco;\n";
+	}
+	if (!empty($this->data['additionalFeeds'])) {
+		foreach($this->data['additionalFeeds'] AS $feed) {
+			echo "	djc.metadata.push(" . json_encode($feed) . ");\n";
+		}
+	}
+	
+	echo "	djc.always = true;\n";
+		
+	echo '
+			$("a.signin").DiscoJuice(djc);
+		});
+	';
+
+?>
+
+
+
+	</script>
+	
+	
+	
+</head>
+<body style="background: #ccc">
+
+	<p style="display: none; text-align: right"><a class="signin" href="/">signin</a></p>
+
+</body>
+</html>
+
+
diff --git a/modules/discojuice/www/central.php b/modules/discojuice/www/central.php
new file mode 100644
index 0000000000000000000000000000000000000000..6f1a7ead6fd137123deefb72e0e98b77073141af
--- /dev/null
+++ b/modules/discojuice/www/central.php
@@ -0,0 +1,49 @@
+<?php
+
+if (empty($_REQUEST['entityID'])) throw new Exception('Missing parameter [entityID]');
+if (empty($_REQUEST['return'])) throw new Exception('Missing parameter [return]');
+
+
+$djconfig = SimpleSAML_Configuration::getOptionalConfig('discojuice.php');
+$config = SimpleSAML_Configuration::getInstance();
+
+// EntityID
+$entityid = $_REQUEST['entityID'];
+
+// Return to...
+$returnidparam = !empty($_REQUEST['returnIDParam']) ? $_REQUEST['returnIDParam'] : 'entityID';
+$href = SimpleSAML_Utilities::addURLparameter(
+	$_REQUEST['return'],
+	array($returnidparam => '')
+);
+
+
+$hostedConfig = array(
+	// Name of service
+	$djconfig->getString('name', 'Service'),
+
+	$entityid,
+	
+	// Url to response
+	SimpleSAML_Module::getModuleURL('discojuice/response.html'),
+	
+	// Set of feeds to subscribe to.
+	$djconfig->getArray('feeds', array('edugain')), 
+	
+	$href
+);
+
+/*
+	"a.signin", "Teest Demooo",
+    "https://example.org/saml2/entityid",
+    "' . SimpleSAML_Module::getModuleURL('discojuice/discojuice/discojuiceDiscoveryResponse.html') . '", ["kalmar"], "http://example.org/login?idp="
+*/
+
+$t = new SimpleSAML_XHTML_Template($config, 'discojuice:central.tpl.php');
+$t->data['hostedConfig'] = $hostedConfig;
+$t->data['enableCentralStorage'] = $djconfig->getBoolean('enableCentralStorage', true);
+$t->data['additionalFeeds'] = $djconfig->getArray('additionalFeeds', null);
+$t->show();
+
+
+
diff --git a/modules/discojuice/www/response.html b/modules/discojuice/www/response.html
new file mode 100644
index 0000000000000000000000000000000000000000..3067d41be9609fab65a30c05e0afdd1b3431288c
--- /dev/null
+++ b/modules/discojuice/www/response.html
@@ -0,0 +1,60 @@
+<!DOCTYPE html>
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+	<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
+	<META HTTP-EQUIV="CACHE-CONTROL" CONTENT="NO-CACHE">
+	<META HTTP-EQUIV="PRAGMA" CONTENT="NO-CACHE">
+	<title>IdP Discovery Response Receiver</title>
+
+	<script type="text/javascript">
+
+function parseURL(url) {
+	var a =  document.createElement('a');
+    a.href = url;
+    return a.hostname;
+}
+
+function receive() {
+	var urlParams = {},
+		cid = null;
+	(function () {
+		var e,
+			a = /\+/g,  // Regex for replacing addition symbol with a space
+			r = /([^&;=]+)=?([^&;]*)/g,
+			d = function (s) { return decodeURIComponent(s.replace(a, " ")); },
+			q = window.location.search.substring(1);
+
+		while (e = r.exec(q))
+		   urlParams[d(e[1])] = d(e[2]);
+	})();
+	
+	if (urlParams.cid) cid = urlParams.cid;
+		
+	var sender = parseURL(document.referrer);
+	
+	// Received a specific entity ID from the storage.
+	if (urlParams.entityID) {
+		window.parent.DiscoJuice.Utils.log('ResponseLocation: Response from discovery service [' + sender + ']: ' + urlParams.entityID + '   subID: ' + urlParams.subID);
+		window.parent.DiscoJuice.Control.discoResponse(sender, urlParams.entityID, urlParams.subID, cid);
+
+	// Received a textual error from the storage, to show in the debug log.
+	} else if (urlParams['error']) {
+		window.parent.DiscoJuice.Control.discoResponseError(cid, 
+			"Error from IdP Discovery Service [" + sender + "]:  " + urlParams.error);
+	
+	// Did not receive a response parameter. This probably means that the Disco storage did not have a stored preference
+	// for the user. Consequently: no error.
+	} else {
+		window.parent.DiscoJuice.Utils.log('No valid response parameters. cid[' + cid + ']');
+		window.parent.DiscoJuice.Control.discoResponseError(cid);
+	}
+
+}
+
+	</script>
+</head>
+
+<body onload="receive();">
+
+</body>
+</html>
\ No newline at end of file
diff --git a/modules/metarefresh/bin/metarefresh.php b/modules/metarefresh/bin/metarefresh.php
index c8a918122f8f7979ca78dc00c57a45ca555bc644..08fe926f819ccbf92c8e0758bb75cb34e07a15cc 100755
--- a/modules/metarefresh/bin/metarefresh.php
+++ b/modules/metarefresh/bin/metarefresh.php
@@ -13,6 +13,8 @@ $baseDir = dirname(dirname(dirname(dirname(__FILE__))));
 /* Add library autoloader. */
 require_once($baseDir . '/lib/_autoload.php');
 
+SimpleSAML_Session::useTransientSession(); /* No need to try to create a session here. */
+
 if(!SimpleSAML_Module::isModuleEnabled('metarefresh')) {
 	echo("You need to enable the metarefresh module before this script can be used.\n");
 	echo("You can enable it by running the following command:\n");
diff --git a/modules/metarefresh/lib/MetaLoader.php b/modules/metarefresh/lib/MetaLoader.php
index 2fb532ff2497840a37d061e69eaaf426e88c3ffd..ea5bfc647a99378b53d2bf2895312bc9104068b0 100644
--- a/modules/metarefresh/lib/MetaLoader.php
+++ b/modules/metarefresh/lib/MetaLoader.php
@@ -45,79 +45,85 @@ class sspmod_metarefresh_MetaLoader {
 	 */
 	public function loadSource($source) {
 
-		// Build new HTTP context
-		$context = $this->createContext($source);
+		if (preg_match('@^https?://@i', $source['src'])) {
+			// Build new HTTP context
+			$context = $this->createContext($source);
+
+			// GET!
+			try {
+				list($data, $responseHeaders) = SimpleSAML_Utilities::fetch($source['src'], $context, TRUE);
+			} catch(Exception $e) {
+				SimpleSAML_Logger::warning('metarefresh: ' . $e->getMessage());
+			}
 
-		// GET!
-		try {
-			list($data, $responseHeaders) = SimpleSAML_Utilities::fetch($source['src'], $context, TRUE);
-		} catch(Exception $e) {
-			SimpleSAML_Logger::warning('metarefresh: ' . $e->getMessage());
+			// We have response headers, so the request succeeded
+			if(!isset($responseHeaders)) {
+				// No response headers, this means the request failed in some way, so re-use old data
+				SimpleSAML_Logger::debug('No response from ' . $source['src'] . ' - attempting to re-use cached metadata');
+				$this->addCachedMetadata($source);
+				return;
+			} elseif(preg_match('@^HTTP/1\.[01]\s304\s@', $responseHeaders[0])) {
+				// 304 response
+				SimpleSAML_Logger::debug('Received HTTP 304 (Not Modified) - attempting to re-use cached metadata');
+				$this->addCachedMetadata($source);
+				return;
+			} elseif(!preg_match('@^HTTP/1\.[01]\s200\s@', $responseHeaders[0])) {
+				// Other error.
+				SimpleSAML_Logger::debug('Error from ' . $source['src'] . ' - attempting to re-use cached metadata');
+				$this->addCachedMetadata($source);
+				return;
+			}
+		} else {
+			/* Local file. */
+			$data = file_get_contents($source['src']);
+			$responseHeaders = NULL;
 		}
 
-		// We have response headers, so the request succeeded
-		if(isset($responseHeaders)) {
-
-			// 200 OK
-			if(preg_match('@^HTTP/1\.[01]\s200\s@', $responseHeaders[0])) {
-
-				if (isset($source['conditionalGET']) && $source['conditionalGET']) {
-					// Stale or no metadata, so a fresh copy
-					SimpleSAML_Logger::debug('Downloaded fresh copy');
-				}
-
-				$entities = $this->loadXML($data, $source);
-
-				foreach($entities as $entity) {
-
-					if(isset($source['blacklist'])) {
-						if(!empty($source['blacklist']) && in_array($entity->getEntityID(), $source['blacklist'])) {
-							SimpleSAML_Logger::info('Skipping "' .  $entity->getEntityID() . '" - blacklisted.' . "\n");
-							continue;
-						}
-					}
-
-					if(isset($source['whitelist'])) {
-						if(!empty($source['whitelist']) && !in_array($entity->getEntityID(), $source['whitelist'])) {
-							SimpleSAML_Logger::info('Skipping "' .  $entity->getEntityID() . '" - not in the whitelist.' . "\n");
-							continue;
-						}
-					}
+		/* Everything OK. Proceed. */
+		if (isset($source['conditionalGET']) && $source['conditionalGET']) {
+			// Stale or no metadata, so a fresh copy
+			SimpleSAML_Logger::debug('Downloaded fresh copy');
+		}
 
-					if(array_key_exists('validateFingerprint', $source) && $source['validateFingerprint'] !== NULL) {
-						if(!$entity->validateFingerprint($source['validateFingerprint'])) {
-							SimpleSAML_Logger::info('Skipping "' . $entity->getEntityId() . '" - could not verify signature.' . "\n");
-							continue;
-						}
-					}
+		$entities = $this->loadXML($data, $source);
 
-					$template = NULL;
-					if (array_key_exists('template', $source)) $template = $source['template'];
+		foreach($entities as $entity) {
 
-					$this->addMetadata($source['src'], $entity->getMetadata1xSP(), 'shib13-sp-remote', $template);
-					$this->addMetadata($source['src'], $entity->getMetadata1xIdP(), 'shib13-idp-remote', $template);
-					$this->addMetadata($source['src'], $entity->getMetadata20SP(), 'saml20-sp-remote', $template);
-					$this->addMetadata($source['src'], $entity->getMetadata20IdP(), 'saml20-idp-remote', $template);
-					$attributeAuthorities = $entity->getAttributeAuthorities();
-					if (!empty($attributeAuthorities)) {
-						$this->addMetadata($source['src'], $attributeAuthorities[0], 'attributeauthority-remote', $template);
-					}
+			if(isset($source['blacklist'])) {
+				if(!empty($source['blacklist']) && in_array($entity->getEntityID(), $source['blacklist'])) {
+					SimpleSAML_Logger::info('Skipping "' .  $entity->getEntityID() . '" - blacklisted.' . "\n");
+					continue;
 				}
+			}
 
-				$this->saveState($source, $responseHeaders);
+			if(isset($source['whitelist'])) {
+				if(!empty($source['whitelist']) && !in_array($entity->getEntityID(), $source['whitelist'])) {
+					SimpleSAML_Logger::info('Skipping "' .  $entity->getEntityID() . '" - not in the whitelist.' . "\n");
+					continue;
+				}
 			}
 
-			// 304 response
-			if(preg_match('@^HTTP/1\.[01]\s304\s@', $responseHeaders[0])) {
-				SimpleSAML_Logger::debug('Received HTTP 304 (Not Modified) - attempting to re-use cached metadata');
-				$this->addCachedMetadata($source);
+			if(array_key_exists('validateFingerprint', $source) && $source['validateFingerprint'] !== NULL) {
+				if(!$entity->validateFingerprint($source['validateFingerprint'])) {
+					SimpleSAML_Logger::info('Skipping "' . $entity->getEntityId() . '" - could not verify signature.' . "\n");
+					continue;
+				}
 			}
 
-		} else {
-			// No response headers, this means the request failed in some way, so re-use old data
-			SimpleSAML_Logger::debug('No response from ' . $source['src'] . ' - attempting to re-use cached metadata');
-			$this->addCachedMetadata($source);
+			$template = NULL;
+			if (array_key_exists('template', $source)) $template = $source['template'];
+
+			$this->addMetadata($source['src'], $entity->getMetadata1xSP(), 'shib13-sp-remote', $template);
+			$this->addMetadata($source['src'], $entity->getMetadata1xIdP(), 'shib13-idp-remote', $template);
+			$this->addMetadata($source['src'], $entity->getMetadata20SP(), 'saml20-sp-remote', $template);
+			$this->addMetadata($source['src'], $entity->getMetadata20IdP(), 'saml20-idp-remote', $template);
+			$attributeAuthorities = $entity->getAttributeAuthorities();
+			if (!empty($attributeAuthorities)) {
+				$this->addMetadata($source['src'], $attributeAuthorities[0], 'attributeauthority-remote', $template);
+			}
 		}
+
+		$this->saveState($source, $responseHeaders);
 	}
 
 	/**
diff --git a/modules/radius/lib/Auth/Source/Radius.php b/modules/radius/lib/Auth/Source/Radius.php
index 283027ec0c08059d0f1158f24eac3f59dcf71982..57c023c6c424465c1b867681ea9aa63929d22b09 100644
--- a/modules/radius/lib/Auth/Source/Radius.php
+++ b/modules/radius/lib/Auth/Source/Radius.php
@@ -49,7 +49,10 @@ class sspmod_radius_Auth_Source_Radius extends sspmod_core_Auth_UserPassBase {
 	 * The vendor-specific attribute for the RADIUS attributes we are interrested in.
 	 */
 	private $vendorType;
-
+	/**
+	 * The NAS-Identifier that should be set in Access-Request packets.
+	 */
+	private $nasIdentifier;
 
 	/**
 	 * Constructor for this authentication source.
@@ -74,6 +77,7 @@ class sspmod_radius_Auth_Source_Radius extends sspmod_core_Auth_UserPassBase {
 		$this->timeout = $config->getInteger('timeout', 5);
 		$this->retries = $config->getInteger('retries', 3);
 		$this->usernameAttribute = $config->getString('username_attribute', NULL);
+		$this->nasIdentifier = $config->getString('nas_identifier', NULL);
 
 		$this->vendor = $config->getInteger('attribute_vendor', NULL);
 		if ($this->vendor !== NULL) {
@@ -105,6 +109,9 @@ class sspmod_radius_Auth_Source_Radius extends sspmod_core_Auth_UserPassBase {
 		radius_put_attr($radius, RADIUS_USER_NAME, $username);
 		radius_put_attr($radius, RADIUS_USER_PASSWORD, $password);
 
+		if ($this->nasIdentifier != NULL)
+			radius_put_attr($radius, RADIUS_NAS_IDENTIFIER, $this->nasIdentifier);
+
 		$res = radius_send_request($radius);
 		if ($res != RADIUS_ACCESS_ACCEPT) {
 			switch ($res) {
@@ -179,6 +186,3 @@ class sspmod_radius_Auth_Source_Radius extends sspmod_core_Auth_UserPassBase {
 	}
 
 }
-
-
-?>
\ No newline at end of file
diff --git a/modules/saml/docs/sp.txt b/modules/saml/docs/sp.txt
index 5e5c66a0cbc8e17d9b22ddfa2064416654bbc819..673d69ca9e021dce7d9418e768b4974e66efc093 100644
--- a/modules/saml/docs/sp.txt
+++ b/modules/saml/docs/sp.txt
@@ -105,6 +105,38 @@ Here we will list some examples for this authentication source.
     ),
 
 
+### Specifying attributes and required attributes
+
+    An SP that wants eduPersonPrincipalName and mail, where eduPersonPrincipalName should be listed as required:
+
+    'example-attributes => array(
+        'saml:SP',
+        'name' => array( //Name required for AttributeConsumingService-element.
+            'en' => 'Example service',
+            'no' => 'Eksempeltjeneste',
+        ),
+        'attributes' => array(
+            'eduPersonPrincipalName',
+            'mail',
+        )
+        'attributes.required' => array (
+            'eduPersonPrincipalName',
+        ),
+        'attributes.NameFormat' => 'urn:oasis:names:tc:SAML:2.0:attrname-format:basic',
+    ),
+
+
+### Limiting supported AssertionConsumerService endpoint bindings
+
+    'example-acs-limit' => array(
+        'saml:SP',
+        'acs.Bindings' => array(
+            'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST',
+            'urn:oasis:names:tc:SAML:1.0:profiles:browser-post',
+        ),
+    ),
+
+
 ### Requesting a specific authentication method.
 
     $auth = new SimpleSAML_Auth_Simple('default-sp');
@@ -127,6 +159,16 @@ Here we will list some examples for this authentication source.
 Options
 -------
 
+`acs.Bindings`
+: List of bindings the SP should support. If it is unset, all will be added.
+: Possible values:
+
+    * `urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST`
+    * `urn:oasis:names:tc:SAML:1.0:profiles:browser-post`
+    * `urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact`
+    * `urn:oasis:names:tc:SAML:1.0:profiles:artifact-01`
+    * `urn:oasis:names:tc:SAML:2.0:profiles:holder-of-key:SSO:browser`
+
 `assertion.encryption`
 :   Whether assertions received by this SP must be encrypted. The default value is `FALSE`.
     If this option is set to `TRUE`, unencrypted assertions will be rejected.
@@ -147,6 +189,10 @@ Options
 `attributes.NameFormat`
 :   The `NameFormat` for the requested attributes.
 
+`attributes.required`
+: If you have attributes added you can here specify which should be marked as required.
+: The attributes should still be present in `attributes`.
+
 `AuthnContextClassRef`
 :   The SP can request authentication with a specific authentication context class.
     One example of usage could be if the IdP supports both username/password authentication as well as software-PKI.
diff --git a/modules/saml/www/sp/metadata.php b/modules/saml/www/sp/metadata.php
index b19a392193cd3e95757dbc56a6ded8e8e424240b..25e9d7fd245bb971d0a5d59b138c5840ed537dd2 100644
--- a/modules/saml/www/sp/metadata.php
+++ b/modules/saml/www/sp/metadata.php
@@ -1,6 +1,5 @@
 <?php
 
-
 if (!array_key_exists('PATH_INFO', $_SERVER)) {
 	throw new SimpleSAML_Error_BadRequest('Missing authentication source id in metadata URL');
 }
@@ -48,36 +47,48 @@ if ($store instanceof SimpleSAML_Store_SQL) {
 	$sp->SingleLogoutService[] = $slo;
 }
 
-$acs = new SAML2_XML_md_IndexedEndpointType();
-$acs->index = 0;
-$acs->Binding = SAML2_Const::BINDING_HTTP_POST;
-$acs->Location = SimpleSAML_Module::getModuleURL('saml/sp/saml2-acs.php/' . $sourceId);
-$sp->AssertionConsumerService[] = $acs;
-
-$acs = new SAML2_XML_md_IndexedEndpointType();
-$acs->index = 1;
-$acs->Binding = 'urn:oasis:names:tc:SAML:1.0:profiles:browser-post';
-$acs->Location = SimpleSAML_Module::getModuleURL('saml/sp/saml1-acs.php/' . $sourceId);
-$sp->AssertionConsumerService[] = $acs;
-
-$acs = new SAML2_XML_md_IndexedEndpointType();
-$acs->index = 2;
-$acs->Binding = 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact';
-$acs->Location = SimpleSAML_Module::getModuleURL('saml/sp/saml2-acs.php/' . $sourceId);
-$sp->AssertionConsumerService[] = $acs;
-
-$acs = new SAML2_XML_md_IndexedEndpointType();
-$acs->index = 3;
-$acs->Binding = 'urn:oasis:names:tc:SAML:1.0:profiles:artifact-01';
-$acs->Location = SimpleSAML_Module::getModuleURL('saml/sp/saml1-acs.php/' . $sourceId . '/artifact');
-$sp->AssertionConsumerService[] = $acs;
-
-$acs = new SAML2_XML_md_IndexedEndpointType();
-$acs->index = 4;
-$acs->Binding = 'urn:oasis:names:tc:SAML:2.0:profiles:holder-of-key:SSO:browser';
-$acs->ProtocolBinding = SAML2_Const::BINDING_HTTP_POST;
-$acs->Location = SimpleSAML_Module::getModuleURL('saml/sp/saml2-acs.php/' . $sourceId);
-$sp->AssertionConsumerService[] = $acs;
+$assertionsconsumerservicesdefault = array(
+	'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST',
+	'urn:oasis:names:tc:SAML:1.0:profiles:browser-post',
+	'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact',
+	'urn:oasis:names:tc:SAML:1.0:profiles:artifact-01',
+	'urn:oasis:names:tc:SAML:2.0:profiles:holder-of-key:SSO:browser',
+);
+
+$assertionsconsumerservices = $spconfig->getArray('acs.Bindings', $assertionsconsumerservicesdefault);
+
+$index = 0;
+foreach ($assertionsconsumerservices as $services) {
+
+	$acs = new SAML2_XML_md_IndexedEndpointType();
+	$acs->index = $index;
+	switch ($services) {
+	case 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST':
+		$acs->Binding = SAML2_Const::BINDING_HTTP_POST;
+		$acs->Location = SimpleSAML_Module::getModuleURL('saml/sp/saml2-acs.php/' . $sourceId);
+		break;
+	case 'urn:oasis:names:tc:SAML:1.0:profiles:browser-post':
+		$acs->Binding = 'urn:oasis:names:tc:SAML:1.0:profiles:browser-post';
+		$acs->Location = SimpleSAML_Module::getModuleURL('saml/sp/saml1-acs.php/' . $sourceId);
+		break;
+	case 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact':
+		$acs->Binding = 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact';
+		$acs->Location = SimpleSAML_Module::getModuleURL('saml/sp/saml2-acs.php/' . $sourceId);
+		break;
+	case 'urn:oasis:names:tc:SAML:1.0:profiles:artifact-01':
+		$acs->Binding = 'urn:oasis:names:tc:SAML:1.0:profiles:artifact-01';
+		$acs->Location = SimpleSAML_Module::getModuleURL('saml/sp/saml1-acs.php/' . $sourceId . '/artifact');
+		break;
+	case 'urn:oasis:names:tc:SAML:2.0:profiles:holder-of-key:SSO:browser':
+		$acs->Binding = 'urn:oasis:names:tc:SAML:2.0:profiles:holder-of-key:SSO:browser';
+		$acs->ProtocolBinding = SAML2_Const::BINDING_HTTP_POST;
+		$acs->Location = SimpleSAML_Module::getModuleURL('saml/sp/saml2-acs.php/' . $sourceId);
+		break;
+	}
+	$sp->AssertionConsumerService[] = $acs;
+	$index++;
+}
+
 
 $keys = array();
 $certInfo = SimpleSAML_Utilities::loadPublicKey($spconfig, FALSE, 'new_');
@@ -99,7 +110,6 @@ if ($certInfo !== NULL && array_key_exists('certData', $certInfo)) {
 		'encryption' => TRUE,
 		'X509Certificate' => $certInfo['certData'],
 	);
-
 } else {
 	$hasNewCert = FALSE;
 }
@@ -124,14 +134,17 @@ if ($certInfo !== NULL && array_key_exists('certData', $certInfo)) {
 		'encryption' => ($hasNewCert ? FALSE : TRUE),
 		'X509Certificate' => $certInfo['certData'],
 	);
-
 } else {
 	$certData = NULL;
 }
 
 $name = $spconfig->getLocalizedString('name', NULL);
 $attributes = $spconfig->getArray('attributes', array());
+
 if ($name !== NULL && !empty($attributes)) {
+
+	$attributesrequired = $spconfig->getArray('attributes.required', array());
+
 	/* We have everything necessary to add an AttributeConsumingService. */
 	$acs = new SAML2_XML_md_AttributeConsumingService();
 	$sp->AttributeConsumingService[] = $acs;
@@ -149,6 +162,10 @@ if ($name !== NULL && !empty($attributes)) {
 		$a = new SAML2_XML_md_RequestedAttribute();
 		$a->Name = $attribute;
 		$a->NameFormat = $nameFormat;
+		// Is the attribute required
+		if (in_array($attribute, $attributesrequired))
+			$a->isRequired = true;
+
 		$acs->RequestedAttribute[] = $a;
 	}
 
@@ -163,6 +180,7 @@ if ($name !== NULL && !empty($attributes)) {
 	}
 }
 
+
 $orgName = $spconfig->getLocalizedString('OrganizationName', NULL);
 if ($orgName !== NULL) {
 	$o = new SAML2_XML_md_Organization();
@@ -233,5 +251,4 @@ if (array_key_exists('output', $_REQUEST) && $_REQUEST['output'] == 'xhtml') {
 	header('Content-Type: application/samlmetadata+xml');
 	echo($xml);
 }
-
 ?>
diff --git a/www/errorreport.php b/www/errorreport.php
index bef296c130150acab056a721ee1cb77647ef456a..5a245c0648659535faab295bbbcefa76c7e9f48f 100644
--- a/www/errorreport.php
+++ b/www/errorreport.php
@@ -17,19 +17,27 @@ $reportId = (string)$_REQUEST['reportId'];
 $email = (string)$_REQUEST['email'];
 $text = htmlspecialchars((string)$_REQUEST['text']);
 
-$session = SimpleSAML_Session::getInstance();
-$data = $session->getData('core:errorreport', $reportId);
+try {
+	$session = SimpleSAML_Session::getInstance();
+	$data = $session->getData('core:errorreport', $reportId);
+} catch (Exception $e) {
+	SimpleSAML_Logger::error('Error loading error report data: ' . var_export($e->getMessage(), TRUE));
+}
 
 if ($data === NULL) {
 	$data = array(
 		'exceptionMsg' => 'not set',
 		'exceptionTrace' => 'not set',
 		'reportId' => $reportId,
-		'trackId' => $session->getTrackId(),
+		'trackId' => 'not set',
 		'url' => 'not set',
 		'version' => $config->getVersion(),
 		'referer' => 'not set',
 	);
+
+	if (isset($session)) {
+		$data['trackId'] = $session->getTrackId();
+	}
 }
 
 foreach ($data as $k => $v) {
diff --git a/www/saml2/idp/metadata.php b/www/saml2/idp/metadata.php
index e9ee76513211920742632a4cae8116e0f7d6a83b..e0b8e0db55a0743f08e2ab7f547ae6ddcabb954f 100644
--- a/www/saml2/idp/metadata.php
+++ b/www/saml2/idp/metadata.php
@@ -105,6 +105,18 @@ try {
 		$metaArray['scope'] = $idpmeta->getArray('scope');
 	}
 
+	if ($idpmeta->hasValue('EntityAttributes')) {
+		$metaArray['EntityAttributes'] = $idpmeta->getArray('EntityAttributes');
+	}
+
+	if ($idpmeta->hasValue('UIInfo')) {
+		$metaArray['UIInfo'] = $idpmeta->getArray('UIInfo');
+	}
+
+	if ($idpmeta->hasValue('DiscoHints')) {
+		$metaArray['DiscoHints'] = $idpmeta->getArray('DiscoHints');
+	}
+
 	$metaflat = '$metadata[' . var_export($idpentityid, TRUE) . '] = ' . var_export($metaArray, TRUE) . ';';
 
 	$metaBuilder = new SimpleSAML_Metadata_SAMLBuilder($idpentityid);