Updated media patches README
[siplcs.git] / src / core / sipe.c
blobc8b6a64da3fb2db785e7f639443ad165426e110f
1 /**
2 * @file sipe.c
4 * pidgin-sipe
6 * Copyright (C) 2010-11 SIPE Project <http://sipe.sourceforge.net/>
7 * Copyright (C) 2010 pier11 <pier11@operamail.com>
8 * Copyright (C) 2009 Anibal Avelar <debianmx@gmail.com>
9 * Copyright (C) 2009 pier11 <pier11@operamail.com>
10 * Copyright (C) 2008 Novell, Inc., Anibal Avelar <debianmx@gmail.com>
11 * Copyright (C) 2007 Anibal Avelar <debianmx@gmail.com>
12 * Copyright (C) 2005 Thomas Butter <butter@uni-mannheim.de>
14 * ***
15 * Thanks to Google's Summer of Code Program and the helpful mentors
16 * ***
18 * Session-based SIP MESSAGE documentation:
19 * http://tools.ietf.org/html/draft-ietf-simple-im-session-00
21 * This program is free software; you can redistribute it and/or modify
22 * it under the terms of the GNU General Public License as published by
23 * the Free Software Foundation; either version 2 of the License, or
24 * (at your option) any later version.
26 * This program is distributed in the hope that it will be useful,
27 * but WITHOUT ANY WARRANTY; without even the implied warranty of
28 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
29 * GNU General Public License for more details.
31 * You should have received a copy of the GNU General Public License
32 * along with this program; if not, write to the Free Software
33 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
36 #ifdef HAVE_CONFIG_H
37 #include "config.h"
38 #endif
40 #include <time.h>
41 #include <stdlib.h>
42 #include <stdio.h>
43 #include <errno.h>
44 #include <string.h>
45 #include <unistd.h>
47 #include <glib.h>
49 #include <libintl.h>
51 #include "sipe-common.h"
53 #include "account.h"
54 #include "blist.h"
55 #include "connection.h"
56 #include "conversation.h"
57 #include "ft.h"
58 #include "notify.h"
59 #include "plugin.h"
60 #include "privacy.h"
61 #include "request.h"
62 #include "savedstatuses.h"
64 #include "core-depurple.h" /* Temporary for the core de-purple transition */
66 #include "http-conn.h"
67 #include "sipmsg.h"
68 #include "sip-csta.h"
69 #include "sip-transport.h"
70 #include "sipe-backend.h"
71 #include "sipe-buddy.h"
72 #include "sipe-cal.h"
73 #include "sipe-chat.h"
74 #include "sipe-conf.h"
75 #include "sipe-core.h"
76 #include "sipe-core-private.h"
77 #include "sipe-dialog.h"
78 #include "sipe-ews.h"
79 #include "sipe-domino.h"
80 #include "sipe-groupchat.h"
81 #include "sipe-im.h"
82 #include "sipe-mime.h"
83 #include "sipe-nls.h"
84 #include "sipe-schedule.h"
85 #include "sipe-session.h"
86 #include "sipe-subscriptions.h"
87 #ifdef HAVE_VV
88 #include "sipe-media.h"
89 #endif
90 #include "sipe-utils.h"
91 #include "sipe-xml.h"
92 #include "uuid.h"
93 #include "sipe.h"
95 #define SIPE_IDLE_SET_DELAY 1 /* 1 sec */
97 #define UPDATE_CALENDAR_DELAY 1*60 /* 1 min */
98 #define UPDATE_CALENDAR_INTERVAL 30*60 /* 30 min */
100 /* Status identifiers (see also: sipe_status_types()) */
101 #define SIPE_STATUS_ID_UNKNOWN purple_primitive_get_id_from_type(PURPLE_STATUS_UNSET) /* Unset (primitive) */
102 #define SIPE_STATUS_ID_OFFLINE purple_primitive_get_id_from_type(PURPLE_STATUS_OFFLINE) /* Offline (primitive) */
103 #define SIPE_STATUS_ID_AVAILABLE purple_primitive_get_id_from_type(PURPLE_STATUS_AVAILABLE) /* Online */
104 /* PURPLE_STATUS_UNAVAILABLE: */
105 #define SIPE_STATUS_ID_BUSY "busy" /* Busy */
106 #define SIPE_STATUS_ID_BUSYIDLE "busyidle" /* BusyIdle */
107 #define SIPE_STATUS_ID_DND "do-not-disturb" /* Do Not Disturb */
108 #define SIPE_STATUS_ID_IN_MEETING "in-a-meeting" /* In a meeting */
109 #define SIPE_STATUS_ID_IN_CONF "in-a-conference" /* In a conference */
110 #define SIPE_STATUS_ID_ON_PHONE "on-the-phone" /* On the phone */
111 #define SIPE_STATUS_ID_INVISIBLE purple_primitive_get_id_from_type(PURPLE_STATUS_INVISIBLE) /* Appear Offline */
112 /* PURPLE_STATUS_AWAY: */
113 #define SIPE_STATUS_ID_IDLE "idle" /* Idle/Inactive */
114 #define SIPE_STATUS_ID_BRB "be-right-back" /* Be Right Back */
115 #define SIPE_STATUS_ID_AWAY purple_primitive_get_id_from_type(PURPLE_STATUS_AWAY) /* Away (primitive) */
116 /** Reuters status (user settable) */
117 #define SIPE_STATUS_ID_LUNCH "out-to-lunch" /* Out To Lunch */
118 /* ??? PURPLE_STATUS_EXTENDED_AWAY */
119 /* ??? PURPLE_STATUS_MOBILE */
120 /* ??? PURPLE_STATUS_TUNE */
122 /* Status attributes (see also sipe_status_types() */
123 #define SIPE_STATUS_ATTR_ID_MESSAGE "message"
125 static struct sipe_activity_map_struct
127 sipe_activity type;
128 const char *token;
129 const char *desc;
130 const char *status_id;
132 } const sipe_activity_map[] =
134 /* This has nothing to do with Availability numbers, like 3500 (online).
135 * Just a mapping of Communicator Activities to Purple statuses to be able display them in Pidgin.
137 { SIPE_ACTIVITY_UNSET, "unset", NULL , NULL },
138 { SIPE_ACTIVITY_ONLINE, "online", NULL , NULL },
139 { SIPE_ACTIVITY_INACTIVE, SIPE_STATUS_ID_IDLE, N_("Inactive") , NULL },
140 { SIPE_ACTIVITY_BUSY, SIPE_STATUS_ID_BUSY, N_("Busy") , SIPE_STATUS_ID_BUSY },
141 { SIPE_ACTIVITY_BUSYIDLE, SIPE_STATUS_ID_BUSYIDLE, N_("Busy-Idle") , NULL },
142 { SIPE_ACTIVITY_DND, SIPE_STATUS_ID_DND, NULL , SIPE_STATUS_ID_DND },
143 { SIPE_ACTIVITY_BRB, SIPE_STATUS_ID_BRB, N_("Be right back") , SIPE_STATUS_ID_BRB },
144 { SIPE_ACTIVITY_AWAY, "away", NULL , NULL },
145 { SIPE_ACTIVITY_LUNCH, SIPE_STATUS_ID_LUNCH, N_("Out to lunch") , NULL },
146 { SIPE_ACTIVITY_OFFLINE, "offline", NULL , NULL },
147 { SIPE_ACTIVITY_ON_PHONE, SIPE_STATUS_ID_ON_PHONE, N_("In a call") , NULL },
148 { SIPE_ACTIVITY_IN_CONF, SIPE_STATUS_ID_IN_CONF, N_("In a conference") , NULL },
149 { SIPE_ACTIVITY_IN_MEETING, SIPE_STATUS_ID_IN_MEETING, N_("In a meeting") , NULL },
150 { SIPE_ACTIVITY_OOF, "out-of-office", N_("Out of office") , NULL },
151 { SIPE_ACTIVITY_URGENT_ONLY, "urgent-interruptions-only", N_("Urgent interruptions only") , NULL }
153 /** @param x is sipe_activity */
154 #define SIPE_ACTIVITY_I18N(x) gettext(sipe_activity_map[x].desc)
156 static sipe_activity
157 sipe_get_activity_by_token(const char *token)
159 int i;
161 for (i = 0; i < SIPE_ACTIVITY_NUM_TYPES; i++)
163 if (sipe_strequal(token, sipe_activity_map[i].token))
164 return sipe_activity_map[i].type;
167 return sipe_activity_map[0].type;
170 static const char *
171 sipe_get_activity_desc_by_token(const char *token)
173 if (!token) return NULL;
175 return SIPE_ACTIVITY_I18N(sipe_get_activity_by_token(token));
178 static void send_presence_status(struct sipe_core_private *sipe_private,
179 void *unused);
182 * @param from0 from URI (with 'sip:' prefix). Will be filled with self-URI if NULL passed.
184 static void
185 send_soap_request_with_cb(struct sipe_core_private *sipe_private,
186 gchar *from0,
187 gchar *body,
188 TransCallback callback,
189 struct transaction_payload *payload)
191 gchar *from = from0 ? g_strdup(from0) : sip_uri_self(sipe_private);
192 gchar *contact = get_contact(sipe_private);
193 gchar *hdr = g_strdup_printf("Contact: %s\r\n"
194 "Content-Type: application/SOAP+xml\r\n",contact);
196 struct transaction *trans = sip_transport_service(sipe_private,
197 from,
198 hdr,
199 body,
200 callback);
201 trans->payload = payload;
203 g_free(from);
204 g_free(contact);
205 g_free(hdr);
208 static void send_soap_request(struct sipe_core_private *sipe_private,
209 gchar *body)
211 send_soap_request_with_cb(sipe_private, NULL, body, NULL, NULL);
215 * Returns pointer to URI without sip: prefix if any
217 * @param sip_uri SIP URI possibly with sip: prefix. Example: sip:first.last@hq.company.com
218 * @return pointer to URL without sip: prefix. Coresponding example: first.last@hq.company.com
220 * Doesn't allocate memory
222 static const char *
223 sipe_get_no_sip_uri(const char *sip_uri)
225 const char *prefix = "sip:";
226 if (!sip_uri) return NULL;
228 if (g_str_has_prefix(sip_uri, prefix)) {
229 return (sip_uri+strlen(prefix));
230 } else {
231 return sip_uri;
235 static void
236 sipe_contact_set_acl (struct sipe_core_private *sipe_private,
237 const gchar *who,
238 gchar *rights)
240 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
241 gchar * body = g_strdup_printf(SIPE_SOAP_ALLOW_DENY, who, rights, sip->acl_delta++);
242 send_soap_request(sipe_private, body);
243 g_free(body);
246 static void
247 sipe_change_access_level(struct sipe_core_private *sipe_private,
248 const int container_id,
249 const gchar *type,
250 const gchar *value);
252 void
253 sipe_core_contact_allow_deny (struct sipe_core_public *sipe_public,
254 const gchar * who, gboolean allow)
256 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
258 if (allow) {
259 SIPE_DEBUG_INFO("Authorizing contact %s", who);
260 } else {
261 SIPE_DEBUG_INFO("Blocking contact %s", who);
264 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
265 sipe_change_access_level(sipe_private, (allow ? -1 : 32000), "user", sipe_get_no_sip_uri(who));
266 } else {
267 sipe_contact_set_acl(sipe_private, who, allow ? "AA" : "BD");
271 static
272 void sipe_auth_user_cb(void * data)
274 struct sipe_auth_job * job = (struct sipe_auth_job *) data;
275 if (!job) return;
277 sipe_core_contact_allow_deny((struct sipe_core_public *)job->sipe_private, job->who, TRUE);
278 g_free(job);
281 static
282 void sipe_deny_user_cb(void * data)
284 struct sipe_auth_job * job = (struct sipe_auth_job *) data;
285 if (!job) return;
287 sipe_core_contact_allow_deny((struct sipe_core_public *)job->sipe_private, job->who, FALSE);
288 g_free(job);
291 /** @applicable: 2005-
293 static void
294 sipe_process_presence_wpending (struct sipe_core_private *sipe_private,
295 struct sipmsg * msg)
297 sipe_xml *watchers;
298 const sipe_xml *watcher;
299 // Ensure it's either not a response (eg it's a BENOTIFY) or that it's a 200 OK response
300 if (msg->response != 0 && msg->response != 200) return;
302 if (msg->bodylen == 0 || msg->body == NULL || sipe_strequal(sipmsg_find_header(msg, "Event"), "msrtc.wpending")) return;
304 watchers = sipe_xml_parse(msg->body, msg->bodylen);
305 if (!watchers) return;
307 for (watcher = sipe_xml_child(watchers, "watcher"); watcher; watcher = sipe_xml_twin(watcher)) {
308 gchar * remote_user = g_strdup(sipe_xml_attribute(watcher, "uri"));
309 gchar * alias = g_strdup(sipe_xml_attribute(watcher, "displayName"));
310 gboolean on_list = g_hash_table_lookup(sipe_private->buddies, remote_user) != NULL;
312 // TODO pull out optional displayName to pass as alias
313 if (remote_user) {
314 struct sipe_auth_job * job = g_new0(struct sipe_auth_job, 1);
315 job->who = remote_user;
316 job->sipe_private = sipe_private;
317 sipe_backend_buddy_request_authorization(SIPE_CORE_PUBLIC,
318 remote_user,
319 alias,
320 on_list,
321 sipe_auth_user_cb,
322 sipe_deny_user_cb,
323 (gpointer)job);
328 sipe_xml_free(watchers);
329 return;
332 static void
333 sipe_group_add(struct sipe_core_private *sipe_private,
334 struct sipe_group * group)
336 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
337 if (sipe_backend_buddy_group_add(SIPE_CORE_PUBLIC,group->name))
339 SIPE_DEBUG_INFO("added group %s (id %d)", group->name, group->id);
340 sip->groups = g_slist_append(sip->groups, group);
342 else
344 SIPE_DEBUG_INFO("did not add group %s", group->name ? group->name : "");
348 static struct sipe_group *sipe_group_find_by_id(struct sipe_core_private *sipe_private,
349 int id)
351 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
352 struct sipe_group *group;
353 GSList *entry;
354 if (sip == NULL) {
355 return NULL;
358 entry = sip->groups;
359 while (entry) {
360 group = entry->data;
361 if (group->id == id) {
362 return group;
364 entry = entry->next;
366 return NULL;
369 static struct sipe_group *sipe_group_find_by_name(struct sipe_core_private *sipe_private,
370 const gchar * name)
372 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
373 struct sipe_group *group;
374 GSList *entry;
375 if (!sip || !name) {
376 return NULL;
379 entry = sip->groups;
380 while (entry) {
381 group = entry->data;
382 if (sipe_strequal(group->name, name)) {
383 return group;
385 entry = entry->next;
387 return NULL;
390 static void
391 sipe_group_rename(struct sipe_core_private *sipe_private,
392 struct sipe_group *group,
393 gchar *name)
395 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
396 gchar *body;
397 SIPE_DEBUG_INFO("Renaming group %s to %s", group->name, name);
398 body = g_markup_printf_escaped(SIPE_SOAP_MOD_GROUP, group->id, name, sip->contacts_delta++);
399 send_soap_request(sipe_private, body);
400 g_free(body);
401 g_free(group->name);
402 group->name = g_strdup(name);
406 * Only appends if no such value already stored.
407 * Like Set in Java.
409 GSList * slist_insert_unique_sorted(GSList *list, gpointer data, GCompareFunc func) {
410 GSList * res = list;
411 if (!g_slist_find_custom(list, data, func)) {
412 res = g_slist_insert_sorted(list, data, func);
414 return res;
417 static int
418 sipe_group_compare(struct sipe_group *group1, struct sipe_group *group2) {
419 return group1->id - group2->id;
423 * Returns string like "2 4 7 8" - group ids buddy belong to.
425 static gchar *
426 sipe_get_buddy_groups_string (struct sipe_buddy *buddy) {
427 int i = 0;
428 gchar *res;
429 //creating array from GList, converting int to gchar*
430 gchar **ids_arr = g_new(gchar *, g_slist_length(buddy->groups) + 1);
431 GSList *entry = buddy->groups;
433 if (!ids_arr) return NULL;
435 while (entry) {
436 struct sipe_group * group = entry->data;
437 ids_arr[i] = g_strdup_printf("%d", group->id);
438 entry = entry->next;
439 i++;
441 ids_arr[i] = NULL;
442 res = g_strjoinv(" ", ids_arr);
443 g_strfreev(ids_arr);
444 return res;
448 * Sends buddy update to server
450 void
451 sipe_core_group_set_user(struct sipe_core_public *sipe_public, const gchar * who)
453 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA;
454 struct sipe_buddy *buddy = g_hash_table_lookup(SIPE_CORE_PRIVATE->buddies, who);
455 sipe_backend_buddy backend_buddy = sipe_backend_buddy_find(sipe_public, who, NULL);
457 if (buddy && backend_buddy) {
458 gchar *alias = sipe_backend_buddy_get_alias(sipe_public, backend_buddy);
459 gchar *groups = sipe_get_buddy_groups_string(buddy);
460 if (groups) {
461 gchar *body;
462 SIPE_DEBUG_INFO("Saving buddy %s with alias %s and groups %s", who, alias, groups);
464 body = g_markup_printf_escaped(SIPE_SOAP_SET_CONTACT,
465 alias, groups, "true", buddy->name, sip->contacts_delta++
467 send_soap_request(SIPE_CORE_PRIVATE, body);
468 g_free(groups);
469 g_free(body);
471 g_free(alias);
475 static gboolean process_add_group_response(struct sipe_core_private *sipe_private,
476 struct sipmsg *msg,
477 struct transaction *trans)
479 if (msg->response == 200) {
480 struct sipe_group *group;
481 struct group_user_context *ctx = trans->payload->data;
482 sipe_xml *xml;
483 const sipe_xml *node;
484 char *group_id;
485 struct sipe_buddy *buddy;
487 xml = sipe_xml_parse(msg->body, msg->bodylen);
488 if (!xml) {
489 return FALSE;
492 node = sipe_xml_child(xml, "Body/addGroup/groupID");
493 if (!node) {
494 sipe_xml_free(xml);
495 return FALSE;
498 group_id = sipe_xml_data(node);
499 if (!group_id) {
500 sipe_xml_free(xml);
501 return FALSE;
504 group = g_new0(struct sipe_group, 1);
505 group->id = (int)g_ascii_strtod(group_id, NULL);
506 g_free(group_id);
507 group->name = g_strdup(ctx->group_name);
509 sipe_group_add(sipe_private, group);
511 if (ctx->user_name) {
512 buddy = g_hash_table_lookup(sipe_private->buddies, ctx->user_name);
513 if (buddy) {
514 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
517 sipe_core_group_set_user(SIPE_CORE_PUBLIC, ctx->user_name);
520 sipe_xml_free(xml);
521 return TRUE;
523 return FALSE;
526 static void sipe_group_context_destroy(gpointer data)
528 struct group_user_context *ctx = data;
529 g_free(ctx->group_name);
530 g_free(ctx->user_name);
531 g_free(ctx);
534 static void sipe_group_create (struct sipe_core_private *sipe_private,
535 const gchar *name, const gchar * who)
537 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
538 struct transaction_payload *payload = g_new0(struct transaction_payload, 1);
539 struct group_user_context *ctx = g_new0(struct group_user_context, 1);
540 const gchar *soap_name = sipe_strequal(name, _("Other Contacts")) ? "~" : name;
541 gchar *body;
542 ctx->group_name = g_strdup(name);
543 ctx->user_name = g_strdup(who);
544 payload->destroy = sipe_group_context_destroy;
545 payload->data = ctx;
547 body = g_markup_printf_escaped(SIPE_SOAP_ADD_GROUP, soap_name, sip->contacts_delta++);
548 send_soap_request_with_cb(sipe_private, NULL, body, process_add_group_response, payload);
549 g_free(body);
552 static void
553 sipe_sched_calendar_status_update(struct sipe_core_private *sipe_private,
554 time_t calculate_from);
556 static int
557 sipe_get_availability_by_status(const char* sipe_status_id, char** activity_token);
559 static const char*
560 sipe_get_status_by_availability(int avail,
561 char** activity);
563 static void
564 sipe_set_purple_account_status_and_note(const PurpleAccount *account,
565 const char *status_id,
566 const char *message,
567 time_t do_not_publish[]);
569 static void
570 sipe_apply_calendar_status(struct sipe_core_private *sipe_private,
571 struct sipe_buddy *sbuddy,
572 const char *status_id)
574 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
575 time_t cal_avail_since;
576 int cal_status = sipe_cal_get_status(sbuddy, time(NULL), &cal_avail_since);
577 int avail;
578 gchar *self_uri;
580 if (!sbuddy) return;
582 if (cal_status < SIPE_CAL_NO_DATA) {
583 SIPE_DEBUG_INFO("sipe_apply_calendar_status: cal_status : %d for %s", cal_status, sbuddy->name);
584 SIPE_DEBUG_INFO("sipe_apply_calendar_status: cal_avail_since : %s", asctime(localtime(&cal_avail_since)));
587 /* scheduled Cal update call */
588 if (!status_id) {
589 status_id = sbuddy->last_non_cal_status_id;
590 g_free(sbuddy->activity);
591 sbuddy->activity = g_strdup(sbuddy->last_non_cal_activity);
594 if (!status_id) {
595 SIPE_DEBUG_INFO("sipe_apply_calendar_status: status_id is NULL for %s, exiting.",
596 sbuddy->name ? sbuddy->name : "" );
597 return;
600 /* adjust to calendar status */
601 if (cal_status != SIPE_CAL_NO_DATA) {
602 SIPE_DEBUG_INFO("sipe_apply_calendar_status: user_avail_since: %s", asctime(localtime(&sbuddy->user_avail_since)));
604 if (cal_status == SIPE_CAL_BUSY
605 && cal_avail_since > sbuddy->user_avail_since
606 && 6500 >= sipe_get_availability_by_status(status_id, NULL))
608 status_id = SIPE_STATUS_ID_BUSY;
609 g_free(sbuddy->activity);
610 sbuddy->activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_IN_MEETING));
612 avail = sipe_get_availability_by_status(status_id, NULL);
614 SIPE_DEBUG_INFO("sipe_apply_calendar_status: activity_since : %s", asctime(localtime(&sbuddy->activity_since)));
615 if (cal_avail_since > sbuddy->activity_since) {
616 if (cal_status == SIPE_CAL_OOF
617 && avail >= 15000) /* 12000 in 2007 */
619 g_free(sbuddy->activity);
620 sbuddy->activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_OOF));
625 /* then set status_id actually */
626 SIPE_DEBUG_INFO("sipe_apply_calendar_status: to %s for %s", status_id, sbuddy->name ? sbuddy->name : "" );
627 sipe_backend_buddy_set_status(SIPE_CORE_PUBLIC, sbuddy->name, status_id);
629 /* set our account state to the one in roaming (including calendar info) */
630 self_uri = sip_uri_self(sipe_private);
631 if (sip->initial_state_published && sipe_strcase_equal(sbuddy->name, self_uri)) {
632 if (sipe_strequal(status_id, SIPE_STATUS_ID_OFFLINE)) {
633 status_id = g_strdup(SIPE_STATUS_ID_INVISIBLE); /* not not let offline status switch us off */
636 SIPE_DEBUG_INFO("sipe_apply_calendar_status: switch to '%s' for the account", sip->status);
637 sipe_set_purple_account_status_and_note(sip->account, status_id, sip->note, sip->do_not_publish);
639 g_free(self_uri);
642 void
643 sipe_core_buddy_got_status(struct sipe_core_public *sipe_public,
644 const gchar* uri,
645 const gchar *status_id)
647 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
648 struct sipe_buddy *sbuddy = g_hash_table_lookup(sipe_private->buddies, uri);
650 if (!sbuddy) return;
652 /* Check if on 2005 system contact's calendar,
653 * then set/preserve it.
655 if (!SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
656 sipe_apply_calendar_status(sipe_private, sbuddy, status_id);
657 } else {
658 sipe_backend_buddy_set_status(sipe_public, uri, status_id);
662 static void
663 update_calendar_status_cb(SIPE_UNUSED_PARAMETER char *name,
664 struct sipe_buddy *sbuddy,
665 struct sipe_core_private *sipe_private)
667 sipe_apply_calendar_status(sipe_private, sbuddy, NULL);
671 * Updates contact's status
672 * based on their calendar information.
674 * Applicability: 2005 systems
676 static void
677 update_calendar_status(struct sipe_core_private *sipe_private,
678 SIPE_UNUSED_PARAMETER void *unused)
680 SIPE_DEBUG_INFO_NOFORMAT("update_calendar_status() started.");
681 g_hash_table_foreach(sipe_private->buddies, (GHFunc)update_calendar_status_cb, sipe_private);
683 /* repeat scheduling */
684 sipe_sched_calendar_status_update(sipe_private, time(NULL) + 3*60 /* 3 min */);
688 * Schedules process of contacts' status update
689 * based on their calendar information.
690 * Should be scheduled to the beginning of every
691 * 15 min interval, like:
692 * 13:00, 13:15, 13:30, 13:45, etc.
694 * Applicability: 2005 systems
696 static void
697 sipe_sched_calendar_status_update(struct sipe_core_private *sipe_private,
698 time_t calculate_from)
700 int interval = 15*60;
701 /** start of the beginning of closest 15 min interval. */
702 time_t next_start = ((time_t)((int)((int)calculate_from)/interval + 1)*interval);
704 SIPE_DEBUG_INFO("sipe_sched_calendar_status_update: calculate_from time: %s",
705 asctime(localtime(&calculate_from)));
706 SIPE_DEBUG_INFO("sipe_sched_calendar_status_update: next start time : %s",
707 asctime(localtime(&next_start)));
709 sipe_schedule_seconds(sipe_private,
710 "<+2005-cal-status>",
711 NULL,
712 next_start - time(NULL),
713 update_calendar_status,
714 NULL);
718 * Schedules process of self status publish
719 * based on own calendar information.
720 * Should be scheduled to the beginning of every
721 * 15 min interval, like:
722 * 13:00, 13:15, 13:30, 13:45, etc.
724 * Applicability: 2007+ systems
726 static void
727 sipe_sched_calendar_status_self_publish(struct sipe_core_private *sipe_private,
728 time_t calculate_from)
730 int interval = 5*60;
731 /** start of the beginning of closest 5 min interval. */
732 time_t next_start = ((time_t)((int)((int)calculate_from)/interval + 1)*interval);
734 SIPE_DEBUG_INFO("sipe_sched_calendar_status_self_publish: calculate_from time: %s",
735 asctime(localtime(&calculate_from)));
736 SIPE_DEBUG_INFO("sipe_sched_calendar_status_self_publish: next start time : %s",
737 asctime(localtime(&next_start)));
739 sipe_schedule_seconds(sipe_private,
740 "<+2007-cal-status>",
741 NULL,
742 next_start - time(NULL),
743 publish_calendar_status_self,
744 NULL);
747 static void sipe_subscribe_resource_uri(const char *name,
748 SIPE_UNUSED_PARAMETER gpointer value,
749 gchar **resources_uri)
751 gchar *tmp = *resources_uri;
752 *resources_uri = g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp, name);
753 g_free(tmp);
756 static void sipe_subscribe_resource_uri_with_context(const char *name, gpointer value, gchar **resources_uri)
758 struct sipe_buddy *sbuddy = (struct sipe_buddy *)value;
759 gchar *context = sbuddy && sbuddy->just_added ? "><context/></resource>" : "/>";
760 gchar *tmp = *resources_uri;
762 if (sbuddy) sbuddy->just_added = FALSE; /* should be enought to include context one time */
764 *resources_uri = g_strdup_printf("%s<resource uri=\"%s\"%s\n", tmp, name, context);
765 g_free(tmp);
769 * Support for Batch Category SUBSCRIBE [MS-PRES] - msrtc-event-categories+xml OCS 2007
770 * Support for Batch Category SUBSCRIBE [MS-SIP] - adrl+xml LCS 2005
771 * The user sends an initial batched category SUBSCRIBE request against all contacts on his roaming list in only a request
772 * A batch category SUBSCRIBE request MUST have the same To-URI and From-URI.
773 * This header will be send only if adhoclist there is a "Supported: adhoclist" in REGISTER answer else will be send a Single Category SUBSCRIBE
776 static void sipe_subscribe_presence_batched_to(struct sipe_core_private *sipe_private,
777 gchar *resources_uri,
778 gchar *to)
780 gchar *contact = get_contact(sipe_private);
781 gchar *request;
782 gchar *content;
783 gchar *require = "";
784 gchar *accept = "";
785 gchar *autoextend = "";
786 gchar *content_type;
788 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
789 require = ", categoryList";
790 accept = ", application/msrtc-event-categories+xml, application/xpidf+xml, application/pidf+xml";
791 content_type = "application/msrtc-adrl-categorylist+xml";
792 content = g_strdup_printf(
793 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
794 "<action name=\"subscribe\" id=\"63792024\">\n"
795 "<adhocList>\n%s</adhocList>\n"
796 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
797 "<category name=\"calendarData\"/>\n"
798 "<category name=\"contactCard\"/>\n"
799 "<category name=\"note\"/>\n"
800 "<category name=\"state\"/>\n"
801 "</categoryList>\n"
802 "</action>\n"
803 "</batchSub>", sipe_private->username, resources_uri);
804 } else {
805 autoextend = "Supported: com.microsoft.autoextend\r\n";
806 content_type = "application/adrl+xml";
807 content = g_strdup_printf(
808 "<adhoclist xmlns=\"urn:ietf:params:xml:ns:adrl\" uri=\"sip:%s\" name=\"sip:%s\">\n"
809 "<create xmlns=\"\">\n%s</create>\n"
810 "</adhoclist>\n", sipe_private->username, sipe_private->username, resources_uri);
812 g_free(resources_uri);
814 request = g_strdup_printf(
815 "Require: adhoclist%s\r\n"
816 "Supported: eventlist\r\n"
817 "Accept: application/rlmi+xml, multipart/related, text/xml+msrtc.pidf%s\r\n"
818 "Supported: ms-piggyback-first-notify\r\n"
819 "%sSupported: ms-benotify\r\n"
820 "Proxy-Require: ms-benotify\r\n"
821 "Event: presence\r\n"
822 "Content-Type: %s\r\n"
823 "Contact: %s\r\n", require, accept, autoextend, content_type, contact);
824 g_free(contact);
826 sipe_subscribe_presence_buddy(sipe_private, to, request, content);
828 g_free(content);
829 g_free(to);
830 g_free(request);
833 static void sipe_subscribe_presence_batched(struct sipe_core_private *sipe_private,
834 SIPE_UNUSED_PARAMETER void *unused)
836 gchar *to = sip_uri_self(sipe_private);
837 gchar *resources_uri = g_strdup("");
838 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
839 g_hash_table_foreach(sipe_private->buddies, (GHFunc) sipe_subscribe_resource_uri_with_context , &resources_uri);
840 } else {
841 g_hash_table_foreach(sipe_private->buddies, (GHFunc) sipe_subscribe_resource_uri, &resources_uri);
844 sipe_subscribe_presence_batched_to(sipe_private, resources_uri, to);
847 struct presence_batched_routed {
848 gchar *host;
849 GSList *buddies;
852 static void sipe_subscribe_presence_batched_routed_free(void *payload)
854 struct presence_batched_routed *data = payload;
855 GSList *buddies = data->buddies;
856 while (buddies) {
857 g_free(buddies->data);
858 buddies = buddies->next;
860 g_slist_free(data->buddies);
861 g_free(data->host);
862 g_free(payload);
865 static void sipe_subscribe_presence_batched_routed(struct sipe_core_private *sipe_private,
866 void *payload)
868 struct presence_batched_routed *data = payload;
869 GSList *buddies = data->buddies;
870 gchar *resources_uri = g_strdup("");
871 while (buddies) {
872 gchar *tmp = resources_uri;
873 resources_uri = g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp, (char *) buddies->data);
874 g_free(tmp);
875 buddies = buddies->next;
877 sipe_subscribe_presence_batched_to(sipe_private, resources_uri,
878 g_strdup(data->host));
882 * Single Category SUBSCRIBE [MS-PRES] ; To send when the server returns a 200 OK message with state="resubscribe" in response.
883 * The user sends a single SUBSCRIBE request to the subscribed contact.
884 * The To-URI and the URI listed in the resource list MUST be the same for a single category SUBSCRIBE request.
888 static void sipe_subscribe_presence_single(struct sipe_core_private *sipe_private,
889 void *buddy_name)
891 gchar *to = sip_uri((char *)buddy_name);
892 gchar *tmp = get_contact(sipe_private);
893 gchar *request;
894 gchar *content = NULL;
895 gchar *autoextend = "";
896 gchar *content_type = "";
897 struct sipe_buddy *sbuddy = g_hash_table_lookup(sipe_private->buddies, to);
898 gchar *context = sbuddy && sbuddy->just_added ? "><context/></resource>" : "/>";
900 if (sbuddy) sbuddy->just_added = FALSE;
902 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
903 content_type = "Content-Type: application/msrtc-adrl-categorylist+xml\r\n";
904 } else {
905 autoextend = "Supported: com.microsoft.autoextend\r\n";
908 request = g_strdup_printf(
909 "Accept: application/msrtc-event-categories+xml, text/xml+msrtc.pidf, application/xpidf+xml, application/pidf+xml, application/rlmi+xml, multipart/related\r\n"
910 "Supported: ms-piggyback-first-notify\r\n"
911 "%s%sSupported: ms-benotify\r\n"
912 "Proxy-Require: ms-benotify\r\n"
913 "Event: presence\r\n"
914 "Contact: %s\r\n", autoextend, content_type, tmp);
916 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
917 content = g_strdup_printf(
918 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
919 "<action name=\"subscribe\" id=\"63792024\"><adhocList>\n"
920 "<resource uri=\"%s\"%s\n"
921 "</adhocList>\n"
922 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
923 "<category name=\"calendarData\"/>\n"
924 "<category name=\"contactCard\"/>\n"
925 "<category name=\"note\"/>\n"
926 "<category name=\"state\"/>\n"
927 "</categoryList>\n"
928 "</action>\n"
929 "</batchSub>", sipe_private->username, to, context);
932 g_free(tmp);
934 sipe_subscribe_presence_buddy(sipe_private, to, request, content);
936 g_free(content);
937 g_free(to);
938 g_free(request);
941 void sipe_set_status(PurpleAccount *account, PurpleStatus *status)
943 SIPE_DEBUG_INFO("sipe_set_status: status=%s", purple_status_get_id(status));
945 if (!purple_status_is_active(status))
946 return;
948 if (account->gc) {
949 struct sipe_core_private *sipe_private = PURPLE_ACCOUNT_TO_SIPE_CORE_PRIVATE;
950 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
952 if (sip) {
953 gchar *action_name;
954 gchar *tmp;
955 time_t now = time(NULL);
956 const char *status_id = purple_status_get_id(status);
957 const char *note = purple_status_get_attr_string(status, SIPE_STATUS_ATTR_ID_MESSAGE);
958 sipe_activity activity = sipe_get_activity_by_token(status_id);
959 gboolean do_not_publish = ((now - sip->do_not_publish[activity]) <= 2);
961 /* when other point of presence clears note, but we are keeping
962 * state if OOF note.
964 if (do_not_publish && !note && sip->cal && sip->cal->oof_note) {
965 SIPE_DEBUG_INFO_NOFORMAT("sipe_set_status: enabling publication as OOF note keepers.");
966 do_not_publish = FALSE;
969 SIPE_DEBUG_INFO("sipe_set_status: was: sip->do_not_publish[%s]=%d [?] now(time)=%d",
970 status_id, (int)sip->do_not_publish[activity], (int)now);
972 sip->do_not_publish[activity] = 0;
973 SIPE_DEBUG_INFO("sipe_set_status: set: sip->do_not_publish[%s]=%d [0]",
974 status_id, (int)sip->do_not_publish[activity]);
976 if (do_not_publish)
978 SIPE_DEBUG_INFO_NOFORMAT("sipe_set_status: publication was switched off, exiting.");
979 return;
982 g_free(sip->status);
983 sip->status = g_strdup(status_id);
985 /* hack to escape apostrof before comparison */
986 tmp = note ? sipe_utils_str_replace(note, "'", "&apos;") : NULL;
988 /* this will preserve OOF flag as well */
989 if (!sipe_strequal(tmp, sip->note)) {
990 sip->is_oof_note = FALSE;
991 g_free(sip->note);
992 sip->note = g_strdup(note);
993 sip->note_since = time(NULL);
995 g_free(tmp);
997 /* schedule 2 sec to capture idle flag */
998 action_name = g_strdup_printf("<%s>", "+set-status");
999 sipe_schedule_seconds(sipe_private,
1000 action_name,
1001 NULL,
1002 SIPE_IDLE_SET_DELAY,
1003 send_presence_status,
1004 NULL);
1005 g_free(action_name);
1010 void
1011 sipe_set_idle(PurpleConnection * gc,
1012 int interval)
1014 SIPE_DEBUG_INFO("sipe_set_idle: interval=%d", interval);
1016 if (gc) {
1017 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
1018 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
1020 if (sip) {
1021 sip->idle_switch = time(NULL);
1022 SIPE_DEBUG_INFO("sipe_set_idle: sip->idle_switch : %s", asctime(localtime(&(sip->idle_switch))));
1027 void
1028 sipe_group_buddy(PurpleConnection *gc,
1029 const char *who,
1030 const char *old_group_name,
1031 const char *new_group_name)
1033 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
1034 struct sipe_buddy * buddy = g_hash_table_lookup(sipe_private->buddies, who);
1035 struct sipe_group * old_group = NULL;
1036 struct sipe_group * new_group;
1038 SIPE_DEBUG_INFO("sipe_group_buddy[CB]: who:%s old_group_name:%s new_group_name:%s",
1039 who ? who : "", old_group_name ? old_group_name : "", new_group_name ? new_group_name : "");
1041 if(!buddy) { // buddy not in roaming list
1042 return;
1045 if (old_group_name) {
1046 old_group = sipe_group_find_by_name(sipe_private, old_group_name);
1048 new_group = sipe_group_find_by_name(sipe_private, new_group_name);
1050 if (old_group) {
1051 buddy->groups = g_slist_remove(buddy->groups, old_group);
1052 SIPE_DEBUG_INFO("buddy %s removed from old group %s", who, old_group_name);
1055 if (!new_group) {
1056 sipe_group_create(sipe_private, new_group_name, who);
1057 } else {
1058 buddy->groups = slist_insert_unique_sorted(buddy->groups, new_group, (GCompareFunc)sipe_group_compare);
1059 sipe_core_group_set_user(SIPE_CORE_PUBLIC, who);
1063 void sipe_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
1065 SIPE_DEBUG_INFO("sipe_add_buddy[CB]: buddy:%s group:%s", buddy ? buddy->name : "", group ? group->name : "");
1067 /* libpurple can call us with undefined buddy or group */
1068 if (buddy && group) {
1069 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
1071 /* Buddy name must be lower case as we use purple_normalize_nocase() to compare */
1072 gchar *buddy_name = g_ascii_strdown(buddy->name, -1);
1073 purple_blist_rename_buddy(buddy, buddy_name);
1074 g_free(buddy_name);
1076 /* Prepend sip: if needed */
1077 if (!g_str_has_prefix(buddy->name, "sip:")) {
1078 gchar *buf = sip_uri_from_name(buddy->name);
1079 purple_blist_rename_buddy(buddy, buf);
1080 g_free(buf);
1083 if (!g_hash_table_lookup(sipe_private->buddies, buddy->name)) {
1084 struct sipe_buddy *b = g_new0(struct sipe_buddy, 1);
1085 SIPE_DEBUG_INFO("sipe_add_buddy: adding %s", buddy->name);
1086 b->name = g_strdup(buddy->name);
1087 b->just_added = TRUE;
1088 g_hash_table_insert(sipe_private->buddies, b->name, b);
1089 /* @TODO should go to callback */
1090 sipe_subscribe_presence_single(sipe_private,
1091 b->name);
1092 } else {
1093 SIPE_DEBUG_INFO("sipe_add_buddy: buddy %s already in internal list", buddy->name);
1096 sipe_group_buddy(gc, buddy->name, NULL, group->name);
1100 static void sipe_free_buddy(struct sipe_buddy *buddy)
1102 #ifndef _WIN32
1104 * We are calling g_hash_table_foreach_steal(). That means that no
1105 * key/value deallocation functions are called. Therefore the glib
1106 * hash code does not touch the key (buddy->name) or value (buddy)
1107 * of the to-be-deleted hash node at all. It follows that we
1109 * - MUST free the memory for the key ourselves and
1110 * - ARE allowed to do it in this function
1112 * Conclusion: glib must be broken on the Windows platform if sipe
1113 * crashes with SIGTRAP when closing. You'll have to live
1114 * with the memory leak until this is fixed.
1116 g_free(buddy->name);
1117 #endif
1118 g_free(buddy->activity);
1119 g_free(buddy->meeting_subject);
1120 g_free(buddy->meeting_location);
1121 g_free(buddy->note);
1123 g_free(buddy->cal_start_time);
1124 g_free(buddy->cal_free_busy_base64);
1125 g_free(buddy->cal_free_busy);
1126 g_free(buddy->last_non_cal_activity);
1128 sipe_cal_free_working_hours(buddy->cal_working_hours);
1130 g_free(buddy->device_name);
1131 g_slist_free(buddy->groups);
1132 g_free(buddy);
1136 * Unassociates buddy from group first.
1137 * Then see if no groups left, removes buddy completely.
1138 * Otherwise updates buddy groups on server.
1140 void sipe_remove_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
1142 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
1143 struct sipe_buddy *b;
1144 struct sipe_group *g = NULL;
1146 SIPE_DEBUG_INFO("sipe_remove_buddy[CB]: buddy:%s group:%s", buddy ? buddy->name : "", group ? group->name : "");
1147 if (!buddy) return;
1149 b = g_hash_table_lookup(sipe_private->buddies, buddy->name);
1150 if (!b) return;
1152 if (group) {
1153 g = sipe_group_find_by_name(sipe_private, group->name);
1156 if (g) {
1157 b->groups = g_slist_remove(b->groups, g);
1158 SIPE_DEBUG_INFO("buddy %s removed from group %s", buddy->name, g->name);
1161 if (g_slist_length(b->groups) < 1) {
1162 gchar *action_name = sipe_utils_presence_key(buddy->name);
1163 sipe_schedule_cancel(sipe_private, action_name);
1164 g_free(action_name);
1166 g_hash_table_remove(sipe_private->buddies, buddy->name);
1168 if (b->name) {
1169 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
1170 gchar * body = g_strdup_printf(SIPE_SOAP_DEL_CONTACT, b->name, sip->contacts_delta++);
1171 send_soap_request(sipe_private, body);
1172 g_free(body);
1175 sipe_free_buddy(b);
1176 } else {
1177 //updates groups on server
1178 sipe_core_group_set_user(SIPE_CORE_PUBLIC, b->name);
1183 void
1184 sipe_rename_group(PurpleConnection *gc,
1185 const char *old_name,
1186 PurpleGroup *group,
1187 SIPE_UNUSED_PARAMETER GList *moved_buddies)
1189 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
1190 struct sipe_group * s_group = sipe_group_find_by_name(sipe_private, old_name);
1191 if (s_group) {
1192 sipe_group_rename(sipe_private, s_group, group->name);
1193 } else {
1194 SIPE_DEBUG_INFO("Cannot find group %s to rename", old_name);
1198 void
1199 sipe_remove_group(PurpleConnection *gc, PurpleGroup *group)
1201 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
1202 struct sipe_group * s_group = sipe_group_find_by_name(sipe_private, group->name);
1203 if (s_group) {
1204 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
1205 gchar *body;
1206 SIPE_DEBUG_INFO("Deleting group %s", group->name);
1207 body = g_strdup_printf(SIPE_SOAP_DEL_GROUP, s_group->id, sip->contacts_delta++);
1208 send_soap_request(sipe_private, body);
1209 g_free(body);
1211 sip->groups = g_slist_remove(sip->groups, s_group);
1212 g_free(s_group->name);
1213 g_free(s_group);
1214 } else {
1215 SIPE_DEBUG_INFO("Cannot find group %s to delete", group->name);
1220 * A callback for g_hash_table_foreach
1222 static void
1223 sipe_buddy_subscribe_cb(char *buddy_name,
1224 SIPE_UNUSED_PARAMETER struct sipe_buddy *buddy,
1225 struct sipe_core_private *sipe_private)
1227 gchar *action_name = sipe_utils_presence_key(buddy_name);
1228 /* g_hash_table_size() can never return 0, otherwise this function wouldn't be called :-) */
1229 guint time_range = (g_hash_table_size(sipe_private->buddies) * 1000) / 25; /* time interval for 25 requests per sec. In msec. */
1230 guint timeout = ((guint) rand()) / (RAND_MAX / time_range) + 1; /* random period within the range but never 0! */
1232 sipe_schedule_mseconds(sipe_private,
1233 action_name,
1234 g_strdup(buddy_name),
1235 timeout,
1236 sipe_subscribe_presence_single,
1237 g_free);
1238 g_free(action_name);
1242 * Removes entries from local buddy list
1243 * that does not correspond ones in the roaming contact list.
1245 static void sipe_cleanup_local_blist(struct sipe_core_private *sipe_private) {
1246 GSList *buddies = sipe_backend_buddy_find_all(SIPE_CORE_PUBLIC, NULL, NULL);
1247 GSList *entry = buddies;
1248 struct sipe_buddy *buddy;
1249 sipe_backend_buddy b;
1250 gchar *bname;
1251 gchar *gname;
1253 SIPE_DEBUG_INFO("sipe_cleanup_local_blist: overall %d backend buddies (including clones)", g_slist_length(buddies));
1254 SIPE_DEBUG_INFO("sipe_cleanup_local_blist: %d sipe buddies (unique)", g_hash_table_size(sipe_private->buddies));
1255 while (entry) {
1256 b = entry->data;
1257 gname = sipe_backend_buddy_get_group_name(SIPE_CORE_PUBLIC, b);
1258 bname = sipe_backend_buddy_get_name(SIPE_CORE_PUBLIC, b);
1259 buddy = g_hash_table_lookup(sipe_private->buddies, bname);
1260 g_free(bname);
1261 if(buddy) {
1262 gboolean in_sipe_groups = FALSE;
1263 GSList *entry2 = buddy->groups;
1264 while (entry2) {
1265 struct sipe_group *group = entry2->data;
1266 if (sipe_strequal(group->name, gname)) {
1267 in_sipe_groups = TRUE;
1268 break;
1270 entry2 = entry2->next;
1272 if(!in_sipe_groups) {
1273 SIPE_DEBUG_INFO("*** REMOVING %s from blist group: %s as not having this group in roaming list", bname, gname);
1274 sipe_backend_buddy_remove(SIPE_CORE_PUBLIC, b);
1276 } else {
1277 SIPE_DEBUG_INFO("*** REMOVING %s from blist group: %s as this buddy not in roaming list", bname, gname);
1278 sipe_backend_buddy_remove(SIPE_CORE_PUBLIC, b);
1280 g_free(gname);
1281 entry = entry->next;
1283 g_slist_free(buddies);
1286 static int
1287 sipe_find_access_level(struct sipe_core_private *sipe_private,
1288 const gchar *type,
1289 const gchar *value,
1290 gboolean *is_group_access);
1292 static void
1293 sipe_refresh_blocked_status_cb(char *buddy_name,
1294 SIPE_UNUSED_PARAMETER struct sipe_buddy *buddy,
1295 struct sipe_core_private *sipe_private)
1297 int container_id = sipe_find_access_level(sipe_private, "user", buddy_name, NULL);
1298 gboolean blocked = (container_id == 32000);
1299 gboolean blocked_in_blist = sipe_backend_buddy_is_blocked(SIPE_CORE_PUBLIC, buddy_name);
1301 /* SIPE_DEBUG_INFO("sipe_refresh_blocked_status_cb: buddy_name=%s, blocked=%s, blocked_in_blist=%s",
1302 buddy_name, blocked ? "T" : "F", blocked_in_blist ? "T" : "F"); */
1304 if (blocked != blocked_in_blist) {
1305 sipe_backend_buddy_set_blocked_status(SIPE_CORE_PUBLIC, buddy_name, blocked);
1309 static void
1310 sipe_refresh_blocked_status(struct sipe_core_private *sipe_private)
1312 g_hash_table_foreach(sipe_private->buddies,
1313 (GHFunc) sipe_refresh_blocked_status_cb,
1314 sipe_private);
1317 static gboolean sipe_process_roaming_contacts(struct sipe_core_private *sipe_private,
1318 struct sipmsg *msg)
1320 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
1321 int len = msg->bodylen;
1323 const gchar *tmp = sipmsg_find_header(msg, "Event");
1324 const sipe_xml *item;
1325 sipe_xml *isc;
1326 const gchar *contacts_delta;
1327 const sipe_xml *group_node;
1328 if (!g_str_has_prefix(tmp, "vnd-microsoft-roaming-contacts")) {
1329 return FALSE;
1332 /* Convert the contact from XML to backend Buddies */
1333 isc = sipe_xml_parse(msg->body, len);
1334 if (!isc) {
1335 return FALSE;
1338 contacts_delta = sipe_xml_attribute(isc, "deltaNum");
1339 if (contacts_delta) {
1340 sip->contacts_delta = (int)g_ascii_strtod(contacts_delta, NULL);
1343 if (sipe_strequal(sipe_xml_name(isc), "contactList")) {
1345 /* Parse groups */
1346 for (group_node = sipe_xml_child(isc, "group"); group_node; group_node = sipe_xml_twin(group_node)) {
1347 struct sipe_group * group = g_new0(struct sipe_group, 1);
1348 const char *name = sipe_xml_attribute(group_node, "name");
1350 if (g_str_has_prefix(name, "~")) {
1351 name = _("Other Contacts");
1353 group->name = g_strdup(name);
1354 group->id = (int)g_ascii_strtod(sipe_xml_attribute(group_node, "id"), NULL);
1356 sipe_group_add(sipe_private, group);
1359 // Make sure we have at least one group
1360 if (g_slist_length(sip->groups) == 0) {
1361 sipe_group_create(sipe_private, _("Other Contacts"), NULL);
1364 /* Parse contacts */
1365 for (item = sipe_xml_child(isc, "contact"); item; item = sipe_xml_twin(item)) {
1366 const gchar *uri = sipe_xml_attribute(item, "uri");
1367 const gchar *name = sipe_xml_attribute(item, "name");
1368 gchar *buddy_name;
1369 struct sipe_buddy *buddy = NULL;
1370 gchar *tmp;
1371 gchar **item_groups;
1372 int i = 0;
1374 /* Buddy name must be lower case as we use purple_normalize_nocase() to compare */
1375 tmp = sip_uri_from_name(uri);
1376 buddy_name = g_ascii_strdown(tmp, -1);
1377 g_free(tmp);
1379 /* assign to group Other Contacts if nothing else received */
1380 tmp = g_strdup(sipe_xml_attribute(item, "groups"));
1381 if(is_empty(tmp)) {
1382 struct sipe_group *group = sipe_group_find_by_name(sipe_private, _("Other Contacts"));
1383 g_free(tmp);
1384 tmp = group ? g_strdup_printf("%d", group->id) : g_strdup("1");
1386 item_groups = g_strsplit(tmp, " ", 0);
1387 g_free(tmp);
1389 while (item_groups[i]) {
1390 struct sipe_group *group = sipe_group_find_by_id(sipe_private, g_ascii_strtod(item_groups[i], NULL));
1392 // If couldn't find the right group for this contact, just put them in the first group we have
1393 if (group == NULL && g_slist_length(sip->groups) > 0) {
1394 group = sip->groups->data;
1397 if (group != NULL) {
1398 gchar *b_alias;
1399 sipe_backend_buddy b = sipe_backend_buddy_find(SIPE_CORE_PUBLIC, buddy_name, group->name);
1400 if (!b){
1401 b = sipe_backend_buddy_add(SIPE_CORE_PUBLIC, buddy_name, uri, group->name);
1402 SIPE_DEBUG_INFO("Created new buddy %s with alias %s", buddy_name, uri);
1405 b_alias = sipe_backend_buddy_get_alias(SIPE_CORE_PUBLIC, b);
1406 if (sipe_strcase_equal(uri, b_alias)) {
1407 if (name != NULL && strlen(name) != 0) {
1408 sipe_backend_buddy_set_alias(SIPE_CORE_PUBLIC, b, name);
1410 SIPE_DEBUG_INFO("Replaced buddy %s alias with %s", buddy_name, name);
1413 g_free(b_alias);
1415 if (!buddy) {
1416 buddy = g_new0(struct sipe_buddy, 1);
1417 buddy->name = sipe_backend_buddy_get_name(SIPE_CORE_PUBLIC, b);
1418 g_hash_table_insert(sipe_private->buddies, buddy->name, buddy);
1420 SIPE_DEBUG_INFO("Added SIPE buddy %s", buddy->name);
1423 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
1425 SIPE_DEBUG_INFO("Added buddy %s to group %s", buddy->name, group->name);
1426 } else {
1427 SIPE_DEBUG_INFO("No group found for contact %s! Unable to add to buddy list",
1428 name);
1431 i++;
1432 } // while, contact groups
1433 g_strfreev(item_groups);
1434 g_free(buddy_name);
1436 } // for, contacts
1438 sipe_cleanup_local_blist(sipe_private);
1440 /* Add self-contact if not there yet. 2005 systems. */
1441 /* This will resemble subscription to roaming_self in 2007 systems */
1442 if (!SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
1443 gchar *self_uri = sip_uri_self(sipe_private);
1444 struct sipe_buddy *buddy = g_hash_table_lookup(sipe_private->buddies, self_uri);
1446 if (!buddy) {
1447 buddy = g_new0(struct sipe_buddy, 1);
1448 buddy->name = g_strdup(self_uri);
1449 g_hash_table_insert(sipe_private->buddies, buddy->name, buddy);
1451 g_free(self_uri);
1454 sipe_xml_free(isc);
1456 /* subscribe to buddies */
1457 if (!sip->subscribed_buddies) { //do it once, then count Expire field to schedule resubscribe.
1458 if (sip->batched_support) {
1459 sipe_subscribe_presence_batched(sipe_private, NULL);
1460 } else {
1461 g_hash_table_foreach(sipe_private->buddies,
1462 (GHFunc)sipe_buddy_subscribe_cb,
1463 sipe_private);
1465 sip->subscribed_buddies = TRUE;
1467 /* for 2005 systems schedule contacts' status update
1468 * based on their calendar information
1470 if (!SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
1471 sipe_sched_calendar_status_update(sipe_private, time(NULL));
1474 return 0;
1478 * Fires on deregistration event initiated by server.
1479 * [MS-SIPREGE] SIP extension.
1482 // 2007 Example
1484 // Content-Type: text/registration-event
1485 // subscription-state: terminated;expires=0
1486 // ms-diagnostics-public: 4141;reason="User disabled"
1488 // deregistered;event=rejected
1490 static void sipe_process_registration_notify(struct sipe_core_private *sipe_private,
1491 struct sipmsg *msg)
1493 const gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
1494 gchar *event = NULL;
1495 gchar *reason = NULL;
1496 const gchar *diagnostics = sipmsg_find_header(msg, "ms-diagnostics");
1497 gchar *warning;
1499 diagnostics = diagnostics ? diagnostics : sipmsg_find_header(msg, "ms-diagnostics-public");
1500 SIPE_DEBUG_INFO_NOFORMAT("sipe_process_registration_notify: deregistration received.");
1502 if (!g_ascii_strncasecmp(contenttype, "text/registration-event", 23)) {
1503 event = sipmsg_find_part_of_header(msg->body, "event=", NULL, NULL);
1504 //@TODO have proper parameter extraction _by_name_ func, case insesitive.
1505 event = event ? event : sipmsg_find_part_of_header(msg->body, "event=", ";", NULL);
1506 } else {
1507 SIPE_DEBUG_INFO_NOFORMAT("sipe_process_registration_notify: unknown content type, exiting.");
1508 return;
1511 if (diagnostics != NULL) {
1512 reason = sipmsg_find_part_of_header(diagnostics, "reason=\"", "\"", NULL);
1513 } else { // for LCS2005
1514 if (event && sipe_strcase_equal(event, "unregistered")) {
1515 //reason = g_strdup(_("User logged out")); // [MS-OCER]
1516 reason = g_strdup(_("you are already signed in at another location"));
1517 } else if (event && sipe_strcase_equal(event, "rejected")) {
1518 reason = g_strdup(_("user disabled")); // [MS-OCER]
1519 } else if (event && sipe_strcase_equal(event, "deactivated")) {
1520 reason = g_strdup(_("user moved")); // [MS-OCER]
1523 g_free(event);
1524 warning = g_strdup_printf(_("You have been rejected by the server: %s"), reason ? reason : _("no reason given"));
1525 g_free(reason);
1527 sipe_backend_connection_error(SIPE_CORE_PUBLIC,
1528 SIPE_CONNECTION_ERROR_INVALID_USERNAME,
1529 warning);
1530 g_free(warning);
1534 static void sipe_process_provisioning_v2(struct sipe_core_private *sipe_private,
1535 struct sipmsg *msg)
1537 sipe_xml *xn_provision_group_list;
1538 const sipe_xml *node;
1540 xn_provision_group_list = sipe_xml_parse(msg->body, msg->bodylen);
1542 /* provisionGroup */
1543 for (node = sipe_xml_child(xn_provision_group_list, "provisionGroup"); node; node = sipe_xml_twin(node)) {
1544 if (sipe_strequal("ServerConfiguration", sipe_xml_attribute(node, "name"))) {
1545 g_free(sipe_private->focus_factory_uri);
1546 sipe_private->focus_factory_uri = sipe_xml_data(sipe_xml_child(node, "focusFactoryUri"));
1547 SIPE_DEBUG_INFO("sipe_process_provisioning_v2: sipe_private->focus_factory_uri=%s",
1548 sipe_private->focus_factory_uri ? sipe_private->focus_factory_uri : "");
1550 #ifdef HAVE_VV
1551 g_free(sipe_private->mras_uri);
1552 sipe_private->mras_uri = g_strstrip(sipe_xml_data(sipe_xml_child(node, "mrasUri")));
1553 SIPE_DEBUG_INFO("sipe_process_provisioning_v2: sipe_private->mras_uri=%s",
1554 sipe_private->mras_uri ? sipe_private->mras_uri : "");
1556 if (sipe_private->mras_uri)
1557 sipe_media_get_av_edge_credentials(sipe_private);
1558 #endif
1559 break;
1562 sipe_xml_free(xn_provision_group_list);
1565 /** for 2005 system */
1566 static void
1567 sipe_process_provisioning(struct sipe_core_private *sipe_private,
1568 struct sipmsg *msg)
1570 sipe_xml *xn_provision;
1571 const sipe_xml *node;
1573 xn_provision = sipe_xml_parse(msg->body, msg->bodylen);
1574 if ((node = sipe_xml_child(xn_provision, "user"))) {
1575 SIPE_DEBUG_INFO("sipe_process_provisioning: uri=%s", sipe_xml_attribute(node, "uri"));
1576 if ((node = sipe_xml_child(node, "line"))) {
1577 const gchar *line_uri = sipe_xml_attribute(node, "uri");
1578 const gchar *server = sipe_xml_attribute(node, "server");
1579 SIPE_DEBUG_INFO("sipe_process_provisioning: line_uri=%s server=%s", line_uri, server);
1580 sip_csta_open(sipe_private, line_uri, server);
1583 sipe_xml_free(xn_provision);
1586 static void sipe_process_roaming_acl(struct sipe_core_private *sipe_private,
1587 struct sipmsg *msg)
1589 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
1590 const gchar *contacts_delta;
1591 sipe_xml *xml;
1593 xml = sipe_xml_parse(msg->body, msg->bodylen);
1594 if (!xml)
1596 return;
1599 contacts_delta = sipe_xml_attribute(xml, "deltaNum");
1600 if (contacts_delta)
1602 sip->acl_delta = (int)g_ascii_strtod(contacts_delta, NULL);
1605 sipe_xml_free(xml);
1608 /** MS-PRES container */
1609 struct sipe_container {
1610 guint id;
1611 guint version;
1612 GSList *members;
1614 /** MS-PRES container member */
1615 struct sipe_container_member {
1616 /** user, domain, sameEnterprise, federated, publicCloud; everyone */
1617 gchar *type;
1618 gchar *value;
1621 static void
1622 free_container_member(struct sipe_container_member *member)
1624 if (!member) return;
1626 g_free(member->type);
1627 g_free(member->value);
1628 g_free(member);
1631 static void
1632 free_container(struct sipe_container *container)
1634 GSList *entry;
1636 if (!container) return;
1638 entry = container->members;
1639 while (entry) {
1640 void *data = entry->data;
1641 entry = g_slist_remove(entry, data);
1642 free_container_member((struct sipe_container_member *)data);
1644 g_free(container);
1647 static void
1648 sipe_send_container_members_prepare(const guint container_id,
1649 const guint container_version,
1650 const gchar *action,
1651 const gchar *type,
1652 const gchar *value,
1653 char **container_xmls)
1655 gchar *value_str = value ? g_strdup_printf(" value=\"%s\"", value) : g_strdup("");
1656 gchar *body;
1658 if (!container_xmls) return;
1660 body = g_strdup_printf(
1661 "<container id=\"%d\" version=\"%d\"><member action=\"%s\" type=\"%s\"%s/></container>",
1662 container_id,
1663 container_version,
1664 action,
1665 type,
1666 value_str);
1667 g_free(value_str);
1669 if ((*container_xmls) == NULL) {
1670 *container_xmls = body;
1671 } else {
1672 char *tmp = *container_xmls;
1674 *container_xmls = g_strconcat(*container_xmls, body, NULL);
1675 g_free(tmp);
1676 g_free(body);
1680 static void
1681 sipe_send_set_container_members(struct sipe_core_private *sipe_private,
1682 char *container_xmls)
1684 gchar *self;
1685 gchar *contact;
1686 gchar *hdr;
1687 gchar *body;
1689 if (!container_xmls) return;
1691 self = sip_uri_self(sipe_private);
1692 body = g_strdup_printf(
1693 "<setContainerMembers xmlns=\"http://schemas.microsoft.com/2006/09/sip/container-management\">"
1694 "%s"
1695 "</setContainerMembers>",
1696 container_xmls);
1698 contact = get_contact(sipe_private);
1699 hdr = g_strdup_printf("Contact: %s\r\n"
1700 "Content-Type: application/msrtc-setcontainermembers+xml\r\n", contact);
1701 g_free(contact);
1703 sip_transport_service(sipe_private,
1704 self,
1705 hdr,
1706 body,
1707 NULL);
1709 g_free(hdr);
1710 g_free(body);
1711 g_free(self);
1715 * Finds locally stored MS-PRES container member
1717 static struct sipe_container_member *
1718 sipe_find_container_member(struct sipe_container *container,
1719 const gchar *type,
1720 const gchar *value)
1722 struct sipe_container_member *member;
1723 GSList *entry;
1725 if (container == NULL || type == NULL) {
1726 return NULL;
1729 entry = container->members;
1730 while (entry) {
1731 member = entry->data;
1732 if (sipe_strcase_equal(member->type, type) &&
1733 sipe_strcase_equal(member->value, value))
1735 return member;
1737 entry = entry->next;
1739 return NULL;
1743 * Finds locally stored MS-PRES container by id
1745 static struct sipe_container *
1746 sipe_find_container(struct sipe_core_private *sipe_private,
1747 guint id)
1749 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
1750 struct sipe_container *container;
1751 GSList *entry;
1753 if (sip == NULL) {
1754 return NULL;
1757 entry = sip->containers;
1758 while (entry) {
1759 container = entry->data;
1760 if (id == container->id) {
1761 return container;
1763 entry = entry->next;
1765 return NULL;
1768 static GSList *
1769 sipe_get_access_domains(struct sipe_core_private *sipe_private)
1771 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
1772 struct sipe_container *container;
1773 struct sipe_container_member *member;
1774 GSList *entry;
1775 GSList *entry2;
1776 GSList *res = NULL;
1778 if (!sip) return NULL;
1780 entry = sip->containers;
1781 while (entry) {
1782 container = entry->data;
1784 entry2 = container->members;
1785 while (entry2) {
1786 member = entry2->data;
1787 if (sipe_strcase_equal(member->type, "domain"))
1789 res = slist_insert_unique_sorted(res, g_strdup(member->value), (GCompareFunc)g_ascii_strcasecmp);
1791 entry2 = entry2->next;
1793 entry = entry->next;
1795 return res;
1799 * Returns pointer to domain part in provided Email URL
1801 * @param email an email URL. Example: first.last@hq.company.com
1802 * @return pointer to domain part of email URL. Coresponding example: hq.company.com
1804 * Doesn't allocate memory
1806 static const char *
1807 sipe_get_domain(const char *email)
1809 char *tmp;
1811 if (!email) return NULL;
1813 tmp = strstr(email, "@");
1815 if (tmp && ((tmp+1) < (email + strlen(email)))) {
1816 return tmp+1;
1817 } else {
1818 return NULL;
1823 /* @TODO: replace with binary search for faster access? */
1824 /** source: http://support.microsoft.com/kb/897567 */
1825 static const char * const public_domains [] = {
1826 "aol.com", "icq.com", "love.com", "mac.com", "br.live.com",
1827 "hotmail.co.il", "hotmail.co.jp", "hotmail.co.th", "hotmail.co.uk",
1828 "hotmail.com", "hotmail.com.ar", "hotmail.com.tr", "hotmail.es",
1829 "hotmail.de", "hotmail.fr", "hotmail.it", "live.at", "live.be",
1830 "live.ca", "live.cl", "live.cn", "live.co.in", "live.co.kr",
1831 "live.co.uk", "live.co.za", "live.com", "live.com.ar", "live.com.au",
1832 "live.com.co", "live.com.mx", "live.com.my", "live.com.pe",
1833 "live.com.ph", "live.com.pk", "live.com.pt", "live.com.sg",
1834 "live.com.ve", "live.de", "live.dk", "live.fr", "live.hk", "live.ie",
1835 "live.in", "live.it", "live.jp", "live.nl", "live.no", "live.ph",
1836 "live.ru", "live.se", "livemail.com.br", "livemail.tw",
1837 "messengeruser.com", "msn.com", "passport.com", "sympatico.ca",
1838 "tw.live.com", "webtv.net", "windowslive.com", "windowslive.es",
1839 "yahoo.com",
1840 NULL};
1842 static gboolean
1843 sipe_is_public_domain(const char *domain)
1845 int i = 0;
1846 while (public_domains[i]) {
1847 if (sipe_strcase_equal(public_domains[i], domain)) {
1848 return TRUE;
1850 i++;
1852 return FALSE;
1856 * Access Levels
1857 * 32000 - Blocked
1858 * 400 - Personal
1859 * 300 - Team
1860 * 200 - Company
1861 * 100 - Public
1863 static const char *
1864 sipe_get_access_level_name(int container_id)
1866 switch(container_id) {
1867 case 32000: return _("Blocked");
1868 case 400: return _("Personal");
1869 case 300: return _("Team");
1870 case 200: return _("Company");
1871 case 100: return _("Public");
1873 return _("Unknown");
1876 static const guint containers[] = {32000, 400, 300, 200, 100};
1877 #define CONTAINERS_LEN (sizeof(containers) / sizeof(guint))
1880 static int
1881 sipe_find_member_access_level(struct sipe_core_private *sipe_private,
1882 const gchar *type,
1883 const gchar *value)
1885 unsigned int i = 0;
1886 const gchar *value_mod = value;
1888 if (!type) return -1;
1890 if (sipe_strequal("user", type)) {
1891 value_mod = sipe_get_no_sip_uri(value);
1894 for (i = 0; i < CONTAINERS_LEN; i++) {
1895 struct sipe_container_member *member;
1896 struct sipe_container *container = sipe_find_container(sipe_private, containers[i]);
1897 if (!container) continue;
1899 member = sipe_find_container_member(container, type, value_mod);
1900 if (member) return containers[i];
1903 return -1;
1906 /** Member type: user, domain, sameEnterprise, federated, publicCloud; everyone */
1907 static int
1908 sipe_find_access_level(struct sipe_core_private *sipe_private,
1909 const gchar *type,
1910 const gchar *value,
1911 gboolean *is_group_access)
1913 int container_id = -1;
1915 if (sipe_strequal("user", type)) {
1916 const char *domain;
1917 const char *no_sip_uri = sipe_get_no_sip_uri(value);
1919 container_id = sipe_find_member_access_level(sipe_private, "user", no_sip_uri);
1920 if (container_id >= 0) {
1921 if (is_group_access) *is_group_access = FALSE;
1922 return container_id;
1925 domain = sipe_get_domain(no_sip_uri);
1926 container_id = sipe_find_member_access_level(sipe_private, "domain", domain);
1927 if (container_id >= 0) {
1928 if (is_group_access) *is_group_access = TRUE;
1929 return container_id;
1932 container_id = sipe_find_member_access_level(sipe_private, "sameEnterprise", NULL);
1933 if ((container_id >= 0) && sipe_strcase_equal(sipe_private->public.sip_domain, domain)) {
1934 if (is_group_access) *is_group_access = TRUE;
1935 return container_id;
1938 container_id = sipe_find_member_access_level(sipe_private, "publicCloud", NULL);
1939 if ((container_id >= 0) && sipe_is_public_domain(domain)) {
1940 if (is_group_access) *is_group_access = TRUE;
1941 return container_id;
1944 container_id = sipe_find_member_access_level(sipe_private, "everyone", NULL);
1945 if ((container_id >= 0)) {
1946 if (is_group_access) *is_group_access = TRUE;
1947 return container_id;
1949 } else {
1950 container_id = sipe_find_member_access_level(sipe_private, type, value);
1951 if (is_group_access) *is_group_access = FALSE;
1954 return container_id;
1958 * @param container_id a new access level. If -1 then current access level
1959 * is just removed (I.e. the member is removed from all containers).
1960 * @param type a type of member. E.g. "user", "sameEnterprise", etc.
1961 * @param value a value for member. E.g. SIP URI for "user" member type.
1963 static void
1964 sipe_change_access_level(struct sipe_core_private *sipe_private,
1965 const int container_id,
1966 const gchar *type,
1967 const gchar *value)
1969 unsigned int i;
1970 int current_container_id = -1;
1971 char *container_xmls = NULL;
1973 /* for each container: find/delete */
1974 for (i = 0; i < CONTAINERS_LEN; i++) {
1975 struct sipe_container_member *member;
1976 struct sipe_container *container = sipe_find_container(sipe_private, containers[i]);
1978 if (!container) continue;
1980 member = sipe_find_container_member(container, type, value);
1981 if (member) {
1982 current_container_id = containers[i];
1983 /* delete/publish current access level */
1984 if (container_id < 0 || container_id != current_container_id) {
1985 sipe_send_container_members_prepare(current_container_id, container->version, "remove", type, value, &container_xmls);
1986 /* remove member from our cache, to be able to recalculate AL below */
1987 container->members = g_slist_remove(container->members, member);
1988 current_container_id = -1;
1993 /* recalculate AL below */
1994 current_container_id = sipe_find_access_level(sipe_private, type, value, NULL);
1996 /* assign/publish new access level */
1997 if (container_id != current_container_id && container_id >= 0) {
1998 struct sipe_container *container = sipe_find_container(sipe_private, container_id);
1999 guint version = container ? container->version : 0;
2001 sipe_send_container_members_prepare(container_id, version, "add", type, value, &container_xmls);
2004 if (container_xmls) {
2005 sipe_send_set_container_members(sipe_private, container_xmls);
2007 g_free(container_xmls);
2010 static void
2011 free_publication(struct sipe_publication *publication)
2013 g_free(publication->category);
2014 g_free(publication->cal_event_hash);
2015 g_free(publication->note);
2017 g_free(publication->working_hours_xml_str);
2018 g_free(publication->fb_start_str);
2019 g_free(publication->free_busy_base64);
2021 g_free(publication);
2024 /* key is <category><instance><container> */
2025 static gboolean
2026 sipe_is_our_publication(struct sipe_core_private *sipe_private,
2027 const gchar *key)
2029 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
2030 GSList *entry;
2032 /* filling keys for our publications if not yet cached */
2033 if (!sip->our_publication_keys) {
2034 guint device_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_DEVICE);
2035 guint machine_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_MACHINE);
2036 guint user_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_USER);
2037 guint calendar_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_CALENDAR);
2038 guint cal_oof_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_CALENDAR_OOF);
2039 guint cal_data_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_CALENDAR_DATA);
2040 guint note_oof_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_NOTE_OOF);
2042 SIPE_DEBUG_INFO_NOFORMAT("* Our Publication Instances *");
2043 SIPE_DEBUG_INFO("\tDevice : %u\t0x%08X", device_instance, device_instance);
2044 SIPE_DEBUG_INFO("\tMachine State : %u\t0x%08X", machine_instance, machine_instance);
2045 SIPE_DEBUG_INFO("\tUser Stare : %u\t0x%08X", user_instance, user_instance);
2046 SIPE_DEBUG_INFO("\tCalendar State : %u\t0x%08X", calendar_instance, calendar_instance);
2047 SIPE_DEBUG_INFO("\tCalendar OOF State : %u\t0x%08X", cal_oof_instance, cal_oof_instance);
2048 SIPE_DEBUG_INFO("\tCalendar FreeBusy : %u\t0x%08X", cal_data_instance, cal_data_instance);
2049 SIPE_DEBUG_INFO("\tOOF Note : %u\t0x%08X", note_oof_instance, note_oof_instance);
2050 SIPE_DEBUG_INFO("\tNote : %u", 0);
2051 SIPE_DEBUG_INFO("\tCalendar WorkingHours: %u", 0);
2053 /* device */
2054 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2055 g_strdup_printf("<%s><%u><%u>", "device", device_instance, 2));
2057 /* state:machineState */
2058 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2059 g_strdup_printf("<%s><%u><%u>", "state", machine_instance, 2));
2060 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2061 g_strdup_printf("<%s><%u><%u>", "state", machine_instance, 3));
2063 /* state:userState */
2064 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2065 g_strdup_printf("<%s><%u><%u>", "state", user_instance, 2));
2066 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2067 g_strdup_printf("<%s><%u><%u>", "state", user_instance, 3));
2069 /* state:calendarState */
2070 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2071 g_strdup_printf("<%s><%u><%u>", "state", calendar_instance, 2));
2072 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2073 g_strdup_printf("<%s><%u><%u>", "state", calendar_instance, 3));
2075 /* state:calendarState OOF */
2076 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2077 g_strdup_printf("<%s><%u><%u>", "state", cal_oof_instance, 2));
2078 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2079 g_strdup_printf("<%s><%u><%u>", "state", cal_oof_instance, 3));
2081 /* note */
2082 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2083 g_strdup_printf("<%s><%u><%u>", "note", 0, 200));
2084 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2085 g_strdup_printf("<%s><%u><%u>", "note", 0, 300));
2086 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2087 g_strdup_printf("<%s><%u><%u>", "note", 0, 400));
2089 /* note OOF */
2090 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2091 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 200));
2092 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2093 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 300));
2094 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2095 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 400));
2097 /* calendarData:WorkingHours */
2098 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2099 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 1));
2100 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2101 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 100));
2102 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2103 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 200));
2104 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2105 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 300));
2106 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2107 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 400));
2108 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2109 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 32000));
2111 /* calendarData:FreeBusy */
2112 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2113 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 1));
2114 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2115 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 100));
2116 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2117 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 200));
2118 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2119 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 300));
2120 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2121 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 400));
2122 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2123 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 32000));
2125 //SIPE_DEBUG_INFO("sipe_is_our_publication: sip->our_publication_keys length=%d",
2126 // sip->our_publication_keys ? (int) g_slist_length(sip->our_publication_keys) : -1);
2129 //SIPE_DEBUG_INFO("sipe_is_our_publication: key=%s", key);
2131 entry = sip->our_publication_keys;
2132 while (entry) {
2133 //SIPE_DEBUG_INFO(" sipe_is_our_publication: entry->data=%s", entry->data);
2134 if (sipe_strequal(entry->data, key)) {
2135 return TRUE;
2137 entry = entry->next;
2139 return FALSE;
2142 /** Property names to store in blist.xml */
2143 #define ALIAS_PROP "alias"
2144 #define EMAIL_PROP "email"
2145 #define PHONE_PROP "phone"
2146 #define PHONE_DISPLAY_PROP "phone-display"
2147 #define PHONE_MOBILE_PROP "phone-mobile"
2148 #define PHONE_MOBILE_DISPLAY_PROP "phone-mobile-display"
2149 #define PHONE_HOME_PROP "phone-home"
2150 #define PHONE_HOME_DISPLAY_PROP "phone-home-display"
2151 #define PHONE_OTHER_PROP "phone-other"
2152 #define PHONE_OTHER_DISPLAY_PROP "phone-other-display"
2153 #define PHONE_CUSTOM1_PROP "phone-custom1"
2154 #define PHONE_CUSTOM1_DISPLAY_PROP "phone-custom1-display"
2155 #define SITE_PROP "site"
2156 #define COMPANY_PROP "company"
2157 #define DEPARTMENT_PROP "department"
2158 #define TITLE_PROP "title"
2159 #define OFFICE_PROP "office"
2160 /** implies work address */
2161 #define ADDRESS_STREET_PROP "address-street"
2162 #define ADDRESS_CITY_PROP "address-city"
2163 #define ADDRESS_STATE_PROP "address-state"
2164 #define ADDRESS_ZIPCODE_PROP "address-zipcode"
2165 #define ADDRESS_COUNTRYCODE_PROP "address-country-code"
2168 * Tries to figure out user first and last name
2169 * based on Display Name and email properties.
2171 * Allocates memory - must be g_free()'d
2173 * Examples to parse:
2174 * First Last
2175 * First Last - Company Name
2176 * Last, First
2177 * Last, First M.
2178 * Last, First (C)(STP) (Company)
2179 * first.last@company.com (preprocessed as "first last")
2180 * first.last.company.com@reuters.net (preprocessed as "first last company com")
2182 * Unusable examples:
2183 * user@company.com (preprocessed as "user")
2184 * first.m.last@company.com (preprocessed as "first m last")
2185 * user.company.com@reuters.net (preprocessed as "user company com")
2187 static void
2188 sipe_get_first_last_names(struct sipe_core_private *sipe_private,
2189 const char *uri,
2190 char **first_name,
2191 char **last_name)
2193 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
2194 sipe_backend_buddy p_buddy;
2195 char *display_name;
2196 gchar *email;
2197 const char *first, *last;
2198 char *tmp;
2199 char **parts;
2200 gboolean has_comma = FALSE;
2202 if (!sip || !uri) return;
2204 p_buddy = sipe_backend_buddy_find(SIPE_CORE_PUBLIC, uri, NULL);
2206 if (!p_buddy) return;
2208 display_name = sipe_backend_buddy_get_alias(SIPE_CORE_PUBLIC, p_buddy);
2209 email = sipe_backend_buddy_get_string(SIPE_CORE_PUBLIC, p_buddy, SIPE_BUDDY_INFO_EMAIL);
2211 if (!display_name && !email) return;
2213 /* if no display name, make "first last anything_else" out of email */
2214 if (email && !display_name) {
2215 display_name = g_strndup(email, strstr(email, "@") - email);
2216 display_name = sipe_utils_str_replace((tmp = display_name), ".", " ");
2217 g_free(tmp);
2220 if (display_name) {
2221 has_comma = (strstr(display_name, ",") != NULL);
2222 display_name = sipe_utils_str_replace((tmp = display_name), ", ", " ");
2223 g_free(tmp);
2224 display_name = sipe_utils_str_replace((tmp = display_name), ",", " ");
2225 g_free(tmp);
2228 parts = g_strsplit(display_name, " ", 0);
2230 if (!parts[0] || !parts[1]) {
2231 g_free(email);
2232 g_free(display_name);
2233 g_strfreev(parts);
2234 return;
2237 if (has_comma) {
2238 last = parts[0];
2239 first = parts[1];
2240 } else {
2241 first = parts[0];
2242 last = parts[1];
2245 if (first_name) {
2246 *first_name = g_strstrip(g_strdup(first));
2249 if (last_name) {
2250 *last_name = g_strstrip(g_strdup(last));
2253 g_free(email);
2254 g_free(display_name);
2255 g_strfreev(parts);
2259 * Update user information
2261 * @param uri buddy SIP URI with 'sip:' prefix whose info we want to change.
2262 * @param property_name
2263 * @param property_value may be modified to strip white space
2265 static void
2266 sipe_update_user_info(struct sipe_core_private *sipe_private,
2267 const char *uri,
2268 sipe_buddy_info_fields propkey,
2269 char *property_value)
2271 GSList *buddies, *entry;
2273 if (property_value)
2274 property_value = g_strstrip(property_value);
2276 entry = buddies = sipe_backend_buddy_find_all(SIPE_CORE_PUBLIC, uri, NULL); /* all buddies in different groups */
2277 while (entry) {
2278 gchar *prop_str;
2279 gchar *server_alias;
2280 gchar *alias;
2281 sipe_backend_buddy p_buddy = entry->data;
2283 /* for Display Name */
2284 if (propkey == SIPE_BUDDY_INFO_DISPLAY_NAME) {
2285 alias = sipe_backend_buddy_get_alias(SIPE_CORE_PUBLIC, p_buddy);
2286 if (property_value && sipe_is_bad_alias(uri, alias)) {
2287 SIPE_DEBUG_INFO("Replacing alias for %s with %s", uri, property_value);
2288 sipe_backend_buddy_set_alias(SIPE_CORE_PUBLIC, p_buddy, property_value);
2290 g_free(alias);
2292 server_alias = sipe_backend_buddy_get_server_alias(SIPE_CORE_PUBLIC, p_buddy);
2293 if (!is_empty(property_value) &&
2294 (!sipe_strequal(property_value, server_alias) || is_empty(server_alias)) )
2296 SIPE_DEBUG_INFO("Replacing service alias for %s with %s", uri, property_value);
2297 sipe_backend_buddy_set_server_alias(SIPE_CORE_PUBLIC, p_buddy, property_value);
2299 g_free(server_alias);
2301 /* for other properties */
2302 else {
2303 if (!is_empty(property_value)) {
2304 prop_str = sipe_backend_buddy_get_string(SIPE_CORE_PUBLIC, p_buddy, propkey);
2305 if (!prop_str || !sipe_strcase_equal(prop_str, property_value)) {
2306 sipe_backend_buddy_set_string(SIPE_CORE_PUBLIC, p_buddy, propkey, property_value);
2308 g_free(prop_str);
2312 entry = entry->next;
2314 g_slist_free(buddies);
2318 * Update user phone
2319 * Suitable for both 2005 and 2007 systems.
2321 * @param uri buddy SIP URI with 'sip:' prefix whose info we want to change.
2322 * @param phone_type
2323 * @param phone may be modified to strip white space
2324 * @param phone_display_string may be modified to strip white space
2326 static void
2327 sipe_update_user_phone(struct sipe_core_private *sipe_private,
2328 const gchar *uri,
2329 const gchar *phone_type,
2330 gchar *phone,
2331 gchar *phone_display_string)
2333 sipe_buddy_info_fields phone_node = SIPE_BUDDY_INFO_WORK_PHONE; /* work phone by default */
2334 sipe_buddy_info_fields phone_display_node = SIPE_BUDDY_INFO_WORK_PHONE_DISPLAY; /* work phone by default */
2336 if(!phone || strlen(phone) == 0) return;
2338 if ((sipe_strequal(phone_type, "mobile") || sipe_strequal(phone_type, "cell"))) {
2339 phone_node = SIPE_BUDDY_INFO_MOBILE_PHONE;
2340 phone_display_node = SIPE_BUDDY_INFO_MOBILE_PHONE_DISPLAY;
2341 } else if (sipe_strequal(phone_type, "home")) {
2342 phone_node = SIPE_BUDDY_INFO_HOME_PHONE;
2343 phone_display_node = SIPE_BUDDY_INFO_HOME_PHONE_DISPLAY;
2344 } else if (sipe_strequal(phone_type, "other")) {
2345 phone_node = SIPE_BUDDY_INFO_OTHER_PHONE;
2346 phone_display_node = SIPE_BUDDY_INFO_OTHER_PHONE_DISPLAY;
2347 } else if (sipe_strequal(phone_type, "custom1")) {
2348 phone_node = SIPE_BUDDY_INFO_CUSTOM1_PHONE;
2349 phone_display_node = SIPE_BUDDY_INFO_CUSTOM1_PHONE_DISPLAY;
2352 sipe_update_user_info(sipe_private, uri, phone_node, phone);
2353 if (phone_display_string) {
2354 sipe_update_user_info(sipe_private, uri, phone_display_node, phone_display_string);
2358 void
2359 sipe_core_update_calendar(struct sipe_core_public *sipe_public)
2361 SIPE_DEBUG_INFO_NOFORMAT("sipe_core_update_calendar: started.");
2363 /* Do in parallel.
2364 * If failed, the branch will be disabled for subsequent calls.
2365 * Can't rely that user turned the functionality on in account settings.
2367 sipe_ews_update_calendar(SIPE_CORE_PRIVATE);
2368 sipe_domino_update_calendar(SIPE_CORE_PRIVATE);
2370 /* schedule repeat */
2371 sipe_schedule_seconds(SIPE_CORE_PRIVATE,
2372 "<+update-calendar>",
2373 NULL,
2374 UPDATE_CALENDAR_INTERVAL,
2375 (sipe_schedule_action)sipe_core_update_calendar,
2376 NULL);
2378 SIPE_DEBUG_INFO_NOFORMAT("sipe_core_update_calendar: finished.");
2382 * This method motivates Purple's Host (e.g. Pidgin) to update its UI
2383 * by using standard Purple's means of signals and saved statuses.
2385 * Thus all UI elements get updated: Status Button with Note, docklet.
2386 * This is ablolutely important as both our status and note can come
2387 * inbound (roaming) or be updated programmatically (e.g. based on our
2388 * calendar data).
2390 static void
2391 sipe_set_purple_account_status_and_note(const PurpleAccount *account,
2392 const char *status_id,
2393 const char *message,
2394 time_t do_not_publish[])
2396 PurpleStatus *status = purple_account_get_active_status(account);
2397 gboolean changed = TRUE;
2399 if (g_str_equal(status_id, purple_status_get_id(status)) &&
2400 sipe_strequal(message, purple_status_get_attr_string(status, SIPE_STATUS_ATTR_ID_MESSAGE)))
2402 changed = FALSE;
2405 if (purple_savedstatus_is_idleaway()) {
2406 changed = FALSE;
2409 if (changed) {
2410 PurpleSavedStatus *saved_status;
2411 const PurpleStatusType *acct_status_type =
2412 purple_status_type_find_with_id(account->status_types, status_id);
2413 PurpleStatusPrimitive primitive = purple_status_type_get_primitive(acct_status_type);
2414 sipe_activity activity = sipe_get_activity_by_token(status_id);
2416 saved_status = purple_savedstatus_find_transient_by_type_and_message(primitive, message);
2417 if (saved_status) {
2418 purple_savedstatus_set_substatus(saved_status, account, acct_status_type, message);
2421 /* If this type+message is unique then create a new transient saved status
2422 * Ref: gtkstatusbox.c
2424 if (!saved_status) {
2425 GList *tmp;
2426 GList *active_accts = purple_accounts_get_all_active();
2428 saved_status = purple_savedstatus_new(NULL, primitive);
2429 purple_savedstatus_set_message(saved_status, message);
2431 for (tmp = active_accts; tmp != NULL; tmp = tmp->next) {
2432 purple_savedstatus_set_substatus(saved_status,
2433 (PurpleAccount *)tmp->data, acct_status_type, message);
2435 g_list_free(active_accts);
2438 do_not_publish[activity] = time(NULL);
2439 SIPE_DEBUG_INFO("sipe_set_purple_account_status_and_note: do_not_publish[%s]=%d [now]",
2440 status_id, (int)do_not_publish[activity]);
2442 /* Set the status for each account */
2443 purple_savedstatus_activate(saved_status);
2447 struct hash_table_delete_payload {
2448 GHashTable *hash_table;
2449 guint container;
2452 static void
2453 sipe_remove_category_container_publications_cb(const char *name,
2454 struct sipe_publication *publication,
2455 struct hash_table_delete_payload *payload)
2457 if (publication->container == payload->container) {
2458 g_hash_table_remove(payload->hash_table, name);
2461 static void
2462 sipe_remove_category_container_publications(GHashTable *our_publications,
2463 const char *category,
2464 guint container)
2466 struct hash_table_delete_payload payload;
2467 payload.hash_table = g_hash_table_lookup(our_publications, category);
2469 if (!payload.hash_table) return;
2471 payload.container = container;
2472 g_hash_table_foreach(payload.hash_table, (GHFunc)sipe_remove_category_container_publications_cb, &payload);
2475 static void
2476 send_publish_category_initial(struct sipe_core_private *sipe_private);
2479 * When we receive some self (BE) NOTIFY with a new subscriber
2480 * we sends a setSubscribers request to him [SIP-PRES] 4.8
2483 static void sipe_process_roaming_self(struct sipe_core_private *sipe_private,
2484 struct sipmsg *msg)
2486 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
2487 gchar *contact;
2488 gchar *to;
2489 sipe_xml *xml;
2490 const sipe_xml *node;
2491 const sipe_xml *node2;
2492 char *display_name = NULL;
2493 char *uri;
2494 GSList *category_names = NULL;
2495 int aggreg_avail = 0;
2496 gboolean do_update_status = FALSE;
2497 gboolean has_note_cleaned = FALSE;
2499 SIPE_DEBUG_INFO_NOFORMAT("sipe_process_roaming_self");
2501 xml = sipe_xml_parse(msg->body, msg->bodylen);
2502 if (!xml) return;
2504 contact = get_contact(sipe_private);
2505 to = sip_uri_self(sipe_private);
2508 /* categories */
2509 /* set list of categories participating in this XML */
2510 for (node = sipe_xml_child(xml, "categories/category"); node; node = sipe_xml_twin(node)) {
2511 const gchar *name = sipe_xml_attribute(node, "name");
2512 category_names = slist_insert_unique_sorted(category_names, (gchar *)name, (GCompareFunc)strcmp);
2514 SIPE_DEBUG_INFO("sipe_process_roaming_self: category_names length=%d",
2515 category_names ? (int) g_slist_length(category_names) : -1);
2516 /* drop category information */
2517 if (category_names) {
2518 GSList *entry = category_names;
2519 while (entry) {
2520 GHashTable *cat_publications;
2521 const gchar *category = entry->data;
2522 entry = entry->next;
2523 SIPE_DEBUG_INFO("sipe_process_roaming_self: dropping category: %s", category);
2524 cat_publications = g_hash_table_lookup(sip->our_publications, category);
2525 if (cat_publications) {
2526 g_hash_table_remove(sip->our_publications, category);
2527 SIPE_DEBUG_INFO("sipe_process_roaming_self: dropped category: %s", category);
2531 g_slist_free(category_names);
2532 /* filling our categories reflected in roaming data */
2533 for (node = sipe_xml_child(xml, "categories/category"); node; node = sipe_xml_twin(node)) {
2534 const char *tmp;
2535 const gchar *name = sipe_xml_attribute(node, "name");
2536 guint container = sipe_xml_int_attribute(node, "container", -1);
2537 guint instance = sipe_xml_int_attribute(node, "instance", -1);
2538 guint version = sipe_xml_int_attribute(node, "version", 0);
2539 time_t publish_time = (tmp = sipe_xml_attribute(node, "publishTime")) ?
2540 sipe_utils_str_to_time(tmp) : 0;
2541 gchar *key;
2542 GHashTable *cat_publications = g_hash_table_lookup(sip->our_publications, name);
2544 /* Ex. clear note: <category name="note"/> */
2545 if (container == (guint)-1) {
2546 g_free(sip->note);
2547 sip->note = NULL;
2548 do_update_status = TRUE;
2549 continue;
2552 /* Ex. clear note: <category name="note" container="200"/> */
2553 if (instance == (guint)-1) {
2554 if (container == 200) {
2555 g_free(sip->note);
2556 sip->note = NULL;
2557 do_update_status = TRUE;
2559 SIPE_DEBUG_INFO("sipe_process_roaming_self: removing publications for: %s/%u", name, container);
2560 sipe_remove_category_container_publications(
2561 sip->our_publications, name, container);
2562 continue;
2565 /* key is <category><instance><container> */
2566 key = g_strdup_printf("<%s><%u><%u>", name, instance, container);
2567 SIPE_DEBUG_INFO("sipe_process_roaming_self: key=%s version=%d", key, version);
2569 /* capture all userState publication for later clean up if required */
2570 if (sipe_strequal(name, "state") && (container == 2 || container == 3)) {
2571 const sipe_xml *xn_state = sipe_xml_child(node, "state");
2573 if (xn_state && sipe_strequal(sipe_xml_attribute(xn_state, "type"), "userState")) {
2574 struct sipe_publication *publication = g_new0(struct sipe_publication, 1);
2575 publication->category = g_strdup(name);
2576 publication->instance = instance;
2577 publication->container = container;
2578 publication->version = version;
2580 if (!sip->user_state_publications) {
2581 sip->user_state_publications = g_hash_table_new_full(
2582 g_str_hash, g_str_equal,
2583 g_free, (GDestroyNotify)free_publication);
2585 g_hash_table_insert(sip->user_state_publications, g_strdup(key), publication);
2586 SIPE_DEBUG_INFO("sipe_process_roaming_self: added to user_state_publications key=%s version=%d",
2587 key, version);
2591 if (sipe_is_our_publication(sipe_private, key)) {
2592 struct sipe_publication *publication = g_new0(struct sipe_publication, 1);
2594 publication->category = g_strdup(name);
2595 publication->instance = instance;
2596 publication->container = container;
2597 publication->version = version;
2599 /* filling publication->availability */
2600 if (sipe_strequal(name, "state")) {
2601 const sipe_xml *xn_state = sipe_xml_child(node, "state");
2602 const sipe_xml *xn_avail = sipe_xml_child(xn_state, "availability");
2604 if (xn_avail) {
2605 gchar *avail_str = sipe_xml_data(xn_avail);
2606 if (avail_str) {
2607 publication->availability = atoi(avail_str);
2609 g_free(avail_str);
2611 /* for calendarState */
2612 if (xn_state && sipe_strequal(sipe_xml_attribute(xn_state, "type"), "calendarState")) {
2613 const sipe_xml *xn_activity = sipe_xml_child(xn_state, "activity");
2614 struct sipe_cal_event *event = g_new0(struct sipe_cal_event, 1);
2616 event->start_time = sipe_utils_str_to_time(sipe_xml_attribute(xn_state, "startTime"));
2617 if (xn_activity) {
2618 if (sipe_strequal(sipe_xml_attribute(xn_activity, "token"),
2619 sipe_activity_map[SIPE_ACTIVITY_IN_MEETING].token))
2621 event->is_meeting = TRUE;
2624 event->subject = sipe_xml_data(sipe_xml_child(xn_state, "meetingSubject"));
2625 event->location = sipe_xml_data(sipe_xml_child(xn_state, "meetingLocation"));
2627 publication->cal_event_hash = sipe_cal_event_hash(event);
2628 SIPE_DEBUG_INFO("sipe_process_roaming_self: hash=%s",
2629 publication->cal_event_hash);
2630 sipe_cal_event_free(event);
2633 /* filling publication->note */
2634 if (sipe_strequal(name, "note")) {
2635 const sipe_xml *xn_body = sipe_xml_child(node, "note/body");
2637 if (!has_note_cleaned) {
2638 has_note_cleaned = TRUE;
2640 g_free(sip->note);
2641 sip->note = NULL;
2642 sip->note_since = publish_time;
2644 do_update_status = TRUE;
2647 g_free(publication->note);
2648 publication->note = NULL;
2649 if (xn_body) {
2650 char *tmp;
2652 publication->note = g_markup_escape_text((tmp = sipe_xml_data(xn_body)), -1);
2653 g_free(tmp);
2654 if (publish_time >= sip->note_since) {
2655 g_free(sip->note);
2656 sip->note = g_strdup(publication->note);
2657 sip->note_since = publish_time;
2658 sip->is_oof_note = sipe_strequal(sipe_xml_attribute(xn_body, "type"), "OOF");
2660 do_update_status = TRUE;
2665 /* filling publication->fb_start_str, free_busy_base64, working_hours_xml_str */
2666 if (sipe_strequal(name, "calendarData") && (publication->container == 300)) {
2667 const sipe_xml *xn_free_busy = sipe_xml_child(node, "calendarData/freeBusy");
2668 const sipe_xml *xn_working_hours = sipe_xml_child(node, "calendarData/WorkingHours");
2669 if (xn_free_busy) {
2670 publication->fb_start_str = g_strdup(sipe_xml_attribute(xn_free_busy, "startTime"));
2671 publication->free_busy_base64 = sipe_xml_data(xn_free_busy);
2673 if (xn_working_hours) {
2674 publication->working_hours_xml_str = sipe_xml_stringify(xn_working_hours);
2678 if (!cat_publications) {
2679 cat_publications = g_hash_table_new_full(
2680 g_str_hash, g_str_equal,
2681 g_free, (GDestroyNotify)free_publication);
2682 g_hash_table_insert(sip->our_publications, g_strdup(name), cat_publications);
2683 SIPE_DEBUG_INFO("sipe_process_roaming_self: added GHashTable cat=%s", name);
2685 g_hash_table_insert(cat_publications, g_strdup(key), publication);
2686 SIPE_DEBUG_INFO("sipe_process_roaming_self: added key=%s version=%d", key, version);
2688 g_free(key);
2690 /* aggregateState (not an our publication) from 2-nd container */
2691 if (sipe_strequal(name, "state") && container == 2) {
2692 const sipe_xml *xn_state = sipe_xml_child(node, "state");
2694 if (xn_state && sipe_strequal(sipe_xml_attribute(xn_state, "type"), "aggregateState")) {
2695 const sipe_xml *xn_avail = sipe_xml_child(xn_state, "availability");
2697 if (xn_avail) {
2698 gchar *avail_str = sipe_xml_data(xn_avail);
2699 if (avail_str) {
2700 aggreg_avail = atoi(avail_str);
2702 g_free(avail_str);
2705 do_update_status = TRUE;
2709 /* userProperties published by server from AD */
2710 if (!sip->csta && sipe_strequal(name, "userProperties")) {
2711 const sipe_xml *line;
2712 /* line, for Remote Call Control (RCC) */
2713 for (line = sipe_xml_child(node, "userProperties/lines/line"); line; line = sipe_xml_twin(line)) {
2714 const gchar *line_server = sipe_xml_attribute(line, "lineServer");
2715 const gchar *line_type = sipe_xml_attribute(line, "lineType");
2716 gchar *line_uri;
2718 if (!line_server || !(sipe_strequal(line_type, "Rcc") || sipe_strequal(line_type, "Dual"))) continue;
2720 line_uri = sipe_xml_data(line);
2721 if (line_uri) {
2722 SIPE_DEBUG_INFO("sipe_process_roaming_self: line_uri=%s server=%s", line_uri, line_server);
2723 sip_csta_open(sipe_private, line_uri, line_server);
2725 g_free(line_uri);
2727 break;
2731 SIPE_DEBUG_INFO("sipe_process_roaming_self: sip->our_publications size=%d",
2732 sip->our_publications ? (int) g_hash_table_size(sip->our_publications) : -1);
2734 /* containers */
2735 for (node = sipe_xml_child(xml, "containers/container"); node; node = sipe_xml_twin(node)) {
2736 guint id = sipe_xml_int_attribute(node, "id", 0);
2737 struct sipe_container *container = sipe_find_container(sipe_private, id);
2739 if (container) {
2740 sip->containers = g_slist_remove(sip->containers, container);
2741 SIPE_DEBUG_INFO("sipe_process_roaming_self: removed existing container id=%d v%d", container->id, container->version);
2742 free_container(container);
2744 container = g_new0(struct sipe_container, 1);
2745 container->id = id;
2746 container->version = sipe_xml_int_attribute(node, "version", 0);
2747 sip->containers = g_slist_append(sip->containers, container);
2748 SIPE_DEBUG_INFO("sipe_process_roaming_self: added container id=%d v%d", container->id, container->version);
2750 for (node2 = sipe_xml_child(node, "member"); node2; node2 = sipe_xml_twin(node2)) {
2751 struct sipe_container_member *member = g_new0(struct sipe_container_member, 1);
2752 member->type = g_strdup(sipe_xml_attribute(node2, "type"));
2753 member->value = g_strdup(sipe_xml_attribute(node2, "value"));
2754 container->members = g_slist_append(container->members, member);
2755 SIPE_DEBUG_INFO("sipe_process_roaming_self: added container member type=%s value=%s",
2756 member->type, member->value ? member->value : "");
2760 SIPE_DEBUG_INFO("sipe_process_roaming_self: sip->access_level_set=%s", sip->access_level_set ? "TRUE" : "FALSE");
2761 if (!sip->access_level_set && sipe_xml_child(xml, "containers")) {
2762 char *container_xmls = NULL;
2763 int sameEnterpriseAL = sipe_find_access_level(sipe_private, "sameEnterprise", NULL, NULL);
2764 int federatedAL = sipe_find_access_level(sipe_private, "federated", NULL, NULL);
2766 SIPE_DEBUG_INFO("sipe_process_roaming_self: sameEnterpriseAL=%d", sameEnterpriseAL);
2767 SIPE_DEBUG_INFO("sipe_process_roaming_self: federatedAL=%d", federatedAL);
2768 /* initial set-up to let counterparties see your status */
2769 if (sameEnterpriseAL < 0) {
2770 struct sipe_container *container = sipe_find_container(sipe_private, 200);
2771 guint version = container ? container->version : 0;
2772 sipe_send_container_members_prepare(200, version, "add", "sameEnterprise", NULL, &container_xmls);
2774 if (federatedAL < 0) {
2775 struct sipe_container *container = sipe_find_container(sipe_private, 100);
2776 guint version = container ? container->version : 0;
2777 sipe_send_container_members_prepare(100, version, "add", "federated", NULL, &container_xmls);
2779 sip->access_level_set = TRUE;
2781 if (container_xmls) {
2782 sipe_send_set_container_members(sipe_private, container_xmls);
2784 g_free(container_xmls);
2787 /* Refresh contacts' blocked status */
2788 sipe_refresh_blocked_status(sipe_private);
2790 /* subscribers */
2791 for (node = sipe_xml_child(xml, "subscribers/subscriber"); node; node = sipe_xml_twin(node)) {
2792 const char *user;
2793 const char *acknowledged;
2794 gchar *hdr;
2795 gchar *body;
2797 user = sipe_xml_attribute(node, "user"); /* without 'sip:' prefix */
2798 if (!user) continue;
2799 SIPE_DEBUG_INFO("sipe_process_roaming_self: user %s", user);
2800 display_name = g_strdup(sipe_xml_attribute(node, "displayName"));
2801 uri = sip_uri_from_name(user);
2803 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_DISPLAY_NAME, display_name);
2805 acknowledged= sipe_xml_attribute(node, "acknowledged");
2806 if(sipe_strcase_equal(acknowledged,"false")){
2807 SIPE_DEBUG_INFO("sipe_process_roaming_self: user added you %s", user);
2808 if (!sipe_backend_buddy_find(SIPE_CORE_PUBLIC, uri, NULL)) {
2809 sipe_backend_buddy_request_add(SIPE_CORE_PUBLIC, uri, display_name);
2812 hdr = g_strdup_printf(
2813 "Contact: %s\r\n"
2814 "Content-Type: application/msrtc-presence-setsubscriber+xml\r\n", contact);
2816 body = g_strdup_printf(
2817 "<setSubscribers xmlns=\"http://schemas.microsoft.com/2006/09/sip/presence-subscribers\">"
2818 "<subscriber user=\"%s\" acknowledged=\"true\"/>"
2819 "</setSubscribers>", user);
2821 sip_transport_service(sipe_private,
2823 hdr,
2824 body,
2825 NULL);
2826 g_free(body);
2827 g_free(hdr);
2829 g_free(display_name);
2830 g_free(uri);
2833 g_free(contact);
2834 sipe_xml_free(xml);
2836 /* Publish initial state if not yet.
2837 * Assuming this happens on initial responce to subscription to roaming-self
2838 * so we've already updated our roaming data in full.
2839 * Only for 2007+
2841 if (!sip->initial_state_published) {
2842 send_publish_category_initial(sipe_private);
2843 sipe_groupchat_init(sipe_private);
2844 sip->initial_state_published = TRUE;
2845 /* dalayed run */
2846 sipe_schedule_seconds(sipe_private,
2847 "<+update-calendar>",
2848 NULL,
2849 UPDATE_CALENDAR_DELAY,
2850 (sipe_schedule_action)sipe_core_update_calendar,
2851 NULL);
2852 do_update_status = FALSE;
2853 } else if (aggreg_avail) {
2855 g_free(sip->status);
2856 if (aggreg_avail && aggreg_avail < 18000) { /* not offline */
2857 sip->status = g_strdup(sipe_get_status_by_availability(aggreg_avail, NULL));
2858 } else {
2859 sip->status = g_strdup(SIPE_STATUS_ID_INVISIBLE); /* not not let offline status switch us off */
2863 if (do_update_status) {
2864 SIPE_DEBUG_INFO("sipe_process_roaming_self: switch to '%s' for the account", sip->status);
2865 sipe_set_purple_account_status_and_note(sip->account, sip->status, sip->note, sip->do_not_publish);
2868 g_free(to);
2871 /* IM Session (INVITE and MESSAGE methods) */
2873 static gboolean
2874 process_options_response(SIPE_UNUSED_PARAMETER struct sipe_core_private *sipe_private,
2875 struct sipmsg *msg,
2876 SIPE_UNUSED_PARAMETER struct transaction *trans)
2878 gboolean ret = TRUE;
2880 if (msg->response != 200) {
2881 SIPE_DEBUG_INFO("process_options_response: OPTIONS response is %d", msg->response);
2882 return FALSE;
2885 SIPE_DEBUG_INFO("process_options_response: body:\n%s", msg->body ? msg->body : "");
2887 return ret;
2891 * Asks UA/proxy about its capabilities.
2893 static void sipe_options_request(struct sipe_core_private *sipe_private,
2894 const char *who)
2896 gchar *to = sip_uri(who);
2897 gchar *contact = get_contact(sipe_private);
2898 gchar *request = g_strdup_printf(
2899 "Accept: application/sdp\r\n"
2900 "Contact: %s\r\n", contact);
2901 g_free(contact);
2903 sip_transport_request(sipe_private,
2904 "OPTIONS",
2907 request,
2908 NULL,
2909 NULL,
2910 process_options_response);
2912 g_free(to);
2913 g_free(request);
2916 void
2917 sipe_convo_closed(PurpleConnection * gc, const char *who)
2919 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
2921 SIPE_DEBUG_INFO("conversation with %s closed", who);
2922 sipe_session_close(sipe_private,
2923 sipe_session_find_im(sipe_private, who));
2927 * Returns 2005-style activity and Availability.
2929 * @param status Sipe statis id.
2931 static void
2932 sipe_get_act_avail_by_status_2005(const char *status,
2933 int *activity,
2934 int *availability)
2936 int avail = 300; /* online */
2937 int act = 400; /* Available */
2939 if (sipe_strequal(status, SIPE_STATUS_ID_AWAY)) {
2940 act = 100;
2941 //} else if (sipe_strequal(status, SIPE_STATUS_ID_LUNCH)) {
2942 // act = 150;
2943 } else if (sipe_strequal(status, SIPE_STATUS_ID_BRB)) {
2944 act = 300;
2945 } else if (sipe_strequal(status, SIPE_STATUS_ID_AVAILABLE)) {
2946 act = 400;
2947 //} else if (sipe_strequal(status, SIPE_STATUS_ID_ON_PHONE)) {
2948 // act = 500;
2949 } else if (sipe_strequal(status, SIPE_STATUS_ID_BUSY) ||
2950 sipe_strequal(status, SIPE_STATUS_ID_DND)) {
2951 act = 600;
2952 } else if (sipe_strequal(status, SIPE_STATUS_ID_INVISIBLE) ||
2953 sipe_strequal(status, SIPE_STATUS_ID_OFFLINE)) {
2954 avail = 0; /* offline */
2955 act = 100;
2956 } else {
2957 act = 400; /* Available */
2960 if (activity) *activity = act;
2961 if (availability) *availability = avail;
2965 * [MS-SIP] 2.2.1
2967 * @param activity 2005 aggregated activity. Ex.: 600
2968 * @param availablity 2005 aggregated availablity. Ex.: 300
2970 static const char *
2971 sipe_get_status_by_act_avail_2005(const int activity,
2972 const int availablity,
2973 char **activity_desc)
2975 const char *status_id = NULL;
2976 const char *act = NULL;
2978 if (activity < 150) {
2979 status_id = SIPE_STATUS_ID_AWAY;
2980 } else if (activity < 200) {
2981 //status_id = SIPE_STATUS_ID_LUNCH;
2982 status_id = SIPE_STATUS_ID_AWAY;
2983 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_LUNCH);
2984 } else if (activity < 300) {
2985 //status_id = SIPE_STATUS_ID_IDLE;
2986 status_id = SIPE_STATUS_ID_AWAY;
2987 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_INACTIVE);
2988 } else if (activity < 400) {
2989 status_id = SIPE_STATUS_ID_BRB;
2990 } else if (activity < 500) {
2991 status_id = SIPE_STATUS_ID_AVAILABLE;
2992 } else if (activity < 600) {
2993 //status_id = SIPE_STATUS_ID_ON_PHONE;
2994 status_id = SIPE_STATUS_ID_BUSY;
2995 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_ON_PHONE);
2996 } else if (activity < 700) {
2997 status_id = SIPE_STATUS_ID_BUSY;
2998 } else if (activity < 800) {
2999 status_id = SIPE_STATUS_ID_AWAY;
3000 } else {
3001 status_id = SIPE_STATUS_ID_AVAILABLE;
3004 if (availablity < 100)
3005 status_id = SIPE_STATUS_ID_OFFLINE;
3007 if (activity_desc && act) {
3008 g_free(*activity_desc);
3009 *activity_desc = g_strdup(act);
3012 return status_id;
3016 * [MS-PRES] Table 3: Conversion of legacyInterop elements and attributes to MSRTC elements and attributes.
3018 static const char*
3019 sipe_get_status_by_availability(int avail,
3020 char** activity_desc)
3022 const char *status;
3023 const char *act = NULL;
3025 if (avail < 3000) {
3026 status = SIPE_STATUS_ID_OFFLINE;
3027 } else if (avail < 4500) {
3028 status = SIPE_STATUS_ID_AVAILABLE;
3029 } else if (avail < 6000) {
3030 //status = SIPE_STATUS_ID_IDLE;
3031 status = SIPE_STATUS_ID_AVAILABLE;
3032 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_INACTIVE);
3033 } else if (avail < 7500) {
3034 status = SIPE_STATUS_ID_BUSY;
3035 } else if (avail < 9000) {
3036 //status = SIPE_STATUS_ID_BUSYIDLE;
3037 status = SIPE_STATUS_ID_BUSY;
3038 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_BUSYIDLE);
3039 } else if (avail < 12000) {
3040 status = SIPE_STATUS_ID_DND;
3041 } else if (avail < 15000) {
3042 status = SIPE_STATUS_ID_BRB;
3043 } else if (avail < 18000) {
3044 status = SIPE_STATUS_ID_AWAY;
3045 } else {
3046 status = SIPE_STATUS_ID_OFFLINE;
3049 if (activity_desc && act) {
3050 g_free(*activity_desc);
3051 *activity_desc = g_strdup(act);
3054 return status;
3058 * Returns 2007-style availability value
3060 * @param sipe_status_id (in)
3061 * @param activity_token (out) Must be g_free()'d after use if consumed.
3063 static int
3064 sipe_get_availability_by_status(const char* sipe_status_id, char** activity_token)
3066 int availability;
3067 sipe_activity activity = SIPE_ACTIVITY_UNSET;
3069 if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_AWAY)) {
3070 availability = 15500;
3071 if (!activity_token || !(*activity_token)) {
3072 activity = SIPE_ACTIVITY_AWAY;
3074 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_BRB)) {
3075 availability = 12500;
3076 activity = SIPE_ACTIVITY_BRB;
3077 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_DND)) {
3078 availability = 9500;
3079 activity = SIPE_ACTIVITY_DND;
3080 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_BUSY)) {
3081 availability = 6500;
3082 if (!activity_token || !(*activity_token)) {
3083 activity = SIPE_ACTIVITY_BUSY;
3085 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_AVAILABLE)) {
3086 availability = 3500;
3087 activity = SIPE_ACTIVITY_ONLINE;
3088 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_UNKNOWN)) {
3089 availability = 0;
3090 } else {
3091 // Offline or invisible
3092 availability = 18500;
3093 activity = SIPE_ACTIVITY_OFFLINE;
3096 if (activity_token) {
3097 *activity_token = g_strdup(sipe_activity_map[activity].token);
3099 return availability;
3102 static void process_incoming_notify_rlmi(struct sipe_core_private *sipe_private,
3103 const gchar *data, unsigned len)
3105 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
3106 const char *uri;
3107 sipe_xml *xn_categories;
3108 const sipe_xml *xn_category;
3109 const char *status = NULL;
3110 gboolean do_update_status = FALSE;
3111 gboolean has_note_cleaned = FALSE;
3112 gboolean has_free_busy_cleaned = FALSE;
3114 xn_categories = sipe_xml_parse(data, len);
3115 uri = sipe_xml_attribute(xn_categories, "uri"); /* with 'sip:' prefix */
3117 for (xn_category = sipe_xml_child(xn_categories, "category");
3118 xn_category ;
3119 xn_category = sipe_xml_twin(xn_category) )
3121 const sipe_xml *xn_node;
3122 const char *tmp;
3123 const char *attrVar = sipe_xml_attribute(xn_category, "name");
3124 time_t publish_time = (tmp = sipe_xml_attribute(xn_category, "publishTime")) ?
3125 sipe_utils_str_to_time(tmp) : 0;
3127 /* contactCard */
3128 if (sipe_strequal(attrVar, "contactCard"))
3130 const sipe_xml *card = sipe_xml_child(xn_category, "contactCard");
3132 if (card) {
3133 const sipe_xml *node;
3134 /* identity - Display Name and email */
3135 node = sipe_xml_child(card, "identity");
3136 if (node) {
3137 char* display_name = sipe_xml_data(
3138 sipe_xml_child(node, "name/displayName"));
3139 char* email = sipe_xml_data(
3140 sipe_xml_child(node, "email"));
3142 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_DISPLAY_NAME, display_name);
3143 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_EMAIL, email);
3145 g_free(display_name);
3146 g_free(email);
3148 /* company */
3149 node = sipe_xml_child(card, "company");
3150 if (node) {
3151 char* company = sipe_xml_data(node);
3152 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_COMPANY, company);
3153 g_free(company);
3155 /* department */
3156 node = sipe_xml_child(card, "department");
3157 if (node) {
3158 char* department = sipe_xml_data(node);
3159 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_DEPARTMENT, department);
3160 g_free(department);
3162 /* title */
3163 node = sipe_xml_child(card, "title");
3164 if (node) {
3165 char* title = sipe_xml_data(node);
3166 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_JOB_TITLE, title);
3167 g_free(title);
3169 /* office */
3170 node = sipe_xml_child(card, "office");
3171 if (node) {
3172 char* office = sipe_xml_data(node);
3173 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_OFFICE, office);
3174 g_free(office);
3176 /* site (url) */
3177 node = sipe_xml_child(card, "url");
3178 if (node) {
3179 char* site = sipe_xml_data(node);
3180 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_SITE, site);
3181 g_free(site);
3183 /* phone */
3184 for (node = sipe_xml_child(card, "phone");
3185 node;
3186 node = sipe_xml_twin(node))
3188 const char *phone_type = sipe_xml_attribute(node, "type");
3189 char* phone = sipe_xml_data(sipe_xml_child(node, "uri"));
3190 char* phone_display_string = sipe_xml_data(sipe_xml_child(node, "displayString"));
3192 sipe_update_user_phone(sipe_private, uri, phone_type, phone, phone_display_string);
3194 g_free(phone);
3195 g_free(phone_display_string);
3197 /* address */
3198 for (node = sipe_xml_child(card, "address");
3199 node;
3200 node = sipe_xml_twin(node))
3202 if (sipe_strequal(sipe_xml_attribute(node, "type"), "work")) {
3203 char* street = sipe_xml_data(sipe_xml_child(node, "street"));
3204 char* city = sipe_xml_data(sipe_xml_child(node, "city"));
3205 char* state = sipe_xml_data(sipe_xml_child(node, "state"));
3206 char* zipcode = sipe_xml_data(sipe_xml_child(node, "zipcode"));
3207 char* country_code = sipe_xml_data(sipe_xml_child(node, "countryCode"));
3209 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_STREET, street);
3210 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_CITY, city);
3211 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_STATE, state);
3212 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_ZIPCODE, zipcode);
3213 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_COUNTRY, country_code);
3215 g_free(street);
3216 g_free(city);
3217 g_free(state);
3218 g_free(zipcode);
3219 g_free(country_code);
3221 break;
3226 /* note */
3227 else if (sipe_strequal(attrVar, "note"))
3229 if (uri) {
3230 struct sipe_buddy *sbuddy = g_hash_table_lookup(sipe_private->buddies, uri);
3232 if (!has_note_cleaned) {
3233 has_note_cleaned = TRUE;
3235 g_free(sbuddy->note);
3236 sbuddy->note = NULL;
3237 sbuddy->is_oof_note = FALSE;
3238 sbuddy->note_since = publish_time;
3240 do_update_status = TRUE;
3242 if (sbuddy && (publish_time >= sbuddy->note_since)) {
3243 /* clean up in case no 'note' element is supplied
3244 * which indicate note removal in client
3246 g_free(sbuddy->note);
3247 sbuddy->note = NULL;
3248 sbuddy->is_oof_note = FALSE;
3249 sbuddy->note_since = publish_time;
3251 xn_node = sipe_xml_child(xn_category, "note/body");
3252 if (xn_node) {
3253 char *tmp;
3254 sbuddy->note = g_markup_escape_text((tmp = sipe_xml_data(xn_node)), -1);
3255 g_free(tmp);
3256 sbuddy->is_oof_note = sipe_strequal(sipe_xml_attribute(xn_node, "type"), "OOF");
3257 sbuddy->note_since = publish_time;
3259 SIPE_DEBUG_INFO("process_incoming_notify_rlmi: uri(%s), note(%s)",
3260 uri, sbuddy->note ? sbuddy->note : "");
3262 /* to trigger UI refresh in case no status info is supplied in this update */
3263 do_update_status = TRUE;
3267 /* state */
3268 else if(sipe_strequal(attrVar, "state"))
3270 char *tmp;
3271 int availability;
3272 const sipe_xml *xn_availability;
3273 const sipe_xml *xn_activity;
3274 const sipe_xml *xn_meeting_subject;
3275 const sipe_xml *xn_meeting_location;
3276 struct sipe_buddy *sbuddy = uri ? g_hash_table_lookup(sipe_private->buddies, uri) : NULL;
3278 xn_node = sipe_xml_child(xn_category, "state");
3279 if (!xn_node) continue;
3280 xn_availability = sipe_xml_child(xn_node, "availability");
3281 if (!xn_availability) continue;
3282 xn_activity = sipe_xml_child(xn_node, "activity");
3283 xn_meeting_subject = sipe_xml_child(xn_node, "meetingSubject");
3284 xn_meeting_location = sipe_xml_child(xn_node, "meetingLocation");
3286 tmp = sipe_xml_data(xn_availability);
3287 availability = atoi(tmp);
3288 g_free(tmp);
3290 /* activity, meeting_subject, meeting_location */
3291 if (sbuddy) {
3292 char *tmp = NULL;
3294 /* activity */
3295 g_free(sbuddy->activity);
3296 sbuddy->activity = NULL;
3297 if (xn_activity) {
3298 const char *token = sipe_xml_attribute(xn_activity, "token");
3299 const sipe_xml *xn_custom = sipe_xml_child(xn_activity, "custom");
3301 /* from token */
3302 if (!is_empty(token)) {
3303 sbuddy->activity = g_strdup(sipe_get_activity_desc_by_token(token));
3305 /* from custom element */
3306 if (xn_custom) {
3307 char *custom = sipe_xml_data(xn_custom);
3309 if (!is_empty(custom)) {
3310 sbuddy->activity = custom;
3311 custom = NULL;
3313 g_free(custom);
3316 /* meeting_subject */
3317 g_free(sbuddy->meeting_subject);
3318 sbuddy->meeting_subject = NULL;
3319 if (xn_meeting_subject) {
3320 char *meeting_subject = sipe_xml_data(xn_meeting_subject);
3322 if (!is_empty(meeting_subject)) {
3323 sbuddy->meeting_subject = meeting_subject;
3324 meeting_subject = NULL;
3326 g_free(meeting_subject);
3328 /* meeting_location */
3329 g_free(sbuddy->meeting_location);
3330 sbuddy->meeting_location = NULL;
3331 if (xn_meeting_location) {
3332 char *meeting_location = sipe_xml_data(xn_meeting_location);
3334 if (!is_empty(meeting_location)) {
3335 sbuddy->meeting_location = meeting_location;
3336 meeting_location = NULL;
3338 g_free(meeting_location);
3341 status = sipe_get_status_by_availability(availability, &tmp);
3342 if (sbuddy->activity && tmp) {
3343 char *tmp2 = sbuddy->activity;
3345 sbuddy->activity = g_strdup_printf("%s, %s", sbuddy->activity, tmp);
3346 g_free(tmp);
3347 g_free(tmp2);
3348 } else if (tmp) {
3349 sbuddy->activity = tmp;
3353 do_update_status = TRUE;
3355 /* calendarData */
3356 else if(sipe_strequal(attrVar, "calendarData"))
3358 struct sipe_buddy *sbuddy = uri ? g_hash_table_lookup(sipe_private->buddies, uri) : NULL;
3359 const sipe_xml *xn_free_busy = sipe_xml_child(xn_category, "calendarData/freeBusy");
3360 const sipe_xml *xn_working_hours = sipe_xml_child(xn_category, "calendarData/WorkingHours");
3362 if (sbuddy && xn_free_busy) {
3363 if (!has_free_busy_cleaned) {
3364 has_free_busy_cleaned = TRUE;
3366 g_free(sbuddy->cal_start_time);
3367 sbuddy->cal_start_time = NULL;
3369 g_free(sbuddy->cal_free_busy_base64);
3370 sbuddy->cal_free_busy_base64 = NULL;
3372 g_free(sbuddy->cal_free_busy);
3373 sbuddy->cal_free_busy = NULL;
3375 sbuddy->cal_free_busy_published = publish_time;
3378 if (publish_time >= sbuddy->cal_free_busy_published) {
3379 g_free(sbuddy->cal_start_time);
3380 sbuddy->cal_start_time = g_strdup(sipe_xml_attribute(xn_free_busy, "startTime"));
3382 sbuddy->cal_granularity = sipe_strcase_equal(sipe_xml_attribute(xn_free_busy, "granularity"), "PT15M") ?
3383 15 : 0;
3385 g_free(sbuddy->cal_free_busy_base64);
3386 sbuddy->cal_free_busy_base64 = sipe_xml_data(xn_free_busy);
3388 g_free(sbuddy->cal_free_busy);
3389 sbuddy->cal_free_busy = NULL;
3391 sbuddy->cal_free_busy_published = publish_time;
3393 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);
3397 if (sbuddy && xn_working_hours) {
3398 sipe_cal_parse_working_hours(xn_working_hours, sbuddy);
3403 if (do_update_status) {
3404 if (!status) { /* no status category in this update, using contact's current status */
3405 PurpleBuddy *pbuddy = purple_find_buddy((PurpleAccount *)sip->account, uri);
3406 const PurplePresence *presence = purple_buddy_get_presence(pbuddy);
3407 const PurpleStatus *pstatus = purple_presence_get_active_status(presence);
3408 status = purple_status_get_id(pstatus);
3411 SIPE_DEBUG_INFO("process_incoming_notify_rlmi: %s", status);
3412 sipe_core_buddy_got_status(SIPE_CORE_PUBLIC, uri, status);
3415 sipe_xml_free(xn_categories);
3418 static void sipe_subscribe_poolfqdn_resource_uri(const char *host,
3419 GSList *server,
3420 struct sipe_core_private *sipe_private)
3422 struct presence_batched_routed *payload = g_malloc(sizeof(struct presence_batched_routed));
3423 SIPE_DEBUG_INFO("process_incoming_notify_rlmi_resub: pool(%s)", host);
3424 payload->host = g_strdup(host);
3425 payload->buddies = server;
3426 sipe_subscribe_presence_batched_routed(sipe_private,
3427 payload);
3428 sipe_subscribe_presence_batched_routed_free(payload);
3431 static void process_incoming_notify_rlmi_resub(struct sipe_core_private *sipe_private,
3432 const gchar *data, unsigned len)
3434 sipe_xml *xn_list;
3435 const sipe_xml *xn_resource;
3436 GHashTable *servers = g_hash_table_new_full(g_str_hash, g_str_equal,
3437 g_free, NULL);
3438 GSList *server;
3439 gchar *host;
3441 xn_list = sipe_xml_parse(data, len);
3443 for (xn_resource = sipe_xml_child(xn_list, "resource");
3444 xn_resource;
3445 xn_resource = sipe_xml_twin(xn_resource) )
3447 const char *uri, *state;
3448 const sipe_xml *xn_instance;
3450 xn_instance = sipe_xml_child(xn_resource, "instance");
3451 if (!xn_instance) continue;
3453 uri = sipe_xml_attribute(xn_resource, "uri");
3454 state = sipe_xml_attribute(xn_instance, "state");
3455 SIPE_DEBUG_INFO("process_incoming_notify_rlmi_resub: uri(%s),state(%s)", uri, state);
3457 if (strstr(state, "resubscribe")) {
3458 const char *poolFqdn = sipe_xml_attribute(xn_instance, "poolFqdn");
3460 if (poolFqdn) { //[MS-PRES] Section 3.4.5.1.3 Processing Details
3461 gchar *user = g_strdup(uri);
3462 host = g_strdup(poolFqdn);
3463 server = g_hash_table_lookup(servers, host);
3464 server = g_slist_append(server, user);
3465 g_hash_table_insert(servers, host, server);
3466 } else {
3467 sipe_subscribe_presence_single(sipe_private,
3468 (void *) uri);
3473 /* Send out any deferred poolFqdn subscriptions */
3474 g_hash_table_foreach(servers, (GHFunc) sipe_subscribe_poolfqdn_resource_uri, sipe_private);
3475 g_hash_table_destroy(servers);
3477 sipe_xml_free(xn_list);
3480 static void process_incoming_notify_pidf(struct sipe_core_private *sipe_private,
3481 const gchar *data, unsigned len)
3483 gchar *uri;
3484 gchar *getbasic;
3485 gchar *activity = NULL;
3486 sipe_xml *pidf;
3487 const sipe_xml *basicstatus = NULL, *tuple, *status;
3488 gboolean isonline = FALSE;
3489 const sipe_xml *display_name_node;
3491 pidf = sipe_xml_parse(data, len);
3492 if (!pidf) {
3493 SIPE_DEBUG_INFO("process_incoming_notify_pidf: no parseable pidf:%s", data);
3494 return;
3497 if ((tuple = sipe_xml_child(pidf, "tuple")))
3499 if ((status = sipe_xml_child(tuple, "status"))) {
3500 basicstatus = sipe_xml_child(status, "basic");
3504 if (!basicstatus) {
3505 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_notify_pidf: no basic found");
3506 sipe_xml_free(pidf);
3507 return;
3510 getbasic = sipe_xml_data(basicstatus);
3511 if (!getbasic) {
3512 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_notify_pidf: no basic data found");
3513 sipe_xml_free(pidf);
3514 return;
3517 SIPE_DEBUG_INFO("process_incoming_notify_pidf: basic-status(%s)", getbasic);
3518 if (strstr(getbasic, "open")) {
3519 isonline = TRUE;
3521 g_free(getbasic);
3523 uri = sip_uri(sipe_xml_attribute(pidf, "entity")); /* with 'sip:' prefix */ /* AOL comes without the prefix */
3525 display_name_node = sipe_xml_child(pidf, "display-name");
3526 if (display_name_node) {
3527 char * display_name = sipe_xml_data(display_name_node);
3529 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_DISPLAY_NAME, display_name);
3530 g_free(display_name);
3533 if ((tuple = sipe_xml_child(pidf, "tuple"))) {
3534 if ((status = sipe_xml_child(tuple, "status"))) {
3535 if ((basicstatus = sipe_xml_child(status, "activities"))) {
3536 if ((basicstatus = sipe_xml_child(basicstatus, "activity"))) {
3537 activity = sipe_xml_data(basicstatus);
3538 SIPE_DEBUG_INFO("process_incoming_notify_pidf: activity(%s)", activity);
3544 if (isonline) {
3545 const gchar * status_id = NULL;
3546 if (activity) {
3547 if (sipe_strequal(activity, sipe_activity_map[SIPE_ACTIVITY_BUSY].token)) {
3548 status_id = SIPE_STATUS_ID_BUSY;
3549 } else if (sipe_strequal(activity, sipe_activity_map[SIPE_ACTIVITY_AWAY].token)) {
3550 status_id = SIPE_STATUS_ID_AWAY;
3554 if (!status_id) {
3555 status_id = SIPE_STATUS_ID_AVAILABLE;
3558 SIPE_DEBUG_INFO("process_incoming_notify_pidf: status_id(%s)", status_id);
3559 sipe_core_buddy_got_status(SIPE_CORE_PUBLIC, uri, status_id);
3560 } else {
3561 sipe_core_buddy_got_status(SIPE_CORE_PUBLIC, uri, SIPE_STATUS_ID_OFFLINE);
3564 g_free(activity);
3565 g_free(uri);
3566 sipe_xml_free(pidf);
3569 /** 2005 */
3570 static void
3571 sipe_user_info_has_updated(struct sipe_core_private *sipe_private,
3572 const sipe_xml *xn_userinfo)
3574 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
3575 const sipe_xml *xn_states;
3577 g_free(sip->user_states);
3578 sip->user_states = NULL;
3579 if ((xn_states = sipe_xml_child(xn_userinfo, "states")) != NULL) {
3580 gchar *orig = sip->user_states = sipe_xml_stringify(xn_states);
3582 /* this is a hack-around to remove added newline after inner element,
3583 * state in this case, where it shouldn't be.
3584 * After several use of sipe_xml_stringify, amount of added newlines
3585 * grows significantly.
3587 if (orig) {
3588 gchar c, *stripped = orig;
3589 while ((c = *orig++)) {
3590 if ((c != '\n') /* && (c != '\r') */) {
3591 *stripped++ = c;
3594 *stripped = '\0';
3598 /* Publish initial state if not yet.
3599 * Assuming this happens on initial responce to self subscription
3600 * so we've already updated our UserInfo.
3602 if (!sip->initial_state_published) {
3603 send_presence_soap(sipe_private, FALSE);
3604 /* dalayed run */
3605 sipe_schedule_seconds(sipe_private,
3606 "<+update-calendar>",
3607 NULL,
3608 UPDATE_CALENDAR_DELAY,
3609 (sipe_schedule_action) sipe_core_update_calendar,
3610 NULL);
3614 static void process_incoming_notify_msrtc(struct sipe_core_private *sipe_private,
3615 const gchar *data, unsigned len)
3617 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
3618 char *activity = NULL;
3619 const char *epid;
3620 const char *status_id = NULL;
3621 const char *name;
3622 char *uri;
3623 char *self_uri = sip_uri_self(sipe_private);
3624 int avl;
3625 int act;
3626 const char *device_name = NULL;
3627 const char *cal_start_time = NULL;
3628 const char *cal_granularity = NULL;
3629 char *cal_free_busy_base64 = NULL;
3630 struct sipe_buddy *sbuddy;
3631 const sipe_xml *node;
3632 sipe_xml *xn_presentity;
3633 const sipe_xml *xn_availability;
3634 const sipe_xml *xn_activity;
3635 const sipe_xml *xn_display_name;
3636 const sipe_xml *xn_email;
3637 const sipe_xml *xn_phone_number;
3638 const sipe_xml *xn_userinfo;
3639 const sipe_xml *xn_note;
3640 const sipe_xml *xn_oof;
3641 const sipe_xml *xn_state;
3642 const sipe_xml *xn_contact;
3643 char *note;
3644 int user_avail;
3645 const char *user_avail_nil;
3646 int res_avail;
3647 time_t user_avail_since = 0;
3648 time_t activity_since = 0;
3650 /* fix for Reuters environment on Linux */
3651 if (data && strstr(data, "encoding=\"utf-16\"")) {
3652 char *tmp_data;
3653 tmp_data = replace(data, "encoding=\"utf-16\"", "encoding=\"utf-8\"");
3654 xn_presentity = sipe_xml_parse(tmp_data, strlen(tmp_data));
3655 g_free(tmp_data);
3656 } else {
3657 xn_presentity = sipe_xml_parse(data, len);
3660 xn_availability = sipe_xml_child(xn_presentity, "availability");
3661 xn_activity = sipe_xml_child(xn_presentity, "activity");
3662 xn_display_name = sipe_xml_child(xn_presentity, "displayName");
3663 xn_email = sipe_xml_child(xn_presentity, "email");
3664 xn_phone_number = sipe_xml_child(xn_presentity, "phoneNumber");
3665 xn_userinfo = sipe_xml_child(xn_presentity, "userInfo");
3666 xn_oof = xn_userinfo ? sipe_xml_child(xn_userinfo, "oof") : NULL;
3667 xn_state = xn_userinfo ? sipe_xml_child(xn_userinfo, "states/state"): NULL;
3668 user_avail = xn_state ? sipe_xml_int_attribute(xn_state, "avail", 0) : 0;
3669 user_avail_since = xn_state ? sipe_utils_str_to_time(sipe_xml_attribute(xn_state, "since")) : 0;
3670 user_avail_nil = xn_state ? sipe_xml_attribute(xn_state, "nil") : NULL;
3671 xn_contact = xn_userinfo ? sipe_xml_child(xn_userinfo, "contact") : NULL;
3672 xn_note = xn_userinfo ? sipe_xml_child(xn_userinfo, "note") : NULL;
3673 note = xn_note ? sipe_xml_data(xn_note) : NULL;
3675 if (sipe_strequal(user_avail_nil, "true")) { /* null-ed */
3676 user_avail = 0;
3677 user_avail_since = 0;
3680 name = sipe_xml_attribute(xn_presentity, "uri"); /* without 'sip:' prefix */
3681 uri = sip_uri_from_name(name);
3682 avl = sipe_xml_int_attribute(xn_availability, "aggregate", 0);
3683 epid = sipe_xml_attribute(xn_availability, "epid");
3684 act = sipe_xml_int_attribute(xn_activity, "aggregate", 0);
3686 status_id = sipe_get_status_by_act_avail_2005(act, avl, &activity);
3687 res_avail = sipe_get_availability_by_status(status_id, NULL);
3688 if (user_avail > res_avail) {
3689 res_avail = user_avail;
3690 status_id = sipe_get_status_by_availability(user_avail, NULL);
3693 if (xn_display_name) {
3694 char *display_name = g_strdup(sipe_xml_attribute(xn_display_name, "displayName"));
3695 char *email = xn_email ? g_strdup(sipe_xml_attribute(xn_email, "email")) : NULL;
3696 char *phone_label = xn_phone_number ? g_strdup(sipe_xml_attribute(xn_phone_number, "label")) : NULL;
3697 char *phone_number = xn_phone_number ? g_strdup(sipe_xml_attribute(xn_phone_number, "number")) : NULL;
3698 char *tel_uri = sip_to_tel_uri(phone_number);
3700 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_DISPLAY_NAME, display_name);
3701 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_EMAIL, email);
3702 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_WORK_PHONE, tel_uri);
3703 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_WORK_PHONE_DISPLAY, !is_empty(phone_label) ? phone_label : phone_number);
3705 g_free(tel_uri);
3706 g_free(phone_label);
3707 g_free(phone_number);
3708 g_free(email);
3709 g_free(display_name);
3712 if (xn_contact) {
3713 /* tel */
3714 for (node = sipe_xml_child(xn_contact, "tel"); node; node = sipe_xml_twin(node))
3716 /* Ex.: <tel type="work">tel:+3222220000</tel> */
3717 const char *phone_type = sipe_xml_attribute(node, "type");
3718 char* phone = sipe_xml_data(node);
3720 sipe_update_user_phone(sipe_private, uri, phone_type, phone, NULL);
3722 g_free(phone);
3726 /* devicePresence */
3727 for (node = sipe_xml_child(xn_presentity, "devices/devicePresence"); node; node = sipe_xml_twin(node)) {
3728 const sipe_xml *xn_device_name;
3729 const sipe_xml *xn_calendar_info;
3730 const sipe_xml *xn_state;
3731 char *state;
3733 /* deviceName */
3734 if (sipe_strequal(sipe_xml_attribute(node, "epid"), epid)) {
3735 xn_device_name = sipe_xml_child(node, "deviceName");
3736 device_name = xn_device_name ? sipe_xml_attribute(xn_device_name, "name") : NULL;
3739 /* calendarInfo */
3740 xn_calendar_info = sipe_xml_child(node, "calendarInfo");
3741 if (xn_calendar_info) {
3742 const char *cal_start_time_tmp = sipe_xml_attribute(xn_calendar_info, "startTime");
3744 if (cal_start_time) {
3745 time_t cal_start_time_t = sipe_utils_str_to_time(cal_start_time);
3746 time_t cal_start_time_t_tmp = sipe_utils_str_to_time(cal_start_time_tmp);
3748 if (cal_start_time_t_tmp > cal_start_time_t) {
3749 cal_start_time = cal_start_time_tmp;
3750 cal_granularity = sipe_xml_attribute(xn_calendar_info, "granularity");
3751 g_free(cal_free_busy_base64);
3752 cal_free_busy_base64 = sipe_xml_data(xn_calendar_info);
3754 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);
3756 } else {
3757 cal_start_time = cal_start_time_tmp;
3758 cal_granularity = sipe_xml_attribute(xn_calendar_info, "granularity");
3759 g_free(cal_free_busy_base64);
3760 cal_free_busy_base64 = sipe_xml_data(xn_calendar_info);
3762 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);
3766 /* state */
3767 xn_state = sipe_xml_child(node, "states/state");
3768 if (xn_state) {
3769 int dev_avail = sipe_xml_int_attribute(xn_state, "avail", 0);
3770 time_t dev_avail_since = sipe_utils_str_to_time(sipe_xml_attribute(xn_state, "since"));
3772 state = sipe_xml_data(xn_state);
3773 if (dev_avail_since > user_avail_since &&
3774 dev_avail >= res_avail)
3776 res_avail = dev_avail;
3777 if (!is_empty(state))
3779 if (sipe_strequal(state, sipe_activity_map[SIPE_ACTIVITY_ON_PHONE].token)) {
3780 g_free(activity);
3781 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_ON_PHONE));
3782 } else if (sipe_strequal(state, "presenting")) {
3783 g_free(activity);
3784 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_IN_CONF));
3785 } else {
3786 activity = state;
3787 state = NULL;
3789 activity_since = dev_avail_since;
3791 status_id = sipe_get_status_by_availability(res_avail, &activity);
3793 g_free(state);
3797 /* oof */
3798 if (xn_oof && res_avail >= 15000) { /* 12000 in 2007 */
3799 g_free(activity);
3800 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_OOF));
3801 activity_since = 0;
3804 sbuddy = g_hash_table_lookup(sipe_private->buddies, uri);
3805 if (sbuddy)
3807 g_free(sbuddy->activity);
3808 sbuddy->activity = activity;
3809 activity = NULL;
3811 sbuddy->activity_since = activity_since;
3813 sbuddy->user_avail = user_avail;
3814 sbuddy->user_avail_since = user_avail_since;
3816 g_free(sbuddy->note);
3817 sbuddy->note = NULL;
3818 if (!is_empty(note)) { sbuddy->note = g_markup_escape_text(note, -1); }
3820 sbuddy->is_oof_note = (xn_oof != NULL);
3822 g_free(sbuddy->device_name);
3823 sbuddy->device_name = NULL;
3824 if (!is_empty(device_name)) { sbuddy->device_name = g_strdup(device_name); }
3826 if (!is_empty(cal_free_busy_base64)) {
3827 g_free(sbuddy->cal_start_time);
3828 sbuddy->cal_start_time = g_strdup(cal_start_time);
3830 sbuddy->cal_granularity = sipe_strcase_equal(cal_granularity, "PT15M") ? 15 : 0;
3832 g_free(sbuddy->cal_free_busy_base64);
3833 sbuddy->cal_free_busy_base64 = cal_free_busy_base64;
3834 cal_free_busy_base64 = NULL;
3836 g_free(sbuddy->cal_free_busy);
3837 sbuddy->cal_free_busy = NULL;
3840 sbuddy->last_non_cal_status_id = status_id;
3841 g_free(sbuddy->last_non_cal_activity);
3842 sbuddy->last_non_cal_activity = g_strdup(sbuddy->activity);
3844 if (sipe_strcase_equal(sbuddy->name, self_uri)) {
3845 if (!sipe_strequal(sbuddy->note, sip->note)) /* not same */
3847 sip->is_oof_note = sbuddy->is_oof_note;
3849 g_free(sip->note);
3850 sip->note = g_strdup(sbuddy->note);
3852 sip->note_since = time(NULL);
3855 g_free(sip->status);
3856 sip->status = g_strdup(sbuddy->last_non_cal_status_id);
3859 g_free(cal_free_busy_base64);
3860 g_free(activity);
3862 SIPE_DEBUG_INFO("process_incoming_notify_msrtc: status(%s)", status_id);
3863 sipe_core_buddy_got_status(SIPE_CORE_PUBLIC, uri, status_id);
3865 if (!SIPE_CORE_PRIVATE_FLAG_IS(OCS2007) && sipe_strcase_equal(self_uri, uri)) {
3866 sipe_user_info_has_updated(sipe_private, xn_userinfo);
3869 g_free(note);
3870 sipe_xml_free(xn_presentity);
3871 g_free(uri);
3872 g_free(self_uri);
3875 static void sipe_presence_mime_cb(gpointer user_data, /* sipe_core_private */
3876 const GSList *fields,
3877 const gchar *body,
3878 gsize length)
3880 const gchar *type = sipe_utils_nameval_find(fields, "Content-Type");
3882 if (strstr(type,"application/rlmi+xml")) {
3883 process_incoming_notify_rlmi_resub(user_data, body, length);
3884 } else if (strstr(type, "text/xml+msrtc.pidf")) {
3885 process_incoming_notify_msrtc(user_data, body, length);
3886 } else {
3887 process_incoming_notify_rlmi(user_data, body, length);
3891 static void sipe_process_presence(struct sipe_core_private *sipe_private,
3892 struct sipmsg *msg)
3894 const char *ctype = sipmsg_find_header(msg, "Content-Type");
3896 SIPE_DEBUG_INFO("sipe_process_presence: Content-Type: %s", ctype ? ctype : "");
3898 if (ctype &&
3899 (strstr(ctype, "application/rlmi+xml") ||
3900 strstr(ctype, "application/msrtc-event-categories+xml")))
3902 if (strstr(ctype, "multipart"))
3904 sipe_mime_parts_foreach(ctype, msg->body, sipe_presence_mime_cb, sipe_private);
3906 else if(strstr(ctype, "application/msrtc-event-categories+xml") )
3908 process_incoming_notify_rlmi(sipe_private, msg->body, msg->bodylen);
3910 else if(strstr(ctype, "application/rlmi+xml"))
3912 process_incoming_notify_rlmi_resub(sipe_private, msg->body, msg->bodylen);
3915 else if(ctype && strstr(ctype, "text/xml+msrtc.pidf"))
3917 process_incoming_notify_msrtc(sipe_private, msg->body, msg->bodylen);
3919 else
3921 process_incoming_notify_pidf(sipe_private, msg->body, msg->bodylen);
3925 static void sipe_presence_timeout_mime_cb(gpointer user_data,
3926 SIPE_UNUSED_PARAMETER const GSList *fields,
3927 const gchar *body,
3928 gsize length)
3930 GSList **buddies = user_data;
3931 sipe_xml *xml = sipe_xml_parse(body, length);
3933 if (xml && !sipe_strequal(sipe_xml_name(xml), "list")) {
3934 const gchar *uri = sipe_xml_attribute(xml, "uri");
3935 const sipe_xml *xn_category;
3938 * automaton: presence is never expected to change
3940 * see: http://msdn.microsoft.com/en-us/library/ee354295(office.13).aspx
3942 for (xn_category = sipe_xml_child(xml, "category");
3943 xn_category;
3944 xn_category = sipe_xml_twin(xn_category)) {
3945 if (sipe_strequal(sipe_xml_attribute(xn_category, "name"),
3946 "contactCard")) {
3947 const sipe_xml *node = sipe_xml_child(xn_category, "contactCard/automaton");
3948 if (node) {
3949 char *boolean = sipe_xml_data(node);
3950 if (sipe_strequal(boolean, "true")) {
3951 SIPE_DEBUG_INFO("sipe_process_presence_timeout: %s is an automaton: - not subscribing to presence updates",
3952 uri);
3953 uri = NULL;
3955 g_free(boolean);
3957 break;
3961 if (uri) {
3962 *buddies = g_slist_append(*buddies, sip_uri(uri));
3966 sipe_xml_free(xml);
3969 static void sipe_process_presence_timeout(struct sipe_core_private *sipe_private,
3970 struct sipmsg *msg, gchar *who,
3971 int timeout)
3973 const char *ctype = sipmsg_find_header(msg, "Content-Type");
3974 gchar *action_name = sipe_utils_presence_key(who);
3976 SIPE_DEBUG_INFO("sipe_process_presence_timeout: Content-Type: %s", ctype ? ctype : "");
3978 if (ctype &&
3979 strstr(ctype, "multipart") &&
3980 (strstr(ctype, "application/rlmi+xml") ||
3981 strstr(ctype, "application/msrtc-event-categories+xml"))) {
3982 GSList *buddies = NULL;
3984 sipe_mime_parts_foreach(ctype, msg->body, sipe_presence_timeout_mime_cb, &buddies);
3986 if (buddies) {
3987 struct presence_batched_routed *payload = g_malloc(sizeof(struct presence_batched_routed));
3988 payload->host = g_strdup(who);
3989 payload->buddies = buddies;
3990 sipe_schedule_seconds(sipe_private,
3991 action_name,
3992 payload,
3993 timeout,
3994 sipe_subscribe_presence_batched_routed,
3995 sipe_subscribe_presence_batched_routed_free);
3996 SIPE_DEBUG_INFO("Resubscription multiple contacts with batched support & route(%s) in %d", who, timeout);
3999 } else {
4000 sipe_schedule_seconds(sipe_private,
4001 action_name,
4002 g_strdup(who),
4003 timeout,
4004 sipe_subscribe_presence_single,
4005 g_free);
4006 SIPE_DEBUG_INFO("Resubscription single contact with batched support(%s) in %d", who, timeout);
4008 g_free(action_name);
4012 * Dispatcher for all incoming subscription information
4013 * whether it comes from NOTIFY, BENOTIFY requests or
4014 * piggy-backed to subscription's OK responce.
4016 * @param request whether initiated from BE/NOTIFY request or OK-response message.
4017 * @param benotify whether initiated from NOTIFY or BENOTIFY request.
4019 void process_incoming_notify(struct sipe_core_private *sipe_private,
4020 struct sipmsg *msg,
4021 gboolean request, gboolean benotify)
4023 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
4024 const gchar *content_type = sipmsg_find_header(msg, "Content-Type");
4025 const gchar *event = sipmsg_find_header(msg, "Event");
4026 const gchar *subscription_state = sipmsg_find_header(msg, "subscription-state");
4028 SIPE_DEBUG_INFO("process_incoming_notify: subscription_state: %s", subscription_state ? subscription_state : "");
4030 /* implicit subscriptions */
4031 if (content_type && g_str_has_prefix(content_type, "application/ms-imdn+xml")) {
4032 sipe_process_imdn(sipe_private, msg);
4035 if (event) {
4036 /* for one off subscriptions (send with Expire: 0) */
4037 if (sipe_strcase_equal(event, "vnd-microsoft-provisioning-v2"))
4039 sipe_process_provisioning_v2(sipe_private, msg);
4041 else if (sipe_strcase_equal(event, "vnd-microsoft-provisioning"))
4043 sipe_process_provisioning(sipe_private, msg);
4045 else if (sipe_strcase_equal(event, "presence"))
4047 sipe_process_presence(sipe_private, msg);
4049 else if (sipe_strcase_equal(event, "registration-notify"))
4051 sipe_process_registration_notify(sipe_private, msg);
4054 if (!subscription_state || strstr(subscription_state, "active"))
4056 if (sipe_strcase_equal(event, "vnd-microsoft-roaming-contacts"))
4058 sipe_process_roaming_contacts(sipe_private, msg);
4060 else if (sipe_strcase_equal(event, "vnd-microsoft-roaming-self"))
4062 sipe_process_roaming_self(sipe_private, msg);
4064 else if (sipe_strcase_equal(event, "vnd-microsoft-roaming-ACL"))
4066 sipe_process_roaming_acl(sipe_private, msg);
4068 else if (sipe_strcase_equal(event, "presence.wpending"))
4070 sipe_process_presence_wpending(sipe_private, msg);
4072 else if (sipe_strcase_equal(event, "conference"))
4074 sipe_process_conference(sipe_private, msg);
4079 /* The server sends status 'terminated' */
4080 if (subscription_state && strstr(subscription_state, "terminated") ) {
4081 gchar *who = parse_from(sipmsg_find_header(msg, request ? "From" : "To"));
4082 gchar *key = sipe_utils_subscription_key(event, who);
4084 SIPE_DEBUG_INFO("process_incoming_notify: server says that subscription to %s was terminated.", who);
4085 g_free(who);
4087 sipe_subscriptions_remove(sipe_private, key);
4088 g_free(key);
4091 if (!request && event) {
4092 const gchar *expires_header = sipmsg_find_header(msg, "Expires");
4093 int timeout = expires_header ? strtol(expires_header, NULL, 10) : 0;
4094 SIPE_DEBUG_INFO("process_incoming_notify: subscription expires:%d", timeout);
4096 if (timeout) {
4097 /* 2 min ahead of expiration */
4098 timeout = (timeout - 120) > 120 ? (timeout - 120) : timeout;
4100 if (sipe_strcase_equal(event, "presence.wpending") &&
4101 g_slist_find_custom(sip->allow_events, "presence.wpending", (GCompareFunc)g_ascii_strcasecmp))
4103 gchar *action_name = g_strdup_printf("<%s>", "presence.wpending");
4104 sipe_schedule_seconds(sipe_private,
4105 action_name,
4106 NULL,
4107 timeout,
4108 sipe_subscribe_presence_wpending,
4109 NULL);
4110 g_free(action_name);
4112 else if (sipe_strcase_equal(event, "presence") &&
4113 g_slist_find_custom(sip->allow_events, "presence", (GCompareFunc)g_ascii_strcasecmp))
4115 gchar *who = parse_from(sipmsg_find_header(msg, "To"));
4116 gchar *action_name = sipe_utils_presence_key(who);
4118 if (sip->batched_support) {
4119 sipe_process_presence_timeout(sipe_private, msg, who, timeout);
4121 else {
4122 sipe_schedule_seconds(sipe_private,
4123 action_name,
4124 g_strdup(who),
4125 timeout,
4126 sipe_subscribe_presence_single,
4127 g_free);
4128 SIPE_DEBUG_INFO("Resubscription single contact (%s) in %d", who, timeout);
4130 g_free(action_name);
4131 g_free(who);
4136 /* The client responses on received a NOTIFY message */
4137 if (request && !benotify)
4139 sip_transport_response(sipe_private, msg, 200, "OK", NULL);
4144 * Whether user manually changed status or
4145 * it was changed automatically due to user
4146 * became inactive/active again
4148 static gboolean
4149 sipe_is_user_state(struct sipe_core_private *sipe_private)
4151 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
4152 gboolean res;
4153 time_t now = time(NULL);
4155 SIPE_DEBUG_INFO("sipe_is_user_state: sip->idle_switch : %s", asctime(localtime(&(sip->idle_switch))));
4156 SIPE_DEBUG_INFO("sipe_is_user_state: now : %s", asctime(localtime(&now)));
4158 res = ((now - SIPE_IDLE_SET_DELAY * 2) >= sip->idle_switch);
4160 SIPE_DEBUG_INFO("sipe_is_user_state: res = %s", res ? "USER" : "MACHINE");
4161 return res;
4164 static void
4165 send_presence_soap0(struct sipe_core_private *sipe_private,
4166 gboolean do_publish_calendar,
4167 gboolean do_reset_status)
4169 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
4170 struct sipe_calendar* cal = sip->cal;
4171 int availability = 0;
4172 int activity = 0;
4173 gchar *body;
4174 gchar *tmp;
4175 gchar *tmp2 = NULL;
4176 gchar *res_note = NULL;
4177 gchar *res_oof = NULL;
4178 const gchar *note_pub = NULL;
4179 gchar *states = NULL;
4180 gchar *calendar_data = NULL;
4181 gchar *epid = get_epid(sipe_private);
4182 time_t now = time(NULL);
4183 gchar *since_time_str = sipe_utils_time_to_str(now);
4184 const gchar *oof_note = cal ? sipe_ews_get_oof_note(cal) : NULL;
4185 const char *user_input;
4186 gboolean pub_oof = cal && oof_note && (!sip->note || cal->updated > sip->note_since);
4188 if (oof_note && sip->note) {
4189 SIPE_DEBUG_INFO("cal->oof_start : %s", asctime(localtime(&(cal->oof_start))));
4190 SIPE_DEBUG_INFO("sip->note_since : %s", asctime(localtime(&(sip->note_since))));
4193 SIPE_DEBUG_INFO("sip->note : %s", sip->note ? sip->note : "");
4195 if (!sip->initial_state_published ||
4196 do_reset_status)
4198 g_free(sip->status);
4199 sip->status = g_strdup(SIPE_STATUS_ID_AVAILABLE);
4202 sipe_get_act_avail_by_status_2005(sip->status, &activity, &availability);
4204 /* Note */
4205 if (pub_oof) {
4206 note_pub = oof_note;
4207 res_oof = SIPE_SOAP_SET_PRESENCE_OOF_XML;
4208 cal->published = TRUE;
4209 } else if (sip->note) {
4210 if (sip->is_oof_note && !oof_note) { /* stale OOF note, as it's not present in cal already */
4211 g_free(sip->note);
4212 sip->note = NULL;
4213 sip->is_oof_note = FALSE;
4214 sip->note_since = 0;
4215 } else {
4216 note_pub = sip->note;
4217 res_oof = sip->is_oof_note ? SIPE_SOAP_SET_PRESENCE_OOF_XML : "";
4221 if (note_pub)
4223 /* to protocol internal plain text format */
4224 tmp = sipe_backend_markup_strip_html(note_pub);
4225 res_note = g_markup_printf_escaped(SIPE_SOAP_SET_PRESENCE_NOTE_XML, tmp);
4226 g_free(tmp);
4229 /* User State */
4230 if (!do_reset_status) {
4231 if (sipe_is_user_state(sipe_private) && !do_publish_calendar && sip->initial_state_published)
4233 gchar *activity_token = NULL;
4234 int avail_2007 = sipe_get_availability_by_status(sip->status, &activity_token);
4236 states = g_strdup_printf(SIPE_SOAP_SET_PRESENCE_STATES,
4237 avail_2007,
4238 since_time_str,
4239 epid,
4240 activity_token);
4241 g_free(activity_token);
4243 else /* preserve existing publication */
4245 if (sip->user_states) {
4246 states = g_strdup(sip->user_states);
4249 } else {
4250 /* do nothing - then User state will be erased */
4252 sip->initial_state_published = TRUE;
4254 /* CalendarInfo */
4255 if (cal && (!is_empty(cal->legacy_dn) || !is_empty(cal->email)) && cal->fb_start && !is_empty(cal->free_busy))
4257 char *fb_start_str = sipe_utils_time_to_str(cal->fb_start);
4258 char *free_busy_base64 = sipe_cal_get_freebusy_base64(cal->free_busy);
4259 calendar_data = g_strdup_printf(SIPE_SOAP_SET_PRESENCE_CALENDAR,
4260 !is_empty(cal->legacy_dn) ? cal->legacy_dn : cal->email,
4261 fb_start_str,
4262 free_busy_base64);
4263 g_free(fb_start_str);
4264 g_free(free_busy_base64);
4267 user_input = (sipe_is_user_state(sipe_private) ||
4268 sipe_strequal(sip->status, SIPE_STATUS_ID_AVAILABLE)) ?
4269 "active" : "idle";
4271 /* forming resulting XML */
4272 body = g_strdup_printf(SIPE_SOAP_SET_PRESENCE,
4273 sipe_private->username,
4274 availability,
4275 activity,
4276 (tmp = g_ascii_strup(g_get_host_name(), -1)),
4277 res_note ? res_note : "",
4278 res_oof ? res_oof : "",
4279 states ? states : "",
4280 calendar_data ? calendar_data : "",
4281 epid,
4282 since_time_str,
4283 since_time_str,
4284 user_input);
4285 g_free(tmp);
4286 g_free(tmp2);
4287 g_free(res_note);
4288 g_free(states);
4289 g_free(calendar_data);
4291 send_soap_request(sipe_private, body);
4293 g_free(body);
4294 g_free(since_time_str);
4295 g_free(epid);
4298 void
4299 send_presence_soap(struct sipe_core_private *sipe_private,
4300 gboolean do_publish_calendar)
4302 return send_presence_soap0(sipe_private, do_publish_calendar, FALSE);
4306 static gboolean
4307 process_send_presence_category_publish_response(struct sipe_core_private *sipe_private,
4308 struct sipmsg *msg,
4309 struct transaction *trans)
4311 const gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
4313 if (msg->response == 409 && g_str_has_prefix(contenttype, "application/msrtc-fault+xml")) {
4314 sipe_xml *xml;
4315 const sipe_xml *node;
4316 gchar *fault_code;
4317 GHashTable *faults;
4318 int index_our;
4319 gboolean has_device_publication = FALSE;
4321 xml = sipe_xml_parse(msg->body, msg->bodylen);
4323 /* test if version mismatch fault */
4324 fault_code = sipe_xml_data(sipe_xml_child(xml, "Faultcode"));
4325 if (!sipe_strequal(fault_code, "Client.BadCall.WrongDelta")) {
4326 SIPE_DEBUG_INFO("process_send_presence_category_publish_response: unsupported fault code:%s returning.", fault_code);
4327 g_free(fault_code);
4328 sipe_xml_free(xml);
4329 return TRUE;
4331 g_free(fault_code);
4333 /* accumulating information about faulty versions */
4334 faults = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
4335 for (node = sipe_xml_child(xml, "details/operation");
4336 node;
4337 node = sipe_xml_twin(node))
4339 const gchar *index = sipe_xml_attribute(node, "index");
4340 const gchar *curVersion = sipe_xml_attribute(node, "curVersion");
4342 g_hash_table_insert(faults, g_strdup(index), g_strdup(curVersion));
4343 SIPE_DEBUG_INFO("fault added: index:%s curVersion:%s", index, curVersion);
4345 sipe_xml_free(xml);
4347 /* here we are parsing our own request to figure out what publication
4348 * referenced here only by index went wrong
4350 xml = sipe_xml_parse(trans->msg->body, trans->msg->bodylen);
4352 /* publication */
4353 for (node = sipe_xml_child(xml, "publications/publication"),
4354 index_our = 1; /* starts with 1 - our first publication */
4355 node;
4356 node = sipe_xml_twin(node), index_our++)
4358 gchar *idx = g_strdup_printf("%d", index_our);
4359 const gchar *curVersion = g_hash_table_lookup(faults, idx);
4360 const gchar *categoryName = sipe_xml_attribute(node, "categoryName");
4361 g_free(idx);
4363 if (sipe_strequal("device", categoryName)) {
4364 has_device_publication = TRUE;
4367 if (curVersion) { /* fault exist on this index */
4368 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
4369 const gchar *container = sipe_xml_attribute(node, "container");
4370 const gchar *instance = sipe_xml_attribute(node, "instance");
4371 /* key is <category><instance><container> */
4372 gchar *key = g_strdup_printf("<%s><%s><%s>", categoryName, instance, container);
4373 GHashTable *category = g_hash_table_lookup(sip->our_publications, categoryName);
4375 if (category) {
4376 struct sipe_publication *publication =
4377 g_hash_table_lookup(category, key);
4379 SIPE_DEBUG_INFO("key is %s", key);
4381 if (publication) {
4382 SIPE_DEBUG_INFO("Updating %s with version %s. Was %d before.",
4383 key, curVersion, publication->version);
4384 /* updating publication's version to the correct one */
4385 publication->version = atoi(curVersion);
4387 } else {
4388 /* We somehow lost this category from our publications... */
4389 struct sipe_publication *publication = g_new0(struct sipe_publication, 1);
4390 publication->category = g_strdup(categoryName);
4391 publication->instance = atoi(instance);
4392 publication->container = atoi(container);
4393 publication->version = atoi(curVersion);
4394 category = g_hash_table_new_full(g_str_hash, g_str_equal,
4395 g_free, (GDestroyNotify)free_publication);
4396 g_hash_table_insert(category, g_strdup(key), publication);
4397 g_hash_table_insert(sip->our_publications, g_strdup(categoryName), category);
4398 SIPE_DEBUG_INFO("added lost category '%s' key '%s'", categoryName, key);
4400 g_free(key);
4403 sipe_xml_free(xml);
4404 g_hash_table_destroy(faults);
4406 /* rebublishing with right versions */
4407 if (has_device_publication) {
4408 send_publish_category_initial(sipe_private);
4409 } else {
4410 send_presence_status(sipe_private, NULL);
4413 return TRUE;
4417 * Returns 'device' XML part for publication.
4418 * Must be g_free'd after use.
4420 static gchar *
4421 sipe_publish_get_category_device(struct sipe_core_private *sipe_private)
4423 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
4424 gchar *uri;
4425 gchar *doc;
4426 gchar *epid = get_epid(sipe_private);
4427 gchar *uuid = generateUUIDfromEPID(epid);
4428 guint device_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_DEVICE);
4429 /* key is <category><instance><container> */
4430 gchar *key = g_strdup_printf("<%s><%u><%u>", "device", device_instance, 2);
4431 struct sipe_publication *publication =
4432 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "device"), key);
4434 g_free(key);
4435 g_free(epid);
4437 uri = sip_uri_self(sipe_private);
4438 doc = g_strdup_printf(SIPE_PUB_XML_DEVICE,
4439 device_instance,
4440 publication ? publication->version : 0,
4441 uuid,
4442 uri,
4443 "00:00:00+01:00", /* @TODO make timezone real*/
4444 g_get_host_name()
4447 g_free(uri);
4448 g_free(uuid);
4450 return doc;
4454 * A service method - use
4455 * - send_publish_get_category_state_machine and
4456 * - send_publish_get_category_state_user instead.
4457 * Must be g_free'd after use.
4459 static gchar *
4460 sipe_publish_get_category_state(struct sipe_core_private *sipe_private,
4461 gboolean is_user_state)
4463 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
4464 int availability = sipe_get_availability_by_status(sip->status, NULL);
4465 guint instance = is_user_state ? sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_USER) :
4466 sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_MACHINE);
4467 /* key is <category><instance><container> */
4468 gchar *key_2 = g_strdup_printf("<%s><%u><%u>", "state", instance, 2);
4469 gchar *key_3 = g_strdup_printf("<%s><%u><%u>", "state", instance, 3);
4470 struct sipe_publication *publication_2 =
4471 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_2);
4472 struct sipe_publication *publication_3 =
4473 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_3);
4475 g_free(key_2);
4476 g_free(key_3);
4478 if (publication_2 && (publication_2->availability == availability))
4480 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_state: state has NOT changed. Exiting.");
4481 return NULL; /* nothing to update */
4484 return g_strdup_printf( is_user_state ? SIPE_PUB_XML_STATE_USER : SIPE_PUB_XML_STATE_MACHINE,
4485 instance,
4486 publication_2 ? publication_2->version : 0,
4487 availability,
4488 instance,
4489 publication_3 ? publication_3->version : 0,
4490 availability);
4494 * Only Busy and OOF calendar event are published.
4495 * Different instances are used for that.
4497 * Must be g_free'd after use.
4499 static gchar *
4500 sipe_publish_get_category_state_calendar(struct sipe_core_private *sipe_private,
4501 struct sipe_cal_event *event,
4502 const char *uri,
4503 int cal_satus)
4505 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
4506 gchar *start_time_str;
4507 int availability = 0;
4508 gchar *res;
4509 gchar *tmp = NULL;
4510 guint instance = (cal_satus == SIPE_CAL_OOF) ?
4511 sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_CALENDAR_OOF) :
4512 sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_CALENDAR);
4514 /* key is <category><instance><container> */
4515 gchar *key_2 = g_strdup_printf("<%s><%u><%u>", "state", instance, 2);
4516 gchar *key_3 = g_strdup_printf("<%s><%u><%u>", "state", instance, 3);
4517 struct sipe_publication *publication_2 =
4518 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_2);
4519 struct sipe_publication *publication_3 =
4520 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_3);
4522 g_free(key_2);
4523 g_free(key_3);
4525 if (!publication_3 && !event) { /* was nothing, have nothing, exiting */
4526 SIPE_DEBUG_INFO("sipe_publish_get_category_state_calendar: "
4527 "Exiting as no publication and no event for cal_satus:%d", cal_satus);
4528 return NULL;
4531 if (event &&
4532 publication_3 &&
4533 (publication_3->availability == availability) &&
4534 sipe_strequal(publication_3->cal_event_hash, (tmp = sipe_cal_event_hash(event))))
4536 g_free(tmp);
4537 SIPE_DEBUG_INFO("sipe_publish_get_category_state_calendar: "
4538 "cal state has NOT changed for cal_satus:%d. Exiting.", cal_satus);
4539 return NULL; /* nothing to update */
4541 g_free(tmp);
4543 if (event &&
4544 (event->cal_status == SIPE_CAL_BUSY ||
4545 event->cal_status == SIPE_CAL_OOF))
4547 gchar *availability_xml_str = NULL;
4548 gchar *activity_xml_str = NULL;
4549 gchar *escaped_subject = event->subject ? g_markup_escape_text(event->subject, -1) : NULL;
4550 gchar *escaped_location = event->location ? g_markup_escape_text(event->location, -1) : NULL;
4552 if (event->cal_status == SIPE_CAL_BUSY) {
4553 availability_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_AVAIL, 6500);
4556 if (event->cal_status == SIPE_CAL_BUSY && event->is_meeting) {
4557 activity_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_ACTIVITY,
4558 sipe_activity_map[SIPE_ACTIVITY_IN_MEETING].token,
4559 "minAvailability=\"6500\"",
4560 "maxAvailability=\"8999\"");
4561 } else if (event->cal_status == SIPE_CAL_OOF) {
4562 activity_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_ACTIVITY,
4563 sipe_activity_map[SIPE_ACTIVITY_OOF].token,
4564 "minAvailability=\"12000\"",
4565 "");
4567 start_time_str = sipe_utils_time_to_str(event->start_time);
4569 res = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR,
4570 instance,
4571 publication_2 ? publication_2->version : 0,
4572 uri,
4573 start_time_str,
4574 availability_xml_str ? availability_xml_str : "",
4575 activity_xml_str ? activity_xml_str : "",
4576 escaped_subject ? escaped_subject : "",
4577 escaped_location ? escaped_location : "",
4579 instance,
4580 publication_3 ? publication_3->version : 0,
4581 uri,
4582 start_time_str,
4583 availability_xml_str ? availability_xml_str : "",
4584 activity_xml_str ? activity_xml_str : "",
4585 escaped_subject ? escaped_subject : "",
4586 escaped_location ? escaped_location : ""
4588 g_free(escaped_location);
4589 g_free(escaped_subject);
4590 g_free(start_time_str);
4591 g_free(availability_xml_str);
4592 g_free(activity_xml_str);
4595 else /* including !event, SIPE_CAL_FREE, SIPE_CAL_TENTATIVE */
4597 res = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_CLEAR,
4598 instance,
4599 publication_2 ? publication_2->version : 0,
4601 instance,
4602 publication_3 ? publication_3->version : 0
4606 return res;
4610 * Returns 'machineState' XML part for publication.
4611 * Must be g_free'd after use.
4613 static gchar *
4614 sipe_publish_get_category_state_machine(struct sipe_core_private *sipe_private)
4616 return sipe_publish_get_category_state(sipe_private, FALSE);
4620 * Returns 'userState' XML part for publication.
4621 * Must be g_free'd after use.
4623 static gchar *
4624 sipe_publish_get_category_state_user(struct sipe_core_private *sipe_private)
4626 return sipe_publish_get_category_state(sipe_private, TRUE);
4630 * Returns 'note' XML part for publication.
4631 * Must be g_free'd after use.
4633 * Protocol format for Note is plain text.
4635 * @param note a note in Sipe internal HTML format
4636 * @param note_type either personal or OOF
4638 static gchar *
4639 sipe_publish_get_category_note(struct sipe_core_private *sipe_private,
4640 const char *note, /* html */
4641 const char *note_type,
4642 time_t note_start,
4643 time_t note_end)
4645 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
4646 guint instance = sipe_strequal("OOF", note_type) ? sipe_get_pub_instance(sipe_private, SIPE_PUB_NOTE_OOF) : 0;
4647 /* key is <category><instance><container> */
4648 gchar *key_note_200 = g_strdup_printf("<%s><%u><%u>", "note", instance, 200);
4649 gchar *key_note_300 = g_strdup_printf("<%s><%u><%u>", "note", instance, 300);
4650 gchar *key_note_400 = g_strdup_printf("<%s><%u><%u>", "note", instance, 400);
4652 struct sipe_publication *publication_note_200 =
4653 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_200);
4654 struct sipe_publication *publication_note_300 =
4655 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_300);
4656 struct sipe_publication *publication_note_400 =
4657 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_400);
4659 char *tmp = note ? sipe_backend_markup_strip_html(note) : NULL;
4660 char *n1 = tmp ? g_markup_escape_text(tmp, -1) : NULL;
4661 const char *n2 = publication_note_200 ? publication_note_200->note : NULL;
4662 char *res, *tmp1, *tmp2, *tmp3;
4663 char *start_time_attr;
4664 char *end_time_attr;
4666 g_free(tmp);
4667 tmp = NULL;
4668 g_free(key_note_200);
4669 g_free(key_note_300);
4670 g_free(key_note_400);
4672 /* we even need to republish empty note */
4673 if (sipe_strequal(n1, n2))
4675 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_note: note has NOT changed. Exiting.");
4676 g_free(n1);
4677 return NULL; /* nothing to update */
4680 start_time_attr = note_start ? g_strdup_printf(" startTime=\"%s\"", (tmp = sipe_utils_time_to_str(note_start))) : NULL;
4681 g_free(tmp);
4682 tmp = NULL;
4683 end_time_attr = note_end ? g_strdup_printf(" endTime=\"%s\"", (tmp = sipe_utils_time_to_str(note_end))) : NULL;
4684 g_free(tmp);
4686 if (n1) {
4687 tmp1 = g_strdup_printf(SIPE_PUB_XML_NOTE,
4688 instance,
4689 200,
4690 publication_note_200 ? publication_note_200->version : 0,
4691 note_type,
4692 start_time_attr ? start_time_attr : "",
4693 end_time_attr ? end_time_attr : "",
4694 n1);
4696 tmp2 = g_strdup_printf(SIPE_PUB_XML_NOTE,
4697 instance,
4698 300,
4699 publication_note_300 ? publication_note_300->version : 0,
4700 note_type,
4701 start_time_attr ? start_time_attr : "",
4702 end_time_attr ? end_time_attr : "",
4703 n1);
4705 tmp3 = g_strdup_printf(SIPE_PUB_XML_NOTE,
4706 instance,
4707 400,
4708 publication_note_400 ? publication_note_400->version : 0,
4709 note_type,
4710 start_time_attr ? start_time_attr : "",
4711 end_time_attr ? end_time_attr : "",
4712 n1);
4713 } else {
4714 tmp1 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
4715 "note",
4716 instance,
4717 200,
4718 publication_note_200 ? publication_note_200->version : 0,
4719 "static");
4720 tmp2 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
4721 "note",
4722 instance,
4723 300,
4724 publication_note_200 ? publication_note_200->version : 0,
4725 "static");
4726 tmp3 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
4727 "note",
4728 instance,
4729 400,
4730 publication_note_200 ? publication_note_200->version : 0,
4731 "static");
4733 res = g_strconcat(tmp1, tmp2, tmp3, NULL);
4735 g_free(start_time_attr);
4736 g_free(end_time_attr);
4737 g_free(tmp1);
4738 g_free(tmp2);
4739 g_free(tmp3);
4740 g_free(n1);
4742 return res;
4746 * Returns 'calendarData' XML part with WorkingHours for publication.
4747 * Must be g_free'd after use.
4749 static gchar *
4750 sipe_publish_get_category_cal_working_hours(struct sipe_core_private *sipe_private)
4752 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
4753 struct sipe_calendar* cal = sip->cal;
4755 /* key is <category><instance><container> */
4756 gchar *key_cal_1 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 1);
4757 gchar *key_cal_100 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 100);
4758 gchar *key_cal_200 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 200);
4759 gchar *key_cal_300 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 300);
4760 gchar *key_cal_400 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 400);
4761 gchar *key_cal_32000 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 32000);
4763 struct sipe_publication *publication_cal_1 =
4764 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_1);
4765 struct sipe_publication *publication_cal_100 =
4766 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_100);
4767 struct sipe_publication *publication_cal_200 =
4768 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_200);
4769 struct sipe_publication *publication_cal_300 =
4770 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_300);
4771 struct sipe_publication *publication_cal_400 =
4772 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_400);
4773 struct sipe_publication *publication_cal_32000 =
4774 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_32000);
4776 const char *n1 = cal ? cal->working_hours_xml_str : NULL;
4777 const char *n2 = publication_cal_300 ? publication_cal_300->working_hours_xml_str : NULL;
4779 g_free(key_cal_1);
4780 g_free(key_cal_100);
4781 g_free(key_cal_200);
4782 g_free(key_cal_300);
4783 g_free(key_cal_400);
4784 g_free(key_cal_32000);
4786 if (!cal || is_empty(cal->email) || is_empty(cal->working_hours_xml_str)) {
4787 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_cal_working_hours: no data to publish, exiting");
4788 return NULL;
4791 if (sipe_strequal(n1, n2))
4793 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_cal_working_hours: WorkingHours has NOT changed. Exiting.");
4794 return NULL; /* nothing to update */
4797 return g_strdup_printf(SIPE_PUB_XML_WORKING_HOURS,
4798 /* 1 */
4799 publication_cal_1 ? publication_cal_1->version : 0,
4800 cal->email,
4801 cal->working_hours_xml_str,
4802 /* 100 - Public */
4803 publication_cal_100 ? publication_cal_100->version : 0,
4804 /* 200 - Company */
4805 publication_cal_200 ? publication_cal_200->version : 0,
4806 cal->email,
4807 cal->working_hours_xml_str,
4808 /* 300 - Team */
4809 publication_cal_300 ? publication_cal_300->version : 0,
4810 cal->email,
4811 cal->working_hours_xml_str,
4812 /* 400 - Personal */
4813 publication_cal_400 ? publication_cal_400->version : 0,
4814 cal->email,
4815 cal->working_hours_xml_str,
4816 /* 32000 - Blocked */
4817 publication_cal_32000 ? publication_cal_32000->version : 0
4822 * Returns 'calendarData' XML part with FreeBusy for publication.
4823 * Must be g_free'd after use.
4825 static gchar *
4826 sipe_publish_get_category_cal_free_busy(struct sipe_core_private *sipe_private)
4828 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
4829 struct sipe_calendar* cal = sip->cal;
4830 guint cal_data_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_CALENDAR_DATA);
4831 char *fb_start_str;
4832 char *free_busy_base64;
4833 /* const char *st; */
4834 /* const char *fb; */
4835 char *res;
4837 /* key is <category><instance><container> */
4838 gchar *key_cal_1 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 1);
4839 gchar *key_cal_100 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 100);
4840 gchar *key_cal_200 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 200);
4841 gchar *key_cal_300 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 300);
4842 gchar *key_cal_400 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 400);
4843 gchar *key_cal_32000 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 32000);
4845 struct sipe_publication *publication_cal_1 =
4846 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_1);
4847 struct sipe_publication *publication_cal_100 =
4848 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_100);
4849 struct sipe_publication *publication_cal_200 =
4850 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_200);
4851 struct sipe_publication *publication_cal_300 =
4852 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_300);
4853 struct sipe_publication *publication_cal_400 =
4854 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_400);
4855 struct sipe_publication *publication_cal_32000 =
4856 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_32000);
4858 g_free(key_cal_1);
4859 g_free(key_cal_100);
4860 g_free(key_cal_200);
4861 g_free(key_cal_300);
4862 g_free(key_cal_400);
4863 g_free(key_cal_32000);
4865 if (!cal || is_empty(cal->email) || !cal->fb_start || is_empty(cal->free_busy)) {
4866 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_cal_free_busy: no data to publish, exiting");
4867 return NULL;
4870 fb_start_str = sipe_utils_time_to_str(cal->fb_start);
4871 free_busy_base64 = sipe_cal_get_freebusy_base64(cal->free_busy);
4873 /* we will rebuplish the same data to refresh publication time,
4874 * so if data from multiple sources, most recent will be choosen
4876 // st = publication_cal_300 ? publication_cal_300->fb_start_str : NULL;
4877 // fb = publication_cal_300 ? publication_cal_300->free_busy_base64 : NULL;
4879 //if (sipe_strequal(st, fb_start_str) && sipe_strequal(fb, free_busy_base64))
4881 // SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_cal_free_busy: FreeBusy has NOT changed. Exiting.");
4882 // g_free(fb_start_str);
4883 // g_free(free_busy_base64);
4884 // return NULL; /* nothing to update */
4887 res = g_strdup_printf(SIPE_PUB_XML_FREE_BUSY,
4888 /* 1 */
4889 cal_data_instance,
4890 publication_cal_1 ? publication_cal_1->version : 0,
4891 /* 100 - Public */
4892 cal_data_instance,
4893 publication_cal_100 ? publication_cal_100->version : 0,
4894 /* 200 - Company */
4895 cal_data_instance,
4896 publication_cal_200 ? publication_cal_200->version : 0,
4897 cal->email,
4898 fb_start_str,
4899 free_busy_base64,
4900 /* 300 - Team */
4901 cal_data_instance,
4902 publication_cal_300 ? publication_cal_300->version : 0,
4903 cal->email,
4904 fb_start_str,
4905 free_busy_base64,
4906 /* 400 - Personal */
4907 cal_data_instance,
4908 publication_cal_400 ? publication_cal_400->version : 0,
4909 cal->email,
4910 fb_start_str,
4911 free_busy_base64,
4912 /* 32000 - Blocked */
4913 cal_data_instance,
4914 publication_cal_32000 ? publication_cal_32000->version : 0
4917 g_free(fb_start_str);
4918 g_free(free_busy_base64);
4919 return res;
4922 static void send_presence_publish(struct sipe_core_private *sipe_private,
4923 const char *publications)
4925 gchar *uri;
4926 gchar *doc;
4927 gchar *tmp;
4928 gchar *hdr;
4930 uri = sip_uri_self(sipe_private);
4931 doc = g_strdup_printf(SIPE_SEND_PRESENCE,
4932 uri,
4933 publications);
4935 tmp = get_contact(sipe_private);
4936 hdr = g_strdup_printf("Contact: %s\r\n"
4937 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp);
4939 sip_transport_service(sipe_private,
4940 uri,
4941 hdr,
4942 doc,
4943 process_send_presence_category_publish_response);
4945 g_free(tmp);
4946 g_free(hdr);
4947 g_free(uri);
4948 g_free(doc);
4951 static void
4952 send_publish_category_initial(struct sipe_core_private *sipe_private)
4954 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
4955 gchar *pub_device = sipe_publish_get_category_device(sipe_private);
4956 gchar *pub_machine;
4957 gchar *publications;
4959 g_free(sip->status);
4960 sip->status = g_strdup(SIPE_STATUS_ID_AVAILABLE); /* our initial state */
4962 pub_machine = sipe_publish_get_category_state_machine(sipe_private);
4963 publications = g_strdup_printf("%s%s",
4964 pub_device,
4965 pub_machine ? pub_machine : "");
4966 g_free(pub_device);
4967 g_free(pub_machine);
4969 send_presence_publish(sipe_private, publications);
4970 g_free(publications);
4973 static void
4974 send_presence_category_publish(struct sipe_core_private *sipe_private)
4976 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
4977 gchar *pub_state = sipe_is_user_state(sipe_private) ?
4978 sipe_publish_get_category_state_user(sipe_private) :
4979 sipe_publish_get_category_state_machine(sipe_private);
4980 gchar *pub_note = sipe_publish_get_category_note(sipe_private,
4981 sip->note,
4982 sip->is_oof_note ? "OOF" : "personal",
4985 gchar *publications;
4987 if (!pub_state && !pub_note) {
4988 SIPE_DEBUG_INFO_NOFORMAT("send_presence_category_publish: nothing has changed. Exiting.");
4989 return;
4992 publications = g_strdup_printf("%s%s",
4993 pub_state ? pub_state : "",
4994 pub_note ? pub_note : "");
4996 g_free(pub_state);
4997 g_free(pub_note);
4999 send_presence_publish(sipe_private, publications);
5000 g_free(publications);
5004 * Publishes self status
5005 * based on own calendar information.
5007 * For 2007+
5009 void
5010 publish_calendar_status_self(struct sipe_core_private *sipe_private,
5011 SIPE_UNUSED_PARAMETER void *unused)
5013 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5014 struct sipe_cal_event* event = NULL;
5015 gchar *pub_cal_working_hours = NULL;
5016 gchar *pub_cal_free_busy = NULL;
5017 gchar *pub_calendar = NULL;
5018 gchar *pub_calendar2 = NULL;
5019 gchar *pub_oof_note = NULL;
5020 const gchar *oof_note;
5021 time_t oof_start = 0;
5022 time_t oof_end = 0;
5024 if (!sip->cal) {
5025 SIPE_DEBUG_INFO_NOFORMAT("publish_calendar_status_self() no calendar data.");
5026 return;
5029 SIPE_DEBUG_INFO_NOFORMAT("publish_calendar_status_self() started.");
5030 if (sip->cal->cal_events) {
5031 event = sipe_cal_get_event(sip->cal->cal_events, time(NULL));
5034 if (!event) {
5035 SIPE_DEBUG_INFO_NOFORMAT("publish_calendar_status_self: current event is NULL");
5036 } else {
5037 char *desc = sipe_cal_event_describe(event);
5038 SIPE_DEBUG_INFO("publish_calendar_status_self: current event is:\n%s", desc ? desc : "");
5039 g_free(desc);
5042 /* Logic
5043 if OOF
5044 OOF publish, Busy clean
5045 ilse if Busy
5046 OOF clean, Busy publish
5047 else
5048 OOF clean, Busy clean
5050 if (event && event->cal_status == SIPE_CAL_OOF) {
5051 pub_calendar = sipe_publish_get_category_state_calendar(sipe_private, event, sip->cal->email, SIPE_CAL_OOF);
5052 pub_calendar2 = sipe_publish_get_category_state_calendar(sipe_private, NULL, sip->cal->email, SIPE_CAL_BUSY);
5053 } else if (event && event->cal_status == SIPE_CAL_BUSY) {
5054 pub_calendar = sipe_publish_get_category_state_calendar(sipe_private, NULL, sip->cal->email, SIPE_CAL_OOF);
5055 pub_calendar2 = sipe_publish_get_category_state_calendar(sipe_private, event, sip->cal->email, SIPE_CAL_BUSY);
5056 } else {
5057 pub_calendar = sipe_publish_get_category_state_calendar(sipe_private, NULL, sip->cal->email, SIPE_CAL_OOF);
5058 pub_calendar2 = sipe_publish_get_category_state_calendar(sipe_private, NULL, sip->cal->email, SIPE_CAL_BUSY);
5061 oof_note = sipe_ews_get_oof_note(sip->cal);
5062 if (sipe_strequal("Scheduled", sip->cal->oof_state)) {
5063 oof_start = sip->cal->oof_start;
5064 oof_end = sip->cal->oof_end;
5066 pub_oof_note = sipe_publish_get_category_note(sipe_private, oof_note, "OOF", oof_start, oof_end);
5068 pub_cal_working_hours = sipe_publish_get_category_cal_working_hours(sipe_private);
5069 pub_cal_free_busy = sipe_publish_get_category_cal_free_busy(sipe_private);
5071 if (!pub_cal_working_hours && !pub_cal_free_busy && !pub_calendar && !pub_calendar2 && !pub_oof_note) {
5072 SIPE_DEBUG_INFO_NOFORMAT("publish_calendar_status_self: nothing has changed.");
5073 } else {
5074 gchar *publications = g_strdup_printf("%s%s%s%s%s",
5075 pub_cal_working_hours ? pub_cal_working_hours : "",
5076 pub_cal_free_busy ? pub_cal_free_busy : "",
5077 pub_calendar ? pub_calendar : "",
5078 pub_calendar2 ? pub_calendar2 : "",
5079 pub_oof_note ? pub_oof_note : "");
5081 send_presence_publish(sipe_private, publications);
5082 g_free(publications);
5085 g_free(pub_cal_working_hours);
5086 g_free(pub_cal_free_busy);
5087 g_free(pub_calendar);
5088 g_free(pub_calendar2);
5089 g_free(pub_oof_note);
5091 /* repeat scheduling */
5092 sipe_sched_calendar_status_self_publish(sipe_private, time(NULL));
5095 static void send_presence_status(struct sipe_core_private *sipe_private,
5096 SIPE_UNUSED_PARAMETER void *unused)
5098 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5099 PurpleStatus * status = purple_account_get_active_status(sip->account);
5101 if (!status) return;
5103 SIPE_DEBUG_INFO("send_presence_status: status: %s (%s)",
5104 purple_status_get_id(status) ? purple_status_get_id(status) : "",
5105 sipe_is_user_state(sipe_private) ? "USER" : "MACHINE");
5107 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
5108 send_presence_category_publish(sipe_private);
5109 } else {
5110 send_presence_soap(sipe_private, FALSE);
5114 static guint sipe_ht_hash_nick(const char *nick)
5116 char *lc = g_utf8_strdown(nick, -1);
5117 guint bucket = g_str_hash(lc);
5118 g_free(lc);
5120 return bucket;
5123 static gboolean sipe_ht_equals_nick(const char *nick1, const char *nick2)
5125 char *nick1_norm = NULL;
5126 char *nick2_norm = NULL;
5127 gboolean equal;
5129 if (nick1 == NULL && nick2 == NULL) return TRUE;
5130 if (nick1 == NULL || nick2 == NULL ||
5131 !g_utf8_validate(nick1, -1, NULL) ||
5132 !g_utf8_validate(nick2, -1, NULL)) return FALSE;
5134 nick1_norm = g_utf8_casefold(nick1, -1);
5135 nick2_norm = g_utf8_casefold(nick2, -1);
5136 equal = g_utf8_collate(nick1_norm, nick2_norm) == 0;
5137 g_free(nick2_norm);
5138 g_free(nick1_norm);
5140 return equal;
5143 /* temporary function */
5144 void sipe_purple_setup(struct sipe_core_public *sipe_public,
5145 PurpleConnection *gc)
5147 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA;
5148 sip->gc = gc;
5149 sip->account = purple_connection_get_account(gc);
5152 struct sipe_core_public *sipe_core_allocate(const gchar *signin_name,
5153 const gchar *login_domain,
5154 const gchar *login_account,
5155 const gchar *password,
5156 const gchar *email,
5157 const gchar *email_url,
5158 const gchar **errmsg)
5160 struct sipe_core_private *sipe_private;
5161 struct sipe_account_data *sip;
5162 gchar **user_domain;
5164 SIPE_DEBUG_INFO("sipe_core_allocate: signin_name '%s'", signin_name);
5166 /* ensure that sign-in name doesn't contain invalid characters */
5167 if (strpbrk(signin_name, "\t\v\r\n") != NULL) {
5168 *errmsg = _("SIP Exchange user name contains invalid characters");
5169 return NULL;
5172 /* ensure that sign-in name format is name@domain */
5173 if (!strchr(signin_name, '@') ||
5174 g_str_has_prefix(signin_name, "@") ||
5175 g_str_has_suffix(signin_name, "@")) {
5176 *errmsg = _("User name should be a valid SIP URI\nExample: user@company.com");
5177 return NULL;
5180 /* ensure that email format is name@domain (if provided) */
5181 if (!is_empty(email) &&
5182 (!strchr(email, '@') ||
5183 g_str_has_prefix(email, "@") ||
5184 g_str_has_suffix(email, "@")))
5186 *errmsg = _("Email address should be valid if provided\nExample: user@company.com");
5187 return NULL;
5190 /* ensure that user name doesn't contain spaces */
5191 user_domain = g_strsplit(signin_name, "@", 2);
5192 SIPE_DEBUG_INFO("sipe_core_allocate: user '%s' domain '%s'", user_domain[0], user_domain[1]);
5193 if (strchr(user_domain[0], ' ') != NULL) {
5194 g_strfreev(user_domain);
5195 *errmsg = _("SIP Exchange user name contains whitespace");
5196 return NULL;
5199 /* ensure that email_url is in proper format if enabled (if provided).
5200 * Example (Exchange): https://server.company.com/EWS/Exchange.asmx
5201 * Example (Domino) : https://[domino_server]/[mail_database_name].nsf
5203 if (!is_empty(email_url)) {
5204 char *tmp = g_ascii_strdown(email_url, -1);
5205 if (!g_str_has_prefix(tmp, "https://"))
5207 g_free(tmp);
5208 g_strfreev(user_domain);
5209 *errmsg = _("Email services URL should be valid if provided\n"
5210 "Example: https://exchange.corp.com/EWS/Exchange.asmx\n"
5211 "Example: https://domino.corp.com/maildatabase.nsf");
5212 return NULL;
5214 g_free(tmp);
5217 sipe_private = g_new0(struct sipe_core_private, 1);
5218 sipe_private->temporary = sip = g_new0(struct sipe_account_data, 1);
5219 sip->subscribed_buddies = FALSE;
5220 sip->initial_state_published = FALSE;
5221 sipe_private->username = g_strdup(signin_name);
5222 sip->email = is_empty(email) ? g_strdup(signin_name) : g_strdup(email);
5223 sip->authdomain = is_empty(login_domain) ? NULL : g_strdup(login_domain);
5224 sip->authuser = is_empty(login_account) ? NULL : g_strdup(login_account);
5225 sip->password = g_strdup(password);
5226 sipe_private->public.sip_name = g_strdup(user_domain[0]);
5227 sipe_private->public.sip_domain = g_strdup(user_domain[1]);
5228 g_strfreev(user_domain);
5230 sipe_private->buddies = g_hash_table_new((GHashFunc)sipe_ht_hash_nick, (GEqualFunc)sipe_ht_equals_nick);
5231 sip->our_publications = g_hash_table_new_full(g_str_hash, g_str_equal,
5232 g_free, (GDestroyNotify)g_hash_table_destroy);
5233 sipe_subscriptions_init(sipe_private);
5234 sip->status = g_strdup(SIPE_STATUS_ID_UNKNOWN);
5236 return((struct sipe_core_public *)sipe_private);
5239 static void
5240 sipe_blist_menu_free_containers(struct sipe_core_private *sipe_private);
5242 void sipe_connection_cleanup(struct sipe_core_private *sipe_private)
5244 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5246 g_free(sipe_private->epid);
5247 sipe_private->epid = NULL;
5249 sip_transport_disconnect(sipe_private);
5251 sipe_schedule_cancel_all(sipe_private);
5253 if (sip->allow_events) {
5254 GSList *entry = sip->allow_events;
5255 while (entry) {
5256 g_free(entry->data);
5257 entry = entry->next;
5260 g_slist_free(sip->allow_events);
5262 if (sip->containers) {
5263 GSList *entry = sip->containers;
5264 while (entry) {
5265 free_container((struct sipe_container *)entry->data);
5266 entry = entry->next;
5269 g_slist_free(sip->containers);
5271 /* libpurple memory leak workaround */
5272 sipe_blist_menu_free_containers(sipe_private);
5274 if (sipe_private->contact)
5275 g_free(sipe_private->contact);
5276 sipe_private->contact = NULL;
5277 if (sip->regcallid)
5278 g_free(sip->regcallid);
5279 sip->regcallid = NULL;
5281 if (sipe_private->focus_factory_uri)
5282 g_free(sipe_private->focus_factory_uri);
5283 sipe_private->focus_factory_uri = NULL;
5285 if (sip->cal) {
5286 sipe_cal_calendar_free(sip->cal);
5288 sip->cal = NULL;
5290 sipe_groupchat_free(sipe_private);
5294 * A callback for g_hash_table_foreach_remove
5296 static gboolean sipe_buddy_remove(SIPE_UNUSED_PARAMETER gpointer key, gpointer buddy,
5297 SIPE_UNUSED_PARAMETER gpointer user_data)
5299 sipe_free_buddy((struct sipe_buddy *) buddy);
5301 /* We must return TRUE as the key/value have already been deleted */
5302 return(TRUE);
5305 void sipe_buddy_free_all(struct sipe_core_private *sipe_private)
5307 g_hash_table_foreach_steal(sipe_private->buddies, sipe_buddy_remove, NULL);
5310 static void sipe_searchresults_im_buddy(PurpleConnection *gc, GList *row,
5311 SIPE_UNUSED_PARAMETER void *user_data)
5313 PurpleAccount *acct = purple_connection_get_account(gc);
5314 char *id = sip_uri_from_name((gchar *)g_list_nth_data(row, 0));
5315 PurpleConversation *conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, id, acct);
5316 if (conv == NULL)
5317 conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, acct, id);
5318 purple_conversation_present(conv);
5319 g_free(id);
5322 static void sipe_searchresults_add_buddy(PurpleConnection *gc, GList *row,
5323 SIPE_UNUSED_PARAMETER void *user_data)
5326 purple_blist_request_add_buddy(purple_connection_get_account(gc),
5327 g_list_nth_data(row, 0), _("Other Contacts"), g_list_nth_data(row, 1));
5330 static gboolean process_search_contact_response(struct sipe_core_private *sipe_private,
5331 struct sipmsg *msg,
5332 SIPE_UNUSED_PARAMETER struct transaction *trans)
5334 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5335 PurpleNotifySearchResults *results;
5336 PurpleNotifySearchColumn *column;
5337 sipe_xml *searchResults;
5338 const sipe_xml *mrow;
5339 int match_count = 0;
5340 gboolean more = FALSE;
5341 gchar *secondary;
5343 SIPE_DEBUG_INFO("process_search_contact_response: body:\n%s", msg->body ? msg->body : "");
5345 searchResults = sipe_xml_parse(msg->body, msg->bodylen);
5346 if (!searchResults) {
5347 SIPE_DEBUG_INFO_NOFORMAT("process_search_contact_response: no parseable searchResults");
5348 return FALSE;
5351 results = purple_notify_searchresults_new();
5353 if (results == NULL) {
5354 SIPE_DEBUG_ERROR_NOFORMAT("purple_parse_searchreply: Unable to display the search results.");
5355 purple_notify_error(sip->gc, NULL, _("Unable to display the search results"), NULL);
5357 sipe_xml_free(searchResults);
5358 return FALSE;
5361 column = purple_notify_searchresults_column_new(_("User name"));
5362 purple_notify_searchresults_column_add(results, column);
5364 column = purple_notify_searchresults_column_new(_("Name"));
5365 purple_notify_searchresults_column_add(results, column);
5367 column = purple_notify_searchresults_column_new(_("Company"));
5368 purple_notify_searchresults_column_add(results, column);
5370 column = purple_notify_searchresults_column_new(_("Country"));
5371 purple_notify_searchresults_column_add(results, column);
5373 column = purple_notify_searchresults_column_new(_("Email"));
5374 purple_notify_searchresults_column_add(results, column);
5376 for (mrow = sipe_xml_child(searchResults, "Body/Array/row"); mrow; mrow = sipe_xml_twin(mrow)) {
5377 GList *row = NULL;
5379 gchar **uri_parts = g_strsplit(sipe_xml_attribute(mrow, "uri"), ":", 2);
5380 row = g_list_append(row, g_strdup(uri_parts[1]));
5381 g_strfreev(uri_parts);
5383 row = g_list_append(row, g_strdup(sipe_xml_attribute(mrow, "displayName")));
5384 row = g_list_append(row, g_strdup(sipe_xml_attribute(mrow, "company")));
5385 row = g_list_append(row, g_strdup(sipe_xml_attribute(mrow, "country")));
5386 row = g_list_append(row, g_strdup(sipe_xml_attribute(mrow, "email")));
5388 purple_notify_searchresults_row_add(results, row);
5389 match_count++;
5392 if ((mrow = sipe_xml_child(searchResults, "Body/directorySearch/moreAvailable")) != NULL) {
5393 char *data = sipe_xml_data(mrow);
5394 more = (g_strcasecmp(data, "true") == 0);
5395 g_free(data);
5398 secondary = g_strdup_printf(
5399 dngettext(PACKAGE_NAME,
5400 "Found %d contact%s:",
5401 "Found %d contacts%s:", match_count),
5402 match_count, more ? _(" (more matched your query)") : "");
5404 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_IM, sipe_searchresults_im_buddy);
5405 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_ADD, sipe_searchresults_add_buddy);
5406 purple_notify_searchresults(sip->gc, NULL, NULL, secondary, results, NULL, NULL);
5408 g_free(secondary);
5409 sipe_xml_free(searchResults);
5410 return TRUE;
5413 void sipe_search_contact_with_cb(PurpleConnection *gc, PurpleRequestFields *fields)
5415 GList *entries = purple_request_field_group_get_fields(purple_request_fields_get_groups(fields)->data);
5416 gchar **attrs = g_new(gchar *, g_list_length(entries) + 1);
5417 unsigned i = 0;
5419 if (!attrs) return;
5421 do {
5422 PurpleRequestField *field = entries->data;
5423 const char *id = purple_request_field_get_id(field);
5424 const char *value = purple_request_field_string_get_value(field);
5426 SIPE_DEBUG_INFO("sipe_search_contact_with_cb: %s = '%s'", id, value ? value : "");
5428 if (value != NULL) attrs[i++] = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, id, value);
5429 } while ((entries = g_list_next(entries)) != NULL);
5430 attrs[i] = NULL;
5432 if (i > 0) {
5433 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
5434 gchar *domain_uri = sip_uri_from_name(sipe_private->public.sip_domain);
5435 gchar *query = g_strjoinv(NULL, attrs);
5436 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 100, query);
5437 SIPE_DEBUG_INFO("sipe_search_contact_with_cb: body:\n%s", body ? body : "");
5438 send_soap_request_with_cb(sipe_private, domain_uri, body,
5439 process_search_contact_response, NULL);
5440 g_free(domain_uri);
5441 g_free(body);
5442 g_free(query);
5445 g_strfreev(attrs);
5448 static void sipe_publish_get_cat_state_user_to_clear(SIPE_UNUSED_PARAMETER const char *name,
5449 gpointer value,
5450 GString* str)
5452 struct sipe_publication *publication = value;
5454 g_string_append_printf( str,
5455 SIPE_PUB_XML_PUBLICATION_CLEAR,
5456 publication->category,
5457 publication->instance,
5458 publication->container,
5459 publication->version,
5460 "static");
5463 void sipe_core_reset_status(struct sipe_core_public *sipe_public)
5465 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
5466 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA;
5467 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) /* 2007+ */
5469 GString* str = g_string_new(NULL);
5470 gchar *publications;
5472 if (!sip->user_state_publications || g_hash_table_size(sip->user_state_publications) == 0) {
5473 SIPE_DEBUG_INFO_NOFORMAT("sipe_reset_status: no userState publications, exiting.");
5474 return;
5477 g_hash_table_foreach(sip->user_state_publications, (GHFunc)sipe_publish_get_cat_state_user_to_clear, str);
5478 publications = g_string_free(str, FALSE);
5480 send_presence_publish(sipe_private, publications);
5481 g_free(publications);
5483 else /* 2005 */
5485 send_presence_soap0(sipe_private, FALSE, TRUE);
5489 /** for Access levels menu */
5490 #define INDENT_FMT " %s"
5492 /** Member is directly placed to access level container.
5493 * For example SIP URI of user is in the container.
5495 #define INDENT_MARKED_FMT "* %s"
5497 /** Member is indirectly belong to access level container.
5498 * For example 'sameEnterprise' is in the container and user
5499 * belongs to that same enterprise.
5501 #define INDENT_MARKED_INHERITED_FMT "= %s"
5503 GSList *sipe_core_buddy_info(struct sipe_core_public *sipe_public,
5504 const gchar *name,
5505 const gchar *status_name,
5506 gboolean is_online)
5508 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
5509 gchar *note = NULL;
5510 gboolean is_oof_note = FALSE;
5511 const gchar *activity = NULL;
5512 gchar *calendar = NULL;
5513 const gchar *meeting_subject = NULL;
5514 const gchar *meeting_location = NULL;
5515 gchar *access_text = NULL;
5516 GSList *info = NULL;
5518 #define SIPE_ADD_BUDDY_INFO_COMMON(l, t) \
5520 struct sipe_buddy_info *sbi = g_malloc(sizeof(struct sipe_buddy_info)); \
5521 sbi->label = (l); \
5522 sbi->text = (t); \
5523 info = g_slist_append(info, sbi); \
5525 #define SIPE_ADD_BUDDY_INFO(l, t) SIPE_ADD_BUDDY_INFO_COMMON((l), g_markup_escape_text((t), -1))
5526 #define SIPE_ADD_BUDDY_INFO_NOESCAPE(l, t) SIPE_ADD_BUDDY_INFO_COMMON((l), (t))
5528 if (sipe_public) { //happens on pidgin exit
5529 struct sipe_buddy *sbuddy = g_hash_table_lookup(sipe_private->buddies, name);
5530 if (sbuddy) {
5531 note = sbuddy->note;
5532 is_oof_note = sbuddy->is_oof_note;
5533 activity = sbuddy->activity;
5534 calendar = sipe_cal_get_description(sbuddy);
5535 meeting_subject = sbuddy->meeting_subject;
5536 meeting_location = sbuddy->meeting_location;
5538 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
5539 gboolean is_group_access = FALSE;
5540 const int container_id = sipe_find_access_level(sipe_private, "user", sipe_get_no_sip_uri(name), &is_group_access);
5541 const char *access_level = sipe_get_access_level_name(container_id);
5542 access_text = is_group_access ?
5543 g_strdup(access_level) :
5544 g_strdup_printf(INDENT_MARKED_FMT, access_level);
5548 //Layout
5549 if (is_online)
5551 const gchar *status_str = activity ? activity : status_name;
5553 SIPE_ADD_BUDDY_INFO(_("Status"), status_str);
5555 if (is_online && !is_empty(calendar))
5557 SIPE_ADD_BUDDY_INFO(_("Calendar"), calendar);
5559 g_free(calendar);
5560 if (!is_empty(meeting_location))
5562 SIPE_DEBUG_INFO("sipe_tooltip_text: %s meeting location: '%s'", name, meeting_location);
5563 SIPE_ADD_BUDDY_INFO(_("Meeting in"), meeting_location);
5565 if (!is_empty(meeting_subject))
5567 SIPE_DEBUG_INFO("sipe_tooltip_text: %s meeting subject: '%s'", name, meeting_subject);
5568 SIPE_ADD_BUDDY_INFO(_("Meeting about"), meeting_subject);
5570 if (note)
5572 SIPE_DEBUG_INFO("sipe_tooltip_text: %s note: '%s'", name, note);
5573 SIPE_ADD_BUDDY_INFO_NOESCAPE(is_oof_note ? _("Out of office note") : _("Note"),
5574 g_strdup_printf("<i>%s</i>", note));
5576 if (access_text) {
5577 SIPE_ADD_BUDDY_INFO(_("Access level"), access_text);
5578 g_free(access_text);
5581 return(info);
5584 static PurpleBuddy *
5585 purple_blist_add_buddy_clone(PurpleGroup * group, PurpleBuddy * buddy)
5587 PurpleBuddy *clone;
5588 const gchar *server_alias, *email;
5589 const PurpleStatus *status = purple_presence_get_active_status(purple_buddy_get_presence(buddy));
5591 clone = purple_buddy_new(buddy->account, buddy->name, buddy->alias);
5593 purple_blist_add_buddy(clone, NULL, group, NULL);
5595 server_alias = purple_buddy_get_server_alias(buddy);
5596 if (server_alias) {
5597 purple_blist_server_alias_buddy(clone, server_alias);
5600 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
5601 if (email) {
5602 purple_blist_node_set_string(&clone->node, EMAIL_PROP, email);
5605 purple_presence_set_status_active(purple_buddy_get_presence(clone), purple_status_get_id(status), TRUE);
5606 //for UI to update;
5607 purple_prpl_got_user_status(clone->account, clone->name, purple_status_get_id(status), NULL);
5608 return clone;
5611 static void
5612 sipe_buddy_menu_copy_to_cb(PurpleBlistNode *node, const char *group_name)
5614 PurpleBuddy *buddy, *b;
5615 PurpleConnection *gc;
5616 PurpleGroup * group = purple_find_group(group_name);
5618 g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node));
5620 buddy = (PurpleBuddy *)node;
5622 SIPE_DEBUG_INFO("sipe_buddy_menu_copy_to_cb: copying %s to %s", buddy->name, group_name);
5623 gc = purple_account_get_connection(buddy->account);
5625 b = purple_find_buddy_in_group(buddy->account, buddy->name, group);
5626 if (!b){
5627 b = purple_blist_add_buddy_clone(group, buddy);
5630 sipe_add_buddy(gc, b, group);
5633 static void
5634 sipe_buddy_menu_chat_new_cb(PurpleBuddy *buddy)
5636 struct sipe_core_private *sipe_private = PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE;
5638 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_new_cb: buddy->name=%s", buddy->name);
5640 /* 2007+ conference */
5641 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007))
5643 sipe_conf_add(sipe_private, buddy->name);
5645 else /* 2005- multiparty chat */
5647 gchar *self = sip_uri_self(sipe_private);
5648 struct sip_session *session;
5650 session = sipe_session_add_chat(sipe_private,
5651 NULL,
5652 TRUE,
5653 self);
5654 session->chat_session->backend = sipe_backend_chat_create(SIPE_CORE_PUBLIC,
5655 session->chat_session,
5656 session->chat_session->title,
5657 self);
5658 g_free(self);
5660 sipe_im_invite(sipe_private, session, buddy->name, NULL, NULL, NULL, FALSE);
5665 * For 2007+ conference only.
5667 static void
5668 sipe_buddy_menu_chat_make_leader_cb(PurpleBuddy *buddy,
5669 struct sipe_chat_session *chat_session)
5671 struct sipe_core_private *sipe_private = PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE;
5672 struct sip_session *session;
5674 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_make_leader_cb: buddy->name=%s", buddy->name);
5675 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_make_leader_cb: chat_title=%s", chat_session->title);
5677 session = sipe_session_find_chat(sipe_private, chat_session);
5679 sipe_conf_modify_user_role(sipe_private, session, buddy->name);
5683 * For 2007+ conference only.
5685 static void
5686 sipe_buddy_menu_chat_remove_cb(PurpleBuddy *buddy,
5687 struct sipe_chat_session *chat_session)
5689 struct sipe_core_private *sipe_private = PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE;
5690 struct sip_session *session;
5692 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_remove_cb: buddy->name=%s", buddy->name);
5693 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_remove_cb: chat_title=%s", chat_session->title);
5695 session = sipe_session_find_chat(sipe_private, chat_session);
5697 sipe_conf_delete_user(sipe_private, session, buddy->name);
5700 static void
5701 sipe_buddy_menu_chat_invite_cb(PurpleBuddy *buddy,
5702 struct sipe_chat_session *chat_session)
5704 struct sipe_core_private *sipe_private = PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE;
5706 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_invite_cb: buddy->name=%s", buddy->name);
5707 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_invite_cb: chat_title=%s", chat_session->title);
5709 sipe_core_chat_invite(SIPE_CORE_PUBLIC, chat_session, buddy->name);
5712 static void
5713 sipe_buddy_menu_make_call_cb(PurpleBuddy *buddy, const char *phone)
5715 struct sipe_core_private *sipe_private = PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE;
5717 SIPE_DEBUG_INFO("sipe_buddy_menu_make_call_cb: buddy->name=%s", buddy->name);
5718 if (phone) {
5719 char *tel_uri = sip_to_tel_uri(phone);
5721 SIPE_DEBUG_INFO("sipe_buddy_menu_make_call_cb: going to call number: %s", tel_uri ? tel_uri : "");
5722 sip_csta_make_call(sipe_private, tel_uri);
5724 g_free(tel_uri);
5728 static void
5729 sipe_buddy_menu_access_level_help_cb(PurpleBuddy *buddy)
5731 /** Translators: replace with URL to localized page
5732 * If it doesn't exist copy the original URL */
5733 purple_notify_uri(buddy->account->gc, _("https://sourceforge.net/apps/mediawiki/sipe/index.php?title=Access_Levels"));
5736 static void
5737 sipe_buddy_menu_send_email_cb(PurpleBuddy *buddy)
5739 const gchar *email;
5740 SIPE_DEBUG_INFO("sipe_buddy_menu_send_email_cb: buddy->name=%s", buddy->name);
5742 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
5743 if (email)
5745 char *command_line = g_strdup_printf(
5746 #ifdef _WIN32
5747 "cmd /c start"
5748 #else
5749 "xdg-email"
5750 #endif
5751 " mailto:%s", email);
5752 SIPE_DEBUG_INFO("sipe_buddy_menu_send_email_cb: going to call email client: %s", command_line);
5754 g_spawn_command_line_async(command_line, NULL);
5755 g_free(command_line);
5757 else
5759 SIPE_DEBUG_INFO("sipe_buddy_menu_send_email_cb: no email address stored for buddy=%s", buddy->name);
5763 static void
5764 sipe_buddy_menu_access_level_cb(PurpleBuddy *buddy,
5765 struct sipe_container *container)
5767 struct sipe_core_private *sipe_private = PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE;
5768 struct sipe_container_member *member;
5770 if (!container || !container->members) return;
5772 member = ((struct sipe_container_member *)container->members->data);
5774 if (!member->type) return;
5776 SIPE_DEBUG_INFO("sipe_buddy_menu_access_level_cb: container->id=%d, member->type=%s, member->value=%s",
5777 container->id, member->type, member->value ? member->value : "");
5779 sipe_change_access_level(sipe_private, container->id, member->type, member->value);
5782 static GList *
5783 sipe_get_access_control_menu(struct sipe_core_private *sipe_private,
5784 const char* uri);
5787 * A menu which appear when right-clicking on buddy in contact list.
5789 GList *
5790 sipe_buddy_menu(PurpleBuddy *buddy)
5792 PurpleBlistNode *g_node;
5793 PurpleGroup *gr_parent;
5794 PurpleMenuAction *act;
5795 GList *menu = NULL;
5796 GList *menu_groups = NULL;
5797 struct sipe_core_private *sipe_private = PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE;
5798 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5799 const char *email;
5800 gchar *self = sip_uri_self(sipe_private);
5802 SIPE_SESSION_FOREACH {
5803 if (!sipe_strcase_equal(self, buddy->name) && session->chat_session)
5805 struct sipe_chat_session *chat_session = session->chat_session;
5806 gboolean is_conf = (chat_session->type == SIPE_CHAT_TYPE_CONFERENCE);
5808 if (sipe_backend_chat_find(chat_session->backend, buddy->name))
5810 gboolean conf_op = sipe_backend_chat_is_operator(chat_session->backend, self);
5812 if (is_conf
5813 && !sipe_backend_chat_is_operator(chat_session->backend, buddy->name) /* Not conf OP */
5814 && conf_op) /* We are a conf OP */
5816 gchar *label = g_strdup_printf(_("Make leader of '%s'"),
5817 chat_session->title);
5818 act = purple_menu_action_new(label,
5819 PURPLE_CALLBACK(sipe_buddy_menu_chat_make_leader_cb),
5820 chat_session, NULL);
5821 g_free(label);
5822 menu = g_list_prepend(menu, act);
5825 if (is_conf
5826 && conf_op) /* We are a conf OP */
5828 gchar *label = g_strdup_printf(_("Remove from '%s'"),
5829 chat_session->title);
5830 act = purple_menu_action_new(label,
5831 PURPLE_CALLBACK(sipe_buddy_menu_chat_remove_cb),
5832 chat_session, NULL);
5833 g_free(label);
5834 menu = g_list_prepend(menu, act);
5837 else
5839 if (!is_conf
5840 || (is_conf && !session->locked))
5842 gchar *label = g_strdup_printf(_("Invite to '%s'"),
5843 chat_session->title);
5844 act = purple_menu_action_new(label,
5845 PURPLE_CALLBACK(sipe_buddy_menu_chat_invite_cb),
5846 chat_session, NULL);
5847 g_free(label);
5848 menu = g_list_prepend(menu, act);
5852 } SIPE_SESSION_FOREACH_END;
5854 act = purple_menu_action_new(_("New chat"),
5855 PURPLE_CALLBACK(sipe_buddy_menu_chat_new_cb),
5856 NULL, NULL);
5857 menu = g_list_prepend(menu, act);
5859 if (sip->csta && !sip->csta->line_status) {
5860 const char *phone;
5861 const char *phone_disp_str;
5862 gchar *tmp = NULL;
5863 /* work phone */
5864 phone = purple_blist_node_get_string(&buddy->node, PHONE_PROP);
5865 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_DISPLAY_PROP);
5866 if (phone) {
5867 gchar *label = g_strdup_printf(_("Work %s"),
5868 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
5869 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
5870 g_free(tmp);
5871 tmp = NULL;
5872 g_free(label);
5873 menu = g_list_prepend(menu, act);
5876 /* mobile phone */
5877 phone = purple_blist_node_get_string(&buddy->node, PHONE_MOBILE_PROP);
5878 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_MOBILE_DISPLAY_PROP);
5879 if (phone) {
5880 gchar *label = g_strdup_printf(_("Mobile %s"),
5881 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
5882 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
5883 g_free(tmp);
5884 tmp = NULL;
5885 g_free(label);
5886 menu = g_list_prepend(menu, act);
5889 /* home phone */
5890 phone = purple_blist_node_get_string(&buddy->node, PHONE_HOME_PROP);
5891 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_HOME_DISPLAY_PROP);
5892 if (phone) {
5893 gchar *label = g_strdup_printf(_("Home %s"),
5894 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
5895 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
5896 g_free(tmp);
5897 tmp = NULL;
5898 g_free(label);
5899 menu = g_list_prepend(menu, act);
5902 /* other phone */
5903 phone = purple_blist_node_get_string(&buddy->node, PHONE_OTHER_PROP);
5904 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_OTHER_DISPLAY_PROP);
5905 if (phone) {
5906 gchar *label = g_strdup_printf(_("Other %s"),
5907 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
5908 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
5909 g_free(tmp);
5910 tmp = NULL;
5911 g_free(label);
5912 menu = g_list_prepend(menu, act);
5915 /* custom1 phone */
5916 phone = purple_blist_node_get_string(&buddy->node, PHONE_CUSTOM1_PROP);
5917 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_CUSTOM1_DISPLAY_PROP);
5918 if (phone) {
5919 gchar *label = g_strdup_printf(_("Custom1 %s"),
5920 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
5921 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
5922 g_free(tmp);
5923 tmp = NULL;
5924 g_free(label);
5925 menu = g_list_prepend(menu, act);
5929 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
5930 if (email) {
5931 act = purple_menu_action_new(_("Send email..."),
5932 PURPLE_CALLBACK(sipe_buddy_menu_send_email_cb),
5933 NULL, NULL);
5934 menu = g_list_prepend(menu, act);
5937 /* Access Level */
5938 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
5939 GList *menu_access_levels = sipe_get_access_control_menu(sipe_private, buddy->name);
5941 act = purple_menu_action_new(_("Access level"),
5942 NULL,
5943 NULL, menu_access_levels);
5944 menu = g_list_prepend(menu, act);
5947 /* Copy to */
5948 gr_parent = purple_buddy_get_group(buddy);
5949 for (g_node = purple_blist_get_root(); g_node; g_node = g_node->next) {
5950 PurpleGroup *group;
5952 if (g_node->type != PURPLE_BLIST_GROUP_NODE)
5953 continue;
5955 group = (PurpleGroup *)g_node;
5956 if (group == gr_parent)
5957 continue;
5959 if (purple_find_buddy_in_group(buddy->account, buddy->name, group))
5960 continue;
5962 act = purple_menu_action_new(purple_group_get_name(group),
5963 PURPLE_CALLBACK(sipe_buddy_menu_copy_to_cb),
5964 group->name, NULL);
5965 menu_groups = g_list_prepend(menu_groups, act);
5967 menu_groups = g_list_reverse(menu_groups);
5969 act = purple_menu_action_new(_("Copy to"),
5970 NULL,
5971 NULL, menu_groups);
5972 menu = g_list_prepend(menu, act);
5974 menu = g_list_reverse(menu);
5976 g_free(self);
5977 return menu;
5980 static void
5981 sipe_ask_access_domain_cb(PurpleConnection *gc, PurpleRequestFields *fields)
5983 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
5984 const char *domain = purple_request_fields_get_string(fields, "access_domain");
5985 int index = purple_request_fields_get_choice(fields, "container_id");
5986 /* move Blocked first */
5987 int i = (index == 4) ? 0 : index + 1;
5988 int container_id = containers[i];
5990 SIPE_DEBUG_INFO("sipe_ask_access_domain_cb: domain=%s, container_id=(%d)%d", domain ? domain : "", index, container_id);
5992 sipe_change_access_level(sipe_private, container_id, "domain", domain);
5995 static void
5996 sipe_ask_access_domain(struct sipe_core_private *sipe_private)
5998 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5999 PurpleAccount *account = sip->account;
6000 PurpleConnection *gc = sip->gc;
6001 PurpleRequestFields *fields;
6002 PurpleRequestFieldGroup *g;
6003 PurpleRequestField *f;
6005 fields = purple_request_fields_new();
6007 g = purple_request_field_group_new(NULL);
6008 f = purple_request_field_string_new("access_domain", _("Domain"), "partner-company.com", FALSE);
6009 purple_request_field_set_required(f, TRUE);
6010 purple_request_field_group_add_field(g, f);
6012 f = purple_request_field_choice_new("container_id", _("Access level"), 0);
6013 purple_request_field_choice_add(f, _("Personal")); /* index 0 */
6014 purple_request_field_choice_add(f, _("Team"));
6015 purple_request_field_choice_add(f, _("Company"));
6016 purple_request_field_choice_add(f, _("Public"));
6017 purple_request_field_choice_add(f, _("Blocked")); /* index 4 */
6018 purple_request_field_choice_set_default_value(f, 3); /* index */
6019 purple_request_field_set_required(f, TRUE);
6020 purple_request_field_group_add_field(g, f);
6022 purple_request_fields_add_group(fields, g);
6024 purple_request_fields(gc, _("Add new domain"),
6025 _("Add new domain"), NULL, fields,
6026 _("Add"), G_CALLBACK(sipe_ask_access_domain_cb),
6027 _("Cancel"), NULL,
6028 account, NULL, NULL, gc);
6031 static void
6032 sipe_buddy_menu_access_level_add_domain_cb(PurpleBuddy *buddy)
6034 sipe_ask_access_domain(PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE);
6038 * Workaround for missing libpurple API to release resources allocated
6039 * during blist_node_menu() callback. See also:
6041 * <http://developer.pidgin.im/ticket/12597>
6043 * We remember all memory blocks in a list and deallocate them when
6045 * - the next time we enter the callback, or
6046 * - the account is disconnected
6048 * That means that after the buddy menu has been closed we have unused
6049 * resources but at least we don't leak them anymore...
6051 static void
6052 sipe_blist_menu_free_containers(struct sipe_core_private *sipe_private)
6054 GSList *entry = sipe_private->blist_menu_containers;
6055 while (entry) {
6056 free_container(entry->data);
6057 entry = entry->next;
6059 g_slist_free(sipe_private->blist_menu_containers);
6060 sipe_private->blist_menu_containers = NULL;
6063 static void
6064 sipe_blist_menu_remember_container(struct sipe_core_private *sipe_private,
6065 struct sipe_container *container)
6067 sipe_private->blist_menu_containers = g_slist_prepend(sipe_private->blist_menu_containers,
6068 container);
6071 static GList *
6072 sipe_get_access_levels_menu(struct sipe_core_private *sipe_private,
6073 const char* member_type,
6074 const char* member_value,
6075 const gboolean extra_menu)
6077 GList *menu_access_levels = NULL;
6078 unsigned int i;
6079 char *menu_name;
6080 PurpleMenuAction *act;
6081 struct sipe_container *container;
6082 struct sipe_container_member *member;
6083 gboolean is_group_access = FALSE;
6084 int container_id = sipe_find_access_level(sipe_private, member_type, member_value, &is_group_access);
6086 for (i = 1; i <= CONTAINERS_LEN; i++) {
6087 /* to put Blocked level last in menu list.
6088 * Blocked should remaim in the first place in the containers[] array.
6090 unsigned int j = (i == CONTAINERS_LEN) ? 0 : i;
6091 const char *acc_level_name = sipe_get_access_level_name(containers[j]);
6093 container = g_new0(struct sipe_container, 1);
6094 member = g_new0(struct sipe_container_member, 1);
6095 container->id = containers[j];
6096 container->members = g_slist_append(container->members, member);
6097 member->type = g_strdup(member_type);
6098 member->value = g_strdup(member_value);
6100 /* libpurple memory leak workaround */
6101 sipe_blist_menu_remember_container(sipe_private, container);
6103 /* current container/access level */
6104 if (((int)containers[j]) == container_id) {
6105 menu_name = is_group_access ?
6106 g_strdup_printf(INDENT_MARKED_INHERITED_FMT, acc_level_name) :
6107 g_strdup_printf(INDENT_MARKED_FMT, acc_level_name);
6108 } else {
6109 menu_name = g_strdup_printf(INDENT_FMT, acc_level_name);
6112 act = purple_menu_action_new(menu_name,
6113 PURPLE_CALLBACK(sipe_buddy_menu_access_level_cb),
6114 container, NULL);
6115 g_free(menu_name);
6116 menu_access_levels = g_list_prepend(menu_access_levels, act);
6119 if (extra_menu && (container_id >= 0)) {
6120 /* separator */
6121 act = purple_menu_action_new(" --------------", NULL, NULL, NULL);
6122 menu_access_levels = g_list_prepend(menu_access_levels, act);
6124 if (!is_group_access) {
6125 container = g_new0(struct sipe_container, 1);
6126 member = g_new0(struct sipe_container_member, 1);
6127 container->id = -1;
6128 container->members = g_slist_append(container->members, member);
6129 member->type = g_strdup(member_type);
6130 member->value = g_strdup(member_value);
6132 /* libpurple memory leak workaround */
6133 sipe_blist_menu_remember_container(sipe_private, container);
6135 /* Translators: remove (clear) previously assigned access level */
6136 menu_name = g_strdup_printf(INDENT_FMT, _("Unspecify"));
6137 act = purple_menu_action_new(menu_name,
6138 PURPLE_CALLBACK(sipe_buddy_menu_access_level_cb),
6139 container, NULL);
6140 g_free(menu_name);
6141 menu_access_levels = g_list_prepend(menu_access_levels, act);
6145 menu_access_levels = g_list_reverse(menu_access_levels);
6146 return menu_access_levels;
6149 static GList *
6150 sipe_get_access_groups_menu(struct sipe_core_private *sipe_private)
6152 GList *menu_access_groups = NULL;
6153 PurpleMenuAction *act;
6154 GSList *access_domains = NULL;
6155 GSList *entry;
6156 char *menu_name;
6157 char *domain;
6159 act = purple_menu_action_new(_("People in my company"),
6160 NULL,
6161 NULL, sipe_get_access_levels_menu(sipe_private, "sameEnterprise", NULL, FALSE));
6162 menu_access_groups = g_list_prepend(menu_access_groups, act);
6164 /* this is original name, don't edit */
6165 act = purple_menu_action_new(_("People in domains connected with my company"),
6166 NULL,
6167 NULL, sipe_get_access_levels_menu(sipe_private, "federated", NULL, FALSE));
6168 menu_access_groups = g_list_prepend(menu_access_groups, act);
6170 act = purple_menu_action_new(_("People in public domains"),
6171 NULL,
6172 NULL, sipe_get_access_levels_menu(sipe_private, "publicCloud", NULL, TRUE));
6173 menu_access_groups = g_list_prepend(menu_access_groups, act);
6175 access_domains = sipe_get_access_domains(sipe_private);
6176 entry = access_domains;
6177 while (entry) {
6178 domain = entry->data;
6180 menu_name = g_strdup_printf(_("People at %s"), domain);
6181 act = purple_menu_action_new(menu_name,
6182 NULL,
6183 NULL, sipe_get_access_levels_menu(sipe_private, "domain", g_strdup(domain), TRUE));
6184 menu_access_groups = g_list_prepend(menu_access_groups, act);
6185 g_free(menu_name);
6187 entry = entry->next;
6190 /* separator */
6191 /* People in domains connected with my company */
6192 act = purple_menu_action_new("-------------------------------------------", NULL, NULL, NULL);
6193 menu_access_groups = g_list_prepend(menu_access_groups, act);
6195 act = purple_menu_action_new(_("Add new domain..."),
6196 PURPLE_CALLBACK(sipe_buddy_menu_access_level_add_domain_cb),
6197 NULL, NULL);
6198 menu_access_groups = g_list_prepend(menu_access_groups, act);
6200 menu_access_groups = g_list_reverse(menu_access_groups);
6202 return menu_access_groups;
6205 static GList *
6206 sipe_get_access_control_menu(struct sipe_core_private *sipe_private,
6207 const char* uri)
6209 GList *menu_access_levels = NULL;
6210 GList *menu_access_groups = NULL;
6211 char *menu_name;
6212 PurpleMenuAction *act;
6214 /* libpurple memory leak workaround */
6215 sipe_blist_menu_free_containers(sipe_private);
6217 menu_access_levels = sipe_get_access_levels_menu(sipe_private, "user", sipe_get_no_sip_uri(uri), TRUE);
6219 menu_access_groups = sipe_get_access_groups_menu(sipe_private);
6221 menu_name = g_strdup_printf(INDENT_FMT, _("Access groups"));
6222 act = purple_menu_action_new(menu_name,
6223 NULL,
6224 NULL, menu_access_groups);
6225 g_free(menu_name);
6226 menu_access_levels = g_list_append(menu_access_levels, act);
6228 menu_name = g_strdup_printf(INDENT_FMT, _("Online help..."));
6229 act = purple_menu_action_new(menu_name,
6230 PURPLE_CALLBACK(sipe_buddy_menu_access_level_help_cb),
6231 NULL, NULL);
6232 g_free(menu_name);
6233 menu_access_levels = g_list_append(menu_access_levels, act);
6235 return menu_access_levels;
6238 static gboolean
6239 process_get_info_response(struct sipe_core_private *sipe_private,
6240 struct sipmsg *msg, struct transaction *trans)
6242 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
6243 char *uri = trans->payload->data;
6245 PurpleNotifyUserInfo *info;
6246 PurpleBuddy *pbuddy = NULL;
6247 struct sipe_buddy *sbuddy;
6248 const char *alias = NULL;
6249 char *device_name = NULL;
6250 char *server_alias = NULL;
6251 char *phone_number = NULL;
6252 char *email = NULL;
6253 const char *site;
6254 char *first_name = NULL;
6255 char *last_name = NULL;
6257 if (!sip) return FALSE;
6259 SIPE_DEBUG_INFO("Fetching %s's user info for %s", uri, sipe_private->username);
6261 pbuddy = purple_find_buddy((PurpleAccount *)sip->account, uri);
6262 alias = purple_buddy_get_local_alias(pbuddy);
6264 //will query buddy UA's capabilities and send answer to log
6265 sipe_options_request(sipe_private, uri);
6267 sbuddy = g_hash_table_lookup(sipe_private->buddies, uri);
6268 if (sbuddy) {
6269 device_name = sbuddy->device_name ? g_strdup(sbuddy->device_name) : NULL;
6272 info = purple_notify_user_info_new();
6274 if (msg->response != 200) {
6275 SIPE_DEBUG_INFO("process_get_info_response: SERVICE response is %d", msg->response);
6276 } else {
6277 sipe_xml *searchResults;
6278 const sipe_xml *mrow;
6280 SIPE_DEBUG_INFO("process_get_info_response: body:\n%s", msg->body ? msg->body : "");
6281 searchResults = sipe_xml_parse(msg->body, msg->bodylen);
6282 if (!searchResults) {
6283 SIPE_DEBUG_INFO_NOFORMAT("process_get_info_response: no parseable searchResults");
6284 } else if ((mrow = sipe_xml_child(searchResults, "Body/Array/row"))) {
6285 const char *value;
6286 server_alias = g_strdup(sipe_xml_attribute(mrow, "displayName"));
6287 email = g_strdup(sipe_xml_attribute(mrow, "email"));
6288 phone_number = g_strdup(sipe_xml_attribute(mrow, "phone"));
6290 /* For 2007 system we will take this from ContactCard -
6291 * it has cleaner tel: URIs at least
6293 if (!SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
6294 char *tel_uri = sip_to_tel_uri(phone_number);
6295 /* trims its parameters, so call first */
6296 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_DISPLAY_NAME, server_alias);
6297 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_EMAIL, email);
6298 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_WORK_PHONE, tel_uri);
6299 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_WORK_PHONE_DISPLAY, phone_number);
6300 g_free(tel_uri);
6303 if (server_alias && strlen(server_alias) > 0) {
6304 purple_notify_user_info_add_pair(info, _("Display name"), server_alias);
6306 if ((value = sipe_xml_attribute(mrow, "title")) && strlen(value) > 0) {
6307 purple_notify_user_info_add_pair(info, _("Job title"), value);
6309 if ((value = sipe_xml_attribute(mrow, "office")) && strlen(value) > 0) {
6310 purple_notify_user_info_add_pair(info, _("Office"), value);
6312 if (phone_number && strlen(phone_number) > 0) {
6313 purple_notify_user_info_add_pair(info, _("Business phone"), phone_number);
6315 if ((value = sipe_xml_attribute(mrow, "company")) && strlen(value) > 0) {
6316 purple_notify_user_info_add_pair(info, _("Company"), value);
6318 if ((value = sipe_xml_attribute(mrow, "city")) && strlen(value) > 0) {
6319 purple_notify_user_info_add_pair(info, _("City"), value);
6321 if ((value = sipe_xml_attribute(mrow, "state")) && strlen(value) > 0) {
6322 purple_notify_user_info_add_pair(info, _("State"), value);
6324 if ((value = sipe_xml_attribute(mrow, "country")) && strlen(value) > 0) {
6325 purple_notify_user_info_add_pair(info, _("Country"), value);
6327 if (email && strlen(email) > 0) {
6328 purple_notify_user_info_add_pair(info, _("Email address"), email);
6332 sipe_xml_free(searchResults);
6335 purple_notify_user_info_add_section_break(info);
6337 if (is_empty(server_alias)) {
6338 g_free(server_alias);
6339 server_alias = g_strdup(purple_buddy_get_server_alias(pbuddy));
6340 if (server_alias) {
6341 purple_notify_user_info_add_pair(info, _("Display name"), server_alias);
6345 /* present alias if it differs from server alias */
6346 if (alias && !sipe_strequal(alias, server_alias))
6348 purple_notify_user_info_add_pair(info, _("Alias"), alias);
6351 if (is_empty(email)) {
6352 g_free(email);
6353 email = g_strdup(purple_blist_node_get_string(&pbuddy->node, EMAIL_PROP));
6354 if (email) {
6355 purple_notify_user_info_add_pair(info, _("Email address"), email);
6359 site = purple_blist_node_get_string(&pbuddy->node, SITE_PROP);
6360 if (site) {
6361 purple_notify_user_info_add_pair(info, _("Site"), site);
6364 sipe_get_first_last_names(sipe_private, uri, &first_name, &last_name);
6365 if (first_name && last_name) {
6366 char *link = g_strconcat("http://www.linkedin.com/pub/dir/", first_name, "/", last_name, NULL);
6368 purple_notify_user_info_add_pair(info, _("Find on LinkedIn"), link);
6369 g_free(link);
6371 g_free(first_name);
6372 g_free(last_name);
6374 if (device_name) {
6375 purple_notify_user_info_add_pair(info, _("Device"), device_name);
6378 /* show a buddy's user info in a nice dialog box */
6379 purple_notify_userinfo(sip->gc, /* connection the buddy info came through */
6380 uri, /* buddy's URI */
6381 info, /* body */
6382 NULL, /* callback called when dialog closed */
6383 NULL); /* userdata for callback */
6385 g_free(phone_number);
6386 g_free(server_alias);
6387 g_free(email);
6388 g_free(device_name);
6390 return TRUE;
6394 * AD search first, LDAP based
6396 void sipe_get_info(PurpleConnection *gc, const char *username)
6398 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
6399 gchar *domain_uri = sip_uri_from_name(sipe_private->public.sip_domain);
6400 char *row = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, "msRTCSIP-PrimaryUserAddress", username);
6401 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 1, row);
6402 struct transaction_payload *payload = g_new0(struct transaction_payload, 1);
6404 payload->destroy = g_free;
6405 payload->data = g_strdup(username);
6407 SIPE_DEBUG_INFO("sipe_get_contact_data: body:\n%s", body ? body : "");
6408 send_soap_request_with_cb(sipe_private, domain_uri, body,
6409 process_get_info_response, payload);
6410 g_free(domain_uri);
6411 g_free(body);
6412 g_free(row);
6416 Local Variables:
6417 mode: c
6418 c-file-style: "bsd"
6419 indent-tabs-mode: t
6420 tab-width: 8
6421 End: