1 /**
2  * @file sipe-buddy.c
3  *
4  * pidgin-sipe
5  *
6  * Copyright (C) 2010-2018 SIPE Project <http://sipe.sourceforge.net/>
7  *
8  * This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 2 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program; if not, write to the Free Software
20  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
21  *
22  * GetUserPhoto operation
23  *  <http://msdn.microsoft.com/en-us/library/office/jj900502.aspx>
24  */
25 
26 #ifdef HAVE_CONFIG_H
27 #include "config.h"
28 #endif
29 
30 #include <stdlib.h>
31 #include <string.h>
32 #include <time.h>
33 
34 #include <glib.h>
35 
36 #include "sipe-common.h"
37 #include "sipmsg.h"
38 #include "sip-csta.h"
39 #include "sip-soap.h"
40 #include "sip-transport.h"
41 #include "sipe-backend.h"
42 #include "sipe-buddy.h"
43 #include "sipe-cal.h"
44 #include "sipe-chat.h"
45 #include "sipe-conf.h"
46 #include "sipe-core.h"
47 #include "sipe-core-private.h"
48 #include "sipe-appshare.h"
49 #include "sipe-digest.h"
50 #include "sipe-group.h"
51 #include "sipe-http.h"
52 #include "sipe-im.h"
53 #include "sipe-media.h"
54 #include "sipe-nls.h"
55 #include "sipe-ocs2005.h"
56 #include "sipe-ocs2007.h"
57 #include "sipe-schedule.h"
58 #include "sipe-session.h"
59 #include "sipe-status.h"
60 #include "sipe-subscriptions.h"
61 #include "sipe-svc.h"
62 #include "sipe-ucs.h"
63 #include "sipe-utils.h"
64 #include "sipe-webticket.h"
65 #include "sipe-xml.h"
66 
67 struct sipe_buddies {
68 	GHashTable *uri;
69 	GHashTable *exchange_key;
70 
71 	/* Pending photo download HTTP requests */
72 	GSList *pending_photo_requests;
73 };
74 
75 struct buddy_group_data {
76 	const struct sipe_group *group;
77 	gboolean is_obsolete;
78 };
79 
80 struct photo_response_data {
81 	gchar *who;
82 	gchar *photo_hash;
83 	struct sipe_http_request *request;
84 };
85 
86 static void buddy_fetch_photo(struct sipe_core_private *sipe_private,
87 			      const gchar *uri);
88 static void photo_response_data_free(struct photo_response_data *data);
89 
sipe_buddy_add_keys(struct sipe_core_private * sipe_private,struct sipe_buddy * buddy,const gchar * exchange_key,const gchar * change_key)90 void sipe_buddy_add_keys(struct sipe_core_private *sipe_private,
91 			 struct sipe_buddy *buddy,
92 			 const gchar *exchange_key,
93 			 const gchar *change_key)
94 {
95 	if (exchange_key) {
96 		buddy->exchange_key = g_strdup(exchange_key);
97 		g_hash_table_insert(sipe_private->buddies->exchange_key,
98 				    buddy->exchange_key,
99 				    buddy);
100 	}
101 	if (change_key)
102 		buddy->change_key = g_strdup(change_key);
103 }
104 
sipe_buddy_add(struct sipe_core_private * sipe_private,const gchar * uri,const gchar * exchange_key,const gchar * change_key)105 struct sipe_buddy *sipe_buddy_add(struct sipe_core_private *sipe_private,
106 				  const gchar *uri,
107 				  const gchar *exchange_key,
108 				  const gchar *change_key)
109 {
110 	/* Buddy name must be lower case as we use purple_normalize_nocase() to compare */
111 	gchar *normalized_uri = g_ascii_strdown(uri, -1);
112 	struct sipe_buddy *buddy = sipe_buddy_find_by_uri(sipe_private,
113 							  normalized_uri);
114 
115 	if (!buddy) {
116 		buddy = g_new0(struct sipe_buddy, 1);
117 		buddy->name = normalized_uri;
118 		g_hash_table_insert(sipe_private->buddies->uri,
119 				    buddy->name,
120 				    buddy);
121 
122 		sipe_buddy_add_keys(sipe_private,
123 				    buddy,
124 				    exchange_key,
125 				    change_key);
126 
127 		SIPE_DEBUG_INFO("sipe_buddy_add: Added buddy %s", normalized_uri);
128 
129 		if (SIPE_CORE_PRIVATE_FLAG_IS(SUBSCRIBED_BUDDIES)) {
130 			buddy->just_added = TRUE;
131 			sipe_subscribe_presence_single_cb(sipe_private,
132 							  buddy->name);
133 		}
134 
135 		buddy_fetch_photo(sipe_private, normalized_uri);
136 
137 		normalized_uri = NULL; /* buddy takes ownership */
138 	} else {
139 		SIPE_DEBUG_INFO("sipe_buddy_add: Buddy %s already exists", normalized_uri);
140 		buddy->is_obsolete = FALSE;
141 	}
142 	g_free(normalized_uri);
143 
144 	return(buddy);
145 }
146 
is_buddy_in_group(struct sipe_buddy * buddy,const gchar * name)147 static gboolean is_buddy_in_group(struct sipe_buddy *buddy,
148 				  const gchar *name)
149 {
150 	if (buddy) {
151 		GSList *entry = buddy->groups;
152 
153 		while (entry) {
154 			struct buddy_group_data *bgd = entry->data;
155 			if (sipe_strequal(bgd->group->name, name)) {
156 				bgd->is_obsolete = FALSE;
157 				return(TRUE);
158 			}
159 			entry = entry->next;
160 		}
161 	}
162 
163 	return(FALSE);
164 }
165 
sipe_buddy_add_to_group(struct sipe_core_private * sipe_private,struct sipe_buddy * buddy,struct sipe_group * group,const gchar * alias)166 void sipe_buddy_add_to_group(struct sipe_core_private *sipe_private,
167 			     struct sipe_buddy *buddy,
168 			     struct sipe_group *group,
169 			     const gchar *alias)
170 {
171 	const gchar *uri = buddy->name;
172 	const gchar *group_name = group->name;
173 	sipe_backend_buddy bb = sipe_backend_buddy_find(SIPE_CORE_PUBLIC,
174 							uri,
175 							group_name);
176 
177 	if (!bb) {
178 		bb = sipe_backend_buddy_add(SIPE_CORE_PUBLIC,
179 					    uri,
180 					    alias,
181 					    group_name);
182 		SIPE_DEBUG_INFO("sipe_buddy_add_to_group: created backend buddy '%s' with alias '%s'",
183 				uri, alias ? alias : "<NONE>");
184 	}
185 
186 
187 	if (!is_empty(alias)) {
188 		gchar *old_alias = sipe_backend_buddy_get_alias(SIPE_CORE_PUBLIC,
189 								bb);
190 
191 		if (sipe_strcase_equal(sipe_get_no_sip_uri(uri),
192 				       old_alias)) {
193 			sipe_backend_buddy_set_alias(SIPE_CORE_PUBLIC,
194 						     bb,
195 						     alias);
196 			SIPE_DEBUG_INFO("sipe_buddy_add_to_group: replaced alias for buddy '%s': old '%s' new '%s'",
197 					uri, old_alias, alias);
198 		}
199 		g_free(old_alias);
200 	}
201 
202 	if (!is_buddy_in_group(buddy, group_name)) {
203 		sipe_buddy_insert_group(buddy, group);
204 		SIPE_DEBUG_INFO("sipe_buddy_add_to_group: added buddy %s to group %s",
205 				uri, group_name);
206 	}
207 }
208 
buddy_group_compare(gconstpointer a,gconstpointer b)209 static gint buddy_group_compare(gconstpointer a, gconstpointer b)
210 {
211 	return(((const struct buddy_group_data *)a)->group->id -
212 	       ((const struct buddy_group_data *)b)->group->id);
213 }
214 
sipe_buddy_insert_group(struct sipe_buddy * buddy,struct sipe_group * group)215 void sipe_buddy_insert_group(struct sipe_buddy *buddy,
216 			     struct sipe_group *group)
217 {
218 	struct buddy_group_data *bgd = g_new0(struct buddy_group_data, 1);
219 
220 	bgd->group = group;
221 
222 	buddy->groups = sipe_utils_slist_insert_unique_sorted(buddy->groups,
223 							      bgd,
224 							      buddy_group_compare,
225 							      NULL);
226 }
227 
buddy_group_free(gpointer data)228 static void buddy_group_free(gpointer data)
229 {
230 	g_free(data);
231 }
232 
buddy_group_remove(struct sipe_buddy * buddy,struct buddy_group_data * bgd)233 static void buddy_group_remove(struct sipe_buddy *buddy,
234 			       struct buddy_group_data *bgd)
235 {
236 	buddy->groups = g_slist_remove(buddy->groups, bgd);
237 	buddy_group_free(bgd);
238 }
239 
sipe_buddy_remove_group(struct sipe_buddy * buddy,const struct sipe_group * group)240 static void sipe_buddy_remove_group(struct sipe_buddy *buddy,
241 				    const struct sipe_group *group)
242 {
243 	GSList *entry = buddy->groups;
244 	struct buddy_group_data *bgd = NULL;
245 
246 	while (entry) {
247 		bgd = entry->data;
248 		if (bgd->group == group)
249 			break;
250 		entry = entry->next;
251 	}
252 
253 	buddy_group_remove(buddy, bgd);
254 }
255 
sipe_buddy_update_groups(struct sipe_core_private * sipe_private,struct sipe_buddy * buddy,GSList * new_groups)256 void sipe_buddy_update_groups(struct sipe_core_private *sipe_private,
257 			      struct sipe_buddy *buddy,
258 			      GSList *new_groups)
259 {
260 	const gchar *uri = buddy->name;
261 	GSList *entry = buddy->groups;
262 
263 	while (entry) {
264 		struct buddy_group_data *bgd = entry->data;
265 		const struct sipe_group *group = bgd->group;
266 
267 		/* next buddy group */
268 		entry = entry->next;
269 
270 		/* old group NOT found in new list? */
271 		if (g_slist_find(new_groups, group) == NULL) {
272 			sipe_backend_buddy oldb = sipe_backend_buddy_find(SIPE_CORE_PUBLIC,
273 									  uri,
274 									  group->name);
275 			SIPE_DEBUG_INFO("sipe_buddy_update_groups: removing buddy %s from group '%s'",
276 					uri, group->name);
277 			/* this should never be NULL */
278 			if (oldb)
279 				sipe_backend_buddy_remove(SIPE_CORE_PUBLIC,
280 							  oldb);
281 			buddy_group_remove(buddy, bgd);
282 		}
283 	}
284 }
285 
sipe_buddy_groups_string(struct sipe_buddy * buddy)286 gchar *sipe_buddy_groups_string(struct sipe_buddy *buddy)
287 {
288 	guint i = 0;
289 	gchar *string;
290 	/* creating array from GList, converting guint to gchar * */
291 	gchar **ids_arr = g_new(gchar *, g_slist_length(buddy->groups) + 1);
292 	GSList *entry = buddy->groups;
293 
294 	if (!ids_arr)
295 		return(NULL);
296 
297 	while (entry) {
298 		const struct sipe_group *group = ((struct buddy_group_data *) entry->data)->group;
299 		ids_arr[i] = g_strdup_printf("%u", group->id);
300 		entry = entry->next;
301 		i++;
302 	}
303 	ids_arr[i] = NULL;
304 
305 	string = g_strjoinv(" ", ids_arr);
306 	g_strfreev(ids_arr);
307 
308 	return(string);
309 }
310 
sipe_buddy_cleanup_local_list(struct sipe_core_private * sipe_private)311 void sipe_buddy_cleanup_local_list(struct sipe_core_private *sipe_private)
312 {
313 	GSList *buddies = sipe_backend_buddy_find_all(SIPE_CORE_PUBLIC,
314 						      NULL,
315 						      NULL);
316 	GSList *entry = buddies;
317 
318 	SIPE_DEBUG_INFO("sipe_buddy_cleanup_local_list: overall %d backend buddies (including clones)",
319 			g_slist_length(buddies));
320 	SIPE_DEBUG_INFO("sipe_buddy_cleanup_local_list: %d sipe buddies (unique)",
321 			sipe_buddy_count(sipe_private));
322 	while (entry) {
323 		sipe_backend_buddy bb = entry->data;
324 		gchar *bname = sipe_backend_buddy_get_name(SIPE_CORE_PUBLIC,
325 							   bb);
326 		gchar *gname = sipe_backend_buddy_get_group_name(SIPE_CORE_PUBLIC,
327 								 bb);
328 		struct sipe_buddy *buddy = sipe_buddy_find_by_uri(sipe_private,
329 								  bname);
330 
331 		if (!is_buddy_in_group(buddy, gname)) {
332 			SIPE_DEBUG_INFO("sipe_buddy_cleanup_local_list: REMOVING '%s' from local group '%s', as buddy is not in that group on remote contact list",
333 					bname, gname);
334 			sipe_backend_buddy_remove(SIPE_CORE_PUBLIC, bb);
335 		}
336 
337 		g_free(gname);
338 		g_free(bname);
339 
340 		entry = entry->next;
341 	}
342 
343 	g_slist_free(buddies);
344 }
345 
sipe_buddy_find_by_uri(struct sipe_core_private * sipe_private,const gchar * uri)346 struct sipe_buddy *sipe_buddy_find_by_uri(struct sipe_core_private *sipe_private,
347 					  const gchar *uri)
348 {
349 	if (!uri) return(NULL);
350 	return(g_hash_table_lookup(sipe_private->buddies->uri, uri));
351 }
352 
sipe_buddy_find_by_exchange_key(struct sipe_core_private * sipe_private,const gchar * exchange_key)353 struct sipe_buddy *sipe_buddy_find_by_exchange_key(struct sipe_core_private *sipe_private,
354 						   const gchar *exchange_key)
355 {
356 	return(g_hash_table_lookup(sipe_private->buddies->exchange_key,
357 				   exchange_key));
358 }
359 
sipe_buddy_foreach(struct sipe_core_private * sipe_private,GHFunc callback,gpointer callback_data)360 void sipe_buddy_foreach(struct sipe_core_private *sipe_private,
361 			GHFunc callback,
362 			gpointer callback_data)
363 {
364 	g_hash_table_foreach(sipe_private->buddies->uri,
365 			     callback,
366 			     callback_data);
367 }
368 
buddy_free(struct sipe_buddy * buddy)369 static void buddy_free(struct sipe_buddy *buddy)
370 {
371 #ifndef _WIN32
372 	 /*
373 	  * We are calling g_hash_table_foreach_steal(). That means that no
374 	  * key/value deallocation functions are called. Therefore the glib
375 	  * hash code does not touch the key (buddy->name) or value (buddy)
376 	  * of the to-be-deleted hash node at all. It follows that we
377 	  *
378 	  *   - MUST free the memory for the key ourselves and
379 	  *   - ARE allowed to do it in this function
380 	  *
381 	  * Conclusion: glib must be broken on the Windows platform if sipe
382 	  *             crashes with SIGTRAP when closing. You'll have to live
383 	  *             with the memory leak until this is fixed.
384 	  */
385 	g_free(buddy->name);
386 #endif
387 	g_free(buddy->exchange_key);
388 	g_free(buddy->change_key);
389 	g_free(buddy->activity);
390 	g_free(buddy->meeting_subject);
391 	g_free(buddy->meeting_location);
392 	g_free(buddy->note);
393 
394 	g_free(buddy->cal_start_time);
395 	g_free(buddy->cal_free_busy_base64);
396 	g_free(buddy->cal_free_busy);
397 	g_free(buddy->last_non_cal_activity);
398 
399 	sipe_cal_free_working_hours(buddy->cal_working_hours);
400 
401 	g_free(buddy->device_name);
402 	sipe_utils_slist_free_full(buddy->groups, buddy_group_free);
403 	g_free(buddy);
404 }
405 
buddy_free_cb(SIPE_UNUSED_PARAMETER gpointer key,gpointer buddy,SIPE_UNUSED_PARAMETER gpointer user_data)406 static gboolean buddy_free_cb(SIPE_UNUSED_PARAMETER gpointer key,
407 			      gpointer buddy,
408 			      SIPE_UNUSED_PARAMETER gpointer user_data)
409 {
410 	buddy_free(buddy);
411 	/* We must return TRUE as the key/value have already been deleted */
412 	return(TRUE);
413 }
414 
sipe_buddy_free(struct sipe_core_private * sipe_private)415 void sipe_buddy_free(struct sipe_core_private *sipe_private)
416 {
417 	struct sipe_buddies *buddies = sipe_private->buddies;
418 
419 	g_hash_table_foreach_steal(buddies->uri,
420 				   buddy_free_cb,
421 				   NULL);
422 
423 	/* core is being deallocated, remove all its pending photo requests */
424 	while (buddies->pending_photo_requests) {
425 		struct photo_response_data *data =
426 			buddies->pending_photo_requests->data;
427 		buddies->pending_photo_requests =
428 			g_slist_remove(buddies->pending_photo_requests, data);
429 		photo_response_data_free(data);
430 	}
431 
432 	g_hash_table_destroy(buddies->uri);
433 	g_hash_table_destroy(buddies->exchange_key);
434 	g_free(buddies);
435 	sipe_private->buddies = NULL;
436 }
437 
buddy_set_obsolete_flag(SIPE_UNUSED_PARAMETER gpointer key,gpointer value,SIPE_UNUSED_PARAMETER gpointer user_data)438 static void buddy_set_obsolete_flag(SIPE_UNUSED_PARAMETER gpointer key,
439 				    gpointer value,
440 				    SIPE_UNUSED_PARAMETER gpointer user_data)
441 {
442 	struct sipe_buddy *buddy = value;
443 	GSList *entry = buddy->groups;
444 
445 	buddy->is_obsolete = TRUE;
446 	while (entry) {
447 		((struct buddy_group_data *) entry->data)->is_obsolete = TRUE;
448 		entry = entry->next;
449 	}
450 }
451 
sipe_buddy_update_start(struct sipe_core_private * sipe_private)452 void sipe_buddy_update_start(struct sipe_core_private *sipe_private)
453 {
454 	g_hash_table_foreach(sipe_private->buddies->uri,
455 			     buddy_set_obsolete_flag,
456 			     NULL);
457 }
458 
buddy_check_obsolete_flag(SIPE_UNUSED_PARAMETER gpointer key,gpointer value,gpointer user_data)459 static gboolean buddy_check_obsolete_flag(SIPE_UNUSED_PARAMETER gpointer key,
460 					  gpointer value,
461 					  gpointer user_data)
462 {
463 	struct sipe_core_private *sipe_private = user_data;
464 	struct sipe_buddy *buddy = value;
465 	const gchar *uri = buddy->name;
466 
467 	if (buddy->is_obsolete) {
468 		/* all backend buddies in different groups */
469 		GSList *buddies = sipe_backend_buddy_find_all(SIPE_CORE_PUBLIC,
470 							      uri,
471 							      NULL);
472 		GSList *entry = buddies;
473 
474 		SIPE_DEBUG_INFO("buddy_check_obsolete_flag: REMOVING %d backend buddies for '%s'",
475 				g_slist_length(buddies),
476 				uri);
477 
478 		while (entry) {
479 			sipe_backend_buddy_remove(SIPE_CORE_PUBLIC,
480 						  entry->data);
481 			entry = entry->next;
482 		}
483 		g_slist_free(buddies);
484 
485 		buddy_free(buddy);
486 		/* return TRUE as the key/value have already been deleted */
487 		return(TRUE);
488 
489 	} else {
490 		GSList *entry = buddy->groups;
491 
492 		while (entry) {
493 			struct buddy_group_data *bgd = entry->data;
494 
495 			/* next buddy group */
496 			entry = entry->next;
497 
498 			if (bgd->is_obsolete) {
499 				const struct sipe_group *group = bgd->group;
500 				sipe_backend_buddy oldb = sipe_backend_buddy_find(SIPE_CORE_PUBLIC,
501 										  uri,
502 										  group->name);
503 				SIPE_DEBUG_INFO("buddy_check_obsolete_flag: removing buddy '%s' from group '%s'",
504 						uri, group->name);
505 				/* this should never be NULL */
506 				if (oldb)
507 					sipe_backend_buddy_remove(SIPE_CORE_PUBLIC,
508 								  oldb);
509 				buddy_group_remove(buddy, bgd);
510 			}
511 		}
512 		return(FALSE);
513 	}
514 }
515 
sipe_buddy_update_finish(struct sipe_core_private * sipe_private)516 void sipe_buddy_update_finish(struct sipe_core_private *sipe_private)
517 {
518 	g_hash_table_foreach_remove(sipe_private->buddies->uri,
519 				    buddy_check_obsolete_flag,
520 				    sipe_private);
521 }
522 
sipe_core_buddy_status(struct sipe_core_public * sipe_public,const gchar * uri,guint activity,const gchar * status_text)523 gchar *sipe_core_buddy_status(struct sipe_core_public *sipe_public,
524 			      const gchar *uri,
525 			      guint activity,
526 			      const gchar *status_text)
527 {
528 	struct sipe_buddy *sbuddy;
529 	GString *status;
530 
531 	if (!sipe_public) return NULL; /* happens on pidgin exit */
532 
533 	sbuddy = sipe_buddy_find_by_uri(SIPE_CORE_PRIVATE, uri);
534 	if (!sbuddy) return NULL;
535 
536 	status = g_string_new(sbuddy->activity ? sbuddy->activity :
537 			      (activity == SIPE_ACTIVITY_BUSY) || (activity == SIPE_ACTIVITY_BRB) ?
538 			      status_text : NULL);
539 
540 	if (sbuddy->is_mobile) {
541 		if (status->len)
542 			g_string_append(status, " - ");
543 		g_string_append(status, _("Mobile"));
544 	}
545 
546 	if (sbuddy->note) {
547 		if (status->len)
548 			g_string_append(status, " - ");
549 		g_string_append(status, sbuddy->note);
550 	}
551 
552 	/* return NULL instead of empty status text */
553 	return(g_string_free(status, status->len ? FALSE : TRUE));
554 }
555 
sipe_buddy_get_alias(struct sipe_core_private * sipe_private,const gchar * with)556 gchar *sipe_buddy_get_alias(struct sipe_core_private *sipe_private,
557 			    const gchar *with)
558 {
559 	sipe_backend_buddy pbuddy;
560 	gchar *alias = NULL;
561 	if ((pbuddy = sipe_backend_buddy_find(SIPE_CORE_PUBLIC, with, NULL))) {
562 		alias = sipe_backend_buddy_get_alias(SIPE_CORE_PUBLIC, pbuddy);
563 	}
564 	return alias;
565 }
566 
sipe_core_buddy_group(struct sipe_core_public * sipe_public,const gchar * who,const gchar * old_group_name,const gchar * new_group_name)567 void sipe_core_buddy_group(struct sipe_core_public *sipe_public,
568 			   const gchar *who,
569 			   const gchar *old_group_name,
570 			   const gchar *new_group_name)
571 {
572 	struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
573 	struct sipe_buddy *buddy = sipe_buddy_find_by_uri(sipe_private,
574 							  who);
575 	struct sipe_group *old_group = NULL;
576 	struct sipe_group *new_group;
577 	struct sipe_ucs_transaction *ucs_trans = NULL;
578 
579 	SIPE_DEBUG_INFO("sipe_core_buddy_group: buddy '%s' old group '%s' new group '%s'",
580 			who ? who : "",
581 			old_group_name ? old_group_name : "<UNDEFINED>",
582 			new_group_name ? new_group_name : "<UNDEFINED>");
583 
584 	if (!buddy)
585 		/* buddy not in roaming list */
586 		return;
587 
588 	old_group = sipe_group_find_by_name(sipe_private, old_group_name);
589 	if (old_group) {
590 		sipe_buddy_remove_group(buddy, old_group);
591 		SIPE_DEBUG_INFO("sipe_core_buddy_group: buddy '%s' removed from old group '%s'",
592 				who, old_group_name);
593 	}
594 
595 	new_group = sipe_group_find_by_name(sipe_private, new_group_name);
596 	if (new_group) {
597 		sipe_buddy_insert_group(buddy, new_group);
598 		SIPE_DEBUG_INFO("sipe_core_buddy_group: buddy '%s' added to new group '%s'",
599 				who, new_group_name);
600 	}
601 
602 	if (sipe_ucs_is_migrated(sipe_private)) {
603 
604 		/* UCS handling */
605 		ucs_trans = sipe_ucs_transaction(sipe_private);
606 
607 		if (new_group) {
608 			/*
609 			 * 1. new buddy added to existing group
610 			 * 2. existing buddy moved from old to existing group
611 			 */
612 			sipe_ucs_group_add_buddy(sipe_private,
613 						 ucs_trans,
614 						 new_group,
615 						 buddy,
616 						 buddy->name);
617 			if (old_group)
618 				sipe_ucs_group_remove_buddy(sipe_private,
619 							    ucs_trans,
620 							    old_group,
621 							    buddy);
622 
623 		} else if (old_group) {
624 			/*
625 			 * 3. existing buddy removed from one of its groups
626 			 * 4. existing buddy removed from last group
627 			 */
628 			sipe_ucs_group_remove_buddy(sipe_private,
629 						    ucs_trans,
630 						    old_group,
631 						    buddy);
632 			if (g_slist_length(buddy->groups) < 1)
633 				sipe_buddy_remove(sipe_private,
634 						  buddy);
635 				/* buddy no longer valid */
636 		}
637 
638 	/* non-UCS handling */
639 	} else if (new_group)
640 		sipe_group_update_buddy(sipe_private, buddy);
641 
642 	/* 5. buddy added to new group */
643 	if (!new_group)
644 		sipe_group_create(sipe_private,
645 				  ucs_trans,
646 				  new_group_name,
647 				  who);
648 }
649 
sipe_core_buddy_add(struct sipe_core_public * sipe_public,const gchar * uri,const gchar * group_name)650 void sipe_core_buddy_add(struct sipe_core_public *sipe_public,
651 			 const gchar *uri,
652 			 const gchar *group_name)
653 {
654 	struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
655 
656 	if (!sipe_buddy_find_by_uri(sipe_private, uri))
657 		sipe_buddy_add(sipe_private,
658 			       uri,
659 			       NULL,
660 			       NULL);
661 	else
662 		SIPE_DEBUG_INFO("sipe_core_buddy_add: buddy %s already in internal list",
663 				uri);
664 
665 	sipe_core_buddy_group(sipe_public,
666 			      uri,
667 			      NULL,
668 			      group_name);
669 }
670 
sipe_buddy_remove(struct sipe_core_private * sipe_private,struct sipe_buddy * buddy)671 void sipe_buddy_remove(struct sipe_core_private *sipe_private,
672 		       struct sipe_buddy *buddy)
673 {
674 	struct sipe_buddies *buddies = sipe_private->buddies;
675 	const gchar *uri = buddy->name;
676 	GSList *entry = buddy->groups;
677 	gchar *action_name = sipe_utils_presence_key(uri);
678 
679 	sipe_schedule_cancel(sipe_private, action_name);
680 	g_free(action_name);
681 
682 	/* If the buddy still has groups, we need to delete backend buddies */
683 	while (entry) {
684 		const struct sipe_group *group = ((struct buddy_group_data *) entry->data)->group;
685 		sipe_backend_buddy oldb = sipe_backend_buddy_find(SIPE_CORE_PUBLIC,
686 								  uri,
687 								  group->name);
688 		/* this should never be NULL */
689 		if (oldb)
690 			sipe_backend_buddy_remove(SIPE_CORE_PUBLIC, oldb);
691 
692 		entry = entry->next;
693 	}
694 
695 	g_hash_table_remove(buddies->uri, uri);
696 	if (buddy->exchange_key)
697 		g_hash_table_remove(buddies->exchange_key,
698 				    buddy->exchange_key);
699 
700 	buddy_free(buddy);
701 }
702 
703 /**
704  * Unassociates buddy from group first.
705  * Then see if no groups left, removes buddy completely.
706  * Otherwise updates buddy groups on server.
707  */
sipe_core_buddy_remove(struct sipe_core_public * sipe_public,const gchar * uri,const gchar * group_name)708 void sipe_core_buddy_remove(struct sipe_core_public *sipe_public,
709 			    const gchar *uri,
710 			    const gchar *group_name)
711 {
712 	struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
713 	struct sipe_buddy *buddy = sipe_buddy_find_by_uri(sipe_private,
714 							  uri);
715 	struct sipe_group *group = NULL;
716 
717 	if (!buddy) return;
718 
719 	if (group_name) {
720 		group = sipe_group_find_by_name(sipe_private, group_name);
721 		if (group) {
722 			sipe_buddy_remove_group(buddy, group);
723 			SIPE_DEBUG_INFO("sipe_core_buddy_remove: buddy '%s' removed from group '%s'",
724 					uri, group->name);
725 		}
726 	}
727 
728 	if (g_slist_length(buddy->groups) < 1) {
729 
730 		if (sipe_ucs_is_migrated(sipe_private)) {
731 			sipe_ucs_group_remove_buddy(sipe_private,
732 						    NULL,
733 						    group,
734 						    buddy);
735 		} else {
736 			gchar *request = g_strdup_printf("<m:URI>%s</m:URI>",
737 							 buddy->name);
738 			sip_soap_request(sipe_private,
739 					 "deleteContact",
740 					 request);
741 			g_free(request);
742 		}
743 
744 		sipe_buddy_remove(sipe_private, buddy);
745 	} else {
746 		if (sipe_ucs_is_migrated(sipe_private)) {
747 			sipe_ucs_group_remove_buddy(sipe_private,
748 						    NULL,
749 						    group,
750 						    buddy);
751 		} else
752 			/* updates groups on server */
753 			sipe_group_update_buddy(sipe_private, buddy);
754 	}
755 }
756 
sipe_core_buddy_got_status(struct sipe_core_public * sipe_public,const gchar * uri,guint activity)757 void sipe_core_buddy_got_status(struct sipe_core_public *sipe_public,
758 				const gchar *uri,
759 				guint activity)
760 {
761 	struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
762 	struct sipe_buddy *sbuddy = sipe_buddy_find_by_uri(sipe_private,
763 							   uri);
764 
765 	if (!sbuddy) return;
766 
767 	/* Check if on 2005 system contact's calendar,
768 	 * then set/preserve it.
769 	 */
770 	if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
771 		sipe_backend_buddy_set_status(sipe_public, uri, activity);
772 	} else {
773 		sipe_ocs2005_apply_calendar_status(sipe_private,
774 						   sbuddy,
775 						   sipe_status_activity_to_token(activity));
776 	}
777 }
778 
sipe_core_buddy_tooltip_info(struct sipe_core_public * sipe_public,const gchar * uri,const gchar * status_name,gboolean is_online,struct sipe_backend_buddy_tooltip * tooltip)779 void sipe_core_buddy_tooltip_info(struct sipe_core_public *sipe_public,
780 				  const gchar *uri,
781 				  const gchar *status_name,
782 				  gboolean is_online,
783 				  struct sipe_backend_buddy_tooltip *tooltip)
784 {
785 	struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
786 	gchar *note = NULL;
787 	gboolean is_oof_note = FALSE;
788 	const gchar *activity = NULL;
789 	gchar *calendar = NULL;
790 	const gchar *meeting_subject = NULL;
791 	const gchar *meeting_location = NULL;
792 	gchar *access_text = NULL;
793 
794 #define SIPE_ADD_BUDDY_INFO(l, t) \
795 	{ \
796 		gchar *tmp = g_markup_escape_text((t), -1); \
797 		sipe_backend_buddy_tooltip_add(sipe_public, tooltip, (l), tmp); \
798 		g_free(tmp); \
799 	}
800 #define SIPE_ADD_BUDDY_INFO_NOESCAPE(l, t) \
801 	sipe_backend_buddy_tooltip_add(sipe_public, tooltip, (l), (t))
802 
803 	if (sipe_public) { /* happens on pidgin exit */
804 		struct sipe_buddy *sbuddy = sipe_buddy_find_by_uri(sipe_private,
805 								   uri);
806 		if (sbuddy) {
807 			note = sbuddy->note;
808 			is_oof_note = sbuddy->is_oof_note;
809 			activity = sbuddy->activity;
810 			calendar = sipe_cal_get_description(sbuddy);
811 			meeting_subject = sbuddy->meeting_subject;
812 			meeting_location = sbuddy->meeting_location;
813 		}
814 		if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
815 			gboolean is_group_access = FALSE;
816 			const int container_id = sipe_ocs2007_find_access_level(sipe_private,
817 										"user",
818 										sipe_get_no_sip_uri(uri),
819 										&is_group_access);
820 			const char *access_level = sipe_ocs2007_access_level_name(container_id);
821 			access_text = is_group_access ?
822 				g_strdup(access_level) :
823 				g_strdup_printf(SIPE_OCS2007_INDENT_MARKED_FMT,
824 						access_level);
825 		}
826 	}
827 
828 	if (is_online) {
829 		const gchar *status_str = activity ? activity : status_name;
830 
831 		SIPE_ADD_BUDDY_INFO(_("Status"), status_str);
832 	}
833 	if (is_online && !is_empty(calendar)) {
834 		SIPE_ADD_BUDDY_INFO(_("Calendar"), calendar);
835 	}
836 	g_free(calendar);
837 	if (!is_empty(meeting_location)) {
838 		SIPE_DEBUG_INFO("sipe_tooltip_text: %s meeting location: '%s'", uri, meeting_location);
839 		SIPE_ADD_BUDDY_INFO(_("Meeting in"), meeting_location);
840 	}
841 	if (!is_empty(meeting_subject)) {
842 		SIPE_DEBUG_INFO("sipe_tooltip_text: %s meeting subject: '%s'", uri, meeting_subject);
843 		SIPE_ADD_BUDDY_INFO(_("Meeting about"), meeting_subject);
844 	}
845 	if (note) {
846 		gchar *note_italics = g_strdup_printf("<i>%s</i>", note);
847 		SIPE_DEBUG_INFO("sipe_tooltip_text: %s note: '%s'", uri, note);
848 		SIPE_ADD_BUDDY_INFO_NOESCAPE(is_oof_note ? _("Out of office note") : _("Note"),
849 					     note_italics);
850 		g_free(note_italics);
851 	}
852 	if (access_text) {
853 		SIPE_ADD_BUDDY_INFO(_("Access level"), access_text);
854 		g_free(access_text);
855 	}
856 }
857 
sipe_buddy_update_property(struct sipe_core_private * sipe_private,const char * uri,sipe_buddy_info_fields propkey,char * property_value)858 void sipe_buddy_update_property(struct sipe_core_private *sipe_private,
859 				const char *uri,
860 				sipe_buddy_info_fields propkey,
861 				char *property_value)
862 {
863 	GSList *buddies, *entry;
864 
865 	if (property_value)
866 		property_value = g_strstrip(property_value);
867 
868 	entry = buddies = sipe_backend_buddy_find_all(SIPE_CORE_PUBLIC, uri, NULL); /* all buddies in different groups */
869 	while (entry) {
870 		gchar *prop_str;
871 		sipe_backend_buddy p_buddy = entry->data;
872 
873 		/* for Display Name */
874 		if (propkey == SIPE_BUDDY_INFO_DISPLAY_NAME) {
875 			gchar *alias;
876 			alias = sipe_backend_buddy_get_alias(SIPE_CORE_PUBLIC, p_buddy);
877 			if (property_value && sipe_is_bad_alias(uri, alias)) {
878 				SIPE_DEBUG_INFO("Replacing alias for %s with %s", uri, property_value);
879 				sipe_backend_buddy_set_alias(SIPE_CORE_PUBLIC, p_buddy, property_value);
880 			}
881 			g_free(alias);
882 
883 			alias = sipe_backend_buddy_get_server_alias(SIPE_CORE_PUBLIC, p_buddy);
884 			if (!is_empty(property_value) &&
885 			   (!sipe_strequal(property_value, alias) || is_empty(alias)) )
886 			{
887 				SIPE_DEBUG_INFO("Replacing service alias for %s with %s", uri, property_value);
888 				sipe_backend_buddy_set_server_alias(SIPE_CORE_PUBLIC, p_buddy, property_value);
889 			}
890 			g_free(alias);
891 		}
892 		/* for other properties */
893 		else {
894 			if (!is_empty(property_value)) {
895 				prop_str = sipe_backend_buddy_get_string(SIPE_CORE_PUBLIC, p_buddy, propkey);
896 				if (!prop_str || !sipe_strcase_equal(prop_str, property_value)) {
897 					sipe_backend_buddy_set_string(SIPE_CORE_PUBLIC, p_buddy, propkey, property_value);
898 				}
899 				g_free(prop_str);
900 			}
901 		}
902 
903 		entry = entry->next;
904 	}
905 	g_slist_free(buddies);
906 }
907 
908 
909 struct ms_dlx_data;
910 struct ms_dlx_data {
911 	GSList *search_rows;
912 	gchar  *other;
913 	guint   max_returns;
914 	sipe_svc_callback *callback;
915 	struct sipe_svc_session *session;
916 	gchar *wsse_security;
917 	struct sipe_backend_search_token *token;
918 	/* must call ms_dlx_free() */
919 	void (*failed_callback)(struct sipe_core_private *sipe_private,
920 				struct ms_dlx_data *mdd);
921 };
922 
free_search_rows(GSList * search_rows)923 static void free_search_rows(GSList *search_rows)
924 {
925 	sipe_utils_slist_free_full(search_rows, g_free);
926 }
927 
ms_dlx_free(struct ms_dlx_data * mdd)928 static void ms_dlx_free(struct ms_dlx_data *mdd)
929 {
930 	free_search_rows(mdd->search_rows);
931 	sipe_svc_session_close(mdd->session);
932 	g_free(mdd->other);
933 	g_free(mdd->wsse_security);
934 	g_free(mdd);
935 }
936 
937 #define SIPE_SOAP_SEARCH_ROW "<m:row m:attrib=\"%s\" m:value=\"%s\"/>"
938 #define DLX_SEARCH_ITEM							\
939 	"<AbEntryRequest.ChangeSearchQuery>"				\
940 	" <SearchOn>%s</SearchOn>"					\
941 	" <Value>%s</Value>"						\
942 	"</AbEntryRequest.ChangeSearchQuery>"
943 
prepare_buddy_search_query(GSList * query_rows,gboolean use_dlx)944 static gchar * prepare_buddy_search_query(GSList *query_rows, gboolean use_dlx) {
945 	gchar **attrs = g_new(gchar *, (g_slist_length(query_rows) / 2) + 1);
946 	guint i = 0;
947 	gchar *query = NULL;
948 
949 	while (query_rows) {
950 		gchar *attr;
951 		gchar *value;
952 		gchar *tmp = NULL;
953 
954 		attr = query_rows->data;
955 		query_rows = g_slist_next(query_rows);
956 		value = query_rows->data;
957 		query_rows = g_slist_next(query_rows);
958 
959 		if (!value)
960 			break;
961 
962 		/*
963 		 * Special value for SIP ID
964 		 *
965 		 * Active Directory seems only to be able to search for
966 		 * SIP URIs. Make sure search string starts with "sip:".
967 		 */
968 		if (!attr) {
969 			attr = "msRTCSIP-PrimaryUserAddress";
970 			if (!use_dlx)
971 				value = tmp = sip_uri(value);
972 		}
973 
974 		attrs[i++] = g_markup_printf_escaped(use_dlx ? DLX_SEARCH_ITEM : SIPE_SOAP_SEARCH_ROW,
975 						     attr, value);
976 		g_free(tmp);
977 	}
978 	attrs[i] = NULL;
979 
980 	if (i) {
981 		query = g_strjoinv(NULL, attrs);
982 		SIPE_DEBUG_INFO("prepare_buddy_search_query: rows:\n%s",
983 				query ? query : "");
984 	}
985 
986 	g_strfreev(attrs);
987 
988 	return query;
989 }
990 
ms_dlx_webticket(struct sipe_core_private * sipe_private,const gchar * base_uri,const gchar * auth_uri,const gchar * wsse_security,SIPE_UNUSED_PARAMETER const gchar * failure_msg,gpointer callback_data)991 static void ms_dlx_webticket(struct sipe_core_private *sipe_private,
992 			     const gchar *base_uri,
993 			     const gchar *auth_uri,
994 			     const gchar *wsse_security,
995 			     SIPE_UNUSED_PARAMETER const gchar *failure_msg,
996 			     gpointer callback_data)
997 {
998 	struct ms_dlx_data *mdd = callback_data;
999 
1000 	if (wsse_security) {
1001 		guint length = g_slist_length(mdd->search_rows);
1002 		gchar *search;
1003 
1004 		SIPE_DEBUG_INFO("ms_dlx_webticket: got ticket for %s",
1005 				base_uri);
1006 
1007 		if (length > 0) {
1008 			/* complex search */
1009 			gchar *query = prepare_buddy_search_query(mdd->search_rows, TRUE);
1010 			search = g_strdup_printf("<ChangeSearch xmlns:q1=\"DistributionListExpander\" soapenc:arrayType=\"q1:AbEntryRequest.ChangeSearchQuery[%d]\">"
1011 						 " %s"
1012 						 "</ChangeSearch>",
1013 						 length / 2,
1014 						 query);
1015 			g_free(query);
1016 		} else {
1017 			/* simple search */
1018 			search = g_strdup_printf("<BasicSearch>"
1019 						 " <SearchList>c,company,displayName,givenName,mail,mailNickname,msRTCSIP-PrimaryUserAddress,sn</SearchList>"
1020 						 " <Value>%s</Value>"
1021 						 " <Verb>BeginsWith</Verb>"
1022 						 "</BasicSearch>",
1023 						 mdd->other);
1024 		}
1025 
1026 		if (sipe_svc_ab_entry_request(sipe_private,
1027 					      mdd->session,
1028 					      auth_uri,
1029 					      wsse_security,
1030 					      search,
1031 					      mdd->max_returns,
1032 					      mdd->callback,
1033 					      mdd)) {
1034 
1035 			/* keep webticket security token for potential further use */
1036 			g_free(mdd->wsse_security);
1037 			mdd->wsse_security = g_strdup(wsse_security);
1038 
1039 			/* callback data passed down the line */
1040 			mdd = NULL;
1041 		}
1042 		g_free(search);
1043 
1044 	} else {
1045 		/* no ticket: this will show the minmum information */
1046 		SIPE_DEBUG_ERROR("ms_dlx_webticket: no web ticket for %s",
1047 				 base_uri);
1048 	}
1049 
1050 	if (mdd)
1051 		mdd->failed_callback(sipe_private, mdd);
1052 }
1053 
ms_dlx_webticket_request(struct sipe_core_private * sipe_private,struct ms_dlx_data * mdd)1054 static void ms_dlx_webticket_request(struct sipe_core_private *sipe_private,
1055 				     struct ms_dlx_data *mdd)
1056 {
1057 	if (!sipe_webticket_request_with_port(sipe_private,
1058 					      mdd->session,
1059 					      sipe_private->dlx_uri,
1060 					      "AddressBookWebTicketBearer",
1061 					      ms_dlx_webticket,
1062 					      mdd)) {
1063 		SIPE_DEBUG_ERROR("ms_dlx_webticket_request: couldn't request webticket for %s",
1064 				 sipe_private->dlx_uri);
1065 		mdd->failed_callback(sipe_private, mdd);
1066 	}
1067 }
1068 
sipe_buddy_search_contacts_finalize(struct sipe_core_private * sipe_private,struct sipe_backend_search_results * results,guint match_count,gboolean more)1069 void sipe_buddy_search_contacts_finalize(struct sipe_core_private *sipe_private,
1070 					 struct sipe_backend_search_results *results,
1071 					 guint match_count,
1072 					 gboolean more)
1073 {
1074 	gchar *secondary = g_strdup_printf(
1075 		dngettext(PACKAGE_NAME,
1076 			  "Found %d contact%s:",
1077 			  "Found %d contacts%s:", match_count),
1078 		match_count, more ? _(" (more matched your query)") : "");
1079 
1080 	sipe_backend_search_results_finalize(SIPE_CORE_PUBLIC,
1081 					     results,
1082 					     secondary,
1083 					     more);
1084 	g_free(secondary);
1085 }
1086 
search_ab_entry_response(struct sipe_core_private * sipe_private,const gchar * uri,SIPE_UNUSED_PARAMETER const gchar * raw,sipe_xml * soap_body,gpointer callback_data)1087 static void search_ab_entry_response(struct sipe_core_private *sipe_private,
1088 				     const gchar *uri,
1089 				     SIPE_UNUSED_PARAMETER const gchar *raw,
1090 				     sipe_xml *soap_body,
1091 				     gpointer callback_data)
1092 {
1093 	struct ms_dlx_data *mdd = callback_data;
1094 
1095 	if (soap_body) {
1096 		const sipe_xml *node;
1097 		struct sipe_backend_search_results *results;
1098 		GHashTable *found;
1099 
1100 		SIPE_DEBUG_INFO("search_ab_entry_response: received valid SOAP message from service %s",
1101 				uri);
1102 
1103 		/* any matches? */
1104 		node = sipe_xml_child(soap_body, "Body/SearchAbEntryResponse/SearchAbEntryResult/Items/AbEntry");
1105 		if (!node) {
1106 			/* try again with simple search, if possible */
1107 			if (mdd->other && mdd->search_rows) {
1108 				SIPE_DEBUG_INFO_NOFORMAT("search_ab_entry_response: no matches, retrying with simple search");
1109 
1110 				/* throw away original search query */
1111 				free_search_rows(mdd->search_rows);
1112 				mdd->search_rows = NULL;
1113 
1114 				ms_dlx_webticket_request(sipe_private, mdd);
1115 
1116 				/* callback data passed down the line */
1117 				return;
1118 
1119 			} else {
1120 				SIPE_DEBUG_ERROR_NOFORMAT("search_ab_entry_response: no matches");
1121 
1122 				sipe_backend_search_failed(SIPE_CORE_PUBLIC,
1123 							   mdd->token,
1124 							   _("No contacts found"));
1125 				ms_dlx_free(mdd);
1126 				return;
1127 			}
1128 		}
1129 
1130 		/* OK, we found something - show the results to the user */
1131 		results = sipe_backend_search_results_start(SIPE_CORE_PUBLIC,
1132 							    mdd->token);
1133 		if (!results) {
1134 			SIPE_DEBUG_ERROR_NOFORMAT("search_ab_entry_response: Unable to display the search results.");
1135 			sipe_backend_search_failed(SIPE_CORE_PUBLIC,
1136 						   mdd->token,
1137 						   _("Unable to display the search results"));
1138 			ms_dlx_free(mdd);
1139 			return;
1140 		}
1141 
1142 		/* SearchAbEntryResult can contain duplicates */
1143 		found = g_hash_table_new_full(g_str_hash, g_str_equal,
1144 					      g_free, NULL);
1145 
1146 		for (/* initialized above */ ; node; node = sipe_xml_twin(node)) {
1147 			const sipe_xml *attrs;
1148 			gchar *sip_uri     = NULL;
1149 			gchar *displayname = NULL;
1150 			gchar *company     = NULL;
1151 			gchar *country     = NULL;
1152 			gchar *email       = NULL;
1153 
1154 			for (attrs = sipe_xml_child(node, "Attributes/Attribute");
1155 			     attrs;
1156 			     attrs = sipe_xml_twin(attrs)) {
1157 				gchar *name  = sipe_xml_data(sipe_xml_child(attrs,
1158 									    "Name"));
1159 				gchar *value = sipe_xml_data(sipe_xml_child(attrs,
1160 									    "Value"));
1161 
1162 				if (!is_empty(value)) {
1163 					if (sipe_strcase_equal(name, "msrtcsip-primaryuseraddress")) {
1164 						g_free(sip_uri);
1165 					        sip_uri = value;
1166 						value = NULL;
1167 					} else if (sipe_strcase_equal(name, "displayname")) {
1168 						g_free(displayname);
1169 						displayname = value;
1170 						value = NULL;
1171 					} else if (sipe_strcase_equal(name, "mail")) {
1172 						g_free(email);
1173 						email = value;
1174 						value = NULL;
1175 					} else if (sipe_strcase_equal(name, "company")) {
1176 						g_free(company);
1177 						company = value;
1178 						value = NULL;
1179 					} else if (sipe_strcase_equal(name, "country")) {
1180 						g_free(country);
1181 						country = value;
1182 						value = NULL;
1183 					}
1184 				}
1185 
1186 				g_free(value);
1187 				g_free(name);
1188 			}
1189 
1190 			if (sip_uri && !g_hash_table_lookup(found, sip_uri)) {
1191 				gchar **uri_parts = g_strsplit(sip_uri, ":", 2);
1192 				sipe_backend_search_results_add(SIPE_CORE_PUBLIC,
1193 								results,
1194 								uri_parts[1],
1195 								displayname,
1196 								company,
1197 								country,
1198 								email);
1199 				g_strfreev(uri_parts);
1200 
1201 				g_hash_table_insert(found, sip_uri, (gpointer) TRUE);
1202 				sip_uri = NULL;
1203 			}
1204 
1205 			g_free(email);
1206 			g_free(country);
1207 			g_free(company);
1208 			g_free(displayname);
1209 			g_free(sip_uri);
1210 		}
1211 
1212 		sipe_buddy_search_contacts_finalize(sipe_private, results,
1213 						    g_hash_table_size(found),
1214 						    FALSE);
1215 		g_hash_table_destroy(found);
1216 		ms_dlx_free(mdd);
1217 
1218 	} else {
1219 		mdd->failed_callback(sipe_private, mdd);
1220 	}
1221 }
1222 
process_search_contact_response(struct sipe_core_private * sipe_private,struct sipmsg * msg,struct transaction * trans)1223 static gboolean process_search_contact_response(struct sipe_core_private *sipe_private,
1224 						struct sipmsg *msg,
1225 						struct transaction *trans)
1226 {
1227 	struct sipe_backend_search_token *token = trans->payload->data;
1228 	struct sipe_backend_search_results *results;
1229 	sipe_xml *searchResults;
1230 	const sipe_xml *mrow;
1231 	guint match_count = 0;
1232 	gboolean more = FALSE;
1233 
1234 	/* valid response? */
1235 	if (msg->response != 200) {
1236 		SIPE_DEBUG_ERROR("process_search_contact_response: request failed (%d)",
1237 				 msg->response);
1238 		sipe_backend_search_failed(SIPE_CORE_PUBLIC,
1239 					   token,
1240 					   _("Contact search failed"));
1241 		return(FALSE);
1242 	}
1243 
1244 	SIPE_DEBUG_INFO("process_search_contact_response: body:\n%s", msg->body ? msg->body : "");
1245 
1246 	/* valid XML? */
1247 	searchResults = sipe_xml_parse(msg->body, msg->bodylen);
1248 	if (!searchResults) {
1249 		SIPE_DEBUG_INFO_NOFORMAT("process_search_contact_response: no parseable searchResults");
1250 		sipe_backend_search_failed(SIPE_CORE_PUBLIC,
1251 					   token,
1252 					   _("Contact search failed"));
1253 		return(FALSE);
1254 	}
1255 
1256 	/* any matches? */
1257 	mrow = sipe_xml_child(searchResults, "Body/Array/row");
1258 	if (!mrow) {
1259 		SIPE_DEBUG_ERROR_NOFORMAT("process_search_contact_response: no matches");
1260 		sipe_backend_search_failed(SIPE_CORE_PUBLIC,
1261 					   token,
1262 					   _("No contacts found"));
1263 
1264 		sipe_xml_free(searchResults);
1265 		return(FALSE);
1266 	}
1267 
1268 	/* OK, we found something - show the results to the user */
1269 	results = sipe_backend_search_results_start(SIPE_CORE_PUBLIC,
1270 						    trans->payload->data);
1271 	if (!results) {
1272 		SIPE_DEBUG_ERROR_NOFORMAT("process_search_contact_response: Unable to display the search results.");
1273 		sipe_backend_search_failed(SIPE_CORE_PUBLIC,
1274 					   token,
1275 					   _("Unable to display the search results"));
1276 
1277 		sipe_xml_free(searchResults);
1278 		return FALSE;
1279 	}
1280 
1281 	for (/* initialized above */ ; mrow; mrow = sipe_xml_twin(mrow)) {
1282 		gchar **uri_parts = g_strsplit(sipe_xml_attribute(mrow, "uri"), ":", 2);
1283 		sipe_backend_search_results_add(SIPE_CORE_PUBLIC,
1284 						results,
1285 						uri_parts[1],
1286 						sipe_xml_attribute(mrow, "displayName"),
1287 						sipe_xml_attribute(mrow, "company"),
1288 						sipe_xml_attribute(mrow, "country"),
1289 						sipe_xml_attribute(mrow, "email"));
1290 		g_strfreev(uri_parts);
1291 		match_count++;
1292 	}
1293 
1294 	if ((mrow = sipe_xml_child(searchResults, "Body/directorySearch/moreAvailable")) != NULL) {
1295 		char *data = sipe_xml_data(mrow);
1296 		more = (g_ascii_strcasecmp(data, "true") == 0);
1297 		g_free(data);
1298 	}
1299 
1300 	sipe_buddy_search_contacts_finalize(sipe_private, results, match_count, more);
1301 	sipe_xml_free(searchResults);
1302 
1303 	return(TRUE);
1304 }
1305 
search_soap_request(struct sipe_core_private * sipe_private,GDestroyNotify destroy,void * data,guint max,SoapTransCallback callback,GSList * search_rows)1306 static void search_soap_request(struct sipe_core_private *sipe_private,
1307 				GDestroyNotify destroy,
1308 				void *data,
1309 				guint max,
1310 				SoapTransCallback callback,
1311 				GSList *search_rows)
1312 {
1313 	gchar *query = prepare_buddy_search_query(search_rows, FALSE);
1314 	struct transaction_payload *payload = g_new0(struct transaction_payload, 1);
1315 
1316 	payload->destroy = destroy;
1317 	payload->data    = data;
1318 
1319 	sip_soap_directory_search(sipe_private,
1320 				  max,
1321 				  query,
1322 				  callback,
1323 				  payload);
1324 	g_free(query);
1325 }
1326 
search_ab_entry_failed(struct sipe_core_private * sipe_private,struct ms_dlx_data * mdd)1327 static void search_ab_entry_failed(struct sipe_core_private *sipe_private,
1328 				   struct ms_dlx_data *mdd)
1329 {
1330 	/* error using [MS-DLX] server, retry using Active Directory */
1331 	if (mdd->search_rows)
1332 		search_soap_request(sipe_private,
1333 				    NULL,
1334 				    mdd->token,
1335 				    100,
1336 				    process_search_contact_response,
1337 				    mdd->search_rows);
1338 	ms_dlx_free(mdd);
1339 }
1340 
sipe_core_buddy_search(struct sipe_core_public * sipe_public,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)1341 void sipe_core_buddy_search(struct sipe_core_public *sipe_public,
1342 			    struct sipe_backend_search_token *token,
1343 			    const gchar *given_name,
1344 			    const gchar *surname,
1345 			    const gchar *email,
1346 			    const gchar *sipid,
1347 			    const gchar *company,
1348 			    const gchar *country)
1349 {
1350 	struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
1351 
1352 	/* Lync 2013 or newer: use UCS if contacts are migrated */
1353 	if (SIPE_CORE_PRIVATE_FLAG_IS(LYNC2013) &&
1354 	    sipe_ucs_is_migrated(sipe_private)) {
1355 
1356 		sipe_ucs_search(sipe_private,
1357 				token,
1358 				given_name,
1359 				surname,
1360 				email,
1361 				sipid,
1362 				company,
1363 				country);
1364 
1365 	} else {
1366 		GSList *query_rows  = NULL;
1367 		guint count         = 0;
1368 		const gchar *simple = NULL;
1369 
1370 #define ADD_QUERY_ROW(attr, val)                                                 \
1371 		if (val) {                                                       \
1372 			query_rows = g_slist_append(query_rows, g_strdup(attr)); \
1373 			query_rows = g_slist_append(query_rows, g_strdup(val));  \
1374 			simple = val;                                            \
1375 			count++;                                                 \
1376 		}
1377 
1378 		ADD_QUERY_ROW("givenName", given_name);
1379 		ADD_QUERY_ROW("sn",        surname);
1380 		ADD_QUERY_ROW("mail",      email);
1381 		/* prepare_buddy_search_query() interprets NULL as SIP ID */
1382 		ADD_QUERY_ROW(NULL,        sipid);
1383 		ADD_QUERY_ROW("company",   company);
1384 		ADD_QUERY_ROW("c",         country);
1385 
1386 		if (query_rows) {
1387 			if (sipe_private->dlx_uri != NULL) {
1388 				struct ms_dlx_data *mdd = g_new0(struct ms_dlx_data, 1);
1389 
1390 				mdd->search_rows     = query_rows;
1391 				/* user entered only one search string, remember that one */
1392 				if (count == 1)
1393 					mdd->other   = g_strdup(simple);
1394 				mdd->max_returns     = 100;
1395 				mdd->callback        = search_ab_entry_response;
1396 				mdd->failed_callback = search_ab_entry_failed;
1397 				mdd->session         = sipe_svc_session_start();
1398 				mdd->token           = token;
1399 
1400 				ms_dlx_webticket_request(sipe_private, mdd);
1401 
1402 			} else {
1403 				/* no [MS-DLX] server, use Active Directory search instead */
1404 				search_soap_request(sipe_private,
1405 						    NULL,
1406 						    token,
1407 						    100,
1408 						    process_search_contact_response,
1409 						    query_rows);
1410 				free_search_rows(query_rows);
1411 			}
1412 		} else
1413 			sipe_backend_search_failed(sipe_public,
1414 						   token,
1415 						   _("Invalid contact search query"));
1416 	}
1417 }
1418 
get_info_finalize(struct sipe_core_private * sipe_private,struct sipe_backend_buddy_info * info,const gchar * uri,const gchar * server_alias,const gchar * email)1419 static void get_info_finalize(struct sipe_core_private *sipe_private,
1420 			      struct sipe_backend_buddy_info *info,
1421 			      const gchar *uri,
1422 			      const gchar *server_alias,
1423 			      const gchar *email)
1424 {
1425 	sipe_backend_buddy bbuddy;
1426 	struct sipe_buddy *sbuddy;
1427 	gchar *alias;
1428 	gchar *value;
1429 
1430 	if (!info) {
1431 		info = sipe_backend_buddy_info_start(SIPE_CORE_PUBLIC);
1432 	} else {
1433 		sipe_backend_buddy_info_break(SIPE_CORE_PUBLIC, info);
1434 	}
1435 	if (!info)
1436 		return;
1437 
1438 	bbuddy = sipe_backend_buddy_find(SIPE_CORE_PUBLIC, uri, NULL);
1439 
1440 	if (is_empty(server_alias)) {
1441 		value = sipe_backend_buddy_get_server_alias(SIPE_CORE_PUBLIC,
1442 							    bbuddy);
1443 		if (value) {
1444 			sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC,
1445 						    info,
1446 						    SIPE_BUDDY_INFO_DISPLAY_NAME,
1447 						    value);
1448 		}
1449 	} else {
1450 		value = g_strdup(server_alias);
1451 	}
1452 
1453 	/* present alias if it differs from server alias */
1454 	alias = sipe_backend_buddy_get_local_alias(SIPE_CORE_PUBLIC, bbuddy);
1455 	if (alias && !sipe_strequal(alias, value))
1456 	{
1457 		sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC,
1458 					     info,
1459 					     SIPE_BUDDY_INFO_ALIAS,
1460 					     alias);
1461 	}
1462 	g_free(alias);
1463 	g_free(value);
1464 
1465 	if (is_empty(email)) {
1466 		value = sipe_backend_buddy_get_string(SIPE_CORE_PUBLIC,
1467 						      bbuddy,
1468 						      SIPE_BUDDY_INFO_EMAIL);
1469 		if (value) {
1470 			sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC,
1471 						    info,
1472 						    SIPE_BUDDY_INFO_EMAIL,
1473 						    value);
1474 			g_free(value);
1475 		}
1476 	}
1477 
1478 	value = sipe_backend_buddy_get_string(SIPE_CORE_PUBLIC,
1479 					      bbuddy,
1480 					      SIPE_BUDDY_INFO_SITE);
1481 	if (value) {
1482 		sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC,
1483 					    info,
1484 					    SIPE_BUDDY_INFO_SITE,
1485 					    value);
1486 		g_free(value);
1487 	}
1488 
1489 	sbuddy = sipe_buddy_find_by_uri(sipe_private, uri);
1490 	if (sbuddy && sbuddy->device_name) {
1491 		sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC,
1492 					    info,
1493 					    SIPE_BUDDY_INFO_DEVICE,
1494 					    sbuddy->device_name);
1495 	}
1496 
1497 	sipe_backend_buddy_info_finalize(SIPE_CORE_PUBLIC, info, uri);
1498 }
1499 
1500 
get_info_ab_entry_response(struct sipe_core_private * sipe_private,const gchar * uri,SIPE_UNUSED_PARAMETER const gchar * raw,sipe_xml * soap_body,gpointer callback_data)1501 static void get_info_ab_entry_response(struct sipe_core_private *sipe_private,
1502 				       const gchar *uri,
1503 				       SIPE_UNUSED_PARAMETER const gchar *raw,
1504 				       sipe_xml *soap_body,
1505 				       gpointer callback_data)
1506 {
1507 	struct ms_dlx_data *mdd = callback_data;
1508 	struct sipe_backend_buddy_info *info = NULL;
1509 	gchar *server_alias = NULL;
1510 	gchar *email        = NULL;
1511 
1512 	if (soap_body) {
1513 		const sipe_xml *node;
1514 
1515 		SIPE_DEBUG_INFO("get_info_ab_entry_response: received valid SOAP message from service %s",
1516 				uri);
1517 
1518 		info = sipe_backend_buddy_info_start(SIPE_CORE_PUBLIC);
1519 
1520 		for (node = sipe_xml_child(soap_body, "Body/SearchAbEntryResponse/SearchAbEntryResult/Items/AbEntry/Attributes/Attribute");
1521 		     node;
1522 		     node = sipe_xml_twin(node)) {
1523 			gchar *name  = sipe_xml_data(sipe_xml_child(node,
1524 								    "Name"));
1525 			gchar *value = sipe_xml_data(sipe_xml_child(node,
1526 								    "Value"));
1527 			const sipe_xml *values = sipe_xml_child(node,
1528 								"Values");
1529 
1530 			/* Single value entries */
1531 			if (!is_empty(value)) {
1532 
1533 				if (sipe_strcase_equal(name, "displayname")) {
1534 					g_free(server_alias);
1535 					server_alias = value;
1536 					value = NULL;
1537 					sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC,
1538 								    info,
1539 								    SIPE_BUDDY_INFO_DISPLAY_NAME,
1540 								    server_alias);
1541 				} else if (sipe_strcase_equal(name, "mail")) {
1542 					g_free(email);
1543 					email = value;
1544 					value = NULL;
1545 					sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC,
1546 								    info,
1547 								    SIPE_BUDDY_INFO_EMAIL,
1548 								    email);
1549 				} else if (sipe_strcase_equal(name, "title")) {
1550 					sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC,
1551 								    info,
1552 								    SIPE_BUDDY_INFO_JOB_TITLE,
1553 								    value);
1554 				} else if (sipe_strcase_equal(name, "company")) {
1555 					sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC,
1556 								    info,
1557 								    SIPE_BUDDY_INFO_COMPANY,
1558 								    value);
1559 				} else if (sipe_strcase_equal(name, "country")) {
1560 					sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC,
1561 								    info,
1562 								    SIPE_BUDDY_INFO_COUNTRY,
1563 								    value);
1564 				}
1565 
1566 			} else if (values) {
1567 				gchar *first = sipe_xml_data(sipe_xml_child(values,
1568 									    "string"));
1569 
1570 				if (sipe_strcase_equal(name, "telephonenumber")) {
1571 					sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC,
1572 								    info,
1573 								    SIPE_BUDDY_INFO_WORK_PHONE,
1574 								    first);
1575 				}
1576 
1577 				g_free(first);
1578 			}
1579 
1580 			g_free(value);
1581 			g_free(name);
1582 		}
1583 	}
1584 
1585 	/* this will show the minmum information */
1586 	get_info_finalize(sipe_private,
1587 			  info,
1588 			  mdd->other,
1589 			  server_alias,
1590 			  email);
1591 
1592 	g_free(email);
1593 	g_free(server_alias);
1594 	ms_dlx_free(mdd);
1595 }
1596 
process_get_info_response(struct sipe_core_private * sipe_private,struct sipmsg * msg,struct transaction * trans)1597 static gboolean process_get_info_response(struct sipe_core_private *sipe_private,
1598 					  struct sipmsg *msg,
1599 					  struct transaction *trans)
1600 {
1601 	const gchar *uri = trans->payload->data;
1602 	struct sipe_backend_buddy_info *info = NULL;
1603 	gchar *server_alias = NULL;
1604 	gchar *email        = NULL;
1605 
1606 	SIPE_DEBUG_INFO("Fetching %s's user info for %s",
1607 			uri, sipe_private->username);
1608 
1609 	if (msg->response != 200) {
1610 		SIPE_DEBUG_INFO("process_get_info_response: SERVICE response is %d", msg->response);
1611 	} else {
1612 		sipe_xml *searchResults;
1613 		const sipe_xml *mrow;
1614 
1615 		SIPE_DEBUG_INFO("process_get_info_response: body:\n%s",
1616 				msg->body ? msg->body : "");
1617 
1618 		searchResults = sipe_xml_parse(msg->body, msg->bodylen);
1619 		if (!searchResults) {
1620 
1621 			SIPE_DEBUG_INFO_NOFORMAT("process_get_info_response: no parseable searchResults");
1622 
1623 		} else if ((mrow = sipe_xml_child(searchResults, "Body/Array/row"))) {
1624 			const gchar *value;
1625 			gchar *phone_number;
1626 
1627 			info = sipe_backend_buddy_info_start(SIPE_CORE_PUBLIC);
1628 
1629 			server_alias = g_strdup(sipe_xml_attribute(mrow, "displayName"));
1630 			email = g_strdup(sipe_xml_attribute(mrow, "email"));
1631 			phone_number = g_strdup(sipe_xml_attribute(mrow, "phone"));
1632 
1633 			/*
1634 			 * For 2007 system we will take this from ContactCard -
1635 			 * it has cleaner tel: URIs at least
1636 			 */
1637 			if (!SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
1638 				char *tel_uri = sip_to_tel_uri(phone_number);
1639 				/* trims its parameters, so call first */
1640 				sipe_buddy_update_property(sipe_private, uri, SIPE_BUDDY_INFO_DISPLAY_NAME, server_alias);
1641 				sipe_buddy_update_property(sipe_private, uri, SIPE_BUDDY_INFO_EMAIL, email);
1642 				sipe_buddy_update_property(sipe_private, uri, SIPE_BUDDY_INFO_WORK_PHONE, tel_uri);
1643 				sipe_buddy_update_property(sipe_private, uri, SIPE_BUDDY_INFO_WORK_PHONE_DISPLAY, phone_number);
1644 				g_free(tel_uri);
1645 
1646 				sipe_backend_buddy_refresh_properties(SIPE_CORE_PUBLIC,
1647 								      uri);
1648 			}
1649 
1650 			if (!is_empty(server_alias)) {
1651 				sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC,
1652 							     info,
1653 							     SIPE_BUDDY_INFO_DISPLAY_NAME,
1654 							     server_alias);
1655 			}
1656 			if ((value = sipe_xml_attribute(mrow, "title")) && strlen(value) > 0) {
1657 				sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC,
1658 							     info,
1659 							     SIPE_BUDDY_INFO_JOB_TITLE,
1660 							     value);
1661 			}
1662 			if ((value = sipe_xml_attribute(mrow, "office")) && strlen(value) > 0) {
1663 				sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC,
1664 							     info,
1665 							     SIPE_BUDDY_INFO_OFFICE,
1666 							     value);
1667 			}
1668 			if (!is_empty(phone_number)) {
1669 				sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC,
1670 							     info,
1671 							     SIPE_BUDDY_INFO_WORK_PHONE,
1672 							     phone_number);
1673 			}
1674 			g_free(phone_number);
1675 			if ((value = sipe_xml_attribute(mrow, "company")) && strlen(value) > 0) {
1676 				sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC,
1677 							     info,
1678 							     SIPE_BUDDY_INFO_COMPANY,
1679 							     value);
1680 			}
1681 			if ((value = sipe_xml_attribute(mrow, "city")) && strlen(value) > 0) {
1682 				sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC,
1683 							     info,
1684 							     SIPE_BUDDY_INFO_CITY,
1685 							     value);
1686 			}
1687 			if ((value = sipe_xml_attribute(mrow, "state")) && strlen(value) > 0) {
1688 				sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC,
1689 							     info,
1690 							     SIPE_BUDDY_INFO_STATE,
1691 							     value);
1692 			}
1693 			if ((value = sipe_xml_attribute(mrow, "country")) && strlen(value) > 0) {
1694 				sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC,
1695 							     info,
1696 							     SIPE_BUDDY_INFO_COUNTRY,
1697 							     value);
1698 			}
1699 			if (!is_empty(email)) {
1700 				sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC,
1701 							     info,
1702 							     SIPE_BUDDY_INFO_EMAIL,
1703 							     email);
1704 			}
1705 		}
1706 		sipe_xml_free(searchResults);
1707 	}
1708 
1709 	/* this will show the minmum information */
1710 	get_info_finalize(sipe_private,
1711 			  info,
1712 			  uri,
1713 			  server_alias,
1714 			  email);
1715 
1716 	g_free(server_alias);
1717 	g_free(email);
1718 
1719 	return TRUE;
1720 }
1721 
get_info_ab_entry_failed(struct sipe_core_private * sipe_private,struct ms_dlx_data * mdd)1722 static void get_info_ab_entry_failed(struct sipe_core_private *sipe_private,
1723 				     struct ms_dlx_data *mdd)
1724 {
1725 	/* error using [MS-DLX] server, retry using Active Directory */
1726 	search_soap_request(sipe_private,
1727 			    g_free,
1728 			    mdd->other,
1729 			    1,
1730 			    process_get_info_response,
1731 			    mdd->search_rows);
1732 	mdd->other = NULL;
1733 	ms_dlx_free(mdd);
1734 }
1735 
search_rows_for_uri(const gchar * uri)1736 static GSList *search_rows_for_uri(const gchar *uri)
1737 {
1738 	/* prepare_buddy_search_query() interprets NULL as SIP ID */
1739 	GSList *l = g_slist_append(NULL, NULL);
1740 	return(g_slist_append(l, g_strdup(uri)));
1741 }
1742 
sipe_core_buddy_get_info(struct sipe_core_public * sipe_public,const gchar * who)1743 void sipe_core_buddy_get_info(struct sipe_core_public *sipe_public,
1744 			      const gchar *who)
1745 {
1746 	struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
1747 	GSList *search_rows = search_rows_for_uri(who);
1748 
1749 	if (sipe_private->dlx_uri) {
1750 		struct ms_dlx_data *mdd = g_new0(struct ms_dlx_data, 1);
1751 
1752 		mdd->search_rows     = search_rows;
1753 		mdd->other           = g_strdup(who);
1754 		mdd->max_returns     = 1;
1755 		mdd->callback        = get_info_ab_entry_response;
1756 		mdd->failed_callback = get_info_ab_entry_failed;
1757 		mdd->session         = sipe_svc_session_start();
1758 
1759 		ms_dlx_webticket_request(sipe_private, mdd);
1760 
1761 	} else {
1762 		/* no [MS-DLX] server, use Active Directory search instead */
1763 		search_soap_request(sipe_private,
1764 				    g_free,
1765 				    g_strdup(who),
1766 				    1,
1767 				    process_get_info_response,
1768 				    search_rows);
1769 		free_search_rows(search_rows);
1770 	}
1771 }
1772 
photo_response_data_free(struct photo_response_data * data)1773 static void photo_response_data_free(struct photo_response_data *data)
1774 {
1775 	g_free(data->who);
1776 	g_free(data->photo_hash);
1777 	if (data->request) {
1778 		sipe_http_request_cancel(data->request);
1779 	}
1780 	g_free(data);
1781 }
1782 
photo_response_data_remove(struct sipe_core_private * sipe_private,struct photo_response_data * data)1783 static void photo_response_data_remove(struct sipe_core_private *sipe_private,
1784 				       struct photo_response_data *data)
1785 {
1786 	data->request = NULL;
1787 	sipe_private->buddies->pending_photo_requests =
1788 		g_slist_remove(sipe_private->buddies->pending_photo_requests, data);
1789 	photo_response_data_free(data);
1790 }
1791 
process_buddy_photo_response(struct sipe_core_private * sipe_private,guint status,GSList * headers,const char * body,gpointer data)1792 static void process_buddy_photo_response(struct sipe_core_private *sipe_private,
1793 					 guint status,
1794 					 GSList *headers,
1795 					 const char *body,
1796 					 gpointer data)
1797 {
1798 	struct photo_response_data *rdata = (struct photo_response_data *) data;
1799 
1800 	if (status == SIPE_HTTP_STATUS_OK) {
1801 		const gchar *len_str = sipe_utils_nameval_find(headers,
1802 							       "Content-Length");
1803 		if (len_str) {
1804 			gsize photo_size = atoi(len_str);
1805 			gpointer photo = g_new(char, photo_size);
1806 
1807 			if (photo) {
1808 				memcpy(photo, body, photo_size);
1809 
1810 				sipe_backend_buddy_set_photo(SIPE_CORE_PUBLIC,
1811 							     rdata->who,
1812 							     photo,
1813 							     photo_size,
1814 							     rdata->photo_hash);
1815 			}
1816 		}
1817 	}
1818 
1819 	photo_response_data_remove(sipe_private, rdata);
1820 }
1821 
process_get_user_photo_response(struct sipe_core_private * sipe_private,guint status,SIPE_UNUSED_PARAMETER GSList * headers,const gchar * body,gpointer data)1822 static void process_get_user_photo_response(struct sipe_core_private *sipe_private,
1823 					    guint status,
1824 					    SIPE_UNUSED_PARAMETER GSList *headers,
1825 					    const gchar *body,
1826 					    gpointer data)
1827 {
1828 	struct photo_response_data *rdata = (struct photo_response_data *) data;
1829 
1830 	if ((status == SIPE_HTTP_STATUS_OK) && body) {
1831 		sipe_xml *xml = sipe_xml_parse(body, strlen(body));
1832 		const sipe_xml *node = sipe_xml_child(xml,
1833 						      "Body/GetUserPhotoResponse/PictureData");
1834 
1835 		if (node) {
1836 			gchar *base64;
1837 			gsize photo_size;
1838 			guchar *photo;
1839 
1840 			/* decode photo data */
1841 			base64 = sipe_xml_data(node);
1842 			photo = g_base64_decode(base64, &photo_size);
1843 			g_free(base64);
1844 
1845 			/* EWS doesn't provide a hash -> calculate SHA-1 digest */
1846 			if (!rdata->photo_hash) {
1847 				guchar digest[SIPE_DIGEST_SHA1_LENGTH];
1848 				sipe_digest_sha1(photo, photo_size, digest);
1849 
1850 				/* rdata takes ownership of digest string */
1851 				rdata->photo_hash = buff_to_hex_str(digest,
1852 								    SIPE_DIGEST_SHA1_LENGTH);
1853 			}
1854 
1855 			/* backend frees "photo" */
1856 			sipe_backend_buddy_set_photo(SIPE_CORE_PUBLIC,
1857 						     rdata->who,
1858 						     photo,
1859 						     photo_size,
1860 						     rdata->photo_hash);
1861 		}
1862 
1863 		sipe_xml_free(xml);
1864 	}
1865 
1866 	photo_response_data_remove(sipe_private, rdata);
1867 }
1868 
create_x_ms_webticket_header(const gchar * wsse_security)1869 static gchar *create_x_ms_webticket_header(const gchar *wsse_security)
1870 {
1871 	gchar *assertion = sipe_xml_extract_raw(wsse_security, "Assertion", TRUE);
1872 	gchar *wsse_security_base64;
1873 	gchar *x_ms_webticket_header;
1874 
1875 	if (!assertion) {
1876 		return NULL;
1877 	}
1878 
1879 	wsse_security_base64 = g_base64_encode((const guchar *)assertion,
1880 			strlen(assertion));
1881 	x_ms_webticket_header = g_strdup_printf("X-MS-WebTicket: opaque=%s\r\n",
1882 			wsse_security_base64);
1883 
1884 	g_free(assertion);
1885 	g_free(wsse_security_base64);
1886 
1887 	return x_ms_webticket_header;
1888 }
1889 
1890 /* see also sipe_ucs_http_request() */
get_user_photo_request(struct sipe_core_private * sipe_private,struct photo_response_data * data,const gchar * ews_url,const gchar * email)1891 static struct sipe_http_request *get_user_photo_request(struct sipe_core_private *sipe_private,
1892 							struct photo_response_data *data,
1893 							const gchar *ews_url,
1894 							const gchar *email)
1895 {
1896 	gchar *soap = g_strdup_printf("<?xml version=\"1.0\"?>\r\n"
1897 				      "<soap:Envelope"
1898 				      " xmlns:m=\"http://schemas.microsoft.com/exchange/services/2006/messages\""
1899 				      " xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\""
1900 				      " xmlns:t=\"http://schemas.microsoft.com/exchange/services/2006/types\""
1901 				      " >"
1902 				      " <soap:Header>"
1903 				      "  <t:RequestServerVersion Version=\"Exchange2013\" />"
1904 				      " </soap:Header>"
1905 				      " <soap:Body>"
1906 				      "  <m:GetUserPhoto>"
1907 				      "   <m:Email>%s</m:Email>"
1908 				      "   <m:SizeRequested>HR48x48</m:SizeRequested>"
1909 				      "  </m:GetUserPhoto>"
1910 				      " </soap:Body>"
1911 				      "</soap:Envelope>",
1912 				      email);
1913 	struct sipe_http_request *request = sipe_http_request_post(sipe_private,
1914 								   ews_url,
1915 								   NULL,
1916 								   soap,
1917 								   "text/xml; charset=UTF-8",
1918 								   process_get_user_photo_response,
1919 								   data);
1920 	g_free(soap);
1921 
1922 	if (request) {
1923 		sipe_core_email_authentication(sipe_private,
1924 					       request);
1925 		sipe_http_request_allow_redirect(request);
1926 	} else {
1927 		SIPE_DEBUG_ERROR_NOFORMAT("get_user_photo_request: failed to create HTTP connection");
1928 	}
1929 
1930 	return(request);
1931 }
1932 
photo_response_data_finalize(struct sipe_core_private * sipe_private,struct photo_response_data * data,const gchar * uri,const gchar * photo_hash)1933 static void photo_response_data_finalize(struct sipe_core_private *sipe_private,
1934 					 struct photo_response_data *data,
1935 					 const gchar *uri,
1936 					 const gchar *photo_hash)
1937 {
1938 	if (data->request) {
1939 		data->who        = g_strdup(uri);
1940 		data->photo_hash = g_strdup(photo_hash);
1941 
1942 		sipe_private->buddies->pending_photo_requests =
1943 			g_slist_append(sipe_private->buddies->pending_photo_requests, data);
1944 		sipe_http_request_ready(data->request);
1945 	} else {
1946 		photo_response_data_free(data);
1947 	}
1948 }
1949 
sipe_buddy_update_photo(struct sipe_core_private * sipe_private,const gchar * uri,const gchar * photo_hash,const gchar * photo_url,const gchar * headers)1950 void sipe_buddy_update_photo(struct sipe_core_private *sipe_private,
1951 			     const gchar *uri,
1952 			     const gchar *photo_hash,
1953 			     const gchar *photo_url,
1954 			     const gchar *headers)
1955 {
1956 	const gchar *photo_hash_old =
1957 		sipe_backend_buddy_get_photo_hash(SIPE_CORE_PUBLIC, uri);
1958 
1959 	if (!sipe_strequal(photo_hash, photo_hash_old)) {
1960 		struct photo_response_data *data = g_new0(struct photo_response_data, 1);
1961 
1962 		SIPE_DEBUG_INFO("sipe_buddy_update_photo: who '%s' url '%s' hash '%s'",
1963 				uri, photo_url, photo_hash);
1964 
1965 		/* Photo URL is embedded XML? */
1966 		if (g_str_has_prefix(photo_url, "<") &&
1967 		    g_str_has_suffix(photo_url, ">")) {
1968 			/* add dummy root to embedded XML string */
1969 			gchar *tmp = g_strdup_printf("<r>%s</r>", photo_url);
1970 			sipe_xml *xml = sipe_xml_parse(tmp, strlen(tmp));
1971 			g_free(tmp);
1972 
1973 			if (xml) {
1974 				gchar *ews_url = sipe_xml_data(sipe_xml_child(xml, "ewsUrl"));
1975 				gchar *email = sipe_xml_data(sipe_xml_child(xml, "primarySMTP"));
1976 
1977 				if (!is_empty(ews_url) && !is_empty(email)) {
1978 					/*
1979 					 * Workaround for missing Office 365 buddy icons
1980 					 *
1981 					 * (All?) Office 365 contact cards have the following
1982 					 * XML embedded as the photo URI XML node text:
1983 					 *
1984 					 *    <ewsUrl>https://outlook.office365.com/EWS/Exchange.asmx/WSSecurity</ewsUrl>
1985 					 *    <primarySMTP>user@company.com</primarySMTP>
1986 					 *
1987 					 * The simple HTTP request by get_user_photo_request()
1988 					 * is rejected with 401. But the response contains
1989 					 *
1990 					 *    WWW-Authenticate: Basic Realm=""
1991 					 *
1992 					 * to which the HTTP transport answers with a retry
1993 					 * using Basic authentication. That in turn is rejected
1994 					 * with 500 and thus the buddy icon retrieval fails.
1995 					 *
1996 					 * As a quick workaround strip the trailing "/WSSecurity"
1997 					 * from the URL. The HTTP request for the buddy icon
1998 					 * retrieval will work with this stripped URL.
1999 					 *
2000 					 * @TODO: this is probably not the correct approach.
2001 					 *        get_user_photo_request() should be updated
2002 					 *        to support also a webticket request.
2003 					 */
2004 					gchar *tmp = g_strrstr(ews_url, "/WSSecurity");
2005 					if (tmp)
2006 						*tmp = '\0';
2007 
2008 					data->request = get_user_photo_request(sipe_private,
2009 									       data,
2010 									       ews_url,
2011 									       email);
2012 				}
2013 
2014 				g_free(email);
2015 				g_free(ews_url);
2016 				sipe_xml_free(xml);
2017 			}
2018 		} else {
2019 			data->request = sipe_http_request_get(sipe_private,
2020 							      photo_url,
2021 							      headers,
2022 							      process_buddy_photo_response,
2023 							      data);
2024 		}
2025 
2026 		photo_response_data_finalize(sipe_private,
2027 					     data,
2028 					     uri,
2029 					     photo_hash);
2030 	}
2031 }
2032 
get_photo_ab_entry_response(struct sipe_core_private * sipe_private,const gchar * uri,SIPE_UNUSED_PARAMETER const gchar * raw,sipe_xml * soap_body,gpointer callback_data)2033 static void get_photo_ab_entry_response(struct sipe_core_private *sipe_private,
2034 					const gchar *uri,
2035 					SIPE_UNUSED_PARAMETER const gchar *raw,
2036 					sipe_xml *soap_body,
2037 					gpointer callback_data)
2038 {
2039 	struct ms_dlx_data *mdd = callback_data;
2040 	gchar *photo_rel_path = NULL;
2041 	gchar *photo_hash = NULL;
2042 
2043 	if (soap_body) {
2044 		const sipe_xml *node;
2045 
2046 		SIPE_DEBUG_INFO("get_photo_ab_entry_response: received valid SOAP message from service %s",
2047 				uri);
2048 
2049 		for (node = sipe_xml_child(soap_body, "Body/SearchAbEntryResponse/SearchAbEntryResult/Items/AbEntry/Attributes/Attribute");
2050 		     node;
2051 		     node = sipe_xml_twin(node)) {
2052 			gchar *name  = sipe_xml_data(sipe_xml_child(node, "Name"));
2053 			gchar *value = sipe_xml_data(sipe_xml_child(node, "Value"));
2054 
2055 			if (!is_empty(value)) {
2056 				if (sipe_strcase_equal(name, "PhotoRelPath")) {
2057 					g_free(photo_rel_path);
2058 					photo_rel_path = value;
2059 					value = NULL;
2060 				} else if (sipe_strcase_equal(name, "PhotoHash")) {
2061 					g_free(photo_hash);
2062 					photo_hash = value;
2063 					value = NULL;
2064 				}
2065 			}
2066 
2067 			g_free(value);
2068 			g_free(name);
2069 		}
2070 	}
2071 
2072 	if (sipe_private->addressbook_uri && photo_rel_path && photo_hash) {
2073 		gchar *photo_url = g_strdup_printf("%s/%s",
2074 				sipe_private->addressbook_uri, photo_rel_path);
2075 		gchar *x_ms_webticket_header = create_x_ms_webticket_header(mdd->wsse_security);
2076 
2077 		sipe_buddy_update_photo(sipe_private,
2078 					mdd->other,
2079 					photo_hash,
2080 					photo_url,
2081 					x_ms_webticket_header);
2082 
2083 		g_free(x_ms_webticket_header);
2084 		g_free(photo_url);
2085 	}
2086 
2087 	g_free(photo_rel_path);
2088 	g_free(photo_hash);
2089 	ms_dlx_free(mdd);
2090 }
2091 
get_photo_ab_entry_failed(SIPE_UNUSED_PARAMETER struct sipe_core_private * sipe_private,struct ms_dlx_data * mdd)2092 static void get_photo_ab_entry_failed(SIPE_UNUSED_PARAMETER struct sipe_core_private *sipe_private,
2093 				      struct ms_dlx_data *mdd)
2094 {
2095 	ms_dlx_free(mdd);
2096 }
2097 
buddy_fetch_photo(struct sipe_core_private * sipe_private,const gchar * uri)2098 static void buddy_fetch_photo(struct sipe_core_private *sipe_private,
2099 			      const gchar *uri)
2100 {
2101         if (sipe_backend_uses_photo()) {
2102 
2103 		/* Lync 2013 or newer: use UCS if contacts are migrated */
2104 		if (SIPE_CORE_PRIVATE_FLAG_IS(LYNC2013) &&
2105 		    sipe_ucs_is_migrated(sipe_private)) {
2106 			struct photo_response_data *data = g_new0(struct photo_response_data, 1);
2107 
2108 			data->request = get_user_photo_request(sipe_private,
2109 							       data,
2110 							       sipe_ucs_ews_url(sipe_private),
2111 							       sipe_get_no_sip_uri(uri));
2112 			photo_response_data_finalize(sipe_private,
2113 						     data,
2114 						     uri,
2115 						     /* there is no hash */
2116 						     NULL);
2117 
2118 		/* Lync 2010: use [MS-DLX] */
2119 		} else if (sipe_private->dlx_uri         &&
2120 			   sipe_private->addressbook_uri) {
2121 			struct ms_dlx_data *mdd = g_new0(struct ms_dlx_data, 1);
2122 
2123 			mdd->search_rows     = search_rows_for_uri(uri);
2124 			mdd->other           = g_strdup(uri);
2125 			mdd->max_returns     = 1;
2126 			mdd->callback        = get_photo_ab_entry_response;
2127 			mdd->failed_callback = get_photo_ab_entry_failed;
2128 			mdd->session         = sipe_svc_session_start();
2129 
2130 			ms_dlx_webticket_request(sipe_private, mdd);
2131 		}
2132 	}
2133 }
2134 
buddy_refresh_photos_cb(gpointer uri,SIPE_UNUSED_PARAMETER gpointer value,gpointer sipe_private)2135 static void buddy_refresh_photos_cb(gpointer uri,
2136 				    SIPE_UNUSED_PARAMETER gpointer value,
2137 				    gpointer sipe_private)
2138 {
2139 	buddy_fetch_photo(sipe_private, uri);
2140 }
2141 
sipe_buddy_refresh_photos(struct sipe_core_private * sipe_private)2142 void sipe_buddy_refresh_photos(struct sipe_core_private *sipe_private)
2143 {
2144 	g_hash_table_foreach(sipe_private->buddies->uri,
2145 			     buddy_refresh_photos_cb,
2146 			     sipe_private);
2147 }
2148 
2149 /* Buddy menu callbacks*/
2150 
sipe_core_buddy_new_chat(struct sipe_core_public * sipe_public,const gchar * who)2151 void sipe_core_buddy_new_chat(struct sipe_core_public *sipe_public,
2152 			      const gchar *who)
2153 {
2154 	struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
2155 
2156 	/* 2007+ conference */
2157 	if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
2158 		sipe_conf_add(sipe_private, who);
2159 
2160 	/* 2005- multiparty chat */
2161 	} else {
2162 		gchar *self = sip_uri_self(sipe_private);
2163 		struct sip_session *session;
2164 
2165 		session = sipe_session_add_chat(sipe_private,
2166 						NULL,
2167 						TRUE,
2168 						self);
2169 		session->chat_session->backend = sipe_backend_chat_create(SIPE_CORE_PUBLIC,
2170 									  session->chat_session,
2171 									  session->chat_session->title,
2172 									  self);
2173 		g_free(self);
2174 
2175 		sipe_im_invite(sipe_private, session, who,
2176 			       NULL, NULL, NULL, FALSE);
2177 	}
2178 }
2179 
sipe_core_buddy_send_email(struct sipe_core_public * sipe_public,const gchar * who)2180 void sipe_core_buddy_send_email(struct sipe_core_public *sipe_public,
2181 				const gchar *who)
2182 {
2183 	sipe_backend_buddy buddy = sipe_backend_buddy_find(sipe_public,
2184 							   who,
2185 							   NULL);
2186 	gchar *email = sipe_backend_buddy_get_string(sipe_public,
2187 						     buddy,
2188 						     SIPE_BUDDY_INFO_EMAIL);
2189 
2190 	if (email) {
2191 		gchar *command_line = g_strdup_printf(
2192 #ifdef _WIN32
2193 			"cmd /c start"
2194 #else
2195 			"xdg-email"
2196 #endif
2197 			" mailto:%s", email);
2198 		g_free(email);
2199 
2200 		SIPE_DEBUG_INFO("sipe_core_buddy_send_email: going to call email client: %s",
2201 				command_line);
2202 		g_spawn_command_line_async(command_line, NULL);
2203 		g_free(command_line);
2204 
2205 	} else {
2206 		SIPE_DEBUG_INFO("sipe_core_buddy_send_email: no email address stored for buddy=%s",
2207 				who);
2208 	}
2209 }
2210 
2211 /* Buddy menu */
2212 
buddy_menu_phone(struct sipe_core_public * sipe_public,struct sipe_backend_buddy_menu * menu,sipe_backend_buddy buddy,sipe_buddy_info_fields id_phone,sipe_buddy_info_fields id_display,const gchar * type)2213 static struct sipe_backend_buddy_menu *buddy_menu_phone(struct sipe_core_public *sipe_public,
2214 							struct sipe_backend_buddy_menu *menu,
2215 							sipe_backend_buddy buddy,
2216 							sipe_buddy_info_fields id_phone,
2217 							sipe_buddy_info_fields id_display,
2218 							const gchar *type)
2219 {
2220 	gchar *phone = sipe_backend_buddy_get_string(sipe_public,
2221 						     buddy,
2222 						     id_phone);
2223 	if (phone) {
2224 		gchar *display = sipe_backend_buddy_get_string(sipe_public,
2225 							       buddy,
2226 							       id_display);
2227 		gchar *tmp   = NULL;
2228 		gchar *label = g_strdup_printf("%s %s",
2229 					       type,
2230 					       display ? display :
2231 					       (tmp = sip_tel_uri_denormalize(phone)));
2232 		menu = sipe_backend_buddy_menu_add(sipe_public,
2233 						   menu,
2234 						   label,
2235 						   SIPE_BUDDY_MENU_MAKE_CALL,
2236 						   phone);
2237 		g_free(tmp);
2238 		g_free(label);
2239 		g_free(display);
2240 		g_free(phone);
2241 	}
2242 
2243 	return(menu);
2244 }
2245 
sipe_core_buddy_create_menu(struct sipe_core_public * sipe_public,const gchar * buddy_name,struct sipe_backend_buddy_menu * menu)2246 struct sipe_backend_buddy_menu *sipe_core_buddy_create_menu(struct sipe_core_public *sipe_public,
2247 							    const gchar *buddy_name,
2248 							    struct sipe_backend_buddy_menu *menu)
2249 {
2250 	struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
2251 	sipe_backend_buddy buddy = sipe_backend_buddy_find(sipe_public,
2252 							   buddy_name,
2253 							   NULL);
2254 	gchar *self = sip_uri_self(sipe_private);
2255 
2256  	SIPE_SESSION_FOREACH {
2257 		if (!sipe_strcase_equal(self, buddy_name) && session->chat_session)
2258 		{
2259 			struct sipe_chat_session *chat_session = session->chat_session;
2260 			gboolean is_conf = (chat_session->type == SIPE_CHAT_TYPE_CONFERENCE);
2261 
2262 			if (sipe_backend_chat_find(chat_session->backend, buddy_name))
2263 			{
2264 				gboolean conf_op = sipe_backend_chat_is_operator(chat_session->backend, self);
2265 
2266 				if (is_conf &&
2267 				    /* Not conf OP */
2268 				    !sipe_backend_chat_is_operator(chat_session->backend, buddy_name) &&
2269 				    /* We are a conf OP */
2270 				    conf_op) {
2271 					gchar *label = g_strdup_printf(_("Make leader of '%s'"),
2272 								       chat_session->title);
2273 					menu = sipe_backend_buddy_menu_add(sipe_public,
2274 									   menu,
2275 									   label,
2276 									   SIPE_BUDDY_MENU_MAKE_CHAT_LEADER,
2277 									   chat_session);
2278 					g_free(label);
2279 				}
2280 
2281 				if (is_conf &&
2282 				    /* We are a conf OP */
2283 				    conf_op) {
2284 					gchar *label = g_strdup_printf(_("Remove from '%s'"),
2285 								       chat_session->title);
2286 					menu = sipe_backend_buddy_menu_add(sipe_public,
2287 									   menu,
2288 									   label,
2289 									   SIPE_BUDDY_MENU_REMOVE_FROM_CHAT,
2290 									   chat_session);
2291 					g_free(label);
2292 				}
2293 			}
2294 			else
2295 			{
2296 				if (!is_conf ||
2297 				    (is_conf && !session->locked)) {
2298 					gchar *label = g_strdup_printf(_("Invite to '%s'"),
2299 								       chat_session->title);
2300 					menu = sipe_backend_buddy_menu_add(sipe_public,
2301 									 menu,
2302 									 label,
2303 									 SIPE_BUDDY_MENU_INVITE_TO_CHAT,
2304 									 chat_session);
2305 					g_free(label);
2306 				}
2307 			}
2308 		}
2309 	} SIPE_SESSION_FOREACH_END;
2310 	g_free(self);
2311 
2312 	menu = sipe_backend_buddy_menu_add(sipe_public,
2313 					   menu,
2314 					   _("New chat"),
2315 					   SIPE_BUDDY_MENU_NEW_CHAT,
2316 					   NULL);
2317 
2318 	/* add buddy's phone numbers if we have call control */
2319 	if (sip_csta_is_idle(sipe_private)) {
2320 
2321 		/* work phone */
2322 		menu = buddy_menu_phone(sipe_public,
2323 					menu,
2324 					buddy,
2325 					SIPE_BUDDY_INFO_WORK_PHONE,
2326 					SIPE_BUDDY_INFO_WORK_PHONE_DISPLAY,
2327 					_("Work"));
2328 		/* mobile phone */
2329 		menu = buddy_menu_phone(sipe_public,
2330 					menu,
2331 					buddy,
2332 					SIPE_BUDDY_INFO_MOBILE_PHONE,
2333 					SIPE_BUDDY_INFO_MOBILE_PHONE_DISPLAY,
2334 					_("Mobile"));
2335 
2336 		/* home phone */
2337 		menu = buddy_menu_phone(sipe_public,
2338 					menu,
2339 					buddy,
2340 					SIPE_BUDDY_INFO_HOME_PHONE,
2341 					SIPE_BUDDY_INFO_HOME_PHONE_DISPLAY,
2342 					_("Home"));
2343 
2344 		/* other phone */
2345 		menu = buddy_menu_phone(sipe_public,
2346 					menu,
2347 					buddy,
2348 					SIPE_BUDDY_INFO_OTHER_PHONE,
2349 					SIPE_BUDDY_INFO_OTHER_PHONE_DISPLAY,
2350 					_("Other"));
2351 
2352 		/* custom1 phone */
2353 		menu = buddy_menu_phone(sipe_public,
2354 					menu,
2355 					buddy,
2356 					SIPE_BUDDY_INFO_CUSTOM1_PHONE,
2357 					SIPE_BUDDY_INFO_CUSTOM1_PHONE_DISPLAY,
2358 					_("Custom1"));
2359 	}
2360 
2361 	{
2362 		gchar *email = sipe_backend_buddy_get_string(sipe_public,
2363 							     buddy,
2364 							     SIPE_BUDDY_INFO_EMAIL);
2365 		if (email) {
2366 			menu = sipe_backend_buddy_menu_add(sipe_public,
2367 							   menu,
2368 							   _("Send email..."),
2369 							   SIPE_BUDDY_MENU_SEND_EMAIL,
2370 							   NULL);
2371 			g_free(email);
2372 		}
2373 	}
2374 
2375 #ifdef HAVE_APPSHARE_SERVER
2376 	{
2377 		struct sipe_media_call *call = sipe_media_call_find(SIPE_CORE_PRIVATE,
2378 								    buddy_name);
2379 
2380 		if (call && sipe_appshare_get_role(call) == SIPE_APPSHARE_ROLE_PRESENTER) {
2381 			/* We're already presenting to this buddy. */
2382 
2383 			if (sipe_core_appshare_get_remote_control(call)) {
2384 				menu = sipe_backend_buddy_menu_add(sipe_public, menu,
2385 						_("Take desktop control"),
2386 						SIPE_BUDDY_MENU_TAKE_DESKTOP_CONTROL,
2387 						call);
2388 			} else {
2389 				menu = sipe_backend_buddy_menu_add(sipe_public, menu,
2390 						_("Give desktop control"),
2391 						SIPE_BUDDY_MENU_GIVE_DESKTOP_CONTROL,
2392 						call);
2393 			}
2394 		} else {
2395 			menu = sipe_backend_buddy_menu_add(sipe_public, menu,
2396 							   _("Share my desktop"),
2397 							   SIPE_BUDDY_MENU_SHARE_DESKTOP,
2398 							   NULL);
2399 		}
2400 	}
2401 #endif // HAVE_APPSHARE_SERVER
2402 
2403 	/* access level control */
2404 	if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007))
2405 		menu = sipe_backend_buddy_sub_menu_add(sipe_public,
2406 						       menu,
2407 						       _("Access level"),
2408 						       sipe_ocs2007_access_control_menu(sipe_private,
2409 											buddy_name));
2410 
2411 	return(menu);
2412 }
2413 
sipe_buddy_count(struct sipe_core_private * sipe_private)2414 guint sipe_buddy_count(struct sipe_core_private *sipe_private)
2415 {
2416 	return(g_hash_table_size(sipe_private->buddies->uri));
2417 }
2418 
sipe_ht_hash_nick(const char * nick)2419 static guint sipe_ht_hash_nick(const char *nick)
2420 {
2421 	char *lc = g_utf8_strdown(nick, -1);
2422 	guint bucket = g_str_hash(lc);
2423 	g_free(lc);
2424 
2425 	return bucket;
2426 }
2427 
sipe_ht_equals_nick(const char * nick1,const char * nick2)2428 static gboolean sipe_ht_equals_nick(const char *nick1, const char *nick2)
2429 {
2430 	char *nick1_norm = NULL;
2431 	char *nick2_norm = NULL;
2432 	gboolean equal;
2433 
2434 	if (nick1 == NULL && nick2 == NULL) return TRUE;
2435 	if (nick1 == NULL || nick2 == NULL    ||
2436 	    !g_utf8_validate(nick1, -1, NULL) ||
2437 	    !g_utf8_validate(nick2, -1, NULL)) return FALSE;
2438 
2439 	nick1_norm = g_utf8_casefold(nick1, -1);
2440 	nick2_norm = g_utf8_casefold(nick2, -1);
2441 	equal = g_utf8_collate(nick1_norm, nick2_norm) == 0;
2442 	g_free(nick2_norm);
2443 	g_free(nick1_norm);
2444 
2445 	return equal;
2446 }
2447 
sipe_buddy_init(struct sipe_core_private * sipe_private)2448 void sipe_buddy_init(struct sipe_core_private *sipe_private)
2449 {
2450 	struct sipe_buddies *buddies = g_new0(struct sipe_buddies, 1);
2451 	buddies->uri          = g_hash_table_new((GHashFunc)  sipe_ht_hash_nick,
2452 						 (GEqualFunc) sipe_ht_equals_nick);
2453 	buddies->exchange_key = g_hash_table_new(g_str_hash,
2454 						 g_str_equal);
2455 	sipe_private->buddies = buddies;
2456 }
2457 
2458 /*
2459   Local Variables:
2460   mode: c
2461   c-file-style: "bsd"
2462   indent-tabs-mode: t
2463   tab-width: 8
2464   End:
2465 */
2466