purple: make it compile against 3.0.x API
[siplcs.git] / src / core / sipe.c
bloba5d63e976c2ad980a3ca56c9f19bfdfcf0f70179
1 /**
2 * @file sipe.c
4 * pidgin-sipe
6 * Copyright (C) 2010-11 SIPE Project <http://sipe.sourceforge.net/>
7 * Copyright (C) 2010 pier11 <pier11@operamail.com>
8 * Copyright (C) 2009 Anibal Avelar <debianmx@gmail.com>
9 * Copyright (C) 2009 pier11 <pier11@operamail.com>
10 * Copyright (C) 2008 Novell, Inc., Anibal Avelar <debianmx@gmail.com>
11 * Copyright (C) 2007 Anibal Avelar <debianmx@gmail.com>
12 * Copyright (C) 2005 Thomas Butter <butter@uni-mannheim.de>
14 * ***
15 * Thanks to Google's Summer of Code Program and the helpful mentors
16 * ***
18 * Session-based SIP MESSAGE documentation:
19 * http://tools.ietf.org/html/draft-ietf-simple-im-session-00
21 * This program is free software; you can redistribute it and/or modify
22 * it under the terms of the GNU General Public License as published by
23 * the Free Software Foundation; either version 2 of the License, or
24 * (at your option) any later version.
26 * This program is distributed in the hope that it will be useful,
27 * but WITHOUT ANY WARRANTY; without even the implied warranty of
28 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
29 * GNU General Public License for more details.
31 * You should have received a copy of the GNU General Public License
32 * along with this program; if not, write to the Free Software
33 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
36 #ifdef HAVE_CONFIG_H
37 #include "config.h"
38 #endif
40 #include <time.h>
41 #include <stdlib.h>
42 #include <stdio.h>
43 #include <errno.h>
44 #include <string.h>
45 #include <unistd.h>
47 #include <glib.h>
49 #include <libintl.h>
51 #include "sipe-common.h"
53 #include "account.h"
54 #include "blist.h"
55 #include "connection.h"
56 #include "conversation.h"
57 #include "ft.h"
58 #include "notify.h"
59 #include "plugin.h"
60 #include "privacy.h"
61 #include "request.h"
62 #include "savedstatuses.h"
63 #include "version.h"
65 #include "core-depurple.h" /* Temporary for the core de-purple transition */
67 #include "http-conn.h"
68 #include "sipmsg.h"
69 #include "sip-csta.h"
70 #include "sip-transport.h"
71 #include "sipe-backend.h"
72 #include "sipe-buddy.h"
73 #include "sipe-cal.h"
74 #include "sipe-chat.h"
75 #include "sipe-conf.h"
76 #include "sipe-core.h"
77 #include "sipe-core-private.h"
78 #include "sipe-group.h"
79 #include "sipe-dialog.h"
80 #include "sipe-ews.h"
81 #include "sipe-domino.h"
82 #include "sipe-groupchat.h"
83 #include "sipe-im.h"
84 #include "sipe-mime.h"
85 #include "sipe-nls.h"
86 #include "sipe-schedule.h"
87 #include "sipe-session.h"
88 #include "sipe-subscriptions.h"
89 #ifdef HAVE_VV
90 #include "sipe-media.h"
91 #endif
92 #include "sipe-utils.h"
93 #include "sipe-xml.h"
94 #include "uuid.h"
95 #include "sipe.h"
97 #define SIPE_IDLE_SET_DELAY 1 /* 1 sec */
99 #define UPDATE_CALENDAR_DELAY 1*60 /* 1 min */
100 #define UPDATE_CALENDAR_INTERVAL 30*60 /* 30 min */
102 /* Status identifiers (see also: sipe_status_types()) */
103 #define SIPE_STATUS_ID_UNKNOWN purple_primitive_get_id_from_type(PURPLE_STATUS_UNSET) /* Unset (primitive) */
104 #define SIPE_STATUS_ID_OFFLINE purple_primitive_get_id_from_type(PURPLE_STATUS_OFFLINE) /* Offline (primitive) */
105 #define SIPE_STATUS_ID_AVAILABLE purple_primitive_get_id_from_type(PURPLE_STATUS_AVAILABLE) /* Online */
106 /* PURPLE_STATUS_UNAVAILABLE: */
107 #define SIPE_STATUS_ID_BUSY "busy" /* Busy */
108 #define SIPE_STATUS_ID_BUSYIDLE "busyidle" /* BusyIdle */
109 #define SIPE_STATUS_ID_DND "do-not-disturb" /* Do Not Disturb */
110 #define SIPE_STATUS_ID_IN_MEETING "in-a-meeting" /* In a meeting */
111 #define SIPE_STATUS_ID_IN_CONF "in-a-conference" /* In a conference */
112 #define SIPE_STATUS_ID_ON_PHONE "on-the-phone" /* On the phone */
113 #define SIPE_STATUS_ID_INVISIBLE purple_primitive_get_id_from_type(PURPLE_STATUS_INVISIBLE) /* Appear Offline */
114 /* PURPLE_STATUS_AWAY: */
115 #define SIPE_STATUS_ID_IDLE "idle" /* Idle/Inactive */
116 #define SIPE_STATUS_ID_BRB "be-right-back" /* Be Right Back */
117 #define SIPE_STATUS_ID_AWAY purple_primitive_get_id_from_type(PURPLE_STATUS_AWAY) /* Away (primitive) */
118 /** Reuters status (user settable) */
119 #define SIPE_STATUS_ID_LUNCH "out-to-lunch" /* Out To Lunch */
120 /* ??? PURPLE_STATUS_EXTENDED_AWAY */
121 /* ??? PURPLE_STATUS_MOBILE */
122 /* ??? PURPLE_STATUS_TUNE */
124 /* Status attributes (see also sipe_status_types() */
125 #define SIPE_STATUS_ATTR_ID_MESSAGE "message"
127 static struct sipe_activity_map_struct
129 sipe_activity type;
130 const char *token;
131 const char *desc;
132 const char *status_id;
134 } const sipe_activity_map[] =
136 /* This has nothing to do with Availability numbers, like 3500 (online).
137 * Just a mapping of Communicator Activities to Purple statuses to be able display them in Pidgin.
139 { SIPE_ACTIVITY_UNSET, "unset", NULL , NULL },
140 { SIPE_ACTIVITY_ONLINE, "online", NULL , NULL },
141 { SIPE_ACTIVITY_INACTIVE, SIPE_STATUS_ID_IDLE, N_("Inactive") , NULL },
142 { SIPE_ACTIVITY_BUSY, SIPE_STATUS_ID_BUSY, N_("Busy") , SIPE_STATUS_ID_BUSY },
143 { SIPE_ACTIVITY_BUSYIDLE, SIPE_STATUS_ID_BUSYIDLE, N_("Busy-Idle") , NULL },
144 { SIPE_ACTIVITY_DND, SIPE_STATUS_ID_DND, NULL , SIPE_STATUS_ID_DND },
145 { SIPE_ACTIVITY_BRB, SIPE_STATUS_ID_BRB, N_("Be right back") , SIPE_STATUS_ID_BRB },
146 { SIPE_ACTIVITY_AWAY, "away", NULL , NULL },
147 { SIPE_ACTIVITY_LUNCH, SIPE_STATUS_ID_LUNCH, N_("Out to lunch") , NULL },
148 { SIPE_ACTIVITY_OFFLINE, "offline", NULL , NULL },
149 { SIPE_ACTIVITY_ON_PHONE, SIPE_STATUS_ID_ON_PHONE, N_("In a call") , NULL },
150 { SIPE_ACTIVITY_IN_CONF, SIPE_STATUS_ID_IN_CONF, N_("In a conference") , NULL },
151 { SIPE_ACTIVITY_IN_MEETING, SIPE_STATUS_ID_IN_MEETING, N_("In a meeting") , NULL },
152 { SIPE_ACTIVITY_OOF, "out-of-office", N_("Out of office") , NULL },
153 { SIPE_ACTIVITY_URGENT_ONLY, "urgent-interruptions-only", N_("Urgent interruptions only") , NULL }
155 /** @param x is sipe_activity */
156 #define SIPE_ACTIVITY_I18N(x) gettext(sipe_activity_map[x].desc)
158 static sipe_activity
159 sipe_get_activity_by_token(const char *token)
161 int i;
163 for (i = 0; i < SIPE_ACTIVITY_NUM_TYPES; i++)
165 if (sipe_strequal(token, sipe_activity_map[i].token))
166 return sipe_activity_map[i].type;
169 return sipe_activity_map[0].type;
172 static const char *
173 sipe_get_activity_desc_by_token(const char *token)
175 if (!token) return NULL;
177 return SIPE_ACTIVITY_I18N(sipe_get_activity_by_token(token));
180 static void send_presence_status(struct sipe_core_private *sipe_private,
181 void *unused);
184 * @param from0 from URI (with 'sip:' prefix). Will be filled with self-URI if NULL passed.
186 void
187 send_soap_request_with_cb(struct sipe_core_private *sipe_private,
188 gchar *from0,
189 gchar *body,
190 TransCallback callback,
191 struct transaction_payload *payload)
193 gchar *from = from0 ? g_strdup(from0) : sip_uri_self(sipe_private);
194 gchar *contact = get_contact(sipe_private);
195 gchar *hdr = g_strdup_printf("Contact: %s\r\n"
196 "Content-Type: application/SOAP+xml\r\n",contact);
198 struct transaction *trans = sip_transport_service(sipe_private,
199 from,
200 hdr,
201 body,
202 callback);
203 trans->payload = payload;
205 g_free(from);
206 g_free(contact);
207 g_free(hdr);
210 void
211 send_soap_request(struct sipe_core_private *sipe_private,
212 gchar *body)
214 send_soap_request_with_cb(sipe_private, NULL, body, NULL, NULL);
218 * Returns pointer to URI without sip: prefix if any
220 * @param sip_uri SIP URI possibly with sip: prefix. Example: sip:first.last@hq.company.com
221 * @return pointer to URL without sip: prefix. Coresponding example: first.last@hq.company.com
223 * Doesn't allocate memory
225 static const char *
226 sipe_get_no_sip_uri(const char *sip_uri)
228 const char *prefix = "sip:";
229 if (!sip_uri) return NULL;
231 if (g_str_has_prefix(sip_uri, prefix)) {
232 return (sip_uri+strlen(prefix));
233 } else {
234 return sip_uri;
238 static void
239 sipe_contact_set_acl (struct sipe_core_private *sipe_private,
240 const gchar *who,
241 gchar *rights)
243 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
244 gchar * body = g_strdup_printf(SIPE_SOAP_ALLOW_DENY, who, rights, sip->acl_delta++);
245 send_soap_request(sipe_private, body);
246 g_free(body);
249 static void
250 sipe_change_access_level(struct sipe_core_private *sipe_private,
251 const int container_id,
252 const gchar *type,
253 const gchar *value);
255 void
256 sipe_core_contact_allow_deny (struct sipe_core_public *sipe_public,
257 const gchar * who, gboolean allow)
259 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
261 if (allow) {
262 SIPE_DEBUG_INFO("Authorizing contact %s", who);
263 } else {
264 SIPE_DEBUG_INFO("Blocking contact %s", who);
267 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
268 sipe_change_access_level(sipe_private, (allow ? -1 : 32000), "user", sipe_get_no_sip_uri(who));
269 } else {
270 sipe_contact_set_acl(sipe_private, who, allow ? "AA" : "BD");
274 static
275 void sipe_auth_user_cb(void * data)
277 struct sipe_auth_job * job = (struct sipe_auth_job *) data;
278 if (!job) return;
280 sipe_core_contact_allow_deny((struct sipe_core_public *)job->sipe_private, job->who, TRUE);
281 g_free(job);
284 static
285 void sipe_deny_user_cb(void * data)
287 struct sipe_auth_job * job = (struct sipe_auth_job *) data;
288 if (!job) return;
290 sipe_core_contact_allow_deny((struct sipe_core_public *)job->sipe_private, job->who, FALSE);
291 g_free(job);
294 /** @applicable: 2005-
296 static void
297 sipe_process_presence_wpending (struct sipe_core_private *sipe_private,
298 struct sipmsg * msg)
300 sipe_xml *watchers;
301 const sipe_xml *watcher;
302 // Ensure it's either not a response (eg it's a BENOTIFY) or that it's a 200 OK response
303 if (msg->response != 0 && msg->response != 200) return;
305 if (msg->bodylen == 0 || msg->body == NULL || sipe_strequal(sipmsg_find_header(msg, "Event"), "msrtc.wpending")) return;
307 watchers = sipe_xml_parse(msg->body, msg->bodylen);
308 if (!watchers) return;
310 for (watcher = sipe_xml_child(watchers, "watcher"); watcher; watcher = sipe_xml_twin(watcher)) {
311 gchar * remote_user = g_strdup(sipe_xml_attribute(watcher, "uri"));
312 gchar * alias = g_strdup(sipe_xml_attribute(watcher, "displayName"));
313 gboolean on_list = g_hash_table_lookup(sipe_private->buddies, remote_user) != NULL;
315 // TODO pull out optional displayName to pass as alias
316 if (remote_user) {
317 struct sipe_auth_job * job = g_new0(struct sipe_auth_job, 1);
318 job->who = remote_user;
319 job->sipe_private = sipe_private;
320 sipe_backend_buddy_request_authorization(SIPE_CORE_PUBLIC,
321 remote_user,
322 alias,
323 on_list,
324 sipe_auth_user_cb,
325 sipe_deny_user_cb,
326 (gpointer)job);
331 sipe_xml_free(watchers);
332 return;
336 * Returns string like "2 4 7 8" - group ids buddy belong to.
338 static gchar *
339 sipe_get_buddy_groups_string (struct sipe_buddy *buddy) {
340 int i = 0;
341 gchar *res;
342 //creating array from GList, converting int to gchar*
343 gchar **ids_arr = g_new(gchar *, g_slist_length(buddy->groups) + 1);
344 GSList *entry = buddy->groups;
346 if (!ids_arr) return NULL;
348 while (entry) {
349 struct sipe_group * group = entry->data;
350 ids_arr[i] = g_strdup_printf("%d", group->id);
351 entry = entry->next;
352 i++;
354 ids_arr[i] = NULL;
355 res = g_strjoinv(" ", ids_arr);
356 g_strfreev(ids_arr);
357 return res;
361 * Sends buddy update to server
363 void
364 sipe_core_group_set_user(struct sipe_core_public *sipe_public, const gchar * who)
366 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA;
367 struct sipe_buddy *buddy = g_hash_table_lookup(SIPE_CORE_PRIVATE->buddies, who);
368 sipe_backend_buddy backend_buddy = sipe_backend_buddy_find(sipe_public, who, NULL);
370 if (buddy && backend_buddy) {
371 gchar *alias = sipe_backend_buddy_get_alias(sipe_public, backend_buddy);
372 gchar *groups = sipe_get_buddy_groups_string(buddy);
373 if (groups) {
374 gchar *body;
375 SIPE_DEBUG_INFO("Saving buddy %s with alias %s and groups %s", who, alias, groups);
377 body = g_markup_printf_escaped(SIPE_SOAP_SET_CONTACT,
378 alias, groups, "true", buddy->name, sip->contacts_delta++
380 send_soap_request(SIPE_CORE_PRIVATE, body);
381 g_free(groups);
382 g_free(body);
384 g_free(alias);
388 static void
389 sipe_sched_calendar_status_update(struct sipe_core_private *sipe_private,
390 time_t calculate_from);
392 static int
393 sipe_get_availability_by_status(const char* sipe_status_id, char** activity_token);
395 static const char*
396 sipe_get_status_by_availability(int avail,
397 char** activity);
399 static void
400 sipe_set_purple_account_status_and_note(const PurpleAccount *account,
401 const char *status_id,
402 const char *message,
403 time_t do_not_publish[]);
405 static void
406 sipe_apply_calendar_status(struct sipe_core_private *sipe_private,
407 struct sipe_buddy *sbuddy,
408 const char *status_id)
410 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
411 time_t cal_avail_since;
412 int cal_status = sipe_cal_get_status(sbuddy, time(NULL), &cal_avail_since);
413 int avail;
414 gchar *self_uri;
416 if (!sbuddy) return;
418 if (cal_status < SIPE_CAL_NO_DATA) {
419 SIPE_DEBUG_INFO("sipe_apply_calendar_status: cal_status : %d for %s", cal_status, sbuddy->name);
420 SIPE_DEBUG_INFO("sipe_apply_calendar_status: cal_avail_since : %s", asctime(localtime(&cal_avail_since)));
423 /* scheduled Cal update call */
424 if (!status_id) {
425 status_id = sbuddy->last_non_cal_status_id;
426 g_free(sbuddy->activity);
427 sbuddy->activity = g_strdup(sbuddy->last_non_cal_activity);
430 if (!status_id) {
431 SIPE_DEBUG_INFO("sipe_apply_calendar_status: status_id is NULL for %s, exiting.",
432 sbuddy->name ? sbuddy->name : "" );
433 return;
436 /* adjust to calendar status */
437 if (cal_status != SIPE_CAL_NO_DATA) {
438 SIPE_DEBUG_INFO("sipe_apply_calendar_status: user_avail_since: %s", asctime(localtime(&sbuddy->user_avail_since)));
440 if (cal_status == SIPE_CAL_BUSY
441 && cal_avail_since > sbuddy->user_avail_since
442 && 6500 >= sipe_get_availability_by_status(status_id, NULL))
444 status_id = SIPE_STATUS_ID_BUSY;
445 g_free(sbuddy->activity);
446 sbuddy->activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_IN_MEETING));
448 avail = sipe_get_availability_by_status(status_id, NULL);
450 SIPE_DEBUG_INFO("sipe_apply_calendar_status: activity_since : %s", asctime(localtime(&sbuddy->activity_since)));
451 if (cal_avail_since > sbuddy->activity_since) {
452 if (cal_status == SIPE_CAL_OOF
453 && avail >= 15000) /* 12000 in 2007 */
455 g_free(sbuddy->activity);
456 sbuddy->activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_OOF));
461 /* then set status_id actually */
462 SIPE_DEBUG_INFO("sipe_apply_calendar_status: to %s for %s", status_id, sbuddy->name ? sbuddy->name : "" );
463 sipe_backend_buddy_set_status(SIPE_CORE_PUBLIC, sbuddy->name, status_id);
465 /* set our account state to the one in roaming (including calendar info) */
466 self_uri = sip_uri_self(sipe_private);
467 if (sip->initial_state_published && sipe_strcase_equal(sbuddy->name, self_uri)) {
468 if (sipe_strequal(status_id, SIPE_STATUS_ID_OFFLINE)) {
469 status_id = g_strdup(SIPE_STATUS_ID_INVISIBLE); /* not not let offline status switch us off */
472 SIPE_DEBUG_INFO("sipe_apply_calendar_status: switch to '%s' for the account", sip->status);
473 sipe_set_purple_account_status_and_note(sip->account, status_id, sip->note, sip->do_not_publish);
475 g_free(self_uri);
478 void
479 sipe_core_buddy_got_status(struct sipe_core_public *sipe_public,
480 const gchar* uri,
481 const gchar *status_id)
483 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
484 struct sipe_buddy *sbuddy = g_hash_table_lookup(sipe_private->buddies, uri);
486 if (!sbuddy) return;
488 /* Check if on 2005 system contact's calendar,
489 * then set/preserve it.
491 if (!SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
492 sipe_apply_calendar_status(sipe_private, sbuddy, status_id);
493 } else {
494 sipe_backend_buddy_set_status(sipe_public, uri, status_id);
498 static void
499 update_calendar_status_cb(SIPE_UNUSED_PARAMETER char *name,
500 struct sipe_buddy *sbuddy,
501 struct sipe_core_private *sipe_private)
503 sipe_apply_calendar_status(sipe_private, sbuddy, NULL);
507 * Updates contact's status
508 * based on their calendar information.
510 * Applicability: 2005 systems
512 static void
513 update_calendar_status(struct sipe_core_private *sipe_private,
514 SIPE_UNUSED_PARAMETER void *unused)
516 SIPE_DEBUG_INFO_NOFORMAT("update_calendar_status() started.");
517 g_hash_table_foreach(sipe_private->buddies, (GHFunc)update_calendar_status_cb, sipe_private);
519 /* repeat scheduling */
520 sipe_sched_calendar_status_update(sipe_private, time(NULL) + 3*60 /* 3 min */);
524 * Schedules process of contacts' status update
525 * based on their calendar information.
526 * Should be scheduled to the beginning of every
527 * 15 min interval, like:
528 * 13:00, 13:15, 13:30, 13:45, etc.
530 * Applicability: 2005 systems
532 static void
533 sipe_sched_calendar_status_update(struct sipe_core_private *sipe_private,
534 time_t calculate_from)
536 int interval = 15*60;
537 /** start of the beginning of closest 15 min interval. */
538 time_t next_start = ((time_t)((int)((int)calculate_from)/interval + 1)*interval);
540 SIPE_DEBUG_INFO("sipe_sched_calendar_status_update: calculate_from time: %s",
541 asctime(localtime(&calculate_from)));
542 SIPE_DEBUG_INFO("sipe_sched_calendar_status_update: next start time : %s",
543 asctime(localtime(&next_start)));
545 sipe_schedule_seconds(sipe_private,
546 "<+2005-cal-status>",
547 NULL,
548 next_start - time(NULL),
549 update_calendar_status,
550 NULL);
554 * Schedules process of self status publish
555 * based on own calendar information.
556 * Should be scheduled to the beginning of every
557 * 15 min interval, like:
558 * 13:00, 13:15, 13:30, 13:45, etc.
560 * Applicability: 2007+ systems
562 static void
563 sipe_sched_calendar_status_self_publish(struct sipe_core_private *sipe_private,
564 time_t calculate_from)
566 int interval = 5*60;
567 /** start of the beginning of closest 5 min interval. */
568 time_t next_start = ((time_t)((int)((int)calculate_from)/interval + 1)*interval);
570 SIPE_DEBUG_INFO("sipe_sched_calendar_status_self_publish: calculate_from time: %s",
571 asctime(localtime(&calculate_from)));
572 SIPE_DEBUG_INFO("sipe_sched_calendar_status_self_publish: next start time : %s",
573 asctime(localtime(&next_start)));
575 sipe_schedule_seconds(sipe_private,
576 "<+2007-cal-status>",
577 NULL,
578 next_start - time(NULL),
579 publish_calendar_status_self,
580 NULL);
583 static void sipe_subscribe_resource_uri(const char *name,
584 SIPE_UNUSED_PARAMETER gpointer value,
585 gchar **resources_uri)
587 gchar *tmp = *resources_uri;
588 *resources_uri = g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp, name);
589 g_free(tmp);
592 static void sipe_subscribe_resource_uri_with_context(const char *name, gpointer value, gchar **resources_uri)
594 struct sipe_buddy *sbuddy = (struct sipe_buddy *)value;
595 gchar *context = sbuddy && sbuddy->just_added ? "><context/></resource>" : "/>";
596 gchar *tmp = *resources_uri;
598 if (sbuddy) sbuddy->just_added = FALSE; /* should be enought to include context one time */
600 *resources_uri = g_strdup_printf("%s<resource uri=\"%s\"%s\n", tmp, name, context);
601 g_free(tmp);
605 * Support for Batch Category SUBSCRIBE [MS-PRES] - msrtc-event-categories+xml OCS 2007
606 * Support for Batch Category SUBSCRIBE [MS-SIP] - adrl+xml LCS 2005
607 * The user sends an initial batched category SUBSCRIBE request against all contacts on his roaming list in only a request
608 * A batch category SUBSCRIBE request MUST have the same To-URI and From-URI.
609 * This header will be send only if adhoclist there is a "Supported: adhoclist" in REGISTER answer else will be send a Single Category SUBSCRIBE
612 static void sipe_subscribe_presence_batched_to(struct sipe_core_private *sipe_private,
613 gchar *resources_uri,
614 gchar *to)
616 gchar *contact = get_contact(sipe_private);
617 gchar *request;
618 gchar *content;
619 gchar *require = "";
620 gchar *accept = "";
621 gchar *autoextend = "";
622 gchar *content_type;
624 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
625 require = ", categoryList";
626 accept = ", application/msrtc-event-categories+xml, application/xpidf+xml, application/pidf+xml";
627 content_type = "application/msrtc-adrl-categorylist+xml";
628 content = g_strdup_printf(
629 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
630 "<action name=\"subscribe\" id=\"63792024\">\n"
631 "<adhocList>\n%s</adhocList>\n"
632 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
633 "<category name=\"calendarData\"/>\n"
634 "<category name=\"contactCard\"/>\n"
635 "<category name=\"note\"/>\n"
636 "<category name=\"state\"/>\n"
637 "</categoryList>\n"
638 "</action>\n"
639 "</batchSub>", sipe_private->username, resources_uri);
640 } else {
641 autoextend = "Supported: com.microsoft.autoextend\r\n";
642 content_type = "application/adrl+xml";
643 content = g_strdup_printf(
644 "<adhoclist xmlns=\"urn:ietf:params:xml:ns:adrl\" uri=\"sip:%s\" name=\"sip:%s\">\n"
645 "<create xmlns=\"\">\n%s</create>\n"
646 "</adhoclist>\n", sipe_private->username, sipe_private->username, resources_uri);
648 g_free(resources_uri);
650 request = g_strdup_printf(
651 "Require: adhoclist%s\r\n"
652 "Supported: eventlist\r\n"
653 "Accept: application/rlmi+xml, multipart/related, text/xml+msrtc.pidf%s\r\n"
654 "Supported: ms-piggyback-first-notify\r\n"
655 "%sSupported: ms-benotify\r\n"
656 "Proxy-Require: ms-benotify\r\n"
657 "Event: presence\r\n"
658 "Content-Type: %s\r\n"
659 "Contact: %s\r\n", require, accept, autoextend, content_type, contact);
660 g_free(contact);
662 sipe_subscribe_presence_buddy(sipe_private, to, request, content);
664 g_free(content);
665 g_free(to);
666 g_free(request);
669 static void sipe_subscribe_presence_batched(struct sipe_core_private *sipe_private,
670 SIPE_UNUSED_PARAMETER void *unused)
672 gchar *to = sip_uri_self(sipe_private);
673 gchar *resources_uri = g_strdup("");
674 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
675 g_hash_table_foreach(sipe_private->buddies, (GHFunc) sipe_subscribe_resource_uri_with_context , &resources_uri);
676 } else {
677 g_hash_table_foreach(sipe_private->buddies, (GHFunc) sipe_subscribe_resource_uri, &resources_uri);
680 sipe_subscribe_presence_batched_to(sipe_private, resources_uri, to);
683 struct presence_batched_routed {
684 gchar *host;
685 GSList *buddies;
688 static void sipe_subscribe_presence_batched_routed_free(void *payload)
690 struct presence_batched_routed *data = payload;
691 GSList *buddies = data->buddies;
692 while (buddies) {
693 g_free(buddies->data);
694 buddies = buddies->next;
696 g_slist_free(data->buddies);
697 g_free(data->host);
698 g_free(payload);
701 static void sipe_subscribe_presence_batched_routed(struct sipe_core_private *sipe_private,
702 void *payload)
704 struct presence_batched_routed *data = payload;
705 GSList *buddies = data->buddies;
706 gchar *resources_uri = g_strdup("");
707 while (buddies) {
708 gchar *tmp = resources_uri;
709 resources_uri = g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp, (char *) buddies->data);
710 g_free(tmp);
711 buddies = buddies->next;
713 sipe_subscribe_presence_batched_to(sipe_private, resources_uri,
714 g_strdup(data->host));
718 * Single Category SUBSCRIBE [MS-PRES] ; To send when the server returns a 200 OK message with state="resubscribe" in response.
719 * The user sends a single SUBSCRIBE request to the subscribed contact.
720 * The To-URI and the URI listed in the resource list MUST be the same for a single category SUBSCRIBE request.
724 static void sipe_subscribe_presence_single(struct sipe_core_private *sipe_private,
725 void *buddy_name)
727 gchar *to = sip_uri((char *)buddy_name);
728 gchar *tmp = get_contact(sipe_private);
729 gchar *request;
730 gchar *content = NULL;
731 gchar *autoextend = "";
732 gchar *content_type = "";
733 struct sipe_buddy *sbuddy = g_hash_table_lookup(sipe_private->buddies, to);
734 gchar *context = sbuddy && sbuddy->just_added ? "><context/></resource>" : "/>";
736 if (sbuddy) sbuddy->just_added = FALSE;
738 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
739 content_type = "Content-Type: application/msrtc-adrl-categorylist+xml\r\n";
740 } else {
741 autoextend = "Supported: com.microsoft.autoextend\r\n";
744 request = g_strdup_printf(
745 "Accept: application/msrtc-event-categories+xml, text/xml+msrtc.pidf, application/xpidf+xml, application/pidf+xml, application/rlmi+xml, multipart/related\r\n"
746 "Supported: ms-piggyback-first-notify\r\n"
747 "%s%sSupported: ms-benotify\r\n"
748 "Proxy-Require: ms-benotify\r\n"
749 "Event: presence\r\n"
750 "Contact: %s\r\n", autoextend, content_type, tmp);
752 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
753 content = g_strdup_printf(
754 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
755 "<action name=\"subscribe\" id=\"63792024\"><adhocList>\n"
756 "<resource uri=\"%s\"%s\n"
757 "</adhocList>\n"
758 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
759 "<category name=\"calendarData\"/>\n"
760 "<category name=\"contactCard\"/>\n"
761 "<category name=\"note\"/>\n"
762 "<category name=\"state\"/>\n"
763 "</categoryList>\n"
764 "</action>\n"
765 "</batchSub>", sipe_private->username, to, context);
768 g_free(tmp);
770 sipe_subscribe_presence_buddy(sipe_private, to, request, content);
772 g_free(content);
773 g_free(to);
774 g_free(request);
777 void sipe_set_status(PurpleAccount *account, PurpleStatus *status)
779 SIPE_DEBUG_INFO("sipe_set_status: status=%s", purple_status_get_id(status));
781 if (!purple_status_is_active(status))
782 return;
784 if (account->gc) {
785 struct sipe_core_private *sipe_private = PURPLE_ACCOUNT_TO_SIPE_CORE_PRIVATE;
786 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
788 if (sip) {
789 gchar *action_name;
790 gchar *tmp;
791 time_t now = time(NULL);
792 const char *status_id = purple_status_get_id(status);
793 const char *note = purple_status_get_attr_string(status, SIPE_STATUS_ATTR_ID_MESSAGE);
794 sipe_activity activity = sipe_get_activity_by_token(status_id);
795 gboolean do_not_publish = ((now - sip->do_not_publish[activity]) <= 2);
797 /* when other point of presence clears note, but we are keeping
798 * state if OOF note.
800 if (do_not_publish && !note && sip->cal && sip->cal->oof_note) {
801 SIPE_DEBUG_INFO_NOFORMAT("sipe_set_status: enabling publication as OOF note keepers.");
802 do_not_publish = FALSE;
805 SIPE_DEBUG_INFO("sipe_set_status: was: sip->do_not_publish[%s]=%d [?] now(time)=%d",
806 status_id, (int)sip->do_not_publish[activity], (int)now);
808 sip->do_not_publish[activity] = 0;
809 SIPE_DEBUG_INFO("sipe_set_status: set: sip->do_not_publish[%s]=%d [0]",
810 status_id, (int)sip->do_not_publish[activity]);
812 if (do_not_publish)
814 SIPE_DEBUG_INFO_NOFORMAT("sipe_set_status: publication was switched off, exiting.");
815 return;
818 g_free(sip->status);
819 sip->status = g_strdup(status_id);
821 /* hack to escape apostrof before comparison */
822 tmp = note ? sipe_utils_str_replace(note, "'", "&apos;") : NULL;
824 /* this will preserve OOF flag as well */
825 if (!sipe_strequal(tmp, sip->note)) {
826 sip->is_oof_note = FALSE;
827 g_free(sip->note);
828 sip->note = g_strdup(note);
829 sip->note_since = time(NULL);
831 g_free(tmp);
833 /* schedule 2 sec to capture idle flag */
834 action_name = g_strdup_printf("<%s>", "+set-status");
835 sipe_schedule_seconds(sipe_private,
836 action_name,
837 NULL,
838 SIPE_IDLE_SET_DELAY,
839 send_presence_status,
840 NULL);
841 g_free(action_name);
846 void
847 sipe_set_idle(PurpleConnection * gc,
848 int interval)
850 SIPE_DEBUG_INFO("sipe_set_idle: interval=%d", interval);
852 if (gc) {
853 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
854 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
856 if (sip) {
857 sip->idle_switch = time(NULL);
858 SIPE_DEBUG_INFO("sipe_set_idle: sip->idle_switch : %s", asctime(localtime(&(sip->idle_switch))));
863 void sipe_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
865 SIPE_DEBUG_INFO("sipe_add_buddy[CB]: buddy:%s group:%s", buddy ? buddy->name : "", group ? group->name : "");
867 /* libpurple can call us with undefined buddy or group */
868 if (buddy && group) {
869 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
871 /* Buddy name must be lower case as we use purple_normalize_nocase() to compare */
872 gchar *buddy_name = g_ascii_strdown(buddy->name, -1);
873 purple_blist_rename_buddy(buddy, buddy_name);
874 g_free(buddy_name);
876 /* Prepend sip: if needed */
877 if (!g_str_has_prefix(buddy->name, "sip:")) {
878 gchar *buf = sip_uri_from_name(buddy->name);
879 purple_blist_rename_buddy(buddy, buf);
880 g_free(buf);
883 if (!g_hash_table_lookup(sipe_private->buddies, buddy->name)) {
884 struct sipe_buddy *b = g_new0(struct sipe_buddy, 1);
885 SIPE_DEBUG_INFO("sipe_add_buddy: adding %s", buddy->name);
886 b->name = g_strdup(buddy->name);
887 b->just_added = TRUE;
888 g_hash_table_insert(sipe_private->buddies, b->name, b);
889 /* @TODO should go to callback */
890 sipe_subscribe_presence_single(sipe_private,
891 b->name);
892 } else {
893 SIPE_DEBUG_INFO("sipe_add_buddy: buddy %s already in internal list", buddy->name);
896 sipe_core_buddy_group(PURPLE_GC_TO_SIPE_CORE_PUBLIC, buddy->name, NULL, group->name);
900 static void sipe_free_buddy(struct sipe_buddy *buddy)
902 #ifndef _WIN32
904 * We are calling g_hash_table_foreach_steal(). That means that no
905 * key/value deallocation functions are called. Therefore the glib
906 * hash code does not touch the key (buddy->name) or value (buddy)
907 * of the to-be-deleted hash node at all. It follows that we
909 * - MUST free the memory for the key ourselves and
910 * - ARE allowed to do it in this function
912 * Conclusion: glib must be broken on the Windows platform if sipe
913 * crashes with SIGTRAP when closing. You'll have to live
914 * with the memory leak until this is fixed.
916 g_free(buddy->name);
917 #endif
918 g_free(buddy->activity);
919 g_free(buddy->meeting_subject);
920 g_free(buddy->meeting_location);
921 g_free(buddy->note);
923 g_free(buddy->cal_start_time);
924 g_free(buddy->cal_free_busy_base64);
925 g_free(buddy->cal_free_busy);
926 g_free(buddy->last_non_cal_activity);
928 sipe_cal_free_working_hours(buddy->cal_working_hours);
930 g_free(buddy->device_name);
931 g_slist_free(buddy->groups);
932 g_free(buddy);
936 * Unassociates buddy from group first.
937 * Then see if no groups left, removes buddy completely.
938 * Otherwise updates buddy groups on server.
940 void sipe_remove_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
942 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
943 struct sipe_buddy *b;
944 struct sipe_group *g = NULL;
946 SIPE_DEBUG_INFO("sipe_remove_buddy[CB]: buddy:%s group:%s", buddy ? buddy->name : "", group ? group->name : "");
947 if (!buddy) return;
949 b = g_hash_table_lookup(sipe_private->buddies, buddy->name);
950 if (!b) return;
952 if (group) {
953 g = sipe_group_find_by_name(sipe_private, group->name);
956 if (g) {
957 b->groups = g_slist_remove(b->groups, g);
958 SIPE_DEBUG_INFO("buddy %s removed from group %s", buddy->name, g->name);
961 if (g_slist_length(b->groups) < 1) {
962 gchar *action_name = sipe_utils_presence_key(buddy->name);
963 sipe_schedule_cancel(sipe_private, action_name);
964 g_free(action_name);
966 g_hash_table_remove(sipe_private->buddies, buddy->name);
968 if (b->name) {
969 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
970 gchar * body = g_strdup_printf(SIPE_SOAP_DEL_CONTACT, b->name, sip->contacts_delta++);
971 send_soap_request(sipe_private, body);
972 g_free(body);
975 sipe_free_buddy(b);
976 } else {
977 //updates groups on server
978 sipe_core_group_set_user(SIPE_CORE_PUBLIC, b->name);
984 * A callback for g_hash_table_foreach
986 static void
987 sipe_buddy_subscribe_cb(char *buddy_name,
988 SIPE_UNUSED_PARAMETER struct sipe_buddy *buddy,
989 struct sipe_core_private *sipe_private)
991 gchar *action_name = sipe_utils_presence_key(buddy_name);
992 /* g_hash_table_size() can never return 0, otherwise this function wouldn't be called :-) */
993 guint time_range = (g_hash_table_size(sipe_private->buddies) * 1000) / 25; /* time interval for 25 requests per sec. In msec. */
994 guint timeout = ((guint) rand()) / (RAND_MAX / time_range) + 1; /* random period within the range but never 0! */
996 sipe_schedule_mseconds(sipe_private,
997 action_name,
998 g_strdup(buddy_name),
999 timeout,
1000 sipe_subscribe_presence_single,
1001 g_free);
1002 g_free(action_name);
1006 * Removes entries from local buddy list
1007 * that does not correspond ones in the roaming contact list.
1009 static void sipe_cleanup_local_blist(struct sipe_core_private *sipe_private) {
1010 GSList *buddies = sipe_backend_buddy_find_all(SIPE_CORE_PUBLIC, NULL, NULL);
1011 GSList *entry = buddies;
1012 struct sipe_buddy *buddy;
1013 sipe_backend_buddy b;
1014 gchar *bname;
1015 gchar *gname;
1017 SIPE_DEBUG_INFO("sipe_cleanup_local_blist: overall %d backend buddies (including clones)", g_slist_length(buddies));
1018 SIPE_DEBUG_INFO("sipe_cleanup_local_blist: %d sipe buddies (unique)", g_hash_table_size(sipe_private->buddies));
1019 while (entry) {
1020 b = entry->data;
1021 gname = sipe_backend_buddy_get_group_name(SIPE_CORE_PUBLIC, b);
1022 bname = sipe_backend_buddy_get_name(SIPE_CORE_PUBLIC, b);
1023 buddy = g_hash_table_lookup(sipe_private->buddies, bname);
1024 g_free(bname);
1025 if(buddy) {
1026 gboolean in_sipe_groups = FALSE;
1027 GSList *entry2 = buddy->groups;
1028 while (entry2) {
1029 struct sipe_group *group = entry2->data;
1030 if (sipe_strequal(group->name, gname)) {
1031 in_sipe_groups = TRUE;
1032 break;
1034 entry2 = entry2->next;
1036 if(!in_sipe_groups) {
1037 SIPE_DEBUG_INFO("*** REMOVING %s from blist group: %s as not having this group in roaming list", bname, gname);
1038 sipe_backend_buddy_remove(SIPE_CORE_PUBLIC, b);
1040 } else {
1041 SIPE_DEBUG_INFO("*** REMOVING %s from blist group: %s as this buddy not in roaming list", bname, gname);
1042 sipe_backend_buddy_remove(SIPE_CORE_PUBLIC, b);
1044 g_free(gname);
1045 entry = entry->next;
1047 g_slist_free(buddies);
1050 static int
1051 sipe_find_access_level(struct sipe_core_private *sipe_private,
1052 const gchar *type,
1053 const gchar *value,
1054 gboolean *is_group_access);
1056 static void
1057 sipe_refresh_blocked_status_cb(char *buddy_name,
1058 SIPE_UNUSED_PARAMETER struct sipe_buddy *buddy,
1059 struct sipe_core_private *sipe_private)
1061 int container_id = sipe_find_access_level(sipe_private, "user", buddy_name, NULL);
1062 gboolean blocked = (container_id == 32000);
1063 gboolean blocked_in_blist = sipe_backend_buddy_is_blocked(SIPE_CORE_PUBLIC, buddy_name);
1065 /* SIPE_DEBUG_INFO("sipe_refresh_blocked_status_cb: buddy_name=%s, blocked=%s, blocked_in_blist=%s",
1066 buddy_name, blocked ? "T" : "F", blocked_in_blist ? "T" : "F"); */
1068 if (blocked != blocked_in_blist) {
1069 sipe_backend_buddy_set_blocked_status(SIPE_CORE_PUBLIC, buddy_name, blocked);
1073 static void
1074 sipe_refresh_blocked_status(struct sipe_core_private *sipe_private)
1076 g_hash_table_foreach(sipe_private->buddies,
1077 (GHFunc) sipe_refresh_blocked_status_cb,
1078 sipe_private);
1081 static gboolean sipe_process_roaming_contacts(struct sipe_core_private *sipe_private,
1082 struct sipmsg *msg)
1084 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
1085 int len = msg->bodylen;
1087 const gchar *tmp = sipmsg_find_header(msg, "Event");
1088 const sipe_xml *item;
1089 sipe_xml *isc;
1090 const gchar *contacts_delta;
1091 const sipe_xml *group_node;
1092 if (!g_str_has_prefix(tmp, "vnd-microsoft-roaming-contacts")) {
1093 return FALSE;
1096 /* Convert the contact from XML to backend Buddies */
1097 isc = sipe_xml_parse(msg->body, len);
1098 if (!isc) {
1099 return FALSE;
1102 contacts_delta = sipe_xml_attribute(isc, "deltaNum");
1103 if (contacts_delta) {
1104 sip->contacts_delta = (int)g_ascii_strtod(contacts_delta, NULL);
1107 if (sipe_strequal(sipe_xml_name(isc), "contactList")) {
1109 /* Parse groups */
1110 for (group_node = sipe_xml_child(isc, "group"); group_node; group_node = sipe_xml_twin(group_node)) {
1111 struct sipe_group * group = g_new0(struct sipe_group, 1);
1112 const char *name = sipe_xml_attribute(group_node, "name");
1114 if (g_str_has_prefix(name, "~")) {
1115 name = _("Other Contacts");
1117 group->name = g_strdup(name);
1118 group->id = (int)g_ascii_strtod(sipe_xml_attribute(group_node, "id"), NULL);
1120 sipe_group_add(sipe_private, group);
1123 // Make sure we have at least one group
1124 if (g_slist_length(sip->groups) == 0) {
1125 sipe_group_create(sipe_private, _("Other Contacts"), NULL);
1128 /* Parse contacts */
1129 for (item = sipe_xml_child(isc, "contact"); item; item = sipe_xml_twin(item)) {
1130 const gchar *uri = sipe_xml_attribute(item, "uri");
1131 const gchar *name = sipe_xml_attribute(item, "name");
1132 gchar *buddy_name;
1133 struct sipe_buddy *buddy = NULL;
1134 gchar *tmp;
1135 gchar **item_groups;
1136 int i = 0;
1138 /* Buddy name must be lower case as we use purple_normalize_nocase() to compare */
1139 tmp = sip_uri_from_name(uri);
1140 buddy_name = g_ascii_strdown(tmp, -1);
1141 g_free(tmp);
1143 /* assign to group Other Contacts if nothing else received */
1144 tmp = g_strdup(sipe_xml_attribute(item, "groups"));
1145 if(is_empty(tmp)) {
1146 struct sipe_group *group = sipe_group_find_by_name(sipe_private, _("Other Contacts"));
1147 g_free(tmp);
1148 tmp = group ? g_strdup_printf("%d", group->id) : g_strdup("1");
1150 item_groups = g_strsplit(tmp, " ", 0);
1151 g_free(tmp);
1153 while (item_groups[i]) {
1154 struct sipe_group *group = sipe_group_find_by_id(sipe_private, g_ascii_strtod(item_groups[i], NULL));
1156 // If couldn't find the right group for this contact, just put them in the first group we have
1157 if (group == NULL && g_slist_length(sip->groups) > 0) {
1158 group = sip->groups->data;
1161 if (group != NULL) {
1162 gchar *b_alias;
1163 sipe_backend_buddy b = sipe_backend_buddy_find(SIPE_CORE_PUBLIC, buddy_name, group->name);
1164 if (!b){
1165 b = sipe_backend_buddy_add(SIPE_CORE_PUBLIC, buddy_name, uri, group->name);
1166 SIPE_DEBUG_INFO("Created new buddy %s with alias %s", buddy_name, uri);
1169 b_alias = sipe_backend_buddy_get_alias(SIPE_CORE_PUBLIC, b);
1170 if (sipe_strcase_equal(uri, b_alias)) {
1171 if (name != NULL && strlen(name) != 0) {
1172 sipe_backend_buddy_set_alias(SIPE_CORE_PUBLIC, b, name);
1174 SIPE_DEBUG_INFO("Replaced buddy %s alias with %s", buddy_name, name);
1177 g_free(b_alias);
1179 if (!buddy) {
1180 buddy = g_new0(struct sipe_buddy, 1);
1181 buddy->name = sipe_backend_buddy_get_name(SIPE_CORE_PUBLIC, b);
1182 g_hash_table_insert(sipe_private->buddies, buddy->name, buddy);
1184 SIPE_DEBUG_INFO("Added SIPE buddy %s", buddy->name);
1187 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
1189 SIPE_DEBUG_INFO("Added buddy %s to group %s", buddy->name, group->name);
1190 } else {
1191 SIPE_DEBUG_INFO("No group found for contact %s! Unable to add to buddy list",
1192 name);
1195 i++;
1196 } // while, contact groups
1197 g_strfreev(item_groups);
1198 g_free(buddy_name);
1200 } // for, contacts
1202 sipe_cleanup_local_blist(sipe_private);
1204 /* Add self-contact if not there yet. 2005 systems. */
1205 /* This will resemble subscription to roaming_self in 2007 systems */
1206 if (!SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
1207 gchar *self_uri = sip_uri_self(sipe_private);
1208 struct sipe_buddy *buddy = g_hash_table_lookup(sipe_private->buddies, self_uri);
1210 if (!buddy) {
1211 buddy = g_new0(struct sipe_buddy, 1);
1212 buddy->name = g_strdup(self_uri);
1213 g_hash_table_insert(sipe_private->buddies, buddy->name, buddy);
1215 g_free(self_uri);
1218 sipe_xml_free(isc);
1220 /* subscribe to buddies */
1221 if (!sip->subscribed_buddies) { //do it once, then count Expire field to schedule resubscribe.
1222 if (sip->batched_support) {
1223 sipe_subscribe_presence_batched(sipe_private, NULL);
1224 } else {
1225 g_hash_table_foreach(sipe_private->buddies,
1226 (GHFunc)sipe_buddy_subscribe_cb,
1227 sipe_private);
1229 sip->subscribed_buddies = TRUE;
1231 /* for 2005 systems schedule contacts' status update
1232 * based on their calendar information
1234 if (!SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
1235 sipe_sched_calendar_status_update(sipe_private, time(NULL));
1238 return 0;
1242 * Fires on deregistration event initiated by server.
1243 * [MS-SIPREGE] SIP extension.
1246 // 2007 Example
1248 // Content-Type: text/registration-event
1249 // subscription-state: terminated;expires=0
1250 // ms-diagnostics-public: 4141;reason="User disabled"
1252 // deregistered;event=rejected
1254 static void sipe_process_registration_notify(struct sipe_core_private *sipe_private,
1255 struct sipmsg *msg)
1257 const gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
1258 gchar *event = NULL;
1259 gchar *reason = NULL;
1260 gchar *warning;
1262 SIPE_DEBUG_INFO_NOFORMAT("sipe_process_registration_notify: deregistration received.");
1264 if (!g_ascii_strncasecmp(contenttype, "text/registration-event", 23)) {
1265 event = sipmsg_find_part_of_header(msg->body, "event=", NULL, NULL);
1266 //@TODO have proper parameter extraction _by_name_ func, case insesitive.
1267 event = event ? event : sipmsg_find_part_of_header(msg->body, "event=", ";", NULL);
1268 } else {
1269 SIPE_DEBUG_INFO_NOFORMAT("sipe_process_registration_notify: unknown content type, exiting.");
1270 return;
1273 reason = sipmsg_get_ms_diagnostics_reason(msg);
1274 reason = reason ? reason : sipmsg_get_ms_diagnostics_public_reason(msg);
1275 if (!reason) { // for LCS2005
1276 if (event && sipe_strcase_equal(event, "unregistered")) {
1277 //reason = g_strdup(_("User logged out")); // [MS-OCER]
1278 reason = g_strdup(_("you are already signed in at another location"));
1279 } else if (event && sipe_strcase_equal(event, "rejected")) {
1280 reason = g_strdup(_("user disabled")); // [MS-OCER]
1281 } else if (event && sipe_strcase_equal(event, "deactivated")) {
1282 reason = g_strdup(_("user moved")); // [MS-OCER]
1285 g_free(event);
1286 warning = g_strdup_printf(_("You have been rejected by the server: %s"), reason ? reason : _("no reason given"));
1287 g_free(reason);
1289 sipe_backend_connection_error(SIPE_CORE_PUBLIC,
1290 SIPE_CONNECTION_ERROR_INVALID_USERNAME,
1291 warning);
1292 g_free(warning);
1296 static void sipe_process_provisioning_v2(struct sipe_core_private *sipe_private,
1297 struct sipmsg *msg)
1299 sipe_xml *xn_provision_group_list;
1300 const sipe_xml *node;
1302 xn_provision_group_list = sipe_xml_parse(msg->body, msg->bodylen);
1304 /* provisionGroup */
1305 for (node = sipe_xml_child(xn_provision_group_list, "provisionGroup"); node; node = sipe_xml_twin(node)) {
1306 if (sipe_strequal("ServerConfiguration", sipe_xml_attribute(node, "name"))) {
1307 g_free(sipe_private->focus_factory_uri);
1308 sipe_private->focus_factory_uri = sipe_xml_data(sipe_xml_child(node, "focusFactoryUri"));
1309 SIPE_DEBUG_INFO("sipe_process_provisioning_v2: sipe_private->focus_factory_uri=%s",
1310 sipe_private->focus_factory_uri ? sipe_private->focus_factory_uri : "");
1312 #ifdef HAVE_VV
1313 g_free(sipe_private->mras_uri);
1314 sipe_private->mras_uri = g_strstrip(sipe_xml_data(sipe_xml_child(node, "mrasUri")));
1315 SIPE_DEBUG_INFO("sipe_process_provisioning_v2: sipe_private->mras_uri=%s",
1316 sipe_private->mras_uri ? sipe_private->mras_uri : "");
1318 if (sipe_private->mras_uri)
1319 sipe_media_get_av_edge_credentials(sipe_private);
1320 #endif
1321 break;
1324 sipe_xml_free(xn_provision_group_list);
1327 /** for 2005 system */
1328 static void
1329 sipe_process_provisioning(struct sipe_core_private *sipe_private,
1330 struct sipmsg *msg)
1332 sipe_xml *xn_provision;
1333 const sipe_xml *node;
1335 xn_provision = sipe_xml_parse(msg->body, msg->bodylen);
1336 if ((node = sipe_xml_child(xn_provision, "user"))) {
1337 SIPE_DEBUG_INFO("sipe_process_provisioning: uri=%s", sipe_xml_attribute(node, "uri"));
1338 if ((node = sipe_xml_child(node, "line"))) {
1339 const gchar *line_uri = sipe_xml_attribute(node, "uri");
1340 const gchar *server = sipe_xml_attribute(node, "server");
1341 SIPE_DEBUG_INFO("sipe_process_provisioning: line_uri=%s server=%s", line_uri, server);
1342 sip_csta_open(sipe_private, line_uri, server);
1345 sipe_xml_free(xn_provision);
1348 static void sipe_process_roaming_acl(struct sipe_core_private *sipe_private,
1349 struct sipmsg *msg)
1351 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
1352 const gchar *contacts_delta;
1353 sipe_xml *xml;
1355 xml = sipe_xml_parse(msg->body, msg->bodylen);
1356 if (!xml)
1358 return;
1361 contacts_delta = sipe_xml_attribute(xml, "deltaNum");
1362 if (contacts_delta)
1364 sip->acl_delta = (int)g_ascii_strtod(contacts_delta, NULL);
1367 sipe_xml_free(xml);
1370 /** MS-PRES container */
1371 struct sipe_container {
1372 guint id;
1373 guint version;
1374 GSList *members;
1376 /** MS-PRES container member */
1377 struct sipe_container_member {
1378 /** user, domain, sameEnterprise, federated, publicCloud; everyone */
1379 gchar *type;
1380 gchar *value;
1383 static void
1384 free_container_member(struct sipe_container_member *member)
1386 if (!member) return;
1388 g_free(member->type);
1389 g_free(member->value);
1390 g_free(member);
1393 static void
1394 free_container(struct sipe_container *container)
1396 GSList *entry;
1398 if (!container) return;
1400 entry = container->members;
1401 while (entry) {
1402 void *data = entry->data;
1403 entry = g_slist_remove(entry, data);
1404 free_container_member((struct sipe_container_member *)data);
1406 g_free(container);
1409 static void
1410 sipe_send_container_members_prepare(const guint container_id,
1411 const guint container_version,
1412 const gchar *action,
1413 const gchar *type,
1414 const gchar *value,
1415 char **container_xmls)
1417 gchar *value_str = value ? g_strdup_printf(" value=\"%s\"", value) : g_strdup("");
1418 gchar *body;
1420 if (!container_xmls) return;
1422 body = g_strdup_printf(
1423 "<container id=\"%d\" version=\"%d\"><member action=\"%s\" type=\"%s\"%s/></container>",
1424 container_id,
1425 container_version,
1426 action,
1427 type,
1428 value_str);
1429 g_free(value_str);
1431 if ((*container_xmls) == NULL) {
1432 *container_xmls = body;
1433 } else {
1434 char *tmp = *container_xmls;
1436 *container_xmls = g_strconcat(*container_xmls, body, NULL);
1437 g_free(tmp);
1438 g_free(body);
1442 static void
1443 sipe_send_set_container_members(struct sipe_core_private *sipe_private,
1444 char *container_xmls)
1446 gchar *self;
1447 gchar *contact;
1448 gchar *hdr;
1449 gchar *body;
1451 if (!container_xmls) return;
1453 self = sip_uri_self(sipe_private);
1454 body = g_strdup_printf(
1455 "<setContainerMembers xmlns=\"http://schemas.microsoft.com/2006/09/sip/container-management\">"
1456 "%s"
1457 "</setContainerMembers>",
1458 container_xmls);
1460 contact = get_contact(sipe_private);
1461 hdr = g_strdup_printf("Contact: %s\r\n"
1462 "Content-Type: application/msrtc-setcontainermembers+xml\r\n", contact);
1463 g_free(contact);
1465 sip_transport_service(sipe_private,
1466 self,
1467 hdr,
1468 body,
1469 NULL);
1471 g_free(hdr);
1472 g_free(body);
1473 g_free(self);
1477 * Finds locally stored MS-PRES container member
1479 static struct sipe_container_member *
1480 sipe_find_container_member(struct sipe_container *container,
1481 const gchar *type,
1482 const gchar *value)
1484 struct sipe_container_member *member;
1485 GSList *entry;
1487 if (container == NULL || type == NULL) {
1488 return NULL;
1491 entry = container->members;
1492 while (entry) {
1493 member = entry->data;
1494 if (sipe_strcase_equal(member->type, type) &&
1495 sipe_strcase_equal(member->value, value))
1497 return member;
1499 entry = entry->next;
1501 return NULL;
1505 * Finds locally stored MS-PRES container by id
1507 static struct sipe_container *
1508 sipe_find_container(struct sipe_core_private *sipe_private,
1509 guint id)
1511 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
1512 struct sipe_container *container;
1513 GSList *entry;
1515 if (sip == NULL) {
1516 return NULL;
1519 entry = sip->containers;
1520 while (entry) {
1521 container = entry->data;
1522 if (id == container->id) {
1523 return container;
1525 entry = entry->next;
1527 return NULL;
1530 static GSList *
1531 sipe_get_access_domains(struct sipe_core_private *sipe_private)
1533 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
1534 struct sipe_container *container;
1535 struct sipe_container_member *member;
1536 GSList *entry;
1537 GSList *entry2;
1538 GSList *res = NULL;
1540 if (!sip) return NULL;
1542 entry = sip->containers;
1543 while (entry) {
1544 container = entry->data;
1546 entry2 = container->members;
1547 while (entry2) {
1548 member = entry2->data;
1549 if (sipe_strcase_equal(member->type, "domain"))
1551 res = slist_insert_unique_sorted(res, g_strdup(member->value), (GCompareFunc)g_ascii_strcasecmp);
1553 entry2 = entry2->next;
1555 entry = entry->next;
1557 return res;
1561 * Returns pointer to domain part in provided Email URL
1563 * @param email an email URL. Example: first.last@hq.company.com
1564 * @return pointer to domain part of email URL. Coresponding example: hq.company.com
1566 * Doesn't allocate memory
1568 static const char *
1569 sipe_get_domain(const char *email)
1571 char *tmp;
1573 if (!email) return NULL;
1575 tmp = strstr(email, "@");
1577 if (tmp && ((tmp+1) < (email + strlen(email)))) {
1578 return tmp+1;
1579 } else {
1580 return NULL;
1585 /* @TODO: replace with binary search for faster access? */
1586 /** source: http://support.microsoft.com/kb/897567 */
1587 static const char * const public_domains [] = {
1588 "aol.com", "icq.com", "love.com", "mac.com", "br.live.com",
1589 "hotmail.co.il", "hotmail.co.jp", "hotmail.co.th", "hotmail.co.uk",
1590 "hotmail.com", "hotmail.com.ar", "hotmail.com.tr", "hotmail.es",
1591 "hotmail.de", "hotmail.fr", "hotmail.it", "live.at", "live.be",
1592 "live.ca", "live.cl", "live.cn", "live.co.in", "live.co.kr",
1593 "live.co.uk", "live.co.za", "live.com", "live.com.ar", "live.com.au",
1594 "live.com.co", "live.com.mx", "live.com.my", "live.com.pe",
1595 "live.com.ph", "live.com.pk", "live.com.pt", "live.com.sg",
1596 "live.com.ve", "live.de", "live.dk", "live.fr", "live.hk", "live.ie",
1597 "live.in", "live.it", "live.jp", "live.nl", "live.no", "live.ph",
1598 "live.ru", "live.se", "livemail.com.br", "livemail.tw",
1599 "messengeruser.com", "msn.com", "passport.com", "sympatico.ca",
1600 "tw.live.com", "webtv.net", "windowslive.com", "windowslive.es",
1601 "yahoo.com",
1602 NULL};
1604 static gboolean
1605 sipe_is_public_domain(const char *domain)
1607 int i = 0;
1608 while (public_domains[i]) {
1609 if (sipe_strcase_equal(public_domains[i], domain)) {
1610 return TRUE;
1612 i++;
1614 return FALSE;
1618 * Access Levels
1619 * 32000 - Blocked
1620 * 400 - Personal
1621 * 300 - Team
1622 * 200 - Company
1623 * 100 - Public
1625 static const char *
1626 sipe_get_access_level_name(int container_id)
1628 switch(container_id) {
1629 case 32000: return _("Blocked");
1630 case 400: return _("Personal");
1631 case 300: return _("Team");
1632 case 200: return _("Company");
1633 case 100: return _("Public");
1635 return _("Unknown");
1638 static const guint containers[] = {32000, 400, 300, 200, 100};
1639 #define CONTAINERS_LEN (sizeof(containers) / sizeof(guint))
1642 static int
1643 sipe_find_member_access_level(struct sipe_core_private *sipe_private,
1644 const gchar *type,
1645 const gchar *value)
1647 unsigned int i = 0;
1648 const gchar *value_mod = value;
1650 if (!type) return -1;
1652 if (sipe_strequal("user", type)) {
1653 value_mod = sipe_get_no_sip_uri(value);
1656 for (i = 0; i < CONTAINERS_LEN; i++) {
1657 struct sipe_container_member *member;
1658 struct sipe_container *container = sipe_find_container(sipe_private, containers[i]);
1659 if (!container) continue;
1661 member = sipe_find_container_member(container, type, value_mod);
1662 if (member) return containers[i];
1665 return -1;
1668 /** Member type: user, domain, sameEnterprise, federated, publicCloud; everyone */
1669 static int
1670 sipe_find_access_level(struct sipe_core_private *sipe_private,
1671 const gchar *type,
1672 const gchar *value,
1673 gboolean *is_group_access)
1675 int container_id = -1;
1677 if (sipe_strequal("user", type)) {
1678 const char *domain;
1679 const char *no_sip_uri = sipe_get_no_sip_uri(value);
1681 container_id = sipe_find_member_access_level(sipe_private, "user", no_sip_uri);
1682 if (container_id >= 0) {
1683 if (is_group_access) *is_group_access = FALSE;
1684 return container_id;
1687 domain = sipe_get_domain(no_sip_uri);
1688 container_id = sipe_find_member_access_level(sipe_private, "domain", domain);
1689 if (container_id >= 0) {
1690 if (is_group_access) *is_group_access = TRUE;
1691 return container_id;
1694 container_id = sipe_find_member_access_level(sipe_private, "sameEnterprise", NULL);
1695 if ((container_id >= 0) && sipe_strcase_equal(sipe_private->public.sip_domain, domain)) {
1696 if (is_group_access) *is_group_access = TRUE;
1697 return container_id;
1700 container_id = sipe_find_member_access_level(sipe_private, "publicCloud", NULL);
1701 if ((container_id >= 0) && sipe_is_public_domain(domain)) {
1702 if (is_group_access) *is_group_access = TRUE;
1703 return container_id;
1706 container_id = sipe_find_member_access_level(sipe_private, "everyone", NULL);
1707 if ((container_id >= 0)) {
1708 if (is_group_access) *is_group_access = TRUE;
1709 return container_id;
1711 } else {
1712 container_id = sipe_find_member_access_level(sipe_private, type, value);
1713 if (is_group_access) *is_group_access = FALSE;
1716 return container_id;
1720 * @param container_id a new access level. If -1 then current access level
1721 * is just removed (I.e. the member is removed from all containers).
1722 * @param type a type of member. E.g. "user", "sameEnterprise", etc.
1723 * @param value a value for member. E.g. SIP URI for "user" member type.
1725 static void
1726 sipe_change_access_level(struct sipe_core_private *sipe_private,
1727 const int container_id,
1728 const gchar *type,
1729 const gchar *value)
1731 unsigned int i;
1732 int current_container_id = -1;
1733 char *container_xmls = NULL;
1735 /* for each container: find/delete */
1736 for (i = 0; i < CONTAINERS_LEN; i++) {
1737 struct sipe_container_member *member;
1738 struct sipe_container *container = sipe_find_container(sipe_private, containers[i]);
1740 if (!container) continue;
1742 member = sipe_find_container_member(container, type, value);
1743 if (member) {
1744 current_container_id = containers[i];
1745 /* delete/publish current access level */
1746 if (container_id < 0 || container_id != current_container_id) {
1747 sipe_send_container_members_prepare(current_container_id, container->version, "remove", type, value, &container_xmls);
1748 /* remove member from our cache, to be able to recalculate AL below */
1749 container->members = g_slist_remove(container->members, member);
1750 current_container_id = -1;
1755 /* recalculate AL below */
1756 current_container_id = sipe_find_access_level(sipe_private, type, value, NULL);
1758 /* assign/publish new access level */
1759 if (container_id != current_container_id && container_id >= 0) {
1760 struct sipe_container *container = sipe_find_container(sipe_private, container_id);
1761 guint version = container ? container->version : 0;
1763 sipe_send_container_members_prepare(container_id, version, "add", type, value, &container_xmls);
1766 if (container_xmls) {
1767 sipe_send_set_container_members(sipe_private, container_xmls);
1769 g_free(container_xmls);
1772 static void
1773 free_publication(struct sipe_publication *publication)
1775 g_free(publication->category);
1776 g_free(publication->cal_event_hash);
1777 g_free(publication->note);
1779 g_free(publication->working_hours_xml_str);
1780 g_free(publication->fb_start_str);
1781 g_free(publication->free_busy_base64);
1783 g_free(publication);
1786 /* key is <category><instance><container> */
1787 static gboolean
1788 sipe_is_our_publication(struct sipe_core_private *sipe_private,
1789 const gchar *key)
1791 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
1792 GSList *entry;
1794 /* filling keys for our publications if not yet cached */
1795 if (!sip->our_publication_keys) {
1796 guint device_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_DEVICE);
1797 guint machine_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_MACHINE);
1798 guint user_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_USER);
1799 guint calendar_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_CALENDAR);
1800 guint cal_oof_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_CALENDAR_OOF);
1801 guint cal_data_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_CALENDAR_DATA);
1802 guint note_oof_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_NOTE_OOF);
1804 SIPE_DEBUG_INFO_NOFORMAT("* Our Publication Instances *");
1805 SIPE_DEBUG_INFO("\tDevice : %u\t0x%08X", device_instance, device_instance);
1806 SIPE_DEBUG_INFO("\tMachine State : %u\t0x%08X", machine_instance, machine_instance);
1807 SIPE_DEBUG_INFO("\tUser Stare : %u\t0x%08X", user_instance, user_instance);
1808 SIPE_DEBUG_INFO("\tCalendar State : %u\t0x%08X", calendar_instance, calendar_instance);
1809 SIPE_DEBUG_INFO("\tCalendar OOF State : %u\t0x%08X", cal_oof_instance, cal_oof_instance);
1810 SIPE_DEBUG_INFO("\tCalendar FreeBusy : %u\t0x%08X", cal_data_instance, cal_data_instance);
1811 SIPE_DEBUG_INFO("\tOOF Note : %u\t0x%08X", note_oof_instance, note_oof_instance);
1812 SIPE_DEBUG_INFO("\tNote : %u", 0);
1813 SIPE_DEBUG_INFO("\tCalendar WorkingHours: %u", 0);
1815 /* device */
1816 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1817 g_strdup_printf("<%s><%u><%u>", "device", device_instance, 2));
1819 /* state:machineState */
1820 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1821 g_strdup_printf("<%s><%u><%u>", "state", machine_instance, 2));
1822 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1823 g_strdup_printf("<%s><%u><%u>", "state", machine_instance, 3));
1825 /* state:userState */
1826 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1827 g_strdup_printf("<%s><%u><%u>", "state", user_instance, 2));
1828 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1829 g_strdup_printf("<%s><%u><%u>", "state", user_instance, 3));
1831 /* state:calendarState */
1832 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1833 g_strdup_printf("<%s><%u><%u>", "state", calendar_instance, 2));
1834 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1835 g_strdup_printf("<%s><%u><%u>", "state", calendar_instance, 3));
1837 /* state:calendarState OOF */
1838 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1839 g_strdup_printf("<%s><%u><%u>", "state", cal_oof_instance, 2));
1840 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1841 g_strdup_printf("<%s><%u><%u>", "state", cal_oof_instance, 3));
1843 /* note */
1844 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1845 g_strdup_printf("<%s><%u><%u>", "note", 0, 200));
1846 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1847 g_strdup_printf("<%s><%u><%u>", "note", 0, 300));
1848 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1849 g_strdup_printf("<%s><%u><%u>", "note", 0, 400));
1851 /* note OOF */
1852 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1853 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 200));
1854 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1855 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 300));
1856 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1857 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 400));
1859 /* calendarData:WorkingHours */
1860 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1861 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 1));
1862 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1863 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 100));
1864 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1865 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 200));
1866 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1867 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 300));
1868 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1869 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 400));
1870 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1871 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 32000));
1873 /* calendarData:FreeBusy */
1874 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1875 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 1));
1876 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1877 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 100));
1878 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1879 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 200));
1880 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1881 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 300));
1882 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1883 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 400));
1884 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1885 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 32000));
1887 //SIPE_DEBUG_INFO("sipe_is_our_publication: sip->our_publication_keys length=%d",
1888 // sip->our_publication_keys ? (int) g_slist_length(sip->our_publication_keys) : -1);
1891 //SIPE_DEBUG_INFO("sipe_is_our_publication: key=%s", key);
1893 entry = sip->our_publication_keys;
1894 while (entry) {
1895 //SIPE_DEBUG_INFO(" sipe_is_our_publication: entry->data=%s", entry->data);
1896 if (sipe_strequal(entry->data, key)) {
1897 return TRUE;
1899 entry = entry->next;
1901 return FALSE;
1904 /** Property names to store in blist.xml */
1905 #define ALIAS_PROP "alias"
1906 #define EMAIL_PROP "email"
1907 #define PHONE_PROP "phone"
1908 #define PHONE_DISPLAY_PROP "phone-display"
1909 #define PHONE_MOBILE_PROP "phone-mobile"
1910 #define PHONE_MOBILE_DISPLAY_PROP "phone-mobile-display"
1911 #define PHONE_HOME_PROP "phone-home"
1912 #define PHONE_HOME_DISPLAY_PROP "phone-home-display"
1913 #define PHONE_OTHER_PROP "phone-other"
1914 #define PHONE_OTHER_DISPLAY_PROP "phone-other-display"
1915 #define PHONE_CUSTOM1_PROP "phone-custom1"
1916 #define PHONE_CUSTOM1_DISPLAY_PROP "phone-custom1-display"
1917 #define SITE_PROP "site"
1918 #define COMPANY_PROP "company"
1919 #define DEPARTMENT_PROP "department"
1920 #define TITLE_PROP "title"
1921 #define OFFICE_PROP "office"
1922 /** implies work address */
1923 #define ADDRESS_STREET_PROP "address-street"
1924 #define ADDRESS_CITY_PROP "address-city"
1925 #define ADDRESS_STATE_PROP "address-state"
1926 #define ADDRESS_ZIPCODE_PROP "address-zipcode"
1927 #define ADDRESS_COUNTRYCODE_PROP "address-country-code"
1930 * Tries to figure out user first and last name
1931 * based on Display Name and email properties.
1933 * Allocates memory - must be g_free()'d
1935 * Examples to parse:
1936 * First Last
1937 * First Last - Company Name
1938 * Last, First
1939 * Last, First M.
1940 * Last, First (C)(STP) (Company)
1941 * first.last@company.com (preprocessed as "first last")
1942 * first.last.company.com@reuters.net (preprocessed as "first last company com")
1944 * Unusable examples:
1945 * user@company.com (preprocessed as "user")
1946 * first.m.last@company.com (preprocessed as "first m last")
1947 * user.company.com@reuters.net (preprocessed as "user company com")
1949 static void
1950 sipe_get_first_last_names(struct sipe_core_private *sipe_private,
1951 const char *uri,
1952 char **first_name,
1953 char **last_name)
1955 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
1956 sipe_backend_buddy p_buddy;
1957 char *display_name;
1958 gchar *email;
1959 const char *first, *last;
1960 char *tmp;
1961 char **parts;
1962 gboolean has_comma = FALSE;
1964 if (!sip || !uri) return;
1966 p_buddy = sipe_backend_buddy_find(SIPE_CORE_PUBLIC, uri, NULL);
1968 if (!p_buddy) return;
1970 display_name = sipe_backend_buddy_get_alias(SIPE_CORE_PUBLIC, p_buddy);
1971 email = sipe_backend_buddy_get_string(SIPE_CORE_PUBLIC, p_buddy, SIPE_BUDDY_INFO_EMAIL);
1973 if (!display_name && !email) return;
1975 /* if no display name, make "first last anything_else" out of email */
1976 if (email && !display_name) {
1977 display_name = g_strndup(email, strstr(email, "@") - email);
1978 display_name = sipe_utils_str_replace((tmp = display_name), ".", " ");
1979 g_free(tmp);
1982 if (display_name) {
1983 has_comma = (strstr(display_name, ",") != NULL);
1984 display_name = sipe_utils_str_replace((tmp = display_name), ", ", " ");
1985 g_free(tmp);
1986 display_name = sipe_utils_str_replace((tmp = display_name), ",", " ");
1987 g_free(tmp);
1990 parts = g_strsplit(display_name, " ", 0);
1992 if (!parts[0] || !parts[1]) {
1993 g_free(email);
1994 g_free(display_name);
1995 g_strfreev(parts);
1996 return;
1999 if (has_comma) {
2000 last = parts[0];
2001 first = parts[1];
2002 } else {
2003 first = parts[0];
2004 last = parts[1];
2007 if (first_name) {
2008 *first_name = g_strstrip(g_strdup(first));
2011 if (last_name) {
2012 *last_name = g_strstrip(g_strdup(last));
2015 g_free(email);
2016 g_free(display_name);
2017 g_strfreev(parts);
2021 * Update user information
2023 * @param uri buddy SIP URI with 'sip:' prefix whose info we want to change.
2024 * @param property_name
2025 * @param property_value may be modified to strip white space
2027 static void
2028 sipe_update_user_info(struct sipe_core_private *sipe_private,
2029 const char *uri,
2030 sipe_buddy_info_fields propkey,
2031 char *property_value)
2033 GSList *buddies, *entry;
2035 if (property_value)
2036 property_value = g_strstrip(property_value);
2038 entry = buddies = sipe_backend_buddy_find_all(SIPE_CORE_PUBLIC, uri, NULL); /* all buddies in different groups */
2039 while (entry) {
2040 gchar *prop_str;
2041 gchar *server_alias;
2042 gchar *alias;
2043 sipe_backend_buddy p_buddy = entry->data;
2045 /* for Display Name */
2046 if (propkey == SIPE_BUDDY_INFO_DISPLAY_NAME) {
2047 alias = sipe_backend_buddy_get_alias(SIPE_CORE_PUBLIC, p_buddy);
2048 if (property_value && sipe_is_bad_alias(uri, alias)) {
2049 SIPE_DEBUG_INFO("Replacing alias for %s with %s", uri, property_value);
2050 sipe_backend_buddy_set_alias(SIPE_CORE_PUBLIC, p_buddy, property_value);
2052 g_free(alias);
2054 server_alias = sipe_backend_buddy_get_server_alias(SIPE_CORE_PUBLIC, p_buddy);
2055 if (!is_empty(property_value) &&
2056 (!sipe_strequal(property_value, server_alias) || is_empty(server_alias)) )
2058 SIPE_DEBUG_INFO("Replacing service alias for %s with %s", uri, property_value);
2059 sipe_backend_buddy_set_server_alias(SIPE_CORE_PUBLIC, p_buddy, property_value);
2061 g_free(server_alias);
2063 /* for other properties */
2064 else {
2065 if (!is_empty(property_value)) {
2066 prop_str = sipe_backend_buddy_get_string(SIPE_CORE_PUBLIC, p_buddy, propkey);
2067 if (!prop_str || !sipe_strcase_equal(prop_str, property_value)) {
2068 sipe_backend_buddy_set_string(SIPE_CORE_PUBLIC, p_buddy, propkey, property_value);
2070 g_free(prop_str);
2074 entry = entry->next;
2076 g_slist_free(buddies);
2080 * Update user phone
2081 * Suitable for both 2005 and 2007 systems.
2083 * @param uri buddy SIP URI with 'sip:' prefix whose info we want to change.
2084 * @param phone_type
2085 * @param phone may be modified to strip white space
2086 * @param phone_display_string may be modified to strip white space
2088 static void
2089 sipe_update_user_phone(struct sipe_core_private *sipe_private,
2090 const gchar *uri,
2091 const gchar *phone_type,
2092 gchar *phone,
2093 gchar *phone_display_string)
2095 sipe_buddy_info_fields phone_node = SIPE_BUDDY_INFO_WORK_PHONE; /* work phone by default */
2096 sipe_buddy_info_fields phone_display_node = SIPE_BUDDY_INFO_WORK_PHONE_DISPLAY; /* work phone by default */
2098 if(!phone || strlen(phone) == 0) return;
2100 if ((sipe_strequal(phone_type, "mobile") || sipe_strequal(phone_type, "cell"))) {
2101 phone_node = SIPE_BUDDY_INFO_MOBILE_PHONE;
2102 phone_display_node = SIPE_BUDDY_INFO_MOBILE_PHONE_DISPLAY;
2103 } else if (sipe_strequal(phone_type, "home")) {
2104 phone_node = SIPE_BUDDY_INFO_HOME_PHONE;
2105 phone_display_node = SIPE_BUDDY_INFO_HOME_PHONE_DISPLAY;
2106 } else if (sipe_strequal(phone_type, "other")) {
2107 phone_node = SIPE_BUDDY_INFO_OTHER_PHONE;
2108 phone_display_node = SIPE_BUDDY_INFO_OTHER_PHONE_DISPLAY;
2109 } else if (sipe_strequal(phone_type, "custom1")) {
2110 phone_node = SIPE_BUDDY_INFO_CUSTOM1_PHONE;
2111 phone_display_node = SIPE_BUDDY_INFO_CUSTOM1_PHONE_DISPLAY;
2114 sipe_update_user_info(sipe_private, uri, phone_node, phone);
2115 if (phone_display_string) {
2116 sipe_update_user_info(sipe_private, uri, phone_display_node, phone_display_string);
2120 void
2121 sipe_core_update_calendar(struct sipe_core_public *sipe_public)
2123 SIPE_DEBUG_INFO_NOFORMAT("sipe_core_update_calendar: started.");
2125 /* Do in parallel.
2126 * If failed, the branch will be disabled for subsequent calls.
2127 * Can't rely that user turned the functionality on in account settings.
2129 sipe_ews_update_calendar(SIPE_CORE_PRIVATE);
2130 sipe_domino_update_calendar(SIPE_CORE_PRIVATE);
2132 /* schedule repeat */
2133 sipe_schedule_seconds(SIPE_CORE_PRIVATE,
2134 "<+update-calendar>",
2135 NULL,
2136 UPDATE_CALENDAR_INTERVAL,
2137 (sipe_schedule_action)sipe_core_update_calendar,
2138 NULL);
2140 SIPE_DEBUG_INFO_NOFORMAT("sipe_core_update_calendar: finished.");
2144 * This method motivates Purple's Host (e.g. Pidgin) to update its UI
2145 * by using standard Purple's means of signals and saved statuses.
2147 * Thus all UI elements get updated: Status Button with Note, docklet.
2148 * This is ablolutely important as both our status and note can come
2149 * inbound (roaming) or be updated programmatically (e.g. based on our
2150 * calendar data).
2152 static void
2153 sipe_set_purple_account_status_and_note(const PurpleAccount *account,
2154 const char *status_id,
2155 const char *message,
2156 time_t do_not_publish[])
2158 PurpleStatus *status = purple_account_get_active_status(account);
2159 gboolean changed = TRUE;
2161 if (g_str_equal(status_id, purple_status_get_id(status)) &&
2162 sipe_strequal(message, purple_status_get_attr_string(status, SIPE_STATUS_ATTR_ID_MESSAGE)))
2164 changed = FALSE;
2167 if (purple_savedstatus_is_idleaway()) {
2168 changed = FALSE;
2171 if (changed) {
2172 PurpleSavedStatus *saved_status;
2173 const PurpleStatusType *acct_status_type =
2174 purple_status_type_find_with_id(account->status_types, status_id);
2175 PurpleStatusPrimitive primitive = purple_status_type_get_primitive(acct_status_type);
2176 sipe_activity activity = sipe_get_activity_by_token(status_id);
2178 saved_status = purple_savedstatus_find_transient_by_type_and_message(primitive, message);
2179 if (saved_status) {
2180 purple_savedstatus_set_substatus(saved_status, account, acct_status_type, message);
2183 /* If this type+message is unique then create a new transient saved status
2184 * Ref: gtkstatusbox.c
2186 if (!saved_status) {
2187 GList *tmp;
2188 GList *active_accts = purple_accounts_get_all_active();
2190 saved_status = purple_savedstatus_new(NULL, primitive);
2191 purple_savedstatus_set_message(saved_status, message);
2193 for (tmp = active_accts; tmp != NULL; tmp = tmp->next) {
2194 purple_savedstatus_set_substatus(saved_status,
2195 (PurpleAccount *)tmp->data, acct_status_type, message);
2197 g_list_free(active_accts);
2200 do_not_publish[activity] = time(NULL);
2201 SIPE_DEBUG_INFO("sipe_set_purple_account_status_and_note: do_not_publish[%s]=%d [now]",
2202 status_id, (int)do_not_publish[activity]);
2204 /* Set the status for each account */
2205 purple_savedstatus_activate(saved_status);
2209 struct hash_table_delete_payload {
2210 GHashTable *hash_table;
2211 guint container;
2214 static void
2215 sipe_remove_category_container_publications_cb(const char *name,
2216 struct sipe_publication *publication,
2217 struct hash_table_delete_payload *payload)
2219 if (publication->container == payload->container) {
2220 g_hash_table_remove(payload->hash_table, name);
2223 static void
2224 sipe_remove_category_container_publications(GHashTable *our_publications,
2225 const char *category,
2226 guint container)
2228 struct hash_table_delete_payload payload;
2229 payload.hash_table = g_hash_table_lookup(our_publications, category);
2231 if (!payload.hash_table) return;
2233 payload.container = container;
2234 g_hash_table_foreach(payload.hash_table, (GHFunc)sipe_remove_category_container_publications_cb, &payload);
2237 static void
2238 send_publish_category_initial(struct sipe_core_private *sipe_private);
2241 * When we receive some self (BE) NOTIFY with a new subscriber
2242 * we sends a setSubscribers request to him [SIP-PRES] 4.8
2245 static void sipe_process_roaming_self(struct sipe_core_private *sipe_private,
2246 struct sipmsg *msg)
2248 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
2249 gchar *contact;
2250 gchar *to;
2251 sipe_xml *xml;
2252 const sipe_xml *node;
2253 const sipe_xml *node2;
2254 char *display_name = NULL;
2255 char *uri;
2256 GSList *category_names = NULL;
2257 int aggreg_avail = 0;
2258 gboolean do_update_status = FALSE;
2259 gboolean has_note_cleaned = FALSE;
2260 GHashTable *devices;
2262 SIPE_DEBUG_INFO_NOFORMAT("sipe_process_roaming_self");
2264 xml = sipe_xml_parse(msg->body, msg->bodylen);
2265 if (!xml) return;
2267 contact = get_contact(sipe_private);
2268 to = sip_uri_self(sipe_private);
2270 /* categories */
2271 /* set list of categories participating in this XML */
2272 for (node = sipe_xml_child(xml, "categories/category"); node; node = sipe_xml_twin(node)) {
2273 const gchar *name = sipe_xml_attribute(node, "name");
2274 category_names = slist_insert_unique_sorted(category_names, (gchar *)name, (GCompareFunc)strcmp);
2276 SIPE_DEBUG_INFO("sipe_process_roaming_self: category_names length=%d",
2277 category_names ? (int) g_slist_length(category_names) : -1);
2278 /* drop category information */
2279 if (category_names) {
2280 GSList *entry = category_names;
2281 while (entry) {
2282 GHashTable *cat_publications;
2283 const gchar *category = entry->data;
2284 entry = entry->next;
2285 SIPE_DEBUG_INFO("sipe_process_roaming_self: dropping category: %s", category);
2286 cat_publications = g_hash_table_lookup(sip->our_publications, category);
2287 if (cat_publications) {
2288 g_hash_table_remove(sip->our_publications, category);
2289 SIPE_DEBUG_INFO("sipe_process_roaming_self: dropped category: %s", category);
2293 g_slist_free(category_names);
2295 /* filling our categories reflected in roaming data */
2296 devices = g_hash_table_new_full(g_str_hash, g_str_equal,
2297 g_free, NULL);
2298 for (node = sipe_xml_child(xml, "categories/category"); node; node = sipe_xml_twin(node)) {
2299 const char *tmp;
2300 const gchar *name = sipe_xml_attribute(node, "name");
2301 guint container = sipe_xml_int_attribute(node, "container", -1);
2302 guint instance = sipe_xml_int_attribute(node, "instance", -1);
2303 guint version = sipe_xml_int_attribute(node, "version", 0);
2304 time_t publish_time = (tmp = sipe_xml_attribute(node, "publishTime")) ?
2305 sipe_utils_str_to_time(tmp) : 0;
2306 gchar *key;
2307 GHashTable *cat_publications = g_hash_table_lookup(sip->our_publications, name);
2309 /* Ex. clear note: <category name="note"/> */
2310 if (container == (guint)-1) {
2311 g_free(sip->note);
2312 sip->note = NULL;
2313 do_update_status = TRUE;
2314 continue;
2317 /* Ex. clear note: <category name="note" container="200"/> */
2318 if (instance == (guint)-1) {
2319 if (container == 200) {
2320 g_free(sip->note);
2321 sip->note = NULL;
2322 do_update_status = TRUE;
2324 SIPE_DEBUG_INFO("sipe_process_roaming_self: removing publications for: %s/%u", name, container);
2325 sipe_remove_category_container_publications(
2326 sip->our_publications, name, container);
2327 continue;
2330 /* key is <category><instance><container> */
2331 key = g_strdup_printf("<%s><%u><%u>", name, instance, container);
2332 SIPE_DEBUG_INFO("sipe_process_roaming_self: key=%s version=%d", key, version);
2334 /* capture all userState publication for later clean up if required */
2335 if (sipe_strequal(name, "state") && (container == 2 || container == 3)) {
2336 const sipe_xml *xn_state = sipe_xml_child(node, "state");
2338 if (xn_state && sipe_strequal(sipe_xml_attribute(xn_state, "type"), "userState")) {
2339 struct sipe_publication *publication = g_new0(struct sipe_publication, 1);
2340 publication->category = g_strdup(name);
2341 publication->instance = instance;
2342 publication->container = container;
2343 publication->version = version;
2345 if (!sip->user_state_publications) {
2346 sip->user_state_publications = g_hash_table_new_full(
2347 g_str_hash, g_str_equal,
2348 g_free, (GDestroyNotify)free_publication);
2350 g_hash_table_insert(sip->user_state_publications, g_strdup(key), publication);
2351 SIPE_DEBUG_INFO("sipe_process_roaming_self: added to user_state_publications key=%s version=%d",
2352 key, version);
2356 /* count each client instance only once */
2357 if (sipe_strequal(name, "device"))
2358 g_hash_table_replace(devices, g_strdup_printf("%u", instance), NULL);
2360 if (sipe_is_our_publication(sipe_private, key)) {
2361 struct sipe_publication *publication = g_new0(struct sipe_publication, 1);
2363 publication->category = g_strdup(name);
2364 publication->instance = instance;
2365 publication->container = container;
2366 publication->version = version;
2368 /* filling publication->availability */
2369 if (sipe_strequal(name, "state")) {
2370 const sipe_xml *xn_state = sipe_xml_child(node, "state");
2371 const sipe_xml *xn_avail = sipe_xml_child(xn_state, "availability");
2373 if (xn_avail) {
2374 gchar *avail_str = sipe_xml_data(xn_avail);
2375 if (avail_str) {
2376 publication->availability = atoi(avail_str);
2378 g_free(avail_str);
2380 /* for calendarState */
2381 if (xn_state && sipe_strequal(sipe_xml_attribute(xn_state, "type"), "calendarState")) {
2382 const sipe_xml *xn_activity = sipe_xml_child(xn_state, "activity");
2383 struct sipe_cal_event *event = g_new0(struct sipe_cal_event, 1);
2385 event->start_time = sipe_utils_str_to_time(sipe_xml_attribute(xn_state, "startTime"));
2386 if (xn_activity) {
2387 if (sipe_strequal(sipe_xml_attribute(xn_activity, "token"),
2388 sipe_activity_map[SIPE_ACTIVITY_IN_MEETING].token))
2390 event->is_meeting = TRUE;
2393 event->subject = sipe_xml_data(sipe_xml_child(xn_state, "meetingSubject"));
2394 event->location = sipe_xml_data(sipe_xml_child(xn_state, "meetingLocation"));
2396 publication->cal_event_hash = sipe_cal_event_hash(event);
2397 SIPE_DEBUG_INFO("sipe_process_roaming_self: hash=%s",
2398 publication->cal_event_hash);
2399 sipe_cal_event_free(event);
2402 /* filling publication->note */
2403 if (sipe_strequal(name, "note")) {
2404 const sipe_xml *xn_body = sipe_xml_child(node, "note/body");
2406 if (!has_note_cleaned) {
2407 has_note_cleaned = TRUE;
2409 g_free(sip->note);
2410 sip->note = NULL;
2411 sip->note_since = publish_time;
2413 do_update_status = TRUE;
2416 g_free(publication->note);
2417 publication->note = NULL;
2418 if (xn_body) {
2419 char *tmp;
2421 publication->note = g_markup_escape_text((tmp = sipe_xml_data(xn_body)), -1);
2422 g_free(tmp);
2423 if (publish_time >= sip->note_since) {
2424 g_free(sip->note);
2425 sip->note = g_strdup(publication->note);
2426 sip->note_since = publish_time;
2427 sip->is_oof_note = sipe_strequal(sipe_xml_attribute(xn_body, "type"), "OOF");
2429 do_update_status = TRUE;
2434 /* filling publication->fb_start_str, free_busy_base64, working_hours_xml_str */
2435 if (sipe_strequal(name, "calendarData") && (publication->container == 300)) {
2436 const sipe_xml *xn_free_busy = sipe_xml_child(node, "calendarData/freeBusy");
2437 const sipe_xml *xn_working_hours = sipe_xml_child(node, "calendarData/WorkingHours");
2438 if (xn_free_busy) {
2439 publication->fb_start_str = g_strdup(sipe_xml_attribute(xn_free_busy, "startTime"));
2440 publication->free_busy_base64 = sipe_xml_data(xn_free_busy);
2442 if (xn_working_hours) {
2443 publication->working_hours_xml_str = sipe_xml_stringify(xn_working_hours);
2447 if (!cat_publications) {
2448 cat_publications = g_hash_table_new_full(
2449 g_str_hash, g_str_equal,
2450 g_free, (GDestroyNotify)free_publication);
2451 g_hash_table_insert(sip->our_publications, g_strdup(name), cat_publications);
2452 SIPE_DEBUG_INFO("sipe_process_roaming_self: added GHashTable cat=%s", name);
2454 g_hash_table_insert(cat_publications, g_strdup(key), publication);
2455 SIPE_DEBUG_INFO("sipe_process_roaming_self: added key=%s version=%d", key, version);
2457 g_free(key);
2459 /* aggregateState (not an our publication) from 2-nd container */
2460 if (sipe_strequal(name, "state") && container == 2) {
2461 const sipe_xml *xn_state = sipe_xml_child(node, "state");
2463 if (xn_state && sipe_strequal(sipe_xml_attribute(xn_state, "type"), "aggregateState")) {
2464 const sipe_xml *xn_avail = sipe_xml_child(xn_state, "availability");
2466 if (xn_avail) {
2467 gchar *avail_str = sipe_xml_data(xn_avail);
2468 if (avail_str) {
2469 aggreg_avail = atoi(avail_str);
2471 g_free(avail_str);
2474 do_update_status = TRUE;
2478 /* userProperties published by server from AD */
2479 if (!sip->csta && sipe_strequal(name, "userProperties")) {
2480 const sipe_xml *line;
2481 /* line, for Remote Call Control (RCC) */
2482 for (line = sipe_xml_child(node, "userProperties/lines/line"); line; line = sipe_xml_twin(line)) {
2483 const gchar *line_server = sipe_xml_attribute(line, "lineServer");
2484 const gchar *line_type = sipe_xml_attribute(line, "lineType");
2485 gchar *line_uri;
2487 if (!line_server || !(sipe_strequal(line_type, "Rcc") || sipe_strequal(line_type, "Dual"))) continue;
2489 line_uri = sipe_xml_data(line);
2490 if (line_uri) {
2491 SIPE_DEBUG_INFO("sipe_process_roaming_self: line_uri=%s server=%s", line_uri, line_server);
2492 sip_csta_open(sipe_private, line_uri, line_server);
2494 g_free(line_uri);
2496 break;
2500 SIPE_DEBUG_INFO("sipe_process_roaming_self: sip->our_publications size=%d",
2501 sip->our_publications ? (int) g_hash_table_size(sip->our_publications) : -1);
2503 /* active clients for user account */
2504 if (g_hash_table_size(devices) > 1) {
2505 SIPE_CORE_PRIVATE_FLAG_SET(MPOP);
2506 SIPE_DEBUG_INFO("sipe_process_roaming_self: multiple clients detected (%d)",
2507 g_hash_table_size(devices));
2508 } else {
2509 SIPE_CORE_PRIVATE_FLAG_UNSET(MPOP);
2510 SIPE_DEBUG_INFO_NOFORMAT("sipe_process_roaming_self: single client detected");
2512 g_hash_table_destroy(devices);
2514 /* containers */
2515 for (node = sipe_xml_child(xml, "containers/container"); node; node = sipe_xml_twin(node)) {
2516 guint id = sipe_xml_int_attribute(node, "id", 0);
2517 struct sipe_container *container = sipe_find_container(sipe_private, id);
2519 if (container) {
2520 sip->containers = g_slist_remove(sip->containers, container);
2521 SIPE_DEBUG_INFO("sipe_process_roaming_self: removed existing container id=%d v%d", container->id, container->version);
2522 free_container(container);
2524 container = g_new0(struct sipe_container, 1);
2525 container->id = id;
2526 container->version = sipe_xml_int_attribute(node, "version", 0);
2527 sip->containers = g_slist_append(sip->containers, container);
2528 SIPE_DEBUG_INFO("sipe_process_roaming_self: added container id=%d v%d", container->id, container->version);
2530 for (node2 = sipe_xml_child(node, "member"); node2; node2 = sipe_xml_twin(node2)) {
2531 struct sipe_container_member *member = g_new0(struct sipe_container_member, 1);
2532 member->type = g_strdup(sipe_xml_attribute(node2, "type"));
2533 member->value = g_strdup(sipe_xml_attribute(node2, "value"));
2534 container->members = g_slist_append(container->members, member);
2535 SIPE_DEBUG_INFO("sipe_process_roaming_self: added container member type=%s value=%s",
2536 member->type, member->value ? member->value : "");
2540 SIPE_DEBUG_INFO("sipe_process_roaming_self: sip->access_level_set=%s", sip->access_level_set ? "TRUE" : "FALSE");
2541 if (!sip->access_level_set && sipe_xml_child(xml, "containers")) {
2542 char *container_xmls = NULL;
2543 int sameEnterpriseAL = sipe_find_access_level(sipe_private, "sameEnterprise", NULL, NULL);
2544 int federatedAL = sipe_find_access_level(sipe_private, "federated", NULL, NULL);
2546 SIPE_DEBUG_INFO("sipe_process_roaming_self: sameEnterpriseAL=%d", sameEnterpriseAL);
2547 SIPE_DEBUG_INFO("sipe_process_roaming_self: federatedAL=%d", federatedAL);
2548 /* initial set-up to let counterparties see your status */
2549 if (sameEnterpriseAL < 0) {
2550 struct sipe_container *container = sipe_find_container(sipe_private, 200);
2551 guint version = container ? container->version : 0;
2552 sipe_send_container_members_prepare(200, version, "add", "sameEnterprise", NULL, &container_xmls);
2554 if (federatedAL < 0) {
2555 struct sipe_container *container = sipe_find_container(sipe_private, 100);
2556 guint version = container ? container->version : 0;
2557 sipe_send_container_members_prepare(100, version, "add", "federated", NULL, &container_xmls);
2559 sip->access_level_set = TRUE;
2561 if (container_xmls) {
2562 sipe_send_set_container_members(sipe_private, container_xmls);
2564 g_free(container_xmls);
2567 /* Refresh contacts' blocked status */
2568 sipe_refresh_blocked_status(sipe_private);
2570 /* subscribers */
2571 for (node = sipe_xml_child(xml, "subscribers/subscriber"); node; node = sipe_xml_twin(node)) {
2572 const char *user;
2573 const char *acknowledged;
2574 gchar *hdr;
2575 gchar *body;
2577 user = sipe_xml_attribute(node, "user"); /* without 'sip:' prefix */
2578 if (!user) continue;
2579 SIPE_DEBUG_INFO("sipe_process_roaming_self: user %s", user);
2580 display_name = g_strdup(sipe_xml_attribute(node, "displayName"));
2581 uri = sip_uri_from_name(user);
2583 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_DISPLAY_NAME, display_name);
2585 acknowledged= sipe_xml_attribute(node, "acknowledged");
2586 if(sipe_strcase_equal(acknowledged,"false")){
2587 SIPE_DEBUG_INFO("sipe_process_roaming_self: user added you %s", user);
2588 if (!sipe_backend_buddy_find(SIPE_CORE_PUBLIC, uri, NULL)) {
2589 sipe_backend_buddy_request_add(SIPE_CORE_PUBLIC, uri, display_name);
2592 hdr = g_strdup_printf(
2593 "Contact: %s\r\n"
2594 "Content-Type: application/msrtc-presence-setsubscriber+xml\r\n", contact);
2596 body = g_strdup_printf(
2597 "<setSubscribers xmlns=\"http://schemas.microsoft.com/2006/09/sip/presence-subscribers\">"
2598 "<subscriber user=\"%s\" acknowledged=\"true\"/>"
2599 "</setSubscribers>", user);
2601 sip_transport_service(sipe_private,
2603 hdr,
2604 body,
2605 NULL);
2606 g_free(body);
2607 g_free(hdr);
2609 g_free(display_name);
2610 g_free(uri);
2613 g_free(contact);
2614 sipe_xml_free(xml);
2616 /* Publish initial state if not yet.
2617 * Assuming this happens on initial responce to subscription to roaming-self
2618 * so we've already updated our roaming data in full.
2619 * Only for 2007+
2621 if (!sip->initial_state_published) {
2622 send_publish_category_initial(sipe_private);
2623 sipe_groupchat_init(sipe_private);
2624 sip->initial_state_published = TRUE;
2625 /* dalayed run */
2626 sipe_schedule_seconds(sipe_private,
2627 "<+update-calendar>",
2628 NULL,
2629 UPDATE_CALENDAR_DELAY,
2630 (sipe_schedule_action)sipe_core_update_calendar,
2631 NULL);
2632 do_update_status = FALSE;
2633 } else if (aggreg_avail) {
2635 g_free(sip->status);
2636 if (aggreg_avail && aggreg_avail < 18000) { /* not offline */
2637 sip->status = g_strdup(sipe_get_status_by_availability(aggreg_avail, NULL));
2638 } else {
2639 sip->status = g_strdup(SIPE_STATUS_ID_INVISIBLE); /* not not let offline status switch us off */
2643 if (do_update_status) {
2644 SIPE_DEBUG_INFO("sipe_process_roaming_self: switch to '%s' for the account", sip->status);
2645 sipe_set_purple_account_status_and_note(sip->account, sip->status, sip->note, sip->do_not_publish);
2648 g_free(to);
2651 /* IM Session (INVITE and MESSAGE methods) */
2653 static gboolean
2654 process_options_response(SIPE_UNUSED_PARAMETER struct sipe_core_private *sipe_private,
2655 struct sipmsg *msg,
2656 SIPE_UNUSED_PARAMETER struct transaction *trans)
2658 gboolean ret = TRUE;
2660 if (msg->response != 200) {
2661 SIPE_DEBUG_INFO("process_options_response: OPTIONS response is %d", msg->response);
2662 return FALSE;
2665 SIPE_DEBUG_INFO("process_options_response: body:\n%s", msg->body ? msg->body : "");
2667 return ret;
2671 * Asks UA/proxy about its capabilities.
2673 static void sipe_options_request(struct sipe_core_private *sipe_private,
2674 const char *who)
2676 gchar *to = sip_uri(who);
2677 gchar *contact = get_contact(sipe_private);
2678 gchar *request = g_strdup_printf(
2679 "Accept: application/sdp\r\n"
2680 "Contact: %s\r\n", contact);
2681 g_free(contact);
2683 sip_transport_request(sipe_private,
2684 "OPTIONS",
2687 request,
2688 NULL,
2689 NULL,
2690 process_options_response);
2692 g_free(to);
2693 g_free(request);
2696 void
2697 sipe_convo_closed(PurpleConnection * gc, const char *who)
2699 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
2701 SIPE_DEBUG_INFO("conversation with %s closed", who);
2702 sipe_session_close(sipe_private,
2703 sipe_session_find_im(sipe_private, who));
2707 * Returns 2005-style activity and Availability.
2709 * @param status Sipe statis id.
2711 static void
2712 sipe_get_act_avail_by_status_2005(const char *status,
2713 int *activity,
2714 int *availability)
2716 int avail = 300; /* online */
2717 int act = 400; /* Available */
2719 if (sipe_strequal(status, SIPE_STATUS_ID_AWAY)) {
2720 act = 100;
2721 //} else if (sipe_strequal(status, SIPE_STATUS_ID_LUNCH)) {
2722 // act = 150;
2723 } else if (sipe_strequal(status, SIPE_STATUS_ID_BRB)) {
2724 act = 300;
2725 } else if (sipe_strequal(status, SIPE_STATUS_ID_AVAILABLE)) {
2726 act = 400;
2727 //} else if (sipe_strequal(status, SIPE_STATUS_ID_ON_PHONE)) {
2728 // act = 500;
2729 } else if (sipe_strequal(status, SIPE_STATUS_ID_BUSY) ||
2730 sipe_strequal(status, SIPE_STATUS_ID_DND)) {
2731 act = 600;
2732 } else if (sipe_strequal(status, SIPE_STATUS_ID_INVISIBLE) ||
2733 sipe_strequal(status, SIPE_STATUS_ID_OFFLINE)) {
2734 avail = 0; /* offline */
2735 act = 100;
2736 } else {
2737 act = 400; /* Available */
2740 if (activity) *activity = act;
2741 if (availability) *availability = avail;
2745 * [MS-SIP] 2.2.1
2747 * @param activity 2005 aggregated activity. Ex.: 600
2748 * @param availablity 2005 aggregated availablity. Ex.: 300
2750 static const char *
2751 sipe_get_status_by_act_avail_2005(const int activity,
2752 const int availablity,
2753 char **activity_desc)
2755 const char *status_id = NULL;
2756 const char *act = NULL;
2758 if (activity < 150) {
2759 status_id = SIPE_STATUS_ID_AWAY;
2760 } else if (activity < 200) {
2761 //status_id = SIPE_STATUS_ID_LUNCH;
2762 status_id = SIPE_STATUS_ID_AWAY;
2763 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_LUNCH);
2764 } else if (activity < 300) {
2765 //status_id = SIPE_STATUS_ID_IDLE;
2766 status_id = SIPE_STATUS_ID_AWAY;
2767 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_INACTIVE);
2768 } else if (activity < 400) {
2769 status_id = SIPE_STATUS_ID_BRB;
2770 } else if (activity < 500) {
2771 status_id = SIPE_STATUS_ID_AVAILABLE;
2772 } else if (activity < 600) {
2773 //status_id = SIPE_STATUS_ID_ON_PHONE;
2774 status_id = SIPE_STATUS_ID_BUSY;
2775 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_ON_PHONE);
2776 } else if (activity < 700) {
2777 status_id = SIPE_STATUS_ID_BUSY;
2778 } else if (activity < 800) {
2779 status_id = SIPE_STATUS_ID_AWAY;
2780 } else {
2781 status_id = SIPE_STATUS_ID_AVAILABLE;
2784 if (availablity < 100)
2785 status_id = SIPE_STATUS_ID_OFFLINE;
2787 if (activity_desc && act) {
2788 g_free(*activity_desc);
2789 *activity_desc = g_strdup(act);
2792 return status_id;
2796 * [MS-PRES] Table 3: Conversion of legacyInterop elements and attributes to MSRTC elements and attributes.
2798 static const char*
2799 sipe_get_status_by_availability(int avail,
2800 char** activity_desc)
2802 const char *status;
2803 const char *act = NULL;
2805 if (avail < 3000) {
2806 status = SIPE_STATUS_ID_OFFLINE;
2807 } else if (avail < 4500) {
2808 status = SIPE_STATUS_ID_AVAILABLE;
2809 } else if (avail < 6000) {
2810 //status = SIPE_STATUS_ID_IDLE;
2811 status = SIPE_STATUS_ID_AVAILABLE;
2812 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_INACTIVE);
2813 } else if (avail < 7500) {
2814 status = SIPE_STATUS_ID_BUSY;
2815 } else if (avail < 9000) {
2816 //status = SIPE_STATUS_ID_BUSYIDLE;
2817 status = SIPE_STATUS_ID_BUSY;
2818 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_BUSYIDLE);
2819 } else if (avail < 12000) {
2820 status = SIPE_STATUS_ID_DND;
2821 } else if (avail < 15000) {
2822 status = SIPE_STATUS_ID_BRB;
2823 } else if (avail < 18000) {
2824 status = SIPE_STATUS_ID_AWAY;
2825 } else {
2826 status = SIPE_STATUS_ID_OFFLINE;
2829 if (activity_desc && act) {
2830 g_free(*activity_desc);
2831 *activity_desc = g_strdup(act);
2834 return status;
2838 * Returns 2007-style availability value
2840 * @param sipe_status_id (in)
2841 * @param activity_token (out) Must be g_free()'d after use if consumed.
2843 static int
2844 sipe_get_availability_by_status(const char* sipe_status_id, char** activity_token)
2846 int availability;
2847 sipe_activity activity = SIPE_ACTIVITY_UNSET;
2849 if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_AWAY)) {
2850 availability = 15500;
2851 if (!activity_token || !(*activity_token)) {
2852 activity = SIPE_ACTIVITY_AWAY;
2854 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_BRB)) {
2855 availability = 12500;
2856 activity = SIPE_ACTIVITY_BRB;
2857 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_DND)) {
2858 availability = 9500;
2859 activity = SIPE_ACTIVITY_DND;
2860 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_BUSY)) {
2861 availability = 6500;
2862 if (!activity_token || !(*activity_token)) {
2863 activity = SIPE_ACTIVITY_BUSY;
2865 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_AVAILABLE)) {
2866 availability = 3500;
2867 activity = SIPE_ACTIVITY_ONLINE;
2868 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_UNKNOWN)) {
2869 availability = 0;
2870 } else {
2871 // Offline or invisible
2872 availability = 18500;
2873 activity = SIPE_ACTIVITY_OFFLINE;
2876 if (activity_token) {
2877 *activity_token = g_strdup(sipe_activity_map[activity].token);
2879 return availability;
2882 static void process_incoming_notify_rlmi(struct sipe_core_private *sipe_private,
2883 const gchar *data, unsigned len)
2885 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
2886 const char *uri;
2887 sipe_xml *xn_categories;
2888 const sipe_xml *xn_category;
2889 const char *status = NULL;
2890 gboolean do_update_status = FALSE;
2891 gboolean has_note_cleaned = FALSE;
2892 gboolean has_free_busy_cleaned = FALSE;
2894 xn_categories = sipe_xml_parse(data, len);
2895 uri = sipe_xml_attribute(xn_categories, "uri"); /* with 'sip:' prefix */
2897 for (xn_category = sipe_xml_child(xn_categories, "category");
2898 xn_category ;
2899 xn_category = sipe_xml_twin(xn_category) )
2901 const sipe_xml *xn_node;
2902 const char *tmp;
2903 const char *attrVar = sipe_xml_attribute(xn_category, "name");
2904 time_t publish_time = (tmp = sipe_xml_attribute(xn_category, "publishTime")) ?
2905 sipe_utils_str_to_time(tmp) : 0;
2907 /* contactCard */
2908 if (sipe_strequal(attrVar, "contactCard"))
2910 const sipe_xml *card = sipe_xml_child(xn_category, "contactCard");
2912 if (card) {
2913 const sipe_xml *node;
2914 /* identity - Display Name and email */
2915 node = sipe_xml_child(card, "identity");
2916 if (node) {
2917 char* display_name = sipe_xml_data(
2918 sipe_xml_child(node, "name/displayName"));
2919 char* email = sipe_xml_data(
2920 sipe_xml_child(node, "email"));
2922 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_DISPLAY_NAME, display_name);
2923 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_EMAIL, email);
2925 g_free(display_name);
2926 g_free(email);
2928 /* company */
2929 node = sipe_xml_child(card, "company");
2930 if (node) {
2931 char* company = sipe_xml_data(node);
2932 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_COMPANY, company);
2933 g_free(company);
2935 /* department */
2936 node = sipe_xml_child(card, "department");
2937 if (node) {
2938 char* department = sipe_xml_data(node);
2939 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_DEPARTMENT, department);
2940 g_free(department);
2942 /* title */
2943 node = sipe_xml_child(card, "title");
2944 if (node) {
2945 char* title = sipe_xml_data(node);
2946 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_JOB_TITLE, title);
2947 g_free(title);
2949 /* office */
2950 node = sipe_xml_child(card, "office");
2951 if (node) {
2952 char* office = sipe_xml_data(node);
2953 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_OFFICE, office);
2954 g_free(office);
2956 /* site (url) */
2957 node = sipe_xml_child(card, "url");
2958 if (node) {
2959 char* site = sipe_xml_data(node);
2960 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_SITE, site);
2961 g_free(site);
2963 /* phone */
2964 for (node = sipe_xml_child(card, "phone");
2965 node;
2966 node = sipe_xml_twin(node))
2968 const char *phone_type = sipe_xml_attribute(node, "type");
2969 char* phone = sipe_xml_data(sipe_xml_child(node, "uri"));
2970 char* phone_display_string = sipe_xml_data(sipe_xml_child(node, "displayString"));
2972 sipe_update_user_phone(sipe_private, uri, phone_type, phone, phone_display_string);
2974 g_free(phone);
2975 g_free(phone_display_string);
2977 /* address */
2978 for (node = sipe_xml_child(card, "address");
2979 node;
2980 node = sipe_xml_twin(node))
2982 if (sipe_strequal(sipe_xml_attribute(node, "type"), "work")) {
2983 char* street = sipe_xml_data(sipe_xml_child(node, "street"));
2984 char* city = sipe_xml_data(sipe_xml_child(node, "city"));
2985 char* state = sipe_xml_data(sipe_xml_child(node, "state"));
2986 char* zipcode = sipe_xml_data(sipe_xml_child(node, "zipcode"));
2987 char* country_code = sipe_xml_data(sipe_xml_child(node, "countryCode"));
2989 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_STREET, street);
2990 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_CITY, city);
2991 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_STATE, state);
2992 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_ZIPCODE, zipcode);
2993 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_COUNTRY, country_code);
2995 g_free(street);
2996 g_free(city);
2997 g_free(state);
2998 g_free(zipcode);
2999 g_free(country_code);
3001 break;
3006 /* note */
3007 else if (sipe_strequal(attrVar, "note"))
3009 if (uri) {
3010 struct sipe_buddy *sbuddy = g_hash_table_lookup(sipe_private->buddies, uri);
3012 if (!has_note_cleaned) {
3013 has_note_cleaned = TRUE;
3015 g_free(sbuddy->note);
3016 sbuddy->note = NULL;
3017 sbuddy->is_oof_note = FALSE;
3018 sbuddy->note_since = publish_time;
3020 do_update_status = TRUE;
3022 if (sbuddy && (publish_time >= sbuddy->note_since)) {
3023 /* clean up in case no 'note' element is supplied
3024 * which indicate note removal in client
3026 g_free(sbuddy->note);
3027 sbuddy->note = NULL;
3028 sbuddy->is_oof_note = FALSE;
3029 sbuddy->note_since = publish_time;
3031 xn_node = sipe_xml_child(xn_category, "note/body");
3032 if (xn_node) {
3033 char *tmp;
3034 sbuddy->note = g_markup_escape_text((tmp = sipe_xml_data(xn_node)), -1);
3035 g_free(tmp);
3036 sbuddy->is_oof_note = sipe_strequal(sipe_xml_attribute(xn_node, "type"), "OOF");
3037 sbuddy->note_since = publish_time;
3039 SIPE_DEBUG_INFO("process_incoming_notify_rlmi: uri(%s), note(%s)",
3040 uri, sbuddy->note ? sbuddy->note : "");
3042 /* to trigger UI refresh in case no status info is supplied in this update */
3043 do_update_status = TRUE;
3047 /* state */
3048 else if(sipe_strequal(attrVar, "state"))
3050 char *tmp;
3051 int availability;
3052 const sipe_xml *xn_availability;
3053 const sipe_xml *xn_activity;
3054 const sipe_xml *xn_meeting_subject;
3055 const sipe_xml *xn_meeting_location;
3056 struct sipe_buddy *sbuddy = uri ? g_hash_table_lookup(sipe_private->buddies, uri) : NULL;
3058 xn_node = sipe_xml_child(xn_category, "state");
3059 if (!xn_node) continue;
3060 xn_availability = sipe_xml_child(xn_node, "availability");
3061 if (!xn_availability) continue;
3062 xn_activity = sipe_xml_child(xn_node, "activity");
3063 xn_meeting_subject = sipe_xml_child(xn_node, "meetingSubject");
3064 xn_meeting_location = sipe_xml_child(xn_node, "meetingLocation");
3066 tmp = sipe_xml_data(xn_availability);
3067 availability = atoi(tmp);
3068 g_free(tmp);
3070 /* activity, meeting_subject, meeting_location */
3071 if (sbuddy) {
3072 char *tmp = NULL;
3074 /* activity */
3075 g_free(sbuddy->activity);
3076 sbuddy->activity = NULL;
3077 if (xn_activity) {
3078 const char *token = sipe_xml_attribute(xn_activity, "token");
3079 const sipe_xml *xn_custom = sipe_xml_child(xn_activity, "custom");
3081 /* from token */
3082 if (!is_empty(token)) {
3083 sbuddy->activity = g_strdup(sipe_get_activity_desc_by_token(token));
3085 /* from custom element */
3086 if (xn_custom) {
3087 char *custom = sipe_xml_data(xn_custom);
3089 if (!is_empty(custom)) {
3090 sbuddy->activity = custom;
3091 custom = NULL;
3093 g_free(custom);
3096 /* meeting_subject */
3097 g_free(sbuddy->meeting_subject);
3098 sbuddy->meeting_subject = NULL;
3099 if (xn_meeting_subject) {
3100 char *meeting_subject = sipe_xml_data(xn_meeting_subject);
3102 if (!is_empty(meeting_subject)) {
3103 sbuddy->meeting_subject = meeting_subject;
3104 meeting_subject = NULL;
3106 g_free(meeting_subject);
3108 /* meeting_location */
3109 g_free(sbuddy->meeting_location);
3110 sbuddy->meeting_location = NULL;
3111 if (xn_meeting_location) {
3112 char *meeting_location = sipe_xml_data(xn_meeting_location);
3114 if (!is_empty(meeting_location)) {
3115 sbuddy->meeting_location = meeting_location;
3116 meeting_location = NULL;
3118 g_free(meeting_location);
3121 status = sipe_get_status_by_availability(availability, &tmp);
3122 if (sbuddy->activity && tmp) {
3123 char *tmp2 = sbuddy->activity;
3125 sbuddy->activity = g_strdup_printf("%s, %s", sbuddy->activity, tmp);
3126 g_free(tmp);
3127 g_free(tmp2);
3128 } else if (tmp) {
3129 sbuddy->activity = tmp;
3133 do_update_status = TRUE;
3135 /* calendarData */
3136 else if(sipe_strequal(attrVar, "calendarData"))
3138 struct sipe_buddy *sbuddy = uri ? g_hash_table_lookup(sipe_private->buddies, uri) : NULL;
3139 const sipe_xml *xn_free_busy = sipe_xml_child(xn_category, "calendarData/freeBusy");
3140 const sipe_xml *xn_working_hours = sipe_xml_child(xn_category, "calendarData/WorkingHours");
3142 if (sbuddy && xn_free_busy) {
3143 if (!has_free_busy_cleaned) {
3144 has_free_busy_cleaned = TRUE;
3146 g_free(sbuddy->cal_start_time);
3147 sbuddy->cal_start_time = NULL;
3149 g_free(sbuddy->cal_free_busy_base64);
3150 sbuddy->cal_free_busy_base64 = NULL;
3152 g_free(sbuddy->cal_free_busy);
3153 sbuddy->cal_free_busy = NULL;
3155 sbuddy->cal_free_busy_published = publish_time;
3158 if (publish_time >= sbuddy->cal_free_busy_published) {
3159 g_free(sbuddy->cal_start_time);
3160 sbuddy->cal_start_time = g_strdup(sipe_xml_attribute(xn_free_busy, "startTime"));
3162 sbuddy->cal_granularity = sipe_strcase_equal(sipe_xml_attribute(xn_free_busy, "granularity"), "PT15M") ?
3163 15 : 0;
3165 g_free(sbuddy->cal_free_busy_base64);
3166 sbuddy->cal_free_busy_base64 = sipe_xml_data(xn_free_busy);
3168 g_free(sbuddy->cal_free_busy);
3169 sbuddy->cal_free_busy = NULL;
3171 sbuddy->cal_free_busy_published = publish_time;
3173 SIPE_DEBUG_INFO("process_incoming_notify_rlmi: startTime=%s granularity=%d cal_free_busy_base64=\n%s", sbuddy->cal_start_time, sbuddy->cal_granularity, sbuddy->cal_free_busy_base64);
3177 if (sbuddy && xn_working_hours) {
3178 sipe_cal_parse_working_hours(xn_working_hours, sbuddy);
3183 if (do_update_status) {
3184 if (!status) { /* no status category in this update, using contact's current status */
3185 PurpleBuddy *pbuddy = purple_find_buddy((PurpleAccount *)sip->account, uri);
3186 const PurplePresence *presence = purple_buddy_get_presence(pbuddy);
3187 const PurpleStatus *pstatus = purple_presence_get_active_status(presence);
3188 status = purple_status_get_id(pstatus);
3191 SIPE_DEBUG_INFO("process_incoming_notify_rlmi: %s", status);
3192 sipe_core_buddy_got_status(SIPE_CORE_PUBLIC, uri, status);
3195 sipe_xml_free(xn_categories);
3198 static void sipe_subscribe_poolfqdn_resource_uri(const char *host,
3199 GSList *server,
3200 struct sipe_core_private *sipe_private)
3202 struct presence_batched_routed *payload = g_malloc(sizeof(struct presence_batched_routed));
3203 SIPE_DEBUG_INFO("process_incoming_notify_rlmi_resub: pool(%s)", host);
3204 payload->host = g_strdup(host);
3205 payload->buddies = server;
3206 sipe_subscribe_presence_batched_routed(sipe_private,
3207 payload);
3208 sipe_subscribe_presence_batched_routed_free(payload);
3211 static void process_incoming_notify_rlmi_resub(struct sipe_core_private *sipe_private,
3212 const gchar *data, unsigned len)
3214 sipe_xml *xn_list;
3215 const sipe_xml *xn_resource;
3216 GHashTable *servers = g_hash_table_new_full(g_str_hash, g_str_equal,
3217 g_free, NULL);
3218 GSList *server;
3219 gchar *host;
3221 xn_list = sipe_xml_parse(data, len);
3223 for (xn_resource = sipe_xml_child(xn_list, "resource");
3224 xn_resource;
3225 xn_resource = sipe_xml_twin(xn_resource) )
3227 const char *uri, *state;
3228 const sipe_xml *xn_instance;
3230 xn_instance = sipe_xml_child(xn_resource, "instance");
3231 if (!xn_instance) continue;
3233 uri = sipe_xml_attribute(xn_resource, "uri");
3234 state = sipe_xml_attribute(xn_instance, "state");
3235 SIPE_DEBUG_INFO("process_incoming_notify_rlmi_resub: uri(%s),state(%s)", uri, state);
3237 if (strstr(state, "resubscribe")) {
3238 const char *poolFqdn = sipe_xml_attribute(xn_instance, "poolFqdn");
3240 if (poolFqdn) { //[MS-PRES] Section 3.4.5.1.3 Processing Details
3241 gchar *user = g_strdup(uri);
3242 host = g_strdup(poolFqdn);
3243 server = g_hash_table_lookup(servers, host);
3244 server = g_slist_append(server, user);
3245 g_hash_table_insert(servers, host, server);
3246 } else {
3247 sipe_subscribe_presence_single(sipe_private,
3248 (void *) uri);
3253 /* Send out any deferred poolFqdn subscriptions */
3254 g_hash_table_foreach(servers, (GHFunc) sipe_subscribe_poolfqdn_resource_uri, sipe_private);
3255 g_hash_table_destroy(servers);
3257 sipe_xml_free(xn_list);
3260 static void process_incoming_notify_pidf(struct sipe_core_private *sipe_private,
3261 const gchar *data, unsigned len)
3263 gchar *uri;
3264 gchar *getbasic;
3265 gchar *activity = NULL;
3266 sipe_xml *pidf;
3267 const sipe_xml *basicstatus = NULL, *tuple, *status;
3268 gboolean isonline = FALSE;
3269 const sipe_xml *display_name_node;
3271 pidf = sipe_xml_parse(data, len);
3272 if (!pidf) {
3273 SIPE_DEBUG_INFO("process_incoming_notify_pidf: no parseable pidf:%s", data);
3274 return;
3277 if ((tuple = sipe_xml_child(pidf, "tuple")))
3279 if ((status = sipe_xml_child(tuple, "status"))) {
3280 basicstatus = sipe_xml_child(status, "basic");
3284 if (!basicstatus) {
3285 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_notify_pidf: no basic found");
3286 sipe_xml_free(pidf);
3287 return;
3290 getbasic = sipe_xml_data(basicstatus);
3291 if (!getbasic) {
3292 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_notify_pidf: no basic data found");
3293 sipe_xml_free(pidf);
3294 return;
3297 SIPE_DEBUG_INFO("process_incoming_notify_pidf: basic-status(%s)", getbasic);
3298 if (strstr(getbasic, "open")) {
3299 isonline = TRUE;
3301 g_free(getbasic);
3303 uri = sip_uri(sipe_xml_attribute(pidf, "entity")); /* with 'sip:' prefix */ /* AOL comes without the prefix */
3305 display_name_node = sipe_xml_child(pidf, "display-name");
3306 if (display_name_node) {
3307 char * display_name = sipe_xml_data(display_name_node);
3309 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_DISPLAY_NAME, display_name);
3310 g_free(display_name);
3313 if ((tuple = sipe_xml_child(pidf, "tuple"))) {
3314 if ((status = sipe_xml_child(tuple, "status"))) {
3315 if ((basicstatus = sipe_xml_child(status, "activities"))) {
3316 if ((basicstatus = sipe_xml_child(basicstatus, "activity"))) {
3317 activity = sipe_xml_data(basicstatus);
3318 SIPE_DEBUG_INFO("process_incoming_notify_pidf: activity(%s)", activity);
3324 if (isonline) {
3325 const gchar * status_id = NULL;
3326 if (activity) {
3327 if (sipe_strequal(activity, sipe_activity_map[SIPE_ACTIVITY_BUSY].token)) {
3328 status_id = SIPE_STATUS_ID_BUSY;
3329 } else if (sipe_strequal(activity, sipe_activity_map[SIPE_ACTIVITY_AWAY].token)) {
3330 status_id = SIPE_STATUS_ID_AWAY;
3334 if (!status_id) {
3335 status_id = SIPE_STATUS_ID_AVAILABLE;
3338 SIPE_DEBUG_INFO("process_incoming_notify_pidf: status_id(%s)", status_id);
3339 sipe_core_buddy_got_status(SIPE_CORE_PUBLIC, uri, status_id);
3340 } else {
3341 sipe_core_buddy_got_status(SIPE_CORE_PUBLIC, uri, SIPE_STATUS_ID_OFFLINE);
3344 g_free(activity);
3345 g_free(uri);
3346 sipe_xml_free(pidf);
3349 /** 2005 */
3350 static void
3351 sipe_user_info_has_updated(struct sipe_core_private *sipe_private,
3352 const sipe_xml *xn_userinfo)
3354 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
3355 const sipe_xml *xn_states;
3357 g_free(sip->user_states);
3358 sip->user_states = NULL;
3359 if ((xn_states = sipe_xml_child(xn_userinfo, "states")) != NULL) {
3360 gchar *orig = sip->user_states = sipe_xml_stringify(xn_states);
3362 /* this is a hack-around to remove added newline after inner element,
3363 * state in this case, where it shouldn't be.
3364 * After several use of sipe_xml_stringify, amount of added newlines
3365 * grows significantly.
3367 if (orig) {
3368 gchar c, *stripped = orig;
3369 while ((c = *orig++)) {
3370 if ((c != '\n') /* && (c != '\r') */) {
3371 *stripped++ = c;
3374 *stripped = '\0';
3378 /* Publish initial state if not yet.
3379 * Assuming this happens on initial responce to self subscription
3380 * so we've already updated our UserInfo.
3382 if (!sip->initial_state_published) {
3383 send_presence_soap(sipe_private, FALSE);
3384 /* dalayed run */
3385 sipe_schedule_seconds(sipe_private,
3386 "<+update-calendar>",
3387 NULL,
3388 UPDATE_CALENDAR_DELAY,
3389 (sipe_schedule_action) sipe_core_update_calendar,
3390 NULL);
3394 static void process_incoming_notify_msrtc(struct sipe_core_private *sipe_private,
3395 const gchar *data, unsigned len)
3397 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
3398 char *activity = NULL;
3399 const char *epid;
3400 const char *status_id = NULL;
3401 const char *name;
3402 char *uri;
3403 char *self_uri = sip_uri_self(sipe_private);
3404 int avl;
3405 int act;
3406 const char *device_name = NULL;
3407 const char *cal_start_time = NULL;
3408 const char *cal_granularity = NULL;
3409 char *cal_free_busy_base64 = NULL;
3410 struct sipe_buddy *sbuddy;
3411 const sipe_xml *node;
3412 sipe_xml *xn_presentity;
3413 const sipe_xml *xn_availability;
3414 const sipe_xml *xn_activity;
3415 const sipe_xml *xn_display_name;
3416 const sipe_xml *xn_email;
3417 const sipe_xml *xn_phone_number;
3418 const sipe_xml *xn_userinfo;
3419 const sipe_xml *xn_note;
3420 const sipe_xml *xn_oof;
3421 const sipe_xml *xn_state;
3422 const sipe_xml *xn_contact;
3423 char *note;
3424 int user_avail;
3425 const char *user_avail_nil;
3426 int res_avail;
3427 time_t user_avail_since = 0;
3428 time_t activity_since = 0;
3430 /* fix for Reuters environment on Linux */
3431 if (data && strstr(data, "encoding=\"utf-16\"")) {
3432 char *tmp_data;
3433 tmp_data = replace(data, "encoding=\"utf-16\"", "encoding=\"utf-8\"");
3434 xn_presentity = sipe_xml_parse(tmp_data, strlen(tmp_data));
3435 g_free(tmp_data);
3436 } else {
3437 xn_presentity = sipe_xml_parse(data, len);
3440 xn_availability = sipe_xml_child(xn_presentity, "availability");
3441 xn_activity = sipe_xml_child(xn_presentity, "activity");
3442 xn_display_name = sipe_xml_child(xn_presentity, "displayName");
3443 xn_email = sipe_xml_child(xn_presentity, "email");
3444 xn_phone_number = sipe_xml_child(xn_presentity, "phoneNumber");
3445 xn_userinfo = sipe_xml_child(xn_presentity, "userInfo");
3446 xn_oof = xn_userinfo ? sipe_xml_child(xn_userinfo, "oof") : NULL;
3447 xn_state = xn_userinfo ? sipe_xml_child(xn_userinfo, "states/state"): NULL;
3448 user_avail = xn_state ? sipe_xml_int_attribute(xn_state, "avail", 0) : 0;
3449 user_avail_since = xn_state ? sipe_utils_str_to_time(sipe_xml_attribute(xn_state, "since")) : 0;
3450 user_avail_nil = xn_state ? sipe_xml_attribute(xn_state, "nil") : NULL;
3451 xn_contact = xn_userinfo ? sipe_xml_child(xn_userinfo, "contact") : NULL;
3452 xn_note = xn_userinfo ? sipe_xml_child(xn_userinfo, "note") : NULL;
3453 note = xn_note ? sipe_xml_data(xn_note) : NULL;
3455 if (sipe_strequal(user_avail_nil, "true")) { /* null-ed */
3456 user_avail = 0;
3457 user_avail_since = 0;
3460 name = sipe_xml_attribute(xn_presentity, "uri"); /* without 'sip:' prefix */
3461 uri = sip_uri_from_name(name);
3462 avl = sipe_xml_int_attribute(xn_availability, "aggregate", 0);
3463 epid = sipe_xml_attribute(xn_availability, "epid");
3464 act = sipe_xml_int_attribute(xn_activity, "aggregate", 0);
3466 status_id = sipe_get_status_by_act_avail_2005(act, avl, &activity);
3467 res_avail = sipe_get_availability_by_status(status_id, NULL);
3468 if (user_avail > res_avail) {
3469 res_avail = user_avail;
3470 status_id = sipe_get_status_by_availability(user_avail, NULL);
3473 if (xn_display_name) {
3474 char *display_name = g_strdup(sipe_xml_attribute(xn_display_name, "displayName"));
3475 char *email = xn_email ? g_strdup(sipe_xml_attribute(xn_email, "email")) : NULL;
3476 char *phone_label = xn_phone_number ? g_strdup(sipe_xml_attribute(xn_phone_number, "label")) : NULL;
3477 char *phone_number = xn_phone_number ? g_strdup(sipe_xml_attribute(xn_phone_number, "number")) : NULL;
3478 char *tel_uri = sip_to_tel_uri(phone_number);
3480 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_DISPLAY_NAME, display_name);
3481 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_EMAIL, email);
3482 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_WORK_PHONE, tel_uri);
3483 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_WORK_PHONE_DISPLAY, !is_empty(phone_label) ? phone_label : phone_number);
3485 g_free(tel_uri);
3486 g_free(phone_label);
3487 g_free(phone_number);
3488 g_free(email);
3489 g_free(display_name);
3492 if (xn_contact) {
3493 /* tel */
3494 for (node = sipe_xml_child(xn_contact, "tel"); node; node = sipe_xml_twin(node))
3496 /* Ex.: <tel type="work">tel:+3222220000</tel> */
3497 const char *phone_type = sipe_xml_attribute(node, "type");
3498 char* phone = sipe_xml_data(node);
3500 sipe_update_user_phone(sipe_private, uri, phone_type, phone, NULL);
3502 g_free(phone);
3506 /* devicePresence */
3507 for (node = sipe_xml_child(xn_presentity, "devices/devicePresence"); node; node = sipe_xml_twin(node)) {
3508 const sipe_xml *xn_device_name;
3509 const sipe_xml *xn_calendar_info;
3510 const sipe_xml *xn_state;
3511 char *state;
3513 /* deviceName */
3514 if (sipe_strequal(sipe_xml_attribute(node, "epid"), epid)) {
3515 xn_device_name = sipe_xml_child(node, "deviceName");
3516 device_name = xn_device_name ? sipe_xml_attribute(xn_device_name, "name") : NULL;
3519 /* calendarInfo */
3520 xn_calendar_info = sipe_xml_child(node, "calendarInfo");
3521 if (xn_calendar_info) {
3522 const char *cal_start_time_tmp = sipe_xml_attribute(xn_calendar_info, "startTime");
3524 if (cal_start_time) {
3525 time_t cal_start_time_t = sipe_utils_str_to_time(cal_start_time);
3526 time_t cal_start_time_t_tmp = sipe_utils_str_to_time(cal_start_time_tmp);
3528 if (cal_start_time_t_tmp > cal_start_time_t) {
3529 cal_start_time = cal_start_time_tmp;
3530 cal_granularity = sipe_xml_attribute(xn_calendar_info, "granularity");
3531 g_free(cal_free_busy_base64);
3532 cal_free_busy_base64 = sipe_xml_data(xn_calendar_info);
3534 SIPE_DEBUG_INFO("process_incoming_notify_msrtc: startTime=%s granularity=%s cal_free_busy_base64=\n%s", cal_start_time, cal_granularity, cal_free_busy_base64);
3536 } else {
3537 cal_start_time = cal_start_time_tmp;
3538 cal_granularity = sipe_xml_attribute(xn_calendar_info, "granularity");
3539 g_free(cal_free_busy_base64);
3540 cal_free_busy_base64 = sipe_xml_data(xn_calendar_info);
3542 SIPE_DEBUG_INFO("process_incoming_notify_msrtc: startTime=%s granularity=%s cal_free_busy_base64=\n%s", cal_start_time, cal_granularity, cal_free_busy_base64);
3546 /* state */
3547 xn_state = sipe_xml_child(node, "states/state");
3548 if (xn_state) {
3549 int dev_avail = sipe_xml_int_attribute(xn_state, "avail", 0);
3550 time_t dev_avail_since = sipe_utils_str_to_time(sipe_xml_attribute(xn_state, "since"));
3552 state = sipe_xml_data(xn_state);
3553 if (dev_avail_since > user_avail_since &&
3554 dev_avail >= res_avail)
3556 res_avail = dev_avail;
3557 if (!is_empty(state))
3559 if (sipe_strequal(state, sipe_activity_map[SIPE_ACTIVITY_ON_PHONE].token)) {
3560 g_free(activity);
3561 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_ON_PHONE));
3562 } else if (sipe_strequal(state, "presenting")) {
3563 g_free(activity);
3564 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_IN_CONF));
3565 } else {
3566 activity = state;
3567 state = NULL;
3569 activity_since = dev_avail_since;
3571 status_id = sipe_get_status_by_availability(res_avail, &activity);
3573 g_free(state);
3577 /* oof */
3578 if (xn_oof && res_avail >= 15000) { /* 12000 in 2007 */
3579 g_free(activity);
3580 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_OOF));
3581 activity_since = 0;
3584 sbuddy = g_hash_table_lookup(sipe_private->buddies, uri);
3585 if (sbuddy)
3587 g_free(sbuddy->activity);
3588 sbuddy->activity = activity;
3589 activity = NULL;
3591 sbuddy->activity_since = activity_since;
3593 sbuddy->user_avail = user_avail;
3594 sbuddy->user_avail_since = user_avail_since;
3596 g_free(sbuddy->note);
3597 sbuddy->note = NULL;
3598 if (!is_empty(note)) { sbuddy->note = g_markup_escape_text(note, -1); }
3600 sbuddy->is_oof_note = (xn_oof != NULL);
3602 g_free(sbuddy->device_name);
3603 sbuddy->device_name = NULL;
3604 if (!is_empty(device_name)) { sbuddy->device_name = g_strdup(device_name); }
3606 if (!is_empty(cal_free_busy_base64)) {
3607 g_free(sbuddy->cal_start_time);
3608 sbuddy->cal_start_time = g_strdup(cal_start_time);
3610 sbuddy->cal_granularity = sipe_strcase_equal(cal_granularity, "PT15M") ? 15 : 0;
3612 g_free(sbuddy->cal_free_busy_base64);
3613 sbuddy->cal_free_busy_base64 = cal_free_busy_base64;
3614 cal_free_busy_base64 = NULL;
3616 g_free(sbuddy->cal_free_busy);
3617 sbuddy->cal_free_busy = NULL;
3620 sbuddy->last_non_cal_status_id = status_id;
3621 g_free(sbuddy->last_non_cal_activity);
3622 sbuddy->last_non_cal_activity = g_strdup(sbuddy->activity);
3624 if (sipe_strcase_equal(sbuddy->name, self_uri)) {
3625 if (!sipe_strequal(sbuddy->note, sip->note)) /* not same */
3627 sip->is_oof_note = sbuddy->is_oof_note;
3629 g_free(sip->note);
3630 sip->note = g_strdup(sbuddy->note);
3632 sip->note_since = time(NULL);
3635 g_free(sip->status);
3636 sip->status = g_strdup(sbuddy->last_non_cal_status_id);
3639 g_free(cal_free_busy_base64);
3640 g_free(activity);
3642 SIPE_DEBUG_INFO("process_incoming_notify_msrtc: status(%s)", status_id);
3643 sipe_core_buddy_got_status(SIPE_CORE_PUBLIC, uri, status_id);
3645 if (!SIPE_CORE_PRIVATE_FLAG_IS(OCS2007) && sipe_strcase_equal(self_uri, uri)) {
3646 sipe_user_info_has_updated(sipe_private, xn_userinfo);
3649 g_free(note);
3650 sipe_xml_free(xn_presentity);
3651 g_free(uri);
3652 g_free(self_uri);
3655 static void sipe_presence_mime_cb(gpointer user_data, /* sipe_core_private */
3656 const GSList *fields,
3657 const gchar *body,
3658 gsize length)
3660 const gchar *type = sipe_utils_nameval_find(fields, "Content-Type");
3662 if (strstr(type,"application/rlmi+xml")) {
3663 process_incoming_notify_rlmi_resub(user_data, body, length);
3664 } else if (strstr(type, "text/xml+msrtc.pidf")) {
3665 process_incoming_notify_msrtc(user_data, body, length);
3666 } else {
3667 process_incoming_notify_rlmi(user_data, body, length);
3671 static void sipe_process_presence(struct sipe_core_private *sipe_private,
3672 struct sipmsg *msg)
3674 const char *ctype = sipmsg_find_header(msg, "Content-Type");
3676 SIPE_DEBUG_INFO("sipe_process_presence: Content-Type: %s", ctype ? ctype : "");
3678 if (ctype &&
3679 (strstr(ctype, "application/rlmi+xml") ||
3680 strstr(ctype, "application/msrtc-event-categories+xml")))
3682 if (strstr(ctype, "multipart"))
3684 sipe_mime_parts_foreach(ctype, msg->body, sipe_presence_mime_cb, sipe_private);
3686 else if(strstr(ctype, "application/msrtc-event-categories+xml") )
3688 process_incoming_notify_rlmi(sipe_private, msg->body, msg->bodylen);
3690 else if(strstr(ctype, "application/rlmi+xml"))
3692 process_incoming_notify_rlmi_resub(sipe_private, msg->body, msg->bodylen);
3695 else if(ctype && strstr(ctype, "text/xml+msrtc.pidf"))
3697 process_incoming_notify_msrtc(sipe_private, msg->body, msg->bodylen);
3699 else
3701 process_incoming_notify_pidf(sipe_private, msg->body, msg->bodylen);
3705 static void sipe_presence_timeout_mime_cb(gpointer user_data,
3706 SIPE_UNUSED_PARAMETER const GSList *fields,
3707 const gchar *body,
3708 gsize length)
3710 GSList **buddies = user_data;
3711 sipe_xml *xml = sipe_xml_parse(body, length);
3713 if (xml && !sipe_strequal(sipe_xml_name(xml), "list")) {
3714 const gchar *uri = sipe_xml_attribute(xml, "uri");
3715 const sipe_xml *xn_category;
3718 * automaton: presence is never expected to change
3720 * see: http://msdn.microsoft.com/en-us/library/ee354295(office.13).aspx
3722 for (xn_category = sipe_xml_child(xml, "category");
3723 xn_category;
3724 xn_category = sipe_xml_twin(xn_category)) {
3725 if (sipe_strequal(sipe_xml_attribute(xn_category, "name"),
3726 "contactCard")) {
3727 const sipe_xml *node = sipe_xml_child(xn_category, "contactCard/automaton");
3728 if (node) {
3729 char *boolean = sipe_xml_data(node);
3730 if (sipe_strequal(boolean, "true")) {
3731 SIPE_DEBUG_INFO("sipe_process_presence_timeout: %s is an automaton: - not subscribing to presence updates",
3732 uri);
3733 uri = NULL;
3735 g_free(boolean);
3737 break;
3741 if (uri) {
3742 *buddies = g_slist_append(*buddies, sip_uri(uri));
3746 sipe_xml_free(xml);
3749 static void sipe_process_presence_timeout(struct sipe_core_private *sipe_private,
3750 struct sipmsg *msg, gchar *who,
3751 int timeout)
3753 const char *ctype = sipmsg_find_header(msg, "Content-Type");
3754 gchar *action_name = sipe_utils_presence_key(who);
3756 SIPE_DEBUG_INFO("sipe_process_presence_timeout: Content-Type: %s", ctype ? ctype : "");
3758 if (ctype &&
3759 strstr(ctype, "multipart") &&
3760 (strstr(ctype, "application/rlmi+xml") ||
3761 strstr(ctype, "application/msrtc-event-categories+xml"))) {
3762 GSList *buddies = NULL;
3764 sipe_mime_parts_foreach(ctype, msg->body, sipe_presence_timeout_mime_cb, &buddies);
3766 if (buddies) {
3767 struct presence_batched_routed *payload = g_malloc(sizeof(struct presence_batched_routed));
3768 payload->host = g_strdup(who);
3769 payload->buddies = buddies;
3770 sipe_schedule_seconds(sipe_private,
3771 action_name,
3772 payload,
3773 timeout,
3774 sipe_subscribe_presence_batched_routed,
3775 sipe_subscribe_presence_batched_routed_free);
3776 SIPE_DEBUG_INFO("Resubscription multiple contacts with batched support & route(%s) in %d", who, timeout);
3779 } else {
3780 sipe_schedule_seconds(sipe_private,
3781 action_name,
3782 g_strdup(who),
3783 timeout,
3784 sipe_subscribe_presence_single,
3785 g_free);
3786 SIPE_DEBUG_INFO("Resubscription single contact with batched support(%s) in %d", who, timeout);
3788 g_free(action_name);
3792 * Dispatcher for all incoming subscription information
3793 * whether it comes from NOTIFY, BENOTIFY requests or
3794 * piggy-backed to subscription's OK responce.
3796 * @param request whether initiated from BE/NOTIFY request or OK-response message.
3797 * @param benotify whether initiated from NOTIFY or BENOTIFY request.
3799 void process_incoming_notify(struct sipe_core_private *sipe_private,
3800 struct sipmsg *msg,
3801 gboolean request, gboolean benotify)
3803 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
3804 const gchar *content_type = sipmsg_find_header(msg, "Content-Type");
3805 const gchar *event = sipmsg_find_header(msg, "Event");
3806 const gchar *subscription_state = sipmsg_find_header(msg, "subscription-state");
3808 SIPE_DEBUG_INFO("process_incoming_notify: subscription_state: %s", subscription_state ? subscription_state : "");
3810 /* implicit subscriptions */
3811 if (content_type && g_str_has_prefix(content_type, "application/ms-imdn+xml")) {
3812 sipe_process_imdn(sipe_private, msg);
3815 if (event) {
3816 /* for one off subscriptions (send with Expire: 0) */
3817 if (sipe_strcase_equal(event, "vnd-microsoft-provisioning-v2"))
3819 sipe_process_provisioning_v2(sipe_private, msg);
3821 else if (sipe_strcase_equal(event, "vnd-microsoft-provisioning"))
3823 sipe_process_provisioning(sipe_private, msg);
3825 else if (sipe_strcase_equal(event, "presence"))
3827 sipe_process_presence(sipe_private, msg);
3829 else if (sipe_strcase_equal(event, "registration-notify"))
3831 sipe_process_registration_notify(sipe_private, msg);
3834 if (!subscription_state || strstr(subscription_state, "active"))
3836 if (sipe_strcase_equal(event, "vnd-microsoft-roaming-contacts"))
3838 sipe_process_roaming_contacts(sipe_private, msg);
3840 else if (sipe_strcase_equal(event, "vnd-microsoft-roaming-self"))
3842 sipe_process_roaming_self(sipe_private, msg);
3844 else if (sipe_strcase_equal(event, "vnd-microsoft-roaming-ACL"))
3846 sipe_process_roaming_acl(sipe_private, msg);
3848 else if (sipe_strcase_equal(event, "presence.wpending"))
3850 sipe_process_presence_wpending(sipe_private, msg);
3852 else if (sipe_strcase_equal(event, "conference"))
3854 sipe_process_conference(sipe_private, msg);
3859 /* The server sends status 'terminated' */
3860 if (subscription_state && strstr(subscription_state, "terminated") ) {
3861 gchar *who = parse_from(sipmsg_find_header(msg, request ? "From" : "To"));
3862 gchar *key = sipe_utils_subscription_key(event, who);
3864 SIPE_DEBUG_INFO("process_incoming_notify: server says that subscription to %s was terminated.", who);
3865 g_free(who);
3867 sipe_subscriptions_remove(sipe_private, key);
3868 g_free(key);
3871 if (!request && event) {
3872 const gchar *expires_header = sipmsg_find_header(msg, "Expires");
3873 int timeout = expires_header ? strtol(expires_header, NULL, 10) : 0;
3874 SIPE_DEBUG_INFO("process_incoming_notify: subscription expires:%d", timeout);
3876 if (timeout) {
3877 /* 2 min ahead of expiration */
3878 timeout = (timeout - 120) > 120 ? (timeout - 120) : timeout;
3880 if (sipe_strcase_equal(event, "presence.wpending") &&
3881 g_slist_find_custom(sip->allow_events, "presence.wpending", (GCompareFunc)g_ascii_strcasecmp))
3883 gchar *action_name = g_strdup_printf("<%s>", "presence.wpending");
3884 sipe_schedule_seconds(sipe_private,
3885 action_name,
3886 NULL,
3887 timeout,
3888 sipe_subscribe_presence_wpending,
3889 NULL);
3890 g_free(action_name);
3892 else if (sipe_strcase_equal(event, "presence") &&
3893 g_slist_find_custom(sip->allow_events, "presence", (GCompareFunc)g_ascii_strcasecmp))
3895 gchar *who = parse_from(sipmsg_find_header(msg, "To"));
3896 gchar *action_name = sipe_utils_presence_key(who);
3898 if (sip->batched_support) {
3899 sipe_process_presence_timeout(sipe_private, msg, who, timeout);
3901 else {
3902 sipe_schedule_seconds(sipe_private,
3903 action_name,
3904 g_strdup(who),
3905 timeout,
3906 sipe_subscribe_presence_single,
3907 g_free);
3908 SIPE_DEBUG_INFO("Resubscription single contact (%s) in %d", who, timeout);
3910 g_free(action_name);
3911 g_free(who);
3916 /* The client responses on received a NOTIFY message */
3917 if (request && !benotify)
3919 sip_transport_response(sipe_private, msg, 200, "OK", NULL);
3924 * Whether user manually changed status or
3925 * it was changed automatically due to user
3926 * became inactive/active again
3928 static gboolean
3929 sipe_is_user_state(struct sipe_core_private *sipe_private)
3931 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
3932 gboolean res;
3933 time_t now = time(NULL);
3935 SIPE_DEBUG_INFO("sipe_is_user_state: sip->idle_switch : %s", asctime(localtime(&(sip->idle_switch))));
3936 SIPE_DEBUG_INFO("sipe_is_user_state: now : %s", asctime(localtime(&now)));
3938 res = ((now - SIPE_IDLE_SET_DELAY * 2) >= sip->idle_switch);
3940 SIPE_DEBUG_INFO("sipe_is_user_state: res = %s", res ? "USER" : "MACHINE");
3941 return res;
3944 static void
3945 send_presence_soap0(struct sipe_core_private *sipe_private,
3946 gboolean do_publish_calendar,
3947 gboolean do_reset_status)
3949 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
3950 struct sipe_calendar* cal = sip->cal;
3951 int availability = 0;
3952 int activity = 0;
3953 gchar *body;
3954 gchar *tmp;
3955 gchar *tmp2 = NULL;
3956 gchar *res_note = NULL;
3957 gchar *res_oof = NULL;
3958 const gchar *note_pub = NULL;
3959 gchar *states = NULL;
3960 gchar *calendar_data = NULL;
3961 gchar *epid = get_epid(sipe_private);
3962 time_t now = time(NULL);
3963 gchar *since_time_str = sipe_utils_time_to_str(now);
3964 const gchar *oof_note = cal ? sipe_ews_get_oof_note(cal) : NULL;
3965 const char *user_input;
3966 gboolean pub_oof = cal && oof_note && (!sip->note || cal->updated > sip->note_since);
3968 if (oof_note && sip->note) {
3969 SIPE_DEBUG_INFO("cal->oof_start : %s", asctime(localtime(&(cal->oof_start))));
3970 SIPE_DEBUG_INFO("sip->note_since : %s", asctime(localtime(&(sip->note_since))));
3973 SIPE_DEBUG_INFO("sip->note : %s", sip->note ? sip->note : "");
3975 if (!sip->initial_state_published ||
3976 do_reset_status)
3978 g_free(sip->status);
3979 sip->status = g_strdup(SIPE_STATUS_ID_AVAILABLE);
3982 sipe_get_act_avail_by_status_2005(sip->status, &activity, &availability);
3984 /* Note */
3985 if (pub_oof) {
3986 note_pub = oof_note;
3987 res_oof = SIPE_SOAP_SET_PRESENCE_OOF_XML;
3988 cal->published = TRUE;
3989 } else if (sip->note) {
3990 if (sip->is_oof_note && !oof_note) { /* stale OOF note, as it's not present in cal already */
3991 g_free(sip->note);
3992 sip->note = NULL;
3993 sip->is_oof_note = FALSE;
3994 sip->note_since = 0;
3995 } else {
3996 note_pub = sip->note;
3997 res_oof = sip->is_oof_note ? SIPE_SOAP_SET_PRESENCE_OOF_XML : "";
4001 if (note_pub)
4003 /* to protocol internal plain text format */
4004 tmp = sipe_backend_markup_strip_html(note_pub);
4005 res_note = g_markup_printf_escaped(SIPE_SOAP_SET_PRESENCE_NOTE_XML, tmp);
4006 g_free(tmp);
4009 /* User State */
4010 if (!do_reset_status) {
4011 if (sipe_is_user_state(sipe_private) && !do_publish_calendar && sip->initial_state_published)
4013 gchar *activity_token = NULL;
4014 int avail_2007 = sipe_get_availability_by_status(sip->status, &activity_token);
4016 states = g_strdup_printf(SIPE_SOAP_SET_PRESENCE_STATES,
4017 avail_2007,
4018 since_time_str,
4019 epid,
4020 activity_token);
4021 g_free(activity_token);
4023 else /* preserve existing publication */
4025 if (sip->user_states) {
4026 states = g_strdup(sip->user_states);
4029 } else {
4030 /* do nothing - then User state will be erased */
4032 sip->initial_state_published = TRUE;
4034 /* CalendarInfo */
4035 if (cal && (!is_empty(cal->legacy_dn) || !is_empty(cal->email)) && cal->fb_start && !is_empty(cal->free_busy))
4037 char *fb_start_str = sipe_utils_time_to_str(cal->fb_start);
4038 char *free_busy_base64 = sipe_cal_get_freebusy_base64(cal->free_busy);
4039 calendar_data = g_strdup_printf(SIPE_SOAP_SET_PRESENCE_CALENDAR,
4040 !is_empty(cal->legacy_dn) ? cal->legacy_dn : cal->email,
4041 fb_start_str,
4042 free_busy_base64);
4043 g_free(fb_start_str);
4044 g_free(free_busy_base64);
4047 user_input = (sipe_is_user_state(sipe_private) ||
4048 sipe_strequal(sip->status, SIPE_STATUS_ID_AVAILABLE)) ?
4049 "active" : "idle";
4051 /* forming resulting XML */
4052 body = g_strdup_printf(SIPE_SOAP_SET_PRESENCE,
4053 sipe_private->username,
4054 availability,
4055 activity,
4056 (tmp = g_ascii_strup(g_get_host_name(), -1)),
4057 res_note ? res_note : "",
4058 res_oof ? res_oof : "",
4059 states ? states : "",
4060 calendar_data ? calendar_data : "",
4061 epid,
4062 since_time_str,
4063 since_time_str,
4064 user_input);
4065 g_free(tmp);
4066 g_free(tmp2);
4067 g_free(res_note);
4068 g_free(states);
4069 g_free(calendar_data);
4071 send_soap_request(sipe_private, body);
4073 g_free(body);
4074 g_free(since_time_str);
4075 g_free(epid);
4078 void
4079 send_presence_soap(struct sipe_core_private *sipe_private,
4080 gboolean do_publish_calendar)
4082 return send_presence_soap0(sipe_private, do_publish_calendar, FALSE);
4086 static gboolean
4087 process_send_presence_category_publish_response(struct sipe_core_private *sipe_private,
4088 struct sipmsg *msg,
4089 struct transaction *trans)
4091 const gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
4093 if (msg->response == 409 && g_str_has_prefix(contenttype, "application/msrtc-fault+xml")) {
4094 sipe_xml *xml;
4095 const sipe_xml *node;
4096 gchar *fault_code;
4097 GHashTable *faults;
4098 int index_our;
4099 gboolean has_device_publication = FALSE;
4101 xml = sipe_xml_parse(msg->body, msg->bodylen);
4103 /* test if version mismatch fault */
4104 fault_code = sipe_xml_data(sipe_xml_child(xml, "Faultcode"));
4105 if (!sipe_strequal(fault_code, "Client.BadCall.WrongDelta")) {
4106 SIPE_DEBUG_INFO("process_send_presence_category_publish_response: unsupported fault code:%s returning.", fault_code);
4107 g_free(fault_code);
4108 sipe_xml_free(xml);
4109 return TRUE;
4111 g_free(fault_code);
4113 /* accumulating information about faulty versions */
4114 faults = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
4115 for (node = sipe_xml_child(xml, "details/operation");
4116 node;
4117 node = sipe_xml_twin(node))
4119 const gchar *index = sipe_xml_attribute(node, "index");
4120 const gchar *curVersion = sipe_xml_attribute(node, "curVersion");
4122 g_hash_table_insert(faults, g_strdup(index), g_strdup(curVersion));
4123 SIPE_DEBUG_INFO("fault added: index:%s curVersion:%s", index, curVersion);
4125 sipe_xml_free(xml);
4127 /* here we are parsing our own request to figure out what publication
4128 * referenced here only by index went wrong
4130 xml = sipe_xml_parse(trans->msg->body, trans->msg->bodylen);
4132 /* publication */
4133 for (node = sipe_xml_child(xml, "publications/publication"),
4134 index_our = 1; /* starts with 1 - our first publication */
4135 node;
4136 node = sipe_xml_twin(node), index_our++)
4138 gchar *idx = g_strdup_printf("%d", index_our);
4139 const gchar *curVersion = g_hash_table_lookup(faults, idx);
4140 const gchar *categoryName = sipe_xml_attribute(node, "categoryName");
4141 g_free(idx);
4143 if (sipe_strequal("device", categoryName)) {
4144 has_device_publication = TRUE;
4147 if (curVersion) { /* fault exist on this index */
4148 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
4149 const gchar *container = sipe_xml_attribute(node, "container");
4150 const gchar *instance = sipe_xml_attribute(node, "instance");
4151 /* key is <category><instance><container> */
4152 gchar *key = g_strdup_printf("<%s><%s><%s>", categoryName, instance, container);
4153 GHashTable *category = g_hash_table_lookup(sip->our_publications, categoryName);
4155 if (category) {
4156 struct sipe_publication *publication =
4157 g_hash_table_lookup(category, key);
4159 SIPE_DEBUG_INFO("key is %s", key);
4161 if (publication) {
4162 SIPE_DEBUG_INFO("Updating %s with version %s. Was %d before.",
4163 key, curVersion, publication->version);
4164 /* updating publication's version to the correct one */
4165 publication->version = atoi(curVersion);
4167 } else {
4168 /* We somehow lost this category from our publications... */
4169 struct sipe_publication *publication = g_new0(struct sipe_publication, 1);
4170 publication->category = g_strdup(categoryName);
4171 publication->instance = atoi(instance);
4172 publication->container = atoi(container);
4173 publication->version = atoi(curVersion);
4174 category = g_hash_table_new_full(g_str_hash, g_str_equal,
4175 g_free, (GDestroyNotify)free_publication);
4176 g_hash_table_insert(category, g_strdup(key), publication);
4177 g_hash_table_insert(sip->our_publications, g_strdup(categoryName), category);
4178 SIPE_DEBUG_INFO("added lost category '%s' key '%s'", categoryName, key);
4180 g_free(key);
4183 sipe_xml_free(xml);
4184 g_hash_table_destroy(faults);
4186 /* rebublishing with right versions */
4187 if (has_device_publication) {
4188 send_publish_category_initial(sipe_private);
4189 } else {
4190 send_presence_status(sipe_private, NULL);
4193 return TRUE;
4197 * Returns 'device' XML part for publication.
4198 * Must be g_free'd after use.
4200 static gchar *
4201 sipe_publish_get_category_device(struct sipe_core_private *sipe_private)
4203 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
4204 gchar *uri;
4205 gchar *doc;
4206 gchar *epid = get_epid(sipe_private);
4207 gchar *uuid = generateUUIDfromEPID(epid);
4208 guint device_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_DEVICE);
4209 /* key is <category><instance><container> */
4210 gchar *key = g_strdup_printf("<%s><%u><%u>", "device", device_instance, 2);
4211 struct sipe_publication *publication =
4212 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "device"), key);
4214 g_free(key);
4215 g_free(epid);
4217 uri = sip_uri_self(sipe_private);
4218 doc = g_strdup_printf(SIPE_PUB_XML_DEVICE,
4219 device_instance,
4220 publication ? publication->version : 0,
4221 uuid,
4222 uri,
4223 "00:00:00+01:00", /* @TODO make timezone real*/
4224 g_get_host_name()
4227 g_free(uri);
4228 g_free(uuid);
4230 return doc;
4234 * A service method - use
4235 * - send_publish_get_category_state_machine and
4236 * - send_publish_get_category_state_user instead.
4237 * Must be g_free'd after use.
4239 static gchar *
4240 sipe_publish_get_category_state(struct sipe_core_private *sipe_private,
4241 gboolean is_user_state)
4243 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
4244 int availability = sipe_get_availability_by_status(sip->status, NULL);
4245 guint instance = is_user_state ? sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_USER) :
4246 sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_MACHINE);
4247 /* key is <category><instance><container> */
4248 gchar *key_2 = g_strdup_printf("<%s><%u><%u>", "state", instance, 2);
4249 gchar *key_3 = g_strdup_printf("<%s><%u><%u>", "state", instance, 3);
4250 struct sipe_publication *publication_2 =
4251 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_2);
4252 struct sipe_publication *publication_3 =
4253 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_3);
4255 g_free(key_2);
4256 g_free(key_3);
4258 if (publication_2 && (publication_2->availability == availability))
4260 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_state: state has NOT changed. Exiting.");
4261 return NULL; /* nothing to update */
4264 return g_strdup_printf( is_user_state ? SIPE_PUB_XML_STATE_USER : SIPE_PUB_XML_STATE_MACHINE,
4265 instance,
4266 publication_2 ? publication_2->version : 0,
4267 availability,
4268 instance,
4269 publication_3 ? publication_3->version : 0,
4270 availability);
4274 * Only Busy and OOF calendar event are published.
4275 * Different instances are used for that.
4277 * Must be g_free'd after use.
4279 static gchar *
4280 sipe_publish_get_category_state_calendar(struct sipe_core_private *sipe_private,
4281 struct sipe_cal_event *event,
4282 const char *uri,
4283 int cal_satus)
4285 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
4286 gchar *start_time_str;
4287 int availability = 0;
4288 gchar *res;
4289 gchar *tmp = NULL;
4290 guint instance = (cal_satus == SIPE_CAL_OOF) ?
4291 sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_CALENDAR_OOF) :
4292 sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_CALENDAR);
4294 /* key is <category><instance><container> */
4295 gchar *key_2 = g_strdup_printf("<%s><%u><%u>", "state", instance, 2);
4296 gchar *key_3 = g_strdup_printf("<%s><%u><%u>", "state", instance, 3);
4297 struct sipe_publication *publication_2 =
4298 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_2);
4299 struct sipe_publication *publication_3 =
4300 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_3);
4302 g_free(key_2);
4303 g_free(key_3);
4305 if (!publication_3 && !event) { /* was nothing, have nothing, exiting */
4306 SIPE_DEBUG_INFO("sipe_publish_get_category_state_calendar: "
4307 "Exiting as no publication and no event for cal_satus:%d", cal_satus);
4308 return NULL;
4311 if (event &&
4312 publication_3 &&
4313 (publication_3->availability == availability) &&
4314 sipe_strequal(publication_3->cal_event_hash, (tmp = sipe_cal_event_hash(event))))
4316 g_free(tmp);
4317 SIPE_DEBUG_INFO("sipe_publish_get_category_state_calendar: "
4318 "cal state has NOT changed for cal_satus:%d. Exiting.", cal_satus);
4319 return NULL; /* nothing to update */
4321 g_free(tmp);
4323 if (event &&
4324 (event->cal_status == SIPE_CAL_BUSY ||
4325 event->cal_status == SIPE_CAL_OOF))
4327 gchar *availability_xml_str = NULL;
4328 gchar *activity_xml_str = NULL;
4329 gchar *escaped_subject = event->subject ? g_markup_escape_text(event->subject, -1) : NULL;
4330 gchar *escaped_location = event->location ? g_markup_escape_text(event->location, -1) : NULL;
4332 if (event->cal_status == SIPE_CAL_BUSY) {
4333 availability_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_AVAIL, 6500);
4336 if (event->cal_status == SIPE_CAL_BUSY && event->is_meeting) {
4337 activity_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_ACTIVITY,
4338 sipe_activity_map[SIPE_ACTIVITY_IN_MEETING].token,
4339 "minAvailability=\"6500\"",
4340 "maxAvailability=\"8999\"");
4341 } else if (event->cal_status == SIPE_CAL_OOF) {
4342 activity_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_ACTIVITY,
4343 sipe_activity_map[SIPE_ACTIVITY_OOF].token,
4344 "minAvailability=\"12000\"",
4345 "");
4347 start_time_str = sipe_utils_time_to_str(event->start_time);
4349 res = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR,
4350 instance,
4351 publication_2 ? publication_2->version : 0,
4352 uri,
4353 start_time_str,
4354 availability_xml_str ? availability_xml_str : "",
4355 activity_xml_str ? activity_xml_str : "",
4356 escaped_subject ? escaped_subject : "",
4357 escaped_location ? escaped_location : "",
4359 instance,
4360 publication_3 ? publication_3->version : 0,
4361 uri,
4362 start_time_str,
4363 availability_xml_str ? availability_xml_str : "",
4364 activity_xml_str ? activity_xml_str : "",
4365 escaped_subject ? escaped_subject : "",
4366 escaped_location ? escaped_location : ""
4368 g_free(escaped_location);
4369 g_free(escaped_subject);
4370 g_free(start_time_str);
4371 g_free(availability_xml_str);
4372 g_free(activity_xml_str);
4375 else /* including !event, SIPE_CAL_FREE, SIPE_CAL_TENTATIVE */
4377 res = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_CLEAR,
4378 instance,
4379 publication_2 ? publication_2->version : 0,
4381 instance,
4382 publication_3 ? publication_3->version : 0
4386 return res;
4390 * Returns 'machineState' XML part for publication.
4391 * Must be g_free'd after use.
4393 static gchar *
4394 sipe_publish_get_category_state_machine(struct sipe_core_private *sipe_private)
4396 return sipe_publish_get_category_state(sipe_private, FALSE);
4400 * Returns 'userState' XML part for publication.
4401 * Must be g_free'd after use.
4403 static gchar *
4404 sipe_publish_get_category_state_user(struct sipe_core_private *sipe_private)
4406 return sipe_publish_get_category_state(sipe_private, TRUE);
4410 * Returns 'note' XML part for publication.
4411 * Must be g_free'd after use.
4413 * Protocol format for Note is plain text.
4415 * @param note a note in Sipe internal HTML format
4416 * @param note_type either personal or OOF
4418 static gchar *
4419 sipe_publish_get_category_note(struct sipe_core_private *sipe_private,
4420 const char *note, /* html */
4421 const char *note_type,
4422 time_t note_start,
4423 time_t note_end)
4425 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
4426 guint instance = sipe_strequal("OOF", note_type) ? sipe_get_pub_instance(sipe_private, SIPE_PUB_NOTE_OOF) : 0;
4427 /* key is <category><instance><container> */
4428 gchar *key_note_200 = g_strdup_printf("<%s><%u><%u>", "note", instance, 200);
4429 gchar *key_note_300 = g_strdup_printf("<%s><%u><%u>", "note", instance, 300);
4430 gchar *key_note_400 = g_strdup_printf("<%s><%u><%u>", "note", instance, 400);
4432 struct sipe_publication *publication_note_200 =
4433 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_200);
4434 struct sipe_publication *publication_note_300 =
4435 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_300);
4436 struct sipe_publication *publication_note_400 =
4437 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_400);
4439 char *tmp = note ? sipe_backend_markup_strip_html(note) : NULL;
4440 char *n1 = tmp ? g_markup_escape_text(tmp, -1) : NULL;
4441 const char *n2 = publication_note_200 ? publication_note_200->note : NULL;
4442 char *res, *tmp1, *tmp2, *tmp3;
4443 char *start_time_attr;
4444 char *end_time_attr;
4446 g_free(tmp);
4447 tmp = NULL;
4448 g_free(key_note_200);
4449 g_free(key_note_300);
4450 g_free(key_note_400);
4452 /* we even need to republish empty note */
4453 if (sipe_strequal(n1, n2))
4455 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_note: note has NOT changed. Exiting.");
4456 g_free(n1);
4457 return NULL; /* nothing to update */
4460 start_time_attr = note_start ? g_strdup_printf(" startTime=\"%s\"", (tmp = sipe_utils_time_to_str(note_start))) : NULL;
4461 g_free(tmp);
4462 tmp = NULL;
4463 end_time_attr = note_end ? g_strdup_printf(" endTime=\"%s\"", (tmp = sipe_utils_time_to_str(note_end))) : NULL;
4464 g_free(tmp);
4466 if (n1) {
4467 tmp1 = g_strdup_printf(SIPE_PUB_XML_NOTE,
4468 instance,
4469 200,
4470 publication_note_200 ? publication_note_200->version : 0,
4471 note_type,
4472 start_time_attr ? start_time_attr : "",
4473 end_time_attr ? end_time_attr : "",
4474 n1);
4476 tmp2 = g_strdup_printf(SIPE_PUB_XML_NOTE,
4477 instance,
4478 300,
4479 publication_note_300 ? publication_note_300->version : 0,
4480 note_type,
4481 start_time_attr ? start_time_attr : "",
4482 end_time_attr ? end_time_attr : "",
4483 n1);
4485 tmp3 = g_strdup_printf(SIPE_PUB_XML_NOTE,
4486 instance,
4487 400,
4488 publication_note_400 ? publication_note_400->version : 0,
4489 note_type,
4490 start_time_attr ? start_time_attr : "",
4491 end_time_attr ? end_time_attr : "",
4492 n1);
4493 } else {
4494 tmp1 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
4495 "note",
4496 instance,
4497 200,
4498 publication_note_200 ? publication_note_200->version : 0,
4499 "static");
4500 tmp2 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
4501 "note",
4502 instance,
4503 300,
4504 publication_note_200 ? publication_note_200->version : 0,
4505 "static");
4506 tmp3 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
4507 "note",
4508 instance,
4509 400,
4510 publication_note_200 ? publication_note_200->version : 0,
4511 "static");
4513 res = g_strconcat(tmp1, tmp2, tmp3, NULL);
4515 g_free(start_time_attr);
4516 g_free(end_time_attr);
4517 g_free(tmp1);
4518 g_free(tmp2);
4519 g_free(tmp3);
4520 g_free(n1);
4522 return res;
4526 * Returns 'calendarData' XML part with WorkingHours for publication.
4527 * Must be g_free'd after use.
4529 static gchar *
4530 sipe_publish_get_category_cal_working_hours(struct sipe_core_private *sipe_private)
4532 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
4533 struct sipe_calendar* cal = sip->cal;
4535 /* key is <category><instance><container> */
4536 gchar *key_cal_1 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 1);
4537 gchar *key_cal_100 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 100);
4538 gchar *key_cal_200 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 200);
4539 gchar *key_cal_300 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 300);
4540 gchar *key_cal_400 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 400);
4541 gchar *key_cal_32000 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 32000);
4543 struct sipe_publication *publication_cal_1 =
4544 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_1);
4545 struct sipe_publication *publication_cal_100 =
4546 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_100);
4547 struct sipe_publication *publication_cal_200 =
4548 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_200);
4549 struct sipe_publication *publication_cal_300 =
4550 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_300);
4551 struct sipe_publication *publication_cal_400 =
4552 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_400);
4553 struct sipe_publication *publication_cal_32000 =
4554 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_32000);
4556 const char *n1 = cal ? cal->working_hours_xml_str : NULL;
4557 const char *n2 = publication_cal_300 ? publication_cal_300->working_hours_xml_str : NULL;
4559 g_free(key_cal_1);
4560 g_free(key_cal_100);
4561 g_free(key_cal_200);
4562 g_free(key_cal_300);
4563 g_free(key_cal_400);
4564 g_free(key_cal_32000);
4566 if (!cal || is_empty(cal->email) || is_empty(cal->working_hours_xml_str)) {
4567 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_cal_working_hours: no data to publish, exiting");
4568 return NULL;
4571 if (sipe_strequal(n1, n2))
4573 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_cal_working_hours: WorkingHours has NOT changed. Exiting.");
4574 return NULL; /* nothing to update */
4577 return g_strdup_printf(SIPE_PUB_XML_WORKING_HOURS,
4578 /* 1 */
4579 publication_cal_1 ? publication_cal_1->version : 0,
4580 cal->email,
4581 cal->working_hours_xml_str,
4582 /* 100 - Public */
4583 publication_cal_100 ? publication_cal_100->version : 0,
4584 /* 200 - Company */
4585 publication_cal_200 ? publication_cal_200->version : 0,
4586 cal->email,
4587 cal->working_hours_xml_str,
4588 /* 300 - Team */
4589 publication_cal_300 ? publication_cal_300->version : 0,
4590 cal->email,
4591 cal->working_hours_xml_str,
4592 /* 400 - Personal */
4593 publication_cal_400 ? publication_cal_400->version : 0,
4594 cal->email,
4595 cal->working_hours_xml_str,
4596 /* 32000 - Blocked */
4597 publication_cal_32000 ? publication_cal_32000->version : 0
4602 * Returns 'calendarData' XML part with FreeBusy for publication.
4603 * Must be g_free'd after use.
4605 static gchar *
4606 sipe_publish_get_category_cal_free_busy(struct sipe_core_private *sipe_private)
4608 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
4609 struct sipe_calendar* cal = sip->cal;
4610 guint cal_data_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_CALENDAR_DATA);
4611 char *fb_start_str;
4612 char *free_busy_base64;
4613 /* const char *st; */
4614 /* const char *fb; */
4615 char *res;
4617 /* key is <category><instance><container> */
4618 gchar *key_cal_1 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 1);
4619 gchar *key_cal_100 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 100);
4620 gchar *key_cal_200 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 200);
4621 gchar *key_cal_300 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 300);
4622 gchar *key_cal_400 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 400);
4623 gchar *key_cal_32000 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 32000);
4625 struct sipe_publication *publication_cal_1 =
4626 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_1);
4627 struct sipe_publication *publication_cal_100 =
4628 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_100);
4629 struct sipe_publication *publication_cal_200 =
4630 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_200);
4631 struct sipe_publication *publication_cal_300 =
4632 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_300);
4633 struct sipe_publication *publication_cal_400 =
4634 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_400);
4635 struct sipe_publication *publication_cal_32000 =
4636 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_32000);
4638 g_free(key_cal_1);
4639 g_free(key_cal_100);
4640 g_free(key_cal_200);
4641 g_free(key_cal_300);
4642 g_free(key_cal_400);
4643 g_free(key_cal_32000);
4645 if (!cal || is_empty(cal->email) || !cal->fb_start || is_empty(cal->free_busy)) {
4646 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_cal_free_busy: no data to publish, exiting");
4647 return NULL;
4650 fb_start_str = sipe_utils_time_to_str(cal->fb_start);
4651 free_busy_base64 = sipe_cal_get_freebusy_base64(cal->free_busy);
4653 /* we will rebuplish the same data to refresh publication time,
4654 * so if data from multiple sources, most recent will be choosen
4656 // st = publication_cal_300 ? publication_cal_300->fb_start_str : NULL;
4657 // fb = publication_cal_300 ? publication_cal_300->free_busy_base64 : NULL;
4659 //if (sipe_strequal(st, fb_start_str) && sipe_strequal(fb, free_busy_base64))
4661 // SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_cal_free_busy: FreeBusy has NOT changed. Exiting.");
4662 // g_free(fb_start_str);
4663 // g_free(free_busy_base64);
4664 // return NULL; /* nothing to update */
4667 res = g_strdup_printf(SIPE_PUB_XML_FREE_BUSY,
4668 /* 1 */
4669 cal_data_instance,
4670 publication_cal_1 ? publication_cal_1->version : 0,
4671 /* 100 - Public */
4672 cal_data_instance,
4673 publication_cal_100 ? publication_cal_100->version : 0,
4674 /* 200 - Company */
4675 cal_data_instance,
4676 publication_cal_200 ? publication_cal_200->version : 0,
4677 cal->email,
4678 fb_start_str,
4679 free_busy_base64,
4680 /* 300 - Team */
4681 cal_data_instance,
4682 publication_cal_300 ? publication_cal_300->version : 0,
4683 cal->email,
4684 fb_start_str,
4685 free_busy_base64,
4686 /* 400 - Personal */
4687 cal_data_instance,
4688 publication_cal_400 ? publication_cal_400->version : 0,
4689 cal->email,
4690 fb_start_str,
4691 free_busy_base64,
4692 /* 32000 - Blocked */
4693 cal_data_instance,
4694 publication_cal_32000 ? publication_cal_32000->version : 0
4697 g_free(fb_start_str);
4698 g_free(free_busy_base64);
4699 return res;
4702 static void send_presence_publish(struct sipe_core_private *sipe_private,
4703 const char *publications)
4705 gchar *uri;
4706 gchar *doc;
4707 gchar *tmp;
4708 gchar *hdr;
4710 uri = sip_uri_self(sipe_private);
4711 doc = g_strdup_printf(SIPE_SEND_PRESENCE,
4712 uri,
4713 publications);
4715 tmp = get_contact(sipe_private);
4716 hdr = g_strdup_printf("Contact: %s\r\n"
4717 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp);
4719 sip_transport_service(sipe_private,
4720 uri,
4721 hdr,
4722 doc,
4723 process_send_presence_category_publish_response);
4725 g_free(tmp);
4726 g_free(hdr);
4727 g_free(uri);
4728 g_free(doc);
4731 static void
4732 send_publish_category_initial(struct sipe_core_private *sipe_private)
4734 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
4735 gchar *pub_device = sipe_publish_get_category_device(sipe_private);
4736 gchar *pub_machine;
4737 gchar *publications;
4739 g_free(sip->status);
4740 sip->status = g_strdup(SIPE_STATUS_ID_AVAILABLE); /* our initial state */
4742 pub_machine = sipe_publish_get_category_state_machine(sipe_private);
4743 publications = g_strdup_printf("%s%s",
4744 pub_device,
4745 pub_machine ? pub_machine : "");
4746 g_free(pub_device);
4747 g_free(pub_machine);
4749 send_presence_publish(sipe_private, publications);
4750 g_free(publications);
4753 static void
4754 send_presence_category_publish(struct sipe_core_private *sipe_private)
4756 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
4757 gchar *pub_state = sipe_is_user_state(sipe_private) ?
4758 sipe_publish_get_category_state_user(sipe_private) :
4759 sipe_publish_get_category_state_machine(sipe_private);
4760 gchar *pub_note = sipe_publish_get_category_note(sipe_private,
4761 sip->note,
4762 sip->is_oof_note ? "OOF" : "personal",
4765 gchar *publications;
4767 if (!pub_state && !pub_note) {
4768 SIPE_DEBUG_INFO_NOFORMAT("send_presence_category_publish: nothing has changed. Exiting.");
4769 return;
4772 publications = g_strdup_printf("%s%s",
4773 pub_state ? pub_state : "",
4774 pub_note ? pub_note : "");
4776 g_free(pub_state);
4777 g_free(pub_note);
4779 send_presence_publish(sipe_private, publications);
4780 g_free(publications);
4784 * Publishes self status
4785 * based on own calendar information.
4787 * For 2007+
4789 void
4790 publish_calendar_status_self(struct sipe_core_private *sipe_private,
4791 SIPE_UNUSED_PARAMETER void *unused)
4793 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
4794 struct sipe_cal_event* event = NULL;
4795 gchar *pub_cal_working_hours = NULL;
4796 gchar *pub_cal_free_busy = NULL;
4797 gchar *pub_calendar = NULL;
4798 gchar *pub_calendar2 = NULL;
4799 gchar *pub_oof_note = NULL;
4800 const gchar *oof_note;
4801 time_t oof_start = 0;
4802 time_t oof_end = 0;
4804 if (!sip->cal) {
4805 SIPE_DEBUG_INFO_NOFORMAT("publish_calendar_status_self() no calendar data.");
4806 return;
4809 SIPE_DEBUG_INFO_NOFORMAT("publish_calendar_status_self() started.");
4810 if (sip->cal->cal_events) {
4811 event = sipe_cal_get_event(sip->cal->cal_events, time(NULL));
4814 if (!event) {
4815 SIPE_DEBUG_INFO_NOFORMAT("publish_calendar_status_self: current event is NULL");
4816 } else {
4817 char *desc = sipe_cal_event_describe(event);
4818 SIPE_DEBUG_INFO("publish_calendar_status_self: current event is:\n%s", desc ? desc : "");
4819 g_free(desc);
4822 /* Logic
4823 if OOF
4824 OOF publish, Busy clean
4825 ilse if Busy
4826 OOF clean, Busy publish
4827 else
4828 OOF clean, Busy clean
4830 if (event && event->cal_status == SIPE_CAL_OOF) {
4831 pub_calendar = sipe_publish_get_category_state_calendar(sipe_private, event, sip->cal->email, SIPE_CAL_OOF);
4832 pub_calendar2 = sipe_publish_get_category_state_calendar(sipe_private, NULL, sip->cal->email, SIPE_CAL_BUSY);
4833 } else if (event && event->cal_status == SIPE_CAL_BUSY) {
4834 pub_calendar = sipe_publish_get_category_state_calendar(sipe_private, NULL, sip->cal->email, SIPE_CAL_OOF);
4835 pub_calendar2 = sipe_publish_get_category_state_calendar(sipe_private, event, sip->cal->email, SIPE_CAL_BUSY);
4836 } else {
4837 pub_calendar = sipe_publish_get_category_state_calendar(sipe_private, NULL, sip->cal->email, SIPE_CAL_OOF);
4838 pub_calendar2 = sipe_publish_get_category_state_calendar(sipe_private, NULL, sip->cal->email, SIPE_CAL_BUSY);
4841 oof_note = sipe_ews_get_oof_note(sip->cal);
4842 if (sipe_strequal("Scheduled", sip->cal->oof_state)) {
4843 oof_start = sip->cal->oof_start;
4844 oof_end = sip->cal->oof_end;
4846 pub_oof_note = sipe_publish_get_category_note(sipe_private, oof_note, "OOF", oof_start, oof_end);
4848 pub_cal_working_hours = sipe_publish_get_category_cal_working_hours(sipe_private);
4849 pub_cal_free_busy = sipe_publish_get_category_cal_free_busy(sipe_private);
4851 if (!pub_cal_working_hours && !pub_cal_free_busy && !pub_calendar && !pub_calendar2 && !pub_oof_note) {
4852 SIPE_DEBUG_INFO_NOFORMAT("publish_calendar_status_self: nothing has changed.");
4853 } else {
4854 gchar *publications = g_strdup_printf("%s%s%s%s%s",
4855 pub_cal_working_hours ? pub_cal_working_hours : "",
4856 pub_cal_free_busy ? pub_cal_free_busy : "",
4857 pub_calendar ? pub_calendar : "",
4858 pub_calendar2 ? pub_calendar2 : "",
4859 pub_oof_note ? pub_oof_note : "");
4861 send_presence_publish(sipe_private, publications);
4862 g_free(publications);
4865 g_free(pub_cal_working_hours);
4866 g_free(pub_cal_free_busy);
4867 g_free(pub_calendar);
4868 g_free(pub_calendar2);
4869 g_free(pub_oof_note);
4871 /* repeat scheduling */
4872 sipe_sched_calendar_status_self_publish(sipe_private, time(NULL));
4875 static void send_presence_status(struct sipe_core_private *sipe_private,
4876 SIPE_UNUSED_PARAMETER void *unused)
4878 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
4879 PurpleStatus * status = purple_account_get_active_status(sip->account);
4881 if (!status) return;
4883 SIPE_DEBUG_INFO("send_presence_status: status: %s (%s)",
4884 purple_status_get_id(status) ? purple_status_get_id(status) : "",
4885 sipe_is_user_state(sipe_private) ? "USER" : "MACHINE");
4887 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
4888 send_presence_category_publish(sipe_private);
4889 } else {
4890 send_presence_soap(sipe_private, FALSE);
4894 static guint sipe_ht_hash_nick(const char *nick)
4896 char *lc = g_utf8_strdown(nick, -1);
4897 guint bucket = g_str_hash(lc);
4898 g_free(lc);
4900 return bucket;
4903 static gboolean sipe_ht_equals_nick(const char *nick1, const char *nick2)
4905 char *nick1_norm = NULL;
4906 char *nick2_norm = NULL;
4907 gboolean equal;
4909 if (nick1 == NULL && nick2 == NULL) return TRUE;
4910 if (nick1 == NULL || nick2 == NULL ||
4911 !g_utf8_validate(nick1, -1, NULL) ||
4912 !g_utf8_validate(nick2, -1, NULL)) return FALSE;
4914 nick1_norm = g_utf8_casefold(nick1, -1);
4915 nick2_norm = g_utf8_casefold(nick2, -1);
4916 equal = g_utf8_collate(nick1_norm, nick2_norm) == 0;
4917 g_free(nick2_norm);
4918 g_free(nick1_norm);
4920 return equal;
4923 /* temporary function */
4924 void sipe_purple_setup(struct sipe_core_public *sipe_public,
4925 PurpleConnection *gc)
4927 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA;
4928 sip->gc = gc;
4929 sip->account = purple_connection_get_account(gc);
4932 struct sipe_core_public *sipe_core_allocate(const gchar *signin_name,
4933 const gchar *login_domain,
4934 const gchar *login_account,
4935 const gchar *password,
4936 const gchar *email,
4937 const gchar *email_url,
4938 const gchar **errmsg)
4940 struct sipe_core_private *sipe_private;
4941 struct sipe_account_data *sip;
4942 gchar **user_domain;
4944 SIPE_DEBUG_INFO("sipe_core_allocate: signin_name '%s'", signin_name);
4946 /* ensure that sign-in name doesn't contain invalid characters */
4947 if (strpbrk(signin_name, "\t\v\r\n") != NULL) {
4948 *errmsg = _("SIP Exchange user name contains invalid characters");
4949 return NULL;
4952 /* ensure that sign-in name format is name@domain */
4953 if (!strchr(signin_name, '@') ||
4954 g_str_has_prefix(signin_name, "@") ||
4955 g_str_has_suffix(signin_name, "@")) {
4956 *errmsg = _("User name should be a valid SIP URI\nExample: user@company.com");
4957 return NULL;
4960 /* ensure that email format is name@domain (if provided) */
4961 if (!is_empty(email) &&
4962 (!strchr(email, '@') ||
4963 g_str_has_prefix(email, "@") ||
4964 g_str_has_suffix(email, "@")))
4966 *errmsg = _("Email address should be valid if provided\nExample: user@company.com");
4967 return NULL;
4970 /* ensure that user name doesn't contain spaces */
4971 user_domain = g_strsplit(signin_name, "@", 2);
4972 SIPE_DEBUG_INFO("sipe_core_allocate: user '%s' domain '%s'", user_domain[0], user_domain[1]);
4973 if (strchr(user_domain[0], ' ') != NULL) {
4974 g_strfreev(user_domain);
4975 *errmsg = _("SIP Exchange user name contains whitespace");
4976 return NULL;
4979 /* ensure that email_url is in proper format if enabled (if provided).
4980 * Example (Exchange): https://server.company.com/EWS/Exchange.asmx
4981 * Example (Domino) : https://[domino_server]/[mail_database_name].nsf
4983 if (!is_empty(email_url)) {
4984 char *tmp = g_ascii_strdown(email_url, -1);
4985 if (!g_str_has_prefix(tmp, "https://"))
4987 g_free(tmp);
4988 g_strfreev(user_domain);
4989 *errmsg = _("Email services URL should be valid if provided\n"
4990 "Example: https://exchange.corp.com/EWS/Exchange.asmx\n"
4991 "Example: https://domino.corp.com/maildatabase.nsf");
4992 return NULL;
4994 g_free(tmp);
4997 sipe_private = g_new0(struct sipe_core_private, 1);
4998 sipe_private->temporary = sip = g_new0(struct sipe_account_data, 1);
4999 sip->subscribed_buddies = FALSE;
5000 sip->initial_state_published = FALSE;
5001 sipe_private->username = g_strdup(signin_name);
5002 sip->email = is_empty(email) ? g_strdup(signin_name) : g_strdup(email);
5003 sip->authdomain = is_empty(login_domain) ? NULL : g_strdup(login_domain);
5004 sip->authuser = is_empty(login_account) ? NULL : g_strdup(login_account);
5005 sip->password = g_strdup(password);
5006 sipe_private->public.sip_name = g_strdup(user_domain[0]);
5007 sipe_private->public.sip_domain = g_strdup(user_domain[1]);
5008 g_strfreev(user_domain);
5010 sipe_private->buddies = g_hash_table_new((GHashFunc)sipe_ht_hash_nick, (GEqualFunc)sipe_ht_equals_nick);
5011 sip->our_publications = g_hash_table_new_full(g_str_hash, g_str_equal,
5012 g_free, (GDestroyNotify)g_hash_table_destroy);
5013 sipe_subscriptions_init(sipe_private);
5014 sip->status = g_strdup(SIPE_STATUS_ID_UNKNOWN);
5016 return((struct sipe_core_public *)sipe_private);
5019 static void
5020 sipe_blist_menu_free_containers(struct sipe_core_private *sipe_private);
5022 void sipe_connection_cleanup(struct sipe_core_private *sipe_private)
5024 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5026 g_free(sipe_private->epid);
5027 sipe_private->epid = NULL;
5029 sip_transport_disconnect(sipe_private);
5031 sipe_schedule_cancel_all(sipe_private);
5033 if (sip->allow_events) {
5034 GSList *entry = sip->allow_events;
5035 while (entry) {
5036 g_free(entry->data);
5037 entry = entry->next;
5040 g_slist_free(sip->allow_events);
5042 if (sip->containers) {
5043 GSList *entry = sip->containers;
5044 while (entry) {
5045 free_container((struct sipe_container *)entry->data);
5046 entry = entry->next;
5049 g_slist_free(sip->containers);
5051 /* libpurple memory leak workaround */
5052 sipe_blist_menu_free_containers(sipe_private);
5054 if (sipe_private->contact)
5055 g_free(sipe_private->contact);
5056 sipe_private->contact = NULL;
5057 if (sip->regcallid)
5058 g_free(sip->regcallid);
5059 sip->regcallid = NULL;
5061 if (sipe_private->focus_factory_uri)
5062 g_free(sipe_private->focus_factory_uri);
5063 sipe_private->focus_factory_uri = NULL;
5065 if (sip->cal) {
5066 sipe_cal_calendar_free(sip->cal);
5068 sip->cal = NULL;
5070 sipe_groupchat_free(sipe_private);
5074 * A callback for g_hash_table_foreach_remove
5076 static gboolean sipe_buddy_remove(SIPE_UNUSED_PARAMETER gpointer key, gpointer buddy,
5077 SIPE_UNUSED_PARAMETER gpointer user_data)
5079 sipe_free_buddy((struct sipe_buddy *) buddy);
5081 /* We must return TRUE as the key/value have already been deleted */
5082 return(TRUE);
5085 void sipe_buddy_free_all(struct sipe_core_private *sipe_private)
5087 g_hash_table_foreach_steal(sipe_private->buddies, sipe_buddy_remove, NULL);
5090 static void sipe_searchresults_im_buddy(PurpleConnection *gc, GList *row,
5091 SIPE_UNUSED_PARAMETER void *user_data)
5093 PurpleAccount *acct = purple_connection_get_account(gc);
5094 char *id = sip_uri_from_name((gchar *)g_list_nth_data(row, 0));
5095 PurpleConversation *conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, id, acct);
5096 if (conv == NULL)
5097 conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, acct, id);
5098 purple_conversation_present(conv);
5099 g_free(id);
5102 static void sipe_searchresults_add_buddy(PurpleConnection *gc, GList *row,
5103 SIPE_UNUSED_PARAMETER void *user_data)
5106 purple_blist_request_add_buddy(purple_connection_get_account(gc),
5107 g_list_nth_data(row, 0), _("Other Contacts"), g_list_nth_data(row, 1));
5110 static gboolean process_search_contact_response(struct sipe_core_private *sipe_private,
5111 struct sipmsg *msg,
5112 SIPE_UNUSED_PARAMETER struct transaction *trans)
5114 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5115 PurpleNotifySearchResults *results;
5116 PurpleNotifySearchColumn *column;
5117 sipe_xml *searchResults;
5118 const sipe_xml *mrow;
5119 int match_count = 0;
5120 gboolean more = FALSE;
5121 gchar *secondary;
5123 SIPE_DEBUG_INFO("process_search_contact_response: body:\n%s", msg->body ? msg->body : "");
5125 searchResults = sipe_xml_parse(msg->body, msg->bodylen);
5126 if (!searchResults) {
5127 SIPE_DEBUG_INFO_NOFORMAT("process_search_contact_response: no parseable searchResults");
5128 return FALSE;
5131 results = purple_notify_searchresults_new();
5133 if (results == NULL) {
5134 SIPE_DEBUG_ERROR_NOFORMAT("purple_parse_searchreply: Unable to display the search results.");
5135 purple_notify_error(sip->gc, NULL, _("Unable to display the search results"), NULL);
5137 sipe_xml_free(searchResults);
5138 return FALSE;
5141 column = purple_notify_searchresults_column_new(_("User name"));
5142 purple_notify_searchresults_column_add(results, column);
5144 column = purple_notify_searchresults_column_new(_("Name"));
5145 purple_notify_searchresults_column_add(results, column);
5147 column = purple_notify_searchresults_column_new(_("Company"));
5148 purple_notify_searchresults_column_add(results, column);
5150 column = purple_notify_searchresults_column_new(_("Country"));
5151 purple_notify_searchresults_column_add(results, column);
5153 column = purple_notify_searchresults_column_new(_("Email"));
5154 purple_notify_searchresults_column_add(results, column);
5156 for (mrow = sipe_xml_child(searchResults, "Body/Array/row"); mrow; mrow = sipe_xml_twin(mrow)) {
5157 GList *row = NULL;
5159 gchar **uri_parts = g_strsplit(sipe_xml_attribute(mrow, "uri"), ":", 2);
5160 row = g_list_append(row, g_strdup(uri_parts[1]));
5161 g_strfreev(uri_parts);
5163 row = g_list_append(row, g_strdup(sipe_xml_attribute(mrow, "displayName")));
5164 row = g_list_append(row, g_strdup(sipe_xml_attribute(mrow, "company")));
5165 row = g_list_append(row, g_strdup(sipe_xml_attribute(mrow, "country")));
5166 row = g_list_append(row, g_strdup(sipe_xml_attribute(mrow, "email")));
5168 purple_notify_searchresults_row_add(results, row);
5169 match_count++;
5172 if ((mrow = sipe_xml_child(searchResults, "Body/directorySearch/moreAvailable")) != NULL) {
5173 char *data = sipe_xml_data(mrow);
5174 more = (g_strcasecmp(data, "true") == 0);
5175 g_free(data);
5178 secondary = g_strdup_printf(
5179 dngettext(PACKAGE_NAME,
5180 "Found %d contact%s:",
5181 "Found %d contacts%s:", match_count),
5182 match_count, more ? _(" (more matched your query)") : "");
5184 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_IM, sipe_searchresults_im_buddy);
5185 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_ADD, sipe_searchresults_add_buddy);
5186 purple_notify_searchresults(sip->gc, NULL, NULL, secondary, results, NULL, NULL);
5188 g_free(secondary);
5189 sipe_xml_free(searchResults);
5190 return TRUE;
5193 void sipe_search_contact_with_cb(PurpleConnection *gc, PurpleRequestFields *fields)
5195 GList *entries = purple_request_field_group_get_fields(purple_request_fields_get_groups(fields)->data);
5196 gchar **attrs = g_new(gchar *, g_list_length(entries) + 1);
5197 unsigned i = 0;
5199 if (!attrs) return;
5201 do {
5202 PurpleRequestField *field = entries->data;
5203 const char *id = purple_request_field_get_id(field);
5204 const char *value = purple_request_field_string_get_value(field);
5206 SIPE_DEBUG_INFO("sipe_search_contact_with_cb: %s = '%s'", id, value ? value : "");
5208 if (value != NULL) attrs[i++] = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, id, value);
5209 } while ((entries = g_list_next(entries)) != NULL);
5210 attrs[i] = NULL;
5212 if (i > 0) {
5213 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
5214 gchar *domain_uri = sip_uri_from_name(sipe_private->public.sip_domain);
5215 gchar *query = g_strjoinv(NULL, attrs);
5216 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 100, query);
5217 SIPE_DEBUG_INFO("sipe_search_contact_with_cb: body:\n%s", body ? body : "");
5218 send_soap_request_with_cb(sipe_private, domain_uri, body,
5219 process_search_contact_response, NULL);
5220 g_free(domain_uri);
5221 g_free(body);
5222 g_free(query);
5225 g_strfreev(attrs);
5228 static void sipe_publish_get_cat_state_user_to_clear(SIPE_UNUSED_PARAMETER const char *name,
5229 gpointer value,
5230 GString* str)
5232 struct sipe_publication *publication = value;
5234 g_string_append_printf( str,
5235 SIPE_PUB_XML_PUBLICATION_CLEAR,
5236 publication->category,
5237 publication->instance,
5238 publication->container,
5239 publication->version,
5240 "static");
5243 void sipe_core_reset_status(struct sipe_core_public *sipe_public)
5245 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
5246 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA;
5247 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) /* 2007+ */
5249 GString* str = g_string_new(NULL);
5250 gchar *publications;
5252 if (!sip->user_state_publications || g_hash_table_size(sip->user_state_publications) == 0) {
5253 SIPE_DEBUG_INFO_NOFORMAT("sipe_reset_status: no userState publications, exiting.");
5254 return;
5257 g_hash_table_foreach(sip->user_state_publications, (GHFunc)sipe_publish_get_cat_state_user_to_clear, str);
5258 publications = g_string_free(str, FALSE);
5260 send_presence_publish(sipe_private, publications);
5261 g_free(publications);
5263 else /* 2005 */
5265 send_presence_soap0(sipe_private, FALSE, TRUE);
5269 /** for Access levels menu */
5270 #define INDENT_FMT " %s"
5272 /** Member is directly placed to access level container.
5273 * For example SIP URI of user is in the container.
5275 #define INDENT_MARKED_FMT "* %s"
5277 /** Member is indirectly belong to access level container.
5278 * For example 'sameEnterprise' is in the container and user
5279 * belongs to that same enterprise.
5281 #define INDENT_MARKED_INHERITED_FMT "= %s"
5283 GSList *sipe_core_buddy_info(struct sipe_core_public *sipe_public,
5284 const gchar *name,
5285 const gchar *status_name,
5286 gboolean is_online)
5288 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
5289 gchar *note = NULL;
5290 gboolean is_oof_note = FALSE;
5291 const gchar *activity = NULL;
5292 gchar *calendar = NULL;
5293 const gchar *meeting_subject = NULL;
5294 const gchar *meeting_location = NULL;
5295 gchar *access_text = NULL;
5296 GSList *info = NULL;
5298 #define SIPE_ADD_BUDDY_INFO_COMMON(l, t) \
5300 struct sipe_buddy_info *sbi = g_malloc(sizeof(struct sipe_buddy_info)); \
5301 sbi->label = (l); \
5302 sbi->text = (t); \
5303 info = g_slist_append(info, sbi); \
5305 #define SIPE_ADD_BUDDY_INFO(l, t) SIPE_ADD_BUDDY_INFO_COMMON((l), g_markup_escape_text((t), -1))
5306 #define SIPE_ADD_BUDDY_INFO_NOESCAPE(l, t) SIPE_ADD_BUDDY_INFO_COMMON((l), (t))
5308 if (sipe_public) { //happens on pidgin exit
5309 struct sipe_buddy *sbuddy = g_hash_table_lookup(sipe_private->buddies, name);
5310 if (sbuddy) {
5311 note = sbuddy->note;
5312 is_oof_note = sbuddy->is_oof_note;
5313 activity = sbuddy->activity;
5314 calendar = sipe_cal_get_description(sbuddy);
5315 meeting_subject = sbuddy->meeting_subject;
5316 meeting_location = sbuddy->meeting_location;
5318 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
5319 gboolean is_group_access = FALSE;
5320 const int container_id = sipe_find_access_level(sipe_private, "user", sipe_get_no_sip_uri(name), &is_group_access);
5321 const char *access_level = sipe_get_access_level_name(container_id);
5322 access_text = is_group_access ?
5323 g_strdup(access_level) :
5324 g_strdup_printf(INDENT_MARKED_FMT, access_level);
5328 //Layout
5329 if (is_online)
5331 const gchar *status_str = activity ? activity : status_name;
5333 SIPE_ADD_BUDDY_INFO(_("Status"), status_str);
5335 if (is_online && !is_empty(calendar))
5337 SIPE_ADD_BUDDY_INFO(_("Calendar"), calendar);
5339 g_free(calendar);
5340 if (!is_empty(meeting_location))
5342 SIPE_DEBUG_INFO("sipe_tooltip_text: %s meeting location: '%s'", name, meeting_location);
5343 SIPE_ADD_BUDDY_INFO(_("Meeting in"), meeting_location);
5345 if (!is_empty(meeting_subject))
5347 SIPE_DEBUG_INFO("sipe_tooltip_text: %s meeting subject: '%s'", name, meeting_subject);
5348 SIPE_ADD_BUDDY_INFO(_("Meeting about"), meeting_subject);
5350 if (note)
5352 SIPE_DEBUG_INFO("sipe_tooltip_text: %s note: '%s'", name, note);
5353 SIPE_ADD_BUDDY_INFO_NOESCAPE(is_oof_note ? _("Out of office note") : _("Note"),
5354 g_strdup_printf("<i>%s</i>", note));
5356 if (access_text) {
5357 SIPE_ADD_BUDDY_INFO(_("Access level"), access_text);
5358 g_free(access_text);
5361 return(info);
5364 static PurpleBuddy *
5365 purple_blist_add_buddy_clone(PurpleGroup * group, PurpleBuddy * buddy)
5367 PurpleBuddy *clone;
5368 const gchar *server_alias, *email;
5369 const PurpleStatus *status = purple_presence_get_active_status(purple_buddy_get_presence(buddy));
5371 clone = purple_buddy_new(buddy->account, buddy->name, buddy->alias);
5373 purple_blist_add_buddy(clone, NULL, group, NULL);
5375 server_alias = purple_buddy_get_server_alias(buddy);
5376 if (server_alias) {
5377 purple_blist_server_alias_buddy(clone, server_alias);
5380 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
5381 if (email) {
5382 purple_blist_node_set_string(&clone->node, EMAIL_PROP, email);
5385 purple_presence_set_status_active(purple_buddy_get_presence(clone), purple_status_get_id(status), TRUE);
5386 //for UI to update;
5387 purple_prpl_got_user_status(clone->account, clone->name, purple_status_get_id(status), NULL);
5388 return clone;
5391 static void
5392 sipe_buddy_menu_copy_to_cb(PurpleBlistNode *node, const char *group_name)
5394 PurpleBuddy *buddy, *b;
5395 PurpleConnection *gc;
5396 PurpleGroup * group = purple_find_group(group_name);
5398 g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node));
5400 buddy = (PurpleBuddy *)node;
5402 SIPE_DEBUG_INFO("sipe_buddy_menu_copy_to_cb: copying %s to %s", buddy->name, group_name);
5403 gc = purple_account_get_connection(buddy->account);
5405 b = purple_find_buddy_in_group(buddy->account, buddy->name, group);
5406 if (!b){
5407 b = purple_blist_add_buddy_clone(group, buddy);
5410 sipe_add_buddy(gc, b, group);
5413 static void
5414 sipe_buddy_menu_chat_new_cb(PurpleBuddy *buddy)
5416 struct sipe_core_private *sipe_private = PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE;
5418 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_new_cb: buddy->name=%s", buddy->name);
5420 /* 2007+ conference */
5421 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007))
5423 sipe_conf_add(sipe_private, buddy->name);
5425 else /* 2005- multiparty chat */
5427 gchar *self = sip_uri_self(sipe_private);
5428 struct sip_session *session;
5430 session = sipe_session_add_chat(sipe_private,
5431 NULL,
5432 TRUE,
5433 self);
5434 session->chat_session->backend = sipe_backend_chat_create(SIPE_CORE_PUBLIC,
5435 session->chat_session,
5436 session->chat_session->title,
5437 self);
5438 g_free(self);
5440 sipe_im_invite(sipe_private, session, buddy->name, NULL, NULL, NULL, FALSE);
5445 * For 2007+ conference only.
5447 static void
5448 sipe_buddy_menu_chat_make_leader_cb(PurpleBuddy *buddy,
5449 struct sipe_chat_session *chat_session)
5451 struct sipe_core_private *sipe_private = PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE;
5452 struct sip_session *session;
5454 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_make_leader_cb: buddy->name=%s", buddy->name);
5455 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_make_leader_cb: chat_title=%s", chat_session->title);
5457 session = sipe_session_find_chat(sipe_private, chat_session);
5459 sipe_conf_modify_user_role(sipe_private, session, buddy->name);
5463 * For 2007+ conference only.
5465 static void
5466 sipe_buddy_menu_chat_remove_cb(PurpleBuddy *buddy,
5467 struct sipe_chat_session *chat_session)
5469 struct sipe_core_private *sipe_private = PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE;
5470 struct sip_session *session;
5472 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_remove_cb: buddy->name=%s", buddy->name);
5473 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_remove_cb: chat_title=%s", chat_session->title);
5475 session = sipe_session_find_chat(sipe_private, chat_session);
5477 sipe_conf_delete_user(sipe_private, session, buddy->name);
5480 static void
5481 sipe_buddy_menu_chat_invite_cb(PurpleBuddy *buddy,
5482 struct sipe_chat_session *chat_session)
5484 struct sipe_core_private *sipe_private = PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE;
5486 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_invite_cb: buddy->name=%s", buddy->name);
5487 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_invite_cb: chat_title=%s", chat_session->title);
5489 sipe_core_chat_invite(SIPE_CORE_PUBLIC, chat_session, buddy->name);
5492 static void
5493 sipe_buddy_menu_make_call_cb(PurpleBuddy *buddy, const char *phone)
5495 struct sipe_core_private *sipe_private = PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE;
5497 SIPE_DEBUG_INFO("sipe_buddy_menu_make_call_cb: buddy->name=%s", buddy->name);
5498 if (phone) {
5499 char *tel_uri = sip_to_tel_uri(phone);
5501 SIPE_DEBUG_INFO("sipe_buddy_menu_make_call_cb: going to call number: %s", tel_uri ? tel_uri : "");
5502 sip_csta_make_call(sipe_private, tel_uri);
5504 g_free(tel_uri);
5508 static void
5509 sipe_buddy_menu_access_level_help_cb(PurpleBuddy *buddy)
5511 /** Translators: replace with URL to localized page
5512 * If it doesn't exist copy the original URL */
5513 purple_notify_uri(buddy->account->gc, _("https://sourceforge.net/apps/mediawiki/sipe/index.php?title=Access_Levels"));
5516 static void
5517 sipe_buddy_menu_send_email_cb(PurpleBuddy *buddy)
5519 const gchar *email;
5520 SIPE_DEBUG_INFO("sipe_buddy_menu_send_email_cb: buddy->name=%s", buddy->name);
5522 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
5523 if (email)
5525 char *command_line = g_strdup_printf(
5526 #ifdef _WIN32
5527 "cmd /c start"
5528 #else
5529 "xdg-email"
5530 #endif
5531 " mailto:%s", email);
5532 SIPE_DEBUG_INFO("sipe_buddy_menu_send_email_cb: going to call email client: %s", command_line);
5534 g_spawn_command_line_async(command_line, NULL);
5535 g_free(command_line);
5537 else
5539 SIPE_DEBUG_INFO("sipe_buddy_menu_send_email_cb: no email address stored for buddy=%s", buddy->name);
5543 static void
5544 sipe_buddy_menu_access_level_cb(PurpleBuddy *buddy,
5545 struct sipe_container *container)
5547 struct sipe_core_private *sipe_private = PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE;
5548 struct sipe_container_member *member;
5550 if (!container || !container->members) return;
5552 member = ((struct sipe_container_member *)container->members->data);
5554 if (!member->type) return;
5556 SIPE_DEBUG_INFO("sipe_buddy_menu_access_level_cb: container->id=%d, member->type=%s, member->value=%s",
5557 container->id, member->type, member->value ? member->value : "");
5559 sipe_change_access_level(sipe_private, container->id, member->type, member->value);
5562 static GList *
5563 sipe_get_access_control_menu(struct sipe_core_private *sipe_private,
5564 const char* uri);
5567 * A menu which appear when right-clicking on buddy in contact list.
5569 GList *
5570 sipe_buddy_menu(PurpleBuddy *buddy)
5572 PurpleBlistNode *g_node;
5573 PurpleGroup *gr_parent;
5574 PurpleMenuAction *act;
5575 GList *menu = NULL;
5576 GList *menu_groups = NULL;
5577 struct sipe_core_private *sipe_private = PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE;
5578 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5579 const char *email;
5580 gchar *self = sip_uri_self(sipe_private);
5582 SIPE_SESSION_FOREACH {
5583 if (!sipe_strcase_equal(self, buddy->name) && session->chat_session)
5585 struct sipe_chat_session *chat_session = session->chat_session;
5586 gboolean is_conf = (chat_session->type == SIPE_CHAT_TYPE_CONFERENCE);
5588 if (sipe_backend_chat_find(chat_session->backend, buddy->name))
5590 gboolean conf_op = sipe_backend_chat_is_operator(chat_session->backend, self);
5592 if (is_conf
5593 && !sipe_backend_chat_is_operator(chat_session->backend, buddy->name) /* Not conf OP */
5594 && conf_op) /* We are a conf OP */
5596 gchar *label = g_strdup_printf(_("Make leader of '%s'"),
5597 chat_session->title);
5598 act = purple_menu_action_new(label,
5599 PURPLE_CALLBACK(sipe_buddy_menu_chat_make_leader_cb),
5600 chat_session, NULL);
5601 g_free(label);
5602 menu = g_list_prepend(menu, act);
5605 if (is_conf
5606 && conf_op) /* We are a conf OP */
5608 gchar *label = g_strdup_printf(_("Remove from '%s'"),
5609 chat_session->title);
5610 act = purple_menu_action_new(label,
5611 PURPLE_CALLBACK(sipe_buddy_menu_chat_remove_cb),
5612 chat_session, NULL);
5613 g_free(label);
5614 menu = g_list_prepend(menu, act);
5617 else
5619 if (!is_conf
5620 || (is_conf && !session->locked))
5622 gchar *label = g_strdup_printf(_("Invite to '%s'"),
5623 chat_session->title);
5624 act = purple_menu_action_new(label,
5625 PURPLE_CALLBACK(sipe_buddy_menu_chat_invite_cb),
5626 chat_session, NULL);
5627 g_free(label);
5628 menu = g_list_prepend(menu, act);
5632 } SIPE_SESSION_FOREACH_END;
5634 act = purple_menu_action_new(_("New chat"),
5635 PURPLE_CALLBACK(sipe_buddy_menu_chat_new_cb),
5636 NULL, NULL);
5637 menu = g_list_prepend(menu, act);
5639 if (sip->csta && !sip->csta->line_status) {
5640 const char *phone;
5641 const char *phone_disp_str;
5642 gchar *tmp = NULL;
5643 /* work phone */
5644 phone = purple_blist_node_get_string(&buddy->node, PHONE_PROP);
5645 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_DISPLAY_PROP);
5646 if (phone) {
5647 gchar *label = g_strdup_printf(_("Work %s"),
5648 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
5649 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
5650 g_free(tmp);
5651 tmp = NULL;
5652 g_free(label);
5653 menu = g_list_prepend(menu, act);
5656 /* mobile phone */
5657 phone = purple_blist_node_get_string(&buddy->node, PHONE_MOBILE_PROP);
5658 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_MOBILE_DISPLAY_PROP);
5659 if (phone) {
5660 gchar *label = g_strdup_printf(_("Mobile %s"),
5661 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
5662 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
5663 g_free(tmp);
5664 tmp = NULL;
5665 g_free(label);
5666 menu = g_list_prepend(menu, act);
5669 /* home phone */
5670 phone = purple_blist_node_get_string(&buddy->node, PHONE_HOME_PROP);
5671 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_HOME_DISPLAY_PROP);
5672 if (phone) {
5673 gchar *label = g_strdup_printf(_("Home %s"),
5674 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
5675 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
5676 g_free(tmp);
5677 tmp = NULL;
5678 g_free(label);
5679 menu = g_list_prepend(menu, act);
5682 /* other phone */
5683 phone = purple_blist_node_get_string(&buddy->node, PHONE_OTHER_PROP);
5684 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_OTHER_DISPLAY_PROP);
5685 if (phone) {
5686 gchar *label = g_strdup_printf(_("Other %s"),
5687 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
5688 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
5689 g_free(tmp);
5690 tmp = NULL;
5691 g_free(label);
5692 menu = g_list_prepend(menu, act);
5695 /* custom1 phone */
5696 phone = purple_blist_node_get_string(&buddy->node, PHONE_CUSTOM1_PROP);
5697 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_CUSTOM1_DISPLAY_PROP);
5698 if (phone) {
5699 gchar *label = g_strdup_printf(_("Custom1 %s"),
5700 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
5701 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
5702 g_free(tmp);
5703 tmp = NULL;
5704 g_free(label);
5705 menu = g_list_prepend(menu, act);
5709 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
5710 if (email) {
5711 act = purple_menu_action_new(_("Send email..."),
5712 PURPLE_CALLBACK(sipe_buddy_menu_send_email_cb),
5713 NULL, NULL);
5714 menu = g_list_prepend(menu, act);
5717 /* Access Level */
5718 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
5719 GList *menu_access_levels = sipe_get_access_control_menu(sipe_private, buddy->name);
5721 act = purple_menu_action_new(_("Access level"),
5722 NULL,
5723 NULL, menu_access_levels);
5724 menu = g_list_prepend(menu, act);
5727 /* Copy to */
5728 gr_parent = purple_buddy_get_group(buddy);
5729 for (g_node = purple_blist_get_root(); g_node; g_node = g_node->next) {
5730 PurpleGroup *group;
5732 if (g_node->type != PURPLE_BLIST_GROUP_NODE)
5733 continue;
5735 group = (PurpleGroup *)g_node;
5736 if (group == gr_parent)
5737 continue;
5739 if (purple_find_buddy_in_group(buddy->account, buddy->name, group))
5740 continue;
5742 act = purple_menu_action_new(purple_group_get_name(group),
5743 PURPLE_CALLBACK(sipe_buddy_menu_copy_to_cb),
5744 group->name, NULL);
5745 menu_groups = g_list_prepend(menu_groups, act);
5747 menu_groups = g_list_reverse(menu_groups);
5749 act = purple_menu_action_new(_("Copy to"),
5750 NULL,
5751 NULL, menu_groups);
5752 menu = g_list_prepend(menu, act);
5754 menu = g_list_reverse(menu);
5756 g_free(self);
5757 return menu;
5760 static void
5761 sipe_ask_access_domain_cb(PurpleConnection *gc, PurpleRequestFields *fields)
5763 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
5764 const char *domain = purple_request_fields_get_string(fields, "access_domain");
5765 int index = purple_request_fields_get_choice(fields, "container_id");
5766 /* move Blocked first */
5767 int i = (index == 4) ? 0 : index + 1;
5768 int container_id = containers[i];
5770 SIPE_DEBUG_INFO("sipe_ask_access_domain_cb: domain=%s, container_id=(%d)%d", domain ? domain : "", index, container_id);
5772 sipe_change_access_level(sipe_private, container_id, "domain", domain);
5775 static void
5776 sipe_ask_access_domain(struct sipe_core_private *sipe_private)
5778 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5779 PurpleAccount *account = sip->account;
5780 PurpleConnection *gc = sip->gc;
5781 PurpleRequestFields *fields;
5782 PurpleRequestFieldGroup *g;
5783 PurpleRequestField *f;
5785 fields = purple_request_fields_new();
5787 g = purple_request_field_group_new(NULL);
5788 f = purple_request_field_string_new("access_domain", _("Domain"), "partner-company.com", FALSE);
5789 purple_request_field_set_required(f, TRUE);
5790 purple_request_field_group_add_field(g, f);
5792 f = purple_request_field_choice_new("container_id", _("Access level"), 0);
5793 purple_request_field_choice_add(f, _("Personal")); /* index 0 */
5794 purple_request_field_choice_add(f, _("Team"));
5795 purple_request_field_choice_add(f, _("Company"));
5796 purple_request_field_choice_add(f, _("Public"));
5797 purple_request_field_choice_add(f, _("Blocked")); /* index 4 */
5798 purple_request_field_choice_set_default_value(f, 3); /* index */
5799 purple_request_field_set_required(f, TRUE);
5800 purple_request_field_group_add_field(g, f);
5802 purple_request_fields_add_group(fields, g);
5804 purple_request_fields(gc, _("Add new domain"),
5805 _("Add new domain"), NULL, fields,
5806 _("Add"), G_CALLBACK(sipe_ask_access_domain_cb),
5807 _("Cancel"), NULL,
5808 account, NULL, NULL, gc);
5811 static void
5812 sipe_buddy_menu_access_level_add_domain_cb(PurpleBuddy *buddy)
5814 sipe_ask_access_domain(PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE);
5818 * Workaround for missing libpurple API to release resources allocated
5819 * during blist_node_menu() callback. See also:
5821 * <http://developer.pidgin.im/ticket/12597>
5823 * We remember all memory blocks in a list and deallocate them when
5825 * - the next time we enter the callback, or
5826 * - the account is disconnected
5828 * That means that after the buddy menu has been closed we have unused
5829 * resources but at least we don't leak them anymore...
5831 static void
5832 sipe_blist_menu_free_containers(struct sipe_core_private *sipe_private)
5834 GSList *entry = sipe_private->blist_menu_containers;
5835 while (entry) {
5836 free_container(entry->data);
5837 entry = entry->next;
5839 g_slist_free(sipe_private->blist_menu_containers);
5840 sipe_private->blist_menu_containers = NULL;
5843 static void
5844 sipe_blist_menu_remember_container(struct sipe_core_private *sipe_private,
5845 struct sipe_container *container)
5847 sipe_private->blist_menu_containers = g_slist_prepend(sipe_private->blist_menu_containers,
5848 container);
5851 static GList *
5852 sipe_get_access_levels_menu(struct sipe_core_private *sipe_private,
5853 const char* member_type,
5854 const char* member_value,
5855 const gboolean extra_menu)
5857 GList *menu_access_levels = NULL;
5858 unsigned int i;
5859 char *menu_name;
5860 PurpleMenuAction *act;
5861 struct sipe_container *container;
5862 struct sipe_container_member *member;
5863 gboolean is_group_access = FALSE;
5864 int container_id = sipe_find_access_level(sipe_private, member_type, member_value, &is_group_access);
5866 for (i = 1; i <= CONTAINERS_LEN; i++) {
5867 /* to put Blocked level last in menu list.
5868 * Blocked should remaim in the first place in the containers[] array.
5870 unsigned int j = (i == CONTAINERS_LEN) ? 0 : i;
5871 const char *acc_level_name = sipe_get_access_level_name(containers[j]);
5873 container = g_new0(struct sipe_container, 1);
5874 member = g_new0(struct sipe_container_member, 1);
5875 container->id = containers[j];
5876 container->members = g_slist_append(container->members, member);
5877 member->type = g_strdup(member_type);
5878 member->value = g_strdup(member_value);
5880 /* libpurple memory leak workaround */
5881 sipe_blist_menu_remember_container(sipe_private, container);
5883 /* current container/access level */
5884 if (((int)containers[j]) == container_id) {
5885 menu_name = is_group_access ?
5886 g_strdup_printf(INDENT_MARKED_INHERITED_FMT, acc_level_name) :
5887 g_strdup_printf(INDENT_MARKED_FMT, acc_level_name);
5888 } else {
5889 menu_name = g_strdup_printf(INDENT_FMT, acc_level_name);
5892 act = purple_menu_action_new(menu_name,
5893 PURPLE_CALLBACK(sipe_buddy_menu_access_level_cb),
5894 container, NULL);
5895 g_free(menu_name);
5896 menu_access_levels = g_list_prepend(menu_access_levels, act);
5899 if (extra_menu && (container_id >= 0)) {
5900 /* separator */
5901 act = purple_menu_action_new(" --------------", NULL, NULL, NULL);
5902 menu_access_levels = g_list_prepend(menu_access_levels, act);
5904 if (!is_group_access) {
5905 container = g_new0(struct sipe_container, 1);
5906 member = g_new0(struct sipe_container_member, 1);
5907 container->id = -1;
5908 container->members = g_slist_append(container->members, member);
5909 member->type = g_strdup(member_type);
5910 member->value = g_strdup(member_value);
5912 /* libpurple memory leak workaround */
5913 sipe_blist_menu_remember_container(sipe_private, container);
5915 /* Translators: remove (clear) previously assigned access level */
5916 menu_name = g_strdup_printf(INDENT_FMT, _("Unspecify"));
5917 act = purple_menu_action_new(menu_name,
5918 PURPLE_CALLBACK(sipe_buddy_menu_access_level_cb),
5919 container, NULL);
5920 g_free(menu_name);
5921 menu_access_levels = g_list_prepend(menu_access_levels, act);
5925 menu_access_levels = g_list_reverse(menu_access_levels);
5926 return menu_access_levels;
5929 static GList *
5930 sipe_get_access_groups_menu(struct sipe_core_private *sipe_private)
5932 GList *menu_access_groups = NULL;
5933 PurpleMenuAction *act;
5934 GSList *access_domains = NULL;
5935 GSList *entry;
5936 char *menu_name;
5937 char *domain;
5939 act = purple_menu_action_new(_("People in my company"),
5940 NULL,
5941 NULL, sipe_get_access_levels_menu(sipe_private, "sameEnterprise", NULL, FALSE));
5942 menu_access_groups = g_list_prepend(menu_access_groups, act);
5944 /* this is original name, don't edit */
5945 act = purple_menu_action_new(_("People in domains connected with my company"),
5946 NULL,
5947 NULL, sipe_get_access_levels_menu(sipe_private, "federated", NULL, FALSE));
5948 menu_access_groups = g_list_prepend(menu_access_groups, act);
5950 act = purple_menu_action_new(_("People in public domains"),
5951 NULL,
5952 NULL, sipe_get_access_levels_menu(sipe_private, "publicCloud", NULL, TRUE));
5953 menu_access_groups = g_list_prepend(menu_access_groups, act);
5955 access_domains = sipe_get_access_domains(sipe_private);
5956 entry = access_domains;
5957 while (entry) {
5958 domain = entry->data;
5960 menu_name = g_strdup_printf(_("People at %s"), domain);
5961 act = purple_menu_action_new(menu_name,
5962 NULL,
5963 NULL, sipe_get_access_levels_menu(sipe_private, "domain", g_strdup(domain), TRUE));
5964 menu_access_groups = g_list_prepend(menu_access_groups, act);
5965 g_free(menu_name);
5967 entry = entry->next;
5970 /* separator */
5971 /* People in domains connected with my company */
5972 act = purple_menu_action_new("-------------------------------------------", NULL, NULL, NULL);
5973 menu_access_groups = g_list_prepend(menu_access_groups, act);
5975 act = purple_menu_action_new(_("Add new domain..."),
5976 PURPLE_CALLBACK(sipe_buddy_menu_access_level_add_domain_cb),
5977 NULL, NULL);
5978 menu_access_groups = g_list_prepend(menu_access_groups, act);
5980 menu_access_groups = g_list_reverse(menu_access_groups);
5982 return menu_access_groups;
5985 static GList *
5986 sipe_get_access_control_menu(struct sipe_core_private *sipe_private,
5987 const char* uri)
5989 GList *menu_access_levels = NULL;
5990 GList *menu_access_groups = NULL;
5991 char *menu_name;
5992 PurpleMenuAction *act;
5994 /* libpurple memory leak workaround */
5995 sipe_blist_menu_free_containers(sipe_private);
5997 menu_access_levels = sipe_get_access_levels_menu(sipe_private, "user", sipe_get_no_sip_uri(uri), TRUE);
5999 menu_access_groups = sipe_get_access_groups_menu(sipe_private);
6001 menu_name = g_strdup_printf(INDENT_FMT, _("Access groups"));
6002 act = purple_menu_action_new(menu_name,
6003 NULL,
6004 NULL, menu_access_groups);
6005 g_free(menu_name);
6006 menu_access_levels = g_list_append(menu_access_levels, act);
6008 menu_name = g_strdup_printf(INDENT_FMT, _("Online help..."));
6009 act = purple_menu_action_new(menu_name,
6010 PURPLE_CALLBACK(sipe_buddy_menu_access_level_help_cb),
6011 NULL, NULL);
6012 g_free(menu_name);
6013 menu_access_levels = g_list_append(menu_access_levels, act);
6015 return menu_access_levels;
6018 static gboolean
6019 process_get_info_response(struct sipe_core_private *sipe_private,
6020 struct sipmsg *msg, struct transaction *trans)
6022 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
6023 char *uri = trans->payload->data;
6025 PurpleNotifyUserInfo *info;
6026 PurpleBuddy *pbuddy = NULL;
6027 struct sipe_buddy *sbuddy;
6028 const char *alias = NULL;
6029 char *device_name = NULL;
6030 char *server_alias = NULL;
6031 char *phone_number = NULL;
6032 char *email = NULL;
6033 const char *site;
6034 char *first_name = NULL;
6035 char *last_name = NULL;
6037 if (!sip) return FALSE;
6039 SIPE_DEBUG_INFO("Fetching %s's user info for %s", uri, sipe_private->username);
6041 pbuddy = purple_find_buddy((PurpleAccount *)sip->account, uri);
6042 alias = purple_buddy_get_local_alias(pbuddy);
6044 //will query buddy UA's capabilities and send answer to log
6045 sipe_options_request(sipe_private, uri);
6047 sbuddy = g_hash_table_lookup(sipe_private->buddies, uri);
6048 if (sbuddy) {
6049 device_name = sbuddy->device_name ? g_strdup(sbuddy->device_name) : NULL;
6052 info = purple_notify_user_info_new();
6054 if (msg->response != 200) {
6055 SIPE_DEBUG_INFO("process_get_info_response: SERVICE response is %d", msg->response);
6056 } else {
6057 sipe_xml *searchResults;
6058 const sipe_xml *mrow;
6060 SIPE_DEBUG_INFO("process_get_info_response: body:\n%s", msg->body ? msg->body : "");
6061 searchResults = sipe_xml_parse(msg->body, msg->bodylen);
6062 if (!searchResults) {
6063 SIPE_DEBUG_INFO_NOFORMAT("process_get_info_response: no parseable searchResults");
6064 } else if ((mrow = sipe_xml_child(searchResults, "Body/Array/row"))) {
6065 const char *value;
6066 server_alias = g_strdup(sipe_xml_attribute(mrow, "displayName"));
6067 email = g_strdup(sipe_xml_attribute(mrow, "email"));
6068 phone_number = g_strdup(sipe_xml_attribute(mrow, "phone"));
6070 /* For 2007 system we will take this from ContactCard -
6071 * it has cleaner tel: URIs at least
6073 if (!SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
6074 char *tel_uri = sip_to_tel_uri(phone_number);
6075 /* trims its parameters, so call first */
6076 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_DISPLAY_NAME, server_alias);
6077 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_EMAIL, email);
6078 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_WORK_PHONE, tel_uri);
6079 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_WORK_PHONE_DISPLAY, phone_number);
6080 g_free(tel_uri);
6083 #if PURPLE_VERSION_CHECK(3,0,0)
6084 #define PURPLE_NOTIFY_USER_INFO_ADD_PAIR purple_notify_user_info_add_pair_html
6085 #else
6086 #define PURPLE_NOTIFY_USER_INFO_ADD_PAIR purple_notify_user_info_add_pair
6087 #endif
6089 if (server_alias && strlen(server_alias) > 0) {
6090 PURPLE_NOTIFY_USER_INFO_ADD_PAIR(info, _("Display name"), server_alias);
6092 if ((value = sipe_xml_attribute(mrow, "title")) && strlen(value) > 0) {
6093 PURPLE_NOTIFY_USER_INFO_ADD_PAIR(info, _("Job title"), value);
6095 if ((value = sipe_xml_attribute(mrow, "office")) && strlen(value) > 0) {
6096 PURPLE_NOTIFY_USER_INFO_ADD_PAIR(info, _("Office"), value);
6098 if (phone_number && strlen(phone_number) > 0) {
6099 PURPLE_NOTIFY_USER_INFO_ADD_PAIR(info, _("Business phone"), phone_number);
6101 if ((value = sipe_xml_attribute(mrow, "company")) && strlen(value) > 0) {
6102 PURPLE_NOTIFY_USER_INFO_ADD_PAIR(info, _("Company"), value);
6104 if ((value = sipe_xml_attribute(mrow, "city")) && strlen(value) > 0) {
6105 PURPLE_NOTIFY_USER_INFO_ADD_PAIR(info, _("City"), value);
6107 if ((value = sipe_xml_attribute(mrow, "state")) && strlen(value) > 0) {
6108 PURPLE_NOTIFY_USER_INFO_ADD_PAIR(info, _("State"), value);
6110 if ((value = sipe_xml_attribute(mrow, "country")) && strlen(value) > 0) {
6111 PURPLE_NOTIFY_USER_INFO_ADD_PAIR(info, _("Country"), value);
6113 if (email && strlen(email) > 0) {
6114 PURPLE_NOTIFY_USER_INFO_ADD_PAIR(info, _("Email address"), email);
6118 sipe_xml_free(searchResults);
6121 purple_notify_user_info_add_section_break(info);
6123 if (is_empty(server_alias)) {
6124 g_free(server_alias);
6125 server_alias = g_strdup(purple_buddy_get_server_alias(pbuddy));
6126 if (server_alias) {
6127 PURPLE_NOTIFY_USER_INFO_ADD_PAIR(info, _("Display name"), server_alias);
6131 /* present alias if it differs from server alias */
6132 if (alias && !sipe_strequal(alias, server_alias))
6134 PURPLE_NOTIFY_USER_INFO_ADD_PAIR(info, _("Alias"), alias);
6137 if (is_empty(email)) {
6138 g_free(email);
6139 email = g_strdup(purple_blist_node_get_string(&pbuddy->node, EMAIL_PROP));
6140 if (email) {
6141 PURPLE_NOTIFY_USER_INFO_ADD_PAIR(info, _("Email address"), email);
6145 site = purple_blist_node_get_string(&pbuddy->node, SITE_PROP);
6146 if (site) {
6147 PURPLE_NOTIFY_USER_INFO_ADD_PAIR(info, _("Site"), site);
6150 sipe_get_first_last_names(sipe_private, uri, &first_name, &last_name);
6151 if (first_name && last_name) {
6152 char *link = g_strconcat("http://www.linkedin.com/pub/dir/", first_name, "/", last_name, NULL);
6154 PURPLE_NOTIFY_USER_INFO_ADD_PAIR(info, _("Find on LinkedIn"), link);
6155 g_free(link);
6157 g_free(first_name);
6158 g_free(last_name);
6160 if (device_name) {
6161 PURPLE_NOTIFY_USER_INFO_ADD_PAIR(info, _("Device"), device_name);
6164 /* show a buddy's user info in a nice dialog box */
6165 purple_notify_userinfo(sip->gc, /* connection the buddy info came through */
6166 uri, /* buddy's URI */
6167 info, /* body */
6168 NULL, /* callback called when dialog closed */
6169 NULL); /* userdata for callback */
6171 g_free(phone_number);
6172 g_free(server_alias);
6173 g_free(email);
6174 g_free(device_name);
6176 return TRUE;
6180 * AD search first, LDAP based
6182 void sipe_get_info(PurpleConnection *gc, const char *username)
6184 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
6185 gchar *domain_uri = sip_uri_from_name(sipe_private->public.sip_domain);
6186 char *row = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, "msRTCSIP-PrimaryUserAddress", username);
6187 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 1, row);
6188 struct transaction_payload *payload = g_new0(struct transaction_payload, 1);
6190 payload->destroy = g_free;
6191 payload->data = g_strdup(username);
6193 SIPE_DEBUG_INFO("sipe_get_contact_data: body:\n%s", body ? body : "");
6194 send_soap_request_with_cb(sipe_private, domain_uri, body,
6195 process_get_info_response, payload);
6196 g_free(domain_uri);
6197 g_free(body);
6198 g_free(row);
6202 Local Variables:
6203 mode: c
6204 c-file-style: "bsd"
6205 indent-tabs-mode: t
6206 tab-width: 8
6207 End: