core cleanup: move [MS-SIP] deltanum counters to sipe_private
[siplcs.git] / src / core / sipe.c
blob7f2a87a73f0f8d814412bb9340c12fed29cd363b
1 /**
2 * @file sipe.c
4 *****************************************************************************
5 *** !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ***
6 *** ***
7 *** THIS MODULE IS DEPECRATED ***
8 *** ***
9 *** DO NOT ADD ANY NEW CODE TO THIS MODULE ***
10 *** ***
11 *** !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ***
12 *****************************************************************************
14 * pidgin-sipe
16 * Copyright (C) 2010-11 SIPE Project <http://sipe.sourceforge.net/>
17 * Copyright (C) 2010 pier11 <pier11@operamail.com>
18 * Copyright (C) 2009 Anibal Avelar <debianmx@gmail.com>
19 * Copyright (C) 2009 pier11 <pier11@operamail.com>
20 * Copyright (C) 2008 Novell, Inc., Anibal Avelar <debianmx@gmail.com>
21 * Copyright (C) 2007 Anibal Avelar <debianmx@gmail.com>
22 * Copyright (C) 2005 Thomas Butter <butter@uni-mannheim.de>
24 * ***
25 * Thanks to Google's Summer of Code Program and the helpful mentors
26 * ***
28 * Session-based SIP MESSAGE documentation:
29 * http://tools.ietf.org/html/draft-ietf-simple-im-session-00
31 * This program is free software; you can redistribute it and/or modify
32 * it under the terms of the GNU General Public License as published by
33 * the Free Software Foundation; either version 2 of the License, or
34 * (at your option) any later version.
36 * This program is distributed in the hope that it will be useful,
37 * but WITHOUT ANY WARRANTY; without even the implied warranty of
38 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
39 * GNU General Public License for more details.
41 * You should have received a copy of the GNU General Public License
42 * along with this program; if not, write to the Free Software
43 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
46 #ifdef HAVE_CONFIG_H
47 #include "config.h"
48 #endif
50 #include <time.h>
51 #include <stdlib.h>
52 #include <stdio.h>
53 #include <errno.h>
54 #include <string.h>
55 #include <unistd.h>
57 #include <glib.h>
59 #include <libintl.h>
61 #include "sipe-common.h"
63 #include "account.h"
64 #include "blist.h"
65 #include "connection.h"
66 #include "conversation.h"
67 #include "ft.h"
68 #include "notify.h"
69 #include "plugin.h"
70 #include "privacy.h"
71 #include "request.h"
72 #include "savedstatuses.h"
73 #include "version.h"
75 #include "core-depurple.h" /* Temporary for the core de-purple transition */
77 #include "http-conn.h"
78 #include "sipmsg.h"
79 #include "sip-csta.h"
80 #include "sip-soap.h"
81 #include "sip-transport.h"
82 #include "sipe-backend.h"
83 #include "sipe-buddy.h"
84 #include "sipe-cal.h"
85 #include "sipe-chat.h"
86 #include "sipe-conf.h"
87 #include "sipe-core.h"
88 #include "sipe-core-private.h"
89 #include "sipe-group.h"
90 #include "sipe-dialog.h"
91 #include "sipe-ews.h"
92 #include "sipe-domino.h"
93 #include "sipe-groupchat.h"
94 #include "sipe-im.h"
95 #include "sipe-mime.h"
96 #include "sipe-nls.h"
97 #include "sipe-schedule.h"
98 #include "sipe-session.h"
99 #include "sipe-subscriptions.h"
100 #ifdef HAVE_VV
101 #include "sipe-media.h"
102 #endif
103 #include "sipe-utils.h"
104 #include "sipe-xml.h"
105 #include "uuid.h"
106 #include "sipe.h"
108 #define SIPE_IDLE_SET_DELAY 1 /* 1 sec */
110 #define UPDATE_CALENDAR_DELAY 1*60 /* 1 min */
111 #define UPDATE_CALENDAR_INTERVAL 30*60 /* 30 min */
113 /* Status identifiers (see also: sipe_status_types()) */
114 #define SIPE_STATUS_ID_UNKNOWN purple_primitive_get_id_from_type(PURPLE_STATUS_UNSET) /* Unset (primitive) */
115 #define SIPE_STATUS_ID_OFFLINE purple_primitive_get_id_from_type(PURPLE_STATUS_OFFLINE) /* Offline (primitive) */
116 #define SIPE_STATUS_ID_AVAILABLE purple_primitive_get_id_from_type(PURPLE_STATUS_AVAILABLE) /* Online */
117 /* PURPLE_STATUS_UNAVAILABLE: */
118 #define SIPE_STATUS_ID_BUSY "busy" /* Busy */
119 #define SIPE_STATUS_ID_BUSYIDLE "busyidle" /* BusyIdle */
120 #define SIPE_STATUS_ID_DND "do-not-disturb" /* Do Not Disturb */
121 #define SIPE_STATUS_ID_IN_MEETING "in-a-meeting" /* In a meeting */
122 #define SIPE_STATUS_ID_IN_CONF "in-a-conference" /* In a conference */
123 #define SIPE_STATUS_ID_ON_PHONE "on-the-phone" /* On the phone */
124 #define SIPE_STATUS_ID_INVISIBLE purple_primitive_get_id_from_type(PURPLE_STATUS_INVISIBLE) /* Appear Offline */
125 /* PURPLE_STATUS_AWAY: */
126 #define SIPE_STATUS_ID_IDLE "idle" /* Idle/Inactive */
127 #define SIPE_STATUS_ID_BRB "be-right-back" /* Be Right Back */
128 #define SIPE_STATUS_ID_AWAY purple_primitive_get_id_from_type(PURPLE_STATUS_AWAY) /* Away (primitive) */
129 /** Reuters status (user settable) */
130 #define SIPE_STATUS_ID_LUNCH "out-to-lunch" /* Out To Lunch */
131 /* ??? PURPLE_STATUS_EXTENDED_AWAY */
132 /* ??? PURPLE_STATUS_MOBILE */
133 /* ??? PURPLE_STATUS_TUNE */
135 /* Status attributes (see also sipe_status_types() */
136 #define SIPE_STATUS_ATTR_ID_MESSAGE "message"
138 static struct sipe_activity_map_struct
140 sipe_activity type;
141 const char *token;
142 const char *desc;
143 const char *status_id;
145 } const sipe_activity_map[] =
147 /* This has nothing to do with Availability numbers, like 3500 (online).
148 * Just a mapping of Communicator Activities to Purple statuses to be able display them in Pidgin.
150 { SIPE_ACTIVITY_UNSET, "unset", NULL , NULL },
151 { SIPE_ACTIVITY_ONLINE, "online", NULL , NULL },
152 { SIPE_ACTIVITY_INACTIVE, SIPE_STATUS_ID_IDLE, N_("Inactive") , NULL },
153 { SIPE_ACTIVITY_BUSY, SIPE_STATUS_ID_BUSY, N_("Busy") , SIPE_STATUS_ID_BUSY },
154 { SIPE_ACTIVITY_BUSYIDLE, SIPE_STATUS_ID_BUSYIDLE, N_("Busy-Idle") , NULL },
155 { SIPE_ACTIVITY_DND, SIPE_STATUS_ID_DND, NULL , SIPE_STATUS_ID_DND },
156 { SIPE_ACTIVITY_BRB, SIPE_STATUS_ID_BRB, N_("Be right back") , SIPE_STATUS_ID_BRB },
157 { SIPE_ACTIVITY_AWAY, "away", NULL , NULL },
158 { SIPE_ACTIVITY_LUNCH, SIPE_STATUS_ID_LUNCH, N_("Out to lunch") , NULL },
159 { SIPE_ACTIVITY_OFFLINE, "offline", NULL , NULL },
160 { SIPE_ACTIVITY_ON_PHONE, SIPE_STATUS_ID_ON_PHONE, N_("In a call") , NULL },
161 { SIPE_ACTIVITY_IN_CONF, SIPE_STATUS_ID_IN_CONF, N_("In a conference") , NULL },
162 { SIPE_ACTIVITY_IN_MEETING, SIPE_STATUS_ID_IN_MEETING, N_("In a meeting") , NULL },
163 { SIPE_ACTIVITY_OOF, "out-of-office", N_("Out of office") , NULL },
164 { SIPE_ACTIVITY_URGENT_ONLY, "urgent-interruptions-only", N_("Urgent interruptions only") , NULL }
166 /** @param x is sipe_activity */
167 #define SIPE_ACTIVITY_I18N(x) gettext(sipe_activity_map[x].desc)
169 static sipe_activity
170 sipe_get_activity_by_token(const char *token)
172 int i;
174 for (i = 0; i < SIPE_ACTIVITY_NUM_TYPES; i++)
176 if (sipe_strequal(token, sipe_activity_map[i].token))
177 return sipe_activity_map[i].type;
180 return sipe_activity_map[0].type;
183 static const char *
184 sipe_get_activity_desc_by_token(const char *token)
186 if (!token) return NULL;
188 return SIPE_ACTIVITY_I18N(sipe_get_activity_by_token(token));
191 static void send_presence_status(struct sipe_core_private *sipe_private,
192 void *unused);
195 * Returns pointer to URI without sip: prefix if any
197 * @param sip_uri SIP URI possibly with sip: prefix. Example: sip:first.last@hq.company.com
198 * @return pointer to URL without sip: prefix. Coresponding example: first.last@hq.company.com
200 * Doesn't allocate memory
202 static const char *
203 sipe_get_no_sip_uri(const char *sip_uri)
205 const char *prefix = "sip:";
206 if (!sip_uri) return NULL;
208 if (g_str_has_prefix(sip_uri, prefix)) {
209 return (sip_uri+strlen(prefix));
210 } else {
211 return sip_uri;
215 static void
216 sipe_change_access_level(struct sipe_core_private *sipe_private,
217 const int container_id,
218 const gchar *type,
219 const gchar *value);
221 void
222 sipe_core_contact_allow_deny (struct sipe_core_public *sipe_public,
223 const gchar * who, gboolean allow)
225 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
227 if (allow) {
228 SIPE_DEBUG_INFO("Authorizing contact %s", who);
229 } else {
230 SIPE_DEBUG_INFO("Blocking contact %s", who);
233 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
234 sipe_change_access_level(sipe_private, (allow ? -1 : 32000), "user", sipe_get_no_sip_uri(who));
235 } else {
236 sip_soap_ocs2005_setacl(sipe_private, who, allow);
240 static
241 void sipe_auth_user_cb(void * data)
243 struct sipe_auth_job * job = (struct sipe_auth_job *) data;
244 if (!job) return;
246 sipe_core_contact_allow_deny((struct sipe_core_public *)job->sipe_private, job->who, TRUE);
247 g_free(job);
250 static
251 void sipe_deny_user_cb(void * data)
253 struct sipe_auth_job * job = (struct sipe_auth_job *) data;
254 if (!job) return;
256 sipe_core_contact_allow_deny((struct sipe_core_public *)job->sipe_private, job->who, FALSE);
257 g_free(job);
260 /** @applicable: 2005-
262 static void
263 sipe_process_presence_wpending (struct sipe_core_private *sipe_private,
264 struct sipmsg * msg)
266 sipe_xml *watchers;
267 const sipe_xml *watcher;
268 // Ensure it's either not a response (eg it's a BENOTIFY) or that it's a 200 OK response
269 if (msg->response != 0 && msg->response != 200) return;
271 if (msg->bodylen == 0 || msg->body == NULL || sipe_strequal(sipmsg_find_header(msg, "Event"), "msrtc.wpending")) return;
273 watchers = sipe_xml_parse(msg->body, msg->bodylen);
274 if (!watchers) return;
276 for (watcher = sipe_xml_child(watchers, "watcher"); watcher; watcher = sipe_xml_twin(watcher)) {
277 gchar * remote_user = g_strdup(sipe_xml_attribute(watcher, "uri"));
278 gchar * alias = g_strdup(sipe_xml_attribute(watcher, "displayName"));
279 gboolean on_list = g_hash_table_lookup(sipe_private->buddies, remote_user) != NULL;
281 // TODO pull out optional displayName to pass as alias
282 if (remote_user) {
283 struct sipe_auth_job * job = g_new0(struct sipe_auth_job, 1);
284 job->who = remote_user;
285 job->sipe_private = sipe_private;
286 sipe_backend_buddy_request_authorization(SIPE_CORE_PUBLIC,
287 remote_user,
288 alias,
289 on_list,
290 sipe_auth_user_cb,
291 sipe_deny_user_cb,
292 (gpointer)job);
297 sipe_xml_free(watchers);
298 return;
302 * Returns string like "2 4 7 8" - group ids buddy belong to.
304 static gchar *
305 sipe_get_buddy_groups_string (struct sipe_buddy *buddy) {
306 int i = 0;
307 gchar *res;
308 //creating array from GList, converting int to gchar*
309 gchar **ids_arr = g_new(gchar *, g_slist_length(buddy->groups) + 1);
310 GSList *entry = buddy->groups;
312 if (!ids_arr) return NULL;
314 while (entry) {
315 struct sipe_group * group = entry->data;
316 ids_arr[i] = g_strdup_printf("%d", group->id);
317 entry = entry->next;
318 i++;
320 ids_arr[i] = NULL;
321 res = g_strjoinv(" ", ids_arr);
322 g_strfreev(ids_arr);
323 return res;
327 * Sends buddy update to server
329 void
330 sipe_core_group_set_user(struct sipe_core_public *sipe_public,
331 const gchar *who)
333 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
334 struct sipe_buddy *buddy = g_hash_table_lookup(sipe_private->buddies, who);
335 sipe_backend_buddy backend_buddy = sipe_backend_buddy_find(sipe_public, who, NULL);
337 if (buddy && backend_buddy) {
338 gchar *alias = sipe_backend_buddy_get_alias(sipe_public, backend_buddy);
339 gchar *groups = sipe_get_buddy_groups_string(buddy);
340 if (groups) {
341 gchar *request;
342 SIPE_DEBUG_INFO("Saving buddy %s with alias %s and groups %s", who, alias, groups);
344 /* alias can contain restricted characters */
345 request = g_markup_printf_escaped("<m:displayName>%s</m:displayName>"
346 "<m:groups>%s</m:groups>"
347 "<m:subscribed>true</m:subscribed>"
348 "<m:URI>%s</m:URI>"
349 "<m:externalURI />",
350 alias, groups, buddy->name);
351 sip_soap_request(sipe_private,
352 "setContact",
353 request);
354 g_free(request);
356 g_free(alias);
360 static void
361 sipe_sched_calendar_status_update(struct sipe_core_private *sipe_private,
362 time_t calculate_from);
364 static int
365 sipe_get_availability_by_status(const char* sipe_status_id, char** activity_token);
367 static const char*
368 sipe_get_status_by_availability(int avail,
369 char** activity);
371 static void
372 sipe_set_purple_account_status_and_note(const PurpleAccount *account,
373 const char *status_id,
374 const char *message,
375 time_t do_not_publish[]);
377 static void
378 sipe_apply_calendar_status(struct sipe_core_private *sipe_private,
379 struct sipe_buddy *sbuddy,
380 const char *status_id)
382 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
383 time_t cal_avail_since;
384 int cal_status = sipe_cal_get_status(sbuddy, time(NULL), &cal_avail_since);
385 int avail;
386 gchar *self_uri;
388 if (!sbuddy) return;
390 if (cal_status < SIPE_CAL_NO_DATA) {
391 SIPE_DEBUG_INFO("sipe_apply_calendar_status: cal_status : %d for %s", cal_status, sbuddy->name);
392 SIPE_DEBUG_INFO("sipe_apply_calendar_status: cal_avail_since : %s", asctime(localtime(&cal_avail_since)));
395 /* scheduled Cal update call */
396 if (!status_id) {
397 status_id = sbuddy->last_non_cal_status_id;
398 g_free(sbuddy->activity);
399 sbuddy->activity = g_strdup(sbuddy->last_non_cal_activity);
402 if (!status_id) {
403 SIPE_DEBUG_INFO("sipe_apply_calendar_status: status_id is NULL for %s, exiting.",
404 sbuddy->name ? sbuddy->name : "" );
405 return;
408 /* adjust to calendar status */
409 if (cal_status != SIPE_CAL_NO_DATA) {
410 SIPE_DEBUG_INFO("sipe_apply_calendar_status: user_avail_since: %s", asctime(localtime(&sbuddy->user_avail_since)));
412 if (cal_status == SIPE_CAL_BUSY
413 && cal_avail_since > sbuddy->user_avail_since
414 && 6500 >= sipe_get_availability_by_status(status_id, NULL))
416 status_id = SIPE_STATUS_ID_BUSY;
417 g_free(sbuddy->activity);
418 sbuddy->activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_IN_MEETING));
420 avail = sipe_get_availability_by_status(status_id, NULL);
422 SIPE_DEBUG_INFO("sipe_apply_calendar_status: activity_since : %s", asctime(localtime(&sbuddy->activity_since)));
423 if (cal_avail_since > sbuddy->activity_since) {
424 if (cal_status == SIPE_CAL_OOF
425 && avail >= 15000) /* 12000 in 2007 */
427 g_free(sbuddy->activity);
428 sbuddy->activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_OOF));
433 /* then set status_id actually */
434 SIPE_DEBUG_INFO("sipe_apply_calendar_status: to %s for %s", status_id, sbuddy->name ? sbuddy->name : "" );
435 sipe_backend_buddy_set_status(SIPE_CORE_PUBLIC, sbuddy->name, status_id);
437 /* set our account state to the one in roaming (including calendar info) */
438 self_uri = sip_uri_self(sipe_private);
439 if (sip->initial_state_published && sipe_strcase_equal(sbuddy->name, self_uri)) {
440 if (sipe_strequal(status_id, SIPE_STATUS_ID_OFFLINE)) {
441 status_id = g_strdup(SIPE_STATUS_ID_INVISIBLE); /* not not let offline status switch us off */
444 SIPE_DEBUG_INFO("sipe_apply_calendar_status: switch to '%s' for the account", sip->status);
445 sipe_set_purple_account_status_and_note(sip->account, status_id, sip->note, sip->do_not_publish);
447 g_free(self_uri);
450 void
451 sipe_core_buddy_got_status(struct sipe_core_public *sipe_public,
452 const gchar* uri,
453 const gchar *status_id)
455 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
456 struct sipe_buddy *sbuddy = g_hash_table_lookup(sipe_private->buddies, uri);
458 if (!sbuddy) return;
460 /* Check if on 2005 system contact's calendar,
461 * then set/preserve it.
463 if (!SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
464 sipe_apply_calendar_status(sipe_private, sbuddy, status_id);
465 } else {
466 sipe_backend_buddy_set_status(sipe_public, uri, status_id);
470 static void
471 update_calendar_status_cb(SIPE_UNUSED_PARAMETER char *name,
472 struct sipe_buddy *sbuddy,
473 struct sipe_core_private *sipe_private)
475 sipe_apply_calendar_status(sipe_private, sbuddy, NULL);
479 * Updates contact's status
480 * based on their calendar information.
482 * Applicability: 2005 systems
484 static void
485 update_calendar_status(struct sipe_core_private *sipe_private,
486 SIPE_UNUSED_PARAMETER void *unused)
488 SIPE_DEBUG_INFO_NOFORMAT("update_calendar_status() started.");
489 g_hash_table_foreach(sipe_private->buddies, (GHFunc)update_calendar_status_cb, sipe_private);
491 /* repeat scheduling */
492 sipe_sched_calendar_status_update(sipe_private, time(NULL) + 3*60 /* 3 min */);
496 * Schedules process of contacts' status update
497 * based on their calendar information.
498 * Should be scheduled to the beginning of every
499 * 15 min interval, like:
500 * 13:00, 13:15, 13:30, 13:45, etc.
502 * Applicability: 2005 systems
504 static void
505 sipe_sched_calendar_status_update(struct sipe_core_private *sipe_private,
506 time_t calculate_from)
508 int interval = 15*60;
509 /** start of the beginning of closest 15 min interval. */
510 time_t next_start = ((time_t)((int)((int)calculate_from)/interval + 1)*interval);
512 SIPE_DEBUG_INFO("sipe_sched_calendar_status_update: calculate_from time: %s",
513 asctime(localtime(&calculate_from)));
514 SIPE_DEBUG_INFO("sipe_sched_calendar_status_update: next start time : %s",
515 asctime(localtime(&next_start)));
517 sipe_schedule_seconds(sipe_private,
518 "<+2005-cal-status>",
519 NULL,
520 next_start - time(NULL),
521 update_calendar_status,
522 NULL);
526 * Schedules process of self status publish
527 * based on own calendar information.
528 * Should be scheduled to the beginning of every
529 * 15 min interval, like:
530 * 13:00, 13:15, 13:30, 13:45, etc.
532 * Applicability: 2007+ systems
534 static void
535 sipe_sched_calendar_status_self_publish(struct sipe_core_private *sipe_private,
536 time_t calculate_from)
538 int interval = 5*60;
539 /** start of the beginning of closest 5 min interval. */
540 time_t next_start = ((time_t)((int)((int)calculate_from)/interval + 1)*interval);
542 SIPE_DEBUG_INFO("sipe_sched_calendar_status_self_publish: calculate_from time: %s",
543 asctime(localtime(&calculate_from)));
544 SIPE_DEBUG_INFO("sipe_sched_calendar_status_self_publish: next start time : %s",
545 asctime(localtime(&next_start)));
547 sipe_schedule_seconds(sipe_private,
548 "<+2007-cal-status>",
549 NULL,
550 next_start - time(NULL),
551 publish_calendar_status_self,
552 NULL);
555 static void sipe_subscribe_resource_uri(const char *name,
556 SIPE_UNUSED_PARAMETER gpointer value,
557 gchar **resources_uri)
559 gchar *tmp = *resources_uri;
560 *resources_uri = g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp, name);
561 g_free(tmp);
564 static void sipe_subscribe_resource_uri_with_context(const char *name, gpointer value, gchar **resources_uri)
566 struct sipe_buddy *sbuddy = (struct sipe_buddy *)value;
567 gchar *context = sbuddy && sbuddy->just_added ? "><context/></resource>" : "/>";
568 gchar *tmp = *resources_uri;
570 if (sbuddy) sbuddy->just_added = FALSE; /* should be enought to include context one time */
572 *resources_uri = g_strdup_printf("%s<resource uri=\"%s\"%s\n", tmp, name, context);
573 g_free(tmp);
577 * Support for Batch Category SUBSCRIBE [MS-PRES] - msrtc-event-categories+xml OCS 2007
578 * Support for Batch Category SUBSCRIBE [MS-SIP] - adrl+xml LCS 2005
579 * The user sends an initial batched category SUBSCRIBE request against all contacts on his roaming list in only a request
580 * A batch category SUBSCRIBE request MUST have the same To-URI and From-URI.
581 * This header will be send only if adhoclist there is a "Supported: adhoclist" in REGISTER answer else will be send a Single Category SUBSCRIBE
584 static void sipe_subscribe_presence_batched_to(struct sipe_core_private *sipe_private,
585 gchar *resources_uri,
586 gchar *to)
588 gchar *contact = get_contact(sipe_private);
589 gchar *request;
590 gchar *content;
591 gchar *require = "";
592 gchar *accept = "";
593 gchar *autoextend = "";
594 gchar *content_type;
596 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
597 require = ", categoryList";
598 accept = ", application/msrtc-event-categories+xml, application/xpidf+xml, application/pidf+xml";
599 content_type = "application/msrtc-adrl-categorylist+xml";
600 content = g_strdup_printf(
601 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
602 "<action name=\"subscribe\" id=\"63792024\">\n"
603 "<adhocList>\n%s</adhocList>\n"
604 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
605 "<category name=\"calendarData\"/>\n"
606 "<category name=\"contactCard\"/>\n"
607 "<category name=\"note\"/>\n"
608 "<category name=\"state\"/>\n"
609 "</categoryList>\n"
610 "</action>\n"
611 "</batchSub>", sipe_private->username, resources_uri);
612 } else {
613 autoextend = "Supported: com.microsoft.autoextend\r\n";
614 content_type = "application/adrl+xml";
615 content = g_strdup_printf(
616 "<adhoclist xmlns=\"urn:ietf:params:xml:ns:adrl\" uri=\"sip:%s\" name=\"sip:%s\">\n"
617 "<create xmlns=\"\">\n%s</create>\n"
618 "</adhoclist>\n", sipe_private->username, sipe_private->username, resources_uri);
620 g_free(resources_uri);
622 request = g_strdup_printf(
623 "Require: adhoclist%s\r\n"
624 "Supported: eventlist\r\n"
625 "Accept: application/rlmi+xml, multipart/related, text/xml+msrtc.pidf%s\r\n"
626 "Supported: ms-piggyback-first-notify\r\n"
627 "%sSupported: ms-benotify\r\n"
628 "Proxy-Require: ms-benotify\r\n"
629 "Event: presence\r\n"
630 "Content-Type: %s\r\n"
631 "Contact: %s\r\n", require, accept, autoextend, content_type, contact);
632 g_free(contact);
634 sipe_subscribe_presence_buddy(sipe_private, to, request, content);
636 g_free(content);
637 g_free(to);
638 g_free(request);
641 static void sipe_subscribe_presence_batched(struct sipe_core_private *sipe_private,
642 SIPE_UNUSED_PARAMETER void *unused)
644 gchar *to = sip_uri_self(sipe_private);
645 gchar *resources_uri = g_strdup("");
646 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
647 g_hash_table_foreach(sipe_private->buddies, (GHFunc) sipe_subscribe_resource_uri_with_context , &resources_uri);
648 } else {
649 g_hash_table_foreach(sipe_private->buddies, (GHFunc) sipe_subscribe_resource_uri, &resources_uri);
652 sipe_subscribe_presence_batched_to(sipe_private, resources_uri, to);
655 struct presence_batched_routed {
656 gchar *host;
657 GSList *buddies;
660 static void sipe_subscribe_presence_batched_routed_free(void *payload)
662 struct presence_batched_routed *data = payload;
663 GSList *buddies = data->buddies;
664 while (buddies) {
665 g_free(buddies->data);
666 buddies = buddies->next;
668 g_slist_free(data->buddies);
669 g_free(data->host);
670 g_free(payload);
673 static void sipe_subscribe_presence_batched_routed(struct sipe_core_private *sipe_private,
674 void *payload)
676 struct presence_batched_routed *data = payload;
677 GSList *buddies = data->buddies;
678 gchar *resources_uri = g_strdup("");
679 while (buddies) {
680 gchar *tmp = resources_uri;
681 resources_uri = g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp, (char *) buddies->data);
682 g_free(tmp);
683 buddies = buddies->next;
685 sipe_subscribe_presence_batched_to(sipe_private, resources_uri,
686 g_strdup(data->host));
690 * Single Category SUBSCRIBE [MS-PRES] ; To send when the server returns a 200 OK message with state="resubscribe" in response.
691 * The user sends a single SUBSCRIBE request to the subscribed contact.
692 * The To-URI and the URI listed in the resource list MUST be the same for a single category SUBSCRIBE request.
696 static void sipe_subscribe_presence_single(struct sipe_core_private *sipe_private,
697 void *buddy_name)
699 gchar *to = sip_uri((char *)buddy_name);
700 gchar *tmp = get_contact(sipe_private);
701 gchar *request;
702 gchar *content = NULL;
703 gchar *autoextend = "";
704 gchar *content_type = "";
705 struct sipe_buddy *sbuddy = g_hash_table_lookup(sipe_private->buddies, to);
706 gchar *context = sbuddy && sbuddy->just_added ? "><context/></resource>" : "/>";
708 if (sbuddy) sbuddy->just_added = FALSE;
710 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
711 content_type = "Content-Type: application/msrtc-adrl-categorylist+xml\r\n";
712 } else {
713 autoextend = "Supported: com.microsoft.autoextend\r\n";
716 request = g_strdup_printf(
717 "Accept: application/msrtc-event-categories+xml, text/xml+msrtc.pidf, application/xpidf+xml, application/pidf+xml, application/rlmi+xml, multipart/related\r\n"
718 "Supported: ms-piggyback-first-notify\r\n"
719 "%s%sSupported: ms-benotify\r\n"
720 "Proxy-Require: ms-benotify\r\n"
721 "Event: presence\r\n"
722 "Contact: %s\r\n", autoextend, content_type, tmp);
724 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
725 content = g_strdup_printf(
726 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
727 "<action name=\"subscribe\" id=\"63792024\"><adhocList>\n"
728 "<resource uri=\"%s\"%s\n"
729 "</adhocList>\n"
730 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
731 "<category name=\"calendarData\"/>\n"
732 "<category name=\"contactCard\"/>\n"
733 "<category name=\"note\"/>\n"
734 "<category name=\"state\"/>\n"
735 "</categoryList>\n"
736 "</action>\n"
737 "</batchSub>", sipe_private->username, to, context);
740 g_free(tmp);
742 sipe_subscribe_presence_buddy(sipe_private, to, request, content);
744 g_free(content);
745 g_free(to);
746 g_free(request);
749 void sipe_set_status(PurpleAccount *account, PurpleStatus *status)
751 SIPE_DEBUG_INFO("sipe_set_status: status=%s", purple_status_get_id(status));
753 if (!purple_status_is_active(status))
754 return;
756 if (account->gc) {
757 struct sipe_core_private *sipe_private = PURPLE_ACCOUNT_TO_SIPE_CORE_PRIVATE;
758 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
760 if (sip) {
761 gchar *action_name;
762 gchar *tmp;
763 time_t now = time(NULL);
764 const char *status_id = purple_status_get_id(status);
765 const char *note = purple_status_get_attr_string(status, SIPE_STATUS_ATTR_ID_MESSAGE);
766 sipe_activity activity = sipe_get_activity_by_token(status_id);
767 gboolean do_not_publish = ((now - sip->do_not_publish[activity]) <= 2);
769 /* when other point of presence clears note, but we are keeping
770 * state if OOF note.
772 if (do_not_publish && !note && sip->cal && sip->cal->oof_note) {
773 SIPE_DEBUG_INFO_NOFORMAT("sipe_set_status: enabling publication as OOF note keepers.");
774 do_not_publish = FALSE;
777 SIPE_DEBUG_INFO("sipe_set_status: was: sip->do_not_publish[%s]=%d [?] now(time)=%d",
778 status_id, (int)sip->do_not_publish[activity], (int)now);
780 sip->do_not_publish[activity] = 0;
781 SIPE_DEBUG_INFO("sipe_set_status: set: sip->do_not_publish[%s]=%d [0]",
782 status_id, (int)sip->do_not_publish[activity]);
784 if (do_not_publish)
786 SIPE_DEBUG_INFO_NOFORMAT("sipe_set_status: publication was switched off, exiting.");
787 return;
790 g_free(sip->status);
791 sip->status = g_strdup(status_id);
793 /* hack to escape apostrof before comparison */
794 tmp = note ? sipe_utils_str_replace(note, "'", "&apos;") : NULL;
796 /* this will preserve OOF flag as well */
797 if (!sipe_strequal(tmp, sip->note)) {
798 sip->is_oof_note = FALSE;
799 g_free(sip->note);
800 sip->note = g_strdup(note);
801 sip->note_since = time(NULL);
803 g_free(tmp);
805 /* schedule 2 sec to capture idle flag */
806 action_name = g_strdup_printf("<%s>", "+set-status");
807 sipe_schedule_seconds(sipe_private,
808 action_name,
809 NULL,
810 SIPE_IDLE_SET_DELAY,
811 send_presence_status,
812 NULL);
813 g_free(action_name);
818 void
819 sipe_set_idle(PurpleConnection * gc,
820 int interval)
822 SIPE_DEBUG_INFO("sipe_set_idle: interval=%d", interval);
824 if (gc) {
825 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
826 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
828 if (sip) {
829 sip->idle_switch = time(NULL);
830 SIPE_DEBUG_INFO("sipe_set_idle: sip->idle_switch : %s", asctime(localtime(&(sip->idle_switch))));
835 void sipe_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
837 SIPE_DEBUG_INFO("sipe_add_buddy[CB]: buddy:%s group:%s", buddy ? buddy->name : "", group ? group->name : "");
839 /* libpurple can call us with undefined buddy or group */
840 if (buddy && group) {
841 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
843 /* Buddy name must be lower case as we use purple_normalize_nocase() to compare */
844 gchar *buddy_name = g_ascii_strdown(buddy->name, -1);
845 purple_blist_rename_buddy(buddy, buddy_name);
846 g_free(buddy_name);
848 /* Prepend sip: if needed */
849 if (!g_str_has_prefix(buddy->name, "sip:")) {
850 gchar *buf = sip_uri_from_name(buddy->name);
851 purple_blist_rename_buddy(buddy, buf);
852 g_free(buf);
855 if (!g_hash_table_lookup(sipe_private->buddies, buddy->name)) {
856 struct sipe_buddy *b = g_new0(struct sipe_buddy, 1);
857 SIPE_DEBUG_INFO("sipe_add_buddy: adding %s", buddy->name);
858 b->name = g_strdup(buddy->name);
859 b->just_added = TRUE;
860 g_hash_table_insert(sipe_private->buddies, b->name, b);
861 /* @TODO should go to callback */
862 sipe_subscribe_presence_single(sipe_private,
863 b->name);
864 } else {
865 SIPE_DEBUG_INFO("sipe_add_buddy: buddy %s already in internal list", buddy->name);
868 sipe_core_buddy_group(PURPLE_GC_TO_SIPE_CORE_PUBLIC, buddy->name, NULL, group->name);
872 static void sipe_free_buddy(struct sipe_buddy *buddy)
874 #ifndef _WIN32
876 * We are calling g_hash_table_foreach_steal(). That means that no
877 * key/value deallocation functions are called. Therefore the glib
878 * hash code does not touch the key (buddy->name) or value (buddy)
879 * of the to-be-deleted hash node at all. It follows that we
881 * - MUST free the memory for the key ourselves and
882 * - ARE allowed to do it in this function
884 * Conclusion: glib must be broken on the Windows platform if sipe
885 * crashes with SIGTRAP when closing. You'll have to live
886 * with the memory leak until this is fixed.
888 g_free(buddy->name);
889 #endif
890 g_free(buddy->activity);
891 g_free(buddy->meeting_subject);
892 g_free(buddy->meeting_location);
893 g_free(buddy->note);
895 g_free(buddy->cal_start_time);
896 g_free(buddy->cal_free_busy_base64);
897 g_free(buddy->cal_free_busy);
898 g_free(buddy->last_non_cal_activity);
900 sipe_cal_free_working_hours(buddy->cal_working_hours);
902 g_free(buddy->device_name);
903 g_slist_free(buddy->groups);
904 g_free(buddy);
908 * Unassociates buddy from group first.
909 * Then see if no groups left, removes buddy completely.
910 * Otherwise updates buddy groups on server.
912 void sipe_remove_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
914 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
915 struct sipe_buddy *b;
916 struct sipe_group *g = NULL;
918 SIPE_DEBUG_INFO("sipe_remove_buddy[CB]: buddy:%s group:%s", buddy ? buddy->name : "", group ? group->name : "");
919 if (!buddy) return;
921 b = g_hash_table_lookup(sipe_private->buddies, buddy->name);
922 if (!b) return;
924 if (group) {
925 g = sipe_group_find_by_name(sipe_private, group->name);
928 if (g) {
929 b->groups = g_slist_remove(b->groups, g);
930 SIPE_DEBUG_INFO("buddy %s removed from group %s", buddy->name, g->name);
933 if (g_slist_length(b->groups) < 1) {
934 gchar *action_name = sipe_utils_presence_key(buddy->name);
935 sipe_schedule_cancel(sipe_private, action_name);
936 g_free(action_name);
938 g_hash_table_remove(sipe_private->buddies, buddy->name);
940 if (b->name) {
941 gchar *request = g_strdup_printf("<m:URI>%s</m:URI>",
942 b->name);
943 sip_soap_request(sipe_private,
944 "deleteContact",
945 request);
946 g_free(request);
949 sipe_free_buddy(b);
950 } else {
951 //updates groups on server
952 sipe_core_group_set_user(SIPE_CORE_PUBLIC, b->name);
958 * A callback for g_hash_table_foreach
960 static void
961 sipe_buddy_subscribe_cb(char *buddy_name,
962 SIPE_UNUSED_PARAMETER struct sipe_buddy *buddy,
963 struct sipe_core_private *sipe_private)
965 gchar *action_name = sipe_utils_presence_key(buddy_name);
966 /* g_hash_table_size() can never return 0, otherwise this function wouldn't be called :-) */
967 guint time_range = (g_hash_table_size(sipe_private->buddies) * 1000) / 25; /* time interval for 25 requests per sec. In msec. */
968 guint timeout = ((guint) rand()) / (RAND_MAX / time_range) + 1; /* random period within the range but never 0! */
970 sipe_schedule_mseconds(sipe_private,
971 action_name,
972 g_strdup(buddy_name),
973 timeout,
974 sipe_subscribe_presence_single,
975 g_free);
976 g_free(action_name);
980 * Removes entries from local buddy list
981 * that does not correspond ones in the roaming contact list.
983 static void sipe_cleanup_local_blist(struct sipe_core_private *sipe_private) {
984 GSList *buddies = sipe_backend_buddy_find_all(SIPE_CORE_PUBLIC, NULL, NULL);
985 GSList *entry = buddies;
986 struct sipe_buddy *buddy;
987 sipe_backend_buddy b;
988 gchar *bname;
989 gchar *gname;
991 SIPE_DEBUG_INFO("sipe_cleanup_local_blist: overall %d backend buddies (including clones)", g_slist_length(buddies));
992 SIPE_DEBUG_INFO("sipe_cleanup_local_blist: %d sipe buddies (unique)", g_hash_table_size(sipe_private->buddies));
993 while (entry) {
994 b = entry->data;
995 gname = sipe_backend_buddy_get_group_name(SIPE_CORE_PUBLIC, b);
996 bname = sipe_backend_buddy_get_name(SIPE_CORE_PUBLIC, b);
997 buddy = g_hash_table_lookup(sipe_private->buddies, bname);
998 g_free(bname);
999 if(buddy) {
1000 gboolean in_sipe_groups = FALSE;
1001 GSList *entry2 = buddy->groups;
1002 while (entry2) {
1003 struct sipe_group *group = entry2->data;
1004 if (sipe_strequal(group->name, gname)) {
1005 in_sipe_groups = TRUE;
1006 break;
1008 entry2 = entry2->next;
1010 if(!in_sipe_groups) {
1011 SIPE_DEBUG_INFO("*** REMOVING %s from blist group: %s as not having this group in roaming list", bname, gname);
1012 sipe_backend_buddy_remove(SIPE_CORE_PUBLIC, b);
1014 } else {
1015 SIPE_DEBUG_INFO("*** REMOVING %s from blist group: %s as this buddy not in roaming list", bname, gname);
1016 sipe_backend_buddy_remove(SIPE_CORE_PUBLIC, b);
1018 g_free(gname);
1019 entry = entry->next;
1021 g_slist_free(buddies);
1024 static int
1025 sipe_find_access_level(struct sipe_core_private *sipe_private,
1026 const gchar *type,
1027 const gchar *value,
1028 gboolean *is_group_access);
1030 static void
1031 sipe_refresh_blocked_status_cb(char *buddy_name,
1032 SIPE_UNUSED_PARAMETER struct sipe_buddy *buddy,
1033 struct sipe_core_private *sipe_private)
1035 int container_id = sipe_find_access_level(sipe_private, "user", buddy_name, NULL);
1036 gboolean blocked = (container_id == 32000);
1037 gboolean blocked_in_blist = sipe_backend_buddy_is_blocked(SIPE_CORE_PUBLIC, buddy_name);
1039 /* SIPE_DEBUG_INFO("sipe_refresh_blocked_status_cb: buddy_name=%s, blocked=%s, blocked_in_blist=%s",
1040 buddy_name, blocked ? "T" : "F", blocked_in_blist ? "T" : "F"); */
1042 if (blocked != blocked_in_blist) {
1043 sipe_backend_buddy_set_blocked_status(SIPE_CORE_PUBLIC, buddy_name, blocked);
1047 static void
1048 sipe_refresh_blocked_status(struct sipe_core_private *sipe_private)
1050 g_hash_table_foreach(sipe_private->buddies,
1051 (GHFunc) sipe_refresh_blocked_status_cb,
1052 sipe_private);
1055 static gboolean sipe_process_roaming_contacts(struct sipe_core_private *sipe_private,
1056 struct sipmsg *msg)
1058 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
1059 int len = msg->bodylen;
1061 const gchar *tmp = sipmsg_find_header(msg, "Event");
1062 const sipe_xml *item;
1063 sipe_xml *isc;
1064 guint delta;
1065 const sipe_xml *group_node;
1066 if (!g_str_has_prefix(tmp, "vnd-microsoft-roaming-contacts")) {
1067 return FALSE;
1070 /* Convert the contact from XML to backend Buddies */
1071 isc = sipe_xml_parse(msg->body, len);
1072 if (!isc) {
1073 return FALSE;
1076 /* [MS-SIP]: deltaNum MUST be non-zero */
1077 delta = sipe_xml_int_attribute(isc, "deltaNum", 0);
1078 if (delta) {
1079 sipe_private->deltanum_contacts = delta;
1082 if (sipe_strequal(sipe_xml_name(isc), "contactList")) {
1084 /* Parse groups */
1085 for (group_node = sipe_xml_child(isc, "group"); group_node; group_node = sipe_xml_twin(group_node)) {
1086 struct sipe_group * group = g_new0(struct sipe_group, 1);
1087 const char *name = sipe_xml_attribute(group_node, "name");
1089 if (g_str_has_prefix(name, "~")) {
1090 name = _("Other Contacts");
1092 group->name = g_strdup(name);
1093 group->id = (int)g_ascii_strtod(sipe_xml_attribute(group_node, "id"), NULL);
1095 sipe_group_add(sipe_private, group);
1098 // Make sure we have at least one group
1099 if (g_slist_length(sip->groups) == 0) {
1100 sipe_group_create(sipe_private, _("Other Contacts"), NULL);
1103 /* Parse contacts */
1104 for (item = sipe_xml_child(isc, "contact"); item; item = sipe_xml_twin(item)) {
1105 const gchar *uri = sipe_xml_attribute(item, "uri");
1106 const gchar *name = sipe_xml_attribute(item, "name");
1107 gchar *buddy_name;
1108 struct sipe_buddy *buddy = NULL;
1109 gchar *tmp;
1110 gchar **item_groups;
1111 int i = 0;
1113 /* Buddy name must be lower case as we use purple_normalize_nocase() to compare */
1114 tmp = sip_uri_from_name(uri);
1115 buddy_name = g_ascii_strdown(tmp, -1);
1116 g_free(tmp);
1118 /* assign to group Other Contacts if nothing else received */
1119 tmp = g_strdup(sipe_xml_attribute(item, "groups"));
1120 if(is_empty(tmp)) {
1121 struct sipe_group *group = sipe_group_find_by_name(sipe_private, _("Other Contacts"));
1122 g_free(tmp);
1123 tmp = group ? g_strdup_printf("%d", group->id) : g_strdup("1");
1125 item_groups = g_strsplit(tmp, " ", 0);
1126 g_free(tmp);
1128 while (item_groups[i]) {
1129 struct sipe_group *group = sipe_group_find_by_id(sipe_private, g_ascii_strtod(item_groups[i], NULL));
1131 // If couldn't find the right group for this contact, just put them in the first group we have
1132 if (group == NULL && g_slist_length(sip->groups) > 0) {
1133 group = sip->groups->data;
1136 if (group != NULL) {
1137 gchar *b_alias;
1138 sipe_backend_buddy b = sipe_backend_buddy_find(SIPE_CORE_PUBLIC, buddy_name, group->name);
1139 if (!b){
1140 b = sipe_backend_buddy_add(SIPE_CORE_PUBLIC, buddy_name, uri, group->name);
1141 SIPE_DEBUG_INFO("Created new buddy %s with alias %s", buddy_name, uri);
1144 b_alias = sipe_backend_buddy_get_alias(SIPE_CORE_PUBLIC, b);
1145 if (sipe_strcase_equal(uri, b_alias)) {
1146 if (name != NULL && strlen(name) != 0) {
1147 sipe_backend_buddy_set_alias(SIPE_CORE_PUBLIC, b, name);
1149 SIPE_DEBUG_INFO("Replaced buddy %s alias with %s", buddy_name, name);
1152 g_free(b_alias);
1154 if (!buddy) {
1155 buddy = g_new0(struct sipe_buddy, 1);
1156 buddy->name = sipe_backend_buddy_get_name(SIPE_CORE_PUBLIC, b);
1157 g_hash_table_insert(sipe_private->buddies, buddy->name, buddy);
1159 SIPE_DEBUG_INFO("Added SIPE buddy %s", buddy->name);
1162 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
1164 SIPE_DEBUG_INFO("Added buddy %s to group %s", buddy->name, group->name);
1165 } else {
1166 SIPE_DEBUG_INFO("No group found for contact %s! Unable to add to buddy list",
1167 name);
1170 i++;
1171 } // while, contact groups
1172 g_strfreev(item_groups);
1173 g_free(buddy_name);
1175 } // for, contacts
1177 sipe_cleanup_local_blist(sipe_private);
1179 /* Add self-contact if not there yet. 2005 systems. */
1180 /* This will resemble subscription to roaming_self in 2007 systems */
1181 if (!SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
1182 gchar *self_uri = sip_uri_self(sipe_private);
1183 struct sipe_buddy *buddy = g_hash_table_lookup(sipe_private->buddies, self_uri);
1185 if (!buddy) {
1186 buddy = g_new0(struct sipe_buddy, 1);
1187 buddy->name = g_strdup(self_uri);
1188 g_hash_table_insert(sipe_private->buddies, buddy->name, buddy);
1190 g_free(self_uri);
1193 sipe_xml_free(isc);
1195 /* subscribe to buddies */
1196 if (!sip->subscribed_buddies) { //do it once, then count Expire field to schedule resubscribe.
1197 if (sip->batched_support) {
1198 sipe_subscribe_presence_batched(sipe_private, NULL);
1199 } else {
1200 g_hash_table_foreach(sipe_private->buddies,
1201 (GHFunc)sipe_buddy_subscribe_cb,
1202 sipe_private);
1204 sip->subscribed_buddies = TRUE;
1206 /* for 2005 systems schedule contacts' status update
1207 * based on their calendar information
1209 if (!SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
1210 sipe_sched_calendar_status_update(sipe_private, time(NULL));
1213 return 0;
1217 * Fires on deregistration event initiated by server.
1218 * [MS-SIPREGE] SIP extension.
1221 // 2007 Example
1223 // Content-Type: text/registration-event
1224 // subscription-state: terminated;expires=0
1225 // ms-diagnostics-public: 4141;reason="User disabled"
1227 // deregistered;event=rejected
1229 static void sipe_process_registration_notify(struct sipe_core_private *sipe_private,
1230 struct sipmsg *msg)
1232 const gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
1233 gchar *event = NULL;
1234 gchar *reason = NULL;
1235 gchar *warning;
1237 SIPE_DEBUG_INFO_NOFORMAT("sipe_process_registration_notify: deregistration received.");
1239 if (!g_ascii_strncasecmp(contenttype, "text/registration-event", 23)) {
1240 event = sipmsg_find_part_of_header(msg->body, "event=", NULL, NULL);
1241 //@TODO have proper parameter extraction _by_name_ func, case insesitive.
1242 event = event ? event : sipmsg_find_part_of_header(msg->body, "event=", ";", NULL);
1243 } else {
1244 SIPE_DEBUG_INFO_NOFORMAT("sipe_process_registration_notify: unknown content type, exiting.");
1245 return;
1248 reason = sipmsg_get_ms_diagnostics_reason(msg);
1249 reason = reason ? reason : sipmsg_get_ms_diagnostics_public_reason(msg);
1250 if (!reason) { // for LCS2005
1251 if (event && sipe_strcase_equal(event, "unregistered")) {
1252 //reason = g_strdup(_("User logged out")); // [MS-OCER]
1253 reason = g_strdup(_("you are already signed in at another location"));
1254 } else if (event && sipe_strcase_equal(event, "rejected")) {
1255 reason = g_strdup(_("user disabled")); // [MS-OCER]
1256 } else if (event && sipe_strcase_equal(event, "deactivated")) {
1257 reason = g_strdup(_("user moved")); // [MS-OCER]
1260 g_free(event);
1261 warning = g_strdup_printf(_("You have been rejected by the server: %s"), reason ? reason : _("no reason given"));
1262 g_free(reason);
1264 sipe_backend_connection_error(SIPE_CORE_PUBLIC,
1265 SIPE_CONNECTION_ERROR_INVALID_USERNAME,
1266 warning);
1267 g_free(warning);
1271 static void sipe_process_provisioning_v2(struct sipe_core_private *sipe_private,
1272 struct sipmsg *msg)
1274 sipe_xml *xn_provision_group_list;
1275 const sipe_xml *node;
1277 xn_provision_group_list = sipe_xml_parse(msg->body, msg->bodylen);
1279 /* provisionGroup */
1280 for (node = sipe_xml_child(xn_provision_group_list, "provisionGroup"); node; node = sipe_xml_twin(node)) {
1281 if (sipe_strequal("ServerConfiguration", sipe_xml_attribute(node, "name"))) {
1282 g_free(sipe_private->focus_factory_uri);
1283 sipe_private->focus_factory_uri = sipe_xml_data(sipe_xml_child(node, "focusFactoryUri"));
1284 SIPE_DEBUG_INFO("sipe_process_provisioning_v2: sipe_private->focus_factory_uri=%s",
1285 sipe_private->focus_factory_uri ? sipe_private->focus_factory_uri : "");
1287 #ifdef HAVE_VV
1288 g_free(sipe_private->mras_uri);
1289 sipe_private->mras_uri = g_strstrip(sipe_xml_data(sipe_xml_child(node, "mrasUri")));
1290 SIPE_DEBUG_INFO("sipe_process_provisioning_v2: sipe_private->mras_uri=%s",
1291 sipe_private->mras_uri ? sipe_private->mras_uri : "");
1293 if (sipe_private->mras_uri)
1294 sipe_media_get_av_edge_credentials(sipe_private);
1295 #endif
1296 break;
1299 sipe_xml_free(xn_provision_group_list);
1302 /** for 2005 system */
1303 static void
1304 sipe_process_provisioning(struct sipe_core_private *sipe_private,
1305 struct sipmsg *msg)
1307 sipe_xml *xn_provision;
1308 const sipe_xml *node;
1310 xn_provision = sipe_xml_parse(msg->body, msg->bodylen);
1311 if ((node = sipe_xml_child(xn_provision, "user"))) {
1312 SIPE_DEBUG_INFO("sipe_process_provisioning: uri=%s", sipe_xml_attribute(node, "uri"));
1313 if ((node = sipe_xml_child(node, "line"))) {
1314 const gchar *line_uri = sipe_xml_attribute(node, "uri");
1315 const gchar *server = sipe_xml_attribute(node, "server");
1316 SIPE_DEBUG_INFO("sipe_process_provisioning: line_uri=%s server=%s", line_uri, server);
1317 sip_csta_open(sipe_private, line_uri, server);
1320 sipe_xml_free(xn_provision);
1323 static void sipe_process_roaming_acl(struct sipe_core_private *sipe_private,
1324 struct sipmsg *msg)
1326 guint delta;
1327 sipe_xml *xml;
1329 xml = sipe_xml_parse(msg->body, msg->bodylen);
1330 if (!xml)
1331 return;
1333 /* [MS-SIP]: deltaNum MUST be non-zero */
1334 delta = sipe_xml_int_attribute(xml, "deltaNum", 0);
1335 if (delta) {
1336 sipe_private->deltanum_acl = delta;
1339 sipe_xml_free(xml);
1342 /** MS-PRES container */
1343 struct sipe_container {
1344 guint id;
1345 guint version;
1346 GSList *members;
1348 /** MS-PRES container member */
1349 struct sipe_container_member {
1350 /** user, domain, sameEnterprise, federated, publicCloud; everyone */
1351 gchar *type;
1352 gchar *value;
1355 static void
1356 free_container_member(struct sipe_container_member *member)
1358 if (!member) return;
1360 g_free(member->type);
1361 g_free(member->value);
1362 g_free(member);
1365 static void
1366 free_container(struct sipe_container *container)
1368 GSList *entry;
1370 if (!container) return;
1372 entry = container->members;
1373 while (entry) {
1374 void *data = entry->data;
1375 entry = g_slist_remove(entry, data);
1376 free_container_member((struct sipe_container_member *)data);
1378 g_free(container);
1381 static void
1382 sipe_send_container_members_prepare(const guint container_id,
1383 const guint container_version,
1384 const gchar *action,
1385 const gchar *type,
1386 const gchar *value,
1387 char **container_xmls)
1389 gchar *value_str = value ? g_strdup_printf(" value=\"%s\"", value) : g_strdup("");
1390 gchar *body;
1392 if (!container_xmls) return;
1394 body = g_strdup_printf(
1395 "<container id=\"%d\" version=\"%d\"><member action=\"%s\" type=\"%s\"%s/></container>",
1396 container_id,
1397 container_version,
1398 action,
1399 type,
1400 value_str);
1401 g_free(value_str);
1403 if ((*container_xmls) == NULL) {
1404 *container_xmls = body;
1405 } else {
1406 char *tmp = *container_xmls;
1408 *container_xmls = g_strconcat(*container_xmls, body, NULL);
1409 g_free(tmp);
1410 g_free(body);
1414 static void
1415 sipe_send_set_container_members(struct sipe_core_private *sipe_private,
1416 char *container_xmls)
1418 gchar *self;
1419 gchar *contact;
1420 gchar *hdr;
1421 gchar *body;
1423 if (!container_xmls) return;
1425 self = sip_uri_self(sipe_private);
1426 body = g_strdup_printf(
1427 "<setContainerMembers xmlns=\"http://schemas.microsoft.com/2006/09/sip/container-management\">"
1428 "%s"
1429 "</setContainerMembers>",
1430 container_xmls);
1432 contact = get_contact(sipe_private);
1433 hdr = g_strdup_printf("Contact: %s\r\n"
1434 "Content-Type: application/msrtc-setcontainermembers+xml\r\n", contact);
1435 g_free(contact);
1437 sip_transport_service(sipe_private,
1438 self,
1439 hdr,
1440 body,
1441 NULL);
1443 g_free(hdr);
1444 g_free(body);
1445 g_free(self);
1449 * Finds locally stored MS-PRES container member
1451 static struct sipe_container_member *
1452 sipe_find_container_member(struct sipe_container *container,
1453 const gchar *type,
1454 const gchar *value)
1456 struct sipe_container_member *member;
1457 GSList *entry;
1459 if (container == NULL || type == NULL) {
1460 return NULL;
1463 entry = container->members;
1464 while (entry) {
1465 member = entry->data;
1466 if (sipe_strcase_equal(member->type, type) &&
1467 sipe_strcase_equal(member->value, value))
1469 return member;
1471 entry = entry->next;
1473 return NULL;
1477 * Finds locally stored MS-PRES container by id
1479 static struct sipe_container *
1480 sipe_find_container(struct sipe_core_private *sipe_private,
1481 guint id)
1483 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
1484 struct sipe_container *container;
1485 GSList *entry;
1487 if (sip == NULL) {
1488 return NULL;
1491 entry = sip->containers;
1492 while (entry) {
1493 container = entry->data;
1494 if (id == container->id) {
1495 return container;
1497 entry = entry->next;
1499 return NULL;
1502 static GSList *
1503 sipe_get_access_domains(struct sipe_core_private *sipe_private)
1505 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
1506 struct sipe_container *container;
1507 struct sipe_container_member *member;
1508 GSList *entry;
1509 GSList *entry2;
1510 GSList *res = NULL;
1512 if (!sip) return NULL;
1514 entry = sip->containers;
1515 while (entry) {
1516 container = entry->data;
1518 entry2 = container->members;
1519 while (entry2) {
1520 member = entry2->data;
1521 if (sipe_strcase_equal(member->type, "domain"))
1523 res = slist_insert_unique_sorted(res, g_strdup(member->value), (GCompareFunc)g_ascii_strcasecmp);
1525 entry2 = entry2->next;
1527 entry = entry->next;
1529 return res;
1533 * Returns pointer to domain part in provided Email URL
1535 * @param email an email URL. Example: first.last@hq.company.com
1536 * @return pointer to domain part of email URL. Coresponding example: hq.company.com
1538 * Doesn't allocate memory
1540 static const char *
1541 sipe_get_domain(const char *email)
1543 char *tmp;
1545 if (!email) return NULL;
1547 tmp = strstr(email, "@");
1549 if (tmp && ((tmp+1) < (email + strlen(email)))) {
1550 return tmp+1;
1551 } else {
1552 return NULL;
1557 /* @TODO: replace with binary search for faster access? */
1558 /** source: http://support.microsoft.com/kb/897567 */
1559 static const char * const public_domains [] = {
1560 "aol.com", "icq.com", "love.com", "mac.com", "br.live.com",
1561 "hotmail.co.il", "hotmail.co.jp", "hotmail.co.th", "hotmail.co.uk",
1562 "hotmail.com", "hotmail.com.ar", "hotmail.com.tr", "hotmail.es",
1563 "hotmail.de", "hotmail.fr", "hotmail.it", "live.at", "live.be",
1564 "live.ca", "live.cl", "live.cn", "live.co.in", "live.co.kr",
1565 "live.co.uk", "live.co.za", "live.com", "live.com.ar", "live.com.au",
1566 "live.com.co", "live.com.mx", "live.com.my", "live.com.pe",
1567 "live.com.ph", "live.com.pk", "live.com.pt", "live.com.sg",
1568 "live.com.ve", "live.de", "live.dk", "live.fr", "live.hk", "live.ie",
1569 "live.in", "live.it", "live.jp", "live.nl", "live.no", "live.ph",
1570 "live.ru", "live.se", "livemail.com.br", "livemail.tw",
1571 "messengeruser.com", "msn.com", "passport.com", "sympatico.ca",
1572 "tw.live.com", "webtv.net", "windowslive.com", "windowslive.es",
1573 "yahoo.com",
1574 NULL};
1576 static gboolean
1577 sipe_is_public_domain(const char *domain)
1579 int i = 0;
1580 while (public_domains[i]) {
1581 if (sipe_strcase_equal(public_domains[i], domain)) {
1582 return TRUE;
1584 i++;
1586 return FALSE;
1590 * Access Levels
1591 * 32000 - Blocked
1592 * 400 - Personal
1593 * 300 - Team
1594 * 200 - Company
1595 * 100 - Public
1597 static const char *
1598 sipe_get_access_level_name(int container_id)
1600 switch(container_id) {
1601 case 32000: return _("Blocked");
1602 case 400: return _("Personal");
1603 case 300: return _("Team");
1604 case 200: return _("Company");
1605 case 100: return _("Public");
1607 return _("Unknown");
1610 static const guint containers[] = {32000, 400, 300, 200, 100};
1611 #define CONTAINERS_LEN (sizeof(containers) / sizeof(guint))
1614 static int
1615 sipe_find_member_access_level(struct sipe_core_private *sipe_private,
1616 const gchar *type,
1617 const gchar *value)
1619 unsigned int i = 0;
1620 const gchar *value_mod = value;
1622 if (!type) return -1;
1624 if (sipe_strequal("user", type)) {
1625 value_mod = sipe_get_no_sip_uri(value);
1628 for (i = 0; i < CONTAINERS_LEN; i++) {
1629 struct sipe_container_member *member;
1630 struct sipe_container *container = sipe_find_container(sipe_private, containers[i]);
1631 if (!container) continue;
1633 member = sipe_find_container_member(container, type, value_mod);
1634 if (member) return containers[i];
1637 return -1;
1640 /** Member type: user, domain, sameEnterprise, federated, publicCloud; everyone */
1641 static int
1642 sipe_find_access_level(struct sipe_core_private *sipe_private,
1643 const gchar *type,
1644 const gchar *value,
1645 gboolean *is_group_access)
1647 int container_id = -1;
1649 if (sipe_strequal("user", type)) {
1650 const char *domain;
1651 const char *no_sip_uri = sipe_get_no_sip_uri(value);
1653 container_id = sipe_find_member_access_level(sipe_private, "user", no_sip_uri);
1654 if (container_id >= 0) {
1655 if (is_group_access) *is_group_access = FALSE;
1656 return container_id;
1659 domain = sipe_get_domain(no_sip_uri);
1660 container_id = sipe_find_member_access_level(sipe_private, "domain", domain);
1661 if (container_id >= 0) {
1662 if (is_group_access) *is_group_access = TRUE;
1663 return container_id;
1666 container_id = sipe_find_member_access_level(sipe_private, "sameEnterprise", NULL);
1667 if ((container_id >= 0) && sipe_strcase_equal(sipe_private->public.sip_domain, domain)) {
1668 if (is_group_access) *is_group_access = TRUE;
1669 return container_id;
1672 container_id = sipe_find_member_access_level(sipe_private, "publicCloud", NULL);
1673 if ((container_id >= 0) && sipe_is_public_domain(domain)) {
1674 if (is_group_access) *is_group_access = TRUE;
1675 return container_id;
1678 container_id = sipe_find_member_access_level(sipe_private, "everyone", NULL);
1679 if ((container_id >= 0)) {
1680 if (is_group_access) *is_group_access = TRUE;
1681 return container_id;
1683 } else {
1684 container_id = sipe_find_member_access_level(sipe_private, type, value);
1685 if (is_group_access) *is_group_access = FALSE;
1688 return container_id;
1692 * @param container_id a new access level. If -1 then current access level
1693 * is just removed (I.e. the member is removed from all containers).
1694 * @param type a type of member. E.g. "user", "sameEnterprise", etc.
1695 * @param value a value for member. E.g. SIP URI for "user" member type.
1697 static void
1698 sipe_change_access_level(struct sipe_core_private *sipe_private,
1699 const int container_id,
1700 const gchar *type,
1701 const gchar *value)
1703 unsigned int i;
1704 int current_container_id = -1;
1705 char *container_xmls = NULL;
1707 /* for each container: find/delete */
1708 for (i = 0; i < CONTAINERS_LEN; i++) {
1709 struct sipe_container_member *member;
1710 struct sipe_container *container = sipe_find_container(sipe_private, containers[i]);
1712 if (!container) continue;
1714 member = sipe_find_container_member(container, type, value);
1715 if (member) {
1716 current_container_id = containers[i];
1717 /* delete/publish current access level */
1718 if (container_id < 0 || container_id != current_container_id) {
1719 sipe_send_container_members_prepare(current_container_id, container->version, "remove", type, value, &container_xmls);
1720 /* remove member from our cache, to be able to recalculate AL below */
1721 container->members = g_slist_remove(container->members, member);
1722 current_container_id = -1;
1727 /* recalculate AL below */
1728 current_container_id = sipe_find_access_level(sipe_private, type, value, NULL);
1730 /* assign/publish new access level */
1731 if (container_id != current_container_id && container_id >= 0) {
1732 struct sipe_container *container = sipe_find_container(sipe_private, container_id);
1733 guint version = container ? container->version : 0;
1735 sipe_send_container_members_prepare(container_id, version, "add", type, value, &container_xmls);
1738 if (container_xmls) {
1739 sipe_send_set_container_members(sipe_private, container_xmls);
1741 g_free(container_xmls);
1744 static void
1745 free_publication(struct sipe_publication *publication)
1747 g_free(publication->category);
1748 g_free(publication->cal_event_hash);
1749 g_free(publication->note);
1751 g_free(publication->working_hours_xml_str);
1752 g_free(publication->fb_start_str);
1753 g_free(publication->free_busy_base64);
1755 g_free(publication);
1758 /* key is <category><instance><container> */
1759 static gboolean
1760 sipe_is_our_publication(struct sipe_core_private *sipe_private,
1761 const gchar *key)
1763 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
1764 GSList *entry;
1766 /* filling keys for our publications if not yet cached */
1767 if (!sip->our_publication_keys) {
1768 guint device_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_DEVICE);
1769 guint machine_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_MACHINE);
1770 guint user_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_USER);
1771 guint calendar_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_CALENDAR);
1772 guint cal_oof_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_CALENDAR_OOF);
1773 guint cal_data_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_CALENDAR_DATA);
1774 guint note_oof_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_NOTE_OOF);
1776 SIPE_DEBUG_INFO_NOFORMAT("* Our Publication Instances *");
1777 SIPE_DEBUG_INFO("\tDevice : %u\t0x%08X", device_instance, device_instance);
1778 SIPE_DEBUG_INFO("\tMachine State : %u\t0x%08X", machine_instance, machine_instance);
1779 SIPE_DEBUG_INFO("\tUser Stare : %u\t0x%08X", user_instance, user_instance);
1780 SIPE_DEBUG_INFO("\tCalendar State : %u\t0x%08X", calendar_instance, calendar_instance);
1781 SIPE_DEBUG_INFO("\tCalendar OOF State : %u\t0x%08X", cal_oof_instance, cal_oof_instance);
1782 SIPE_DEBUG_INFO("\tCalendar FreeBusy : %u\t0x%08X", cal_data_instance, cal_data_instance);
1783 SIPE_DEBUG_INFO("\tOOF Note : %u\t0x%08X", note_oof_instance, note_oof_instance);
1784 SIPE_DEBUG_INFO("\tNote : %u", 0);
1785 SIPE_DEBUG_INFO("\tCalendar WorkingHours: %u", 0);
1787 /* device */
1788 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1789 g_strdup_printf("<%s><%u><%u>", "device", device_instance, 2));
1791 /* state:machineState */
1792 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1793 g_strdup_printf("<%s><%u><%u>", "state", machine_instance, 2));
1794 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1795 g_strdup_printf("<%s><%u><%u>", "state", machine_instance, 3));
1797 /* state:userState */
1798 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1799 g_strdup_printf("<%s><%u><%u>", "state", user_instance, 2));
1800 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1801 g_strdup_printf("<%s><%u><%u>", "state", user_instance, 3));
1803 /* state:calendarState */
1804 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1805 g_strdup_printf("<%s><%u><%u>", "state", calendar_instance, 2));
1806 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1807 g_strdup_printf("<%s><%u><%u>", "state", calendar_instance, 3));
1809 /* state:calendarState OOF */
1810 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1811 g_strdup_printf("<%s><%u><%u>", "state", cal_oof_instance, 2));
1812 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1813 g_strdup_printf("<%s><%u><%u>", "state", cal_oof_instance, 3));
1815 /* note */
1816 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1817 g_strdup_printf("<%s><%u><%u>", "note", 0, 200));
1818 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1819 g_strdup_printf("<%s><%u><%u>", "note", 0, 300));
1820 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1821 g_strdup_printf("<%s><%u><%u>", "note", 0, 400));
1823 /* note OOF */
1824 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1825 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 200));
1826 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1827 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 300));
1828 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1829 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 400));
1831 /* calendarData:WorkingHours */
1832 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1833 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 1));
1834 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1835 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 100));
1836 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1837 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 200));
1838 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1839 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 300));
1840 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1841 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 400));
1842 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1843 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 32000));
1845 /* calendarData:FreeBusy */
1846 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1847 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 1));
1848 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1849 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 100));
1850 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1851 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 200));
1852 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1853 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 300));
1854 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1855 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 400));
1856 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1857 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 32000));
1859 //SIPE_DEBUG_INFO("sipe_is_our_publication: sip->our_publication_keys length=%d",
1860 // sip->our_publication_keys ? (int) g_slist_length(sip->our_publication_keys) : -1);
1863 //SIPE_DEBUG_INFO("sipe_is_our_publication: key=%s", key);
1865 entry = sip->our_publication_keys;
1866 while (entry) {
1867 //SIPE_DEBUG_INFO(" sipe_is_our_publication: entry->data=%s", entry->data);
1868 if (sipe_strequal(entry->data, key)) {
1869 return TRUE;
1871 entry = entry->next;
1873 return FALSE;
1876 /** Property names to store in blist.xml */
1877 #define ALIAS_PROP "alias"
1878 #define EMAIL_PROP "email"
1879 #define PHONE_PROP "phone"
1880 #define PHONE_DISPLAY_PROP "phone-display"
1881 #define PHONE_MOBILE_PROP "phone-mobile"
1882 #define PHONE_MOBILE_DISPLAY_PROP "phone-mobile-display"
1883 #define PHONE_HOME_PROP "phone-home"
1884 #define PHONE_HOME_DISPLAY_PROP "phone-home-display"
1885 #define PHONE_OTHER_PROP "phone-other"
1886 #define PHONE_OTHER_DISPLAY_PROP "phone-other-display"
1887 #define PHONE_CUSTOM1_PROP "phone-custom1"
1888 #define PHONE_CUSTOM1_DISPLAY_PROP "phone-custom1-display"
1889 #define SITE_PROP "site"
1890 #define COMPANY_PROP "company"
1891 #define DEPARTMENT_PROP "department"
1892 #define TITLE_PROP "title"
1893 #define OFFICE_PROP "office"
1894 /** implies work address */
1895 #define ADDRESS_STREET_PROP "address-street"
1896 #define ADDRESS_CITY_PROP "address-city"
1897 #define ADDRESS_STATE_PROP "address-state"
1898 #define ADDRESS_ZIPCODE_PROP "address-zipcode"
1899 #define ADDRESS_COUNTRYCODE_PROP "address-country-code"
1902 * Tries to figure out user first and last name
1903 * based on Display Name and email properties.
1905 * Allocates memory - must be g_free()'d
1907 * Examples to parse:
1908 * First Last
1909 * First Last - Company Name
1910 * Last, First
1911 * Last, First M.
1912 * Last, First (C)(STP) (Company)
1913 * first.last@company.com (preprocessed as "first last")
1914 * first.last.company.com@reuters.net (preprocessed as "first last company com")
1916 * Unusable examples:
1917 * user@company.com (preprocessed as "user")
1918 * first.m.last@company.com (preprocessed as "first m last")
1919 * user.company.com@reuters.net (preprocessed as "user company com")
1921 static void
1922 sipe_get_first_last_names(struct sipe_core_private *sipe_private,
1923 const char *uri,
1924 char **first_name,
1925 char **last_name)
1927 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
1928 sipe_backend_buddy p_buddy;
1929 char *display_name;
1930 gchar *email;
1931 const char *first, *last;
1932 char *tmp;
1933 char **parts;
1934 gboolean has_comma = FALSE;
1936 if (!sip || !uri) return;
1938 p_buddy = sipe_backend_buddy_find(SIPE_CORE_PUBLIC, uri, NULL);
1940 if (!p_buddy) return;
1942 display_name = sipe_backend_buddy_get_alias(SIPE_CORE_PUBLIC, p_buddy);
1943 email = sipe_backend_buddy_get_string(SIPE_CORE_PUBLIC, p_buddy, SIPE_BUDDY_INFO_EMAIL);
1945 if (!display_name && !email) return;
1947 /* if no display name, make "first last anything_else" out of email */
1948 if (email && !display_name) {
1949 display_name = g_strndup(email, strstr(email, "@") - email);
1950 display_name = sipe_utils_str_replace((tmp = display_name), ".", " ");
1951 g_free(tmp);
1954 if (display_name) {
1955 has_comma = (strstr(display_name, ",") != NULL);
1956 display_name = sipe_utils_str_replace((tmp = display_name), ", ", " ");
1957 g_free(tmp);
1958 display_name = sipe_utils_str_replace((tmp = display_name), ",", " ");
1959 g_free(tmp);
1962 parts = g_strsplit(display_name, " ", 0);
1964 if (!parts[0] || !parts[1]) {
1965 g_free(email);
1966 g_free(display_name);
1967 g_strfreev(parts);
1968 return;
1971 if (has_comma) {
1972 last = parts[0];
1973 first = parts[1];
1974 } else {
1975 first = parts[0];
1976 last = parts[1];
1979 if (first_name) {
1980 *first_name = g_strstrip(g_strdup(first));
1983 if (last_name) {
1984 *last_name = g_strstrip(g_strdup(last));
1987 g_free(email);
1988 g_free(display_name);
1989 g_strfreev(parts);
1993 * Update user information
1995 * @param uri buddy SIP URI with 'sip:' prefix whose info we want to change.
1996 * @param property_name
1997 * @param property_value may be modified to strip white space
1999 static void
2000 sipe_update_user_info(struct sipe_core_private *sipe_private,
2001 const char *uri,
2002 sipe_buddy_info_fields propkey,
2003 char *property_value)
2005 GSList *buddies, *entry;
2007 if (property_value)
2008 property_value = g_strstrip(property_value);
2010 entry = buddies = sipe_backend_buddy_find_all(SIPE_CORE_PUBLIC, uri, NULL); /* all buddies in different groups */
2011 while (entry) {
2012 gchar *prop_str;
2013 gchar *server_alias;
2014 gchar *alias;
2015 sipe_backend_buddy p_buddy = entry->data;
2017 /* for Display Name */
2018 if (propkey == SIPE_BUDDY_INFO_DISPLAY_NAME) {
2019 alias = sipe_backend_buddy_get_alias(SIPE_CORE_PUBLIC, p_buddy);
2020 if (property_value && sipe_is_bad_alias(uri, alias)) {
2021 SIPE_DEBUG_INFO("Replacing alias for %s with %s", uri, property_value);
2022 sipe_backend_buddy_set_alias(SIPE_CORE_PUBLIC, p_buddy, property_value);
2024 g_free(alias);
2026 server_alias = sipe_backend_buddy_get_server_alias(SIPE_CORE_PUBLIC, p_buddy);
2027 if (!is_empty(property_value) &&
2028 (!sipe_strequal(property_value, server_alias) || is_empty(server_alias)) )
2030 SIPE_DEBUG_INFO("Replacing service alias for %s with %s", uri, property_value);
2031 sipe_backend_buddy_set_server_alias(SIPE_CORE_PUBLIC, p_buddy, property_value);
2033 g_free(server_alias);
2035 /* for other properties */
2036 else {
2037 if (!is_empty(property_value)) {
2038 prop_str = sipe_backend_buddy_get_string(SIPE_CORE_PUBLIC, p_buddy, propkey);
2039 if (!prop_str || !sipe_strcase_equal(prop_str, property_value)) {
2040 sipe_backend_buddy_set_string(SIPE_CORE_PUBLIC, p_buddy, propkey, property_value);
2042 g_free(prop_str);
2046 entry = entry->next;
2048 g_slist_free(buddies);
2052 * Update user phone
2053 * Suitable for both 2005 and 2007 systems.
2055 * @param uri buddy SIP URI with 'sip:' prefix whose info we want to change.
2056 * @param phone_type
2057 * @param phone may be modified to strip white space
2058 * @param phone_display_string may be modified to strip white space
2060 static void
2061 sipe_update_user_phone(struct sipe_core_private *sipe_private,
2062 const gchar *uri,
2063 const gchar *phone_type,
2064 gchar *phone,
2065 gchar *phone_display_string)
2067 sipe_buddy_info_fields phone_node = SIPE_BUDDY_INFO_WORK_PHONE; /* work phone by default */
2068 sipe_buddy_info_fields phone_display_node = SIPE_BUDDY_INFO_WORK_PHONE_DISPLAY; /* work phone by default */
2070 if(!phone || strlen(phone) == 0) return;
2072 if ((sipe_strequal(phone_type, "mobile") || sipe_strequal(phone_type, "cell"))) {
2073 phone_node = SIPE_BUDDY_INFO_MOBILE_PHONE;
2074 phone_display_node = SIPE_BUDDY_INFO_MOBILE_PHONE_DISPLAY;
2075 } else if (sipe_strequal(phone_type, "home")) {
2076 phone_node = SIPE_BUDDY_INFO_HOME_PHONE;
2077 phone_display_node = SIPE_BUDDY_INFO_HOME_PHONE_DISPLAY;
2078 } else if (sipe_strequal(phone_type, "other")) {
2079 phone_node = SIPE_BUDDY_INFO_OTHER_PHONE;
2080 phone_display_node = SIPE_BUDDY_INFO_OTHER_PHONE_DISPLAY;
2081 } else if (sipe_strequal(phone_type, "custom1")) {
2082 phone_node = SIPE_BUDDY_INFO_CUSTOM1_PHONE;
2083 phone_display_node = SIPE_BUDDY_INFO_CUSTOM1_PHONE_DISPLAY;
2086 sipe_update_user_info(sipe_private, uri, phone_node, phone);
2087 if (phone_display_string) {
2088 sipe_update_user_info(sipe_private, uri, phone_display_node, phone_display_string);
2092 void
2093 sipe_core_update_calendar(struct sipe_core_public *sipe_public)
2095 SIPE_DEBUG_INFO_NOFORMAT("sipe_core_update_calendar: started.");
2097 /* Do in parallel.
2098 * If failed, the branch will be disabled for subsequent calls.
2099 * Can't rely that user turned the functionality on in account settings.
2101 sipe_ews_update_calendar(SIPE_CORE_PRIVATE);
2102 sipe_domino_update_calendar(SIPE_CORE_PRIVATE);
2104 /* schedule repeat */
2105 sipe_schedule_seconds(SIPE_CORE_PRIVATE,
2106 "<+update-calendar>",
2107 NULL,
2108 UPDATE_CALENDAR_INTERVAL,
2109 (sipe_schedule_action)sipe_core_update_calendar,
2110 NULL);
2112 SIPE_DEBUG_INFO_NOFORMAT("sipe_core_update_calendar: finished.");
2116 * This method motivates Purple's Host (e.g. Pidgin) to update its UI
2117 * by using standard Purple's means of signals and saved statuses.
2119 * Thus all UI elements get updated: Status Button with Note, docklet.
2120 * This is ablolutely important as both our status and note can come
2121 * inbound (roaming) or be updated programmatically (e.g. based on our
2122 * calendar data).
2124 static void
2125 sipe_set_purple_account_status_and_note(const PurpleAccount *account,
2126 const char *status_id,
2127 const char *message,
2128 time_t do_not_publish[])
2130 PurpleStatus *status = purple_account_get_active_status(account);
2131 gboolean changed = TRUE;
2133 if (g_str_equal(status_id, purple_status_get_id(status)) &&
2134 sipe_strequal(message, purple_status_get_attr_string(status, SIPE_STATUS_ATTR_ID_MESSAGE)))
2136 changed = FALSE;
2139 if (purple_savedstatus_is_idleaway()) {
2140 changed = FALSE;
2143 if (changed) {
2144 PurpleSavedStatus *saved_status;
2145 const PurpleStatusType *acct_status_type =
2146 purple_status_type_find_with_id(account->status_types, status_id);
2147 PurpleStatusPrimitive primitive = purple_status_type_get_primitive(acct_status_type);
2148 sipe_activity activity = sipe_get_activity_by_token(status_id);
2150 saved_status = purple_savedstatus_find_transient_by_type_and_message(primitive, message);
2151 if (saved_status) {
2152 purple_savedstatus_set_substatus(saved_status, account, acct_status_type, message);
2155 /* If this type+message is unique then create a new transient saved status
2156 * Ref: gtkstatusbox.c
2158 if (!saved_status) {
2159 GList *tmp;
2160 GList *active_accts = purple_accounts_get_all_active();
2162 saved_status = purple_savedstatus_new(NULL, primitive);
2163 purple_savedstatus_set_message(saved_status, message);
2165 for (tmp = active_accts; tmp != NULL; tmp = tmp->next) {
2166 purple_savedstatus_set_substatus(saved_status,
2167 (PurpleAccount *)tmp->data, acct_status_type, message);
2169 g_list_free(active_accts);
2172 do_not_publish[activity] = time(NULL);
2173 SIPE_DEBUG_INFO("sipe_set_purple_account_status_and_note: do_not_publish[%s]=%d [now]",
2174 status_id, (int)do_not_publish[activity]);
2176 /* Set the status for each account */
2177 purple_savedstatus_activate(saved_status);
2181 struct hash_table_delete_payload {
2182 GHashTable *hash_table;
2183 guint container;
2186 static void
2187 sipe_remove_category_container_publications_cb(const char *name,
2188 struct sipe_publication *publication,
2189 struct hash_table_delete_payload *payload)
2191 if (publication->container == payload->container) {
2192 g_hash_table_remove(payload->hash_table, name);
2195 static void
2196 sipe_remove_category_container_publications(GHashTable *our_publications,
2197 const char *category,
2198 guint container)
2200 struct hash_table_delete_payload payload;
2201 payload.hash_table = g_hash_table_lookup(our_publications, category);
2203 if (!payload.hash_table) return;
2205 payload.container = container;
2206 g_hash_table_foreach(payload.hash_table, (GHFunc)sipe_remove_category_container_publications_cb, &payload);
2209 static void
2210 send_publish_category_initial(struct sipe_core_private *sipe_private);
2213 * When we receive some self (BE) NOTIFY with a new subscriber
2214 * we sends a setSubscribers request to him [SIP-PRES] 4.8
2217 static void sipe_process_roaming_self(struct sipe_core_private *sipe_private,
2218 struct sipmsg *msg)
2220 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
2221 gchar *contact;
2222 gchar *to;
2223 sipe_xml *xml;
2224 const sipe_xml *node;
2225 const sipe_xml *node2;
2226 char *display_name = NULL;
2227 char *uri;
2228 GSList *category_names = NULL;
2229 int aggreg_avail = 0;
2230 gboolean do_update_status = FALSE;
2231 gboolean has_note_cleaned = FALSE;
2232 GHashTable *devices;
2234 SIPE_DEBUG_INFO_NOFORMAT("sipe_process_roaming_self");
2236 xml = sipe_xml_parse(msg->body, msg->bodylen);
2237 if (!xml) return;
2239 contact = get_contact(sipe_private);
2240 to = sip_uri_self(sipe_private);
2242 /* categories */
2243 /* set list of categories participating in this XML */
2244 for (node = sipe_xml_child(xml, "categories/category"); node; node = sipe_xml_twin(node)) {
2245 const gchar *name = sipe_xml_attribute(node, "name");
2246 category_names = slist_insert_unique_sorted(category_names, (gchar *)name, (GCompareFunc)strcmp);
2248 SIPE_DEBUG_INFO("sipe_process_roaming_self: category_names length=%d",
2249 category_names ? (int) g_slist_length(category_names) : -1);
2250 /* drop category information */
2251 if (category_names) {
2252 GSList *entry = category_names;
2253 while (entry) {
2254 GHashTable *cat_publications;
2255 const gchar *category = entry->data;
2256 entry = entry->next;
2257 SIPE_DEBUG_INFO("sipe_process_roaming_self: dropping category: %s", category);
2258 cat_publications = g_hash_table_lookup(sip->our_publications, category);
2259 if (cat_publications) {
2260 g_hash_table_remove(sip->our_publications, category);
2261 SIPE_DEBUG_INFO("sipe_process_roaming_self: dropped category: %s", category);
2265 g_slist_free(category_names);
2267 /* filling our categories reflected in roaming data */
2268 devices = g_hash_table_new_full(g_str_hash, g_str_equal,
2269 g_free, NULL);
2270 for (node = sipe_xml_child(xml, "categories/category"); node; node = sipe_xml_twin(node)) {
2271 const char *tmp;
2272 const gchar *name = sipe_xml_attribute(node, "name");
2273 guint container = sipe_xml_int_attribute(node, "container", -1);
2274 guint instance = sipe_xml_int_attribute(node, "instance", -1);
2275 guint version = sipe_xml_int_attribute(node, "version", 0);
2276 time_t publish_time = (tmp = sipe_xml_attribute(node, "publishTime")) ?
2277 sipe_utils_str_to_time(tmp) : 0;
2278 gchar *key;
2279 GHashTable *cat_publications = g_hash_table_lookup(sip->our_publications, name);
2281 /* Ex. clear note: <category name="note"/> */
2282 if (container == (guint)-1) {
2283 g_free(sip->note);
2284 sip->note = NULL;
2285 do_update_status = TRUE;
2286 continue;
2289 /* Ex. clear note: <category name="note" container="200"/> */
2290 if (instance == (guint)-1) {
2291 if (container == 200) {
2292 g_free(sip->note);
2293 sip->note = NULL;
2294 do_update_status = TRUE;
2296 SIPE_DEBUG_INFO("sipe_process_roaming_self: removing publications for: %s/%u", name, container);
2297 sipe_remove_category_container_publications(
2298 sip->our_publications, name, container);
2299 continue;
2302 /* key is <category><instance><container> */
2303 key = g_strdup_printf("<%s><%u><%u>", name, instance, container);
2304 SIPE_DEBUG_INFO("sipe_process_roaming_self: key=%s version=%d", key, version);
2306 /* capture all userState publication for later clean up if required */
2307 if (sipe_strequal(name, "state") && (container == 2 || container == 3)) {
2308 const sipe_xml *xn_state = sipe_xml_child(node, "state");
2310 if (xn_state && sipe_strequal(sipe_xml_attribute(xn_state, "type"), "userState")) {
2311 struct sipe_publication *publication = g_new0(struct sipe_publication, 1);
2312 publication->category = g_strdup(name);
2313 publication->instance = instance;
2314 publication->container = container;
2315 publication->version = version;
2317 if (!sip->user_state_publications) {
2318 sip->user_state_publications = g_hash_table_new_full(
2319 g_str_hash, g_str_equal,
2320 g_free, (GDestroyNotify)free_publication);
2322 g_hash_table_insert(sip->user_state_publications, g_strdup(key), publication);
2323 SIPE_DEBUG_INFO("sipe_process_roaming_self: added to user_state_publications key=%s version=%d",
2324 key, version);
2328 /* count each client instance only once */
2329 if (sipe_strequal(name, "device"))
2330 g_hash_table_replace(devices, g_strdup_printf("%u", instance), NULL);
2332 if (sipe_is_our_publication(sipe_private, key)) {
2333 struct sipe_publication *publication = g_new0(struct sipe_publication, 1);
2335 publication->category = g_strdup(name);
2336 publication->instance = instance;
2337 publication->container = container;
2338 publication->version = version;
2340 /* filling publication->availability */
2341 if (sipe_strequal(name, "state")) {
2342 const sipe_xml *xn_state = sipe_xml_child(node, "state");
2343 const sipe_xml *xn_avail = sipe_xml_child(xn_state, "availability");
2345 if (xn_avail) {
2346 gchar *avail_str = sipe_xml_data(xn_avail);
2347 if (avail_str) {
2348 publication->availability = atoi(avail_str);
2350 g_free(avail_str);
2352 /* for calendarState */
2353 if (xn_state && sipe_strequal(sipe_xml_attribute(xn_state, "type"), "calendarState")) {
2354 const sipe_xml *xn_activity = sipe_xml_child(xn_state, "activity");
2355 struct sipe_cal_event *event = g_new0(struct sipe_cal_event, 1);
2357 event->start_time = sipe_utils_str_to_time(sipe_xml_attribute(xn_state, "startTime"));
2358 if (xn_activity) {
2359 if (sipe_strequal(sipe_xml_attribute(xn_activity, "token"),
2360 sipe_activity_map[SIPE_ACTIVITY_IN_MEETING].token))
2362 event->is_meeting = TRUE;
2365 event->subject = sipe_xml_data(sipe_xml_child(xn_state, "meetingSubject"));
2366 event->location = sipe_xml_data(sipe_xml_child(xn_state, "meetingLocation"));
2368 publication->cal_event_hash = sipe_cal_event_hash(event);
2369 SIPE_DEBUG_INFO("sipe_process_roaming_self: hash=%s",
2370 publication->cal_event_hash);
2371 sipe_cal_event_free(event);
2374 /* filling publication->note */
2375 if (sipe_strequal(name, "note")) {
2376 const sipe_xml *xn_body = sipe_xml_child(node, "note/body");
2378 if (!has_note_cleaned) {
2379 has_note_cleaned = TRUE;
2381 g_free(sip->note);
2382 sip->note = NULL;
2383 sip->note_since = publish_time;
2385 do_update_status = TRUE;
2388 g_free(publication->note);
2389 publication->note = NULL;
2390 if (xn_body) {
2391 char *tmp;
2393 publication->note = g_markup_escape_text((tmp = sipe_xml_data(xn_body)), -1);
2394 g_free(tmp);
2395 if (publish_time >= sip->note_since) {
2396 g_free(sip->note);
2397 sip->note = g_strdup(publication->note);
2398 sip->note_since = publish_time;
2399 sip->is_oof_note = sipe_strequal(sipe_xml_attribute(xn_body, "type"), "OOF");
2401 do_update_status = TRUE;
2406 /* filling publication->fb_start_str, free_busy_base64, working_hours_xml_str */
2407 if (sipe_strequal(name, "calendarData") && (publication->container == 300)) {
2408 const sipe_xml *xn_free_busy = sipe_xml_child(node, "calendarData/freeBusy");
2409 const sipe_xml *xn_working_hours = sipe_xml_child(node, "calendarData/WorkingHours");
2410 if (xn_free_busy) {
2411 publication->fb_start_str = g_strdup(sipe_xml_attribute(xn_free_busy, "startTime"));
2412 publication->free_busy_base64 = sipe_xml_data(xn_free_busy);
2414 if (xn_working_hours) {
2415 publication->working_hours_xml_str = sipe_xml_stringify(xn_working_hours);
2419 if (!cat_publications) {
2420 cat_publications = g_hash_table_new_full(
2421 g_str_hash, g_str_equal,
2422 g_free, (GDestroyNotify)free_publication);
2423 g_hash_table_insert(sip->our_publications, g_strdup(name), cat_publications);
2424 SIPE_DEBUG_INFO("sipe_process_roaming_self: added GHashTable cat=%s", name);
2426 g_hash_table_insert(cat_publications, g_strdup(key), publication);
2427 SIPE_DEBUG_INFO("sipe_process_roaming_self: added key=%s version=%d", key, version);
2429 g_free(key);
2431 /* aggregateState (not an our publication) from 2-nd container */
2432 if (sipe_strequal(name, "state") && container == 2) {
2433 const sipe_xml *xn_state = sipe_xml_child(node, "state");
2435 if (xn_state && sipe_strequal(sipe_xml_attribute(xn_state, "type"), "aggregateState")) {
2436 const sipe_xml *xn_avail = sipe_xml_child(xn_state, "availability");
2438 if (xn_avail) {
2439 gchar *avail_str = sipe_xml_data(xn_avail);
2440 if (avail_str) {
2441 aggreg_avail = atoi(avail_str);
2443 g_free(avail_str);
2446 do_update_status = TRUE;
2450 /* userProperties published by server from AD */
2451 if (!sip->csta && sipe_strequal(name, "userProperties")) {
2452 const sipe_xml *line;
2453 /* line, for Remote Call Control (RCC) */
2454 for (line = sipe_xml_child(node, "userProperties/lines/line"); line; line = sipe_xml_twin(line)) {
2455 const gchar *line_server = sipe_xml_attribute(line, "lineServer");
2456 const gchar *line_type = sipe_xml_attribute(line, "lineType");
2457 gchar *line_uri;
2459 if (!line_server || !(sipe_strequal(line_type, "Rcc") || sipe_strequal(line_type, "Dual"))) continue;
2461 line_uri = sipe_xml_data(line);
2462 if (line_uri) {
2463 SIPE_DEBUG_INFO("sipe_process_roaming_self: line_uri=%s server=%s", line_uri, line_server);
2464 sip_csta_open(sipe_private, line_uri, line_server);
2466 g_free(line_uri);
2468 break;
2472 SIPE_DEBUG_INFO("sipe_process_roaming_self: sip->our_publications size=%d",
2473 sip->our_publications ? (int) g_hash_table_size(sip->our_publications) : -1);
2475 /* active clients for user account */
2476 if (g_hash_table_size(devices) > 1) {
2477 SIPE_CORE_PRIVATE_FLAG_SET(MPOP);
2478 SIPE_DEBUG_INFO("sipe_process_roaming_self: multiple clients detected (%d)",
2479 g_hash_table_size(devices));
2480 } else {
2481 SIPE_CORE_PRIVATE_FLAG_UNSET(MPOP);
2482 SIPE_DEBUG_INFO_NOFORMAT("sipe_process_roaming_self: single client detected");
2484 g_hash_table_destroy(devices);
2486 /* containers */
2487 for (node = sipe_xml_child(xml, "containers/container"); node; node = sipe_xml_twin(node)) {
2488 guint id = sipe_xml_int_attribute(node, "id", 0);
2489 struct sipe_container *container = sipe_find_container(sipe_private, id);
2491 if (container) {
2492 sip->containers = g_slist_remove(sip->containers, container);
2493 SIPE_DEBUG_INFO("sipe_process_roaming_self: removed existing container id=%d v%d", container->id, container->version);
2494 free_container(container);
2496 container = g_new0(struct sipe_container, 1);
2497 container->id = id;
2498 container->version = sipe_xml_int_attribute(node, "version", 0);
2499 sip->containers = g_slist_append(sip->containers, container);
2500 SIPE_DEBUG_INFO("sipe_process_roaming_self: added container id=%d v%d", container->id, container->version);
2502 for (node2 = sipe_xml_child(node, "member"); node2; node2 = sipe_xml_twin(node2)) {
2503 struct sipe_container_member *member = g_new0(struct sipe_container_member, 1);
2504 member->type = g_strdup(sipe_xml_attribute(node2, "type"));
2505 member->value = g_strdup(sipe_xml_attribute(node2, "value"));
2506 container->members = g_slist_append(container->members, member);
2507 SIPE_DEBUG_INFO("sipe_process_roaming_self: added container member type=%s value=%s",
2508 member->type, member->value ? member->value : "");
2512 SIPE_DEBUG_INFO("sipe_process_roaming_self: sip->access_level_set=%s", sip->access_level_set ? "TRUE" : "FALSE");
2513 if (!sip->access_level_set && sipe_xml_child(xml, "containers")) {
2514 char *container_xmls = NULL;
2515 int sameEnterpriseAL = sipe_find_access_level(sipe_private, "sameEnterprise", NULL, NULL);
2516 int federatedAL = sipe_find_access_level(sipe_private, "federated", NULL, NULL);
2518 SIPE_DEBUG_INFO("sipe_process_roaming_self: sameEnterpriseAL=%d", sameEnterpriseAL);
2519 SIPE_DEBUG_INFO("sipe_process_roaming_self: federatedAL=%d", federatedAL);
2520 /* initial set-up to let counterparties see your status */
2521 if (sameEnterpriseAL < 0) {
2522 struct sipe_container *container = sipe_find_container(sipe_private, 200);
2523 guint version = container ? container->version : 0;
2524 sipe_send_container_members_prepare(200, version, "add", "sameEnterprise", NULL, &container_xmls);
2526 if (federatedAL < 0) {
2527 struct sipe_container *container = sipe_find_container(sipe_private, 100);
2528 guint version = container ? container->version : 0;
2529 sipe_send_container_members_prepare(100, version, "add", "federated", NULL, &container_xmls);
2531 sip->access_level_set = TRUE;
2533 if (container_xmls) {
2534 sipe_send_set_container_members(sipe_private, container_xmls);
2536 g_free(container_xmls);
2539 /* Refresh contacts' blocked status */
2540 sipe_refresh_blocked_status(sipe_private);
2542 /* subscribers */
2543 for (node = sipe_xml_child(xml, "subscribers/subscriber"); node; node = sipe_xml_twin(node)) {
2544 const char *user;
2545 const char *acknowledged;
2546 gchar *hdr;
2547 gchar *body;
2549 user = sipe_xml_attribute(node, "user"); /* without 'sip:' prefix */
2550 if (!user) continue;
2551 SIPE_DEBUG_INFO("sipe_process_roaming_self: user %s", user);
2552 display_name = g_strdup(sipe_xml_attribute(node, "displayName"));
2553 uri = sip_uri_from_name(user);
2555 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_DISPLAY_NAME, display_name);
2557 acknowledged= sipe_xml_attribute(node, "acknowledged");
2558 if(sipe_strcase_equal(acknowledged,"false")){
2559 SIPE_DEBUG_INFO("sipe_process_roaming_self: user added you %s", user);
2560 if (!sipe_backend_buddy_find(SIPE_CORE_PUBLIC, uri, NULL)) {
2561 sipe_backend_buddy_request_add(SIPE_CORE_PUBLIC, uri, display_name);
2564 hdr = g_strdup_printf(
2565 "Contact: %s\r\n"
2566 "Content-Type: application/msrtc-presence-setsubscriber+xml\r\n", contact);
2568 body = g_strdup_printf(
2569 "<setSubscribers xmlns=\"http://schemas.microsoft.com/2006/09/sip/presence-subscribers\">"
2570 "<subscriber user=\"%s\" acknowledged=\"true\"/>"
2571 "</setSubscribers>", user);
2573 sip_transport_service(sipe_private,
2575 hdr,
2576 body,
2577 NULL);
2578 g_free(body);
2579 g_free(hdr);
2581 g_free(display_name);
2582 g_free(uri);
2585 g_free(contact);
2586 sipe_xml_free(xml);
2588 /* Publish initial state if not yet.
2589 * Assuming this happens on initial responce to subscription to roaming-self
2590 * so we've already updated our roaming data in full.
2591 * Only for 2007+
2593 if (!sip->initial_state_published) {
2594 send_publish_category_initial(sipe_private);
2595 sipe_groupchat_init(sipe_private);
2596 sip->initial_state_published = TRUE;
2597 /* dalayed run */
2598 sipe_schedule_seconds(sipe_private,
2599 "<+update-calendar>",
2600 NULL,
2601 UPDATE_CALENDAR_DELAY,
2602 (sipe_schedule_action)sipe_core_update_calendar,
2603 NULL);
2604 do_update_status = FALSE;
2605 } else if (aggreg_avail) {
2607 g_free(sip->status);
2608 if (aggreg_avail && aggreg_avail < 18000) { /* not offline */
2609 sip->status = g_strdup(sipe_get_status_by_availability(aggreg_avail, NULL));
2610 } else {
2611 sip->status = g_strdup(SIPE_STATUS_ID_INVISIBLE); /* not not let offline status switch us off */
2615 if (do_update_status) {
2616 SIPE_DEBUG_INFO("sipe_process_roaming_self: switch to '%s' for the account", sip->status);
2617 sipe_set_purple_account_status_and_note(sip->account, sip->status, sip->note, sip->do_not_publish);
2620 g_free(to);
2623 /* IM Session (INVITE and MESSAGE methods) */
2625 static gboolean
2626 process_options_response(SIPE_UNUSED_PARAMETER struct sipe_core_private *sipe_private,
2627 struct sipmsg *msg,
2628 SIPE_UNUSED_PARAMETER struct transaction *trans)
2630 gboolean ret = TRUE;
2632 if (msg->response != 200) {
2633 SIPE_DEBUG_INFO("process_options_response: OPTIONS response is %d", msg->response);
2634 return FALSE;
2637 SIPE_DEBUG_INFO("process_options_response: body:\n%s", msg->body ? msg->body : "");
2639 return ret;
2643 * Asks UA/proxy about its capabilities.
2645 static void sipe_options_request(struct sipe_core_private *sipe_private,
2646 const char *who)
2648 gchar *to = sip_uri(who);
2649 gchar *contact = get_contact(sipe_private);
2650 gchar *request = g_strdup_printf(
2651 "Accept: application/sdp\r\n"
2652 "Contact: %s\r\n", contact);
2653 g_free(contact);
2655 sip_transport_request(sipe_private,
2656 "OPTIONS",
2659 request,
2660 NULL,
2661 NULL,
2662 process_options_response);
2664 g_free(to);
2665 g_free(request);
2668 void
2669 sipe_convo_closed(PurpleConnection * gc, const char *who)
2671 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
2673 SIPE_DEBUG_INFO("conversation with %s closed", who);
2674 sipe_session_close(sipe_private,
2675 sipe_session_find_im(sipe_private, who));
2679 * Returns 2005-style activity and Availability.
2681 * @param status Sipe statis id.
2683 static void
2684 sipe_get_act_avail_by_status_2005(const char *status,
2685 int *activity,
2686 int *availability)
2688 int avail = 300; /* online */
2689 int act = 400; /* Available */
2691 if (sipe_strequal(status, SIPE_STATUS_ID_AWAY)) {
2692 act = 100;
2693 //} else if (sipe_strequal(status, SIPE_STATUS_ID_LUNCH)) {
2694 // act = 150;
2695 } else if (sipe_strequal(status, SIPE_STATUS_ID_BRB)) {
2696 act = 300;
2697 } else if (sipe_strequal(status, SIPE_STATUS_ID_AVAILABLE)) {
2698 act = 400;
2699 //} else if (sipe_strequal(status, SIPE_STATUS_ID_ON_PHONE)) {
2700 // act = 500;
2701 } else if (sipe_strequal(status, SIPE_STATUS_ID_BUSY) ||
2702 sipe_strequal(status, SIPE_STATUS_ID_DND)) {
2703 act = 600;
2704 } else if (sipe_strequal(status, SIPE_STATUS_ID_INVISIBLE) ||
2705 sipe_strequal(status, SIPE_STATUS_ID_OFFLINE)) {
2706 avail = 0; /* offline */
2707 act = 100;
2708 } else {
2709 act = 400; /* Available */
2712 if (activity) *activity = act;
2713 if (availability) *availability = avail;
2717 * [MS-SIP] 2.2.1
2719 * @param activity 2005 aggregated activity. Ex.: 600
2720 * @param availablity 2005 aggregated availablity. Ex.: 300
2722 static const char *
2723 sipe_get_status_by_act_avail_2005(const int activity,
2724 const int availablity,
2725 char **activity_desc)
2727 const char *status_id = NULL;
2728 const char *act = NULL;
2730 if (activity < 150) {
2731 status_id = SIPE_STATUS_ID_AWAY;
2732 } else if (activity < 200) {
2733 //status_id = SIPE_STATUS_ID_LUNCH;
2734 status_id = SIPE_STATUS_ID_AWAY;
2735 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_LUNCH);
2736 } else if (activity < 300) {
2737 //status_id = SIPE_STATUS_ID_IDLE;
2738 status_id = SIPE_STATUS_ID_AWAY;
2739 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_INACTIVE);
2740 } else if (activity < 400) {
2741 status_id = SIPE_STATUS_ID_BRB;
2742 } else if (activity < 500) {
2743 status_id = SIPE_STATUS_ID_AVAILABLE;
2744 } else if (activity < 600) {
2745 //status_id = SIPE_STATUS_ID_ON_PHONE;
2746 status_id = SIPE_STATUS_ID_BUSY;
2747 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_ON_PHONE);
2748 } else if (activity < 700) {
2749 status_id = SIPE_STATUS_ID_BUSY;
2750 } else if (activity < 800) {
2751 status_id = SIPE_STATUS_ID_AWAY;
2752 } else {
2753 status_id = SIPE_STATUS_ID_AVAILABLE;
2756 if (availablity < 100)
2757 status_id = SIPE_STATUS_ID_OFFLINE;
2759 if (activity_desc && act) {
2760 g_free(*activity_desc);
2761 *activity_desc = g_strdup(act);
2764 return status_id;
2768 * [MS-PRES] Table 3: Conversion of legacyInterop elements and attributes to MSRTC elements and attributes.
2770 static const char*
2771 sipe_get_status_by_availability(int avail,
2772 char** activity_desc)
2774 const char *status;
2775 const char *act = NULL;
2777 if (avail < 3000) {
2778 status = SIPE_STATUS_ID_OFFLINE;
2779 } else if (avail < 4500) {
2780 status = SIPE_STATUS_ID_AVAILABLE;
2781 } else if (avail < 6000) {
2782 //status = SIPE_STATUS_ID_IDLE;
2783 status = SIPE_STATUS_ID_AVAILABLE;
2784 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_INACTIVE);
2785 } else if (avail < 7500) {
2786 status = SIPE_STATUS_ID_BUSY;
2787 } else if (avail < 9000) {
2788 //status = SIPE_STATUS_ID_BUSYIDLE;
2789 status = SIPE_STATUS_ID_BUSY;
2790 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_BUSYIDLE);
2791 } else if (avail < 12000) {
2792 status = SIPE_STATUS_ID_DND;
2793 } else if (avail < 15000) {
2794 status = SIPE_STATUS_ID_BRB;
2795 } else if (avail < 18000) {
2796 status = SIPE_STATUS_ID_AWAY;
2797 } else {
2798 status = SIPE_STATUS_ID_OFFLINE;
2801 if (activity_desc && act) {
2802 g_free(*activity_desc);
2803 *activity_desc = g_strdup(act);
2806 return status;
2810 * Returns 2007-style availability value
2812 * @param sipe_status_id (in)
2813 * @param activity_token (out) Must be g_free()'d after use if consumed.
2815 static int
2816 sipe_get_availability_by_status(const char* sipe_status_id, char** activity_token)
2818 int availability;
2819 sipe_activity activity = SIPE_ACTIVITY_UNSET;
2821 if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_AWAY)) {
2822 availability = 15500;
2823 if (!activity_token || !(*activity_token)) {
2824 activity = SIPE_ACTIVITY_AWAY;
2826 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_BRB)) {
2827 availability = 12500;
2828 activity = SIPE_ACTIVITY_BRB;
2829 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_DND)) {
2830 availability = 9500;
2831 activity = SIPE_ACTIVITY_DND;
2832 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_BUSY)) {
2833 availability = 6500;
2834 if (!activity_token || !(*activity_token)) {
2835 activity = SIPE_ACTIVITY_BUSY;
2837 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_AVAILABLE)) {
2838 availability = 3500;
2839 activity = SIPE_ACTIVITY_ONLINE;
2840 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_UNKNOWN)) {
2841 availability = 0;
2842 } else {
2843 // Offline or invisible
2844 availability = 18500;
2845 activity = SIPE_ACTIVITY_OFFLINE;
2848 if (activity_token) {
2849 *activity_token = g_strdup(sipe_activity_map[activity].token);
2851 return availability;
2854 static void process_incoming_notify_rlmi(struct sipe_core_private *sipe_private,
2855 const gchar *data, unsigned len)
2857 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
2858 const char *uri;
2859 sipe_xml *xn_categories;
2860 const sipe_xml *xn_category;
2861 const char *status = NULL;
2862 gboolean do_update_status = FALSE;
2863 gboolean has_note_cleaned = FALSE;
2864 gboolean has_free_busy_cleaned = FALSE;
2866 xn_categories = sipe_xml_parse(data, len);
2867 uri = sipe_xml_attribute(xn_categories, "uri"); /* with 'sip:' prefix */
2869 for (xn_category = sipe_xml_child(xn_categories, "category");
2870 xn_category ;
2871 xn_category = sipe_xml_twin(xn_category) )
2873 const sipe_xml *xn_node;
2874 const char *tmp;
2875 const char *attrVar = sipe_xml_attribute(xn_category, "name");
2876 time_t publish_time = (tmp = sipe_xml_attribute(xn_category, "publishTime")) ?
2877 sipe_utils_str_to_time(tmp) : 0;
2879 /* contactCard */
2880 if (sipe_strequal(attrVar, "contactCard"))
2882 const sipe_xml *card = sipe_xml_child(xn_category, "contactCard");
2884 if (card) {
2885 const sipe_xml *node;
2886 /* identity - Display Name and email */
2887 node = sipe_xml_child(card, "identity");
2888 if (node) {
2889 char* display_name = sipe_xml_data(
2890 sipe_xml_child(node, "name/displayName"));
2891 char* email = sipe_xml_data(
2892 sipe_xml_child(node, "email"));
2894 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_DISPLAY_NAME, display_name);
2895 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_EMAIL, email);
2897 g_free(display_name);
2898 g_free(email);
2900 /* company */
2901 node = sipe_xml_child(card, "company");
2902 if (node) {
2903 char* company = sipe_xml_data(node);
2904 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_COMPANY, company);
2905 g_free(company);
2907 /* department */
2908 node = sipe_xml_child(card, "department");
2909 if (node) {
2910 char* department = sipe_xml_data(node);
2911 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_DEPARTMENT, department);
2912 g_free(department);
2914 /* title */
2915 node = sipe_xml_child(card, "title");
2916 if (node) {
2917 char* title = sipe_xml_data(node);
2918 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_JOB_TITLE, title);
2919 g_free(title);
2921 /* office */
2922 node = sipe_xml_child(card, "office");
2923 if (node) {
2924 char* office = sipe_xml_data(node);
2925 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_OFFICE, office);
2926 g_free(office);
2928 /* site (url) */
2929 node = sipe_xml_child(card, "url");
2930 if (node) {
2931 char* site = sipe_xml_data(node);
2932 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_SITE, site);
2933 g_free(site);
2935 /* phone */
2936 for (node = sipe_xml_child(card, "phone");
2937 node;
2938 node = sipe_xml_twin(node))
2940 const char *phone_type = sipe_xml_attribute(node, "type");
2941 char* phone = sipe_xml_data(sipe_xml_child(node, "uri"));
2942 char* phone_display_string = sipe_xml_data(sipe_xml_child(node, "displayString"));
2944 sipe_update_user_phone(sipe_private, uri, phone_type, phone, phone_display_string);
2946 g_free(phone);
2947 g_free(phone_display_string);
2949 /* address */
2950 for (node = sipe_xml_child(card, "address");
2951 node;
2952 node = sipe_xml_twin(node))
2954 if (sipe_strequal(sipe_xml_attribute(node, "type"), "work")) {
2955 char* street = sipe_xml_data(sipe_xml_child(node, "street"));
2956 char* city = sipe_xml_data(sipe_xml_child(node, "city"));
2957 char* state = sipe_xml_data(sipe_xml_child(node, "state"));
2958 char* zipcode = sipe_xml_data(sipe_xml_child(node, "zipcode"));
2959 char* country_code = sipe_xml_data(sipe_xml_child(node, "countryCode"));
2961 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_STREET, street);
2962 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_CITY, city);
2963 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_STATE, state);
2964 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_ZIPCODE, zipcode);
2965 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_COUNTRY, country_code);
2967 g_free(street);
2968 g_free(city);
2969 g_free(state);
2970 g_free(zipcode);
2971 g_free(country_code);
2973 break;
2978 /* note */
2979 else if (sipe_strequal(attrVar, "note"))
2981 if (uri) {
2982 struct sipe_buddy *sbuddy = g_hash_table_lookup(sipe_private->buddies, uri);
2984 if (!has_note_cleaned) {
2985 has_note_cleaned = TRUE;
2987 g_free(sbuddy->note);
2988 sbuddy->note = NULL;
2989 sbuddy->is_oof_note = FALSE;
2990 sbuddy->note_since = publish_time;
2992 do_update_status = TRUE;
2994 if (sbuddy && (publish_time >= sbuddy->note_since)) {
2995 /* clean up in case no 'note' element is supplied
2996 * which indicate note removal in client
2998 g_free(sbuddy->note);
2999 sbuddy->note = NULL;
3000 sbuddy->is_oof_note = FALSE;
3001 sbuddy->note_since = publish_time;
3003 xn_node = sipe_xml_child(xn_category, "note/body");
3004 if (xn_node) {
3005 char *tmp;
3006 sbuddy->note = g_markup_escape_text((tmp = sipe_xml_data(xn_node)), -1);
3007 g_free(tmp);
3008 sbuddy->is_oof_note = sipe_strequal(sipe_xml_attribute(xn_node, "type"), "OOF");
3009 sbuddy->note_since = publish_time;
3011 SIPE_DEBUG_INFO("process_incoming_notify_rlmi: uri(%s), note(%s)",
3012 uri, sbuddy->note ? sbuddy->note : "");
3014 /* to trigger UI refresh in case no status info is supplied in this update */
3015 do_update_status = TRUE;
3019 /* state */
3020 else if(sipe_strequal(attrVar, "state"))
3022 char *tmp;
3023 int availability;
3024 const sipe_xml *xn_availability;
3025 const sipe_xml *xn_activity;
3026 const sipe_xml *xn_meeting_subject;
3027 const sipe_xml *xn_meeting_location;
3028 struct sipe_buddy *sbuddy = uri ? g_hash_table_lookup(sipe_private->buddies, uri) : NULL;
3030 xn_node = sipe_xml_child(xn_category, "state");
3031 if (!xn_node) continue;
3032 xn_availability = sipe_xml_child(xn_node, "availability");
3033 if (!xn_availability) continue;
3034 xn_activity = sipe_xml_child(xn_node, "activity");
3035 xn_meeting_subject = sipe_xml_child(xn_node, "meetingSubject");
3036 xn_meeting_location = sipe_xml_child(xn_node, "meetingLocation");
3038 tmp = sipe_xml_data(xn_availability);
3039 availability = atoi(tmp);
3040 g_free(tmp);
3042 /* activity, meeting_subject, meeting_location */
3043 if (sbuddy) {
3044 char *tmp = NULL;
3046 /* activity */
3047 g_free(sbuddy->activity);
3048 sbuddy->activity = NULL;
3049 if (xn_activity) {
3050 const char *token = sipe_xml_attribute(xn_activity, "token");
3051 const sipe_xml *xn_custom = sipe_xml_child(xn_activity, "custom");
3053 /* from token */
3054 if (!is_empty(token)) {
3055 sbuddy->activity = g_strdup(sipe_get_activity_desc_by_token(token));
3057 /* from custom element */
3058 if (xn_custom) {
3059 char *custom = sipe_xml_data(xn_custom);
3061 if (!is_empty(custom)) {
3062 sbuddy->activity = custom;
3063 custom = NULL;
3065 g_free(custom);
3068 /* meeting_subject */
3069 g_free(sbuddy->meeting_subject);
3070 sbuddy->meeting_subject = NULL;
3071 if (xn_meeting_subject) {
3072 char *meeting_subject = sipe_xml_data(xn_meeting_subject);
3074 if (!is_empty(meeting_subject)) {
3075 sbuddy->meeting_subject = meeting_subject;
3076 meeting_subject = NULL;
3078 g_free(meeting_subject);
3080 /* meeting_location */
3081 g_free(sbuddy->meeting_location);
3082 sbuddy->meeting_location = NULL;
3083 if (xn_meeting_location) {
3084 char *meeting_location = sipe_xml_data(xn_meeting_location);
3086 if (!is_empty(meeting_location)) {
3087 sbuddy->meeting_location = meeting_location;
3088 meeting_location = NULL;
3090 g_free(meeting_location);
3093 status = sipe_get_status_by_availability(availability, &tmp);
3094 if (sbuddy->activity && tmp) {
3095 char *tmp2 = sbuddy->activity;
3097 sbuddy->activity = g_strdup_printf("%s, %s", sbuddy->activity, tmp);
3098 g_free(tmp);
3099 g_free(tmp2);
3100 } else if (tmp) {
3101 sbuddy->activity = tmp;
3105 do_update_status = TRUE;
3107 /* calendarData */
3108 else if(sipe_strequal(attrVar, "calendarData"))
3110 struct sipe_buddy *sbuddy = uri ? g_hash_table_lookup(sipe_private->buddies, uri) : NULL;
3111 const sipe_xml *xn_free_busy = sipe_xml_child(xn_category, "calendarData/freeBusy");
3112 const sipe_xml *xn_working_hours = sipe_xml_child(xn_category, "calendarData/WorkingHours");
3114 if (sbuddy && xn_free_busy) {
3115 if (!has_free_busy_cleaned) {
3116 has_free_busy_cleaned = TRUE;
3118 g_free(sbuddy->cal_start_time);
3119 sbuddy->cal_start_time = NULL;
3121 g_free(sbuddy->cal_free_busy_base64);
3122 sbuddy->cal_free_busy_base64 = NULL;
3124 g_free(sbuddy->cal_free_busy);
3125 sbuddy->cal_free_busy = NULL;
3127 sbuddy->cal_free_busy_published = publish_time;
3130 if (publish_time >= sbuddy->cal_free_busy_published) {
3131 g_free(sbuddy->cal_start_time);
3132 sbuddy->cal_start_time = g_strdup(sipe_xml_attribute(xn_free_busy, "startTime"));
3134 sbuddy->cal_granularity = sipe_strcase_equal(sipe_xml_attribute(xn_free_busy, "granularity"), "PT15M") ?
3135 15 : 0;
3137 g_free(sbuddy->cal_free_busy_base64);
3138 sbuddy->cal_free_busy_base64 = sipe_xml_data(xn_free_busy);
3140 g_free(sbuddy->cal_free_busy);
3141 sbuddy->cal_free_busy = NULL;
3143 sbuddy->cal_free_busy_published = publish_time;
3145 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);
3149 if (sbuddy && xn_working_hours) {
3150 sipe_cal_parse_working_hours(xn_working_hours, sbuddy);
3155 if (do_update_status) {
3156 if (!status) { /* no status category in this update, using contact's current status */
3157 PurpleBuddy *pbuddy = purple_find_buddy((PurpleAccount *)sip->account, uri);
3158 const PurplePresence *presence = purple_buddy_get_presence(pbuddy);
3159 const PurpleStatus *pstatus = purple_presence_get_active_status(presence);
3160 status = purple_status_get_id(pstatus);
3163 SIPE_DEBUG_INFO("process_incoming_notify_rlmi: %s", status);
3164 sipe_core_buddy_got_status(SIPE_CORE_PUBLIC, uri, status);
3167 sipe_xml_free(xn_categories);
3170 static void sipe_subscribe_poolfqdn_resource_uri(const char *host,
3171 GSList *server,
3172 struct sipe_core_private *sipe_private)
3174 struct presence_batched_routed *payload = g_malloc(sizeof(struct presence_batched_routed));
3175 SIPE_DEBUG_INFO("process_incoming_notify_rlmi_resub: pool(%s)", host);
3176 payload->host = g_strdup(host);
3177 payload->buddies = server;
3178 sipe_subscribe_presence_batched_routed(sipe_private,
3179 payload);
3180 sipe_subscribe_presence_batched_routed_free(payload);
3183 static void process_incoming_notify_rlmi_resub(struct sipe_core_private *sipe_private,
3184 const gchar *data, unsigned len)
3186 sipe_xml *xn_list;
3187 const sipe_xml *xn_resource;
3188 GHashTable *servers = g_hash_table_new_full(g_str_hash, g_str_equal,
3189 g_free, NULL);
3190 GSList *server;
3191 gchar *host;
3193 xn_list = sipe_xml_parse(data, len);
3195 for (xn_resource = sipe_xml_child(xn_list, "resource");
3196 xn_resource;
3197 xn_resource = sipe_xml_twin(xn_resource) )
3199 const char *uri, *state;
3200 const sipe_xml *xn_instance;
3202 xn_instance = sipe_xml_child(xn_resource, "instance");
3203 if (!xn_instance) continue;
3205 uri = sipe_xml_attribute(xn_resource, "uri");
3206 state = sipe_xml_attribute(xn_instance, "state");
3207 SIPE_DEBUG_INFO("process_incoming_notify_rlmi_resub: uri(%s),state(%s)", uri, state);
3209 if (strstr(state, "resubscribe")) {
3210 const char *poolFqdn = sipe_xml_attribute(xn_instance, "poolFqdn");
3212 if (poolFqdn) { //[MS-PRES] Section 3.4.5.1.3 Processing Details
3213 gchar *user = g_strdup(uri);
3214 host = g_strdup(poolFqdn);
3215 server = g_hash_table_lookup(servers, host);
3216 server = g_slist_append(server, user);
3217 g_hash_table_insert(servers, host, server);
3218 } else {
3219 sipe_subscribe_presence_single(sipe_private,
3220 (void *) uri);
3225 /* Send out any deferred poolFqdn subscriptions */
3226 g_hash_table_foreach(servers, (GHFunc) sipe_subscribe_poolfqdn_resource_uri, sipe_private);
3227 g_hash_table_destroy(servers);
3229 sipe_xml_free(xn_list);
3232 static void process_incoming_notify_pidf(struct sipe_core_private *sipe_private,
3233 const gchar *data, unsigned len)
3235 gchar *uri;
3236 gchar *getbasic;
3237 gchar *activity = NULL;
3238 sipe_xml *pidf;
3239 const sipe_xml *basicstatus = NULL, *tuple, *status;
3240 gboolean isonline = FALSE;
3241 const sipe_xml *display_name_node;
3243 pidf = sipe_xml_parse(data, len);
3244 if (!pidf) {
3245 SIPE_DEBUG_INFO("process_incoming_notify_pidf: no parseable pidf:%s", data);
3246 return;
3249 if ((tuple = sipe_xml_child(pidf, "tuple")))
3251 if ((status = sipe_xml_child(tuple, "status"))) {
3252 basicstatus = sipe_xml_child(status, "basic");
3256 if (!basicstatus) {
3257 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_notify_pidf: no basic found");
3258 sipe_xml_free(pidf);
3259 return;
3262 getbasic = sipe_xml_data(basicstatus);
3263 if (!getbasic) {
3264 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_notify_pidf: no basic data found");
3265 sipe_xml_free(pidf);
3266 return;
3269 SIPE_DEBUG_INFO("process_incoming_notify_pidf: basic-status(%s)", getbasic);
3270 if (strstr(getbasic, "open")) {
3271 isonline = TRUE;
3273 g_free(getbasic);
3275 uri = sip_uri(sipe_xml_attribute(pidf, "entity")); /* with 'sip:' prefix */ /* AOL comes without the prefix */
3277 display_name_node = sipe_xml_child(pidf, "display-name");
3278 if (display_name_node) {
3279 char * display_name = sipe_xml_data(display_name_node);
3281 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_DISPLAY_NAME, display_name);
3282 g_free(display_name);
3285 if ((tuple = sipe_xml_child(pidf, "tuple"))) {
3286 if ((status = sipe_xml_child(tuple, "status"))) {
3287 if ((basicstatus = sipe_xml_child(status, "activities"))) {
3288 if ((basicstatus = sipe_xml_child(basicstatus, "activity"))) {
3289 activity = sipe_xml_data(basicstatus);
3290 SIPE_DEBUG_INFO("process_incoming_notify_pidf: activity(%s)", activity);
3296 if (isonline) {
3297 const gchar * status_id = NULL;
3298 if (activity) {
3299 if (sipe_strequal(activity, sipe_activity_map[SIPE_ACTIVITY_BUSY].token)) {
3300 status_id = SIPE_STATUS_ID_BUSY;
3301 } else if (sipe_strequal(activity, sipe_activity_map[SIPE_ACTIVITY_AWAY].token)) {
3302 status_id = SIPE_STATUS_ID_AWAY;
3306 if (!status_id) {
3307 status_id = SIPE_STATUS_ID_AVAILABLE;
3310 SIPE_DEBUG_INFO("process_incoming_notify_pidf: status_id(%s)", status_id);
3311 sipe_core_buddy_got_status(SIPE_CORE_PUBLIC, uri, status_id);
3312 } else {
3313 sipe_core_buddy_got_status(SIPE_CORE_PUBLIC, uri, SIPE_STATUS_ID_OFFLINE);
3316 g_free(activity);
3317 g_free(uri);
3318 sipe_xml_free(pidf);
3321 /** 2005 */
3322 static void
3323 sipe_user_info_has_updated(struct sipe_core_private *sipe_private,
3324 const sipe_xml *xn_userinfo)
3326 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
3327 const sipe_xml *xn_states;
3329 g_free(sip->user_states);
3330 sip->user_states = NULL;
3331 if ((xn_states = sipe_xml_child(xn_userinfo, "states")) != NULL) {
3332 gchar *orig = sip->user_states = sipe_xml_stringify(xn_states);
3334 /* this is a hack-around to remove added newline after inner element,
3335 * state in this case, where it shouldn't be.
3336 * After several use of sipe_xml_stringify, amount of added newlines
3337 * grows significantly.
3339 if (orig) {
3340 gchar c, *stripped = orig;
3341 while ((c = *orig++)) {
3342 if ((c != '\n') /* && (c != '\r') */) {
3343 *stripped++ = c;
3346 *stripped = '\0';
3350 /* Publish initial state if not yet.
3351 * Assuming this happens on initial responce to self subscription
3352 * so we've already updated our UserInfo.
3354 if (!sip->initial_state_published) {
3355 send_presence_soap(sipe_private, FALSE);
3356 /* dalayed run */
3357 sipe_schedule_seconds(sipe_private,
3358 "<+update-calendar>",
3359 NULL,
3360 UPDATE_CALENDAR_DELAY,
3361 (sipe_schedule_action) sipe_core_update_calendar,
3362 NULL);
3366 static void process_incoming_notify_msrtc(struct sipe_core_private *sipe_private,
3367 const gchar *data, unsigned len)
3369 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
3370 char *activity = NULL;
3371 const char *epid;
3372 const char *status_id = NULL;
3373 const char *name;
3374 char *uri;
3375 char *self_uri = sip_uri_self(sipe_private);
3376 int avl;
3377 int act;
3378 const char *device_name = NULL;
3379 const char *cal_start_time = NULL;
3380 const char *cal_granularity = NULL;
3381 char *cal_free_busy_base64 = NULL;
3382 struct sipe_buddy *sbuddy;
3383 const sipe_xml *node;
3384 sipe_xml *xn_presentity;
3385 const sipe_xml *xn_availability;
3386 const sipe_xml *xn_activity;
3387 const sipe_xml *xn_display_name;
3388 const sipe_xml *xn_email;
3389 const sipe_xml *xn_phone_number;
3390 const sipe_xml *xn_userinfo;
3391 const sipe_xml *xn_note;
3392 const sipe_xml *xn_oof;
3393 const sipe_xml *xn_state;
3394 const sipe_xml *xn_contact;
3395 char *note;
3396 int user_avail;
3397 const char *user_avail_nil;
3398 int res_avail;
3399 time_t user_avail_since = 0;
3400 time_t activity_since = 0;
3402 /* fix for Reuters environment on Linux */
3403 if (data && strstr(data, "encoding=\"utf-16\"")) {
3404 char *tmp_data;
3405 tmp_data = replace(data, "encoding=\"utf-16\"", "encoding=\"utf-8\"");
3406 xn_presentity = sipe_xml_parse(tmp_data, strlen(tmp_data));
3407 g_free(tmp_data);
3408 } else {
3409 xn_presentity = sipe_xml_parse(data, len);
3412 xn_availability = sipe_xml_child(xn_presentity, "availability");
3413 xn_activity = sipe_xml_child(xn_presentity, "activity");
3414 xn_display_name = sipe_xml_child(xn_presentity, "displayName");
3415 xn_email = sipe_xml_child(xn_presentity, "email");
3416 xn_phone_number = sipe_xml_child(xn_presentity, "phoneNumber");
3417 xn_userinfo = sipe_xml_child(xn_presentity, "userInfo");
3418 xn_oof = xn_userinfo ? sipe_xml_child(xn_userinfo, "oof") : NULL;
3419 xn_state = xn_userinfo ? sipe_xml_child(xn_userinfo, "states/state"): NULL;
3420 user_avail = xn_state ? sipe_xml_int_attribute(xn_state, "avail", 0) : 0;
3421 user_avail_since = xn_state ? sipe_utils_str_to_time(sipe_xml_attribute(xn_state, "since")) : 0;
3422 user_avail_nil = xn_state ? sipe_xml_attribute(xn_state, "nil") : NULL;
3423 xn_contact = xn_userinfo ? sipe_xml_child(xn_userinfo, "contact") : NULL;
3424 xn_note = xn_userinfo ? sipe_xml_child(xn_userinfo, "note") : NULL;
3425 note = xn_note ? sipe_xml_data(xn_note) : NULL;
3427 if (sipe_strequal(user_avail_nil, "true")) { /* null-ed */
3428 user_avail = 0;
3429 user_avail_since = 0;
3432 name = sipe_xml_attribute(xn_presentity, "uri"); /* without 'sip:' prefix */
3433 uri = sip_uri_from_name(name);
3434 avl = sipe_xml_int_attribute(xn_availability, "aggregate", 0);
3435 epid = sipe_xml_attribute(xn_availability, "epid");
3436 act = sipe_xml_int_attribute(xn_activity, "aggregate", 0);
3438 status_id = sipe_get_status_by_act_avail_2005(act, avl, &activity);
3439 res_avail = sipe_get_availability_by_status(status_id, NULL);
3440 if (user_avail > res_avail) {
3441 res_avail = user_avail;
3442 status_id = sipe_get_status_by_availability(user_avail, NULL);
3445 if (xn_display_name) {
3446 char *display_name = g_strdup(sipe_xml_attribute(xn_display_name, "displayName"));
3447 char *email = xn_email ? g_strdup(sipe_xml_attribute(xn_email, "email")) : NULL;
3448 char *phone_label = xn_phone_number ? g_strdup(sipe_xml_attribute(xn_phone_number, "label")) : NULL;
3449 char *phone_number = xn_phone_number ? g_strdup(sipe_xml_attribute(xn_phone_number, "number")) : NULL;
3450 char *tel_uri = sip_to_tel_uri(phone_number);
3452 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_DISPLAY_NAME, display_name);
3453 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_EMAIL, email);
3454 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_WORK_PHONE, tel_uri);
3455 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_WORK_PHONE_DISPLAY, !is_empty(phone_label) ? phone_label : phone_number);
3457 g_free(tel_uri);
3458 g_free(phone_label);
3459 g_free(phone_number);
3460 g_free(email);
3461 g_free(display_name);
3464 if (xn_contact) {
3465 /* tel */
3466 for (node = sipe_xml_child(xn_contact, "tel"); node; node = sipe_xml_twin(node))
3468 /* Ex.: <tel type="work">tel:+3222220000</tel> */
3469 const char *phone_type = sipe_xml_attribute(node, "type");
3470 char* phone = sipe_xml_data(node);
3472 sipe_update_user_phone(sipe_private, uri, phone_type, phone, NULL);
3474 g_free(phone);
3478 /* devicePresence */
3479 for (node = sipe_xml_child(xn_presentity, "devices/devicePresence"); node; node = sipe_xml_twin(node)) {
3480 const sipe_xml *xn_device_name;
3481 const sipe_xml *xn_calendar_info;
3482 const sipe_xml *xn_state;
3483 char *state;
3485 /* deviceName */
3486 if (sipe_strequal(sipe_xml_attribute(node, "epid"), epid)) {
3487 xn_device_name = sipe_xml_child(node, "deviceName");
3488 device_name = xn_device_name ? sipe_xml_attribute(xn_device_name, "name") : NULL;
3491 /* calendarInfo */
3492 xn_calendar_info = sipe_xml_child(node, "calendarInfo");
3493 if (xn_calendar_info) {
3494 const char *cal_start_time_tmp = sipe_xml_attribute(xn_calendar_info, "startTime");
3496 if (cal_start_time) {
3497 time_t cal_start_time_t = sipe_utils_str_to_time(cal_start_time);
3498 time_t cal_start_time_t_tmp = sipe_utils_str_to_time(cal_start_time_tmp);
3500 if (cal_start_time_t_tmp > cal_start_time_t) {
3501 cal_start_time = cal_start_time_tmp;
3502 cal_granularity = sipe_xml_attribute(xn_calendar_info, "granularity");
3503 g_free(cal_free_busy_base64);
3504 cal_free_busy_base64 = sipe_xml_data(xn_calendar_info);
3506 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);
3508 } else {
3509 cal_start_time = cal_start_time_tmp;
3510 cal_granularity = sipe_xml_attribute(xn_calendar_info, "granularity");
3511 g_free(cal_free_busy_base64);
3512 cal_free_busy_base64 = sipe_xml_data(xn_calendar_info);
3514 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);
3518 /* state */
3519 xn_state = sipe_xml_child(node, "states/state");
3520 if (xn_state) {
3521 int dev_avail = sipe_xml_int_attribute(xn_state, "avail", 0);
3522 time_t dev_avail_since = sipe_utils_str_to_time(sipe_xml_attribute(xn_state, "since"));
3524 state = sipe_xml_data(xn_state);
3525 if (dev_avail_since > user_avail_since &&
3526 dev_avail >= res_avail)
3528 res_avail = dev_avail;
3529 if (!is_empty(state))
3531 if (sipe_strequal(state, sipe_activity_map[SIPE_ACTIVITY_ON_PHONE].token)) {
3532 g_free(activity);
3533 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_ON_PHONE));
3534 } else if (sipe_strequal(state, "presenting")) {
3535 g_free(activity);
3536 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_IN_CONF));
3537 } else {
3538 activity = state;
3539 state = NULL;
3541 activity_since = dev_avail_since;
3543 status_id = sipe_get_status_by_availability(res_avail, &activity);
3545 g_free(state);
3549 /* oof */
3550 if (xn_oof && res_avail >= 15000) { /* 12000 in 2007 */
3551 g_free(activity);
3552 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_OOF));
3553 activity_since = 0;
3556 sbuddy = g_hash_table_lookup(sipe_private->buddies, uri);
3557 if (sbuddy)
3559 g_free(sbuddy->activity);
3560 sbuddy->activity = activity;
3561 activity = NULL;
3563 sbuddy->activity_since = activity_since;
3565 sbuddy->user_avail = user_avail;
3566 sbuddy->user_avail_since = user_avail_since;
3568 g_free(sbuddy->note);
3569 sbuddy->note = NULL;
3570 if (!is_empty(note)) { sbuddy->note = g_markup_escape_text(note, -1); }
3572 sbuddy->is_oof_note = (xn_oof != NULL);
3574 g_free(sbuddy->device_name);
3575 sbuddy->device_name = NULL;
3576 if (!is_empty(device_name)) { sbuddy->device_name = g_strdup(device_name); }
3578 if (!is_empty(cal_free_busy_base64)) {
3579 g_free(sbuddy->cal_start_time);
3580 sbuddy->cal_start_time = g_strdup(cal_start_time);
3582 sbuddy->cal_granularity = sipe_strcase_equal(cal_granularity, "PT15M") ? 15 : 0;
3584 g_free(sbuddy->cal_free_busy_base64);
3585 sbuddy->cal_free_busy_base64 = cal_free_busy_base64;
3586 cal_free_busy_base64 = NULL;
3588 g_free(sbuddy->cal_free_busy);
3589 sbuddy->cal_free_busy = NULL;
3592 sbuddy->last_non_cal_status_id = status_id;
3593 g_free(sbuddy->last_non_cal_activity);
3594 sbuddy->last_non_cal_activity = g_strdup(sbuddy->activity);
3596 if (sipe_strcase_equal(sbuddy->name, self_uri)) {
3597 if (!sipe_strequal(sbuddy->note, sip->note)) /* not same */
3599 sip->is_oof_note = sbuddy->is_oof_note;
3601 g_free(sip->note);
3602 sip->note = g_strdup(sbuddy->note);
3604 sip->note_since = time(NULL);
3607 g_free(sip->status);
3608 sip->status = g_strdup(sbuddy->last_non_cal_status_id);
3611 g_free(cal_free_busy_base64);
3612 g_free(activity);
3614 SIPE_DEBUG_INFO("process_incoming_notify_msrtc: status(%s)", status_id);
3615 sipe_core_buddy_got_status(SIPE_CORE_PUBLIC, uri, status_id);
3617 if (!SIPE_CORE_PRIVATE_FLAG_IS(OCS2007) && sipe_strcase_equal(self_uri, uri)) {
3618 sipe_user_info_has_updated(sipe_private, xn_userinfo);
3621 g_free(note);
3622 sipe_xml_free(xn_presentity);
3623 g_free(uri);
3624 g_free(self_uri);
3627 static void sipe_presence_mime_cb(gpointer user_data, /* sipe_core_private */
3628 const GSList *fields,
3629 const gchar *body,
3630 gsize length)
3632 const gchar *type = sipe_utils_nameval_find(fields, "Content-Type");
3634 if (strstr(type,"application/rlmi+xml")) {
3635 process_incoming_notify_rlmi_resub(user_data, body, length);
3636 } else if (strstr(type, "text/xml+msrtc.pidf")) {
3637 process_incoming_notify_msrtc(user_data, body, length);
3638 } else {
3639 process_incoming_notify_rlmi(user_data, body, length);
3643 static void sipe_process_presence(struct sipe_core_private *sipe_private,
3644 struct sipmsg *msg)
3646 const char *ctype = sipmsg_find_header(msg, "Content-Type");
3648 SIPE_DEBUG_INFO("sipe_process_presence: Content-Type: %s", ctype ? ctype : "");
3650 if (ctype &&
3651 (strstr(ctype, "application/rlmi+xml") ||
3652 strstr(ctype, "application/msrtc-event-categories+xml")))
3654 if (strstr(ctype, "multipart"))
3656 sipe_mime_parts_foreach(ctype, msg->body, sipe_presence_mime_cb, sipe_private);
3658 else if(strstr(ctype, "application/msrtc-event-categories+xml") )
3660 process_incoming_notify_rlmi(sipe_private, msg->body, msg->bodylen);
3662 else if(strstr(ctype, "application/rlmi+xml"))
3664 process_incoming_notify_rlmi_resub(sipe_private, msg->body, msg->bodylen);
3667 else if(ctype && strstr(ctype, "text/xml+msrtc.pidf"))
3669 process_incoming_notify_msrtc(sipe_private, msg->body, msg->bodylen);
3671 else
3673 process_incoming_notify_pidf(sipe_private, msg->body, msg->bodylen);
3677 static void sipe_presence_timeout_mime_cb(gpointer user_data,
3678 SIPE_UNUSED_PARAMETER const GSList *fields,
3679 const gchar *body,
3680 gsize length)
3682 GSList **buddies = user_data;
3683 sipe_xml *xml = sipe_xml_parse(body, length);
3685 if (xml && !sipe_strequal(sipe_xml_name(xml), "list")) {
3686 const gchar *uri = sipe_xml_attribute(xml, "uri");
3687 const sipe_xml *xn_category;
3690 * automaton: presence is never expected to change
3692 * see: http://msdn.microsoft.com/en-us/library/ee354295(office.13).aspx
3694 for (xn_category = sipe_xml_child(xml, "category");
3695 xn_category;
3696 xn_category = sipe_xml_twin(xn_category)) {
3697 if (sipe_strequal(sipe_xml_attribute(xn_category, "name"),
3698 "contactCard")) {
3699 const sipe_xml *node = sipe_xml_child(xn_category, "contactCard/automaton");
3700 if (node) {
3701 char *boolean = sipe_xml_data(node);
3702 if (sipe_strequal(boolean, "true")) {
3703 SIPE_DEBUG_INFO("sipe_process_presence_timeout: %s is an automaton: - not subscribing to presence updates",
3704 uri);
3705 uri = NULL;
3707 g_free(boolean);
3709 break;
3713 if (uri) {
3714 *buddies = g_slist_append(*buddies, sip_uri(uri));
3718 sipe_xml_free(xml);
3721 static void sipe_process_presence_timeout(struct sipe_core_private *sipe_private,
3722 struct sipmsg *msg, gchar *who,
3723 int timeout)
3725 const char *ctype = sipmsg_find_header(msg, "Content-Type");
3726 gchar *action_name = sipe_utils_presence_key(who);
3728 SIPE_DEBUG_INFO("sipe_process_presence_timeout: Content-Type: %s", ctype ? ctype : "");
3730 if (ctype &&
3731 strstr(ctype, "multipart") &&
3732 (strstr(ctype, "application/rlmi+xml") ||
3733 strstr(ctype, "application/msrtc-event-categories+xml"))) {
3734 GSList *buddies = NULL;
3736 sipe_mime_parts_foreach(ctype, msg->body, sipe_presence_timeout_mime_cb, &buddies);
3738 if (buddies) {
3739 struct presence_batched_routed *payload = g_malloc(sizeof(struct presence_batched_routed));
3740 payload->host = g_strdup(who);
3741 payload->buddies = buddies;
3742 sipe_schedule_seconds(sipe_private,
3743 action_name,
3744 payload,
3745 timeout,
3746 sipe_subscribe_presence_batched_routed,
3747 sipe_subscribe_presence_batched_routed_free);
3748 SIPE_DEBUG_INFO("Resubscription multiple contacts with batched support & route(%s) in %d", who, timeout);
3751 } else {
3752 sipe_schedule_seconds(sipe_private,
3753 action_name,
3754 g_strdup(who),
3755 timeout,
3756 sipe_subscribe_presence_single,
3757 g_free);
3758 SIPE_DEBUG_INFO("Resubscription single contact with batched support(%s) in %d", who, timeout);
3760 g_free(action_name);
3764 * Dispatcher for all incoming subscription information
3765 * whether it comes from NOTIFY, BENOTIFY requests or
3766 * piggy-backed to subscription's OK responce.
3768 * @param request whether initiated from BE/NOTIFY request or OK-response message.
3769 * @param benotify whether initiated from NOTIFY or BENOTIFY request.
3771 void process_incoming_notify(struct sipe_core_private *sipe_private,
3772 struct sipmsg *msg,
3773 gboolean request, gboolean benotify)
3775 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
3776 const gchar *content_type = sipmsg_find_header(msg, "Content-Type");
3777 const gchar *event = sipmsg_find_header(msg, "Event");
3778 const gchar *subscription_state = sipmsg_find_header(msg, "subscription-state");
3780 SIPE_DEBUG_INFO("process_incoming_notify: subscription_state: %s", subscription_state ? subscription_state : "");
3782 /* implicit subscriptions */
3783 if (content_type && g_str_has_prefix(content_type, "application/ms-imdn+xml")) {
3784 sipe_process_imdn(sipe_private, msg);
3787 if (event) {
3788 /* for one off subscriptions (send with Expire: 0) */
3789 if (sipe_strcase_equal(event, "vnd-microsoft-provisioning-v2"))
3791 sipe_process_provisioning_v2(sipe_private, msg);
3793 else if (sipe_strcase_equal(event, "vnd-microsoft-provisioning"))
3795 sipe_process_provisioning(sipe_private, msg);
3797 else if (sipe_strcase_equal(event, "presence"))
3799 sipe_process_presence(sipe_private, msg);
3801 else if (sipe_strcase_equal(event, "registration-notify"))
3803 sipe_process_registration_notify(sipe_private, msg);
3806 if (!subscription_state || strstr(subscription_state, "active"))
3808 if (sipe_strcase_equal(event, "vnd-microsoft-roaming-contacts"))
3810 sipe_process_roaming_contacts(sipe_private, msg);
3812 else if (sipe_strcase_equal(event, "vnd-microsoft-roaming-self"))
3814 sipe_process_roaming_self(sipe_private, msg);
3816 else if (sipe_strcase_equal(event, "vnd-microsoft-roaming-ACL"))
3818 sipe_process_roaming_acl(sipe_private, msg);
3820 else if (sipe_strcase_equal(event, "presence.wpending"))
3822 sipe_process_presence_wpending(sipe_private, msg);
3824 else if (sipe_strcase_equal(event, "conference"))
3826 sipe_process_conference(sipe_private, msg);
3831 /* The server sends status 'terminated' */
3832 if (subscription_state && strstr(subscription_state, "terminated") ) {
3833 gchar *who = parse_from(sipmsg_find_header(msg, request ? "From" : "To"));
3834 gchar *key = sipe_utils_subscription_key(event, who);
3836 SIPE_DEBUG_INFO("process_incoming_notify: server says that subscription to %s was terminated.", who);
3837 g_free(who);
3839 sipe_subscriptions_remove(sipe_private, key);
3840 g_free(key);
3843 if (!request && event) {
3844 const gchar *expires_header = sipmsg_find_header(msg, "Expires");
3845 int timeout = expires_header ? strtol(expires_header, NULL, 10) : 0;
3846 SIPE_DEBUG_INFO("process_incoming_notify: subscription expires:%d", timeout);
3848 if (timeout) {
3849 /* 2 min ahead of expiration */
3850 timeout = (timeout - 120) > 120 ? (timeout - 120) : timeout;
3852 if (sipe_strcase_equal(event, "presence.wpending") &&
3853 g_slist_find_custom(sip->allow_events, "presence.wpending", (GCompareFunc)g_ascii_strcasecmp))
3855 gchar *action_name = g_strdup_printf("<%s>", "presence.wpending");
3856 sipe_schedule_seconds(sipe_private,
3857 action_name,
3858 NULL,
3859 timeout,
3860 sipe_subscribe_presence_wpending,
3861 NULL);
3862 g_free(action_name);
3864 else if (sipe_strcase_equal(event, "presence") &&
3865 g_slist_find_custom(sip->allow_events, "presence", (GCompareFunc)g_ascii_strcasecmp))
3867 gchar *who = parse_from(sipmsg_find_header(msg, "To"));
3868 gchar *action_name = sipe_utils_presence_key(who);
3870 if (sip->batched_support) {
3871 sipe_process_presence_timeout(sipe_private, msg, who, timeout);
3873 else {
3874 sipe_schedule_seconds(sipe_private,
3875 action_name,
3876 g_strdup(who),
3877 timeout,
3878 sipe_subscribe_presence_single,
3879 g_free);
3880 SIPE_DEBUG_INFO("Resubscription single contact (%s) in %d", who, timeout);
3882 g_free(action_name);
3883 g_free(who);
3888 /* The client responses on received a NOTIFY message */
3889 if (request && !benotify)
3891 sip_transport_response(sipe_private, msg, 200, "OK", NULL);
3896 * Whether user manually changed status or
3897 * it was changed automatically due to user
3898 * became inactive/active again
3900 static gboolean
3901 sipe_is_user_state(struct sipe_core_private *sipe_private)
3903 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
3904 gboolean res;
3905 time_t now = time(NULL);
3907 SIPE_DEBUG_INFO("sipe_is_user_state: sip->idle_switch : %s", asctime(localtime(&(sip->idle_switch))));
3908 SIPE_DEBUG_INFO("sipe_is_user_state: now : %s", asctime(localtime(&now)));
3910 res = ((now - SIPE_IDLE_SET_DELAY * 2) >= sip->idle_switch);
3912 SIPE_DEBUG_INFO("sipe_is_user_state: res = %s", res ? "USER" : "MACHINE");
3913 return res;
3917 * OCS2005 presence XML messages
3919 * Calendar publication entry
3921 * @param legacy_dn (%s) Ex.: /o=EXCHANGE/ou=BTUK02/cn=Recipients/cn=AHHBTT
3922 * @param fb_start_time_str (%s) Ex.: 2009-12-06T17:15:00Z
3923 * @param free_busy_base64 (%s) Ex.: AAAAAAAAAAAAAAAAA......
3925 #define SIPE_SOAP_SET_PRESENCE_CALENDAR \
3926 "<calendarInfo xmlns=\"http://schemas.microsoft.com/2002/09/sip/presence\" mailboxId=\"%s\" startTime=\"%s\" granularity=\"PT15M\">%s</calendarInfo>"
3929 * Note publication entry
3931 * @param note (%s) Ex.: Working from home
3933 #define SIPE_SOAP_SET_PRESENCE_NOTE_XML "<note>%s</note>"
3936 * Note's OOF publication entry
3938 #define SIPE_SOAP_SET_PRESENCE_OOF_XML "<oof></oof>"
3941 * States publication entry for User State
3943 * @param avail (%d) Availability 2007-style. Ex.: 9500
3944 * @param since_time_str (%s) Ex.: 2010-01-13T10:30:05Z
3945 * @param device_id (%s) epid. Ex.: 4c77e6ec72
3946 * @param activity_token (%s) Ex.: do-not-disturb
3948 #define SIPE_SOAP_SET_PRESENCE_STATES \
3949 "<states>"\
3950 "<state avail=\"%d\" since=\"%s\" validWith=\"any-device\" deviceId=\"%s\" set=\"manual\" xsi:type=\"userState\">%s</state>"\
3951 "</states>"
3954 * Presentity publication entry.
3956 * @param uri (%s) SIP URI without 'sip:' prefix. Ex.: fox@atlanta.local
3957 * @param aggr_availability (%d) Ex.: 300
3958 * @param aggr_activity (%d) Ex.: 600
3959 * @param host_name (%s) Uppercased. Ex.: ATLANTA
3960 * @param note_xml_str (%s) XML string as SIPE_SOAP_SET_PRESENCE_NOTE_XML
3961 * @param oof_xml_str (%s) XML string as SIPE_SOAP_SET_PRESENCE_OOF_XML
3962 * @param states_xml_str (%s) XML string as SIPE_SOAP_SET_PRESENCE_STATES
3963 * @param calendar_info_xml_str (%s) XML string as SIPE_SOAP_SET_PRESENCE_CALENDAR
3964 * @param device_id (%s) epid. Ex.: 4c77e6ec72
3965 * @param since_time_str (%s) Ex.: 2010-01-13T10:30:05Z
3966 * @param since_time_str (%s) Ex.: 2010-01-13T10:30:05Z
3967 * @param user_input (%s) active, idle
3969 #define SIPE_SOAP_SET_PRESENCE \
3970 "<s:Envelope" \
3971 " xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\"" \
3972 " xmlns:m=\"http://schemas.microsoft.com/winrtc/2002/11/sip\"" \
3973 ">" \
3974 "<s:Body>" \
3975 "<m:setPresence>" \
3976 "<m:presentity xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" m:uri=\"sip:%s\">"\
3977 "<m:availability m:aggregate=\"%d\"/>"\
3978 "<m:activity m:aggregate=\"%d\"/>"\
3979 "<deviceName xmlns=\"http://schemas.microsoft.com/2002/09/sip/presence\" name=\"%s\"/>"\
3980 "<rtc:devicedata xmlns:rtc=\"http://schemas.microsoft.com/winrtc/2002/11/sip\" namespace=\"rtcService\">"\
3981 "<![CDATA[<caps><renders_gif/><renders_isf/></caps>]]></rtc:devicedata>"\
3982 "<userInfo xmlns=\"http://schemas.microsoft.com/2002/09/sip/presence\">"\
3983 "%s%s" \
3984 "%s" \
3985 "</userInfo>"\
3986 "%s" \
3987 "<device xmlns=\"http://schemas.microsoft.com/2002/09/sip/presence\" deviceId=\"%s\" since=\"%s\" >"\
3988 "<userInput since=\"%s\" >%s</userInput>"\
3989 "</device>"\
3990 "</m:presentity>" \
3991 "</m:setPresence>"\
3992 "</s:Body>" \
3993 "</s:Envelope>"
3995 static void
3996 send_presence_soap0(struct sipe_core_private *sipe_private,
3997 gboolean do_publish_calendar,
3998 gboolean do_reset_status)
4000 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
4001 struct sipe_calendar* cal = sip->cal;
4002 int availability = 0;
4003 int activity = 0;
4004 gchar *body;
4005 gchar *tmp;
4006 gchar *tmp2 = NULL;
4007 gchar *res_note = NULL;
4008 gchar *res_oof = NULL;
4009 const gchar *note_pub = NULL;
4010 gchar *states = NULL;
4011 gchar *calendar_data = NULL;
4012 gchar *epid = get_epid(sipe_private);
4013 gchar *from = sip_uri_self(sipe_private);
4014 time_t now = time(NULL);
4015 gchar *since_time_str = sipe_utils_time_to_str(now);
4016 const gchar *oof_note = cal ? sipe_ews_get_oof_note(cal) : NULL;
4017 const char *user_input;
4018 gboolean pub_oof = cal && oof_note && (!sip->note || cal->updated > sip->note_since);
4020 if (oof_note && sip->note) {
4021 SIPE_DEBUG_INFO("cal->oof_start : %s", asctime(localtime(&(cal->oof_start))));
4022 SIPE_DEBUG_INFO("sip->note_since : %s", asctime(localtime(&(sip->note_since))));
4025 SIPE_DEBUG_INFO("sip->note : %s", sip->note ? sip->note : "");
4027 if (!sip->initial_state_published ||
4028 do_reset_status)
4030 g_free(sip->status);
4031 sip->status = g_strdup(SIPE_STATUS_ID_AVAILABLE);
4034 sipe_get_act_avail_by_status_2005(sip->status, &activity, &availability);
4036 /* Note */
4037 if (pub_oof) {
4038 note_pub = oof_note;
4039 res_oof = SIPE_SOAP_SET_PRESENCE_OOF_XML;
4040 cal->published = TRUE;
4041 } else if (sip->note) {
4042 if (sip->is_oof_note && !oof_note) { /* stale OOF note, as it's not present in cal already */
4043 g_free(sip->note);
4044 sip->note = NULL;
4045 sip->is_oof_note = FALSE;
4046 sip->note_since = 0;
4047 } else {
4048 note_pub = sip->note;
4049 res_oof = sip->is_oof_note ? SIPE_SOAP_SET_PRESENCE_OOF_XML : "";
4053 if (note_pub)
4055 /* to protocol internal plain text format */
4056 tmp = sipe_backend_markup_strip_html(note_pub);
4057 res_note = g_markup_printf_escaped(SIPE_SOAP_SET_PRESENCE_NOTE_XML, tmp);
4058 g_free(tmp);
4061 /* User State */
4062 if (!do_reset_status) {
4063 if (sipe_is_user_state(sipe_private) && !do_publish_calendar && sip->initial_state_published)
4065 gchar *activity_token = NULL;
4066 int avail_2007 = sipe_get_availability_by_status(sip->status, &activity_token);
4068 states = g_strdup_printf(SIPE_SOAP_SET_PRESENCE_STATES,
4069 avail_2007,
4070 since_time_str,
4071 epid,
4072 activity_token);
4073 g_free(activity_token);
4075 else /* preserve existing publication */
4077 if (sip->user_states) {
4078 states = g_strdup(sip->user_states);
4081 } else {
4082 /* do nothing - then User state will be erased */
4084 sip->initial_state_published = TRUE;
4086 /* CalendarInfo */
4087 if (cal && (!is_empty(cal->legacy_dn) || !is_empty(cal->email)) && cal->fb_start && !is_empty(cal->free_busy))
4089 char *fb_start_str = sipe_utils_time_to_str(cal->fb_start);
4090 char *free_busy_base64 = sipe_cal_get_freebusy_base64(cal->free_busy);
4091 calendar_data = g_strdup_printf(SIPE_SOAP_SET_PRESENCE_CALENDAR,
4092 !is_empty(cal->legacy_dn) ? cal->legacy_dn : cal->email,
4093 fb_start_str,
4094 free_busy_base64);
4095 g_free(fb_start_str);
4096 g_free(free_busy_base64);
4099 user_input = (sipe_is_user_state(sipe_private) ||
4100 sipe_strequal(sip->status, SIPE_STATUS_ID_AVAILABLE)) ?
4101 "active" : "idle";
4103 /* forming resulting XML */
4104 body = g_strdup_printf(SIPE_SOAP_SET_PRESENCE,
4105 sipe_private->username,
4106 availability,
4107 activity,
4108 (tmp = g_ascii_strup(g_get_host_name(), -1)),
4109 res_note ? res_note : "",
4110 res_oof ? res_oof : "",
4111 states ? states : "",
4112 calendar_data ? calendar_data : "",
4113 epid,
4114 since_time_str,
4115 since_time_str,
4116 user_input);
4117 g_free(tmp);
4118 g_free(tmp2);
4119 g_free(res_note);
4120 g_free(states);
4121 g_free(calendar_data);
4122 g_free(since_time_str);
4123 g_free(epid);
4125 sip_soap_raw_request_cb(sipe_private, from, body, NULL, NULL);
4127 g_free(body);
4130 void
4131 send_presence_soap(struct sipe_core_private *sipe_private,
4132 gboolean do_publish_calendar)
4134 return send_presence_soap0(sipe_private, do_publish_calendar, FALSE);
4138 static gboolean
4139 process_send_presence_category_publish_response(struct sipe_core_private *sipe_private,
4140 struct sipmsg *msg,
4141 struct transaction *trans)
4143 const gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
4145 if (msg->response == 409 && g_str_has_prefix(contenttype, "application/msrtc-fault+xml")) {
4146 sipe_xml *xml;
4147 const sipe_xml *node;
4148 gchar *fault_code;
4149 GHashTable *faults;
4150 int index_our;
4151 gboolean has_device_publication = FALSE;
4153 xml = sipe_xml_parse(msg->body, msg->bodylen);
4155 /* test if version mismatch fault */
4156 fault_code = sipe_xml_data(sipe_xml_child(xml, "Faultcode"));
4157 if (!sipe_strequal(fault_code, "Client.BadCall.WrongDelta")) {
4158 SIPE_DEBUG_INFO("process_send_presence_category_publish_response: unsupported fault code:%s returning.", fault_code);
4159 g_free(fault_code);
4160 sipe_xml_free(xml);
4161 return TRUE;
4163 g_free(fault_code);
4165 /* accumulating information about faulty versions */
4166 faults = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
4167 for (node = sipe_xml_child(xml, "details/operation");
4168 node;
4169 node = sipe_xml_twin(node))
4171 const gchar *index = sipe_xml_attribute(node, "index");
4172 const gchar *curVersion = sipe_xml_attribute(node, "curVersion");
4174 g_hash_table_insert(faults, g_strdup(index), g_strdup(curVersion));
4175 SIPE_DEBUG_INFO("fault added: index:%s curVersion:%s", index, curVersion);
4177 sipe_xml_free(xml);
4179 /* here we are parsing our own request to figure out what publication
4180 * referenced here only by index went wrong
4182 xml = sipe_xml_parse(trans->msg->body, trans->msg->bodylen);
4184 /* publication */
4185 for (node = sipe_xml_child(xml, "publications/publication"),
4186 index_our = 1; /* starts with 1 - our first publication */
4187 node;
4188 node = sipe_xml_twin(node), index_our++)
4190 gchar *idx = g_strdup_printf("%d", index_our);
4191 const gchar *curVersion = g_hash_table_lookup(faults, idx);
4192 const gchar *categoryName = sipe_xml_attribute(node, "categoryName");
4193 g_free(idx);
4195 if (sipe_strequal("device", categoryName)) {
4196 has_device_publication = TRUE;
4199 if (curVersion) { /* fault exist on this index */
4200 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
4201 const gchar *container = sipe_xml_attribute(node, "container");
4202 const gchar *instance = sipe_xml_attribute(node, "instance");
4203 /* key is <category><instance><container> */
4204 gchar *key = g_strdup_printf("<%s><%s><%s>", categoryName, instance, container);
4205 GHashTable *category = g_hash_table_lookup(sip->our_publications, categoryName);
4207 if (category) {
4208 struct sipe_publication *publication =
4209 g_hash_table_lookup(category, key);
4211 SIPE_DEBUG_INFO("key is %s", key);
4213 if (publication) {
4214 SIPE_DEBUG_INFO("Updating %s with version %s. Was %d before.",
4215 key, curVersion, publication->version);
4216 /* updating publication's version to the correct one */
4217 publication->version = atoi(curVersion);
4219 } else {
4220 /* We somehow lost this category from our publications... */
4221 struct sipe_publication *publication = g_new0(struct sipe_publication, 1);
4222 publication->category = g_strdup(categoryName);
4223 publication->instance = atoi(instance);
4224 publication->container = atoi(container);
4225 publication->version = atoi(curVersion);
4226 category = g_hash_table_new_full(g_str_hash, g_str_equal,
4227 g_free, (GDestroyNotify)free_publication);
4228 g_hash_table_insert(category, g_strdup(key), publication);
4229 g_hash_table_insert(sip->our_publications, g_strdup(categoryName), category);
4230 SIPE_DEBUG_INFO("added lost category '%s' key '%s'", categoryName, key);
4232 g_free(key);
4235 sipe_xml_free(xml);
4236 g_hash_table_destroy(faults);
4238 /* rebublishing with right versions */
4239 if (has_device_publication) {
4240 send_publish_category_initial(sipe_private);
4241 } else {
4242 send_presence_status(sipe_private, NULL);
4245 return TRUE;
4249 * Publishes 'device' category.
4250 * @param instance (%u) Ex.: 1938468728
4251 * @param version (%u) Ex.: 1
4252 * @param endpointId (%s) Ex.: C707E38E-1E10-5413-94D9-ECAC260A0269
4253 * @param uri (%s) Self URI. Ex.: sip:alice7@boston.local
4254 * @param timezone (%s) Ex.: 00:00:00+01:00
4255 * @param machineName (%s) Ex.: BOSTON-OCS07
4257 #define SIPE_PUB_XML_DEVICE \
4258 "<publication categoryName=\"device\" instance=\"%u\" container=\"2\" version=\"%u\" expireType=\"endpoint\">"\
4259 "<device xmlns=\"http://schemas.microsoft.com/2006/09/sip/device\" endpointId=\"%s\">"\
4260 "<capabilities preferred=\"false\" uri=\"%s\">"\
4261 "<text capture=\"true\" render=\"true\" publish=\"false\"/>"\
4262 "<gifInk capture=\"false\" render=\"true\" publish=\"false\"/>"\
4263 "<isfInk capture=\"false\" render=\"true\" publish=\"false\"/>"\
4264 "</capabilities>"\
4265 "<timezone>%s</timezone>"\
4266 "<machineName>%s</machineName>"\
4267 "</device>"\
4268 "</publication>"
4271 * Returns 'device' XML part for publication.
4272 * Must be g_free'd after use.
4274 static gchar *
4275 sipe_publish_get_category_device(struct sipe_core_private *sipe_private)
4277 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
4278 gchar *uri;
4279 gchar *doc;
4280 gchar *uuid = get_uuid(sipe_private);
4281 guint device_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_DEVICE);
4282 /* key is <category><instance><container> */
4283 gchar *key = g_strdup_printf("<%s><%u><%u>", "device", device_instance, 2);
4284 struct sipe_publication *publication =
4285 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "device"), key);
4287 g_free(key);
4289 uri = sip_uri_self(sipe_private);
4290 doc = g_strdup_printf(SIPE_PUB_XML_DEVICE,
4291 device_instance,
4292 publication ? publication->version : 0,
4293 uuid,
4294 uri,
4295 "00:00:00+01:00", /* @TODO make timezone real*/
4296 g_get_host_name()
4299 g_free(uri);
4300 g_free(uuid);
4302 return doc;
4306 * Publishes 'machineState' category.
4307 * @param instance (%u) Ex.: 926460663
4308 * @param version (%u) Ex.: 22
4309 * @param availability (%d) Ex.: 3500
4310 * @param instance (%u) Ex.: 926460663
4311 * @param version (%u) Ex.: 22
4312 * @param availability (%d) Ex.: 3500
4314 #define SIPE_PUB_XML_STATE_MACHINE \
4315 "<publication categoryName=\"state\" instance=\"%u\" container=\"2\" version=\"%u\" expireType=\"endpoint\">"\
4316 "<state xmlns=\"http://schemas.microsoft.com/2006/09/sip/state\" manual=\"false\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"machineState\">"\
4317 "<availability>%d</availability>"\
4318 "<endpointLocation/>"\
4319 "</state>"\
4320 "</publication>"\
4321 "<publication categoryName=\"state\" instance=\"%u\" container=\"3\" version=\"%u\" expireType=\"endpoint\">"\
4322 "<state xmlns=\"http://schemas.microsoft.com/2006/09/sip/state\" manual=\"false\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"machineState\">"\
4323 "<availability>%d</availability>"\
4324 "<endpointLocation/>"\
4325 "</state>"\
4326 "</publication>"
4329 * Publishes 'userState' category.
4330 * @param instance (%u) User. Ex.: 536870912
4331 * @param version (%u) User Container 2. Ex.: 22
4332 * @param availability (%d) User Container 2. Ex.: 15500
4333 * @param instance (%u) User. Ex.: 536870912
4334 * @param version (%u) User Container 3.Ex.: 22
4335 * @param availability (%d) User Container 3. Ex.: 15500
4337 #define SIPE_PUB_XML_STATE_USER \
4338 "<publication categoryName=\"state\" instance=\"%u\" container=\"2\" version=\"%u\" expireType=\"static\">"\
4339 "<state xmlns=\"http://schemas.microsoft.com/2006/09/sip/state\" manual=\"true\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"userState\">"\
4340 "<availability>%d</availability>"\
4341 "<endpointLocation/>"\
4342 "</state>"\
4343 "</publication>"\
4344 "<publication categoryName=\"state\" instance=\"%u\" container=\"3\" version=\"%u\" expireType=\"static\">"\
4345 "<state xmlns=\"http://schemas.microsoft.com/2006/09/sip/state\" manual=\"true\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"userState\">"\
4346 "<availability>%d</availability>"\
4347 "<endpointLocation/>"\
4348 "</state>"\
4349 "</publication>"
4352 * A service method - use
4353 * - send_publish_get_category_state_machine and
4354 * - send_publish_get_category_state_user instead.
4355 * Must be g_free'd after use.
4357 static gchar *
4358 sipe_publish_get_category_state(struct sipe_core_private *sipe_private,
4359 gboolean is_user_state)
4361 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
4362 int availability = sipe_get_availability_by_status(sip->status, NULL);
4363 guint instance = is_user_state ? sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_USER) :
4364 sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_MACHINE);
4365 /* key is <category><instance><container> */
4366 gchar *key_2 = g_strdup_printf("<%s><%u><%u>", "state", instance, 2);
4367 gchar *key_3 = g_strdup_printf("<%s><%u><%u>", "state", instance, 3);
4368 struct sipe_publication *publication_2 =
4369 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_2);
4370 struct sipe_publication *publication_3 =
4371 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_3);
4373 g_free(key_2);
4374 g_free(key_3);
4376 if (publication_2 && (publication_2->availability == availability))
4378 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_state: state has NOT changed. Exiting.");
4379 return NULL; /* nothing to update */
4382 return g_strdup_printf( is_user_state ? SIPE_PUB_XML_STATE_USER : SIPE_PUB_XML_STATE_MACHINE,
4383 instance,
4384 publication_2 ? publication_2->version : 0,
4385 availability,
4386 instance,
4387 publication_3 ? publication_3->version : 0,
4388 availability);
4392 * An availability XML entry for SIPE_PUB_XML_STATE_CALENDAR
4393 * @param availability (%d) Ex.: 6500
4395 #define SIPE_PUB_XML_STATE_CALENDAR_AVAIL \
4396 "<availability>%d</availability>"
4398 * An activity XML entry for SIPE_PUB_XML_STATE_CALENDAR
4399 * @param token (%s) Ex.: in-a-meeting
4400 * @param minAvailability_attr (%s) Ex.: minAvailability="6500"
4401 * @param maxAvailability_attr (%s) Ex.: maxAvailability="8999" or none
4403 #define SIPE_PUB_XML_STATE_CALENDAR_ACTIVITY \
4404 "<activity token=\"%s\" %s %s></activity>"
4406 * Publishes 'calendarState' category.
4407 * @param instance (%u) Ex.: 1339299275
4408 * @param version (%u) Ex.: 1
4409 * @param uri (%s) Ex.: john@contoso.com
4410 * @param start_time_str (%s) Ex.: 2008-01-11T19:00:00Z
4411 * @param availability (%s) XML string as SIPE_PUB_XML_STATE_CALENDAR_AVAIL
4412 * @param activity (%s) XML string as SIPE_PUB_XML_STATE_CALENDAR_ACTIVITY
4413 * @param meeting_subject (%s) Ex.: Customer Meeting
4414 * @param meeting_location (%s) Ex.: Conf Room 100
4416 * @param instance (%u) Ex.: 1339299275
4417 * @param version (%u) Ex.: 1
4418 * @param uri (%s) Ex.: john@contoso.com
4419 * @param start_time_str (%s) Ex.: 2008-01-11T19:00:00Z
4420 * @param availability (%s) XML string as SIPE_PUB_XML_STATE_CALENDAR_AVAIL
4421 * @param activity (%s) XML string as SIPE_PUB_XML_STATE_CALENDAR_ACTIVITY
4422 * @param meeting_subject (%s) Ex.: Customer Meeting
4423 * @param meeting_location (%s) Ex.: Conf Room 100
4425 #define SIPE_PUB_XML_STATE_CALENDAR \
4426 "<publication categoryName=\"state\" instance=\"%u\" container=\"2\" version=\"%u\" expireType=\"endpoint\">"\
4427 "<state xmlns=\"http://schemas.microsoft.com/2006/09/sip/state\" manual=\"false\" uri=\"%s\" startTime=\"%s\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"calendarState\">"\
4428 "%s"\
4429 "%s"\
4430 "<endpointLocation/>"\
4431 "<meetingSubject>%s</meetingSubject>"\
4432 "<meetingLocation>%s</meetingLocation>"\
4433 "</state>"\
4434 "</publication>"\
4435 "<publication categoryName=\"state\" instance=\"%u\" container=\"3\" version=\"%u\" expireType=\"endpoint\">"\
4436 "<state xmlns=\"http://schemas.microsoft.com/2006/09/sip/state\" manual=\"false\" uri=\"%s\" startTime=\"%s\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"calendarState\">"\
4437 "%s"\
4438 "%s"\
4439 "<endpointLocation/>"\
4440 "<meetingSubject>%s</meetingSubject>"\
4441 "<meetingLocation>%s</meetingLocation>"\
4442 "</state>"\
4443 "</publication>"
4445 * Publishes to clear 'calendarState' category
4446 * @param instance (%u) Ex.: 1251210982
4447 * @param version (%u) Ex.: 1
4449 #define SIPE_PUB_XML_STATE_CALENDAR_CLEAR \
4450 "<publication categoryName=\"state\" instance=\"%u\" container=\"2\" version=\"%u\" expireType=\"endpoint\" expires=\"0\"/>"\
4451 "<publication categoryName=\"state\" instance=\"%u\" container=\"3\" version=\"%u\" expireType=\"endpoint\" expires=\"0\"/>"
4454 * Publishes to clear any category
4455 * @param category_name (%s) Ex.: state
4456 * @param instance (%u) Ex.: 536870912
4457 * @param container (%u) Ex.: 3
4458 * @param version (%u) Ex.: 1
4459 * @param expireType (%s) Ex.: static
4461 #define SIPE_PUB_XML_PUBLICATION_CLEAR \
4462 "<publication categoryName=\"%s\" instance=\"%u\" container=\"%u\" version=\"%u\" expireType=\"%s\" expires=\"0\"/>"
4465 * Publishes 'note' category.
4466 * @param instance (%u) Ex.: 2135971629; 0 for personal
4467 * @param container (%u) Ex.: 200
4468 * @param version (%u) Ex.: 2
4469 * @param type (%s) Ex.: personal or OOF
4470 * @param startTime_attr (%s) Ex.: startTime="2008-01-11T19:00:00Z"
4471 * @param endTime_attr (%s) Ex.: endTime="2008-01-15T19:00:00Z"
4472 * @param body (%s) Ex.: In the office
4474 #define SIPE_PUB_XML_NOTE \
4475 "<publication categoryName=\"note\" instance=\"%u\" container=\"%u\" version=\"%d\" expireType=\"static\">"\
4476 "<note xmlns=\"http://schemas.microsoft.com/2006/09/sip/note\">"\
4477 "<body type=\"%s\" uri=\"\"%s%s>%s</body>"\
4478 "</note>"\
4479 "</publication>"
4482 * Only Busy and OOF calendar event are published.
4483 * Different instances are used for that.
4485 * Must be g_free'd after use.
4487 static gchar *
4488 sipe_publish_get_category_state_calendar(struct sipe_core_private *sipe_private,
4489 struct sipe_cal_event *event,
4490 const char *uri,
4491 int cal_satus)
4493 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
4494 gchar *start_time_str;
4495 int availability = 0;
4496 gchar *res;
4497 gchar *tmp = NULL;
4498 guint instance = (cal_satus == SIPE_CAL_OOF) ?
4499 sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_CALENDAR_OOF) :
4500 sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_CALENDAR);
4502 /* key is <category><instance><container> */
4503 gchar *key_2 = g_strdup_printf("<%s><%u><%u>", "state", instance, 2);
4504 gchar *key_3 = g_strdup_printf("<%s><%u><%u>", "state", instance, 3);
4505 struct sipe_publication *publication_2 =
4506 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_2);
4507 struct sipe_publication *publication_3 =
4508 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_3);
4510 g_free(key_2);
4511 g_free(key_3);
4513 if (!publication_3 && !event) { /* was nothing, have nothing, exiting */
4514 SIPE_DEBUG_INFO("sipe_publish_get_category_state_calendar: "
4515 "Exiting as no publication and no event for cal_satus:%d", cal_satus);
4516 return NULL;
4519 if (event &&
4520 publication_3 &&
4521 (publication_3->availability == availability) &&
4522 sipe_strequal(publication_3->cal_event_hash, (tmp = sipe_cal_event_hash(event))))
4524 g_free(tmp);
4525 SIPE_DEBUG_INFO("sipe_publish_get_category_state_calendar: "
4526 "cal state has NOT changed for cal_satus:%d. Exiting.", cal_satus);
4527 return NULL; /* nothing to update */
4529 g_free(tmp);
4531 if (event &&
4532 (event->cal_status == SIPE_CAL_BUSY ||
4533 event->cal_status == SIPE_CAL_OOF))
4535 gchar *availability_xml_str = NULL;
4536 gchar *activity_xml_str = NULL;
4537 gchar *escaped_subject = event->subject ? g_markup_escape_text(event->subject, -1) : NULL;
4538 gchar *escaped_location = event->location ? g_markup_escape_text(event->location, -1) : NULL;
4540 if (event->cal_status == SIPE_CAL_BUSY) {
4541 availability_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_AVAIL, 6500);
4544 if (event->cal_status == SIPE_CAL_BUSY && event->is_meeting) {
4545 activity_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_ACTIVITY,
4546 sipe_activity_map[SIPE_ACTIVITY_IN_MEETING].token,
4547 "minAvailability=\"6500\"",
4548 "maxAvailability=\"8999\"");
4549 } else if (event->cal_status == SIPE_CAL_OOF) {
4550 activity_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_ACTIVITY,
4551 sipe_activity_map[SIPE_ACTIVITY_OOF].token,
4552 "minAvailability=\"12000\"",
4553 "");
4555 start_time_str = sipe_utils_time_to_str(event->start_time);
4557 res = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR,
4558 instance,
4559 publication_2 ? publication_2->version : 0,
4560 uri,
4561 start_time_str,
4562 availability_xml_str ? availability_xml_str : "",
4563 activity_xml_str ? activity_xml_str : "",
4564 escaped_subject ? escaped_subject : "",
4565 escaped_location ? escaped_location : "",
4567 instance,
4568 publication_3 ? publication_3->version : 0,
4569 uri,
4570 start_time_str,
4571 availability_xml_str ? availability_xml_str : "",
4572 activity_xml_str ? activity_xml_str : "",
4573 escaped_subject ? escaped_subject : "",
4574 escaped_location ? escaped_location : ""
4576 g_free(escaped_location);
4577 g_free(escaped_subject);
4578 g_free(start_time_str);
4579 g_free(availability_xml_str);
4580 g_free(activity_xml_str);
4583 else /* including !event, SIPE_CAL_FREE, SIPE_CAL_TENTATIVE */
4585 res = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_CLEAR,
4586 instance,
4587 publication_2 ? publication_2->version : 0,
4589 instance,
4590 publication_3 ? publication_3->version : 0
4594 return res;
4598 * Returns 'machineState' XML part for publication.
4599 * Must be g_free'd after use.
4601 static gchar *
4602 sipe_publish_get_category_state_machine(struct sipe_core_private *sipe_private)
4604 return sipe_publish_get_category_state(sipe_private, FALSE);
4608 * Returns 'userState' XML part for publication.
4609 * Must be g_free'd after use.
4611 static gchar *
4612 sipe_publish_get_category_state_user(struct sipe_core_private *sipe_private)
4614 return sipe_publish_get_category_state(sipe_private, TRUE);
4619 * Returns 'note' XML part for publication.
4620 * Must be g_free'd after use.
4622 * Protocol format for Note is plain text.
4624 * @param note a note in Sipe internal HTML format
4625 * @param note_type either personal or OOF
4627 static gchar *
4628 sipe_publish_get_category_note(struct sipe_core_private *sipe_private,
4629 const char *note, /* html */
4630 const char *note_type,
4631 time_t note_start,
4632 time_t note_end)
4634 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
4635 guint instance = sipe_strequal("OOF", note_type) ? sipe_get_pub_instance(sipe_private, SIPE_PUB_NOTE_OOF) : 0;
4636 /* key is <category><instance><container> */
4637 gchar *key_note_200 = g_strdup_printf("<%s><%u><%u>", "note", instance, 200);
4638 gchar *key_note_300 = g_strdup_printf("<%s><%u><%u>", "note", instance, 300);
4639 gchar *key_note_400 = g_strdup_printf("<%s><%u><%u>", "note", instance, 400);
4641 struct sipe_publication *publication_note_200 =
4642 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_200);
4643 struct sipe_publication *publication_note_300 =
4644 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_300);
4645 struct sipe_publication *publication_note_400 =
4646 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_400);
4648 char *tmp = note ? sipe_backend_markup_strip_html(note) : NULL;
4649 char *n1 = tmp ? g_markup_escape_text(tmp, -1) : NULL;
4650 const char *n2 = publication_note_200 ? publication_note_200->note : NULL;
4651 char *res, *tmp1, *tmp2, *tmp3;
4652 char *start_time_attr;
4653 char *end_time_attr;
4655 g_free(tmp);
4656 tmp = NULL;
4657 g_free(key_note_200);
4658 g_free(key_note_300);
4659 g_free(key_note_400);
4661 /* we even need to republish empty note */
4662 if (sipe_strequal(n1, n2))
4664 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_note: note has NOT changed. Exiting.");
4665 g_free(n1);
4666 return NULL; /* nothing to update */
4669 start_time_attr = note_start ? g_strdup_printf(" startTime=\"%s\"", (tmp = sipe_utils_time_to_str(note_start))) : NULL;
4670 g_free(tmp);
4671 tmp = NULL;
4672 end_time_attr = note_end ? g_strdup_printf(" endTime=\"%s\"", (tmp = sipe_utils_time_to_str(note_end))) : NULL;
4673 g_free(tmp);
4675 if (n1) {
4676 tmp1 = g_strdup_printf(SIPE_PUB_XML_NOTE,
4677 instance,
4678 200,
4679 publication_note_200 ? publication_note_200->version : 0,
4680 note_type,
4681 start_time_attr ? start_time_attr : "",
4682 end_time_attr ? end_time_attr : "",
4683 n1);
4685 tmp2 = g_strdup_printf(SIPE_PUB_XML_NOTE,
4686 instance,
4687 300,
4688 publication_note_300 ? publication_note_300->version : 0,
4689 note_type,
4690 start_time_attr ? start_time_attr : "",
4691 end_time_attr ? end_time_attr : "",
4692 n1);
4694 tmp3 = g_strdup_printf(SIPE_PUB_XML_NOTE,
4695 instance,
4696 400,
4697 publication_note_400 ? publication_note_400->version : 0,
4698 note_type,
4699 start_time_attr ? start_time_attr : "",
4700 end_time_attr ? end_time_attr : "",
4701 n1);
4702 } else {
4703 tmp1 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
4704 "note",
4705 instance,
4706 200,
4707 publication_note_200 ? publication_note_200->version : 0,
4708 "static");
4709 tmp2 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
4710 "note",
4711 instance,
4712 300,
4713 publication_note_200 ? publication_note_200->version : 0,
4714 "static");
4715 tmp3 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
4716 "note",
4717 instance,
4718 400,
4719 publication_note_200 ? publication_note_200->version : 0,
4720 "static");
4722 res = g_strconcat(tmp1, tmp2, tmp3, NULL);
4724 g_free(start_time_attr);
4725 g_free(end_time_attr);
4726 g_free(tmp1);
4727 g_free(tmp2);
4728 g_free(tmp3);
4729 g_free(n1);
4731 return res;
4735 * Publishes 'calendarData' category's WorkingHours.
4737 * @param version (%u) Ex.: 1
4738 * @param email (%s) Ex.: alice@cosmo.local
4739 * @param working_hours_xml_str (%s) Ex.: <WorkingHours xmlns=.....
4741 * @param version (%u)
4743 * @param version (%u)
4744 * @param email (%s)
4745 * @param working_hours_xml_str (%s)
4747 * @param version (%u)
4748 * @param email (%s)
4749 * @param working_hours_xml_str (%s)
4751 * @param version (%u)
4752 * @param email (%s)
4753 * @param working_hours_xml_str (%s)
4755 * @param version (%u)
4757 #define SIPE_PUB_XML_WORKING_HOURS \
4758 "<publication categoryName=\"calendarData\" instance=\"0\" container=\"1\" version=\"%d\" expireType=\"static\">"\
4759 "<calendarData xmlns=\"http://schemas.microsoft.com/2006/09/sip/calendarData\" mailboxID=\"%s\">%s"\
4760 "</calendarData>"\
4761 "</publication>"\
4762 "<publication categoryName=\"calendarData\" instance=\"0\" container=\"100\" version=\"%d\" expireType=\"static\">"\
4763 "<calendarData xmlns=\"http://schemas.microsoft.com/2006/09/sip/calendarData\"/>"\
4764 "</publication>"\
4765 "<publication categoryName=\"calendarData\" instance=\"0\" container=\"200\" version=\"%d\" expireType=\"static\">"\
4766 "<calendarData xmlns=\"http://schemas.microsoft.com/2006/09/sip/calendarData\" mailboxID=\"%s\">%s"\
4767 "</calendarData>"\
4768 "</publication>"\
4769 "<publication categoryName=\"calendarData\" instance=\"0\" container=\"300\" version=\"%d\" expireType=\"static\">"\
4770 "<calendarData xmlns=\"http://schemas.microsoft.com/2006/09/sip/calendarData\" mailboxID=\"%s\">%s"\
4771 "</calendarData>"\
4772 "</publication>"\
4773 "<publication categoryName=\"calendarData\" instance=\"0\" container=\"400\" version=\"%d\" expireType=\"static\">"\
4774 "<calendarData xmlns=\"http://schemas.microsoft.com/2006/09/sip/calendarData\" mailboxID=\"%s\">%s"\
4775 "</calendarData>"\
4776 "</publication>"\
4777 "<publication categoryName=\"calendarData\" instance=\"0\" container=\"32000\" version=\"%d\" expireType=\"static\">"\
4778 "<calendarData xmlns=\"http://schemas.microsoft.com/2006/09/sip/calendarData\"/>"\
4779 "</publication>"
4782 * Returns 'calendarData' XML part with WorkingHours for publication.
4783 * Must be g_free'd after use.
4785 static gchar *
4786 sipe_publish_get_category_cal_working_hours(struct sipe_core_private *sipe_private)
4788 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
4789 struct sipe_calendar* cal = sip->cal;
4791 /* key is <category><instance><container> */
4792 gchar *key_cal_1 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 1);
4793 gchar *key_cal_100 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 100);
4794 gchar *key_cal_200 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 200);
4795 gchar *key_cal_300 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 300);
4796 gchar *key_cal_400 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 400);
4797 gchar *key_cal_32000 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 32000);
4799 struct sipe_publication *publication_cal_1 =
4800 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_1);
4801 struct sipe_publication *publication_cal_100 =
4802 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_100);
4803 struct sipe_publication *publication_cal_200 =
4804 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_200);
4805 struct sipe_publication *publication_cal_300 =
4806 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_300);
4807 struct sipe_publication *publication_cal_400 =
4808 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_400);
4809 struct sipe_publication *publication_cal_32000 =
4810 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_32000);
4812 const char *n1 = cal ? cal->working_hours_xml_str : NULL;
4813 const char *n2 = publication_cal_300 ? publication_cal_300->working_hours_xml_str : NULL;
4815 g_free(key_cal_1);
4816 g_free(key_cal_100);
4817 g_free(key_cal_200);
4818 g_free(key_cal_300);
4819 g_free(key_cal_400);
4820 g_free(key_cal_32000);
4822 if (!cal || is_empty(cal->email) || is_empty(cal->working_hours_xml_str)) {
4823 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_cal_working_hours: no data to publish, exiting");
4824 return NULL;
4827 if (sipe_strequal(n1, n2))
4829 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_cal_working_hours: WorkingHours has NOT changed. Exiting.");
4830 return NULL; /* nothing to update */
4833 return g_strdup_printf(SIPE_PUB_XML_WORKING_HOURS,
4834 /* 1 */
4835 publication_cal_1 ? publication_cal_1->version : 0,
4836 cal->email,
4837 cal->working_hours_xml_str,
4838 /* 100 - Public */
4839 publication_cal_100 ? publication_cal_100->version : 0,
4840 /* 200 - Company */
4841 publication_cal_200 ? publication_cal_200->version : 0,
4842 cal->email,
4843 cal->working_hours_xml_str,
4844 /* 300 - Team */
4845 publication_cal_300 ? publication_cal_300->version : 0,
4846 cal->email,
4847 cal->working_hours_xml_str,
4848 /* 400 - Personal */
4849 publication_cal_400 ? publication_cal_400->version : 0,
4850 cal->email,
4851 cal->working_hours_xml_str,
4852 /* 32000 - Blocked */
4853 publication_cal_32000 ? publication_cal_32000->version : 0
4858 * Publishes 'calendarData' category's FreeBusy.
4860 * @param instance (%u) Ex.: 1300372959
4861 * @param version (%u) Ex.: 1
4863 * @param instance (%u) Ex.: 1300372959
4864 * @param version (%u) Ex.: 1
4866 * @param instance (%u) Ex.: 1300372959
4867 * @param version (%u) Ex.: 1
4868 * @param email (%s) Ex.: alice@cosmo.local
4869 * @param fb_start_time_str (%s) Ex.: 2009-12-03T00:00:00Z
4870 * @param free_busy_base64 (%s) Ex.: AAAAAAAAAAAAAAAAAAAAA.....
4872 * @param instance (%u) Ex.: 1300372959
4873 * @param version (%u) Ex.: 1
4874 * @param email (%s) Ex.: alice@cosmo.local
4875 * @param fb_start_time_str (%s) Ex.: 2009-12-03T00:00:00Z
4876 * @param free_busy_base64 (%s) Ex.: AAAAAAAAAAAAAAAAAAAAA.....
4878 * @param instance (%u) Ex.: 1300372959
4879 * @param version (%u) Ex.: 1
4880 * @param email (%s) Ex.: alice@cosmo.local
4881 * @param fb_start_time_str (%s) Ex.: 2009-12-03T00:00:00Z
4882 * @param free_busy_base64 (%s) Ex.: AAAAAAAAAAAAAAAAAAAAA.....
4884 * @param instance (%u) Ex.: 1300372959
4885 * @param version (%u) Ex.: 1
4887 #define SIPE_PUB_XML_FREE_BUSY \
4888 "<publication categoryName=\"calendarData\" instance=\"%u\" container=\"1\" version=\"%d\" expireType=\"endpoint\">"\
4889 "<calendarData xmlns=\"http://schemas.microsoft.com/2006/09/sip/calendarData\"/>"\
4890 "</publication>"\
4891 "<publication categoryName=\"calendarData\" instance=\"%u\" container=\"100\" version=\"%d\" expireType=\"endpoint\">"\
4892 "<calendarData xmlns=\"http://schemas.microsoft.com/2006/09/sip/calendarData\"/>"\
4893 "</publication>"\
4894 "<publication categoryName=\"calendarData\" instance=\"%u\" container=\"200\" version=\"%d\" expireType=\"endpoint\">"\
4895 "<calendarData xmlns=\"http://schemas.microsoft.com/2006/09/sip/calendarData\" mailboxID=\"%s\">"\
4896 "<freeBusy startTime=\"%s\" granularity=\"PT15M\" encodingVersion=\"1\">%s</freeBusy>"\
4897 "</calendarData>"\
4898 "</publication>"\
4899 "<publication categoryName=\"calendarData\" instance=\"%u\" container=\"300\" version=\"%d\" expireType=\"endpoint\">"\
4900 "<calendarData xmlns=\"http://schemas.microsoft.com/2006/09/sip/calendarData\" mailboxID=\"%s\">"\
4901 "<freeBusy startTime=\"%s\" granularity=\"PT15M\" encodingVersion=\"1\">%s</freeBusy>"\
4902 "</calendarData>"\
4903 "</publication>"\
4904 "<publication categoryName=\"calendarData\" instance=\"%u\" container=\"400\" version=\"%d\" expireType=\"endpoint\">"\
4905 "<calendarData xmlns=\"http://schemas.microsoft.com/2006/09/sip/calendarData\" mailboxID=\"%s\">"\
4906 "<freeBusy startTime=\"%s\" granularity=\"PT15M\" encodingVersion=\"1\">%s</freeBusy>"\
4907 "</calendarData>"\
4908 "</publication>"\
4909 "<publication categoryName=\"calendarData\" instance=\"%u\" container=\"32000\" version=\"%d\" expireType=\"endpoint\">"\
4910 "<calendarData xmlns=\"http://schemas.microsoft.com/2006/09/sip/calendarData\"/>"\
4911 "</publication>"
4914 * Returns 'calendarData' XML part with FreeBusy for publication.
4915 * Must be g_free'd after use.
4917 static gchar *
4918 sipe_publish_get_category_cal_free_busy(struct sipe_core_private *sipe_private)
4920 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
4921 struct sipe_calendar* cal = sip->cal;
4922 guint cal_data_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_CALENDAR_DATA);
4923 char *fb_start_str;
4924 char *free_busy_base64;
4925 /* const char *st; */
4926 /* const char *fb; */
4927 char *res;
4929 /* key is <category><instance><container> */
4930 gchar *key_cal_1 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 1);
4931 gchar *key_cal_100 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 100);
4932 gchar *key_cal_200 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 200);
4933 gchar *key_cal_300 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 300);
4934 gchar *key_cal_400 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 400);
4935 gchar *key_cal_32000 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 32000);
4937 struct sipe_publication *publication_cal_1 =
4938 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_1);
4939 struct sipe_publication *publication_cal_100 =
4940 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_100);
4941 struct sipe_publication *publication_cal_200 =
4942 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_200);
4943 struct sipe_publication *publication_cal_300 =
4944 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_300);
4945 struct sipe_publication *publication_cal_400 =
4946 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_400);
4947 struct sipe_publication *publication_cal_32000 =
4948 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_32000);
4950 g_free(key_cal_1);
4951 g_free(key_cal_100);
4952 g_free(key_cal_200);
4953 g_free(key_cal_300);
4954 g_free(key_cal_400);
4955 g_free(key_cal_32000);
4957 if (!cal || is_empty(cal->email) || !cal->fb_start || is_empty(cal->free_busy)) {
4958 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_cal_free_busy: no data to publish, exiting");
4959 return NULL;
4962 fb_start_str = sipe_utils_time_to_str(cal->fb_start);
4963 free_busy_base64 = sipe_cal_get_freebusy_base64(cal->free_busy);
4965 /* we will rebuplish the same data to refresh publication time,
4966 * so if data from multiple sources, most recent will be choosen
4968 // st = publication_cal_300 ? publication_cal_300->fb_start_str : NULL;
4969 // fb = publication_cal_300 ? publication_cal_300->free_busy_base64 : NULL;
4971 //if (sipe_strequal(st, fb_start_str) && sipe_strequal(fb, free_busy_base64))
4973 // SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_cal_free_busy: FreeBusy has NOT changed. Exiting.");
4974 // g_free(fb_start_str);
4975 // g_free(free_busy_base64);
4976 // return NULL; /* nothing to update */
4979 res = g_strdup_printf(SIPE_PUB_XML_FREE_BUSY,
4980 /* 1 */
4981 cal_data_instance,
4982 publication_cal_1 ? publication_cal_1->version : 0,
4983 /* 100 - Public */
4984 cal_data_instance,
4985 publication_cal_100 ? publication_cal_100->version : 0,
4986 /* 200 - Company */
4987 cal_data_instance,
4988 publication_cal_200 ? publication_cal_200->version : 0,
4989 cal->email,
4990 fb_start_str,
4991 free_busy_base64,
4992 /* 300 - Team */
4993 cal_data_instance,
4994 publication_cal_300 ? publication_cal_300->version : 0,
4995 cal->email,
4996 fb_start_str,
4997 free_busy_base64,
4998 /* 400 - Personal */
4999 cal_data_instance,
5000 publication_cal_400 ? publication_cal_400->version : 0,
5001 cal->email,
5002 fb_start_str,
5003 free_busy_base64,
5004 /* 32000 - Blocked */
5005 cal_data_instance,
5006 publication_cal_32000 ? publication_cal_32000->version : 0
5009 g_free(fb_start_str);
5010 g_free(free_busy_base64);
5011 return res;
5015 * Publishes categories.
5016 * @param uri (%s) Self URI. Ex.: sip:alice7@boston.local
5017 * @param publications (%s) XML publications
5019 #define SIPE_SEND_PRESENCE \
5020 "<publish xmlns=\"http://schemas.microsoft.com/2006/09/sip/rich-presence\">"\
5021 "<publications uri=\"%s\">"\
5022 "%s"\
5023 "</publications>"\
5024 "</publish>"
5026 static void send_presence_publish(struct sipe_core_private *sipe_private,
5027 const char *publications)
5029 gchar *uri;
5030 gchar *doc;
5031 gchar *tmp;
5032 gchar *hdr;
5034 uri = sip_uri_self(sipe_private);
5035 doc = g_strdup_printf(SIPE_SEND_PRESENCE,
5036 uri,
5037 publications);
5039 tmp = get_contact(sipe_private);
5040 hdr = g_strdup_printf("Contact: %s\r\n"
5041 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp);
5043 sip_transport_service(sipe_private,
5044 uri,
5045 hdr,
5046 doc,
5047 process_send_presence_category_publish_response);
5049 g_free(tmp);
5050 g_free(hdr);
5051 g_free(uri);
5052 g_free(doc);
5055 static void
5056 send_publish_category_initial(struct sipe_core_private *sipe_private)
5058 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5059 gchar *pub_device = sipe_publish_get_category_device(sipe_private);
5060 gchar *pub_machine;
5061 gchar *publications;
5063 g_free(sip->status);
5064 sip->status = g_strdup(SIPE_STATUS_ID_AVAILABLE); /* our initial state */
5066 pub_machine = sipe_publish_get_category_state_machine(sipe_private);
5067 publications = g_strdup_printf("%s%s",
5068 pub_device,
5069 pub_machine ? pub_machine : "");
5070 g_free(pub_device);
5071 g_free(pub_machine);
5073 send_presence_publish(sipe_private, publications);
5074 g_free(publications);
5077 static void
5078 send_presence_category_publish(struct sipe_core_private *sipe_private)
5080 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5081 gchar *pub_state = sipe_is_user_state(sipe_private) ?
5082 sipe_publish_get_category_state_user(sipe_private) :
5083 sipe_publish_get_category_state_machine(sipe_private);
5084 gchar *pub_note = sipe_publish_get_category_note(sipe_private,
5085 sip->note,
5086 sip->is_oof_note ? "OOF" : "personal",
5089 gchar *publications;
5091 if (!pub_state && !pub_note) {
5092 SIPE_DEBUG_INFO_NOFORMAT("send_presence_category_publish: nothing has changed. Exiting.");
5093 return;
5096 publications = g_strdup_printf("%s%s",
5097 pub_state ? pub_state : "",
5098 pub_note ? pub_note : "");
5100 g_free(pub_state);
5101 g_free(pub_note);
5103 send_presence_publish(sipe_private, publications);
5104 g_free(publications);
5108 * Publishes self status
5109 * based on own calendar information.
5111 * For 2007+
5113 void
5114 publish_calendar_status_self(struct sipe_core_private *sipe_private,
5115 SIPE_UNUSED_PARAMETER void *unused)
5117 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5118 struct sipe_cal_event* event = NULL;
5119 gchar *pub_cal_working_hours = NULL;
5120 gchar *pub_cal_free_busy = NULL;
5121 gchar *pub_calendar = NULL;
5122 gchar *pub_calendar2 = NULL;
5123 gchar *pub_oof_note = NULL;
5124 const gchar *oof_note;
5125 time_t oof_start = 0;
5126 time_t oof_end = 0;
5128 if (!sip->cal) {
5129 SIPE_DEBUG_INFO_NOFORMAT("publish_calendar_status_self() no calendar data.");
5130 return;
5133 SIPE_DEBUG_INFO_NOFORMAT("publish_calendar_status_self() started.");
5134 if (sip->cal->cal_events) {
5135 event = sipe_cal_get_event(sip->cal->cal_events, time(NULL));
5138 if (!event) {
5139 SIPE_DEBUG_INFO_NOFORMAT("publish_calendar_status_self: current event is NULL");
5140 } else {
5141 char *desc = sipe_cal_event_describe(event);
5142 SIPE_DEBUG_INFO("publish_calendar_status_self: current event is:\n%s", desc ? desc : "");
5143 g_free(desc);
5146 /* Logic
5147 if OOF
5148 OOF publish, Busy clean
5149 ilse if Busy
5150 OOF clean, Busy publish
5151 else
5152 OOF clean, Busy clean
5154 if (event && event->cal_status == SIPE_CAL_OOF) {
5155 pub_calendar = sipe_publish_get_category_state_calendar(sipe_private, event, sip->cal->email, SIPE_CAL_OOF);
5156 pub_calendar2 = sipe_publish_get_category_state_calendar(sipe_private, NULL, sip->cal->email, SIPE_CAL_BUSY);
5157 } else if (event && event->cal_status == SIPE_CAL_BUSY) {
5158 pub_calendar = sipe_publish_get_category_state_calendar(sipe_private, NULL, sip->cal->email, SIPE_CAL_OOF);
5159 pub_calendar2 = sipe_publish_get_category_state_calendar(sipe_private, event, sip->cal->email, SIPE_CAL_BUSY);
5160 } else {
5161 pub_calendar = sipe_publish_get_category_state_calendar(sipe_private, NULL, sip->cal->email, SIPE_CAL_OOF);
5162 pub_calendar2 = sipe_publish_get_category_state_calendar(sipe_private, NULL, sip->cal->email, SIPE_CAL_BUSY);
5165 oof_note = sipe_ews_get_oof_note(sip->cal);
5166 if (sipe_strequal("Scheduled", sip->cal->oof_state)) {
5167 oof_start = sip->cal->oof_start;
5168 oof_end = sip->cal->oof_end;
5170 pub_oof_note = sipe_publish_get_category_note(sipe_private, oof_note, "OOF", oof_start, oof_end);
5172 pub_cal_working_hours = sipe_publish_get_category_cal_working_hours(sipe_private);
5173 pub_cal_free_busy = sipe_publish_get_category_cal_free_busy(sipe_private);
5175 if (!pub_cal_working_hours && !pub_cal_free_busy && !pub_calendar && !pub_calendar2 && !pub_oof_note) {
5176 SIPE_DEBUG_INFO_NOFORMAT("publish_calendar_status_self: nothing has changed.");
5177 } else {
5178 gchar *publications = g_strdup_printf("%s%s%s%s%s",
5179 pub_cal_working_hours ? pub_cal_working_hours : "",
5180 pub_cal_free_busy ? pub_cal_free_busy : "",
5181 pub_calendar ? pub_calendar : "",
5182 pub_calendar2 ? pub_calendar2 : "",
5183 pub_oof_note ? pub_oof_note : "");
5185 send_presence_publish(sipe_private, publications);
5186 g_free(publications);
5189 g_free(pub_cal_working_hours);
5190 g_free(pub_cal_free_busy);
5191 g_free(pub_calendar);
5192 g_free(pub_calendar2);
5193 g_free(pub_oof_note);
5195 /* repeat scheduling */
5196 sipe_sched_calendar_status_self_publish(sipe_private, time(NULL));
5199 static void send_presence_status(struct sipe_core_private *sipe_private,
5200 SIPE_UNUSED_PARAMETER void *unused)
5202 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5203 PurpleStatus * status = purple_account_get_active_status(sip->account);
5205 if (!status) return;
5207 SIPE_DEBUG_INFO("send_presence_status: status: %s (%s)",
5208 purple_status_get_id(status) ? purple_status_get_id(status) : "",
5209 sipe_is_user_state(sipe_private) ? "USER" : "MACHINE");
5211 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
5212 send_presence_category_publish(sipe_private);
5213 } else {
5214 send_presence_soap(sipe_private, FALSE);
5218 static guint sipe_ht_hash_nick(const char *nick)
5220 char *lc = g_utf8_strdown(nick, -1);
5221 guint bucket = g_str_hash(lc);
5222 g_free(lc);
5224 return bucket;
5227 static gboolean sipe_ht_equals_nick(const char *nick1, const char *nick2)
5229 char *nick1_norm = NULL;
5230 char *nick2_norm = NULL;
5231 gboolean equal;
5233 if (nick1 == NULL && nick2 == NULL) return TRUE;
5234 if (nick1 == NULL || nick2 == NULL ||
5235 !g_utf8_validate(nick1, -1, NULL) ||
5236 !g_utf8_validate(nick2, -1, NULL)) return FALSE;
5238 nick1_norm = g_utf8_casefold(nick1, -1);
5239 nick2_norm = g_utf8_casefold(nick2, -1);
5240 equal = g_utf8_collate(nick1_norm, nick2_norm) == 0;
5241 g_free(nick2_norm);
5242 g_free(nick1_norm);
5244 return equal;
5247 /* temporary function */
5248 void sipe_purple_setup(struct sipe_core_public *sipe_public,
5249 PurpleConnection *gc)
5251 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA;
5252 sip->gc = gc;
5253 sip->account = purple_connection_get_account(gc);
5256 struct sipe_core_public *sipe_core_allocate(const gchar *signin_name,
5257 const gchar *login_domain,
5258 const gchar *login_account,
5259 const gchar *password,
5260 const gchar *email,
5261 const gchar *email_url,
5262 const gchar **errmsg)
5264 struct sipe_core_private *sipe_private;
5265 struct sipe_account_data *sip;
5266 gchar **user_domain;
5268 SIPE_DEBUG_INFO("sipe_core_allocate: signin_name '%s'", signin_name);
5270 /* ensure that sign-in name doesn't contain invalid characters */
5271 if (strpbrk(signin_name, "\t\v\r\n") != NULL) {
5272 *errmsg = _("SIP Exchange user name contains invalid characters");
5273 return NULL;
5276 /* ensure that sign-in name format is name@domain */
5277 if (!strchr(signin_name, '@') ||
5278 g_str_has_prefix(signin_name, "@") ||
5279 g_str_has_suffix(signin_name, "@")) {
5280 *errmsg = _("User name should be a valid SIP URI\nExample: user@company.com");
5281 return NULL;
5284 /* ensure that email format is name@domain (if provided) */
5285 if (!is_empty(email) &&
5286 (!strchr(email, '@') ||
5287 g_str_has_prefix(email, "@") ||
5288 g_str_has_suffix(email, "@")))
5290 *errmsg = _("Email address should be valid if provided\nExample: user@company.com");
5291 return NULL;
5294 /* ensure that user name doesn't contain spaces */
5295 user_domain = g_strsplit(signin_name, "@", 2);
5296 SIPE_DEBUG_INFO("sipe_core_allocate: user '%s' domain '%s'", user_domain[0], user_domain[1]);
5297 if (strchr(user_domain[0], ' ') != NULL) {
5298 g_strfreev(user_domain);
5299 *errmsg = _("SIP Exchange user name contains whitespace");
5300 return NULL;
5303 /* ensure that email_url is in proper format if enabled (if provided).
5304 * Example (Exchange): https://server.company.com/EWS/Exchange.asmx
5305 * Example (Domino) : https://[domino_server]/[mail_database_name].nsf
5307 if (!is_empty(email_url)) {
5308 char *tmp = g_ascii_strdown(email_url, -1);
5309 if (!g_str_has_prefix(tmp, "https://"))
5311 g_free(tmp);
5312 g_strfreev(user_domain);
5313 *errmsg = _("Email services URL should be valid if provided\n"
5314 "Example: https://exchange.corp.com/EWS/Exchange.asmx\n"
5315 "Example: https://domino.corp.com/maildatabase.nsf");
5316 return NULL;
5318 g_free(tmp);
5321 sipe_private = g_new0(struct sipe_core_private, 1);
5322 sipe_private->temporary = sip = g_new0(struct sipe_account_data, 1);
5323 sip->subscribed_buddies = FALSE;
5324 sip->initial_state_published = FALSE;
5325 sipe_private->username = g_strdup(signin_name);
5326 sip->email = is_empty(email) ? g_strdup(signin_name) : g_strdup(email);
5327 sip->authdomain = is_empty(login_domain) ? NULL : g_strdup(login_domain);
5328 sip->authuser = is_empty(login_account) ? NULL : g_strdup(login_account);
5329 sip->password = g_strdup(password);
5330 sipe_private->public.sip_name = g_strdup(user_domain[0]);
5331 sipe_private->public.sip_domain = g_strdup(user_domain[1]);
5332 g_strfreev(user_domain);
5334 sipe_private->buddies = g_hash_table_new((GHashFunc)sipe_ht_hash_nick, (GEqualFunc)sipe_ht_equals_nick);
5335 sip->our_publications = g_hash_table_new_full(g_str_hash, g_str_equal,
5336 g_free, (GDestroyNotify)g_hash_table_destroy);
5337 sipe_subscriptions_init(sipe_private);
5338 sip->status = g_strdup(SIPE_STATUS_ID_UNKNOWN);
5340 return((struct sipe_core_public *)sipe_private);
5343 static void
5344 sipe_blist_menu_free_containers(struct sipe_core_private *sipe_private);
5346 void sipe_connection_cleanup(struct sipe_core_private *sipe_private)
5348 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5350 g_free(sipe_private->epid);
5351 sipe_private->epid = NULL;
5353 sip_transport_disconnect(sipe_private);
5355 sipe_schedule_cancel_all(sipe_private);
5357 if (sip->allow_events) {
5358 GSList *entry = sip->allow_events;
5359 while (entry) {
5360 g_free(entry->data);
5361 entry = entry->next;
5364 g_slist_free(sip->allow_events);
5366 if (sip->containers) {
5367 GSList *entry = sip->containers;
5368 while (entry) {
5369 free_container((struct sipe_container *)entry->data);
5370 entry = entry->next;
5373 g_slist_free(sip->containers);
5375 /* libpurple memory leak workaround */
5376 sipe_blist_menu_free_containers(sipe_private);
5378 if (sipe_private->contact)
5379 g_free(sipe_private->contact);
5380 sipe_private->contact = NULL;
5381 if (sip->regcallid)
5382 g_free(sip->regcallid);
5383 sip->regcallid = NULL;
5385 if (sipe_private->focus_factory_uri)
5386 g_free(sipe_private->focus_factory_uri);
5387 sipe_private->focus_factory_uri = NULL;
5389 if (sip->cal) {
5390 sipe_cal_calendar_free(sip->cal);
5392 sip->cal = NULL;
5394 sipe_groupchat_free(sipe_private);
5398 * A callback for g_hash_table_foreach_remove
5400 static gboolean sipe_buddy_remove(SIPE_UNUSED_PARAMETER gpointer key, gpointer buddy,
5401 SIPE_UNUSED_PARAMETER gpointer user_data)
5403 sipe_free_buddy((struct sipe_buddy *) buddy);
5405 /* We must return TRUE as the key/value have already been deleted */
5406 return(TRUE);
5409 void sipe_buddy_free_all(struct sipe_core_private *sipe_private)
5411 g_hash_table_foreach_steal(sipe_private->buddies, sipe_buddy_remove, NULL);
5414 static void sipe_searchresults_im_buddy(PurpleConnection *gc, GList *row,
5415 SIPE_UNUSED_PARAMETER void *user_data)
5417 PurpleAccount *acct = purple_connection_get_account(gc);
5418 char *id = sip_uri_from_name((gchar *)g_list_nth_data(row, 0));
5419 PurpleConversation *conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, id, acct);
5420 if (conv == NULL)
5421 conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, acct, id);
5422 purple_conversation_present(conv);
5423 g_free(id);
5426 static void sipe_searchresults_add_buddy(PurpleConnection *gc, GList *row,
5427 SIPE_UNUSED_PARAMETER void *user_data)
5430 purple_blist_request_add_buddy(purple_connection_get_account(gc),
5431 g_list_nth_data(row, 0), _("Other Contacts"), g_list_nth_data(row, 1));
5434 static gboolean process_search_contact_response(struct sipe_core_private *sipe_private,
5435 struct sipmsg *msg,
5436 SIPE_UNUSED_PARAMETER struct transaction *trans)
5438 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5439 PurpleNotifySearchResults *results;
5440 PurpleNotifySearchColumn *column;
5441 sipe_xml *searchResults;
5442 const sipe_xml *mrow;
5443 int match_count = 0;
5444 gboolean more = FALSE;
5445 gchar *secondary;
5447 /* valid response? */
5448 if (msg->response != 200) {
5449 SIPE_DEBUG_ERROR("process_search_contact_response: request failed (%d)",
5450 msg->response);
5451 purple_notify_error(sip->gc, NULL,
5452 _("Contact search failed"),
5453 NULL);
5454 return(FALSE);
5457 SIPE_DEBUG_INFO("process_search_contact_response: body:\n%s", msg->body ? msg->body : "");
5459 /* valid XML? */
5460 searchResults = sipe_xml_parse(msg->body, msg->bodylen);
5461 if (!searchResults) {
5462 SIPE_DEBUG_INFO_NOFORMAT("process_search_contact_response: no parseable searchResults");
5463 purple_notify_error(sip->gc, NULL,
5464 _("Contact search failed"),
5465 NULL);
5466 return FALSE;
5469 /* any matches? */
5470 mrow = sipe_xml_child(searchResults, "Body/Array/row");
5471 if (!mrow) {
5472 SIPE_DEBUG_ERROR_NOFORMAT("process_search_contact_response: no matches");
5473 purple_notify_error(sip->gc, NULL,
5474 _("No contacts found"),
5475 NULL);
5477 sipe_xml_free(searchResults);
5478 return(FALSE);
5481 /* OK, we found something - show the results to the user */
5482 results = purple_notify_searchresults_new();
5483 if (!results) {
5484 SIPE_DEBUG_ERROR_NOFORMAT("process_search_contact_response: Unable to display the search results.");
5485 purple_notify_error(sip->gc, NULL, _("Unable to display the search results"), NULL);
5487 sipe_xml_free(searchResults);
5488 return FALSE;
5491 column = purple_notify_searchresults_column_new(_("User name"));
5492 purple_notify_searchresults_column_add(results, column);
5494 column = purple_notify_searchresults_column_new(_("Name"));
5495 purple_notify_searchresults_column_add(results, column);
5497 column = purple_notify_searchresults_column_new(_("Company"));
5498 purple_notify_searchresults_column_add(results, column);
5500 column = purple_notify_searchresults_column_new(_("Country"));
5501 purple_notify_searchresults_column_add(results, column);
5503 column = purple_notify_searchresults_column_new(_("Email"));
5504 purple_notify_searchresults_column_add(results, column);
5506 for (/* initialized above */ ; mrow; mrow = sipe_xml_twin(mrow)) {
5507 GList *row = NULL;
5509 gchar **uri_parts = g_strsplit(sipe_xml_attribute(mrow, "uri"), ":", 2);
5510 row = g_list_append(row, g_strdup(uri_parts[1]));
5511 g_strfreev(uri_parts);
5513 row = g_list_append(row, g_strdup(sipe_xml_attribute(mrow, "displayName")));
5514 row = g_list_append(row, g_strdup(sipe_xml_attribute(mrow, "company")));
5515 row = g_list_append(row, g_strdup(sipe_xml_attribute(mrow, "country")));
5516 row = g_list_append(row, g_strdup(sipe_xml_attribute(mrow, "email")));
5518 purple_notify_searchresults_row_add(results, row);
5519 match_count++;
5522 if ((mrow = sipe_xml_child(searchResults, "Body/directorySearch/moreAvailable")) != NULL) {
5523 char *data = sipe_xml_data(mrow);
5524 more = (g_strcasecmp(data, "true") == 0);
5525 g_free(data);
5528 secondary = g_strdup_printf(
5529 dngettext(PACKAGE_NAME,
5530 "Found %d contact%s:",
5531 "Found %d contacts%s:", match_count),
5532 match_count, more ? _(" (more matched your query)") : "");
5534 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_IM, sipe_searchresults_im_buddy);
5535 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_ADD, sipe_searchresults_add_buddy);
5536 purple_notify_searchresults(sip->gc, NULL, NULL, secondary, results, NULL, NULL);
5538 g_free(secondary);
5539 sipe_xml_free(searchResults);
5540 return TRUE;
5543 #define SIPE_SOAP_SEARCH_ROW "<m:row m:attrib=\"%s\" m:value=\"%s\"/>"
5545 void sipe_search_contact_with_cb(PurpleConnection *gc, PurpleRequestFields *fields)
5547 GList *entries = purple_request_field_group_get_fields(purple_request_fields_get_groups(fields)->data);
5548 gchar **attrs = g_new(gchar *, g_list_length(entries) + 1);
5549 unsigned i = 0;
5551 if (!attrs) return;
5553 do {
5554 PurpleRequestField *field = entries->data;
5555 const char *id = purple_request_field_get_id(field);
5556 const char *value = purple_request_field_string_get_value(field);
5558 SIPE_DEBUG_INFO("sipe_search_contact_with_cb: %s = '%s'", id, value ? value : "");
5560 if (value != NULL) attrs[i++] = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, id, value);
5561 } while ((entries = g_list_next(entries)) != NULL);
5562 attrs[i] = NULL;
5564 if (i > 0) {
5565 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
5566 gchar *query = g_strjoinv(NULL, attrs);
5567 SIPE_DEBUG_INFO("sipe_search_contact_with_cb: rows:\n%s", query ? query : "");
5568 sip_soap_directory_search(sipe_private,
5569 100,
5570 query,
5571 process_search_contact_response,
5572 NULL);
5573 g_free(query);
5576 g_strfreev(attrs);
5579 static void sipe_publish_get_cat_state_user_to_clear(SIPE_UNUSED_PARAMETER const char *name,
5580 gpointer value,
5581 GString* str)
5583 struct sipe_publication *publication = value;
5585 g_string_append_printf( str,
5586 SIPE_PUB_XML_PUBLICATION_CLEAR,
5587 publication->category,
5588 publication->instance,
5589 publication->container,
5590 publication->version,
5591 "static");
5594 void sipe_core_reset_status(struct sipe_core_public *sipe_public)
5596 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
5597 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA;
5598 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) /* 2007+ */
5600 GString* str = g_string_new(NULL);
5601 gchar *publications;
5603 if (!sip->user_state_publications || g_hash_table_size(sip->user_state_publications) == 0) {
5604 SIPE_DEBUG_INFO_NOFORMAT("sipe_reset_status: no userState publications, exiting.");
5605 return;
5608 g_hash_table_foreach(sip->user_state_publications, (GHFunc)sipe_publish_get_cat_state_user_to_clear, str);
5609 publications = g_string_free(str, FALSE);
5611 send_presence_publish(sipe_private, publications);
5612 g_free(publications);
5614 else /* 2005 */
5616 send_presence_soap0(sipe_private, FALSE, TRUE);
5620 /** for Access levels menu */
5621 #define INDENT_FMT " %s"
5623 /** Member is directly placed to access level container.
5624 * For example SIP URI of user is in the container.
5626 #define INDENT_MARKED_FMT "* %s"
5628 /** Member is indirectly belong to access level container.
5629 * For example 'sameEnterprise' is in the container and user
5630 * belongs to that same enterprise.
5632 #define INDENT_MARKED_INHERITED_FMT "= %s"
5634 GSList *sipe_core_buddy_info(struct sipe_core_public *sipe_public,
5635 const gchar *name,
5636 const gchar *status_name,
5637 gboolean is_online)
5639 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
5640 gchar *note = NULL;
5641 gboolean is_oof_note = FALSE;
5642 const gchar *activity = NULL;
5643 gchar *calendar = NULL;
5644 const gchar *meeting_subject = NULL;
5645 const gchar *meeting_location = NULL;
5646 gchar *access_text = NULL;
5647 GSList *info = NULL;
5649 #define SIPE_ADD_BUDDY_INFO_COMMON(l, t) \
5651 struct sipe_buddy_info *sbi = g_malloc(sizeof(struct sipe_buddy_info)); \
5652 sbi->label = (l); \
5653 sbi->text = (t); \
5654 info = g_slist_append(info, sbi); \
5656 #define SIPE_ADD_BUDDY_INFO(l, t) SIPE_ADD_BUDDY_INFO_COMMON((l), g_markup_escape_text((t), -1))
5657 #define SIPE_ADD_BUDDY_INFO_NOESCAPE(l, t) SIPE_ADD_BUDDY_INFO_COMMON((l), (t))
5659 if (sipe_public) { //happens on pidgin exit
5660 struct sipe_buddy *sbuddy = g_hash_table_lookup(sipe_private->buddies, name);
5661 if (sbuddy) {
5662 note = sbuddy->note;
5663 is_oof_note = sbuddy->is_oof_note;
5664 activity = sbuddy->activity;
5665 calendar = sipe_cal_get_description(sbuddy);
5666 meeting_subject = sbuddy->meeting_subject;
5667 meeting_location = sbuddy->meeting_location;
5669 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
5670 gboolean is_group_access = FALSE;
5671 const int container_id = sipe_find_access_level(sipe_private, "user", sipe_get_no_sip_uri(name), &is_group_access);
5672 const char *access_level = sipe_get_access_level_name(container_id);
5673 access_text = is_group_access ?
5674 g_strdup(access_level) :
5675 g_strdup_printf(INDENT_MARKED_FMT, access_level);
5679 //Layout
5680 if (is_online)
5682 const gchar *status_str = activity ? activity : status_name;
5684 SIPE_ADD_BUDDY_INFO(_("Status"), status_str);
5686 if (is_online && !is_empty(calendar))
5688 SIPE_ADD_BUDDY_INFO(_("Calendar"), calendar);
5690 g_free(calendar);
5691 if (!is_empty(meeting_location))
5693 SIPE_DEBUG_INFO("sipe_tooltip_text: %s meeting location: '%s'", name, meeting_location);
5694 SIPE_ADD_BUDDY_INFO(_("Meeting in"), meeting_location);
5696 if (!is_empty(meeting_subject))
5698 SIPE_DEBUG_INFO("sipe_tooltip_text: %s meeting subject: '%s'", name, meeting_subject);
5699 SIPE_ADD_BUDDY_INFO(_("Meeting about"), meeting_subject);
5701 if (note)
5703 SIPE_DEBUG_INFO("sipe_tooltip_text: %s note: '%s'", name, note);
5704 SIPE_ADD_BUDDY_INFO_NOESCAPE(is_oof_note ? _("Out of office note") : _("Note"),
5705 g_strdup_printf("<i>%s</i>", note));
5707 if (access_text) {
5708 SIPE_ADD_BUDDY_INFO(_("Access level"), access_text);
5709 g_free(access_text);
5712 return(info);
5715 static PurpleBuddy *
5716 purple_blist_add_buddy_clone(PurpleGroup * group, PurpleBuddy * buddy)
5718 PurpleBuddy *clone;
5719 const gchar *server_alias, *email;
5720 const PurpleStatus *status = purple_presence_get_active_status(purple_buddy_get_presence(buddy));
5722 clone = purple_buddy_new(buddy->account, buddy->name, buddy->alias);
5724 purple_blist_add_buddy(clone, NULL, group, NULL);
5726 server_alias = purple_buddy_get_server_alias(buddy);
5727 if (server_alias) {
5728 purple_blist_server_alias_buddy(clone, server_alias);
5731 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
5732 if (email) {
5733 purple_blist_node_set_string(&clone->node, EMAIL_PROP, email);
5736 purple_presence_set_status_active(purple_buddy_get_presence(clone), purple_status_get_id(status), TRUE);
5737 //for UI to update;
5738 purple_prpl_got_user_status(clone->account, clone->name, purple_status_get_id(status), NULL);
5739 return clone;
5742 static void
5743 sipe_buddy_menu_copy_to_cb(PurpleBlistNode *node, const char *group_name)
5745 PurpleBuddy *buddy, *b;
5746 PurpleConnection *gc;
5747 PurpleGroup * group = purple_find_group(group_name);
5749 g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node));
5751 buddy = (PurpleBuddy *)node;
5753 SIPE_DEBUG_INFO("sipe_buddy_menu_copy_to_cb: copying %s to %s", buddy->name, group_name);
5754 gc = purple_account_get_connection(buddy->account);
5756 b = purple_find_buddy_in_group(buddy->account, buddy->name, group);
5757 if (!b){
5758 b = purple_blist_add_buddy_clone(group, buddy);
5761 sipe_add_buddy(gc, b, group);
5764 static void
5765 sipe_buddy_menu_chat_new_cb(PurpleBuddy *buddy)
5767 struct sipe_core_private *sipe_private = PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE;
5769 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_new_cb: buddy->name=%s", buddy->name);
5771 /* 2007+ conference */
5772 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007))
5774 sipe_conf_add(sipe_private, buddy->name);
5776 else /* 2005- multiparty chat */
5778 gchar *self = sip_uri_self(sipe_private);
5779 struct sip_session *session;
5781 session = sipe_session_add_chat(sipe_private,
5782 NULL,
5783 TRUE,
5784 self);
5785 session->chat_session->backend = sipe_backend_chat_create(SIPE_CORE_PUBLIC,
5786 session->chat_session,
5787 session->chat_session->title,
5788 self);
5789 g_free(self);
5791 sipe_im_invite(sipe_private, session, buddy->name, NULL, NULL, NULL, FALSE);
5796 * For 2007+ conference only.
5798 static void
5799 sipe_buddy_menu_chat_make_leader_cb(PurpleBuddy *buddy,
5800 struct sipe_chat_session *chat_session)
5802 struct sipe_core_private *sipe_private = PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE;
5803 struct sip_session *session;
5805 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_make_leader_cb: buddy->name=%s", buddy->name);
5806 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_make_leader_cb: chat_title=%s", chat_session->title);
5808 session = sipe_session_find_chat(sipe_private, chat_session);
5810 sipe_conf_modify_user_role(sipe_private, session, buddy->name);
5814 * For 2007+ conference only.
5816 static void
5817 sipe_buddy_menu_chat_remove_cb(PurpleBuddy *buddy,
5818 struct sipe_chat_session *chat_session)
5820 struct sipe_core_private *sipe_private = PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE;
5821 struct sip_session *session;
5823 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_remove_cb: buddy->name=%s", buddy->name);
5824 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_remove_cb: chat_title=%s", chat_session->title);
5826 session = sipe_session_find_chat(sipe_private, chat_session);
5828 sipe_conf_delete_user(sipe_private, session, buddy->name);
5831 static void
5832 sipe_buddy_menu_chat_invite_cb(PurpleBuddy *buddy,
5833 struct sipe_chat_session *chat_session)
5835 struct sipe_core_private *sipe_private = PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE;
5837 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_invite_cb: buddy->name=%s", buddy->name);
5838 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_invite_cb: chat_title=%s", chat_session->title);
5840 sipe_core_chat_invite(SIPE_CORE_PUBLIC, chat_session, buddy->name);
5843 static void
5844 sipe_buddy_menu_make_call_cb(PurpleBuddy *buddy, const char *phone)
5846 struct sipe_core_private *sipe_private = PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE;
5848 SIPE_DEBUG_INFO("sipe_buddy_menu_make_call_cb: buddy->name=%s", buddy->name);
5849 if (phone) {
5850 char *tel_uri = sip_to_tel_uri(phone);
5852 SIPE_DEBUG_INFO("sipe_buddy_menu_make_call_cb: going to call number: %s", tel_uri ? tel_uri : "");
5853 sip_csta_make_call(sipe_private, tel_uri);
5855 g_free(tel_uri);
5859 static void
5860 sipe_buddy_menu_access_level_help_cb(PurpleBuddy *buddy)
5862 /** Translators: replace with URL to localized page
5863 * If it doesn't exist copy the original URL */
5864 purple_notify_uri(buddy->account->gc, _("https://sourceforge.net/apps/mediawiki/sipe/index.php?title=Access_Levels"));
5867 static void
5868 sipe_buddy_menu_send_email_cb(PurpleBuddy *buddy)
5870 const gchar *email;
5871 SIPE_DEBUG_INFO("sipe_buddy_menu_send_email_cb: buddy->name=%s", buddy->name);
5873 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
5874 if (email)
5876 char *command_line = g_strdup_printf(
5877 #ifdef _WIN32
5878 "cmd /c start"
5879 #else
5880 "xdg-email"
5881 #endif
5882 " mailto:%s", email);
5883 SIPE_DEBUG_INFO("sipe_buddy_menu_send_email_cb: going to call email client: %s", command_line);
5885 g_spawn_command_line_async(command_line, NULL);
5886 g_free(command_line);
5888 else
5890 SIPE_DEBUG_INFO("sipe_buddy_menu_send_email_cb: no email address stored for buddy=%s", buddy->name);
5894 static void
5895 sipe_buddy_menu_access_level_cb(PurpleBuddy *buddy,
5896 struct sipe_container *container)
5898 struct sipe_core_private *sipe_private = PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE;
5899 struct sipe_container_member *member;
5901 if (!container || !container->members) return;
5903 member = ((struct sipe_container_member *)container->members->data);
5905 if (!member->type) return;
5907 SIPE_DEBUG_INFO("sipe_buddy_menu_access_level_cb: container->id=%d, member->type=%s, member->value=%s",
5908 container->id, member->type, member->value ? member->value : "");
5910 sipe_change_access_level(sipe_private, container->id, member->type, member->value);
5913 static GList *
5914 sipe_get_access_control_menu(struct sipe_core_private *sipe_private,
5915 const char* uri);
5918 * A menu which appear when right-clicking on buddy in contact list.
5920 GList *
5921 sipe_buddy_menu(PurpleBuddy *buddy)
5923 PurpleBlistNode *g_node;
5924 PurpleGroup *gr_parent;
5925 PurpleMenuAction *act;
5926 GList *menu = NULL;
5927 GList *menu_groups = NULL;
5928 struct sipe_core_private *sipe_private = PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE;
5929 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5930 const char *email;
5931 gchar *self = sip_uri_self(sipe_private);
5933 SIPE_SESSION_FOREACH {
5934 if (!sipe_strcase_equal(self, buddy->name) && session->chat_session)
5936 struct sipe_chat_session *chat_session = session->chat_session;
5937 gboolean is_conf = (chat_session->type == SIPE_CHAT_TYPE_CONFERENCE);
5939 if (sipe_backend_chat_find(chat_session->backend, buddy->name))
5941 gboolean conf_op = sipe_backend_chat_is_operator(chat_session->backend, self);
5943 if (is_conf
5944 && !sipe_backend_chat_is_operator(chat_session->backend, buddy->name) /* Not conf OP */
5945 && conf_op) /* We are a conf OP */
5947 gchar *label = g_strdup_printf(_("Make leader of '%s'"),
5948 chat_session->title);
5949 act = purple_menu_action_new(label,
5950 PURPLE_CALLBACK(sipe_buddy_menu_chat_make_leader_cb),
5951 chat_session, NULL);
5952 g_free(label);
5953 menu = g_list_prepend(menu, act);
5956 if (is_conf
5957 && conf_op) /* We are a conf OP */
5959 gchar *label = g_strdup_printf(_("Remove from '%s'"),
5960 chat_session->title);
5961 act = purple_menu_action_new(label,
5962 PURPLE_CALLBACK(sipe_buddy_menu_chat_remove_cb),
5963 chat_session, NULL);
5964 g_free(label);
5965 menu = g_list_prepend(menu, act);
5968 else
5970 if (!is_conf
5971 || (is_conf && !session->locked))
5973 gchar *label = g_strdup_printf(_("Invite to '%s'"),
5974 chat_session->title);
5975 act = purple_menu_action_new(label,
5976 PURPLE_CALLBACK(sipe_buddy_menu_chat_invite_cb),
5977 chat_session, NULL);
5978 g_free(label);
5979 menu = g_list_prepend(menu, act);
5983 } SIPE_SESSION_FOREACH_END;
5985 act = purple_menu_action_new(_("New chat"),
5986 PURPLE_CALLBACK(sipe_buddy_menu_chat_new_cb),
5987 NULL, NULL);
5988 menu = g_list_prepend(menu, act);
5990 if (sip->csta && !sip->csta->line_status) {
5991 const char *phone;
5992 const char *phone_disp_str;
5993 gchar *tmp = NULL;
5994 /* work phone */
5995 phone = purple_blist_node_get_string(&buddy->node, PHONE_PROP);
5996 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_DISPLAY_PROP);
5997 if (phone) {
5998 gchar *label = g_strdup_printf(_("Work %s"),
5999 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
6000 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
6001 g_free(tmp);
6002 tmp = NULL;
6003 g_free(label);
6004 menu = g_list_prepend(menu, act);
6007 /* mobile phone */
6008 phone = purple_blist_node_get_string(&buddy->node, PHONE_MOBILE_PROP);
6009 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_MOBILE_DISPLAY_PROP);
6010 if (phone) {
6011 gchar *label = g_strdup_printf(_("Mobile %s"),
6012 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
6013 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
6014 g_free(tmp);
6015 tmp = NULL;
6016 g_free(label);
6017 menu = g_list_prepend(menu, act);
6020 /* home phone */
6021 phone = purple_blist_node_get_string(&buddy->node, PHONE_HOME_PROP);
6022 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_HOME_DISPLAY_PROP);
6023 if (phone) {
6024 gchar *label = g_strdup_printf(_("Home %s"),
6025 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
6026 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
6027 g_free(tmp);
6028 tmp = NULL;
6029 g_free(label);
6030 menu = g_list_prepend(menu, act);
6033 /* other phone */
6034 phone = purple_blist_node_get_string(&buddy->node, PHONE_OTHER_PROP);
6035 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_OTHER_DISPLAY_PROP);
6036 if (phone) {
6037 gchar *label = g_strdup_printf(_("Other %s"),
6038 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
6039 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
6040 g_free(tmp);
6041 tmp = NULL;
6042 g_free(label);
6043 menu = g_list_prepend(menu, act);
6046 /* custom1 phone */
6047 phone = purple_blist_node_get_string(&buddy->node, PHONE_CUSTOM1_PROP);
6048 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_CUSTOM1_DISPLAY_PROP);
6049 if (phone) {
6050 gchar *label = g_strdup_printf(_("Custom1 %s"),
6051 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
6052 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
6053 g_free(tmp);
6054 tmp = NULL;
6055 g_free(label);
6056 menu = g_list_prepend(menu, act);
6060 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
6061 if (email) {
6062 act = purple_menu_action_new(_("Send email..."),
6063 PURPLE_CALLBACK(sipe_buddy_menu_send_email_cb),
6064 NULL, NULL);
6065 menu = g_list_prepend(menu, act);
6068 /* Access Level */
6069 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
6070 GList *menu_access_levels = sipe_get_access_control_menu(sipe_private, buddy->name);
6072 act = purple_menu_action_new(_("Access level"),
6073 NULL,
6074 NULL, menu_access_levels);
6075 menu = g_list_prepend(menu, act);
6078 /* Copy to */
6079 gr_parent = purple_buddy_get_group(buddy);
6080 for (g_node = purple_blist_get_root(); g_node; g_node = g_node->next) {
6081 PurpleGroup *group;
6083 if (g_node->type != PURPLE_BLIST_GROUP_NODE)
6084 continue;
6086 group = (PurpleGroup *)g_node;
6087 if (group == gr_parent)
6088 continue;
6090 if (purple_find_buddy_in_group(buddy->account, buddy->name, group))
6091 continue;
6093 act = purple_menu_action_new(purple_group_get_name(group),
6094 PURPLE_CALLBACK(sipe_buddy_menu_copy_to_cb),
6095 group->name, NULL);
6096 menu_groups = g_list_prepend(menu_groups, act);
6098 menu_groups = g_list_reverse(menu_groups);
6100 act = purple_menu_action_new(_("Copy to"),
6101 NULL,
6102 NULL, menu_groups);
6103 menu = g_list_prepend(menu, act);
6105 menu = g_list_reverse(menu);
6107 g_free(self);
6108 return menu;
6111 static void
6112 sipe_ask_access_domain_cb(PurpleConnection *gc, PurpleRequestFields *fields)
6114 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
6115 const char *domain = purple_request_fields_get_string(fields, "access_domain");
6116 int index = purple_request_fields_get_choice(fields, "container_id");
6117 /* move Blocked first */
6118 int i = (index == 4) ? 0 : index + 1;
6119 int container_id = containers[i];
6121 SIPE_DEBUG_INFO("sipe_ask_access_domain_cb: domain=%s, container_id=(%d)%d", domain ? domain : "", index, container_id);
6123 sipe_change_access_level(sipe_private, container_id, "domain", domain);
6126 static void
6127 sipe_ask_access_domain(struct sipe_core_private *sipe_private)
6129 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
6130 PurpleAccount *account = sip->account;
6131 PurpleConnection *gc = sip->gc;
6132 PurpleRequestFields *fields;
6133 PurpleRequestFieldGroup *g;
6134 PurpleRequestField *f;
6136 fields = purple_request_fields_new();
6138 g = purple_request_field_group_new(NULL);
6139 f = purple_request_field_string_new("access_domain", _("Domain"), "partner-company.com", FALSE);
6140 purple_request_field_set_required(f, TRUE);
6141 purple_request_field_group_add_field(g, f);
6143 f = purple_request_field_choice_new("container_id", _("Access level"), 0);
6144 purple_request_field_choice_add(f, _("Personal")); /* index 0 */
6145 purple_request_field_choice_add(f, _("Team"));
6146 purple_request_field_choice_add(f, _("Company"));
6147 purple_request_field_choice_add(f, _("Public"));
6148 purple_request_field_choice_add(f, _("Blocked")); /* index 4 */
6149 purple_request_field_choice_set_default_value(f, 3); /* index */
6150 purple_request_field_set_required(f, TRUE);
6151 purple_request_field_group_add_field(g, f);
6153 purple_request_fields_add_group(fields, g);
6155 purple_request_fields(gc, _("Add new domain"),
6156 _("Add new domain"), NULL, fields,
6157 _("Add"), G_CALLBACK(sipe_ask_access_domain_cb),
6158 _("Cancel"), NULL,
6159 account, NULL, NULL, gc);
6162 static void
6163 sipe_buddy_menu_access_level_add_domain_cb(PurpleBuddy *buddy)
6165 sipe_ask_access_domain(PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE);
6169 * Workaround for missing libpurple API to release resources allocated
6170 * during blist_node_menu() callback. See also:
6172 * <http://developer.pidgin.im/ticket/12597>
6174 * We remember all memory blocks in a list and deallocate them when
6176 * - the next time we enter the callback, or
6177 * - the account is disconnected
6179 * That means that after the buddy menu has been closed we have unused
6180 * resources but at least we don't leak them anymore...
6182 static void
6183 sipe_blist_menu_free_containers(struct sipe_core_private *sipe_private)
6185 GSList *entry = sipe_private->blist_menu_containers;
6186 while (entry) {
6187 free_container(entry->data);
6188 entry = entry->next;
6190 g_slist_free(sipe_private->blist_menu_containers);
6191 sipe_private->blist_menu_containers = NULL;
6194 static void
6195 sipe_blist_menu_remember_container(struct sipe_core_private *sipe_private,
6196 struct sipe_container *container)
6198 sipe_private->blist_menu_containers = g_slist_prepend(sipe_private->blist_menu_containers,
6199 container);
6202 static GList *
6203 sipe_get_access_levels_menu(struct sipe_core_private *sipe_private,
6204 const char* member_type,
6205 const char* member_value,
6206 const gboolean extra_menu)
6208 GList *menu_access_levels = NULL;
6209 unsigned int i;
6210 char *menu_name;
6211 PurpleMenuAction *act;
6212 struct sipe_container *container;
6213 struct sipe_container_member *member;
6214 gboolean is_group_access = FALSE;
6215 int container_id = sipe_find_access_level(sipe_private, member_type, member_value, &is_group_access);
6217 for (i = 1; i <= CONTAINERS_LEN; i++) {
6218 /* to put Blocked level last in menu list.
6219 * Blocked should remaim in the first place in the containers[] array.
6221 unsigned int j = (i == CONTAINERS_LEN) ? 0 : i;
6222 const char *acc_level_name = sipe_get_access_level_name(containers[j]);
6224 container = g_new0(struct sipe_container, 1);
6225 member = g_new0(struct sipe_container_member, 1);
6226 container->id = containers[j];
6227 container->members = g_slist_append(container->members, member);
6228 member->type = g_strdup(member_type);
6229 member->value = g_strdup(member_value);
6231 /* libpurple memory leak workaround */
6232 sipe_blist_menu_remember_container(sipe_private, container);
6234 /* current container/access level */
6235 if (((int)containers[j]) == container_id) {
6236 menu_name = is_group_access ?
6237 g_strdup_printf(INDENT_MARKED_INHERITED_FMT, acc_level_name) :
6238 g_strdup_printf(INDENT_MARKED_FMT, acc_level_name);
6239 } else {
6240 menu_name = g_strdup_printf(INDENT_FMT, acc_level_name);
6243 act = purple_menu_action_new(menu_name,
6244 PURPLE_CALLBACK(sipe_buddy_menu_access_level_cb),
6245 container, NULL);
6246 g_free(menu_name);
6247 menu_access_levels = g_list_prepend(menu_access_levels, act);
6250 if (extra_menu && (container_id >= 0)) {
6251 /* separator */
6252 act = purple_menu_action_new(" --------------", NULL, NULL, NULL);
6253 menu_access_levels = g_list_prepend(menu_access_levels, act);
6255 if (!is_group_access) {
6256 container = g_new0(struct sipe_container, 1);
6257 member = g_new0(struct sipe_container_member, 1);
6258 container->id = -1;
6259 container->members = g_slist_append(container->members, member);
6260 member->type = g_strdup(member_type);
6261 member->value = g_strdup(member_value);
6263 /* libpurple memory leak workaround */
6264 sipe_blist_menu_remember_container(sipe_private, container);
6266 /* Translators: remove (clear) previously assigned access level */
6267 menu_name = g_strdup_printf(INDENT_FMT, _("Unspecify"));
6268 act = purple_menu_action_new(menu_name,
6269 PURPLE_CALLBACK(sipe_buddy_menu_access_level_cb),
6270 container, NULL);
6271 g_free(menu_name);
6272 menu_access_levels = g_list_prepend(menu_access_levels, act);
6276 menu_access_levels = g_list_reverse(menu_access_levels);
6277 return menu_access_levels;
6280 static GList *
6281 sipe_get_access_groups_menu(struct sipe_core_private *sipe_private)
6283 GList *menu_access_groups = NULL;
6284 PurpleMenuAction *act;
6285 GSList *access_domains = NULL;
6286 GSList *entry;
6287 char *menu_name;
6288 char *domain;
6290 act = purple_menu_action_new(_("People in my company"),
6291 NULL,
6292 NULL, sipe_get_access_levels_menu(sipe_private, "sameEnterprise", NULL, FALSE));
6293 menu_access_groups = g_list_prepend(menu_access_groups, act);
6295 /* this is original name, don't edit */
6296 act = purple_menu_action_new(_("People in domains connected with my company"),
6297 NULL,
6298 NULL, sipe_get_access_levels_menu(sipe_private, "federated", NULL, FALSE));
6299 menu_access_groups = g_list_prepend(menu_access_groups, act);
6301 act = purple_menu_action_new(_("People in public domains"),
6302 NULL,
6303 NULL, sipe_get_access_levels_menu(sipe_private, "publicCloud", NULL, TRUE));
6304 menu_access_groups = g_list_prepend(menu_access_groups, act);
6306 access_domains = sipe_get_access_domains(sipe_private);
6307 entry = access_domains;
6308 while (entry) {
6309 domain = entry->data;
6311 menu_name = g_strdup_printf(_("People at %s"), domain);
6312 act = purple_menu_action_new(menu_name,
6313 NULL,
6314 NULL, sipe_get_access_levels_menu(sipe_private, "domain", g_strdup(domain), TRUE));
6315 menu_access_groups = g_list_prepend(menu_access_groups, act);
6316 g_free(menu_name);
6318 entry = entry->next;
6321 /* separator */
6322 /* People in domains connected with my company */
6323 act = purple_menu_action_new("-------------------------------------------", NULL, NULL, NULL);
6324 menu_access_groups = g_list_prepend(menu_access_groups, act);
6326 act = purple_menu_action_new(_("Add new domain..."),
6327 PURPLE_CALLBACK(sipe_buddy_menu_access_level_add_domain_cb),
6328 NULL, NULL);
6329 menu_access_groups = g_list_prepend(menu_access_groups, act);
6331 menu_access_groups = g_list_reverse(menu_access_groups);
6333 return menu_access_groups;
6336 static GList *
6337 sipe_get_access_control_menu(struct sipe_core_private *sipe_private,
6338 const char* uri)
6340 GList *menu_access_levels = NULL;
6341 GList *menu_access_groups = NULL;
6342 char *menu_name;
6343 PurpleMenuAction *act;
6345 /* libpurple memory leak workaround */
6346 sipe_blist_menu_free_containers(sipe_private);
6348 menu_access_levels = sipe_get_access_levels_menu(sipe_private, "user", sipe_get_no_sip_uri(uri), TRUE);
6350 menu_access_groups = sipe_get_access_groups_menu(sipe_private);
6352 menu_name = g_strdup_printf(INDENT_FMT, _("Access groups"));
6353 act = purple_menu_action_new(menu_name,
6354 NULL,
6355 NULL, menu_access_groups);
6356 g_free(menu_name);
6357 menu_access_levels = g_list_append(menu_access_levels, act);
6359 menu_name = g_strdup_printf(INDENT_FMT, _("Online help..."));
6360 act = purple_menu_action_new(menu_name,
6361 PURPLE_CALLBACK(sipe_buddy_menu_access_level_help_cb),
6362 NULL, NULL);
6363 g_free(menu_name);
6364 menu_access_levels = g_list_append(menu_access_levels, act);
6366 return menu_access_levels;
6369 static gboolean
6370 process_get_info_response(struct sipe_core_private *sipe_private,
6371 struct sipmsg *msg, struct transaction *trans)
6373 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
6374 char *uri = trans->payload->data;
6376 PurpleNotifyUserInfo *info;
6377 PurpleBuddy *pbuddy = NULL;
6378 struct sipe_buddy *sbuddy;
6379 const char *alias = NULL;
6380 char *device_name = NULL;
6381 char *server_alias = NULL;
6382 char *phone_number = NULL;
6383 char *email = NULL;
6384 const char *site;
6385 char *first_name = NULL;
6386 char *last_name = NULL;
6388 if (!sip) return FALSE;
6390 SIPE_DEBUG_INFO("Fetching %s's user info for %s", uri, sipe_private->username);
6392 pbuddy = purple_find_buddy((PurpleAccount *)sip->account, uri);
6393 alias = purple_buddy_get_local_alias(pbuddy);
6395 //will query buddy UA's capabilities and send answer to log
6396 sipe_options_request(sipe_private, uri);
6398 sbuddy = g_hash_table_lookup(sipe_private->buddies, uri);
6399 if (sbuddy) {
6400 device_name = sbuddy->device_name ? g_strdup(sbuddy->device_name) : NULL;
6403 info = purple_notify_user_info_new();
6405 if (msg->response != 200) {
6406 SIPE_DEBUG_INFO("process_get_info_response: SERVICE response is %d", msg->response);
6407 } else {
6408 sipe_xml *searchResults;
6409 const sipe_xml *mrow;
6411 SIPE_DEBUG_INFO("process_get_info_response: body:\n%s", msg->body ? msg->body : "");
6412 searchResults = sipe_xml_parse(msg->body, msg->bodylen);
6413 if (!searchResults) {
6414 SIPE_DEBUG_INFO_NOFORMAT("process_get_info_response: no parseable searchResults");
6415 } else if ((mrow = sipe_xml_child(searchResults, "Body/Array/row"))) {
6416 const char *value;
6417 server_alias = g_strdup(sipe_xml_attribute(mrow, "displayName"));
6418 email = g_strdup(sipe_xml_attribute(mrow, "email"));
6419 phone_number = g_strdup(sipe_xml_attribute(mrow, "phone"));
6421 /* For 2007 system we will take this from ContactCard -
6422 * it has cleaner tel: URIs at least
6424 if (!SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
6425 char *tel_uri = sip_to_tel_uri(phone_number);
6426 /* trims its parameters, so call first */
6427 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_DISPLAY_NAME, server_alias);
6428 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_EMAIL, email);
6429 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_WORK_PHONE, tel_uri);
6430 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_WORK_PHONE_DISPLAY, phone_number);
6431 g_free(tel_uri);
6434 #if PURPLE_VERSION_CHECK(3,0,0)
6435 #define PURPLE_NOTIFY_USER_INFO_ADD_PAIR purple_notify_user_info_add_pair_html
6436 #else
6437 #define PURPLE_NOTIFY_USER_INFO_ADD_PAIR purple_notify_user_info_add_pair
6438 #endif
6440 if (server_alias && strlen(server_alias) > 0) {
6441 PURPLE_NOTIFY_USER_INFO_ADD_PAIR(info, _("Display name"), server_alias);
6443 if ((value = sipe_xml_attribute(mrow, "title")) && strlen(value) > 0) {
6444 PURPLE_NOTIFY_USER_INFO_ADD_PAIR(info, _("Job title"), value);
6446 if ((value = sipe_xml_attribute(mrow, "office")) && strlen(value) > 0) {
6447 PURPLE_NOTIFY_USER_INFO_ADD_PAIR(info, _("Office"), value);
6449 if (phone_number && strlen(phone_number) > 0) {
6450 PURPLE_NOTIFY_USER_INFO_ADD_PAIR(info, _("Business phone"), phone_number);
6452 if ((value = sipe_xml_attribute(mrow, "company")) && strlen(value) > 0) {
6453 PURPLE_NOTIFY_USER_INFO_ADD_PAIR(info, _("Company"), value);
6455 if ((value = sipe_xml_attribute(mrow, "city")) && strlen(value) > 0) {
6456 PURPLE_NOTIFY_USER_INFO_ADD_PAIR(info, _("City"), value);
6458 if ((value = sipe_xml_attribute(mrow, "state")) && strlen(value) > 0) {
6459 PURPLE_NOTIFY_USER_INFO_ADD_PAIR(info, _("State"), value);
6461 if ((value = sipe_xml_attribute(mrow, "country")) && strlen(value) > 0) {
6462 PURPLE_NOTIFY_USER_INFO_ADD_PAIR(info, _("Country"), value);
6464 if (email && strlen(email) > 0) {
6465 PURPLE_NOTIFY_USER_INFO_ADD_PAIR(info, _("Email address"), email);
6469 sipe_xml_free(searchResults);
6472 purple_notify_user_info_add_section_break(info);
6474 if (is_empty(server_alias)) {
6475 g_free(server_alias);
6476 server_alias = g_strdup(purple_buddy_get_server_alias(pbuddy));
6477 if (server_alias) {
6478 PURPLE_NOTIFY_USER_INFO_ADD_PAIR(info, _("Display name"), server_alias);
6482 /* present alias if it differs from server alias */
6483 if (alias && !sipe_strequal(alias, server_alias))
6485 PURPLE_NOTIFY_USER_INFO_ADD_PAIR(info, _("Alias"), alias);
6488 if (is_empty(email)) {
6489 g_free(email);
6490 email = g_strdup(purple_blist_node_get_string(&pbuddy->node, EMAIL_PROP));
6491 if (email) {
6492 PURPLE_NOTIFY_USER_INFO_ADD_PAIR(info, _("Email address"), email);
6496 site = purple_blist_node_get_string(&pbuddy->node, SITE_PROP);
6497 if (site) {
6498 PURPLE_NOTIFY_USER_INFO_ADD_PAIR(info, _("Site"), site);
6501 sipe_get_first_last_names(sipe_private, uri, &first_name, &last_name);
6502 if (first_name && last_name) {
6503 char *link = g_strconcat("http://www.linkedin.com/pub/dir/", first_name, "/", last_name, NULL);
6505 PURPLE_NOTIFY_USER_INFO_ADD_PAIR(info, _("Find on LinkedIn"), link);
6506 g_free(link);
6508 g_free(first_name);
6509 g_free(last_name);
6511 if (device_name) {
6512 PURPLE_NOTIFY_USER_INFO_ADD_PAIR(info, _("Device"), device_name);
6515 /* show a buddy's user info in a nice dialog box */
6516 purple_notify_userinfo(sip->gc, /* connection the buddy info came through */
6517 uri, /* buddy's URI */
6518 info, /* body */
6519 NULL, /* callback called when dialog closed */
6520 NULL); /* userdata for callback */
6522 g_free(phone_number);
6523 g_free(server_alias);
6524 g_free(email);
6525 g_free(device_name);
6527 return TRUE;
6531 * AD search first, LDAP based
6533 void sipe_get_info(PurpleConnection *gc, const char *username)
6535 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
6536 char *row = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, "msRTCSIP-PrimaryUserAddress", username);
6537 struct transaction_payload *payload = g_new0(struct transaction_payload, 1);
6539 payload->destroy = g_free;
6540 payload->data = g_strdup(username);
6542 SIPE_DEBUG_INFO("sipe_get_info: row: %s", row ? row : "");
6543 sip_soap_directory_search(sipe_private,
6545 row,
6546 process_get_info_response,
6547 payload);
6548 g_free(row);
6552 Local Variables:
6553 mode: c
6554 c-file-style: "bsd"
6555 indent-tabs-mode: t
6556 tab-width: 8
6557 End: