2 * @file sipe-lync-autodiscover.c
6 * Copyright (C) 2016-2018 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
23 * Specification references:
25 * - [MS-OCDISCWS]: https://msdn.microsoft.com/en-us/library/hh623245.aspx
26 * - Understanding Autodiscover in Lync Server 2013
27 * https://technet.microsoft.com/en-us/library/jj945654.aspx
34 #include "sipe-common.h"
35 #include "sipe-backend.h"
36 #include "sipe-core.h"
37 #include "sipe-core-private.h"
38 #include "sipe-http.h"
39 #include "sipe-lync-autodiscover.h"
40 #include "sipe-utils.h"
42 #include "sipe-webticket.h"
45 #define LYNC_AUTODISCOVER_ACCEPT_HEADER \
46 "Accept: application/vnd.microsoft.rtc.autodiscover+xml;v=1\r\n"
48 struct lync_autodiscover_request
{
49 sipe_lync_autodiscover_callback
*cb
;
51 gpointer id
; /* != NULL for active request */
52 struct sipe_http_request
*request
;
53 struct sipe_svc_session
*session
;
54 const gchar
*protocol
;
60 struct sipe_lync_autodiscover
{
61 GSList
*pending_requests
;
64 /* Use "lar" inside the code fragment */
65 #define FOR_ALL_REQUESTS_WITH_SAME_ID(code) \
67 GSList *entry = sipe_private->lync_autodiscover->pending_requests; \
69 struct lync_autodiscover_request *lar = entry->data; \
70 entry = entry->next; \
71 if (lar->id == id) { \
77 static void sipe_lync_autodiscover_request_free(struct sipe_core_private
*sipe_private
,
78 struct lync_autodiscover_request
*request
)
80 struct sipe_lync_autodiscover
*sla
= sipe_private
->lync_autodiscover
;
82 sla
->pending_requests
= g_slist_remove(sla
->pending_requests
, request
);
85 sipe_http_request_cancel(request
->request
);
87 /* Callback: aborted */
88 (*request
->cb
)(sipe_private
, NULL
, request
->cb_data
);
89 sipe_svc_session_close(request
->session
);
94 static void sipe_lync_autodiscover_cb(struct sipe_core_private
*sipe_private
,
98 gpointer callback_data
);
99 static void lync_request(struct sipe_core_private
*sipe_private
,
100 struct lync_autodiscover_request
*request
,
102 const gchar
*headers
)
104 request
->request
= sipe_http_request_get(sipe_private
,
106 headers
? headers
: LYNC_AUTODISCOVER_ACCEPT_HEADER
,
107 sipe_lync_autodiscover_cb
,
110 if (request
->request
)
111 sipe_http_request_ready(request
->request
);
114 static GSList
*sipe_lync_autodiscover_add(GSList
*servers
,
115 const sipe_xml
*node
,
118 const sipe_xml
*child
= sipe_xml_child(node
, name
);
119 const gchar
*fqdn
= sipe_xml_attribute(child
, "fqdn");
120 guint port
= sipe_xml_int_attribute(child
, "port", 0);
122 /* Add new entry to head of list */
123 if (fqdn
&& (port
!= 0)) {
124 struct sipe_lync_autodiscover_data
*lync_data
= g_new0(struct sipe_lync_autodiscover_data
, 1);
125 lync_data
->server
= g_strdup(fqdn
);
126 lync_data
->port
= port
;
127 servers
= g_slist_prepend(servers
, lync_data
);
133 GSList
*sipe_lync_autodiscover_pop(GSList
*servers
)
136 struct sipe_lync_autodiscover_data
*lync_data
= servers
->data
;
137 servers
= g_slist_remove(servers
, lync_data
);
140 g_free((gchar
*) lync_data
->server
);
148 static void sipe_lync_autodiscover_queue_request(struct sipe_core_private
*sipe_private
,
149 struct lync_autodiscover_request
*request
);
150 static void sipe_lync_autodiscover_parse(struct sipe_core_private
*sipe_private
,
151 struct lync_autodiscover_request
*request
,
154 sipe_xml
*xml
= sipe_xml_parse(body
, strlen(body
));
155 const sipe_xml
*node
;
156 gboolean next
= TRUE
;
158 /* Root/Link: resources exposed by this server */
159 for (node
= sipe_xml_child(xml
, "Root/Link");
161 node
= sipe_xml_twin(node
)) {
162 const gchar
*token
= sipe_xml_attribute(node
, "token");
163 const gchar
*uri
= sipe_xml_attribute(node
, "href");
167 if (sipe_strcase_equal(token
, "Redirect")) {
168 SIPE_DEBUG_INFO("sipe_lync_autodiscover_parse: redirect to %s",
170 lync_request(sipe_private
, request
, uri
, NULL
);
175 } else if (sipe_strcase_equal(token
, "User")) {
176 SIPE_DEBUG_INFO("sipe_lync_autodiscover_parse: user %s",
179 /* remember URI for authentication failure */
180 request
->uri
= g_strdup(uri
);
182 lync_request(sipe_private
, request
, uri
, NULL
);
187 SIPE_DEBUG_INFO("sipe_lync_autodiscover_parse: unknown token %s",
192 /* User/Link: topology information of the user’s home server */
193 for (node
= sipe_xml_child(xml
, "User/Link");
195 node
= sipe_xml_twin(node
)) {
196 const gchar
*token
= sipe_xml_attribute(node
, "token");
197 const gchar
*uri
= sipe_xml_attribute(node
, "href");
201 if (sipe_strcase_equal(token
, "Redirect")) {
202 SIPE_DEBUG_INFO("sipe_lync_autodiscover_parse: redirect to %s",
204 lync_request(sipe_private
, request
, uri
, NULL
);
208 SIPE_DEBUG_INFO("sipe_lync_autodiscover_parse: unknown token %s",
213 /* if nothing else matched */
215 const gchar
*access_location
= sipe_xml_attribute(xml
, "AccessLocation");
217 /* User: topology information of the user’s home server */
218 if ((node
= sipe_xml_child(xml
, "User")) != NULL
) {
219 gpointer id
= request
->id
;
221 /* Active request? */
225 /* List is reversed, i.e. internal will be tried first */
226 servers
= g_slist_prepend(NULL
, NULL
);
228 if (!access_location
||
229 sipe_strcase_equal(access_location
, "external")) {
230 servers
= sipe_lync_autodiscover_add(servers
,
232 "SipClientExternalAccess");
235 if (!access_location
||
236 sipe_strcase_equal(access_location
, "internal")) {
237 servers
= sipe_lync_autodiscover_add(servers
,
239 "SipClientInternalAccess");
242 /* Callback takes ownership of servers list */
243 (*request
->cb
)(sipe_private
, servers
, request
->cb_data
);
245 /* We're done with requests for this callback */
246 FOR_ALL_REQUESTS_WITH_SAME_ID( \
253 /* Request completed */
255 sipe_lync_autodiscover_request_free(sipe_private
, request
);
256 /* request is invalid */
263 sipe_lync_autodiscover_queue_request(sipe_private
, request
);
266 static void sipe_lync_autodiscover_webticket(struct sipe_core_private
*sipe_private
,
267 SIPE_UNUSED_PARAMETER
const gchar
*base_uri
,
268 const gchar
*auth_uri
,
269 const gchar
*wsse_security
,
270 SIPE_UNUSED_PARAMETER
const gchar
*failure_msg
,
271 gpointer callback_data
)
273 struct lync_autodiscover_request
*request
= callback_data
;
276 /* Extract SAML Assertion from WSSE Security XML text */
278 ((saml
= sipe_xml_extract_raw(wsse_security
,
281 gchar
*base64
= g_base64_encode((const guchar
*) saml
,
283 gchar
*headers
= g_strdup_printf(LYNC_AUTODISCOVER_ACCEPT_HEADER
284 "X-MS-WebTicket: opaque=%s\r\n",
288 SIPE_DEBUG_INFO("sipe_lync_autodiscover_webticket: got ticket for Auth URI %s",
292 lync_request(sipe_private
, request
, auth_uri
, headers
);
296 sipe_lync_autodiscover_queue_request(sipe_private
, request
);
299 static void sipe_lync_autodiscover_cb(struct sipe_core_private
*sipe_private
,
303 gpointer callback_data
)
305 struct lync_autodiscover_request
*request
= callback_data
;
306 const gchar
*type
= sipe_utils_nameval_find(headers
, "Content-Type");
307 gchar
*uri
= request
->uri
;
309 request
->request
= NULL
;
313 case SIPE_HTTP_STATUS_OK
:
314 /* only accept Autodiscover XML responses */
315 if (body
&& g_str_has_prefix(type
, "application/vnd.microsoft.rtc.autodiscover+xml"))
316 sipe_lync_autodiscover_parse(sipe_private
, request
, body
);
318 sipe_lync_autodiscover_queue_request(sipe_private
, request
);
321 case SIPE_HTTP_STATUS_FAILED
:
324 /* check for authentication failure */
325 const gchar
*webticket_uri
= sipe_utils_nameval_find(headers
,
326 "X-MS-WebTicketURL");
328 if (!(webticket_uri
&&
329 sipe_webticket_request_with_auth(sipe_private
,
333 sipe_lync_autodiscover_webticket
,
335 sipe_lync_autodiscover_queue_request(sipe_private
, request
);
337 sipe_lync_autodiscover_queue_request(sipe_private
, request
);
341 case SIPE_HTTP_STATUS_ABORTED
:
342 /* we are not allowed to generate new requests */
343 sipe_lync_autodiscover_request_free(sipe_private
, request
);
347 sipe_lync_autodiscover_queue_request(sipe_private
, request
);
354 /* Proceed to next method for request */
355 static void sipe_lync_autodiscover_request(struct sipe_core_private
*sipe_private
,
356 struct lync_autodiscover_request
*request
)
358 gpointer id
= request
->id
;
360 /* Active request? */
362 static const gchar
*methods
[] = {
363 "%s://LyncDiscoverInternal.%s/?sipuri=%s",
364 "%s://LyncDiscover.%s/?sipuri=%s",
368 request
->is_pending
= TRUE
;
373 request
->method
= methods
;
375 if (*request
->method
) {
376 gchar
*uri
= g_strdup_printf(*request
->method
,
378 SIPE_CORE_PUBLIC
->sip_domain
,
379 sipe_private
->username
);
381 SIPE_DEBUG_INFO("sipe_lync_autodiscover_request: trying '%s'", uri
);
383 lync_request(sipe_private
, request
, uri
, NULL
);
389 /* Count entries with the same request ID */
390 FOR_ALL_REQUESTS_WITH_SAME_ID( \
396 * This is the last pending request for this
397 * ID, i.e. autodiscover has failed. Create
398 * empty server list and return it.
400 GSList
*servers
= g_slist_prepend(NULL
, NULL
);
402 /* All methods tried, indicate failure to caller */
403 SIPE_DEBUG_INFO_NOFORMAT("sipe_lync_autodiscover_request: no more methods to try!");
405 /* Callback takes ownership of servers list */
406 (*request
->cb
)(sipe_private
, servers
, request
->cb_data
);
409 /* Request completed */
411 sipe_lync_autodiscover_request_free(sipe_private
, request
);
412 /* request is invalid */
415 /* Inactive request, callback already NULL */
416 sipe_lync_autodiscover_request_free(sipe_private
, request
);
417 /* request is invalid */
421 /* Proceed to next method for all requests */
422 static void sipe_lync_autodiscover_queue_request(struct sipe_core_private
*sipe_private
,
423 struct lync_autodiscover_request
*request
)
425 gpointer id
= request
->id
;
427 /* This request is ready to proceed to next method */
428 request
->is_pending
= FALSE
;
430 /* Is any request for the same ID still pending? */
431 FOR_ALL_REQUESTS_WITH_SAME_ID( \
432 if (lar
->is_pending
) \
436 SIPE_DEBUG_INFO_NOFORMAT("sipe_lync_autodiscover_queue_request: proceed in lockstep");
438 /* No, proceed to next method for all requests */
439 FOR_ALL_REQUESTS_WITH_SAME_ID( \
440 sipe_lync_autodiscover_request(sipe_private
, \
445 static gpointer
sipe_lync_autodiscover_create(struct sipe_core_private
*sipe_private
,
447 const gchar
*protocol
,
448 sipe_lync_autodiscover_callback
*callback
,
449 gpointer callback_data
)
451 struct sipe_lync_autodiscover
*sla
= sipe_private
->lync_autodiscover
;
452 struct lync_autodiscover_request
*request
= g_new0(struct lync_autodiscover_request
, 1);
454 /* use address of first request structure as unique ID */
458 request
->protocol
= protocol
;
459 request
->cb
= callback
;
460 request
->cb_data
= callback_data
;
462 request
->session
= sipe_svc_session_start();
464 sla
->pending_requests
= g_slist_prepend(sla
->pending_requests
,
467 sipe_lync_autodiscover_request(sipe_private
, request
);
472 void sipe_lync_autodiscover_start(struct sipe_core_private
*sipe_private
,
473 sipe_lync_autodiscover_callback
*callback
,
474 gpointer callback_data
)
478 #define CREATE(protocol) \
479 id = sipe_lync_autodiscover_create(sipe_private, \
488 void sipe_lync_autodiscover_init(struct sipe_core_private
*sipe_private
)
490 struct sipe_lync_autodiscover
*sla
= g_new0(struct sipe_lync_autodiscover
, 1);
492 sipe_private
->lync_autodiscover
= sla
;
495 void sipe_lync_autodiscover_free(struct sipe_core_private
*sipe_private
)
497 struct sipe_lync_autodiscover
*sla
= sipe_private
->lync_autodiscover
;
499 while (sla
->pending_requests
)
500 sipe_lync_autodiscover_request_free(sipe_private
,
501 sla
->pending_requests
->data
);
504 sipe_private
->lync_autodiscover
= NULL
;