1 /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
3 * Copyright (C) 2012 Red Hat, Inc.
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2 of the License, or (at your option) any later version.
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
15 * You should have received a copy of the GNU Lesser General
16 * Public License along with this library; if not, write to the
17 * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
18 * Boston, MA 02111-1307, USA.
20 * Author: Debarshi Ray <debarshir@gnome.org>
23 /* Based on code by the Evolution team.
25 * This was originally written as a part of evolution-ews:
26 * evolution-ews/src/server/e-ews-connection.c
30 #include <glib/gi18n-lib.h>
32 #include <libsoup/soup.h>
33 #include <libxml/xmlIO.h>
35 #include <libedataserver/libedataserver.h>
37 #include "goaewsclient.h"
40 GCancellable *cancellable;
43 gulong cancellable_id;
54 } AutodiscoverAuthData;
56 #ifdef HAVE_GOA_PASSWORD_BASED
58 ews_autodiscover_data_free (AutodiscoverData *data)
60 if (data->cancellable_id > 0) {
61 g_cancellable_disconnect (
62 data->cancellable, data->cancellable_id);
63 g_object_unref (data->cancellable);
66 /* soup_session_queue_message stole the references to data->msgs */
67 xmlOutputBufferClose (data->buf);
68 g_object_unref (data->session);
70 g_free (data->as_url);
71 g_free (data->oab_url);
73 g_slice_free (AutodiscoverData, data);
77 ews_autodiscover_auth_data_free (gpointer data,
80 AutodiscoverAuthData *auth = data;
82 g_free (auth->password);
83 g_free (auth->username);
84 g_slice_free (AutodiscoverAuthData, auth);
88 ews_check_node (const xmlNode *node,
91 g_return_val_if_fail (node != NULL, FALSE);
93 return (node->type == XML_ELEMENT_NODE) &&
94 (g_strcmp0 ((gchar *) node->name, name) == 0);
98 ews_authenticate (SoupSession *session,
102 AutodiscoverAuthData *data)
107 soup_auth_authenticate (auth, data->username, data->password);
111 ews_autodiscover_cancelled_cb (GCancellable *cancellable,
112 AutodiscoverData *data)
114 soup_session_abort (data->session);
118 ews_autodiscover_parse_protocol (xmlNode *node,
119 AutodiscoverData *data)
121 gboolean got_as_url = FALSE;
122 gboolean got_oab_url = FALSE;
124 for (node = node->children; node; node = node->next) {
127 if (ews_check_node (node, "ASUrl")) {
128 content = xmlNodeGetContent (node);
129 data->as_url = g_strdup ((gchar *) content);
133 } else if (ews_check_node (node, "OABUrl")) {
134 content = xmlNodeGetContent (node);
135 data->oab_url = g_strdup ((gchar *) content);
140 if (got_as_url && got_oab_url)
144 return (got_as_url && got_oab_url);
148 ews_autodiscover_response_cb (SoupSession *session,
152 GSimpleAsyncResult *simple;
153 AutodiscoverData *data;
154 gboolean success = FALSE;
160 GError *error = NULL;
162 simple = G_SIMPLE_ASYNC_RESULT (user_data);
163 data = g_simple_async_result_get_op_res_gpointer (simple);
165 status = msg->status_code;
166 if (status == SOUP_STATUS_CANCELLED)
169 size = sizeof (data->msgs) / sizeof (data->msgs[0]);
171 for (idx = 0; idx < size; idx++) {
172 if (data->msgs[idx] == msg)
178 data->msgs[idx] = NULL;
180 if (status != SOUP_STATUS_OK) {
183 GOA_ERROR_FAILED, /* TODO: more specific */
184 _("Code: %u - Unexpected response from server"),
190 soup_message_body_flatten (
191 SOUP_MESSAGE (msg)->response_body));
193 g_debug ("The response headers");
194 g_debug ("===================");
195 g_debug ("%s", SOUP_MESSAGE (msg)->response_body->data);
197 doc = xmlReadMemory (
198 msg->response_body->data,
199 msg->response_body->length,
200 "autodiscover.xml", NULL, 0);
204 GOA_ERROR_FAILED, /* TODO: more specific */
205 _("Failed to parse autodiscover response XML"));
209 node = xmlDocGetRootElement (doc);
210 if (g_strcmp0 ((gchar *) node->name, "Autodiscover") != 0) {
213 GOA_ERROR_FAILED, /* TODO: more specific */
214 _("Failed to find Autodiscover element"));
218 for (node = node->children; node; node = node->next) {
219 if (ews_check_node (node, "Response"))
225 GOA_ERROR_FAILED, /* TODO: more specific */
226 _("Failed to find Response element"));
230 for (node = node->children; node; node = node->next) {
231 if (ews_check_node (node, "Account"))
237 GOA_ERROR_FAILED, /* TODO: more specific */
238 _("Failed to find Account element"));
242 for (node = node->children; node; node = node->next) {
243 if (ews_check_node (node, "Protocol")) {
244 success = ews_autodiscover_parse_protocol (node, data);
251 GOA_ERROR_FAILED, /* TODO: more specific */
252 _("Failed to find ASUrl and OABUrl in autodiscover response"));
256 for (idx = 0; idx < size; idx++) {
257 if (data->msgs[idx] != NULL) {
258 /* Since we are cancelling from the same thread
259 * that we queued the message, the callback (ie.
260 * this function) will be invoked before
261 * soup_session_cancel_message returns. */
262 soup_session_cancel_message (
263 data->session, data->msgs[idx],
264 SOUP_STATUS_CANCELLED);
265 data->msgs[idx] = NULL;
271 for (idx = 0; idx < size; idx++) {
272 if (data->msgs[idx] != NULL) {
273 /* There's another request outstanding.
274 * Hope that it has better luck. */
275 g_clear_error (&error);
279 g_simple_async_result_take_error (simple, error);
282 g_simple_async_result_complete_in_idle (simple);
283 g_object_unref (simple);
287 ews_create_autodiscover_xml (const gchar *email)
293 doc = xmlNewDoc ((xmlChar *) "1.0");
295 node = xmlNewDocNode (doc, NULL, (xmlChar *) "Autodiscover", NULL);
296 xmlDocSetRootElement (doc, node);
299 (xmlChar *) "http://schemas.microsoft.com/exchange/autodiscover/outlook/requestschema/2006",
302 node = xmlNewChild (node, ns, (xmlChar *) "Request", NULL);
303 xmlNewChild (node, ns, (xmlChar *) "EMailAddress", (xmlChar *) email);
306 (xmlChar *) "AcceptableResponseSchema",
307 (xmlChar *) "http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a");
313 compat_libxml_output_buffer_get_content (xmlOutputBufferPtr buf,
316 #ifdef LIBXML2_NEW_BUFFER
317 *out_len = xmlOutputBufferGetSize (buf);
318 return xmlOutputBufferGetContent (buf);
320 *out_len = buf->buffer->use;
321 return buf->buffer->content;
326 ews_post_restarted_cb (SoupMessage *msg,
329 xmlOutputBuffer *buf = data;
330 gconstpointer buf_content;
333 /* In violation of RFC2616, libsoup will change a
334 * POST request to a GET on receiving a 302 redirect. */
335 g_debug ("Working around libsoup bug with redirect");
336 g_object_set (msg, SOUP_MESSAGE_METHOD, "POST", NULL);
338 buf_content = compat_libxml_output_buffer_get_content (buf, &buf_size);
339 soup_message_set_request (
340 msg, "text/xml; charset=utf-8",
342 buf_content, buf_size);
346 ews_create_msg_for_url (const gchar *url,
347 xmlOutputBuffer *buf)
350 gconstpointer buf_content;
353 msg = soup_message_new (buf != NULL ? "POST" : "GET", url);
354 soup_message_headers_append (
355 msg->request_headers, "User-Agent", "libews/0.1");
358 buf_content = compat_libxml_output_buffer_get_content (buf, &buf_size);
359 soup_message_set_request (
360 msg, "text/xml; charset=utf-8",
362 buf_content, buf_size);
365 G_CALLBACK (ews_post_restarted_cb), buf);
369 soup_message_body_flatten (
370 SOUP_MESSAGE (msg)->request_body));
372 g_debug ("The request headers");
373 g_debug ("===================");
374 g_debug ("%s", SOUP_MESSAGE (msg)->request_body->data);
378 #endif /* HAVE_GOA_PASSWORD_BASED */
381 goa_ews_autodiscover (GoaObject *goa_object,
382 GCancellable *cancellable,
383 GAsyncReadyCallback callback,
386 /* XXX This function is only called if HAVE_GOA_PASSWORD_BASED
387 * is defined, so don't worry about a fallback behavior. */
388 #ifdef HAVE_GOA_PASSWORD_BASED
389 GoaAccount *goa_account;
390 GoaExchange *goa_exchange;
391 GoaPasswordBased *goa_password;
392 GSimpleAsyncResult *simple;
393 AutodiscoverData *data;
394 AutodiscoverAuthData *auth;
398 xmlOutputBuffer *buf;
401 gchar *password = NULL;
402 GError *error = NULL;
404 g_return_if_fail (GOA_IS_OBJECT (goa_object));
406 goa_account = goa_object_get_account (goa_object);
407 goa_exchange = goa_object_get_exchange (goa_object);
408 goa_password = goa_object_get_password_based (goa_object);
410 email = goa_account_dup_presentation_identity (goa_account);
411 host = goa_exchange_dup_host (goa_exchange);
413 doc = ews_create_autodiscover_xml (email);
414 buf = xmlAllocOutputBuffer (NULL);
415 xmlNodeDumpOutput (buf, doc, xmlDocGetRootElement (doc), 0, 1, NULL);
416 xmlOutputBufferFlush (buf);
418 url1 = g_strdup_printf (
419 "https://%s/autodiscover/autodiscover.xml", host);
420 url2 = g_strdup_printf (
421 "https://autodiscover.%s/autodiscover/autodiscover.xml", host);
426 /* http://msdn.microsoft.com/en-us/library/ee332364.aspx says we are
427 * supposed to try $domain and then autodiscover.$domain. But some
428 * people have broken firewalls on the former which drop packets
429 * instead of rejecting connections, and make the request take ages
430 * to time out. So run both queries in parallel and let the fastest
431 * (successful) one win. */
432 data = g_slice_new0 (AutodiscoverData);
434 data->msgs[0] = ews_create_msg_for_url (url1, buf);
435 data->msgs[1] = ews_create_msg_for_url (url2, buf);
436 data->session = soup_session_async_new_with_options (
437 SOUP_SESSION_USE_NTLM, TRUE,
438 SOUP_SESSION_USE_THREAD_CONTEXT, TRUE,
439 SOUP_SESSION_TIMEOUT, 90,
441 if (G_IS_CANCELLABLE (cancellable)) {
442 data->cancellable = g_object_ref (cancellable);
443 data->cancellable_id = g_cancellable_connect (
445 G_CALLBACK (ews_autodiscover_cancelled_cb),
449 simple = g_simple_async_result_new (
450 G_OBJECT (goa_object), callback,
451 user_data, goa_ews_autodiscover);
453 g_simple_async_result_set_check_cancellable (simple, cancellable);
455 g_simple_async_result_set_op_res_gpointer (
456 simple, data, (GDestroyNotify) ews_autodiscover_data_free);
458 goa_password_based_call_get_password_sync (
459 goa_password, "", &password, cancellable, &error);
463 ((password != NULL) && (error == NULL)) ||
464 ((password == NULL) && (error != NULL)));
469 username = goa_account_dup_identity (goa_account);
471 auth = g_slice_new0 (AutodiscoverAuthData);
472 auth->username = username; /* takes ownership */
473 auth->password = password; /* takes ownership */
475 g_signal_connect_data (
476 data->session, "authenticate",
477 G_CALLBACK (ews_authenticate), auth,
478 ews_autodiscover_auth_data_free, 0);
480 soup_session_queue_message (
481 data->session, data->msgs[0],
482 ews_autodiscover_response_cb, simple);
483 soup_session_queue_message (
484 data->session, data->msgs[1],
485 ews_autodiscover_response_cb, simple);
487 g_simple_async_result_take_error (simple, error);
488 g_simple_async_result_complete_in_idle (simple);
489 g_object_unref (simple);
496 g_object_unref (goa_account);
497 g_object_unref (goa_exchange);
498 g_object_unref (goa_password);
499 #endif /* HAVE_GOA_PASSWORD_BASED */
503 goa_ews_autodiscover_finish (GoaObject *goa_object,
504 GAsyncResult *result,
509 GSimpleAsyncResult *simple;
510 AutodiscoverData *data;
512 g_return_val_if_fail (
513 g_simple_async_result_is_valid (
514 result, G_OBJECT (goa_object),
515 goa_ews_autodiscover), FALSE);
517 simple = G_SIMPLE_ASYNC_RESULT (result);
518 data = g_simple_async_result_get_op_res_gpointer (simple);
520 if (g_simple_async_result_propagate_error (simple, error))
523 if (out_as_url != NULL) {
524 *out_as_url = data->as_url;
528 if (out_oab_url != NULL) {
529 *out_oab_url = data->oab_url;
530 data->oab_url = NULL;
537 goa_ews_autodiscover_sync (GoaObject *goa_object,
540 GCancellable *cancellable,
543 EAsyncClosure *closure;
544 GAsyncResult *result;
547 g_return_val_if_fail (GOA_IS_OBJECT (goa_object), FALSE);
549 closure = e_async_closure_new ();
551 goa_ews_autodiscover (
552 goa_object, cancellable,
553 e_async_closure_callback, closure);
555 result = e_async_closure_wait (closure);
557 success = goa_ews_autodiscover_finish (
558 goa_object, result, out_as_url, out_oab_url, error);
560 e_async_closure_free (closure);