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 has_suffix_icmp (const gchar *text,
123 g_return_val_if_fail (text != NULL, FALSE);
124 g_return_val_if_fail (suffix != NULL, FALSE);
126 tlen = strlen (text);
127 slen = strlen (suffix);
129 if (!*text || !*suffix || tlen < slen)
132 for (ii = 0; ii < slen; ii++) {
133 if (g_ascii_tolower (text[tlen - ii - 1]) !=
134 g_ascii_tolower (suffix[slen - ii - 1]))
142 ews_autodiscover_parse_protocol (xmlNode *node,
143 AutodiscoverData *data)
145 gboolean got_as_url = FALSE;
146 gboolean got_oab_url = FALSE;
148 for (node = node->children; node; node = node->next) {
151 if (ews_check_node (node, "ASUrl")) {
152 content = xmlNodeGetContent (node);
153 data->as_url = g_strdup ((gchar *) content);
157 } else if (ews_check_node (node, "OABUrl")) {
158 const gchar *oab_url;
160 content = xmlNodeGetContent (node);
161 oab_url = (const char *) content;
163 if (!has_suffix_icmp (oab_url, "oab.xml")) {
166 if (g_str_has_suffix (oab_url, "/"))
167 tmp = g_strconcat (oab_url, "oab.xml", NULL);
169 tmp = g_strconcat (oab_url, "/", "oab.xml", NULL);
171 data->oab_url = tmp; /* takes ownership */
173 data->oab_url = g_strdup (oab_url);
179 if (got_as_url && got_oab_url)
183 return (got_as_url && got_oab_url);
187 ews_autodiscover_response_cb (SoupSession *session,
191 GSimpleAsyncResult *simple;
192 AutodiscoverData *data;
193 gboolean success = FALSE;
199 GError *error = NULL;
201 simple = G_SIMPLE_ASYNC_RESULT (user_data);
202 data = g_simple_async_result_get_op_res_gpointer (simple);
204 status = msg->status_code;
205 if (status == SOUP_STATUS_CANCELLED)
208 size = sizeof (data->msgs) / sizeof (data->msgs[0]);
210 for (idx = 0; idx < size; idx++) {
211 if (data->msgs[idx] == msg)
217 data->msgs[idx] = NULL;
219 if (status != SOUP_STATUS_OK) {
222 GOA_ERROR_FAILED, /* TODO: more specific */
223 _("Code: %u - Unexpected response from server"),
229 soup_message_body_flatten (
230 SOUP_MESSAGE (msg)->response_body));
232 g_debug ("The response headers");
233 g_debug ("===================");
234 g_debug ("%s", SOUP_MESSAGE (msg)->response_body->data);
236 doc = xmlReadMemory (
237 msg->response_body->data,
238 msg->response_body->length,
239 "autodiscover.xml", NULL, 0);
243 GOA_ERROR_FAILED, /* TODO: more specific */
244 _("Failed to parse autodiscover response XML"));
248 node = xmlDocGetRootElement (doc);
249 if (g_strcmp0 ((gchar *) node->name, "Autodiscover") != 0) {
252 GOA_ERROR_FAILED, /* TODO: more specific */
253 _("Failed to find Autodiscover element"));
257 for (node = node->children; node; node = node->next) {
258 if (ews_check_node (node, "Response"))
264 GOA_ERROR_FAILED, /* TODO: more specific */
265 _("Failed to find Response element"));
269 for (node = node->children; node; node = node->next) {
270 if (ews_check_node (node, "Account"))
276 GOA_ERROR_FAILED, /* TODO: more specific */
277 _("Failed to find Account element"));
281 for (node = node->children; node; node = node->next) {
282 if (ews_check_node (node, "Protocol")) {
283 success = ews_autodiscover_parse_protocol (node, data);
290 GOA_ERROR_FAILED, /* TODO: more specific */
291 _("Failed to find ASUrl and OABUrl in autodiscover response"));
295 for (idx = 0; idx < size; idx++) {
296 if (data->msgs[idx] != NULL) {
297 /* Since we are cancelling from the same thread
298 * that we queued the message, the callback (ie.
299 * this function) will be invoked before
300 * soup_session_cancel_message returns. */
301 soup_session_cancel_message (
302 data->session, data->msgs[idx],
303 SOUP_STATUS_CANCELLED);
304 data->msgs[idx] = NULL;
310 for (idx = 0; idx < size; idx++) {
311 if (data->msgs[idx] != NULL) {
312 /* There's another request outstanding.
313 * Hope that it has better luck. */
314 g_clear_error (&error);
318 g_simple_async_result_take_error (simple, error);
321 g_simple_async_result_complete_in_idle (simple);
322 g_object_unref (simple);
326 ews_create_autodiscover_xml (const gchar *email)
332 doc = xmlNewDoc ((xmlChar *) "1.0");
334 node = xmlNewDocNode (doc, NULL, (xmlChar *) "Autodiscover", NULL);
335 xmlDocSetRootElement (doc, node);
338 (xmlChar *) "http://schemas.microsoft.com/exchange/autodiscover/outlook/requestschema/2006",
341 node = xmlNewChild (node, ns, (xmlChar *) "Request", NULL);
342 xmlNewChild (node, ns, (xmlChar *) "EMailAddress", (xmlChar *) email);
345 (xmlChar *) "AcceptableResponseSchema",
346 (xmlChar *) "http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a");
352 compat_libxml_output_buffer_get_content (xmlOutputBufferPtr buf,
355 #ifdef LIBXML2_NEW_BUFFER
356 *out_len = xmlOutputBufferGetSize (buf);
357 return xmlOutputBufferGetContent (buf);
359 *out_len = buf->buffer->use;
360 return buf->buffer->content;
365 ews_post_restarted_cb (SoupMessage *msg,
368 xmlOutputBuffer *buf = data;
369 gconstpointer buf_content;
372 /* In violation of RFC2616, libsoup will change a
373 * POST request to a GET on receiving a 302 redirect. */
374 g_debug ("Working around libsoup bug with redirect");
375 g_object_set (msg, SOUP_MESSAGE_METHOD, "POST", NULL);
377 buf_content = compat_libxml_output_buffer_get_content (buf, &buf_size);
378 soup_message_set_request (
379 msg, "text/xml; charset=utf-8",
381 buf_content, buf_size);
385 ews_create_msg_for_url (const gchar *url,
386 xmlOutputBuffer *buf)
389 gconstpointer buf_content;
392 msg = soup_message_new (buf != NULL ? "POST" : "GET", url);
393 soup_message_headers_append (
394 msg->request_headers, "User-Agent", "libews/0.1");
397 buf_content = compat_libxml_output_buffer_get_content (buf, &buf_size);
398 soup_message_set_request (
399 msg, "text/xml; charset=utf-8",
401 buf_content, buf_size);
404 G_CALLBACK (ews_post_restarted_cb), buf);
408 soup_message_body_flatten (
409 SOUP_MESSAGE (msg)->request_body));
411 g_debug ("The request headers");
412 g_debug ("===================");
413 g_debug ("%s", SOUP_MESSAGE (msg)->request_body->data);
417 #endif /* HAVE_GOA_PASSWORD_BASED */
420 goa_ews_autodiscover (GoaObject *goa_object,
421 GCancellable *cancellable,
422 GAsyncReadyCallback callback,
425 /* XXX This function is only called if HAVE_GOA_PASSWORD_BASED
426 * is defined, so don't worry about a fallback behavior. */
427 #ifdef HAVE_GOA_PASSWORD_BASED
428 GoaAccount *goa_account;
429 GoaExchange *goa_exchange;
430 GoaPasswordBased *goa_password;
431 GSimpleAsyncResult *simple;
432 AutodiscoverData *data;
433 AutodiscoverAuthData *auth;
437 xmlOutputBuffer *buf;
440 gchar *password = NULL;
441 GError *error = NULL;
443 g_return_if_fail (GOA_IS_OBJECT (goa_object));
445 goa_account = goa_object_get_account (goa_object);
446 goa_exchange = goa_object_get_exchange (goa_object);
447 goa_password = goa_object_get_password_based (goa_object);
449 email = goa_account_dup_presentation_identity (goa_account);
450 host = goa_exchange_dup_host (goa_exchange);
452 doc = ews_create_autodiscover_xml (email);
453 buf = xmlAllocOutputBuffer (NULL);
454 xmlNodeDumpOutput (buf, doc, xmlDocGetRootElement (doc), 0, 1, NULL);
455 xmlOutputBufferFlush (buf);
457 url1 = g_strdup_printf (
458 "https://%s/autodiscover/autodiscover.xml", host);
459 url2 = g_strdup_printf (
460 "https://autodiscover.%s/autodiscover/autodiscover.xml", host);
465 /* http://msdn.microsoft.com/en-us/library/ee332364.aspx says we are
466 * supposed to try $domain and then autodiscover.$domain. But some
467 * people have broken firewalls on the former which drop packets
468 * instead of rejecting connections, and make the request take ages
469 * to time out. So run both queries in parallel and let the fastest
470 * (successful) one win. */
471 data = g_slice_new0 (AutodiscoverData);
473 data->msgs[0] = ews_create_msg_for_url (url1, buf);
474 data->msgs[1] = ews_create_msg_for_url (url2, buf);
475 data->session = soup_session_async_new_with_options (
476 SOUP_SESSION_USE_NTLM, TRUE,
477 SOUP_SESSION_USE_THREAD_CONTEXT, TRUE,
478 SOUP_SESSION_TIMEOUT, 90,
480 if (G_IS_CANCELLABLE (cancellable)) {
481 data->cancellable = g_object_ref (cancellable);
482 data->cancellable_id = g_cancellable_connect (
484 G_CALLBACK (ews_autodiscover_cancelled_cb),
488 simple = g_simple_async_result_new (
489 G_OBJECT (goa_object), callback,
490 user_data, goa_ews_autodiscover);
492 g_simple_async_result_set_check_cancellable (simple, cancellable);
494 g_simple_async_result_set_op_res_gpointer (
495 simple, data, (GDestroyNotify) ews_autodiscover_data_free);
497 goa_password_based_call_get_password_sync (
498 goa_password, "", &password, cancellable, &error);
502 ((password != NULL) && (error == NULL)) ||
503 ((password == NULL) && (error != NULL)));
508 username = goa_account_dup_identity (goa_account);
510 auth = g_slice_new0 (AutodiscoverAuthData);
511 auth->username = username; /* takes ownership */
512 auth->password = password; /* takes ownership */
514 g_signal_connect_data (
515 data->session, "authenticate",
516 G_CALLBACK (ews_authenticate), auth,
517 ews_autodiscover_auth_data_free, 0);
519 soup_session_queue_message (
520 data->session, data->msgs[0],
521 ews_autodiscover_response_cb, simple);
522 soup_session_queue_message (
523 data->session, data->msgs[1],
524 ews_autodiscover_response_cb, simple);
526 g_simple_async_result_take_error (simple, error);
527 g_simple_async_result_complete_in_idle (simple);
528 g_object_unref (simple);
535 g_object_unref (goa_account);
536 g_object_unref (goa_exchange);
537 g_object_unref (goa_password);
538 #endif /* HAVE_GOA_PASSWORD_BASED */
542 goa_ews_autodiscover_finish (GoaObject *goa_object,
543 GAsyncResult *result,
548 GSimpleAsyncResult *simple;
549 AutodiscoverData *data;
551 g_return_val_if_fail (
552 g_simple_async_result_is_valid (
553 result, G_OBJECT (goa_object),
554 goa_ews_autodiscover), FALSE);
556 simple = G_SIMPLE_ASYNC_RESULT (result);
557 data = g_simple_async_result_get_op_res_gpointer (simple);
559 if (g_simple_async_result_propagate_error (simple, error))
562 if (out_as_url != NULL) {
563 *out_as_url = data->as_url;
567 if (out_oab_url != NULL) {
568 *out_oab_url = data->oab_url;
569 data->oab_url = NULL;
576 goa_ews_autodiscover_sync (GoaObject *goa_object,
579 GCancellable *cancellable,
582 EAsyncClosure *closure;
583 GAsyncResult *result;
586 g_return_val_if_fail (GOA_IS_OBJECT (goa_object), FALSE);
588 closure = e_async_closure_new ();
590 goa_ews_autodiscover (
591 goa_object, cancellable,
592 e_async_closure_callback, closure);
594 result = e_async_closure_wait (closure);
596 success = goa_ews_autodiscover_finish (
597 goa_object, result, out_as_url, out_oab_url, error);
599 e_async_closure_free (closure);