Commit Graph

5552 Commits

Author SHA1 Message Date
Benjamin Dauvergne eaabf4c611 Release 2.5.0 2015-09-02 16:20:14 +02:00
Benjamin Dauvergne 959ad0eadf remove errors.c, it breaks computation of version from tags 2015-09-02 16:20:14 +02:00
Benjamin Dauvergne 0099e845ec java: fix AllJunitTests generation when building out of source directory 2015-09-02 16:20:09 +02:00
Benjamin Dauvergne 8d4c940ac1 Revert "Add messageID and idp_list to profile properties"
This reverts commit b10c48058e.
2015-09-01 18:04:46 +02:00
John Dennis 247b69b1cf fix test08_lasso_key test failure
Note: the rest of this message is formatted as reStructuredText (rst).

Test Failure
============

The unit tests run by "make check" fail with the following error:

::

    tests.c:61:F:Lasso keys:test08_lasso_key:0: No logging output expected: message «ID _E3F8E9116EE08F0E2607CF9789649BB4 already defined
    » was emitted for domain «Lasso» at the level «128»

This is not a regression in Lasso, rather the failure is caused by one
of the components Lasso is dependent upon. It was first observed when
the identical Lasso package was built in Fedora 22, no problems were
observed in Fedora 21. This implies one or more updated components in
Fedora 22 is the cause.

This was a particularity difficult error to track down, first one had
to identify who was emitting the message and on what file descriptor
(stream) and who was triggering on the message emission and causing a
check failure. The obvious assumption the check library was
responsible for detecting the message emission and failing the test is
wrong.

Who is emitting the message and why?
------------------------------------

