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
&& !wcd
->tried_fedbearer
) {
552 SIPE_DEBUG_INFO("webticket_token: anonymous authentication to service %s failed, retrying with federated authentication",
555 if (initiate_fedbearer(sipe_private
, wcd
)) {
556 /* callback data passed down the line */
564 gchar
*failure_msg
= NULL
;
567 failure_msg
= sipe_xml_data(sipe_xml_child(soap_body
,
568 "Body/Fault/Detail/error/internalerror/text"));
569 /* XML data can end in 
 */
570 g_strstrip(failure_msg
);
573 callback_execute(sipe_private
,
580 callback_data_free(wcd
);
584 static gboolean
federated_authentication(struct sipe_core_private
*sipe_private
,
585 struct webticket_callback_data
*wcd
)
589 if ((success
= sipe_svc_webticket_lmc_federated(sipe_private
,
591 sipe_private
->webticket
->adfs_token
,
592 wcd
->webticket_fedbearer_uri
,
595 wcd
->token_state
= TOKEN_STATE_FED_BEARER
;
597 /* If TRUE then callback data has been passed down the line */
601 static gboolean
fedbearer_authentication(struct sipe_core_private
*sipe_private
,
602 struct webticket_callback_data
*wcd
)
604 struct sipe_webticket
*webticket
= sipe_private
->webticket
;
607 /* make sure a cached ADFS token is still valid for 60 seconds */
608 if (webticket
->adfs_token
&&
609 (webticket
->adfs_token_expires
>= time(NULL
) + 60)) {
611 SIPE_DEBUG_INFO_NOFORMAT("fedbearer_authentication: reusing cached ADFS token");
612 success
= federated_authentication(sipe_private
, wcd
);
614 } else if (webticket
->webticket_adfs_uri
) {
615 if ((success
= sipe_svc_webticket_adfs(sipe_private
,
617 webticket
->webticket_adfs_uri
,
620 wcd
->token_state
= TOKEN_STATE_FEDERATION
;
622 if ((success
= sipe_svc_webticket_lmc(sipe_private
,
624 wcd
->webticket_fedbearer_uri
,
627 wcd
->token_state
= TOKEN_STATE_FED_BEARER
;
630 /* If TRUE then callback data has been passed down the line */
634 static void realminfo(struct sipe_core_private
*sipe_private
,
636 SIPE_UNUSED_PARAMETER
const gchar
*raw
,
638 gpointer callback_data
)
640 struct sipe_webticket
*webticket
= sipe_private
->webticket
;
641 struct webticket_callback_data
*wcd
= callback_data
;
643 /* Only try retrieving of RealmInfo once */
644 webticket
->retrieved_realminfo
= TRUE
;
647 * We must specifically check for abort, because
648 * realminfo == NULL is a valid response
652 /* detect ADFS setup. See also:
654 * http://en.wikipedia.org/wiki/Active_Directory_Federation_Services
656 * NOTE: this is based on observed behaviour.
657 * It is unkown if this is documented somewhere...
659 SIPE_DEBUG_INFO("realminfo: data for user %s retrieved successfully",
660 sipe_private
->username
);
662 webticket
->webticket_adfs_uri
= sipe_xml_data(sipe_xml_child(realminfo
,
666 if (webticket
->webticket_adfs_uri
)
667 SIPE_DEBUG_INFO("realminfo: ADFS setup detected: %s",
668 webticket
->webticket_adfs_uri
);
670 SIPE_DEBUG_INFO_NOFORMAT("realminfo: no RealmInfo found or no ADFS setup detected - try direct login");
672 if (fedbearer_authentication(sipe_private
, wcd
)) {
673 /* callback data passed down the line */
679 callback_execute(sipe_private
,
684 callback_data_free(wcd
);
688 static gboolean
initiate_fedbearer(struct sipe_core_private
*sipe_private
,
689 struct webticket_callback_data
*wcd
)
693 if (sipe_private
->webticket
->retrieved_realminfo
) {
694 /* skip retrieval and go to authentication */
695 success
= fedbearer_authentication(sipe_private
, wcd
);
697 success
= sipe_svc_realminfo(sipe_private
,
703 wcd
->tried_fedbearer
= TRUE
;
708 static void webticket_metadata(struct sipe_core_private
*sipe_private
,
710 SIPE_UNUSED_PARAMETER
const gchar
*raw
,
712 gpointer callback_data
)
714 struct webticket_callback_data
*wcd
= callback_data
;
717 const sipe_xml
*node
;
719 SIPE_DEBUG_INFO("webticket_metadata: metadata for service %s retrieved successfully",
722 /* Authentication ports accepted by WebTicket Service */
723 for (node
= sipe_xml_child(metadata
, "service/port");
725 node
= sipe_xml_twin(node
)) {
726 const gchar
*auth_uri
= sipe_xml_attribute(sipe_xml_child(node
,
731 if (sipe_strcase_equal(sipe_xml_attribute(node
, "name"),
732 "WebTicketServiceWinNegotiate")) {
733 SIPE_DEBUG_INFO("webticket_metadata: WebTicket Windows Negotiate Auth URI %s", auth_uri
);
734 g_free(wcd
->webticket_negotiate_uri
);
735 wcd
->webticket_negotiate_uri
= g_strdup(auth_uri
);
736 } else if (sipe_strcase_equal(sipe_xml_attribute(node
, "name"),
738 SIPE_DEBUG_INFO("webticket_metadata: WebTicket FedBearer Auth URI %s", auth_uri
);
739 g_free(wcd
->webticket_fedbearer_uri
);
740 wcd
->webticket_fedbearer_uri
= g_strdup(auth_uri
);
745 if (wcd
->webticket_negotiate_uri
|| wcd
->webticket_fedbearer_uri
) {
748 /* Entropy: 256 random bits */
749 if (!wcd
->entropy
.buffer
)
750 sipe_tls_fill_random(&wcd
->entropy
, 256);
752 if (wcd
->webticket_negotiate_uri
) {
753 /* Try Negotiate authentication first */
755 success
= sipe_svc_webticket(sipe_private
,
757 wcd
->webticket_negotiate_uri
,
759 wcd
->service_auth_uri
,
763 wcd
->token_state
= TOKEN_STATE_SERVICE
;
765 success
= initiate_fedbearer(sipe_private
,
770 /* callback data passed down the line */
777 callback_execute(sipe_private
,
782 callback_data_free(wcd
);
786 static void service_metadata(struct sipe_core_private
*sipe_private
,
788 SIPE_UNUSED_PARAMETER
const gchar
*raw
,
790 gpointer callback_data
)
792 struct webticket_callback_data
*wcd
= callback_data
;
795 const sipe_xml
*node
;
796 gchar
*policy
= g_strdup_printf("%s_policy", wcd
->service_port
);
797 gchar
*ticket_uri
= NULL
;
799 SIPE_DEBUG_INFO("webservice_metadata: metadata for service %s retrieved successfully",
802 /* WebTicket policies accepted by Web Service */
803 for (node
= sipe_xml_child(metadata
, "Policy");
805 node
= sipe_xml_twin(node
)) {
806 if (sipe_strcase_equal(sipe_xml_attribute(node
, "Id"),
809 SIPE_DEBUG_INFO_NOFORMAT("webservice_metadata: WebTicket policy found");
811 ticket_uri
= sipe_xml_data(sipe_xml_child(node
,
812 "ExactlyOne/All/EndorsingSupportingTokens/Policy/IssuedToken/Issuer/Address"));
814 /* this token type requires signing */
815 wcd
->requires_signing
= TRUE
;
817 /* try alternative token type */
818 ticket_uri
= sipe_xml_data(sipe_xml_child(node
,
819 "ExactlyOne/All/SignedSupportingTokens/Policy/IssuedToken/Issuer/Address"));
822 SIPE_DEBUG_INFO("webservice_metadata: WebTicket URI %s", ticket_uri
);
831 /* Authentication ports accepted by Web Service */
832 for (node
= sipe_xml_child(metadata
, "service/port");
834 node
= sipe_xml_twin(node
)) {
835 if (sipe_strcase_equal(sipe_xml_attribute(node
, "name"),
836 wcd
->service_port
)) {
837 const gchar
*auth_uri
;
839 SIPE_DEBUG_INFO_NOFORMAT("webservice_metadata: authentication port found");
841 auth_uri
= sipe_xml_attribute(sipe_xml_child(node
,
845 SIPE_DEBUG_INFO("webservice_metadata: Auth URI %s", auth_uri
);
847 if (sipe_svc_metadata(sipe_private
,
852 /* Remember for later */
853 wcd
->service_auth_uri
= g_strdup(auth_uri
);
855 /* callback data passed down the line */
867 callback_execute(sipe_private
,
872 callback_data_free(wcd
);
876 gboolean
sipe_webticket_request(struct sipe_core_private
*sipe_private
,
877 struct sipe_svc_session
*session
,
878 const gchar
*base_uri
,
879 const gchar
*port_name
,
880 sipe_webticket_callback
*callback
,
881 gpointer callback_data
)
883 struct sipe_webticket
*webticket
;
884 gboolean ret
= FALSE
;
886 sipe_webticket_init(sipe_private
);
887 webticket
= sipe_private
->webticket
;
889 if (webticket
->shutting_down
) {
890 SIPE_DEBUG_ERROR("sipe_webticket_request: new Web Ticket request during shutdown: THIS SHOULD NOT HAPPEN! Debugging information:\n"
897 const struct webticket_token
*wt
= cache_hit(sipe_private
, base_uri
);
899 /* cache hit for this URI? */
901 SIPE_DEBUG_INFO("sipe_webticket_request: using cached token for URI %s (Auth URI %s)",
902 base_uri
, wt
->auth_uri
);
903 callback(sipe_private
,
911 GHashTable
*pending
= webticket
->pending
;
912 struct webticket_callback_data
*wcd
= g_hash_table_lookup(pending
,
915 /* is there already a pending request for this URI? */
917 SIPE_DEBUG_INFO("sipe_webticket_request: pending request found for URI %s - queueing",
919 queue_request(wcd
, callback
, callback_data
);
922 wcd
= g_new0(struct webticket_callback_data
, 1);
924 ret
= sipe_svc_metadata(sipe_private
,
931 wcd
->service_uri
= g_strdup(base_uri
);
932 wcd
->service_port
= port_name
;
933 wcd
->callback
= callback
;
934 wcd
->callback_data
= callback_data
;
935 wcd
->session
= session
;
936 wcd
->token_state
= TOKEN_STATE_NONE
;
937 g_hash_table_insert(pending
,
938 wcd
->service_uri
, /* borrowed */