6 * Copyright (C) 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 * Implementation for Unified Contact Store [MS-OXWSCOS]
25 * <http://msdn.microsoft.com/en-us/library/jj194130.aspx>
32 #include "sipe-backend.h"
33 #include "sipe-buddy.h"
34 #include "sipe-common.h"
35 #include "sipe-core.h"
36 #include "sipe-core-private.h"
37 #include "sipe-digest.h"
38 #include "sipe-ews-autodiscover.h"
39 #include "sipe-group.h"
40 #include "sipe-http.h"
41 #include "sipe-subscriptions.h"
43 #include "sipe-utils.h"
46 typedef void (ucs_callback
)(struct sipe_core_private
*sipe_private
,
48 gpointer callback_data
);
59 struct sipe_http_request
*request
;
64 GSList
*deferred_requests
;
65 GSList
*pending_requests
;
68 gboolean shutting_down
;
71 static void sipe_ucs_deferred_free(struct sipe_core_private
*sipe_private
,
72 struct ucs_deferred
*data
)
75 /* Callback: aborted */
76 (*data
->cb
)(sipe_private
, NULL
, data
->cb_data
);
81 static void sipe_ucs_request_free(struct sipe_core_private
*sipe_private
,
82 struct ucs_request
*data
)
85 sipe_http_request_cancel(data
->request
);
87 /* Callback: aborted */
88 (*data
->cb
)(sipe_private
, NULL
, data
->cb_data
);
92 static void sipe_ucs_http_response(struct sipe_core_private
*sipe_private
,
94 SIPE_UNUSED_PARAMETER GSList
*headers
,
96 gpointer callback_data
)
98 struct ucs_request
*data
= callback_data
;
99 struct sipe_ucs
*ucs
= sipe_private
->ucs
;
101 SIPE_DEBUG_INFO("sipe_ucs_http_response: code %d", status
);
102 data
->request
= NULL
;
104 if ((status
== SIPE_HTTP_STATUS_OK
) && body
) {
105 sipe_xml
*xml
= sipe_xml_parse(body
, strlen(body
));
106 const sipe_xml
*soap_body
= sipe_xml_child(xml
, "Body");
107 /* Callback: success */
108 (*data
->cb
)(sipe_private
, soap_body
, data
->cb_data
);
111 /* Callback: failed */
112 (*data
->cb
)(sipe_private
, NULL
, data
->cb_data
);
115 /* already been called */
118 ucs
->pending_requests
= g_slist_remove(ucs
->pending_requests
,
120 sipe_ucs_request_free(sipe_private
, data
);
123 static gboolean
sipe_ucs_http_request(struct sipe_core_private
*sipe_private
,
125 ucs_callback
*callback
,
126 gpointer callback_data
)
128 struct sipe_ucs
*ucs
= sipe_private
->ucs
;
129 gboolean success
= FALSE
;
131 if (ucs
->shutting_down
) {
132 SIPE_DEBUG_ERROR("sipe_ucs_http_request: new UCS request during shutdown: THIS SHOULD NOT HAPPEN! Debugging information:\n"
134 body
? body
: "<EMPTY>");
136 } else if (ucs
->ews_url
) {
137 struct ucs_request
*data
= g_new0(struct ucs_request
, 1);
138 gchar
*soap
= g_strdup_printf("<?xml version=\"1.0\"?>\r\n"
140 " xmlns:m=\"http://schemas.microsoft.com/exchange/services/2006/messages\""
141 " xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\""
142 " xmlns:t=\"http://schemas.microsoft.com/exchange/services/2006/types\""
145 " <t:RequestServerVersion Version=\"Exchange2013\" />"
152 struct sipe_http_request
*request
= sipe_http_request_post(sipe_private
,
156 "text/xml; charset=UTF-8",
157 sipe_ucs_http_response
,
163 data
->cb_data
= callback_data
;
164 data
->request
= request
;
166 ucs
->pending_requests
= g_slist_prepend(ucs
->pending_requests
,
169 sipe_core_email_authentication(sipe_private
,
171 sipe_http_request_allow_redirect(request
);
172 sipe_http_request_ready(request
);
176 SIPE_DEBUG_ERROR_NOFORMAT("sipe_ucs_http_request: failed to create HTTP connection");
181 struct ucs_deferred
*data
= g_new0(struct ucs_deferred
, 1);
183 data
->cb_data
= callback_data
;
184 data
->body
= g_strdup(body
);
186 ucs
->deferred_requests
= g_slist_prepend(ucs
->deferred_requests
,
194 static void sipe_ucs_get_user_photo_response(struct sipe_core_private
*sipe_private
,
195 const sipe_xml
*body
,
196 gpointer callback_data
)
198 gchar
*uri
= callback_data
;
199 const sipe_xml
*node
= sipe_xml_child(body
,
200 "GetUserPhotoResponse/PictureData");
206 guchar digest
[SIPE_DIGEST_SHA1_LENGTH
];
207 gchar
*digest_string
;
209 /* decode photo data */
210 base64
= sipe_xml_data(node
);
211 photo
= g_base64_decode(base64
, &photo_size
);
214 /* EWS doesn't provide a hash -> calculate SHA-1 digest */
215 sipe_digest_sha1(photo
, photo_size
, digest
);
216 digest_string
= buff_to_hex_str(digest
,
217 SIPE_DIGEST_SHA1_LENGTH
);
219 /* backend frees "photo" */
220 sipe_backend_buddy_set_photo(SIPE_CORE_PUBLIC
,
225 g_free(digest_string
);
231 void sipe_ucs_get_photo(struct sipe_core_private
*sipe_private
,
234 gchar
*payload
= g_strdup(uri
);
235 gchar
*body
= g_strdup_printf("<m:GetUserPhoto>"
236 " <m:Email>%s</m:Email>"
237 " <m:SizeRequested>HR48x48</m:SizeRequested>"
239 sipe_get_no_sip_uri(uri
));
241 if (!sipe_ucs_http_request(sipe_private
,
243 sipe_ucs_get_user_photo_response
,
250 static void sipe_ucs_ignore_response(SIPE_UNUSED_PARAMETER
struct sipe_core_private
*sipe_private
,
251 SIPE_UNUSED_PARAMETER
const sipe_xml
*body
,
252 SIPE_UNUSED_PARAMETER gpointer callback_data
)
254 SIPE_DEBUG_INFO_NOFORMAT("sipe_ucs_ignore_response: done");
257 static void ucs_extract_keys(const sipe_xml
*persona_node
,
259 const gchar
**change
)
261 const sipe_xml
*attr_node
;
263 /* extract Exchange key - not sure if this is correct */
264 for (attr_node
= sipe_xml_child(persona_node
,
265 "Attributions/Attribution");
267 attr_node
= sipe_xml_twin(attr_node
)) {
268 const sipe_xml
*id_node
= sipe_xml_child(attr_node
,
270 gchar
*type
= sipe_xml_data(sipe_xml_child(attr_node
,
273 sipe_strequal(type
, "Lync Contacts")) {
274 *key
= sipe_xml_attribute(id_node
, "Id");
275 *change
= sipe_xml_attribute(id_node
, "ChangeKey");
283 static void sipe_ucs_add_new_im_contact_to_group_response(struct sipe_core_private
*sipe_private
,
284 const sipe_xml
*body
,
285 gpointer callback_data
)
287 gchar
*who
= callback_data
;
288 struct sipe_buddy
*buddy
= sipe_buddy_find_by_uri(sipe_private
, who
);
289 const sipe_xml
*persona_node
= sipe_xml_child(body
,
290 "AddNewImContactToGroupResponse/Persona");
294 is_empty(buddy
->exchange_key
) &&
295 is_empty(buddy
->change_key
)) {
296 const gchar
*key
= NULL
;
297 const gchar
*change
= NULL
;
299 ucs_extract_keys(persona_node
, &key
, &change
);
301 if (!is_empty(key
) && !is_empty(change
)) {
303 sipe_buddy_add_keys(sipe_private
,
308 SIPE_DEBUG_INFO("sipe_ucs_add_new_im_contact_to_group_response: persona URI '%s' key '%s' change '%s'",
309 buddy
->name
, key
, change
);
316 void sipe_ucs_group_add_buddy(struct sipe_core_private
*sipe_private
,
317 struct sipe_group
*group
,
318 struct sipe_buddy
*buddy
)
320 /* existing or new byddy? */
321 if (buddy
->exchange_key
) {
322 gchar
*body
= g_strdup_printf("<m:AddImContactToGroup>"
323 " <m:ContactId Id=\"%s\" ChangeKey=\"%s\"/>"
324 " <m:GroupId Id=\"%s\" ChangeKey=\"%s\"/>"
325 "</m:AddImContactToGroup>",
331 sipe_ucs_http_request(sipe_private
,
333 sipe_ucs_ignore_response
,
337 gchar
*body
= g_strdup_printf("<m:AddNewImContactToGroup>"
338 " <m:ImAddress>%s</m:ImAddress>"
339 " <m:GroupId Id=\"%s\" ChangeKey=\"%s\"/>"
340 "</m:AddNewImContactToGroup>",
341 sipe_get_no_sip_uri(buddy
->name
),
345 sipe_ucs_http_request(sipe_private
,
347 sipe_ucs_add_new_im_contact_to_group_response
,
348 g_strdup(buddy
->name
));
353 void sipe_ucs_group_remove_buddy(struct sipe_core_private
*sipe_private
,
354 struct sipe_group
*group
,
355 struct sipe_buddy
*buddy
)
359 * If a contact is removed from last group, it will also be
360 * removed from contact list completely. The documentation has
361 * a RemoveContactFromImList operation, but that doesn't seem
362 * to work at all, i.e. it is always rejected by the server.
364 gchar
*body
= g_strdup_printf("<m:RemoveImContactFromGroup>"
365 " <m:ContactId Id=\"%s\" ChangeKey=\"%s\"/>"
366 " <m:GroupId Id=\"%s\" ChangeKey=\"%s\"/>"
367 "</m:RemoveImContactFromGroup>",
373 sipe_ucs_http_request(sipe_private
,
375 sipe_ucs_ignore_response
,
381 static struct sipe_group
*ucs_create_group(struct sipe_core_private
*sipe_private
,
382 const sipe_xml
*group_node
)
384 const sipe_xml
*id_node
= sipe_xml_child(group_node
,
386 const gchar
*key
= sipe_xml_attribute(id_node
, "Id");
387 const gchar
*change
= sipe_xml_attribute(id_node
, "ChangeKey");
388 struct sipe_group
*group
= NULL
;
390 if (!(is_empty(key
) || is_empty(change
))) {
391 gchar
*name
= sipe_xml_data(sipe_xml_child(group_node
,
393 group
= sipe_group_add(sipe_private
,
397 /* sipe_group must have unique ID */
398 ++sipe_private
->ucs
->group_id
);
405 static void sipe_ucs_add_im_group_response(struct sipe_core_private
*sipe_private
,
406 const sipe_xml
*body
,
407 gpointer callback_data
)
409 gchar
*who
= callback_data
;
410 const sipe_xml
*group_node
= sipe_xml_child(body
,
411 "AddImGroupResponse/ImGroup");
412 struct sipe_group
*group
= ucs_create_group(sipe_private
, group_node
);
415 struct sipe_buddy
*buddy
= sipe_buddy_find_by_uri(sipe_private
,
419 sipe_buddy_insert_group(buddy
, group
);
420 sipe_ucs_group_add_buddy(sipe_private
,
429 void sipe_ucs_group_create(struct sipe_core_private
*sipe_private
,
433 /* new_name can contain restricted characters */
434 gchar
*body
= g_markup_printf_escaped("<m:AddImGroup>"
435 " <m:DisplayName>%s</m:DisplayName>"
439 sipe_ucs_http_request(sipe_private
,
441 sipe_ucs_add_im_group_response
,
446 void sipe_ucs_group_rename(struct sipe_core_private
*sipe_private
,
447 struct sipe_group
*group
,
448 const gchar
*new_name
)
450 /* new_name can contain restricted characters */
451 gchar
*body
= g_markup_printf_escaped("<m:SetImGroup>"
452 " <m:GroupId Id=\"%s\" ChangeKey=\"%s\"/>"
453 " <m:NewDisplayName>%s</m:NewDisplayName>"
459 sipe_ucs_http_request(sipe_private
,
461 sipe_ucs_ignore_response
,
466 void sipe_ucs_group_remove(struct sipe_core_private
*sipe_private
,
467 struct sipe_group
*group
)
469 gchar
*body
= g_strdup_printf("<m:RemoveImGroup>"
470 " <m:GroupId Id=\"%s\" ChangeKey=\"%s\"/>"
471 "</m:RemoveImGroup>",
475 sipe_ucs_http_request(sipe_private
,
477 sipe_ucs_ignore_response
,
482 static void sipe_ucs_get_im_item_list_response(struct sipe_core_private
*sipe_private
,
483 const sipe_xml
*body
,
484 SIPE_UNUSED_PARAMETER gpointer callback_data
)
486 const sipe_xml
*node
= sipe_xml_child(body
,
487 "GetImItemListResponse/ImItemList");
490 const sipe_xml
*persona_node
;
491 const sipe_xml
*group_node
;
493 /* Start processing contact list */
494 if (SIPE_CORE_PRIVATE_FLAG_IS(SUBSCRIBED_BUDDIES
)) {
495 sipe_group_update_start(sipe_private
);
496 sipe_buddy_update_start(sipe_private
);
498 sipe_backend_buddy_list_processing_start(SIPE_CORE_PUBLIC
);
500 for (persona_node
= sipe_xml_child(node
, "Personas/Persona");
502 persona_node
= sipe_xml_twin(persona_node
)) {
503 gchar
*address
= sipe_xml_data(sipe_xml_child(persona_node
,
505 const gchar
*key
= NULL
;
506 const gchar
*change
= NULL
;
508 ucs_extract_keys(persona_node
, &key
, &change
);
510 if (!(is_empty(address
) || is_empty(key
) || is_empty(change
))) {
511 gchar
*uri
= sip_uri_from_name(address
);
512 struct sipe_buddy
*buddy
= sipe_buddy_add(sipe_private
,
518 SIPE_DEBUG_INFO("sipe_ucs_get_im_item_list_response: persona URI '%s' key '%s' change '%s'",
519 buddy
->name
, key
, change
);
524 for (group_node
= sipe_xml_child(node
, "Groups/ImGroup");
526 group_node
= sipe_xml_twin(group_node
)) {
527 struct sipe_group
*group
= ucs_create_group(sipe_private
,
531 const sipe_xml
*member_node
;
533 for (member_node
= sipe_xml_child(group_node
,
534 "MemberCorrelationKey/ItemId");
536 member_node
= sipe_xml_twin(member_node
)) {
537 struct sipe_buddy
*buddy
= sipe_buddy_find_by_exchange_key(sipe_private
,
538 sipe_xml_attribute(member_node
,
541 sipe_buddy_add_to_group(sipe_private
,
544 /* alias will be set via buddy presence update */
550 /* Finished processing contact list */
551 if (SIPE_CORE_PRIVATE_FLAG_IS(SUBSCRIBED_BUDDIES
)) {
552 sipe_buddy_update_finish(sipe_private
);
553 sipe_group_update_finish(sipe_private
);
555 sipe_buddy_cleanup_local_list(sipe_private
);
556 sipe_backend_buddy_list_processing_finish(SIPE_CORE_PUBLIC
);
557 sipe_subscribe_presence_initial(sipe_private
);
562 static void ucs_get_im_item_list(struct sipe_core_private
*sipe_private
)
564 if (sipe_private
->ucs
->migrated
)
565 sipe_ucs_http_request(sipe_private
,
566 "<m:GetImItemList/>",
567 sipe_ucs_get_im_item_list_response
,
571 static void ucs_ews_autodiscover_cb(struct sipe_core_private
*sipe_private
,
572 const struct sipe_ews_autodiscover_data
*ews_data
,
573 SIPE_UNUSED_PARAMETER gpointer callback_data
)
575 struct sipe_ucs
*ucs
= sipe_private
->ucs
;
576 const gchar
*ews_url
;
578 if (!ucs
|| !ews_data
)
581 ews_url
= ews_data
->ews_url
;
582 if (is_empty(ews_url
)) {
583 SIPE_DEBUG_ERROR_NOFORMAT("ucs_ews_autodiscover_cb: can't detect EWS URL, contact list operations will not work!");
587 SIPE_DEBUG_INFO("ucs_ews_autodiscover_cb: EWS URL '%s'", ews_url
);
588 ucs
->ews_url
= g_strdup(ews_url
);
590 ucs_get_im_item_list(sipe_private
);
592 /* EWS URL is valid, send all deferred requests now */
593 if (ucs
->deferred_requests
) {
594 GSList
*entry
= ucs
->deferred_requests
;
596 struct ucs_deferred
*data
= entry
->data
;
598 sipe_ucs_http_request(sipe_private
,
603 /* callback & data has been forwarded */
605 sipe_ucs_deferred_free(sipe_private
, data
);
609 g_slist_free(ucs
->deferred_requests
);
610 ucs
->deferred_requests
= NULL
;
614 gboolean
sipe_ucs_is_migrated(struct sipe_core_private
*sipe_private
)
616 return(sipe_private
->ucs
? sipe_private
->ucs
->migrated
: FALSE
);
619 void sipe_ucs_init(struct sipe_core_private
*sipe_private
,
622 struct sipe_ucs
*ucs
;
624 if (sipe_private
->ucs
) {
625 /* contact list update trigger -> request list again */
626 if (SIPE_CORE_PRIVATE_FLAG_IS(SUBSCRIBED_BUDDIES
))
627 ucs_get_im_item_list(sipe_private
);
632 sipe_private
->ucs
= ucs
= g_new0(struct sipe_ucs
, 1);
633 ucs
->migrated
= migrated
;
635 sipe_ews_autodiscover_start(sipe_private
,
636 ucs_ews_autodiscover_cb
,
640 void sipe_ucs_free(struct sipe_core_private
*sipe_private
)
642 struct sipe_ucs
*ucs
= sipe_private
->ucs
;
647 /* UCS stack is shutting down: reject all new requests */
648 ucs
->shutting_down
= TRUE
;
650 if (ucs
->deferred_requests
) {
651 GSList
*entry
= ucs
->deferred_requests
;
653 sipe_ucs_deferred_free(sipe_private
, entry
->data
);
656 g_slist_free(ucs
->deferred_requests
);
659 if (ucs
->pending_requests
) {
660 GSList
*entry
= ucs
->pending_requests
;
662 sipe_ucs_request_free(sipe_private
, entry
->data
);
665 g_slist_free(ucs
->pending_requests
);
668 g_free(ucs
->ews_url
);
670 sipe_private
->ucs
= NULL
;