2 * @file sipe-webticket.c
6 * Copyright (C) 2011-2013 SIPE Project <http://sipe.sourceforge.net/>
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24 * Specification references:
26 * - [MS-OCAUTHWS]: http://msdn.microsoft.com/en-us/library/ff595592.aspx
27 * - MS Tech-Ed Europe 2010 "UNC310: Microsoft Lync 2010 Technology Explained"
28 * http://ecn.channel9.msdn.com/o9/te/Europe/2010/pptx/unc310.pptx
36 #include "sipe-common.h"
37 #include "sipe-backend.h"
38 #include "sipe-core.h"
39 #include "sipe-core-private.h"
40 #include "sipe-digest.h"
43 #include "sipe-webticket.h"
44 #include "sipe-utils.h"
47 struct webticket_queued_data
{
48 sipe_webticket_callback
*callback
;
49 gpointer callback_data
;
52 struct webticket_callback_data
{
54 const gchar
*service_port
;
55 gchar
*service_auth_uri
;
57 gchar
*webticket_negotiate_uri
;
58 gchar
*webticket_fedbearer_uri
;
60 gboolean tried_fedbearer
;
61 gboolean requires_signing
;
65 TOKEN_STATE_FEDERATION
,
66 TOKEN_STATE_FED_BEARER
,
69 struct sipe_tls_random entropy
;
71 sipe_webticket_callback
*callback
;
72 gpointer callback_data
;
74 struct sipe_svc_session
*session
;
79 struct webticket_token
{
85 struct sipe_webticket
{
89 gchar
*webticket_adfs_uri
;
91 time_t adfs_token_expires
;
93 gboolean retrieved_realminfo
;
94 gboolean shutting_down
;
97 void sipe_webticket_free(struct sipe_core_private
*sipe_private
)
99 struct sipe_webticket
*webticket
= sipe_private
->webticket
;
103 /* Web Ticket stack is shutting down: reject all new requests */
104 webticket
->shutting_down
= TRUE
;
106 g_free(webticket
->webticket_adfs_uri
);
107 g_free(webticket
->adfs_token
);
108 if (webticket
->pending
)
109 g_hash_table_destroy(webticket
->pending
);
110 if (webticket
->cache
)
111 g_hash_table_destroy(webticket
->cache
);
113 sipe_private
->webticket
= NULL
;
116 static void free_token(gpointer data
)
118 struct webticket_token
*wt
= data
;
119 g_free(wt
->auth_uri
);
124 static void sipe_webticket_init(struct sipe_core_private
*sipe_private
)
126 struct sipe_webticket
*webticket
;
128 if (sipe_private
->webticket
)
131 sipe_private
->webticket
= webticket
= g_new0(struct sipe_webticket
, 1);
133 webticket
->cache
= g_hash_table_new_full(g_str_hash
,
137 webticket
->pending
= g_hash_table_new(g_str_hash
,
141 /* takes ownership of "token" */
142 static void cache_token(struct sipe_core_private
*sipe_private
,
143 const gchar
*service_uri
,
144 const gchar
*auth_uri
,
148 struct webticket_token
*wt
= g_new0(struct webticket_token
, 1);
149 wt
->auth_uri
= g_strdup(auth_uri
);
151 wt
->expires
= expires
;
152 g_hash_table_insert(sipe_private
->webticket
->cache
,
153 g_strdup(service_uri
),
157 static const struct webticket_token
*cache_hit(struct sipe_core_private
*sipe_private
,
158 const gchar
*service_uri
)
160 const struct webticket_token
*wt
;
162 /* make sure a cached Web Ticket is still valid for 60 seconds */
163 wt
= g_hash_table_lookup(sipe_private
->webticket
->cache
,
165 if (wt
&& (wt
->expires
< time(NULL
) + 60)) {
166 SIPE_DEBUG_INFO("cache_hit: cached token for URI %s has expired",
174 /* frees just the main request data, when this is called "queued" is cleared */
175 static void callback_data_free(struct webticket_callback_data
*wcd
)
178 sipe_tls_free_random(&wcd
->entropy
);
179 g_free(wcd
->webticket_negotiate_uri
);
180 g_free(wcd
->webticket_fedbearer_uri
);
181 g_free(wcd
->service_auth_uri
);
182 g_free(wcd
->service_uri
);
187 static void queue_request(struct webticket_callback_data
*wcd
,
188 sipe_webticket_callback
*callback
,
189 gpointer callback_data
)
191 struct webticket_queued_data
*wqd
= g_new0(struct webticket_queued_data
, 1);
193 wqd
->callback
= callback
;
194 wqd
->callback_data
= callback_data
;
196 wcd
->queued
= g_slist_prepend(wcd
->queued
, wqd
);
199 static void callback_execute(struct sipe_core_private
*sipe_private
,
200 struct webticket_callback_data
*wcd
,
201 const gchar
*auth_uri
,
202 const gchar
*wsse_security
,
203 const gchar
*failure_msg
)
205 GSList
*entry
= wcd
->queued
;
207 /* complete main request */
208 wcd
->callback(sipe_private
,
215 /* complete queued requests */
217 struct webticket_queued_data
*wqd
= entry
->data
;
219 SIPE_DEBUG_INFO("callback_execute: completing queue request URI %s (Auth URI %s)",
220 wcd
->service_uri
, auth_uri
);
221 wqd
->callback(sipe_private
,
231 g_slist_free(wcd
->queued
);
233 /* drop request from pending hash */
234 g_hash_table_remove(sipe_private
->webticket
->pending
,
238 static gchar
*extract_raw_xml_attribute(const gchar
*xml
,
241 gchar
*attr_start
= g_strdup_printf("%s=\"", name
);
243 const gchar
*start
= strstr(xml
, attr_start
);
246 const gchar
*value
= start
+ strlen(attr_start
);
247 const gchar
*end
= strchr(value
, '"');
249 data
= g_strndup(value
, end
- value
);
257 static gchar
*generate_timestamp(const gchar
*raw
,
258 const gchar
*lifetime_tag
)
260 gchar
*lifetime
= sipe_xml_extract_raw(raw
, lifetime_tag
, FALSE
);
261 gchar
*timestamp
= NULL
;
263 timestamp
= g_strdup_printf("<wsu:Timestamp xmlns:wsu=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd\" wsu:Id=\"timestamp\">%s</wsu:Timestamp>",
269 static gchar
*generate_fedbearer_wsse(const gchar
*raw
)
271 gchar
*timestamp
= generate_timestamp(raw
, "wst:Lifetime");
272 gchar
*keydata
= sipe_xml_extract_raw(raw
, "EncryptedData", TRUE
);
273 gchar
*wsse_security
= NULL
;
275 if (timestamp
&& keydata
) {
276 SIPE_DEBUG_INFO_NOFORMAT("generate_fedbearer_wsse: found timestamp & keydata");
277 wsse_security
= g_strconcat(timestamp
, keydata
, NULL
);
282 return(wsse_security
);
285 static void generate_federation_wsse(struct sipe_webticket
*webticket
,
288 gchar
*timestamp
= generate_timestamp(raw
, "t:Lifetime");
289 gchar
*keydata
= sipe_xml_extract_raw(raw
, "saml:Assertion", TRUE
);
291 /* try alternative names */
293 timestamp
= generate_timestamp(raw
, "wst:Lifetime");
295 keydata
= sipe_xml_extract_raw(raw
, "saml1:Assertion", TRUE
);
297 /* clear old ADFS token */
298 g_free(webticket
->adfs_token
);
299 webticket
->adfs_token
= NULL
;
301 if (timestamp
&& keydata
) {
302 gchar
*expires_string
= sipe_xml_extract_raw(timestamp
,
306 if (expires_string
) {
308 SIPE_DEBUG_INFO("generate_federation_wsse: found timestamp & keydata, expires %s",
311 /* cache ADFS token */
312 webticket
->adfs_token
= g_strconcat(timestamp
,
315 webticket
->adfs_token_expires
= sipe_utils_str_to_time(expires_string
);
316 g_free(expires_string
);
324 static gchar
*generate_sha1_proof_wsse(const gchar
*raw
,
325 struct sipe_tls_random
*entropy
,
328 gchar
*timestamp
= generate_timestamp(raw
, "Lifetime");
329 gchar
*keydata
= sipe_xml_extract_raw(raw
, "saml:Assertion", TRUE
);
330 gchar
*wsse_security
= NULL
;
332 if (timestamp
&& keydata
) {
333 gchar
*expires_string
= sipe_xml_extract_raw(timestamp
,
338 gchar
*assertionID
= extract_raw_xml_attribute(keydata
,
344 * http://docs.oasis-open.org/ws-sx/ws-trust/200512/CK/PSHA1:
346 * "The key is computed using P_SHA1() from the TLS sepcification to generate
347 * a bit stream using entropy from both sides. The exact form is:
349 * key = P_SHA1(Entropy_REQ, Entropy_RES)"
351 gchar
*entropy_res_base64
= sipe_xml_extract_raw(raw
, "BinarySecret", FALSE
);
352 gsize entropy_res_length
;
353 guchar
*entropy_response
= g_base64_decode(entropy_res_base64
,
354 &entropy_res_length
);
355 guchar
*key
= sipe_tls_p_sha1(entropy
->buffer
,
360 g_free(entropy_response
);
361 g_free(entropy_res_base64
);
363 SIPE_DEBUG_INFO_NOFORMAT("generate_sha1_proof_wsse: found timestamp & keydata");
365 if (assertionID
&& key
) {
366 /* same as SIPE_DIGEST_HMAC_SHA1_LENGTH */
367 guchar digest
[SIPE_DIGEST_SHA1_LENGTH
];
372 SIPE_DEBUG_INFO_NOFORMAT("generate_sha1_proof_wsse: found assertionID and successfully computed the key");
374 /* Digest over reference element (#timestamp -> wsu:Timestamp) */
375 sipe_digest_sha1((guchar
*) timestamp
,
378 base64
= g_base64_encode(digest
,
379 SIPE_DIGEST_SHA1_LENGTH
);
381 /* XML-Sig: SignedInfo for reference element */
382 signed_info
= g_strdup_printf("<SignedInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\">"
383 "<CanonicalizationMethod Algorithm=\"http://www.w3.org/2001/10/xml-exc-c14n#\"/>"
384 "<SignatureMethod Algorithm=\"http://www.w3.org/2000/09/xmldsig#hmac-sha1\"/>"
385 "<Reference URI=\"#timestamp\">"
387 "<Transform Algorithm=\"http://www.w3.org/2001/10/xml-exc-c14n#\"/>"
389 "<DigestMethod Algorithm=\"http://www.w3.org/2000/09/xmldsig#sha1\"/>"
390 "<DigestValue>%s</DigestValue>"
396 /* XML-Sig: SignedInfo in canonical form */
397 canon
= sipe_xml_exc_c14n(signed_info
);
403 /* calculate signature */
404 sipe_digest_hmac_sha1(key
, entropy
->length
,
408 base64
= g_base64_encode(digest
,
409 SIPE_DIGEST_HMAC_SHA1_LENGTH
);
411 /* XML-Sig: Signature from SignedInfo + Key */
412 signature
= g_strdup_printf("<Signature xmlns=\"http://www.w3.org/2000/09/xmldsig#\">"
414 " <SignatureValue>%s</SignatureValue>"
416 " <wsse:SecurityTokenReference wsse:TokenType=\"http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV1.1\">"
417 " <wsse:KeyIdentifier ValueType=\"http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.0#SAMLAssertionID\">%s</wsse:KeyIdentifier>"
418 " </wsse:SecurityTokenReference>"
427 wsse_security
= g_strconcat(timestamp
,
439 /* token doesn't require signature */
440 SIPE_DEBUG_INFO_NOFORMAT("generate_sha1_proof_wsse: found timestamp & keydata, no signing required");
441 wsse_security
= g_strconcat(timestamp
,
447 if (expires_string
) {
448 *expires
= sipe_utils_str_to_time(expires_string
);
449 g_free(expires_string
);
455 return(wsse_security
);
458 static gboolean
federated_authentication(struct sipe_core_private
*sipe_private
,
459 struct webticket_callback_data
*wcd
);
460 static gboolean
initiate_fedbearer(struct sipe_core_private
*sipe_private
,
461 struct webticket_callback_data
*wcd
);
462 static void webticket_token(struct sipe_core_private
*sipe_private
,
466 gpointer callback_data
)
468 struct webticket_callback_data
*wcd
= callback_data
;
469 gboolean failed
= TRUE
;
472 switch (wcd
->token_state
) {
473 case TOKEN_STATE_NONE
:
474 SIPE_DEBUG_INFO_NOFORMAT("webticket_token: ILLEGAL STATE - should not happen...");
477 case TOKEN_STATE_SERVICE
: {
478 /* WebTicket for Web Service */
480 gchar
*wsse_security
= generate_sha1_proof_wsse(raw
,
481 wcd
->requires_signing
? &wcd
->entropy
: NULL
,
485 /* cache takes ownership of wsse_security */
486 cache_token(sipe_private
,
488 wcd
->service_auth_uri
,
491 callback_execute(sipe_private
,
493 wcd
->service_auth_uri
,
501 case TOKEN_STATE_FEDERATION
:
502 /* WebTicket from ADFS for federated authentication */
503 generate_federation_wsse(sipe_private
->webticket
,
506 if (sipe_private
->webticket
->adfs_token
) {
508 SIPE_DEBUG_INFO("webticket_token: received valid SOAP message from ADFS %s",
511 if (federated_authentication(sipe_private
,
513 /* callback data passed down the line */
519 case TOKEN_STATE_FED_BEARER
: {
520 /* WebTicket for federated authentication */
521 gchar
*wsse_security
= generate_fedbearer_wsse(raw
);
525 SIPE_DEBUG_INFO("webticket_token: received valid SOAP message from service %s",
528 if (sipe_svc_webticket(sipe_private
,
530 wcd
->webticket_fedbearer_uri
,
532 wcd
->service_auth_uri
,
536 wcd
->token_state
= TOKEN_STATE_SERVICE
;
538 /* callback data passed down the line */
541 g_free(wsse_security
);
546 /* end of: switch (wcd->token_state) { */
550 /* Retry with federated authentication? */
551 if (wcd
->webticket_fedbearer_uri
) {
553 /* Authentication against ADFS failed? */
554 if (wcd
->token_state
== TOKEN_STATE_FEDERATION
) {
555 struct sipe_webticket
*webticket
= sipe_private
->webticket
;
557 SIPE_DEBUG_INFO_NOFORMAT("webticket_token: ADFS authentication failed - assuming Multi-Factor Authentication (MFA)");
559 /* forget ADFS URI */
560 g_free(webticket
->webticket_adfs_uri
);
561 webticket
->webticket_adfs_uri
= NULL
;
564 if (!wcd
->tried_fedbearer
) {
565 SIPE_DEBUG_INFO("webticket_token: anonymous authentication to service %s failed, retrying with federated authentication",
568 if (initiate_fedbearer(sipe_private
, wcd
)) {
569 /* callback data passed down the line */
578 gchar
*failure_msg
= NULL
;
581 failure_msg
= sipe_xml_data(sipe_xml_child(soap_body
,
582 "Body/Fault/Detail/error/internalerror/text"));
583 /* XML data can end in 
 */
584 g_strstrip(failure_msg
);
587 callback_execute(sipe_private
,
594 callback_data_free(wcd
);
598 static gboolean
federated_authentication(struct sipe_core_private
*sipe_private
,
599 struct webticket_callback_data
*wcd
)
603 if ((success
= sipe_svc_webticket_lmc_federated(sipe_private
,
605 sipe_private
->webticket
->adfs_token
,
606 wcd
->webticket_fedbearer_uri
,
609 wcd
->token_state
= TOKEN_STATE_FED_BEARER
;
611 /* If TRUE then callback data has been passed down the line */
615 static gboolean
fedbearer_authentication(struct sipe_core_private
*sipe_private
,
616 struct webticket_callback_data
*wcd
)
618 struct sipe_webticket
*webticket
= sipe_private
->webticket
;
621 /* make sure a cached ADFS token is still valid for 60 seconds */
622 if (webticket
->adfs_token
&&
623 (webticket
->adfs_token_expires
>= time(NULL
) + 60)) {
625 SIPE_DEBUG_INFO_NOFORMAT("fedbearer_authentication: reusing cached ADFS token");
626 success
= federated_authentication(sipe_private
, wcd
);
628 } else if (webticket
->webticket_adfs_uri
) {
629 if ((success
= sipe_svc_webticket_adfs(sipe_private
,
631 webticket
->webticket_adfs_uri
,
634 wcd
->token_state
= TOKEN_STATE_FEDERATION
;
636 if ((success
= sipe_svc_webticket_lmc(sipe_private
,
638 wcd
->webticket_fedbearer_uri
,
641 wcd
->token_state
= TOKEN_STATE_FED_BEARER
;
644 /* If TRUE then callback data has been passed down the line */
648 static void realminfo(struct sipe_core_private
*sipe_private
,
650 SIPE_UNUSED_PARAMETER
const gchar
*raw
,
652 gpointer callback_data
)
654 struct sipe_webticket
*webticket
= sipe_private
->webticket
;
655 struct webticket_callback_data
*wcd
= callback_data
;
657 /* Only try retrieving of RealmInfo once */
658 webticket
->retrieved_realminfo
= TRUE
;
661 * We must specifically check for abort, because
662 * realminfo == NULL is a valid response
666 /* detect ADFS setup. See also:
668 * http://en.wikipedia.org/wiki/Active_Directory_Federation_Services
670 * NOTE: this is based on observed behaviour.
671 * It is unkown if this is documented somewhere...
673 SIPE_DEBUG_INFO("realminfo: data for user %s retrieved successfully",
674 sipe_private
->username
);
676 webticket
->webticket_adfs_uri
= sipe_xml_data(sipe_xml_child(realminfo
,
680 if (webticket
->webticket_adfs_uri
)
681 SIPE_DEBUG_INFO("realminfo: ADFS setup detected: %s",
682 webticket
->webticket_adfs_uri
);
684 SIPE_DEBUG_INFO_NOFORMAT("realminfo: no RealmInfo found or no ADFS setup detected - try direct login");
686 if (fedbearer_authentication(sipe_private
, wcd
)) {
687 /* callback data passed down the line */
693 callback_execute(sipe_private
,
698 callback_data_free(wcd
);
702 static gboolean
initiate_fedbearer(struct sipe_core_private
*sipe_private
,
703 struct webticket_callback_data
*wcd
)
707 if (sipe_private
->webticket
->retrieved_realminfo
) {
708 /* skip retrieval and go to authentication */
709 wcd
->tried_fedbearer
= TRUE
;
710 success
= fedbearer_authentication(sipe_private
, wcd
);
712 success
= sipe_svc_realminfo(sipe_private
,
721 static void webticket_metadata(struct sipe_core_private
*sipe_private
,
723 SIPE_UNUSED_PARAMETER
const gchar
*raw
,
725 gpointer callback_data
)
727 struct webticket_callback_data
*wcd
= callback_data
;
730 const sipe_xml
*node
;
732 SIPE_DEBUG_INFO("webticket_metadata: metadata for service %s retrieved successfully",
735 /* Authentication ports accepted by WebTicket Service */
736 for (node
= sipe_xml_child(metadata
, "service/port");
738 node
= sipe_xml_twin(node
)) {
739 const gchar
*auth_uri
= sipe_xml_attribute(sipe_xml_child(node
,
744 if (sipe_strcase_equal(sipe_xml_attribute(node
, "name"),
745 "WebTicketServiceWinNegotiate")) {
746 SIPE_DEBUG_INFO("webticket_metadata: WebTicket Windows Negotiate Auth URI %s", auth_uri
);
747 g_free(wcd
->webticket_negotiate_uri
);
748 wcd
->webticket_negotiate_uri
= g_strdup(auth_uri
);
749 } else if (sipe_strcase_equal(sipe_xml_attribute(node
, "name"),
751 SIPE_DEBUG_INFO("webticket_metadata: WebTicket FedBearer Auth URI %s", auth_uri
);
752 g_free(wcd
->webticket_fedbearer_uri
);
753 wcd
->webticket_fedbearer_uri
= g_strdup(auth_uri
);
758 if (wcd
->webticket_negotiate_uri
|| wcd
->webticket_fedbearer_uri
) {
761 /* Entropy: 256 random bits */
762 if (!wcd
->entropy
.buffer
)
763 sipe_tls_fill_random(&wcd
->entropy
, 256);
765 if (wcd
->webticket_negotiate_uri
) {
766 /* Try Negotiate authentication first */
768 success
= sipe_svc_webticket(sipe_private
,
770 wcd
->webticket_negotiate_uri
,
772 wcd
->service_auth_uri
,
776 wcd
->token_state
= TOKEN_STATE_SERVICE
;
778 success
= initiate_fedbearer(sipe_private
,
783 /* callback data passed down the line */
790 callback_execute(sipe_private
,
795 callback_data_free(wcd
);
799 static void service_metadata(struct sipe_core_private
*sipe_private
,
801 SIPE_UNUSED_PARAMETER
const gchar
*raw
,
803 gpointer callback_data
)
805 struct webticket_callback_data
*wcd
= callback_data
;
808 const sipe_xml
*node
;
809 gchar
*policy
= g_strdup_printf("%s_policy", wcd
->service_port
);
810 gchar
*ticket_uri
= NULL
;
812 SIPE_DEBUG_INFO("webservice_metadata: metadata for service %s retrieved successfully",
815 /* WebTicket policies accepted by Web Service */
816 for (node
= sipe_xml_child(metadata
, "Policy");
818 node
= sipe_xml_twin(node
)) {
819 if (sipe_strcase_equal(sipe_xml_attribute(node
, "Id"),
822 SIPE_DEBUG_INFO_NOFORMAT("webservice_metadata: WebTicket policy found");
824 ticket_uri
= sipe_xml_data(sipe_xml_child(node
,
825 "ExactlyOne/All/EndorsingSupportingTokens/Policy/IssuedToken/Issuer/Address"));
827 /* this token type requires signing */
828 wcd
->requires_signing
= TRUE
;
830 /* try alternative token type */
831 ticket_uri
= sipe_xml_data(sipe_xml_child(node
,
832 "ExactlyOne/All/SignedSupportingTokens/Policy/IssuedToken/Issuer/Address"));
835 SIPE_DEBUG_INFO("webservice_metadata: WebTicket URI %s", ticket_uri
);
844 /* Authentication ports accepted by Web Service */
845 for (node
= sipe_xml_child(metadata
, "service/port");
847 node
= sipe_xml_twin(node
)) {
848 if (sipe_strcase_equal(sipe_xml_attribute(node
, "name"),
849 wcd
->service_port
)) {
850 const gchar
*auth_uri
;
852 SIPE_DEBUG_INFO_NOFORMAT("webservice_metadata: authentication port found");
854 auth_uri
= sipe_xml_attribute(sipe_xml_child(node
,
858 SIPE_DEBUG_INFO("webservice_metadata: Auth URI %s", auth_uri
);
860 if (sipe_svc_metadata(sipe_private
,
865 /* Remember for later */
866 wcd
->service_auth_uri
= g_strdup(auth_uri
);
868 /* callback data passed down the line */
880 callback_execute(sipe_private
,
885 callback_data_free(wcd
);
889 gboolean
sipe_webticket_request(struct sipe_core_private
*sipe_private
,
890 struct sipe_svc_session
*session
,
891 const gchar
*base_uri
,
892 const gchar
*port_name
,
893 sipe_webticket_callback
*callback
,
894 gpointer callback_data
)
896 struct sipe_webticket
*webticket
;
897 gboolean ret
= FALSE
;
899 sipe_webticket_init(sipe_private
);
900 webticket
= sipe_private
->webticket
;
902 if (webticket
->shutting_down
) {
903 SIPE_DEBUG_ERROR("sipe_webticket_request: new Web Ticket request during shutdown: THIS SHOULD NOT HAPPEN! Debugging information:\n"
910 const struct webticket_token
*wt
= cache_hit(sipe_private
, base_uri
);
912 /* cache hit for this URI? */
914 SIPE_DEBUG_INFO("sipe_webticket_request: using cached token for URI %s (Auth URI %s)",
915 base_uri
, wt
->auth_uri
);
916 callback(sipe_private
,
924 GHashTable
*pending
= webticket
->pending
;
925 struct webticket_callback_data
*wcd
= g_hash_table_lookup(pending
,
928 /* is there already a pending request for this URI? */
930 SIPE_DEBUG_INFO("sipe_webticket_request: pending request found for URI %s - queueing",
932 queue_request(wcd
, callback
, callback_data
);
935 wcd
= g_new0(struct webticket_callback_data
, 1);
937 ret
= sipe_svc_metadata(sipe_private
,
944 wcd
->service_uri
= g_strdup(base_uri
);
945 wcd
->service_port
= port_name
;
946 wcd
->callback
= callback
;
947 wcd
->callback_data
= callback_data
;
948 wcd
->session
= session
;
949 wcd
->token_state
= TOKEN_STATE_NONE
;
950 g_hash_table_insert(pending
,
951 wcd
->service_uri
, /* borrowed */