1 /**
2  * @file sipe-ucs.c
3  *
4  * pidgin-sipe
5  *
6  * Copyright (C) 2013-2018 SIPE Project <http://sipe.sourceforge.net/>
7  *
8  *
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.
13  *
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.
18  *
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
22  *
23  *
24  * Implementation for Unified Contact Store [MS-OXWSCOS]
25  *  <http://msdn.microsoft.com/en-us/library/jj194130.aspx>
26  * EWS Reference
27  *  <http://msdn.microsoft.com/en-us/library/office/bb204119.aspx>
28  * Photo Web Service Protocol [MS-OXWSPHOTO]
29  *  <http://msdn.microsoft.com/en-us/library/jj194353.aspx>
30  * FindPeople operation
31  *  <http://msdn.microsoft.com/en-us/library/office/jj191039.aspx>
32  */
33 
34 #ifdef HAVE_CONFIG_H
35 #include "config.h"
36 #endif
37 
38 #include <string.h>
39 
40 #include <glib.h>
41 #include <time.h>
42 
43 #include "sipe-backend.h"
44 #include "sipe-buddy.h"
45 #include "sipe-common.h"
46 #include "sipe-core.h"
47 #include "sipe-core-private.h"
48 #include "sipe-ews-autodiscover.h"
49 #include "sipe-group.h"
50 #include "sipe-http.h"
51 #include "sipe-nls.h"
52 #include "sipe-subscriptions.h"
53 #include "sipe-ucs.h"
54 #include "sipe-utils.h"
55 #include "sipe-xml.h"
56 
57 struct sipe_ucs_transaction {
58 	GSList *pending_requests;
59 };
60 
61 typedef void (ucs_callback)(struct sipe_core_private *sipe_private,
62 			    struct sipe_ucs_transaction *trans,
63 			    const sipe_xml *body,
64 			    gpointer callback_data);
65 
66 struct ucs_request {
67 	gchar *body;
68 	ucs_callback *cb;
69 	gpointer cb_data;
70 	struct sipe_ucs_transaction *transaction;
71 	struct sipe_http_request *request;
72 };
73 
74 struct sipe_ucs {
75 	struct ucs_request *active_request;
76 	GSList *transactions;
77 	GSList *default_transaction;
78 	gchar *ews_url;
79 	time_t last_response;
80 	guint group_id;
81 	gboolean migrated;
82 	gboolean shutting_down;
83 };
84 
sipe_ucs_request_free(struct sipe_core_private * sipe_private,struct ucs_request * data)85 static void sipe_ucs_request_free(struct sipe_core_private *sipe_private,
86 				  struct ucs_request *data)
87 {
88 	struct sipe_ucs *ucs = sipe_private->ucs;
89 	struct sipe_ucs_transaction *trans = data->transaction;
90 
91 	/* remove request from transaction */
92 	trans->pending_requests = g_slist_remove(trans->pending_requests,
93 						 data);
94 	sipe_private->ucs->active_request = NULL;
95 
96 	/* remove completed transactions (except default transaction) */
97 	if (!trans->pending_requests &&
98 	    (trans != ucs->default_transaction->data)) {
99 		ucs->transactions = g_slist_remove(ucs->transactions,
100 						   trans);
101 		g_free(trans);
102 	}
103 
104 	if (data->request)
105 		sipe_http_request_cancel(data->request);
106 	if (data->cb)
107 		/* Callback: aborted */
108 		(*data->cb)(sipe_private, NULL, NULL, data->cb_data);
109 	g_free(data->body);
110 	g_free(data);
111 }
112 
113 static void sipe_ucs_next_request(struct sipe_core_private *sipe_private);
sipe_ucs_http_response(struct sipe_core_private * sipe_private,guint status,SIPE_UNUSED_PARAMETER GSList * headers,const gchar * body,gpointer callback_data)114 static void sipe_ucs_http_response(struct sipe_core_private *sipe_private,
115 				   guint status,
116 				   SIPE_UNUSED_PARAMETER GSList *headers,
117 				   const gchar *body,
118 				   gpointer callback_data)
119 {
120 	struct ucs_request *data = callback_data;
121 
122 	SIPE_DEBUG_INFO("sipe_ucs_http_response: code %d", status);
123 	data->request = NULL;
124 
125 	if ((status == SIPE_HTTP_STATUS_OK) && body) {
126 		sipe_xml *xml = sipe_xml_parse(body, strlen(body));
127 		const sipe_xml *soap_body = sipe_xml_child(xml, "Body");
128 		/* Callback: success */
129 		(*data->cb)(sipe_private,
130 			    data->transaction,
131 			    soap_body,
132 			    data->cb_data);
133 		sipe_xml_free(xml);
134 	} else {
135 		/* Callback: failed */
136 		(*data->cb)(sipe_private, NULL, NULL, data->cb_data);
137 	}
138 
139 	/* already been called */
140 	data->cb = NULL;
141 
142 	sipe_ucs_request_free(sipe_private, data);
143 	sipe_ucs_next_request(sipe_private);
144 }
145 
sipe_ucs_next_request(struct sipe_core_private * sipe_private)146 static void sipe_ucs_next_request(struct sipe_core_private *sipe_private)
147 {
148 	struct sipe_ucs *ucs = sipe_private->ucs;
149 	struct sipe_ucs_transaction *trans;
150 
151 	if (ucs->active_request || ucs->shutting_down || !ucs->ews_url)
152 		return;
153 
154 	trans = ucs->transactions->data;
155 	while (trans->pending_requests) {
156 		struct ucs_request *data = trans->pending_requests->data;
157 		gchar *soap = g_strdup_printf("<?xml version=\"1.0\"?>\r\n"
158 					      "<soap:Envelope"
159 					      " xmlns:m=\"http://schemas.microsoft.com/exchange/services/2006/messages\""
160 					      " xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\""
161 					      " xmlns:t=\"http://schemas.microsoft.com/exchange/services/2006/types\""
162 					      " >"
163 					      " <soap:Header>"
164 					      "  <t:RequestServerVersion Version=\"Exchange2013\" />"
165 					      " </soap:Header>"
166 					      " <soap:Body>"
167 					      "  %s"
168 					      " </soap:Body>"
169 					      "</soap:Envelope>",
170 					      data->body);
171 		struct sipe_http_request *request = sipe_http_request_post(sipe_private,
172 									   ucs->ews_url,
173 									   NULL,
174 									   soap,
175 									   "text/xml; charset=UTF-8",
176 									   sipe_ucs_http_response,
177 									   data);
178 		g_free(soap);
179 
180 		if (request) {
181 			g_free(data->body);
182 			data->body    = NULL;
183 			data->request = request;
184 
185 			ucs->active_request = data;
186 
187 			sipe_core_email_authentication(sipe_private,
188 						       request);
189 			sipe_http_request_allow_redirect(request);
190 			sipe_http_request_ready(request);
191 
192 			break;
193 		} else {
194 			SIPE_DEBUG_ERROR_NOFORMAT("sipe_ucs_next_request: failed to create HTTP connection");
195 			sipe_ucs_request_free(sipe_private, data);
196 		}
197 	}
198 }
199 
sipe_ucs_http_request(struct sipe_core_private * sipe_private,struct sipe_ucs_transaction * trans,gchar * body,ucs_callback * callback,gpointer callback_data)200 static gboolean sipe_ucs_http_request(struct sipe_core_private *sipe_private,
201 				      struct sipe_ucs_transaction *trans,
202 				      gchar *body,  /* takes ownership */
203 				      ucs_callback *callback,
204 				      gpointer callback_data)
205 {
206 	struct sipe_ucs *ucs = sipe_private->ucs;
207 
208 	if (!ucs || ucs->shutting_down) {
209 		SIPE_DEBUG_ERROR("sipe_ucs_http_request: new UCS request during shutdown: THIS SHOULD NOT HAPPEN! Debugging information:\n"
210 				 "Body:   %s\n",
211 				 body ? body : "<EMPTY>");
212 		g_free(body);
213 		return(FALSE);
214 
215 	} else {
216 		struct ucs_request *data = g_new0(struct ucs_request, 1);
217 
218 		data->cb      = callback;
219 		data->cb_data = callback_data;
220 		data->body    = body;
221 
222 		if (!trans)
223 			trans = ucs->default_transaction->data;
224 		data->transaction = trans;
225 		trans->pending_requests = g_slist_append(trans->pending_requests,
226 							 data);
227 
228 		sipe_ucs_next_request(sipe_private);
229 		return(TRUE);
230 	}
231 }
232 
sipe_ucs_transaction(struct sipe_core_private * sipe_private)233 struct sipe_ucs_transaction *sipe_ucs_transaction(struct sipe_core_private *sipe_private)
234 {
235 	struct sipe_ucs *ucs = sipe_private->ucs;
236 	struct sipe_ucs_transaction *trans;
237 
238 	if (!ucs)
239 		return(NULL);
240 
241 	/* always insert new transactions before default transaction */
242 	trans = g_new0(struct sipe_ucs_transaction, 1);
243 	ucs->transactions = g_slist_insert_before(ucs->transactions,
244 						  ucs->default_transaction,
245 						  trans);
246 
247 	return(trans);
248 }
249 
sipe_ucs_search_response(struct sipe_core_private * sipe_private,SIPE_UNUSED_PARAMETER struct sipe_ucs_transaction * trans,const sipe_xml * body,gpointer callback_data)250 static void sipe_ucs_search_response(struct sipe_core_private *sipe_private,
251 				     SIPE_UNUSED_PARAMETER struct sipe_ucs_transaction *trans,
252 				     const sipe_xml *body,
253 				     gpointer callback_data)
254 {
255 	const sipe_xml *persona_node;
256 	struct sipe_backend_search_results *results = NULL;
257 	guint match_count = 0;
258 
259 	for (persona_node = sipe_xml_child(body,
260 					   "FindPeopleResponse/People/Persona");
261 	     persona_node;
262 	     persona_node = sipe_xml_twin(persona_node)) {
263 		const sipe_xml *address = sipe_xml_child(persona_node,
264 							 "ImAddress");
265 
266 		/* only display Persona nodes which have an "ImAddress" node */
267 		if (address) {
268 			gchar *uri;
269 			gchar *displayname;
270 			gchar *company;
271 			gchar *email;
272 
273 			/* OK, we found something - show the results to the user */
274 			match_count++;
275 			if (!results) {
276 				results = sipe_backend_search_results_start(SIPE_CORE_PUBLIC,
277 									    callback_data);
278 				if (!results) {
279 					SIPE_DEBUG_ERROR_NOFORMAT("sipe_ucs_search_response: Unable to display the search results.");
280 					sipe_backend_search_failed(SIPE_CORE_PUBLIC,
281 								   callback_data,
282 								   _("Unable to display the search results"));
283 					return;
284 				}
285 			}
286 
287 			uri         = sipe_xml_data(address);
288 			displayname = sipe_xml_data(sipe_xml_child(persona_node,
289 								   "DisplayName"));
290 			company     = sipe_xml_data(sipe_xml_child(persona_node,
291 								   "CompanyName"));
292 			email       = sipe_xml_data(sipe_xml_child(persona_node,
293 								   "EmailAddress/EmailAddress"));
294 
295 			sipe_backend_search_results_add(SIPE_CORE_PUBLIC,
296 							results,
297 							sipe_get_no_sip_uri(uri),
298 							displayname,
299 							company,
300 							NULL,
301 							email);
302 
303 			g_free(email);
304 			g_free(company);
305 			g_free(displayname);
306 			g_free(uri);
307 		}
308 	}
309 
310 	if (match_count > 0)
311 		sipe_buddy_search_contacts_finalize(sipe_private,
312 						    results,
313 						    match_count,
314 						    FALSE);
315 	else
316 		sipe_backend_search_failed(SIPE_CORE_PUBLIC,
317 					   callback_data,
318 					   _("No contacts found"));
319 }
320 
sipe_ucs_search(struct sipe_core_private * sipe_private,struct sipe_backend_search_token * token,const gchar * given_name,const gchar * surname,const gchar * email,const gchar * sipid,const gchar * company,const gchar * country)321 void sipe_ucs_search(struct sipe_core_private *sipe_private,
322 		     struct sipe_backend_search_token *token,
323 		     const gchar *given_name,
324 		     const gchar *surname,
325 		     const gchar *email,
326 		     const gchar *sipid,
327 		     const gchar *company,
328 		     const gchar *country)
329 {
330 	guint count    = 0;
331 	GString *query = g_string_new(NULL);
332 
333 	/*
334 	 * Search GAL for matching entries
335 	 *
336 	 * QueryString should support field properties and quoting ("")
337 	 * according to the specification. But in my trials I couldn't get
338 	 * them to work. Concatenate all query words to a single string.
339 	 * Only items that match ALL words will be returned by this query.
340 	 */
341 #define ADD_QUERY_VALUE(val)			       \
342 	if (val) {				       \
343 		if (count++)			       \
344 			g_string_append_c(query, ' '); \
345 		g_string_append(query, val);	       \
346 	}
347 
348 	ADD_QUERY_VALUE(given_name);
349 	ADD_QUERY_VALUE(surname);
350 	ADD_QUERY_VALUE(email);
351 	ADD_QUERY_VALUE(sipid);
352 	ADD_QUERY_VALUE(company);
353 	ADD_QUERY_VALUE(country);
354 
355 	if (count > 0) {
356 		gchar *body = g_markup_printf_escaped("<m:FindPeople>"
357 						      " <m:PersonaShape>"
358 						      "  <t:BaseShape>IdOnly</t:BaseShape>"
359 						      "  <t:AdditionalProperties>"
360 						      "   <t:FieldURI FieldURI=\"persona:CompanyName\"/>"
361 						      "   <t:FieldURI FieldURI=\"persona:DisplayName\"/>"
362 						      "   <t:FieldURI FieldURI=\"persona:EmailAddress\"/>"
363 						      "   <t:FieldURI FieldURI=\"persona:ImAddress\"/>"
364 						      /* Locations doesn't seem to work
365 						      "   <t:FieldURI FieldURI=\"persona:Locations\"/>"
366 						      */
367 						      "  </t:AdditionalProperties>"
368 						      " </m:PersonaShape>"
369 						      " <m:IndexedPageItemView BasePoint=\"Beginning\" MaxEntriesReturned=\"100\" Offset=\"0\"/>"
370 						      /*
371 						       * I have no idea why Exchnage doesn't accept this
372 						       * FieldURI for restrictions. Without it the search
373 						       * will return users that don't have an ImAddress
374 						       * and we need to filter them out ourselves :-(
375 						      " <m:Restriction>"
376 						      "  <t:Exists>"
377 						      "   <t:FieldURI FieldURI=\"persona:ImAddress\"/>"
378 						      "  </t:Exists>"
379 						      " </m:Restriction>"
380 						      */
381 						      " <m:ParentFolderId>"
382 						      "  <t:DistinguishedFolderId Id=\"directory\"/>"
383 						      " </m:ParentFolderId>"
384 						      " <m:QueryString>%s</m:QueryString>"
385 						      "</m:FindPeople>",
386 						      query->str);
387 
388 		if (!sipe_ucs_http_request(sipe_private,
389 					   NULL,
390 					   body,
391 					   sipe_ucs_search_response,
392 					   token))
393 			sipe_backend_search_failed(SIPE_CORE_PUBLIC,
394 						   token,
395 						   _("Contact search failed"));
396 	} else
397 		sipe_backend_search_failed(SIPE_CORE_PUBLIC,
398 					   token,
399 					   _("Invalid contact search query"));
400 
401 	g_string_free(query, TRUE);
402 }
403 
sipe_ucs_ignore_response(struct sipe_core_private * sipe_private,SIPE_UNUSED_PARAMETER struct sipe_ucs_transaction * trans,SIPE_UNUSED_PARAMETER const sipe_xml * body,SIPE_UNUSED_PARAMETER gpointer callback_data)404 static void sipe_ucs_ignore_response(struct sipe_core_private *sipe_private,
405 				     SIPE_UNUSED_PARAMETER struct sipe_ucs_transaction *trans,
406 				     SIPE_UNUSED_PARAMETER const sipe_xml *body,
407 				     SIPE_UNUSED_PARAMETER gpointer callback_data)
408 {
409 	SIPE_DEBUG_INFO_NOFORMAT("sipe_ucs_ignore_response: done");
410 	sipe_private->ucs->last_response = time(NULL);
411 }
412 
ucs_extract_keys(const sipe_xml * persona_node,const gchar ** key,const gchar ** change)413 static void ucs_extract_keys(const sipe_xml *persona_node,
414 			     const gchar **key,
415 			     const gchar **change)
416 {
417 	const sipe_xml *attr_node;
418 
419 	/*
420 	 * extract Exchange key - play the guessing game :-(
421 	 *
422 	 * We can't use the "DisplayName" node, because the text is localized.
423 	 *
424 	 * Assume that IsQuickContact == "true" and IsHidden == "false" means
425 	 * this Attribution node contains the information for the Lync contact.
426 	 */
427 	for (attr_node = sipe_xml_child(persona_node,
428 					"Attributions/Attribution");
429 	     attr_node;
430 	     attr_node = sipe_xml_twin(attr_node)) {
431 		const sipe_xml *id_node = sipe_xml_child(attr_node,
432 							 "SourceId");
433 		gchar *hidden = sipe_xml_data(sipe_xml_child(attr_node,
434 							     "IsHidden"));
435 		gchar *quick = sipe_xml_data(sipe_xml_child(attr_node,
436 							    "IsQuickContact"));
437 		if (id_node &&
438 		    sipe_strcase_equal(hidden, "false") &&
439 		    sipe_strcase_equal(quick,  "true")) {
440 			*key = sipe_xml_attribute(id_node, "Id");
441 			*change = sipe_xml_attribute(id_node, "ChangeKey");
442 			g_free(quick);
443 			g_free(hidden);
444 			break;
445 		}
446 		g_free(quick);
447 		g_free(hidden);
448 	}
449 }
450 
sipe_ucs_add_new_im_contact_to_group_response(struct sipe_core_private * sipe_private,SIPE_UNUSED_PARAMETER struct sipe_ucs_transaction * trans,const sipe_xml * body,gpointer callback_data)451 static void sipe_ucs_add_new_im_contact_to_group_response(struct sipe_core_private *sipe_private,
452 							  SIPE_UNUSED_PARAMETER struct sipe_ucs_transaction *trans,
453 							  const sipe_xml *body,
454 							  gpointer callback_data)
455 {
456 	gchar *who = callback_data;
457 	struct sipe_buddy *buddy = sipe_buddy_find_by_uri(sipe_private, who);
458 	const sipe_xml *persona_node = sipe_xml_child(body,
459 						      "AddNewImContactToGroupResponse/Persona");
460 
461 	sipe_private->ucs->last_response = time(NULL);
462 
463 	if (persona_node                  &&
464 	    buddy                         &&
465 	    is_empty(buddy->exchange_key) &&
466 	    is_empty(buddy->change_key)) {
467 		const gchar *key = NULL;
468 		const gchar *change = NULL;
469 
470 		ucs_extract_keys(persona_node, &key, &change);
471 
472 		if (!is_empty(key) && !is_empty(change)) {
473 
474 			sipe_buddy_add_keys(sipe_private,
475 					    buddy,
476 					    key,
477 					    change);
478 
479 			SIPE_DEBUG_INFO("sipe_ucs_add_new_im_contact_to_group_response: persona URI '%s' key '%s' change '%s'",
480 					buddy->name, key, change);
481 		}
482 	}
483 
484 	g_free(who);
485 }
486 
sipe_ucs_group_add_buddy(struct sipe_core_private * sipe_private,struct sipe_ucs_transaction * trans,struct sipe_group * group,struct sipe_buddy * buddy,const gchar * who)487 void sipe_ucs_group_add_buddy(struct sipe_core_private *sipe_private,
488 			      struct sipe_ucs_transaction *trans,
489 			      struct sipe_group *group,
490 			      struct sipe_buddy *buddy,
491 			      const gchar *who)
492 {
493 	/* existing or new buddy? */
494 	if (buddy && buddy->exchange_key) {
495 		gchar *body = g_strdup_printf("<m:AddImContactToGroup>"
496 					      " <m:ContactId Id=\"%s\" ChangeKey=\"%s\"/>"
497 					      " <m:GroupId Id=\"%s\" ChangeKey=\"%s\"/>"
498 					      "</m:AddImContactToGroup>",
499 					      buddy->exchange_key,
500 					      buddy->change_key,
501 					      group->exchange_key,
502 					      group->change_key);
503 
504 		sipe_ucs_http_request(sipe_private,
505 				      trans,
506 				      body,
507 				      sipe_ucs_ignore_response,
508 				      NULL);
509 	} else {
510 		gchar *payload = g_strdup(who);
511 		gchar *body = g_strdup_printf("<m:AddNewImContactToGroup>"
512 					      " <m:ImAddress>%s</m:ImAddress>"
513 					      " <m:GroupId Id=\"%s\" ChangeKey=\"%s\"/>"
514 					      "</m:AddNewImContactToGroup>",
515 					      sipe_get_no_sip_uri(who),
516 					      group->exchange_key,
517 					      group->change_key);
518 
519 		if (!sipe_ucs_http_request(sipe_private,
520 					   trans,
521 					   body,
522 					   sipe_ucs_add_new_im_contact_to_group_response,
523 					   payload))
524 			g_free(payload);
525 	}
526 }
527 
sipe_ucs_group_remove_buddy(struct sipe_core_private * sipe_private,struct sipe_ucs_transaction * trans,struct sipe_group * group,struct sipe_buddy * buddy)528 void sipe_ucs_group_remove_buddy(struct sipe_core_private *sipe_private,
529 				 struct sipe_ucs_transaction *trans,
530 				 struct sipe_group *group,
531 				 struct sipe_buddy *buddy)
532 {
533 	if (group) {
534 		/*
535 		 * If a contact is removed from last group, it will also be
536 		 * removed from contact list completely. The documentation has
537 		 * a RemoveContactFromImList operation, but that doesn't seem
538 		 * to work at all, i.e. it is always rejected by the server.
539 		 */
540 		gchar *body = g_strdup_printf("<m:RemoveImContactFromGroup>"
541 					      " <m:ContactId Id=\"%s\" ChangeKey=\"%s\"/>"
542 					      " <m:GroupId Id=\"%s\" ChangeKey=\"%s\"/>"
543 					      "</m:RemoveImContactFromGroup>",
544 					      buddy->exchange_key,
545 					      buddy->change_key,
546 					      group->exchange_key,
547 					      group->change_key);
548 
549 		sipe_ucs_http_request(sipe_private,
550 				      trans,
551 				      body,
552 				      sipe_ucs_ignore_response,
553 				      NULL);
554 	}
555 }
556 
ucs_create_group(struct sipe_core_private * sipe_private,const sipe_xml * group_node)557 static struct sipe_group *ucs_create_group(struct sipe_core_private *sipe_private,
558 					   const sipe_xml *group_node)
559 {
560 	const sipe_xml *id_node = sipe_xml_child(group_node,
561 						 "ExchangeStoreId");
562 	const gchar *key = sipe_xml_attribute(id_node, "Id");
563 	const gchar *change = sipe_xml_attribute(id_node, "ChangeKey");
564 	struct sipe_group *group = NULL;
565 
566 	if (!(is_empty(key) || is_empty(change))) {
567 		gchar *name = sipe_xml_data(sipe_xml_child(group_node,
568 							   "DisplayName"));
569 		group = sipe_group_add(sipe_private,
570 				       name,
571 				       key,
572 				       change,
573 				       /* sipe_group must have unique ID */
574 				       ++sipe_private->ucs->group_id);
575 		g_free(name);
576 	}
577 
578 	return(group);
579 }
580 
sipe_ucs_add_im_group_response(struct sipe_core_private * sipe_private,struct sipe_ucs_transaction * trans,const sipe_xml * body,gpointer callback_data)581 static void sipe_ucs_add_im_group_response(struct sipe_core_private *sipe_private,
582 					   struct sipe_ucs_transaction *trans,
583 					   const sipe_xml *body,
584 					   gpointer callback_data)
585 {
586 	gchar *who = callback_data;
587 	const sipe_xml *group_node = sipe_xml_child(body,
588 						    "AddImGroupResponse/ImGroup");
589 	struct sipe_group *group = ucs_create_group(sipe_private, group_node);
590 
591 	sipe_private->ucs->last_response = time(NULL);
592 
593 	if (group) {
594 		struct sipe_buddy *buddy = sipe_buddy_find_by_uri(sipe_private,
595 								  who);
596 
597 		if (buddy)
598 			sipe_buddy_insert_group(buddy, group);
599 
600 		sipe_ucs_group_add_buddy(sipe_private,
601 					 trans,
602 					 group,
603 					 buddy,
604 					 who);
605 	}
606 
607 	g_free(who);
608 }
609 
sipe_ucs_group_create(struct sipe_core_private * sipe_private,struct sipe_ucs_transaction * trans,const gchar * name,const gchar * who)610 void sipe_ucs_group_create(struct sipe_core_private *sipe_private,
611 			   struct sipe_ucs_transaction *trans,
612 			   const gchar *name,
613 			   const gchar *who)
614 {
615 	gchar *payload = g_strdup(who);
616 	/* new_name can contain restricted characters */
617 	gchar *body = g_markup_printf_escaped("<m:AddImGroup>"
618 					      " <m:DisplayName>%s</m:DisplayName>"
619 					      "</m:AddImGroup>",
620 					      name);
621 
622 	if (!sipe_ucs_http_request(sipe_private,
623 				   trans,
624 				   body,
625 				   sipe_ucs_add_im_group_response,
626 				   payload))
627 		g_free(payload);
628 }
629 
sipe_ucs_group_rename(struct sipe_core_private * sipe_private,struct sipe_group * group,const gchar * new_name)630 void sipe_ucs_group_rename(struct sipe_core_private *sipe_private,
631 			   struct sipe_group *group,
632 			   const gchar *new_name)
633 {
634 	/* new_name can contain restricted characters */
635 	gchar *body = g_markup_printf_escaped("<m:SetImGroup>"
636 					      " <m:GroupId Id=\"%s\" ChangeKey=\"%s\"/>"
637 					      " <m:NewDisplayName>%s</m:NewDisplayName>"
638 					      "</m:SetImGroup>",
639 					      group->exchange_key,
640 					      group->change_key,
641 					      new_name);
642 
643 	sipe_ucs_http_request(sipe_private,
644 			      NULL,
645 			      body,
646 			      sipe_ucs_ignore_response,
647 			      NULL);
648 }
649 
sipe_ucs_group_remove(struct sipe_core_private * sipe_private,struct sipe_group * group)650 void sipe_ucs_group_remove(struct sipe_core_private *sipe_private,
651 			   struct sipe_group *group)
652 {
653 	gchar *body = g_strdup_printf("<m:RemoveImGroup>"
654 				      " <m:GroupId Id=\"%s\" ChangeKey=\"%s\"/>"
655 				      "</m:RemoveImGroup>",
656 				      group->exchange_key,
657 				      group->change_key);
658 
659 	sipe_ucs_http_request(sipe_private,
660 			      NULL,
661 			      body,
662 			      sipe_ucs_ignore_response,
663 			      NULL);
664 }
665 
ucs_init_failure(struct sipe_core_private * sipe_private)666 static void ucs_init_failure(struct sipe_core_private *sipe_private)
667 {
668 	/* Did the user specify any email settings? */
669 	gboolean default_settings =
670 		is_empty(sipe_backend_setting(SIPE_CORE_PUBLIC,
671 					      SIPE_SETTING_EMAIL_URL))   &&
672 		is_empty(sipe_backend_setting(SIPE_CORE_PUBLIC,
673 					      SIPE_SETTING_EMAIL_LOGIN)) &&
674 		is_empty(sipe_backend_setting(SIPE_CORE_PUBLIC,
675 					      SIPE_SETTING_EMAIL_PASSWORD));
676 
677 	sipe_backend_notify_error(SIPE_CORE_PUBLIC,
678 				  _("UCS initialization failed!"),
679 				  default_settings ?
680 				  _("Couldn't find an Exchange server with the default Email settings. Therefore the contacts list will not work.\n\nYou'll need to provide Email settings in the account setup.") :
681 				  _("Couldn't find an Exchange server with the Email settings provided in the account setup. Therefore the contacts list will not work.\n\nPlease correct your Email settings."));
682 }
683 
sipe_ucs_get_im_item_list_response(struct sipe_core_private * sipe_private,SIPE_UNUSED_PARAMETER struct sipe_ucs_transaction * trans,const sipe_xml * body,SIPE_UNUSED_PARAMETER gpointer callback_data)684 static void sipe_ucs_get_im_item_list_response(struct sipe_core_private *sipe_private,
685 					       SIPE_UNUSED_PARAMETER struct sipe_ucs_transaction *trans,
686 					       const sipe_xml *body,
687 					       SIPE_UNUSED_PARAMETER gpointer callback_data)
688 {
689 	const sipe_xml *node = sipe_xml_child(body,
690 					      "GetImItemListResponse/ImItemList");
691 
692 	if (node) {
693 		const sipe_xml *persona_node;
694 		const sipe_xml *group_node;
695 		GHashTable *uri_to_alias = g_hash_table_new_full(g_str_hash,
696 								 g_str_equal,
697 								 NULL,
698 								 g_free);
699 
700 		/* Start processing contact list */
701 		if (SIPE_CORE_PRIVATE_FLAG_IS(SUBSCRIBED_BUDDIES)) {
702 			sipe_group_update_start(sipe_private);
703 			sipe_buddy_update_start(sipe_private);
704 		} else
705 			sipe_backend_buddy_list_processing_start(SIPE_CORE_PUBLIC);
706 
707 		for (persona_node = sipe_xml_child(node, "Personas/Persona");
708 		     persona_node;
709 		     persona_node = sipe_xml_twin(persona_node)) {
710 			gchar *address = sipe_xml_data(sipe_xml_child(persona_node,
711 								      "ImAddress"));
712 			const gchar *key = NULL;
713 			const gchar *change = NULL;
714 
715 			ucs_extract_keys(persona_node, &key, &change);
716 
717 			if (!(is_empty(address) || is_empty(key) || is_empty(change))) {
718 				gchar *alias = sipe_xml_data(sipe_xml_child(persona_node,
719 									    "DisplayName"));
720 				/*
721 				 * it seems to be undefined if ImAddress node
722 				 * contains "sip:" prefix or not...
723 				 */
724 				gchar *uri = sip_uri(address);
725 				struct sipe_buddy *buddy = sipe_buddy_add(sipe_private,
726 									  uri,
727 									  key,
728 									  change);
729 				g_free(uri);
730 
731 				/* hash table takes ownership of alias */
732 				g_hash_table_insert(uri_to_alias,
733 						    buddy->name,
734 						    alias);
735 
736 				SIPE_DEBUG_INFO("sipe_ucs_get_im_item_list_response: persona URI '%s' key '%s' change '%s'",
737 						buddy->name, key, change);
738 			}
739 			g_free(address);
740 		}
741 
742 		for (group_node = sipe_xml_child(node, "Groups/ImGroup");
743 		     group_node;
744 		     group_node = sipe_xml_twin(group_node)) {
745 			struct sipe_group *group = ucs_create_group(sipe_private,
746 								    group_node);
747 
748 			if (group) {
749 				const sipe_xml *member_node;
750 
751 				for (member_node = sipe_xml_child(group_node,
752 								  "MemberCorrelationKey/ItemId");
753 				     member_node;
754 				     member_node = sipe_xml_twin(member_node)) {
755 					struct sipe_buddy *buddy = sipe_buddy_find_by_exchange_key(sipe_private,
756 												   sipe_xml_attribute(member_node,
757 														      "Id"));
758 					if (buddy)
759 						sipe_buddy_add_to_group(sipe_private,
760 									buddy,
761 									group,
762 									g_hash_table_lookup(uri_to_alias,
763 											    buddy->name));
764 				}
765 			}
766 		}
767 
768 		g_hash_table_destroy(uri_to_alias);
769 
770 		/* Finished processing contact list */
771 		if (SIPE_CORE_PRIVATE_FLAG_IS(SUBSCRIBED_BUDDIES)) {
772 			sipe_buddy_update_finish(sipe_private);
773 			sipe_group_update_finish(sipe_private);
774 		} else {
775 			sipe_buddy_cleanup_local_list(sipe_private);
776 			sipe_backend_buddy_list_processing_finish(SIPE_CORE_PUBLIC);
777 			sipe_subscribe_presence_initial(sipe_private);
778 		}
779 	} else if (sipe_private->ucs) {
780 		SIPE_DEBUG_ERROR_NOFORMAT("sipe_ucs_get_im_item_list_response: query failed, contact list operations will not work!");
781 		ucs_init_failure(sipe_private);
782 	}
783 }
784 
ucs_get_im_item_list(struct sipe_core_private * sipe_private)785 static void ucs_get_im_item_list(struct sipe_core_private *sipe_private)
786 {
787 	if (sipe_private->ucs->migrated)
788 		sipe_ucs_http_request(sipe_private,
789 				      /* prioritize over pending default requests */
790 				      sipe_ucs_transaction(sipe_private),
791 				      g_strdup("<m:GetImItemList/>"),
792 				      sipe_ucs_get_im_item_list_response,
793 				      NULL);
794 }
795 
ucs_set_ews_url(struct sipe_core_private * sipe_private,const gchar * ews_url)796 static void ucs_set_ews_url(struct sipe_core_private *sipe_private,
797 		      const gchar *ews_url)
798 {
799 	struct sipe_ucs *ucs = sipe_private->ucs;
800 
801 	SIPE_DEBUG_INFO("ucs_set_ews_url: '%s'", ews_url);
802 	ucs->ews_url = g_strdup(ews_url);
803 
804 	/* this will trigger sending of the first deferred request */
805 	ucs_get_im_item_list(sipe_private);
806 }
807 
ucs_ews_autodiscover_cb(struct sipe_core_private * sipe_private,const struct sipe_ews_autodiscover_data * ews_data,SIPE_UNUSED_PARAMETER gpointer callback_data)808 static void ucs_ews_autodiscover_cb(struct sipe_core_private *sipe_private,
809 				    const struct sipe_ews_autodiscover_data *ews_data,
810 				    SIPE_UNUSED_PARAMETER gpointer callback_data)
811 {
812 	struct sipe_ucs *ucs = sipe_private->ucs;
813 	const gchar *ews_url = NULL;
814 
815 	if (!ucs)
816 		return;
817 
818 	if (ews_data)
819 		ews_url = ews_data->ews_url;
820 
821 	if (is_empty(ews_url)) {
822 		SIPE_DEBUG_ERROR_NOFORMAT("ucs_ews_autodiscover_cb: can't detect EWS URL, contact list operations will not work!");
823 		ucs_init_failure(sipe_private);
824 	} else {
825 		ucs_set_ews_url(sipe_private, ews_url);
826 	}
827 }
828 
sipe_ucs_is_migrated(struct sipe_core_private * sipe_private)829 gboolean sipe_ucs_is_migrated(struct sipe_core_private *sipe_private)
830 {
831 	return(sipe_private->ucs ? sipe_private->ucs->migrated : FALSE);
832 }
833 
sipe_ucs_ews_url(struct sipe_core_private * sipe_private)834 const gchar *sipe_ucs_ews_url(struct sipe_core_private *sipe_private)
835 {
836 	return(sipe_private->ucs ? sipe_private->ucs->ews_url : NULL);
837 }
838 
sipe_ucs_init(struct sipe_core_private * sipe_private,gboolean migrated)839 void sipe_ucs_init(struct sipe_core_private *sipe_private,
840 		   gboolean migrated)
841 {
842 	struct sipe_ucs *ucs;
843 
844 	if (sipe_private->ucs) {
845 		struct sipe_ucs *ucs = sipe_private->ucs;
846 
847 		/*
848 		 * contact list update trigger -> request list again
849 		 *
850 		 * If the trigger arrives less than 10 seconds after our
851 		 * last UCS response, then ignore it, because it is caused
852 		 * by our own changes to the contact list.
853 		 */
854 		if (SIPE_CORE_PRIVATE_FLAG_IS(SUBSCRIBED_BUDDIES)) {
855 			if ((time(NULL) - ucs->last_response) >= 10)
856 				ucs_get_im_item_list(sipe_private);
857 			else
858 				SIPE_DEBUG_INFO_NOFORMAT("sipe_ucs_init: ignoring this contact list update - triggered by our last change");
859 		}
860 
861 		ucs->last_response = 0;
862 		return;
863 	}
864 
865 	sipe_private->ucs = ucs = g_new0(struct sipe_ucs, 1);
866 	ucs->migrated           = migrated;
867 
868 	/* create default transaction */
869 	sipe_ucs_transaction(sipe_private);
870 	ucs->default_transaction = ucs->transactions;
871 
872 	if (migrated) {
873 		/* user specified a service URL? */
874 		const gchar *ews_url = sipe_backend_setting(SIPE_CORE_PUBLIC, SIPE_SETTING_EMAIL_URL);
875 
876 		if (is_empty(ews_url))
877 			sipe_ews_autodiscover_start(sipe_private,
878 						    ucs_ews_autodiscover_cb,
879 						    NULL);
880 		else
881 			ucs_set_ews_url(sipe_private, ews_url);
882 	}
883 }
884 
sipe_ucs_free(struct sipe_core_private * sipe_private)885 void sipe_ucs_free(struct sipe_core_private *sipe_private)
886 {
887 	struct sipe_ucs *ucs = sipe_private->ucs;
888 	GSList *entry;
889 
890 	if (!ucs)
891 		return;
892 
893 	/* UCS stack is shutting down: reject all new requests */
894 	ucs->shutting_down = TRUE;
895 
896 	entry = ucs->transactions;
897 	while (entry) {
898 		struct sipe_ucs_transaction *trans = entry->data;
899 		GSList *entry2 = trans->pending_requests;
900 
901 		/* transactions get deleted by sipe_ucs_request_free() */
902 		entry = entry->next;
903 
904 		while (entry2) {
905 			struct ucs_request *request = entry2->data;
906 
907 			/* transactions get deleted by sipe_ucs_request_free() */
908 			entry2 = entry2->next;
909 
910 			sipe_ucs_request_free(sipe_private, request);
911 		}
912 
913 	}
914 	/* only default transaction is left... */
915 	sipe_utils_slist_free_full(ucs->transactions, g_free);
916 
917 	g_free(ucs->ews_url);
918 	g_free(ucs);
919 	sipe_private->ucs = NULL;
920 }
921 
922 /*
923   Local Variables:
924   mode: c
925   c-file-style: "bsd"
926   indent-tabs-mode: t
927   tab-width: 8
928   End:
929 */
930