2 * @file sipe-ews-autodiscover.c
6 * Copyright (C) 2013-2015 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 * - POX: plain old XML autodiscover
26 * - [MS-OXDSCLI]: http://msdn.microsoft.com/en-us/library/cc463896.aspx
27 * - Autdiscover for Exchange:
28 * http://msdn.microsoft.com/en-us/library/office/jj900169.aspx
29 * - POX autodiscover: http://msdn.microsoft.com/en-us/library/office/aa581522.aspx
30 * - POX redirect: http://msdn.microsoft.com/en-us/library/office/dn467392.aspx
37 #include "sipe-backend.h"
38 #include "sipe-common.h"
39 #include "sipe-core.h"
40 #include "sipe-core-private.h"
41 #include "sipe-ews-autodiscover.h"
42 #include "sipe-http.h"
43 #include "sipe-utils.h"
46 struct sipe_ews_autodiscover_cb
{
47 sipe_ews_autodiscover_callback
*cb
;
51 struct autodiscover_method
{
52 const gchar
*template;
56 struct sipe_ews_autodiscover
{
57 struct sipe_ews_autodiscover_data
*data
;
58 struct sipe_http_request
*request
;
61 const struct autodiscover_method
*method
;
66 static void sipe_ews_autodiscover_complete(struct sipe_core_private
*sipe_private
,
67 struct sipe_ews_autodiscover_data
*ews_data
)
69 struct sipe_ews_autodiscover
*sea
= sipe_private
->ews_autodiscover
;
70 GSList
*entry
= sea
->callbacks
;
73 struct sipe_ews_autodiscover_cb
*sea_cb
= entry
->data
;
74 sea_cb
->cb(sipe_private
, ews_data
, sea_cb
->cb_data
);
78 g_slist_free(sea
->callbacks
);
79 sea
->callbacks
= NULL
;
80 sea
->completed
= TRUE
;
83 static void sipe_ews_autodiscover_request(struct sipe_core_private
*sipe_private
,
84 gboolean next_method
);
85 static gboolean
sipe_ews_autodiscover_url(struct sipe_core_private
*sipe_private
,
87 static void sipe_ews_autodiscover_parse(struct sipe_core_private
*sipe_private
,
90 struct sipe_ews_autodiscover
*sea
= sipe_private
->ews_autodiscover
;
91 struct sipe_ews_autodiscover_data
*ews_data
= sea
->data
=
92 g_new0(struct sipe_ews_autodiscover_data
, 1);
93 sipe_xml
*xml
= sipe_xml_parse(body
, strlen(body
));
94 const sipe_xml
*account
= sipe_xml_child(xml
, "Response/Account");
95 gboolean complete
= TRUE
;
97 /* valid POX autodiscover response? */
101 /* POX autodiscover settings? */
102 if ((node
= sipe_xml_child(account
, "Protocol")) != NULL
) {
104 /* Autodiscover/Response/User/LegacyDN (requires trimming) */
105 gchar
*tmp
= sipe_xml_data(sipe_xml_child(xml
,
106 "Response/User/LegacyDN"));
108 ews_data
->legacy_dn
= g_strstrip(tmp
);
110 /* extract settings */
111 for (; node
; node
= sipe_xml_twin(node
)) {
112 gchar
*type
= sipe_xml_data(sipe_xml_child(node
,
115 /* Exchange or Office 365 */
116 if (sipe_strequal("EXCH", type
) ||
117 sipe_strequal("EXPR", type
)) {
119 #define _URL(name, field) \
120 if (!ews_data->field) { \
121 ews_data->field = sipe_xml_data(sipe_xml_child(node, #name)); \
122 SIPE_DEBUG_INFO("sipe_ews_autodiscover_parse: " #field " = '%s'", \
123 ews_data->field ? ews_data->field : "<NOT FOUND>"); \
126 /* use first entry */
128 _URL(EwsUrl
, ews_url
);
129 _URL(OABUrl
, oab_url
);
130 _URL(OOFUrl
, oof_url
);
137 /* POX autodiscover redirect to new email address? */
138 } else if ((node
= sipe_xml_child(account
, "RedirectAddr")) != NULL
) {
139 gchar
*addr
= sipe_xml_data(node
);
142 * Sanity checks for new email address:
143 * - must contain a "@" character
144 * - must be different from current address
146 if (addr
&& strchr(addr
, '@') &&
147 !sipe_strequal(sea
->email
, addr
)) {
150 addr
= NULL
; /* sea takes ownership */
152 SIPE_DEBUG_INFO("sipe_ews_autodiscover_parse: restarting with email address '%s'",
155 /* restart process with new email address */
158 sipe_ews_autodiscover_request(sipe_private
,
163 /* POX autodiscover redirect to new URL? */
164 } else if ((node
= sipe_xml_child(account
, "RedirectUrl")) != NULL
) {
165 gchar
*url
= sipe_xml_data(node
);
167 if (!is_empty(url
)) {
168 SIPE_DEBUG_INFO("sipe_ews_autodiscover_parse: redirected to URL '%s'",
170 complete
= !sipe_ews_autodiscover_url(sipe_private
,
175 /* ignore all other POX autodiscover responses */
177 SIPE_DEBUG_ERROR_NOFORMAT("sipe_ews_autodiscover_parse: unknown response detected");
183 sipe_ews_autodiscover_complete(sipe_private
, ews_data
);
186 static void sipe_ews_autodiscover_response(struct sipe_core_private
*sipe_private
,
192 struct sipe_ews_autodiscover
*sea
= data
;
193 const gchar
*type
= sipe_utils_nameval_find(headers
, "Content-Type");
198 case SIPE_HTTP_STATUS_OK
:
199 /* only accept XML responses */
200 if (body
&& g_str_has_prefix(type
, "text/xml"))
201 sipe_ews_autodiscover_parse(sipe_private
, body
);
203 sipe_ews_autodiscover_request(sipe_private
, TRUE
);
206 case SIPE_HTTP_STATUS_CLIENT_FORBIDDEN
:
208 * Authentication succeeded but we still weren't allowed to
209 * view the page. At least at our work place this error is
210 * temporary, i.e. the next access with the exact same
211 * authentication succeeds.
213 * Let's try again, but only once...
215 sipe_ews_autodiscover_request(sipe_private
, !sea
->retry
);
218 case SIPE_HTTP_STATUS_ABORTED
:
219 /* we are not allowed to generate new requests */
223 sipe_ews_autodiscover_request(sipe_private
, TRUE
);
228 static gboolean
sipe_ews_autodiscover_url(struct sipe_core_private
*sipe_private
,
231 struct sipe_ews_autodiscover
*sea
= sipe_private
->ews_autodiscover
;
232 gchar
*body
= g_strdup_printf("<Autodiscover xmlns=\"http://schemas.microsoft.com/exchange/autodiscover/outlook/requestschema/2006\">"
234 " <EMailAddress>%s</EMailAddress>"
235 " <AcceptableResponseSchema>http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a</AcceptableResponseSchema>"
240 SIPE_DEBUG_INFO("sipe_ews_autodiscover_url: trying '%s'", url
);
242 sea
->request
= sipe_http_request_post(sipe_private
,
244 "Accept: text/xml\r\n",
247 sipe_ews_autodiscover_response
,
252 sipe_core_email_authentication(sipe_private
,
254 sipe_http_request_allow_redirect(sea
->request
);
255 sipe_http_request_ready(sea
->request
);
262 static void sipe_ews_autodiscover_redirect_response(struct sipe_core_private
*sipe_private
,
265 SIPE_UNUSED_PARAMETER
const gchar
*body
,
268 struct sipe_ews_autodiscover
*sea
= data
;
269 gboolean failed
= (status
!= (guint
) SIPE_HTTP_STATUS_ABORTED
);
273 /* Start attempt with URL from redirect (3xx) response */
274 if ((status
>= SIPE_HTTP_STATUS_REDIRECTION
) &&
275 (status
< SIPE_HTTP_STATUS_CLIENT_ERROR
)) {
276 const gchar
*location
= sipe_utils_nameval_find_instance(headers
,
280 failed
= !sipe_ews_autodiscover_url(sipe_private
,
285 sipe_ews_autodiscover_request(sipe_private
, TRUE
);
288 static gboolean
sipe_ews_autodiscover_redirect(struct sipe_core_private
*sipe_private
,
291 struct sipe_ews_autodiscover
*sea
= sipe_private
->ews_autodiscover
;
293 SIPE_DEBUG_INFO("sipe_ews_autodiscover_redirect: trying '%s'", url
);
295 sea
->request
= sipe_http_request_get(sipe_private
,
298 sipe_ews_autodiscover_redirect_response
,
302 sipe_http_request_ready(sea
->request
);
309 static void sipe_ews_autodiscover_request(struct sipe_core_private
*sipe_private
,
310 gboolean next_method
)
312 struct sipe_ews_autodiscover
*sea
= sipe_private
->ews_autodiscover
;
313 static const struct autodiscover_method methods
[] = {
314 { "https://Autodiscover.%s/Autodiscover/Autodiscover.xml", FALSE
},
315 { "http://Autodiscover.%s/Autodiscover/Autodiscover.xml", TRUE
},
316 { "http://Autodiscover.%s/Autodiscover/Autodiscover.xml", FALSE
},
317 { "https://%s/Autodiscover/Autodiscover.xml", FALSE
},
321 sea
->retry
= next_method
;
326 sea
->method
= methods
;
328 if (sea
->method
->template) {
329 gchar
*url
= g_strdup_printf(sea
->method
->template,
330 strstr(sea
->email
, "@") + 1);
332 if (!(sea
->method
->redirect
?
333 sipe_ews_autodiscover_redirect(sipe_private
, url
) :
334 sipe_ews_autodiscover_url(sipe_private
, url
)))
335 sipe_ews_autodiscover_request(sipe_private
, TRUE
);
340 SIPE_DEBUG_INFO_NOFORMAT("sipe_ews_autodiscover_request: no more methods to try!");
341 sipe_ews_autodiscover_complete(sipe_private
, NULL
);
345 void sipe_ews_autodiscover_start(struct sipe_core_private
*sipe_private
,
346 sipe_ews_autodiscover_callback
*callback
,
347 gpointer callback_data
)
349 struct sipe_ews_autodiscover
*sea
= sipe_private
->ews_autodiscover
;
351 if (sea
->completed
) {
352 (*callback
)(sipe_private
, sea
->data
, callback_data
);
354 struct sipe_ews_autodiscover_cb
*sea_cb
= g_new(struct sipe_ews_autodiscover_cb
, 1);
355 sea_cb
->cb
= callback
;
356 sea_cb
->cb_data
= callback_data
;
357 sea
->callbacks
= g_slist_prepend(sea
->callbacks
, sea_cb
);
360 sipe_ews_autodiscover_request(sipe_private
, TRUE
);
364 void sipe_ews_autodiscover_init(struct sipe_core_private
*sipe_private
)
366 struct sipe_ews_autodiscover
*sea
= g_new0(struct sipe_ews_autodiscover
, 1);
368 sea
->email
= g_strdup(sipe_private
->email
);
370 sipe_private
->ews_autodiscover
= sea
;
373 void sipe_ews_autodiscover_free(struct sipe_core_private
*sipe_private
)
375 struct sipe_ews_autodiscover
*sea
= sipe_private
->ews_autodiscover
;
376 struct sipe_ews_autodiscover_data
*ews_data
= sea
->data
;
377 sipe_ews_autodiscover_complete(sipe_private
, NULL
);
379 g_free((gchar
*)ews_data
->as_url
);
380 g_free((gchar
*)ews_data
->ews_url
);
381 g_free((gchar
*)ews_data
->legacy_dn
);
382 g_free((gchar
*)ews_data
->oab_url
);
383 g_free((gchar
*)ews_data
->oof_url
);