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
);
292 /* clear old ADFS token */
293 g_free(webticket
->adfs_token
);
294 webticket
->adfs_token
= NULL
;
296 if (timestamp
&& keydata
) {
297 gchar
*expires_string
= sipe_xml_extract_raw(timestamp
,
301 if (expires_string
) {
303 SIPE_DEBUG_INFO("generate_federation_wsse: found timestamp & keydata, expires %s",
306 /* cache ADFS token */
307 webticket
->adfs_token
= g_strconcat(timestamp
,
310 webticket
->adfs_token_expires
= sipe_utils_str_to_time(expires_string
);
311 g_free(expires_string
);
319 static gchar
*generate_sha1_proof_wsse(const gchar
*raw
,
320 struct sipe_tls_random
*entropy
,
323 gchar
*timestamp
= generate_timestamp(raw
, "Lifetime");
324 gchar
*keydata
= sipe_xml_extract_raw(raw
, "saml:Assertion", TRUE
);
325 gchar
*wsse_security
= NULL
;
327 if (timestamp
&& keydata
) {
328 gchar
*expires_string
= sipe_xml_extract_raw(timestamp
,
333 gchar
*assertionID
= extract_raw_xml_attribute(keydata
,
339 * http://docs.oasis-open.org/ws-sx/ws-trust/200512/CK/PSHA1:
341 * "The key is computed using P_SHA1() from the TLS sepcification to generate
342 * a bit stream using entropy from both sides. The exact form is:
344 * key = P_SHA1(Entropy_REQ, Entropy_RES)"
346 gchar
*entropy_res_base64
= sipe_xml_extract_raw(raw
, "BinarySecret", FALSE
);
347 gsize entropy_res_length
;
348 guchar
*entropy_response
= g_base64_decode(entropy_res_base64
,
349 &entropy_res_length
);
350 guchar
*key
= sipe_tls_p_sha1(entropy
->buffer
,
355 g_free(entropy_response
);
356 g_free(entropy_res_base64
);
358 SIPE_DEBUG_INFO_NOFORMAT("generate_sha1_proof_wsse: found timestamp & keydata");
360 if (assertionID
&& key
) {
361 /* same as SIPE_DIGEST_HMAC_SHA1_LENGTH */
362 guchar digest
[SIPE_DIGEST_SHA1_LENGTH
];
367 SIPE_DEBUG_INFO_NOFORMAT("generate_sha1_proof_wsse: found assertionID and successfully computed the key");
369 /* Digest over reference element (#timestamp -> wsu:Timestamp) */
370 sipe_digest_sha1((guchar
*) timestamp
,
373 base64
= g_base64_encode(digest
,
374 SIPE_DIGEST_SHA1_LENGTH
);
376 /* XML-Sig: SignedInfo for reference element */
377 signed_info
= g_strdup_printf("<SignedInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\">"
378 "<CanonicalizationMethod Algorithm=\"http://www.w3.org/2001/10/xml-exc-c14n#\"/>"
379 "<SignatureMethod Algorithm=\"http://www.w3.org/2000/09/xmldsig#hmac-sha1\"/>"
380 "<Reference URI=\"#timestamp\">"
382 "<Transform Algorithm=\"http://www.w3.org/2001/10/xml-exc-c14n#\"/>"
384 "<DigestMethod Algorithm=\"http://www.w3.org/2000/09/xmldsig#sha1\"/>"
385 "<DigestValue>%s</DigestValue>"
391 /* XML-Sig: SignedInfo in canonical form */
392 canon
= sipe_xml_exc_c14n(signed_info
);
398 /* calculate signature */
399 sipe_digest_hmac_sha1(key
, entropy
->length
,
403 base64
= g_base64_encode(digest
,
404 SIPE_DIGEST_HMAC_SHA1_LENGTH
);
406 /* XML-Sig: Signature from SignedInfo + Key */
407 signature
= g_strdup_printf("<Signature xmlns=\"http://www.w3.org/2000/09/xmldsig#\">"
409 " <SignatureValue>%s</SignatureValue>"
411 " <wsse:SecurityTokenReference wsse:TokenType=\"http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV1.1\">"
412 " <wsse:KeyIdentifier ValueType=\"http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.0#SAMLAssertionID\">%s</wsse:KeyIdentifier>"
413 " </wsse:SecurityTokenReference>"
422 wsse_security
= g_strconcat(timestamp
,
434 /* token doesn't require signature */
435 SIPE_DEBUG_INFO_NOFORMAT("generate_sha1_proof_wsse: found timestamp & keydata, no signing required");
436 wsse_security
= g_strconcat(timestamp
,
442 if (expires_string
) {
443 *expires
= sipe_utils_str_to_time(expires_string
);
444 g_free(expires_string
);
450 return(wsse_security
);
453 static gboolean
federated_authentication(struct sipe_core_private
*sipe_private
,
454 struct webticket_callback_data
*wcd
);
455 static gboolean
initiate_fedbearer(struct sipe_core_private
*sipe_private
,
456 struct webticket_callback_data
*wcd
);
457 static void webticket_token(struct sipe_core_private
*sipe_private
,
461 gpointer callback_data
)
463 struct webticket_callback_data
*wcd
= callback_data
;
464 gboolean failed
= TRUE
;
467 switch (wcd
->token_state
) {
468 case TOKEN_STATE_NONE
:
469 SIPE_DEBUG_INFO_NOFORMAT("webticket_token: ILLEGAL STATE - should not happen...");
472 case TOKEN_STATE_SERVICE
: {
473 /* WebTicket for Web Service */
475 gchar
*wsse_security
= generate_sha1_proof_wsse(raw
,
476 wcd
->requires_signing
? &wcd
->entropy
: NULL
,
480 /* cache takes ownership of wsse_security */
481 cache_token(sipe_private
,
483 wcd
->service_auth_uri
,
486 callback_execute(sipe_private
,
488 wcd
->service_auth_uri
,
496 case TOKEN_STATE_FEDERATION
:
497 /* WebTicket from ADFS for federated authentication */
498 generate_federation_wsse(sipe_private
->webticket
,
501 if (sipe_private
->webticket
->adfs_token
) {
503 SIPE_DEBUG_INFO("webticket_token: received valid SOAP message from ADFS %s",
506 if (federated_authentication(sipe_private
,
508 /* callback data passed down the line */
514 case TOKEN_STATE_FED_BEARER
: {
515 /* WebTicket for federated authentication */
516 gchar
*wsse_security
= generate_fedbearer_wsse(raw
);
520 SIPE_DEBUG_INFO("webticket_token: received valid SOAP message from service %s",
523 if (sipe_svc_webticket(sipe_private
,
525 wcd
->webticket_fedbearer_uri
,
527 wcd
->service_auth_uri
,
531 wcd
->token_state
= TOKEN_STATE_SERVICE
;
533 /* callback data passed down the line */
536 g_free(wsse_security
);
541 /* end of: switch (wcd->token_state) { */
545 /* Retry with federated authentication? */
546 if (wcd
->webticket_fedbearer_uri
&& !wcd
->tried_fedbearer
) {
547 SIPE_DEBUG_INFO("webticket_token: anonymous authentication to service %s failed, retrying with federated authentication",
550 if (initiate_fedbearer(sipe_private
, wcd
)) {
551 /* callback data passed down the line */
559 gchar
*failure_msg
= NULL
;
562 failure_msg
= sipe_xml_data(sipe_xml_child(soap_body
,
563 "Body/Fault/Detail/error/internalerror/text"));
564 /* XML data can end in 
 */
565 g_strstrip(failure_msg
);
568 callback_execute(sipe_private
,
575 callback_data_free(wcd
);
579 static gboolean
federated_authentication(struct sipe_core_private
*sipe_private
,
580 struct webticket_callback_data
*wcd
)
584 if ((success
= sipe_svc_webticket_lmc_federated(sipe_private
,
586 sipe_private
->webticket
->adfs_token
,
587 wcd
->webticket_fedbearer_uri
,
590 wcd
->token_state
= TOKEN_STATE_FED_BEARER
;
592 /* If TRUE then callback data has been passed down the line */
596 static gboolean
fedbearer_authentication(struct sipe_core_private
*sipe_private
,
597 struct webticket_callback_data
*wcd
)
599 struct sipe_webticket
*webticket
= sipe_private
->webticket
;
602 /* make sure a cached ADFS token is still valid for 60 seconds */
603 if (webticket
->adfs_token
&&
604 (webticket
->adfs_token_expires
>= time(NULL
) + 60)) {
606 SIPE_DEBUG_INFO_NOFORMAT("fedbearer_authentication: reusing cached ADFS token");
607 success
= federated_authentication(sipe_private
, wcd
);
609 } else if (webticket
->webticket_adfs_uri
) {
610 if ((success
= sipe_svc_webticket_adfs(sipe_private
,
612 webticket
->webticket_adfs_uri
,
615 wcd
->token_state
= TOKEN_STATE_FEDERATION
;
617 if ((success
= sipe_svc_webticket_lmc(sipe_private
,
619 wcd
->webticket_fedbearer_uri
,
622 wcd
->token_state
= TOKEN_STATE_FED_BEARER
;
625 /* If TRUE then callback data has been passed down the line */
629 static void realminfo(struct sipe_core_private
*sipe_private
,
631 SIPE_UNUSED_PARAMETER
const gchar
*raw
,
633 gpointer callback_data
)
635 struct sipe_webticket
*webticket
= sipe_private
->webticket
;
636 struct webticket_callback_data
*wcd
= callback_data
;
638 /* Only try retrieving of RealmInfo once */
639 webticket
->retrieved_realminfo
= TRUE
;
642 * We must specifically check for abort, because
643 * realminfo == NULL is a valid response
647 /* detect ADFS setup. See also:
649 * http://en.wikipedia.org/wiki/Active_Directory_Federation_Services
651 * NOTE: this is based on observed behaviour.
652 * It is unkown if this is documented somewhere...
654 SIPE_DEBUG_INFO("realminfo: data for user %s retrieved successfully",
655 sipe_private
->username
);
657 webticket
->webticket_adfs_uri
= sipe_xml_data(sipe_xml_child(realminfo
,
661 if (webticket
->webticket_adfs_uri
)
662 SIPE_DEBUG_INFO("realminfo: ADFS setup detected: %s",
663 webticket
->webticket_adfs_uri
);
665 SIPE_DEBUG_INFO_NOFORMAT("realminfo: no RealmInfo found or no ADFS setup detected - try direct login");
667 if (fedbearer_authentication(sipe_private
, wcd
)) {
668 /* callback data passed down the line */
674 callback_execute(sipe_private
,
679 callback_data_free(wcd
);
683 static gboolean
initiate_fedbearer(struct sipe_core_private
*sipe_private
,
684 struct webticket_callback_data
*wcd
)
688 if (sipe_private
->webticket
->retrieved_realminfo
) {
689 /* skip retrieval and go to authentication */
690 success
= fedbearer_authentication(sipe_private
, wcd
);
692 success
= sipe_svc_realminfo(sipe_private
,
698 wcd
->tried_fedbearer
= TRUE
;
703 static void webticket_metadata(struct sipe_core_private
*sipe_private
,
705 SIPE_UNUSED_PARAMETER
const gchar
*raw
,
707 gpointer callback_data
)
709 struct webticket_callback_data
*wcd
= callback_data
;
712 const sipe_xml
*node
;
714 SIPE_DEBUG_INFO("webticket_metadata: metadata for service %s retrieved successfully",
717 /* Authentication ports accepted by WebTicket Service */
718 for (node
= sipe_xml_child(metadata
, "service/port");
720 node
= sipe_xml_twin(node
)) {
721 const gchar
*auth_uri
= sipe_xml_attribute(sipe_xml_child(node
,
726 if (sipe_strcase_equal(sipe_xml_attribute(node
, "name"),
727 "WebTicketServiceWinNegotiate")) {
728 SIPE_DEBUG_INFO("webticket_metadata: WebTicket Windows Negotiate Auth URI %s", auth_uri
);
729 g_free(wcd
->webticket_negotiate_uri
);
730 wcd
->webticket_negotiate_uri
= g_strdup(auth_uri
);
731 } else if (sipe_strcase_equal(sipe_xml_attribute(node
, "name"),
733 SIPE_DEBUG_INFO("webticket_metadata: WebTicket FedBearer Auth URI %s", auth_uri
);
734 g_free(wcd
->webticket_fedbearer_uri
);
735 wcd
->webticket_fedbearer_uri
= g_strdup(auth_uri
);
740 if (wcd
->webticket_negotiate_uri
|| wcd
->webticket_fedbearer_uri
) {
743 /* Entropy: 256 random bits */
744 if (!wcd
->entropy
.buffer
)
745 sipe_tls_fill_random(&wcd
->entropy
, 256);
747 if (wcd
->webticket_negotiate_uri
) {
748 /* Try Negotiate authentication first */
750 success
= sipe_svc_webticket(sipe_private
,
752 wcd
->webticket_negotiate_uri
,
754 wcd
->service_auth_uri
,
758 wcd
->token_state
= TOKEN_STATE_SERVICE
;
760 success
= initiate_fedbearer(sipe_private
,
765 /* callback data passed down the line */
772 callback_execute(sipe_private
,
777 callback_data_free(wcd
);
781 static void service_metadata(struct sipe_core_private
*sipe_private
,
783 SIPE_UNUSED_PARAMETER
const gchar
*raw
,
785 gpointer callback_data
)
787 struct webticket_callback_data
*wcd
= callback_data
;
790 const sipe_xml
*node
;
791 gchar
*policy
= g_strdup_printf("%s_policy", wcd
->service_port
);
792 gchar
*ticket_uri
= NULL
;
794 SIPE_DEBUG_INFO("webservice_metadata: metadata for service %s retrieved successfully",
797 /* WebTicket policies accepted by Web Service */
798 for (node
= sipe_xml_child(metadata
, "Policy");
800 node
= sipe_xml_twin(node
)) {
801 if (sipe_strcase_equal(sipe_xml_attribute(node
, "Id"),
804 SIPE_DEBUG_INFO_NOFORMAT("webservice_metadata: WebTicket policy found");
806 ticket_uri
= sipe_xml_data(sipe_xml_child(node
,
807 "ExactlyOne/All/EndorsingSupportingTokens/Policy/IssuedToken/Issuer/Address"));
809 /* this token type requires signing */
810 wcd
->requires_signing
= TRUE
;
812 /* try alternative token type */
813 ticket_uri
= sipe_xml_data(sipe_xml_child(node
,
814 "ExactlyOne/All/SignedSupportingTokens/Policy/IssuedToken/Issuer/Address"));
817 SIPE_DEBUG_INFO("webservice_metadata: WebTicket URI %s", ticket_uri
);
826 /* Authentication ports accepted by Web Service */
827 for (node
= sipe_xml_child(metadata
, "service/port");
829 node
= sipe_xml_twin(node
)) {
830 if (sipe_strcase_equal(sipe_xml_attribute(node
, "name"),
831 wcd
->service_port
)) {
832 const gchar
*auth_uri
;
834 SIPE_DEBUG_INFO_NOFORMAT("webservice_metadata: authentication port found");
836 auth_uri
= sipe_xml_attribute(sipe_xml_child(node
,
840 SIPE_DEBUG_INFO("webservice_metadata: Auth URI %s", auth_uri
);
842 if (sipe_svc_metadata(sipe_private
,
847 /* Remember for later */
848 wcd
->service_auth_uri
= g_strdup(auth_uri
);
850 /* callback data passed down the line */
862 callback_execute(sipe_private
,
867 callback_data_free(wcd
);
871 gboolean
sipe_webticket_request(struct sipe_core_private
*sipe_private
,
872 struct sipe_svc_session
*session
,
873 const gchar
*base_uri
,
874 const gchar
*port_name
,
875 sipe_webticket_callback
*callback
,
876 gpointer callback_data
)
878 struct sipe_webticket
*webticket
;
879 gboolean ret
= FALSE
;
881 sipe_webticket_init(sipe_private
);
882 webticket
= sipe_private
->webticket
;
884 if (webticket
->shutting_down
) {
885 SIPE_DEBUG_ERROR("sipe_webticket_request: new Web Ticket request during shutdown: THIS SHOULD NOT HAPPEN! Debugging information:\n"
892 const struct webticket_token
*wt
= cache_hit(sipe_private
, base_uri
);
894 /* cache hit for this URI? */
896 SIPE_DEBUG_INFO("sipe_webticket_request: using cached token for URI %s (Auth URI %s)",
897 base_uri
, wt
->auth_uri
);
898 callback(sipe_private
,
906 GHashTable
*pending
= webticket
->pending
;
907 struct webticket_callback_data
*wcd
= g_hash_table_lookup(pending
,
910 /* is there already a pending request for this URI? */
912 SIPE_DEBUG_INFO("sipe_webticket_request: pending request found for URI %s - queueing",
914 queue_request(wcd
, callback
, callback_data
);
917 wcd
= g_new0(struct webticket_callback_data
, 1);
919 ret
= sipe_svc_metadata(sipe_private
,
926 wcd
->service_uri
= g_strdup(base_uri
);
927 wcd
->service_port
= port_name
;
928 wcd
->callback
= callback
;
929 wcd
->callback_data
= callback_data
;
930 wcd
->session
= session
;
931 wcd
->token_state
= TOKEN_STATE_NONE
;
932 g_hash_table_insert(pending
,
933 wcd
->service_uri
, /* borrowed */