The message is emitted by libxml2 in the function `xmlAddID()`
(valid.c:2578). It occurs at the end of xmlAddID() when it detects the
ID (which is supposed to be unique to the document is already defined,
which for valid XML is illegal (violates uniquenesss constraint). The
message emission occurs because of the code fragment

::

        if (xmlHashAddEntry(table, value, ret) < 0) {
    #ifdef LIBXML_VALID_ENABLED
            /*
             * The id is already defined in this DTD.
             */
            xmlErrValidNode(ctxt, attr->parent, XML_DTD_ID_REDEFINED,
                            "ID %s already defined\n", value, NULL, NULL);
    #endif /* LIBXML_VALID_ENABLED */
            xmlFreeID(ret);
            return(NULL);
        }

Why is the message emission different between libxml2 versions?
---------------------------------------------------------------

The change occured between libxml2 version 2.9.1 and 2.9.2 in commit
a16eb968075a82ec33b2c1e77db8909a35b44620

::

    commit a16eb968075a82ec33b2c1e77db8909a35b44620
    Author: Daniel Veillard <veillard@redhat.com>
    Date:   Tue Jun 10 16:06:14 2014 +0800

        erroneously ignores a validation error if no error callback set

        Reported by Stefan Behnel
        https://bugzilla.gnome.org/show_bug.cgi?id=724903

    diff --git a/valid.c b/valid.c
    index aedd9d7..1e03a7c 100644
    --- a/valid.c
    +++ b/valid.c
    @@ -2633,11 +2633,8 @@ xmlAddID(xmlValidCtxtPtr ctxt, xmlDocPtr doc, const xmlChar *value,
            /*
             * The id is already defined in this DTD.
             */
    -	if ((ctxt != NULL) && (ctxt->error != NULL)) {
    -	    xmlErrValidNode(ctxt, attr->parent, XML_DTD_ID_REDEFINED,
    -	                    "ID %s already defined\n",
    -			    value, NULL, NULL);
    -	}
    +	xmlErrValidNode(ctxt, attr->parent, XML_DTD_ID_REDEFINED,
    +			"ID %s already defined\n", value, NULL, NULL);
     #endif /* LIBXML_VALID_ENABLED */
            xmlFreeID(ret);
            return(NULL);

In both versions of libxml2 the conditional complilation
LIBXML_VALID_ENABLED is enabled by default via the configure
script. What is different is the the requirement ctxt be
non-NULL. Lasso invokes xmlAddID with a NULL ctxt parameter. Because
the NULL test for ctxt is absent in libxlm2 2.9.2 the message is now
emitted where previously it was not.

Who triggers on messge emission and fails the test?
---------------------------------------------------

This is a Lasso feature, it is not part of libcheck. In tests/tests.c
is the following function

::

    void error_logger(const gchar *log_domain, GLogLevelFlags log_level,
                    const gchar *message, G_GNUC_UNUSED gpointer user_data)
    {
            fail("No logging output expected: message «%s» was emitted for domain «%s» at the level"
                            " «%d»", message, log_domain, log_level);
    }

Before the test are run the error_logger function is installed as a
glib handler

::

    g_log_set_default_handler(error_logger, NULL);

When the message is emitted the error_logger traps it and invokes the
libcheck (deprecated) function fail() which aborts the test case.

Why does `test08_lasso_key` cause an XML validation failure?
------------------------------------------------------------

`test08_lasso_key` invokes `lasso_key_saml2_xml_verify()` twice on the
same XML document. Any time `lasso_key_saml2_xml_verify()` is called
more than once the XML validation will fail on the second and
subsequent invocations. This occurs because
`lasso_key_saml2_xml_verify()` invokes `lasso_verify_signature()`
passing it the node id in the `id_attr_name` parameter. Inside
`lasso_verify_signature()` is this code fragment:

::

	/* Find ID */
	if (id_attr_name) {
		id = xmlGetProp(signed_node, (xmlChar*)id_attr_name);
		if (id) {
			xmlAddID(NULL, doc, id, xmlHasProp(signed_node, (xmlChar*)id_attr_name));
		}
	}

Note that it unconditionally invokes `xmlAddID()`, which adds the ID
to the set of unique element ID's in the document. But if you invoke
`xmlAddID()` more than once with the same ID in the same document you
violate the uniqueness constraint.

The ID needs to be registered in the document because the <Reference>
element of the <SignedInfo> may utilize an XPointer reference to the
signed data. In it's simplest form the XPointer reference is an ID
attribute on a node. Thus to locate the signed data referenced by the
ID it should (must?) be in a table of ID's for the document.

Simple Solution (patch)
-----------------------

The solution is simple now that the problem is understood. The ID
should not be unconditionally added to the document, instead it should
only be added if it's not already registered. Prior to calling
`xmlAddID()` one should call `xmlGetID()` and test for a NULL result
indicating the ID has not be registered previously.

Signed-off-by: John Dennis <jdennis@redhat.com>
License: MIT
2015-09-01 16:32:42 +02:00
John Dennis 640f96c8c6 add support for automake 1.15
Signed-off-by: John Dennis <jdennis@redhat.com>
License: MIT
2015-09-01 16:32:42 +02:00
John Dennis 29897506c7 Fix coverity lasso_get_hmac_key() warning
lasso_get_hmac_key() did not check return value. Now check the return
code, emit a critical message and return early with cleanup.

Signed-off-by: John Dennis <jdennis@redhat.com>
License: MIT
2015-09-01 16:32:42 +02:00
John Dennis b10c48058e Add messageID and idp_list to profile properties
ECP needs a place to store the messageID and idp_list. Normally values
like this would located in a "context" passed to the relevant
routines. But currently there is no such context, the closest thing to
a context we have is the profile so we add them here in the profile
private data using accessors. They are currently not relevant outside
of ECP.

Adds functions:

lasso_profile_get_message_id()
lasso_profile_set_message_id()
lasso_profile_get_idp_list()
lasso_profile_set_idp_list()

Signed-off-by: John Dennis <jdennis@redhat.com>
License: MIT
2015-09-01 16:32:42 +02:00
Benjamin Dauvergne 262d1dae91 configure.ac: move test framework detection after pkg-config detection 2015-08-24 16:57:49 +02:00
Benjamin Dauvergne 911e3d279b debian-jessie: add build dependency on pkg-config 2015-08-24 16:36:35 +02:00
Benjamin Dauvergne 8b17576c90 Add 'debian-jessie/' from commit 'dc7374e9f41214557dd45735789a7535d6bbe681'
git-subtree-dir: debian-jessie
git-subtree-mainline: 83f6319c01
git-subtree-split: dc7374e9f4
2015-08-24 16:35:56 +02:00
Benjamin Dauvergne 83f6319c01 bindings/java: fix test script generation 2015-08-24 16:18:33 +02:00
John Dennis 964530aaca add ECP unit test
Test ECP.
3 different variations of the SP provided IDP List are exercised.

Signed-off-by: John Dennis <jdennis@redhat.com>
License: MIT
2015-08-24 16:05:29 +02:00
John Dennis 9a2f8d404e Implement PAOS request and response messages
Re-implement lasso_profile_saml20_build_paos_request_msg() and
lasso_saml20_login_process_paos_response_msg() to use the
functionality introduced by earlier patches and to assure they are
functionally complete.

Signed-off-by: John Dennis <jdennis@redhat.com>
License: MIT
2015-08-24 16:05:29 +02:00
John Dennis 43bcc8cddf Add messageID and idp_list to profile properties
ECP needs a place to store the messageID and idp_list. Normally values
like this would located in a "context" passed to the relevant
routines. But currently there is no such context, the closest thing to
a context we have is the profile so we add them here in the profile
private data using accessors. They are currently not relevant outside
of ECP.

Adds functions:

lasso_profile_get_message_id()
lasso_profile_set_message_id()
lasso_profile_get_idp_list()
lasso_profile_set_idp_list()

Signed-off-by: John Dennis <jdennis@redhat.com>
License: MIT
2015-08-24 16:05:29 +02:00
John Dennis bdecdc248c ECP and PAOS special handling
ECP does not require an SP to know the remote IdP provider. Existing
code made the assumption the remote provider always was
necessary. Determination and setting of the remote consumer URL is
different in the presence of ECP. Rework the logic to reflect
differing requirements.

Signed-off-by: John Dennis <jdennis@redhat.com>
License: MIT
2015-08-24 16:05:29 +02:00
John Dennis 4544ea9e9d Add function to set protocol conformance
Lasso uses an internal private variable bound to the provider to
indicate which protocol the provider is servicing. It is vital this
value be correctly set because many Lasso routines used it to dispatch
to the appropriate protocol handlers.

Normally the provider's protocol conformance is set as a side-effect
of parsing the XML metadata that describes the provider (e.g. an SP or
IdP). However there are some providers (e.g. an ECP client) which do
not have metadata. For providers lacking metadata it is essential
there be a mechanism to set the protocol conformance otherwise the
library will malfunction.

The function comes with documentation that includes a clear warning
this is to be used only in limited circumstances.

Signed-off-by: John Dennis <jdennis@redhat.com>
License: MIT
2015-08-24 16:05:29 +02:00
John Dennis 1c31736ded Implement ECP client functionality
Implement everything needed to support a SAMLv2 ECP client.

Re-implement lasso_ecp_process_authn_request_msg() and
lasso_ecp_process_response_msg() to use the Lasso XML serialization
subsystem with the ECP and PASO LassoNode's introduced earlier. This
replaces one-off explicit direct use of the libxml API with Lasso
common code. In the process provide support for 100% of the ECP and
PAOS SAMLv2 parameters, not just a subset. Include support for
receiving an IDPList from the SP in conjuction with selecting an IdP
known to the ECP client. Add extensive documentation.

Modify LassoSamlp2AuthnRequest to preserve it's original XML (enable
keep_xmlnode flag) so that when serializing the SOAP request the
LassoSamlp2AuthnRequest received from the SP is exactly duplicated.

Add the following internal static utility functions:

is_provider_in_sp_idplist()
is_idp_entry_in_entity_id_list()
intersect_sp_idplist_with_entity_id_list()

Add the following exported utility functions:

lasso_ecp_is_provider_in_sp_idplist()
lasso_ecp_is_idp_entry_known_idp_supporting_ecp()
lasso_ecp_set_known_sp_provided_idp_entries_supporting_ecp()
lasso_ecp_has_sp_idplist()
lasso_ecp_get_endpoint_url_by_entity_id()
lasso_ecp_process_sp_idp_list()

Add the following members to the ECP class:

message_id
response_consumer_url
relaystate
issuer
provider_name
is_passive
sp_idp_list
known_sp_provided_idp_entries_supporting_ecp
known_idp_entity_ids_supporting_ecp

Signed-off-by: John Dennis <jdennis@redhat.com>
License: MIT
2015-08-24 16:05:29 +02:00
John Dennis 75b0284c8e Clean up ECP and PAOS XML generation
Re-implement lasso_node_export_to_ecp_soap_response() and
lasso_node_export_to_paos_request(). Add new function
lasso_node_export_to_paos_request_full() with full functionality which
deprecates lasso_node_export_to_paos_request().

The existing code had two significant deficiencies, it performed
explicit direct xml manipulation using the libxml API rather than
calling into Lasso's extensive XML utilities, this was in stark
contrast the rest of the Lasso library. It also failed to handle a
number of ECP parameters leaving a functionality gap in the API.

The new code makes use of the Lasso XML serialization
subsystem. Rather than hand crafted xml manipulation we use the ECP
and PAOS LassoNode objects introduced in an earlier patch. This is
consistent with the rest of Lasso and because those LassoNodes are
used elsewhere we have a better guarantee of robustness because the
same common code is being called from multiple places. Other Lasso
common utilities (some introduced in previous patches) are invoked
instead of handcrafted xml manipulation, once again common code is
preferred.

Finally lasso_node_export_to_paos_request_full() was introduced to
expose in the Lasso API all ECP
parameters. lasso_node_export_to_paos_request() now trivially calls
into lasso_node_export_to_paos_request_full().

Signed-off-by: John Dennis <jdennis@redhat.com>
License: MIT
2015-08-24 16:05:29 +02:00
John Dennis 6102c73fd7 Server utility returns list of providers supporting endpoint type
Add lasso_server_get_filtered_provider_list() utility.

Iterate over the server providers and build a list of provider EntityID's who
have the specified role and at least one endpoint matching the
protocol_type and http_method. Return a GList list of EntityID's

Signed-off-by: John Dennis <jdennis@redhat.com>
License: MIT
2015-08-24 16:05:29 +02:00
John Dennis 237b7424bd Add server utility lasso_server_get_endpoint_url_by_id()
Locate the provider in the server's list of providers, then select an
endpoint given the @endpoint_description and return that endpoint's URL.
If the provider cannot be found or if the provider does not have a
matching endpoint NULL will be returned.

Signed-off-by: John Dennis <jdennis@redhat.com>
License: MIT
2015-08-24 16:05:29 +02:00
John Dennis 41d771c628 Add ECP and PAOS to prefix_from_href_and_nodename()
prefix_from_href_and_nodename() did not know about the ECP and PAOS
XML prefixes so add them.

Signed-off-by: John Dennis <jdennis@redhat.com>
License: MIT
2015-08-24 16:05:29 +02:00
John Dennis a7a54cabad Export LassonNode to SOAP with arbitrary SOAP headers
Add function lasso_node_export_to_soap_with_headers()

Utility function to build a full SOAP envelope message with arbitrary
headers. The LassoNode becomes the body of the SOAP envelope. The
headers are passed as a GList of LassoNode's and are added as header
elements to the SOAP envelope header. This is a flexible way to build
a SOAP envelope that contains headers without constraints on the
headers.

Signed-off-by: John Dennis <jdennis@redhat.com>
License: MIT
2015-08-24 16:05:29 +02:00
John Dennis ad3751f2b0 LassoSamlp2IDPList is not list capable
LassoSamlp2IDPList is supposed to handle a list of LassoSamlp2IDPEntry
but in fact it had no list support. Change the snippet flag
SNIPPET_NODE to SNIPPET_LIST_NODES and add the special list comment on
the struct member so that the binding generator knows what type of
GList it is.

Signed-off-by: John Dennis <jdennis@redhat.com>
License: MIT
2015-08-24 16:05:29 +02:00
John Dennis 9629925c1e Add LassoNode objects for ECP and PAOS
The SAMLv2 protocol defines 5 XML types which we need to map to
LassoNode objectes so thay can be serialized from XML and back into
XML.

ecp:RelayState
ecp:Request
ecp:Response
paos:Request
paso:Response

This patch addes these 5 new LassoNode's and updates the build
configuration to include them.

Signed-off-by: John Dennis <jdennis@redhat.com>
License: MIT
2015-08-24 16:05:29 +02:00
John Dennis 904e23d7ff Enhance process soap response to include processing soap headers
The existing lasso_saml20_profile_process_soap_response() assumed
there were no SOAP headers (prior to ECP none of the SOAP messages
contained headers). A new function
lasso_saml20_profile_process_soap_response_with_headers() was
implemented that serializes from the XML SOAP headers into a
LassoSoapHeader node and optionally will return the LassoSoapHeader
node.

The functionality in lasso_saml20_profile_process_soap_response() was
moved into the new
lasso_saml20_profile_process_soap_response_with_headers() and now
lasso_saml20_profile_process_soap_response() simply calls
lasso_saml20_profile_process_soap_response_with_headers() passing NULL
for the header return.

Signed-off-by: John Dennis <jdennis@redhat.com>
License: MIT
2015-08-24 16:05:29 +02:00
John Dennis a0909e732f Add new LassoSoapEnvelope constructor, lasso_soap_envelope_new_full()
The existing LassoSoapEnvelope constructors did not populate the node
with it's constituent members, namely a SOAP header (LassoSoapHeader)
and a SOAP body (LassoSoapBody). lasso_soap_envelope_new_full() allows
one to create a SOAP envelope and immediately begin to add header and
body elements.

Signed-off-by: John Dennis <jdennis@redhat.com>
License: MIT
2015-08-24 16:05:29 +02:00
John Dennis 27f98071e7 Fix LassoSoapHeader, was unable to serialize from XML.
The existing Lasso code never made use of SOAP headers because up
until now nothing used them. LassoSoapHeader was unable to serialize
from XML into a GList of LassoNode objects because it was missing one
of the necessary snippet flags. This corrects this omission and now
parsing a SOAP header will yield a sequence of LassoNode's.

Signed-off-by: John Dennis <jdennis@redhat.com>
License: MIT
2015-08-24 16:05:29 +02:00
John Dennis a3f01cd42c Add new error codes and their matching error descriptions
Signed-off-by: John Dennis <jdennis@redhat.com>
License: MIT
2015-08-24 16:05:29 +02:00
John Dennis a6014fc51b Add lasso_is_in_list_of_strings macro to utils.h
Add macro that tests to see if a string is a member in a list of
strings.

Signed-off-by: John Dennis <jdennis@redhat.com>
License: MIT
2015-08-24 16:05:29 +02:00
John Dennis f3849b2664 Fix build failure, remove inclusion of xml/private.h in utils.h
The public utils.h header includes the private xml/private.h file
which is not installed. Therefore anyone trying to build against lasso
and include utils.h will fail because xml/private.h cannot be
found. There doesn't seem to be any need to include this file.

Signed-off-by: John Dennis <jdennis@redhat.com>
License: MIT
2015-08-24 16:05:29 +02:00
John Dennis a7f6219f5a Eliminate _BSD_SOURCE and _SVID_SOURCE deprecation warning
Because all warnings are treated as errors and this warning is emitted:

warning "_BSD_SOURCE and _SVID_SOURCE are deprecated, use _DEFAULT_SOURCE"

the build fails.

The fix is to define _DEFAULT_SOURCE in lasso/xml/tools.c

The effect of defining the _DEFAULT_SOURCE macro is equivalent to
the effect of explicitly defining three macros in earlier glibc
versions: -D_BSD_SOURCE -D_SVID_SOURCE -D_POSIX_C_SOURCE=200809C

Signed-off-by: John Dennis <jdennis@redhat.com>
License: MIT
2015-08-24 16:05:29 +02:00
Benjamin Dauvergne eb6ed4f85a configure.ac: provide fallback for systems where libcheck is not installed with pkg-config 2015-08-24 16:05:14 +02:00
Benjamin Dauvergne 9e5c4389a8 Add checks for failure of an allocation function from libxml (#8070)
g_malloc always trap on allocation errors but not xmlMalloc.
2015-08-24 10:25:03 +02:00
Benjamin Dauvergne 9854cd50f3 xml: handle failure of xmlSecBase64Decode() (fixes #8070)
Thanks to fpeters for the patch.
2015-08-24 10:25:03 +02:00
Benjamin Dauvergne 6e8326293d FAQ: add section about getting the issuer before parsing the received message (#4378) 2015-08-24 10:25:03 +02:00
Benjamin Dauvergne 65bc705235 profile: add two new class methods, lasso_profile_get_issuer and lasso_profile_get_in_response_to (#4378)
The goal of those two methods is to allow IdP and SP to load metadata
dynamically without processing completely the incoming. Currently it's
impossible as message parsing and signature checking is done in the same
function.
2015-08-24 10:25:03 +02:00
Benjamin Dauvergne 67d0a0349d configure.ac: use pkg-config for libcheck 2015-08-24 10:24:29 +02:00
Benjamin Dauvergne 3946807122 saml-2.0/login.c: change default value of WantAuthnRequestSigned (fixes #8105)
Specification says it should default to FALSE. We comply.
2015-08-24 10:24:27 +02:00
Benjamin Dauvergne c5ec98a018 Makefile.am: fix automake warning
It fixes this warning:

	warning: 'INCLUDES' is the old name for 'AM_CPPFLAGS' (or '*_CPPFLAGS')

it seems INCLUDES is not to be used anymore.
2015-08-24 10:18:52 +02:00
Benjamin Dauvergne 34ee3446e8 Add 'debian-squeeze/' from commit '33d67ddd1352a2db97d252c7d18f7806ec91e616'
git-subtree-dir: debian-squeeze
git-subtree-mainline: 80a2e0ea47
git-subtree-split: 33d67ddd13
2015-04-03 10:01:56 +02:00
Benjamin Dauvergne 80a2e0ea47 Add 'debian-wheezy/' from commit '0001ab9af1e3a7e19000a65b75ebc3c42f76a739'
git-subtree-dir: debian-wheezy
git-subtree-mainline: 9f99176b3c
git-subtree-split: 0001ab9af1
2015-04-03 10:01:19 +02:00
Benjamin Dauvergne 9f99176b3c SAML-2.0: rework on commit 05fe802b8d, improve handling of ProtocolBinding and AssertionConsumerServiceURL
When the same URL was used for many bindings, the current code did not
work. Now we use
lasso_saml20_provider_check_assertion_consumer_service_url() to validate
url and binding are matching, if no binding is suggested we take the
first one defined for this URL.

Using AssertionConsumerServiceIndex and any of the other assertion
consumer designator attributes is still forbidden.
2015-03-26 19:36:44 +01:00
John Dennis bbcee8a480 Fix build failures
Fix a mistake in the documentation markup that prevented the
doc from building, needed to reverse the order of two tags.

Remove the $(PYTHON) from TESTS_ENVIRONMENT, it was causing
python to be invoked passing /bin/sh to it as a script.

License: MIT
Signed-off-by: John Dennis <jdennis@redhat.com>
2015-03-23 14:28:48 +01:00
John Dennis ec73384ccf Add Destination attribute for SAML ECP Response
The Destination attribute on SAML Response element was not being set
when handling an ECP response. It is a requirement of SAML 2.0 that
signed values contain a Destination attribute on the root element
otherwise the client will reject the response. This is documented in
the SAML Bindings Specification, Section 3.4.5.2 "Security
Considerations":

    If the message is signed, the Destination XML attribute in the
    root SAML element of the protocol message MUST contain the URL to
    which the sender has instructed the user agent to deliver the
    message. The recipient MUST then verify that the value matches the
    location at which the message has been received.

Normally on login one calls
lasso_saml20_login_build_authn_response_msg() which then calls
lasso_saml20_profile_build_response_msg() which sets the Destination
attribute on the SAML Response. But when doing ECP you do not call
lasso_saml20_login_build_authn_response_msg(), instead you call call
lasso_saml20_login_build_response_msg() and if it's ECP it then calls
lasso_node_export_to_ecp_soap_response(). Thus the ECP
response never gets the Destination attribute set because of the
different code path, plus for ECP the destination is different, it's
the assertion consumer service.

FWIW this line of code was copied almost verbatim from
lasso_saml20_profile_build_response_msg which also sets the
Destination attribute.

License: MIT
Signed-off-by: John Dennis <jdennis@redhat.com>
2015-03-11 09:13:22 +01:00
Jérôme Schneider dc7374e9f4 php5-lasso.prerm: s/phpdismod/php5dismod/ 2015-02-26 10:29:53 +01:00
Jérôme Schneider e42b1dd7fc Merge with lasso in Jessie, re-activate java and gen-default-control.sh 2015-02-26 10:24:18 +01:00
Jérôme Schneider 2f8ed5a0b0 control: build depends on dh-python 2015-02-26 09:13:03 +01:00
Jérôme Schneider 0001ab9af1 python-lasso.install: just install python 2 files 2015-02-25 19:58:57 +01:00
Jérôme Schneider 5b21d1594d python-lasso.install: python-lasso is for python2 only 2015-02-25 19:48:39 +01:00