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
;
67 gboolean shutting_down
;
70 static void sipe_ucs_deferred_free(struct sipe_core_private
*sipe_private
,
71 struct ucs_deferred
*data
)
74 /* Callback: aborted */
75 (*data
->cb
)(sipe_private
, NULL
, data
->cb_data
);
80 static void sipe_ucs_request_free(struct sipe_core_private
*sipe_private
,
81 struct ucs_request
*data
)
84 sipe_http_request_cancel(data
->request
);
86 /* Callback: aborted */
87 (*data
->cb
)(sipe_private
, NULL
, data
->cb_data
);
91 static void sipe_ucs_http_response(struct sipe_core_private
*sipe_private
,
93 SIPE_UNUSED_PARAMETER GSList
*headers
,
95 gpointer callback_data
)
97 struct ucs_request
*data
= callback_data
;
98 struct sipe_ucs
*ucs
= sipe_private
->ucs
;
100 SIPE_DEBUG_INFO("sipe_ucs_http_response: code %d", status
);
101 data
->request
= NULL
;
103 if ((status
== SIPE_HTTP_STATUS_OK
) && body
) {
104 sipe_xml
*xml
= sipe_xml_parse(body
, strlen(body
));
105 const sipe_xml
*soap_body
= sipe_xml_child(xml
, "Body");
106 /* Callback: success */
107 (*data
->cb
)(sipe_private
, soap_body
, data
->cb_data
);
110 /* Callback: failed */
111 (*data
->cb
)(sipe_private
, NULL
, data
->cb_data
);
114 /* already been called */
117 ucs
->pending_requests
= g_slist_remove(ucs
->pending_requests
,
119 sipe_ucs_request_free(sipe_private
, data
);
122 static gboolean
sipe_ucs_http_request(struct sipe_core_private
*sipe_private
,
124 ucs_callback
*callback
,
125 gpointer callback_data
)
127 struct sipe_ucs
*ucs
= sipe_private
->ucs
;
128 gboolean success
= FALSE
;
130 if (ucs
->shutting_down
) {
131 SIPE_DEBUG_ERROR("sipe_ucs_http_request: new UCS request during shutdown: THIS SHOULD NOT HAPPEN! Debugging information:\n"
133 body
? body
: "<EMPTY>");
135 } else if (ucs
->ews_url
) {
136 struct ucs_request
*data
= g_new0(struct ucs_request
, 1);
137 gchar
*soap
= g_strdup_printf("<?xml version=\"1.0\"?>\r\n"
139 " xmlns:m=\"http://schemas.microsoft.com/exchange/services/2006/messages\""
140 " xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\""
141 " xmlns:t=\"http://schemas.microsoft.com/exchange/services/2006/types\""
144 " <t:RequestServerVersion Version=\"Exchange2013\" />"
151 struct sipe_http_request
*request
= sipe_http_request_post(sipe_private
,
155 "text/xml; charset=UTF-8",
156 sipe_ucs_http_response
,
162 data
->cb_data
= callback_data
;
163 data
->request
= request
;
165 ucs
->pending_requests
= g_slist_prepend(ucs
->pending_requests
,
168 sipe_core_email_authentication(sipe_private
,
170 sipe_http_request_allow_redirect(request
);
171 sipe_http_request_ready(request
);
175 SIPE_DEBUG_ERROR_NOFORMAT("sipe_ucs_http_request: failed to create HTTP connection");
180 struct ucs_deferred
*data
= g_new0(struct ucs_deferred
, 1);
182 data
->cb_data
= callback_data
;
183 data
->body
= g_strdup(body
);
185 ucs
->deferred_requests
= g_slist_prepend(ucs
->deferred_requests
,
193 static void sipe_ucs_get_user_photo_response(struct sipe_core_private
*sipe_private
,
194 const sipe_xml
*body
,
195 gpointer callback_data
)
197 gchar
*uri
= callback_data
;
198 const sipe_xml
*node
= sipe_xml_child(body
,
199 "GetUserPhotoResponse/PictureData");
205 guchar digest
[SIPE_DIGEST_SHA1_LENGTH
];
206 gchar
*digest_string
;
208 /* decode photo data */
209 base64
= sipe_xml_data(node
);
210 photo
= g_base64_decode(base64
, &photo_size
);
213 /* EWS doesn't provide a hash -> calculate SHA-1 digest */
214 sipe_digest_sha1(photo
, photo_size
, digest
);
215 digest_string
= buff_to_hex_str(digest
,
216 SIPE_DIGEST_SHA1_LENGTH
);
218 /* backend frees "photo" */
219 sipe_backend_buddy_set_photo(SIPE_CORE_PUBLIC
,
224 g_free(digest_string
);
230 void sipe_ucs_get_photo(struct sipe_core_private
*sipe_private
,
233 gchar
*payload
= g_strdup(uri
);
234 gchar
*body
= g_strdup_printf("<m:GetUserPhoto>"
235 " <m:Email>%s</m:Email>"
236 " <m:SizeRequested>HR48x48</m:SizeRequested>"
238 sipe_get_no_sip_uri(uri
));
240 if (!sipe_ucs_http_request(sipe_private
,
242 sipe_ucs_get_user_photo_response
,
249 static void sipe_ucs_get_im_item_list_response(struct sipe_core_private
*sipe_private
,
250 const sipe_xml
*body
,
251 SIPE_UNUSED_PARAMETER gpointer callback_data
)
253 const sipe_xml
*node
= sipe_xml_child(body
,
254 "GetImItemListResponse/ImItemList");
257 const sipe_xml
*persona_node
;
258 const sipe_xml
*group_node
;
260 /* Start processing contact list */
261 sipe_backend_buddy_list_processing_start(SIPE_CORE_PUBLIC
);
263 for (persona_node
= sipe_xml_child(node
, "Personas/Persona");
265 persona_node
= sipe_xml_twin(persona_node
)) {
266 gchar
*address
= sipe_xml_data(sipe_xml_child(persona_node
,
268 const gchar
*key
= NULL
;
269 const gchar
*change
= NULL
;
270 const sipe_xml
*attr_node
;
272 /* extract Exchange key - not sure if this is correct */
273 for (attr_node
= sipe_xml_child(persona_node
,
274 "Attributions/Attribution");
276 attr_node
= sipe_xml_twin(attr_node
)) {
277 const sipe_xml
*id_node
= sipe_xml_child(attr_node
,
279 gchar
*type
= sipe_xml_data(sipe_xml_child(attr_node
,
282 sipe_strequal(type
, "Lync Contacts")) {
283 key
= sipe_xml_attribute(id_node
, "Id");
284 change
= sipe_xml_attribute(id_node
, "ChangeKey");
291 if (!(is_empty(address
) || is_empty(key
) || is_empty(change
))) {
292 gchar
*uri
= sip_uri_from_name(address
);
293 struct sipe_buddy
*buddy
= sipe_buddy_add(sipe_private
,
299 SIPE_DEBUG_INFO("sipe_ucs_get_im_item_list_response: persona URI '%s' key '%s' change '%s'",
300 buddy
->name
, key
, change
);
305 for (group_node
= sipe_xml_child(node
, "Groups/ImGroup");
307 group_node
= sipe_xml_twin(group_node
)) {
308 const sipe_xml
*id_node
= sipe_xml_child(group_node
,
310 const gchar
*key
= sipe_xml_attribute(id_node
, "Id");
311 const gchar
*change
= sipe_xml_attribute(id_node
, "ChangeKey");
313 if (!(is_empty(key
) || is_empty(change
))) {
314 gchar
*name
= sipe_xml_data(sipe_xml_child(group_node
,
316 struct sipe_group
*group
= sipe_group_add(sipe_private
,
321 const sipe_xml
*member_node
;
326 SIPE_DEBUG_INFO("sipe_ucs_get_im_item_list_response: group '%s' key '%s' change '%s'",
327 group
->name
, key
, change
);
329 for (member_node
= sipe_xml_child(group_node
,
330 "MemberCorrelationKey/ItemId");
332 member_node
= sipe_xml_twin(member_node
)) {
333 struct sipe_buddy
*buddy
= sipe_buddy_find_by_exchange_key(sipe_private
,
334 sipe_xml_attribute(member_node
,
337 sipe_buddy_add_to_group(sipe_private
,
340 /* alias will be set via buddy presence update */
347 /* Finished processing contact list */
348 sipe_buddy_cleanup_local_list(sipe_private
);
349 sipe_backend_buddy_list_processing_finish(SIPE_CORE_PUBLIC
);
350 sipe_subscribe_presence_initial(sipe_private
);
354 static void ucs_ews_autodiscover_cb(struct sipe_core_private
*sipe_private
,
355 const struct sipe_ews_autodiscover_data
*ews_data
,
356 SIPE_UNUSED_PARAMETER gpointer callback_data
)
358 struct sipe_ucs
*ucs
= sipe_private
->ucs
;
359 const gchar
*ews_url
;
361 if (!ucs
|| !ews_data
)
364 ews_url
= ews_data
->ews_url
;
365 if (is_empty(ews_url
)) {
366 SIPE_DEBUG_ERROR_NOFORMAT("ucs_ews_autodiscover_cb: can't detect EWS URL, contact list operations will not work!");
370 SIPE_DEBUG_INFO("ucs_ews_autodiscover_cb: EWS URL '%s'", ews_url
);
371 ucs
->ews_url
= g_strdup(ews_url
);
373 /* Request migrated contact list */
375 sipe_ucs_http_request(sipe_private
,
376 "<m:GetImItemList/>",
377 sipe_ucs_get_im_item_list_response
,
380 /* EWS URL is valid, send all deferred requests now */
381 if (ucs
->deferred_requests
) {
382 GSList
*entry
= ucs
->deferred_requests
;
384 struct ucs_deferred
*data
= entry
->data
;
386 sipe_ucs_http_request(sipe_private
,
391 /* callback & data has been forwarded */
393 sipe_ucs_deferred_free(sipe_private
, data
);
397 g_slist_free(ucs
->deferred_requests
);
398 ucs
->deferred_requests
= NULL
;
402 gboolean
sipe_ucs_is_migrated(struct sipe_core_private
*sipe_private
)
404 return(sipe_private
->ucs
? sipe_private
->ucs
->migrated
: FALSE
);
407 void sipe_ucs_init(struct sipe_core_private
*sipe_private
,
410 struct sipe_ucs
*ucs
;
412 if (sipe_private
->ucs
)
415 sipe_private
->ucs
= ucs
= g_new0(struct sipe_ucs
, 1);
416 ucs
->migrated
= migrated
;
418 sipe_ews_autodiscover_start(sipe_private
,
419 ucs_ews_autodiscover_cb
,
423 void sipe_ucs_free(struct sipe_core_private
*sipe_private
)
425 struct sipe_ucs
*ucs
= sipe_private
->ucs
;
430 /* UCS stack is shutting down: reject all new requests */
431 ucs
->shutting_down
= TRUE
;
433 if (ucs
->deferred_requests
) {
434 GSList
*entry
= ucs
->deferred_requests
;
436 sipe_ucs_deferred_free(sipe_private
, entry
->data
);
439 g_slist_free(ucs
->deferred_requests
);
442 if (ucs
->pending_requests
) {
443 GSList
*entry
= ucs
->pending_requests
;
445 sipe_ucs_request_free(sipe_private
, entry
->data
);
448 g_slist_free(ucs
->pending_requests
);
451 g_free(ucs
->ews_url
);
453 sipe_private
->ucs
= NULL
;