core cleanup: compilation fixes with enabled voice support
[siplcs.git] / src / core / sipe.c
blob5db7b3bcb48083d592479f44e2dd1ee0eee7b2d6
1 /**
2 * @file sipe.c
4 * pidgin-sipe
6 * Copyright (C) 2010 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 "sipe-common.h"
51 #include "account.h"
52 #include "blist.h"
53 #include "connection.h"
54 #include "conversation.h"
55 #include "ft.h"
56 #include "notify.h"
57 #include "plugin.h"
58 #include "privacy.h"
59 #include "request.h"
60 #include "savedstatuses.h"
62 #include "core-depurple.h" /* Temporary for the core de-purple transition */
64 #include "http-conn.h"
65 #include "sipmsg.h"
66 #include "sip-csta.h"
67 #include "sip-transport.h"
68 #include "sipe-backend.h"
69 #include "sipe-buddy.h"
70 #include "sipe-cal.h"
71 #include "sipe-chat.h"
72 #include "sipe-conf.h"
73 #include "sipe-core.h"
74 #include "sipe-core-private.h"
75 #include "sipe-dialog.h"
76 #include "sipe-ews.h"
77 #include "sipe-domino.h"
78 #include "sipe-ft.h"
79 #include "sipe-mime.h"
80 #include "sipe-nls.h"
81 #include "sipe-schedule.h"
82 #include "sipe-session.h"
83 #include "sipe-subscriptions.h"
84 #include "sipe-media.h"
85 #include "sipe-utils.h"
86 #include "sipe-xml.h"
87 #include "uuid.h"
88 #include "sipe.h"
90 #define SIPE_IDLE_SET_DELAY 1 /* 1 sec */
92 #define UPDATE_CALENDAR_DELAY 1*60 /* 1 min */
93 #define UPDATE_CALENDAR_INTERVAL 30*60 /* 30 min */
95 /* Status identifiers (see also: sipe_status_types()) */
96 #define SIPE_STATUS_ID_UNKNOWN purple_primitive_get_id_from_type(PURPLE_STATUS_UNSET) /* Unset (primitive) */
97 #define SIPE_STATUS_ID_OFFLINE purple_primitive_get_id_from_type(PURPLE_STATUS_OFFLINE) /* Offline (primitive) */
98 #define SIPE_STATUS_ID_AVAILABLE purple_primitive_get_id_from_type(PURPLE_STATUS_AVAILABLE) /* Online */
99 /* PURPLE_STATUS_UNAVAILABLE: */
100 #define SIPE_STATUS_ID_BUSY "busy" /* Busy */
101 #define SIPE_STATUS_ID_BUSYIDLE "busyidle" /* BusyIdle */
102 #define SIPE_STATUS_ID_DND "do-not-disturb" /* Do Not Disturb */
103 #define SIPE_STATUS_ID_IN_MEETING "in-a-meeting" /* In a meeting */
104 #define SIPE_STATUS_ID_IN_CONF "in-a-conference" /* In a conference */
105 #define SIPE_STATUS_ID_ON_PHONE "on-the-phone" /* On the phone */
106 #define SIPE_STATUS_ID_INVISIBLE purple_primitive_get_id_from_type(PURPLE_STATUS_INVISIBLE) /* Appear Offline */
107 /* PURPLE_STATUS_AWAY: */
108 #define SIPE_STATUS_ID_IDLE "idle" /* Idle/Inactive */
109 #define SIPE_STATUS_ID_BRB "be-right-back" /* Be Right Back */
110 #define SIPE_STATUS_ID_AWAY purple_primitive_get_id_from_type(PURPLE_STATUS_AWAY) /* Away (primitive) */
111 /** Reuters status (user settable) */
112 #define SIPE_STATUS_ID_LUNCH "out-to-lunch" /* Out To Lunch */
113 /* ??? PURPLE_STATUS_EXTENDED_AWAY */
114 /* ??? PURPLE_STATUS_MOBILE */
115 /* ??? PURPLE_STATUS_TUNE */
117 /* Status attributes (see also sipe_status_types() */
118 #define SIPE_STATUS_ATTR_ID_MESSAGE "message"
120 static struct sipe_activity_map_struct
122 sipe_activity type;
123 const char *token;
124 const char *desc;
125 const char *status_id;
127 } const sipe_activity_map[] =
129 /* This has nothing to do with Availability numbers, like 3500 (online).
130 * Just a mapping of Communicator Activities to Purple statuses to be able display them in Pidgin.
132 { SIPE_ACTIVITY_UNSET, "unset", NULL , NULL },
133 { SIPE_ACTIVITY_ONLINE, "online", NULL , NULL },
134 { SIPE_ACTIVITY_INACTIVE, SIPE_STATUS_ID_IDLE, N_("Inactive") , NULL },
135 { SIPE_ACTIVITY_BUSY, SIPE_STATUS_ID_BUSY, N_("Busy") , SIPE_STATUS_ID_BUSY },
136 { SIPE_ACTIVITY_BUSYIDLE, SIPE_STATUS_ID_BUSYIDLE, N_("Busy-Idle") , NULL },
137 { SIPE_ACTIVITY_DND, SIPE_STATUS_ID_DND, NULL , SIPE_STATUS_ID_DND },
138 { SIPE_ACTIVITY_BRB, SIPE_STATUS_ID_BRB, N_("Be right back") , SIPE_STATUS_ID_BRB },
139 { SIPE_ACTIVITY_AWAY, "away", NULL , NULL },
140 { SIPE_ACTIVITY_LUNCH, SIPE_STATUS_ID_LUNCH, N_("Out to lunch") , NULL },
141 { SIPE_ACTIVITY_OFFLINE, "offline", NULL , NULL },
142 { SIPE_ACTIVITY_ON_PHONE, SIPE_STATUS_ID_ON_PHONE, N_("In a call") , NULL },
143 { SIPE_ACTIVITY_IN_CONF, SIPE_STATUS_ID_IN_CONF, N_("In a conference") , NULL },
144 { SIPE_ACTIVITY_IN_MEETING, SIPE_STATUS_ID_IN_MEETING, N_("In a meeting") , NULL },
145 { SIPE_ACTIVITY_OOF, "out-of-office", N_("Out of office") , NULL },
146 { SIPE_ACTIVITY_URGENT_ONLY, "urgent-interruptions-only", N_("Urgent interruptions only") , NULL }
148 /** @param x is sipe_activity */
149 #define SIPE_ACTIVITY_I18N(x) gettext(sipe_activity_map[x].desc)
151 static sipe_activity
152 sipe_get_activity_by_token(const char *token)
154 int i;
156 for (i = 0; i < SIPE_ACTIVITY_NUM_TYPES; i++)
158 if (sipe_strequal(token, sipe_activity_map[i].token))
159 return sipe_activity_map[i].type;
162 return sipe_activity_map[0].type;
165 static const char *
166 sipe_get_activity_desc_by_token(const char *token)
168 if (!token) return NULL;
170 return SIPE_ACTIVITY_I18N(sipe_get_activity_by_token(token));
173 static void send_presence_status(struct sipe_core_private *sipe_private,
174 void *unused);
177 * @param from0 from URI (with 'sip:' prefix). Will be filled with self-URI if NULL passed.
179 static void
180 send_soap_request_with_cb(struct sipe_core_private *sipe_private,
181 gchar *from0,
182 gchar *body,
183 TransCallback callback,
184 struct transaction_payload *payload)
186 gchar *from = from0 ? g_strdup(from0) : sip_uri_self(sipe_private);
187 gchar *contact = get_contact(sipe_private);
188 gchar *hdr = g_strdup_printf("Contact: %s\r\n"
189 "Content-Type: application/SOAP+xml\r\n",contact);
191 struct transaction *trans = sip_transport_service(sipe_private,
192 from,
193 hdr,
194 body,
195 callback);
196 trans->payload = payload;
198 g_free(from);
199 g_free(contact);
200 g_free(hdr);
203 static void send_soap_request(struct sipe_core_private *sipe_private,
204 gchar *body)
206 send_soap_request_with_cb(sipe_private, NULL, body, NULL, NULL);
210 * Returns pointer to URI without sip: prefix if any
212 * @param sip_uri SIP URI possibly with sip: prefix. Example: sip:first.last@hq.company.com
213 * @return pointer to URL without sip: prefix. Coresponding example: first.last@hq.company.com
215 * Doesn't allocate memory
217 static const char *
218 sipe_get_no_sip_uri(const char *sip_uri)
220 const char *prefix = "sip:";
221 if (!sip_uri) return NULL;
223 if (g_str_has_prefix(sip_uri, prefix)) {
224 return (sip_uri+strlen(prefix));
225 } else {
226 return sip_uri;
230 static void
231 sipe_contact_set_acl (struct sipe_core_private *sipe_private,
232 const gchar *who,
233 gchar *rights)
235 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
236 gchar * body = g_strdup_printf(SIPE_SOAP_ALLOW_DENY, who, rights, sip->acl_delta++);
237 send_soap_request(sipe_private, body);
238 g_free(body);
241 static void
242 sipe_change_access_level(struct sipe_core_private *sipe_private,
243 const int container_id,
244 const gchar *type,
245 const gchar *value);
247 void
248 sipe_core_contact_allow_deny (struct sipe_core_public *sipe_public,
249 const gchar * who, gboolean allow)
251 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
253 if (allow) {
254 SIPE_DEBUG_INFO("Authorizing contact %s", who);
255 } else {
256 SIPE_DEBUG_INFO("Blocking contact %s", who);
259 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
260 sipe_change_access_level(sipe_private, (allow ? -1 : 32000), "user", sipe_get_no_sip_uri(who));
261 } else {
262 sipe_contact_set_acl(sipe_private, who, allow ? "AA" : "BD");
266 static
267 void sipe_auth_user_cb(void * data)
269 struct sipe_auth_job * job = (struct sipe_auth_job *) data;
270 if (!job) return;
272 sipe_core_contact_allow_deny((struct sipe_core_public *)job->sipe_private, job->who, TRUE);
273 g_free(job);
276 static
277 void sipe_deny_user_cb(void * data)
279 struct sipe_auth_job * job = (struct sipe_auth_job *) data;
280 if (!job) return;
282 sipe_core_contact_allow_deny((struct sipe_core_public *)job->sipe_private, job->who, FALSE);
283 g_free(job);
286 /** @applicable: 2005-
288 static void
289 sipe_process_presence_wpending (struct sipe_core_private *sipe_private,
290 struct sipmsg * msg)
292 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
293 sipe_xml *watchers;
294 const sipe_xml *watcher;
295 // Ensure it's either not a response (eg it's a BENOTIFY) or that it's a 200 OK response
296 if (msg->response != 0 && msg->response != 200) return;
298 if (msg->bodylen == 0 || msg->body == NULL || sipe_strequal(sipmsg_find_header(msg, "Event"), "msrtc.wpending")) return;
300 watchers = sipe_xml_parse(msg->body, msg->bodylen);
301 if (!watchers) return;
303 for (watcher = sipe_xml_child(watchers, "watcher"); watcher; watcher = sipe_xml_twin(watcher)) {
304 gchar * remote_user = g_strdup(sipe_xml_attribute(watcher, "uri"));
305 gchar * alias = g_strdup(sipe_xml_attribute(watcher, "displayName"));
306 gboolean on_list = g_hash_table_lookup(sipe_private->buddies, remote_user) != NULL;
308 // TODO pull out optional displayName to pass as alias
309 if (remote_user) {
310 struct sipe_auth_job * job = g_new0(struct sipe_auth_job, 1);
311 job->who = remote_user;
312 job->sipe_private = sipe_private;
313 purple_account_request_authorization(
314 sip->account,
315 remote_user,
316 _("you"), /* id */
317 alias,
318 NULL, /* message */
319 on_list,
320 sipe_auth_user_cb,
321 sipe_deny_user_cb,
322 (void *) job);
327 sipe_xml_free(watchers);
328 return;
331 static void
332 sipe_group_add(struct sipe_core_private *sipe_private,
333 struct sipe_group * group)
335 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
336 PurpleGroup * purple_group = purple_find_group(group->name);
337 if (!purple_group) {
338 purple_group = purple_group_new(group->name);
339 purple_blist_add_group(purple_group, NULL);
342 if (purple_group) {
343 group->purple_group = purple_group;
344 sip->groups = g_slist_append(sip->groups, group);
345 SIPE_DEBUG_INFO("added group %s (id %d)", group->name, group->id);
346 } else {
347 SIPE_DEBUG_INFO("did not add group %s", group->name ? group->name : "");
351 static struct sipe_group *sipe_group_find_by_id(struct sipe_core_private *sipe_private,
352 int id)
354 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
355 struct sipe_group *group;
356 GSList *entry;
357 if (sip == NULL) {
358 return NULL;
361 entry = sip->groups;
362 while (entry) {
363 group = entry->data;
364 if (group->id == id) {
365 return group;
367 entry = entry->next;
369 return NULL;
372 static struct sipe_group *sipe_group_find_by_name(struct sipe_core_private *sipe_private,
373 const gchar * name)
375 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
376 struct sipe_group *group;
377 GSList *entry;
378 if (!sip || !name) {
379 return NULL;
382 entry = sip->groups;
383 while (entry) {
384 group = entry->data;
385 if (sipe_strequal(group->name, name)) {
386 return group;
388 entry = entry->next;
390 return NULL;
393 static void
394 sipe_group_rename(struct sipe_core_private *sipe_private,
395 struct sipe_group *group,
396 gchar *name)
398 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
399 gchar *body;
400 SIPE_DEBUG_INFO("Renaming group %s to %s", group->name, name);
401 body = g_markup_printf_escaped(SIPE_SOAP_MOD_GROUP, group->id, name, sip->contacts_delta++);
402 send_soap_request(sipe_private, body);
403 g_free(body);
404 g_free(group->name);
405 group->name = g_strdup(name);
409 * Only appends if no such value already stored.
410 * Like Set in Java.
412 GSList * slist_insert_unique_sorted(GSList *list, gpointer data, GCompareFunc func) {
413 GSList * res = list;
414 if (!g_slist_find_custom(list, data, func)) {
415 res = g_slist_insert_sorted(list, data, func);
417 return res;
420 static int
421 sipe_group_compare(struct sipe_group *group1, struct sipe_group *group2) {
422 return group1->id - group2->id;
426 * Returns string like "2 4 7 8" - group ids buddy belong to.
428 static gchar *
429 sipe_get_buddy_groups_string (struct sipe_buddy *buddy) {
430 int i = 0;
431 gchar *res;
432 //creating array from GList, converting int to gchar*
433 gchar **ids_arr = g_new(gchar *, g_slist_length(buddy->groups) + 1);
434 GSList *entry = buddy->groups;
436 if (!ids_arr) return NULL;
438 while (entry) {
439 struct sipe_group * group = entry->data;
440 ids_arr[i] = g_strdup_printf("%d", group->id);
441 entry = entry->next;
442 i++;
444 ids_arr[i] = NULL;
445 res = g_strjoinv(" ", ids_arr);
446 g_strfreev(ids_arr);
447 return res;
451 * Sends buddy update to server
453 void
454 sipe_core_group_set_user(struct sipe_core_public *sipe_public, const gchar * who)
456 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA;
457 struct sipe_buddy *buddy = g_hash_table_lookup(SIPE_CORE_PRIVATE->buddies, who);
458 PurpleBuddy *purple_buddy = purple_find_buddy (sip->account, who);
460 if (buddy && purple_buddy) {
461 const char *alias = purple_buddy_get_alias(purple_buddy);
462 gchar *groups = sipe_get_buddy_groups_string(buddy);
463 if (groups) {
464 gchar *body;
465 SIPE_DEBUG_INFO("Saving buddy %s with alias %s and groups %s", who, alias, groups);
467 body = g_markup_printf_escaped(SIPE_SOAP_SET_CONTACT,
468 alias, groups, "true", buddy->name, sip->contacts_delta++
470 send_soap_request(SIPE_CORE_PRIVATE, body);
471 g_free(groups);
472 g_free(body);
477 static gboolean process_add_group_response(struct sipe_core_private *sipe_private,
478 struct sipmsg *msg,
479 struct transaction *trans)
481 if (msg->response == 200) {
482 struct sipe_group *group;
483 struct group_user_context *ctx = trans->payload->data;
484 sipe_xml *xml;
485 const sipe_xml *node;
486 char *group_id;
487 struct sipe_buddy *buddy;
489 xml = sipe_xml_parse(msg->body, msg->bodylen);
490 if (!xml) {
491 return FALSE;
494 node = sipe_xml_child(xml, "Body/addGroup/groupID");
495 if (!node) {
496 sipe_xml_free(xml);
497 return FALSE;
500 group_id = sipe_xml_data(node);
501 if (!group_id) {
502 sipe_xml_free(xml);
503 return FALSE;
506 group = g_new0(struct sipe_group, 1);
507 group->id = (int)g_ascii_strtod(group_id, NULL);
508 g_free(group_id);
509 group->name = g_strdup(ctx->group_name);
511 sipe_group_add(sipe_private, group);
513 buddy = g_hash_table_lookup(sipe_private->buddies, ctx->user_name);
514 if (buddy) {
515 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
518 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 gchar *body;
541 ctx->group_name = g_strdup(name);
542 ctx->user_name = g_strdup(who);
543 payload->destroy = sipe_group_context_destroy;
544 payload->data = ctx;
546 body = g_markup_printf_escaped(SIPE_SOAP_ADD_GROUP, name, sip->contacts_delta++);
547 send_soap_request_with_cb(sipe_private, NULL, body, process_add_group_response, payload);
548 g_free(body);
551 static void
552 sipe_sched_calendar_status_update(struct sipe_core_private *sipe_private,
553 time_t calculate_from);
555 static int
556 sipe_get_availability_by_status(const char* sipe_status_id, char** activity_token);
558 static const char*
559 sipe_get_status_by_availability(int avail,
560 char** activity);
562 static void
563 sipe_set_purple_account_status_and_note(const PurpleAccount *account,
564 const char *status_id,
565 const char *message,
566 time_t do_not_publish[]);
568 static void
569 sipe_apply_calendar_status(struct sipe_core_private *sipe_private,
570 struct sipe_buddy *sbuddy,
571 const char *status_id)
573 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
574 time_t cal_avail_since;
575 int cal_status = sipe_cal_get_status(sbuddy, time(NULL), &cal_avail_since);
576 int avail;
577 gchar *self_uri;
579 if (!sbuddy) return;
581 if (cal_status < SIPE_CAL_NO_DATA) {
582 SIPE_DEBUG_INFO("sipe_apply_calendar_status: cal_status : %d for %s", cal_status, sbuddy->name);
583 SIPE_DEBUG_INFO("sipe_apply_calendar_status: cal_avail_since : %s", asctime(localtime(&cal_avail_since)));
586 /* scheduled Cal update call */
587 if (!status_id) {
588 status_id = sbuddy->last_non_cal_status_id;
589 g_free(sbuddy->activity);
590 sbuddy->activity = g_strdup(sbuddy->last_non_cal_activity);
593 if (!status_id) {
594 SIPE_DEBUG_INFO("sipe_apply_calendar_status: status_id is NULL for %s, exiting.",
595 sbuddy->name ? sbuddy->name : "" );
596 return;
599 /* adjust to calendar status */
600 if (cal_status != SIPE_CAL_NO_DATA) {
601 SIPE_DEBUG_INFO("sipe_apply_calendar_status: user_avail_since: %s", asctime(localtime(&sbuddy->user_avail_since)));
603 if (cal_status == SIPE_CAL_BUSY
604 && cal_avail_since > sbuddy->user_avail_since
605 && 6500 >= sipe_get_availability_by_status(status_id, NULL))
607 status_id = SIPE_STATUS_ID_BUSY;
608 g_free(sbuddy->activity);
609 sbuddy->activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_IN_MEETING));
611 avail = sipe_get_availability_by_status(status_id, NULL);
613 SIPE_DEBUG_INFO("sipe_apply_calendar_status: activity_since : %s", asctime(localtime(&sbuddy->activity_since)));
614 if (cal_avail_since > sbuddy->activity_since) {
615 if (cal_status == SIPE_CAL_OOF
616 && avail >= 15000) /* 12000 in 2007 */
618 g_free(sbuddy->activity);
619 sbuddy->activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_OOF));
624 /* then set status_id actually */
625 SIPE_DEBUG_INFO("sipe_apply_calendar_status: to %s for %s", status_id, sbuddy->name ? sbuddy->name : "" );
626 purple_prpl_got_user_status(sip->account, sbuddy->name, status_id, NULL);
628 /* set our account state to the one in roaming (including calendar info) */
629 self_uri = sip_uri_self(sipe_private);
630 if (sip->initial_state_published && sipe_strcase_equal(sbuddy->name, self_uri)) {
631 if (sipe_strequal(status_id, SIPE_STATUS_ID_OFFLINE)) {
632 status_id = g_strdup(SIPE_STATUS_ID_INVISIBLE); /* not not let offline status switch us off */
635 SIPE_DEBUG_INFO("sipe_apply_calendar_status: switch to '%s' for the account", sip->status);
636 sipe_set_purple_account_status_and_note(sip->account, status_id, sip->note, sip->do_not_publish);
638 g_free(self_uri);
641 static void
642 sipe_got_user_status(struct sipe_core_private *sipe_private,
643 const char* uri,
644 const char *status_id)
646 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
647 struct sipe_buddy *sbuddy = g_hash_table_lookup(sipe_private->buddies, uri);
649 if (!sbuddy) return;
651 /* Check if on 2005 system contact's calendar,
652 * then set/preserve it.
654 if (!SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
655 sipe_apply_calendar_status(sipe_private, sbuddy, status_id);
656 } else {
657 purple_prpl_got_user_status(sip->account, uri, status_id, NULL);
661 static void
662 update_calendar_status_cb(SIPE_UNUSED_PARAMETER char *name,
663 struct sipe_buddy *sbuddy,
664 struct sipe_core_private *sipe_private)
666 sipe_apply_calendar_status(sipe_private, sbuddy, NULL);
670 * Updates contact's status
671 * based on their calendar information.
673 * Applicability: 2005 systems
675 static void
676 update_calendar_status(struct sipe_core_private *sipe_private,
677 SIPE_UNUSED_PARAMETER void *unused)
679 SIPE_DEBUG_INFO_NOFORMAT("update_calendar_status() started.");
680 g_hash_table_foreach(sipe_private->buddies, (GHFunc)update_calendar_status_cb, sipe_private);
682 /* repeat scheduling */
683 sipe_sched_calendar_status_update(sipe_private, time(NULL) + 3*60 /* 3 min */);
687 * Schedules process of contacts' status update
688 * based on their calendar information.
689 * Should be scheduled to the beginning of every
690 * 15 min interval, like:
691 * 13:00, 13:15, 13:30, 13:45, etc.
693 * Applicability: 2005 systems
695 static void
696 sipe_sched_calendar_status_update(struct sipe_core_private *sipe_private,
697 time_t calculate_from)
699 int interval = 15*60;
700 /** start of the beginning of closest 15 min interval. */
701 time_t next_start = ((time_t)((int)((int)calculate_from)/interval + 1)*interval);
703 SIPE_DEBUG_INFO("sipe_sched_calendar_status_update: calculate_from time: %s",
704 asctime(localtime(&calculate_from)));
705 SIPE_DEBUG_INFO("sipe_sched_calendar_status_update: next start time : %s",
706 asctime(localtime(&next_start)));
708 sipe_schedule_seconds(sipe_private,
709 "<+2005-cal-status>",
710 NULL,
711 next_start - time(NULL),
712 update_calendar_status,
713 NULL);
717 * Schedules process of self status publish
718 * based on own calendar information.
719 * Should be scheduled to the beginning of every
720 * 15 min interval, like:
721 * 13:00, 13:15, 13:30, 13:45, etc.
723 * Applicability: 2007+ systems
725 static void
726 sipe_sched_calendar_status_self_publish(struct sipe_core_private *sipe_private,
727 time_t calculate_from)
729 int interval = 5*60;
730 /** start of the beginning of closest 5 min interval. */
731 time_t next_start = ((time_t)((int)((int)calculate_from)/interval + 1)*interval);
733 SIPE_DEBUG_INFO("sipe_sched_calendar_status_self_publish: calculate_from time: %s",
734 asctime(localtime(&calculate_from)));
735 SIPE_DEBUG_INFO("sipe_sched_calendar_status_self_publish: next start time : %s",
736 asctime(localtime(&next_start)));
738 sipe_schedule_seconds(sipe_private,
739 "<+2007-cal-status>",
740 NULL,
741 next_start - time(NULL),
742 publish_calendar_status_self,
743 NULL);
746 static void sipe_subscribe_resource_uri(const char *name,
747 SIPE_UNUSED_PARAMETER gpointer value,
748 gchar **resources_uri)
750 gchar *tmp = *resources_uri;
751 *resources_uri = g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp, name);
752 g_free(tmp);
755 static void sipe_subscribe_resource_uri_with_context(const char *name, gpointer value, gchar **resources_uri)
757 struct sipe_buddy *sbuddy = (struct sipe_buddy *)value;
758 gchar *context = sbuddy && sbuddy->just_added ? "><context/></resource>" : "/>";
759 gchar *tmp = *resources_uri;
761 if (sbuddy) sbuddy->just_added = FALSE; /* should be enought to include context one time */
763 *resources_uri = g_strdup_printf("%s<resource uri=\"%s\"%s\n", tmp, name, context);
764 g_free(tmp);
768 * Support for Batch Category SUBSCRIBE [MS-PRES] - msrtc-event-categories+xml OCS 2007
769 * Support for Batch Category SUBSCRIBE [MS-SIP] - adrl+xml LCS 2005
770 * The user sends an initial batched category SUBSCRIBE request against all contacts on his roaming list in only a request
771 * A batch category SUBSCRIBE request MUST have the same To-URI and From-URI.
772 * This header will be send only if adhoclist there is a "Supported: adhoclist" in REGISTER answer else will be send a Single Category SUBSCRIBE
775 static void sipe_subscribe_presence_batched_to(struct sipe_core_private *sipe_private,
776 gchar *resources_uri,
777 gchar *to)
779 gchar *contact = get_contact(sipe_private);
780 gchar *request;
781 gchar *content;
782 gchar *require = "";
783 gchar *accept = "";
784 gchar *autoextend = "";
785 gchar *content_type;
787 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
788 require = ", categoryList";
789 accept = ", application/msrtc-event-categories+xml, application/xpidf+xml, application/pidf+xml";
790 content_type = "application/msrtc-adrl-categorylist+xml";
791 content = g_strdup_printf(
792 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
793 "<action name=\"subscribe\" id=\"63792024\">\n"
794 "<adhocList>\n%s</adhocList>\n"
795 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
796 "<category name=\"calendarData\"/>\n"
797 "<category name=\"contactCard\"/>\n"
798 "<category name=\"note\"/>\n"
799 "<category name=\"state\"/>\n"
800 "</categoryList>\n"
801 "</action>\n"
802 "</batchSub>", sipe_private->username, resources_uri);
803 } else {
804 autoextend = "Supported: com.microsoft.autoextend\r\n";
805 content_type = "application/adrl+xml";
806 content = g_strdup_printf(
807 "<adhoclist xmlns=\"urn:ietf:params:xml:ns:adrl\" uri=\"sip:%s\" name=\"sip:%s\">\n"
808 "<create xmlns=\"\">\n%s</create>\n"
809 "</adhoclist>\n", sipe_private->username, sipe_private->username, resources_uri);
811 g_free(resources_uri);
813 request = g_strdup_printf(
814 "Require: adhoclist%s\r\n"
815 "Supported: eventlist\r\n"
816 "Accept: application/rlmi+xml, multipart/related, text/xml+msrtc.pidf%s\r\n"
817 "Supported: ms-piggyback-first-notify\r\n"
818 "%sSupported: ms-benotify\r\n"
819 "Proxy-Require: ms-benotify\r\n"
820 "Event: presence\r\n"
821 "Content-Type: %s\r\n"
822 "Contact: %s\r\n", require, accept, autoextend, content_type, contact);
823 g_free(contact);
825 sipe_subscribe_presence_buddy(sipe_private, to, request, content);
827 g_free(content);
828 g_free(to);
829 g_free(request);
832 static void sipe_subscribe_presence_batched(struct sipe_core_private *sipe_private,
833 SIPE_UNUSED_PARAMETER void *unused)
835 gchar *to = sip_uri_self(sipe_private);
836 gchar *resources_uri = g_strdup("");
837 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
838 g_hash_table_foreach(sipe_private->buddies, (GHFunc) sipe_subscribe_resource_uri_with_context , &resources_uri);
839 } else {
840 g_hash_table_foreach(sipe_private->buddies, (GHFunc) sipe_subscribe_resource_uri, &resources_uri);
843 sipe_subscribe_presence_batched_to(sipe_private, resources_uri, to);
846 struct presence_batched_routed {
847 gchar *host;
848 GSList *buddies;
851 static void sipe_subscribe_presence_batched_routed_free(void *payload)
853 struct presence_batched_routed *data = payload;
854 GSList *buddies = data->buddies;
855 while (buddies) {
856 g_free(buddies->data);
857 buddies = buddies->next;
859 g_slist_free(data->buddies);
860 g_free(data->host);
861 g_free(payload);
864 static void sipe_subscribe_presence_batched_routed(struct sipe_core_private *sipe_private,
865 void *payload)
867 struct presence_batched_routed *data = payload;
868 GSList *buddies = data->buddies;
869 gchar *resources_uri = g_strdup("");
870 while (buddies) {
871 gchar *tmp = resources_uri;
872 resources_uri = g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp, (char *) buddies->data);
873 g_free(tmp);
874 buddies = buddies->next;
876 sipe_subscribe_presence_batched_to(sipe_private, resources_uri,
877 g_strdup(data->host));
881 * Single Category SUBSCRIBE [MS-PRES] ; To send when the server returns a 200 OK message with state="resubscribe" in response.
882 * The user sends a single SUBSCRIBE request to the subscribed contact.
883 * The To-URI and the URI listed in the resource list MUST be the same for a single category SUBSCRIBE request.
887 static void sipe_subscribe_presence_single(struct sipe_core_private *sipe_private,
888 void *buddy_name)
890 gchar *to = sip_uri((char *)buddy_name);
891 gchar *tmp = get_contact(sipe_private);
892 gchar *request;
893 gchar *content = NULL;
894 gchar *autoextend = "";
895 gchar *content_type = "";
896 struct sipe_buddy *sbuddy = g_hash_table_lookup(sipe_private->buddies, to);
897 gchar *context = sbuddy && sbuddy->just_added ? "><context/></resource>" : "/>";
899 if (sbuddy) sbuddy->just_added = FALSE;
901 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
902 content_type = "Content-Type: application/msrtc-adrl-categorylist+xml\r\n";
903 } else {
904 autoextend = "Supported: com.microsoft.autoextend\r\n";
907 request = g_strdup_printf(
908 "Accept: application/msrtc-event-categories+xml, text/xml+msrtc.pidf, application/xpidf+xml, application/pidf+xml, application/rlmi+xml, multipart/related\r\n"
909 "Supported: ms-piggyback-first-notify\r\n"
910 "%s%sSupported: ms-benotify\r\n"
911 "Proxy-Require: ms-benotify\r\n"
912 "Event: presence\r\n"
913 "Contact: %s\r\n", autoextend, content_type, tmp);
915 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
916 content = g_strdup_printf(
917 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
918 "<action name=\"subscribe\" id=\"63792024\"><adhocList>\n"
919 "<resource uri=\"%s\"%s\n"
920 "</adhocList>\n"
921 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
922 "<category name=\"calendarData\"/>\n"
923 "<category name=\"contactCard\"/>\n"
924 "<category name=\"note\"/>\n"
925 "<category name=\"state\"/>\n"
926 "</categoryList>\n"
927 "</action>\n"
928 "</batchSub>", sipe_private->username, to, context);
931 g_free(tmp);
933 sipe_subscribe_presence_buddy(sipe_private, to, request, content);
935 g_free(content);
936 g_free(to);
937 g_free(request);
940 void sipe_set_status(PurpleAccount *account, PurpleStatus *status)
942 SIPE_DEBUG_INFO("sipe_set_status: status=%s", purple_status_get_id(status));
944 if (!purple_status_is_active(status))
945 return;
947 if (account->gc) {
948 struct sipe_core_private *sipe_private = PURPLE_ACCOUNT_TO_SIPE_CORE_PRIVATE;
949 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
951 if (sip) {
952 gchar *action_name;
953 gchar *tmp;
954 time_t now = time(NULL);
955 const char *status_id = purple_status_get_id(status);
956 const char *note = purple_status_get_attr_string(status, SIPE_STATUS_ATTR_ID_MESSAGE);
957 sipe_activity activity = sipe_get_activity_by_token(status_id);
958 gboolean do_not_publish = ((now - sip->do_not_publish[activity]) <= 2);
960 /* when other point of presence clears note, but we are keeping
961 * state if OOF note.
963 if (do_not_publish && !note && sip->cal && sip->cal->oof_note) {
964 SIPE_DEBUG_INFO_NOFORMAT("sipe_set_status: enabling publication as OOF note keepers.");
965 do_not_publish = FALSE;
968 SIPE_DEBUG_INFO("sipe_set_status: was: sip->do_not_publish[%s]=%d [?] now(time)=%d",
969 status_id, (int)sip->do_not_publish[activity], (int)now);
971 sip->do_not_publish[activity] = 0;
972 SIPE_DEBUG_INFO("sipe_set_status: set: sip->do_not_publish[%s]=%d [0]",
973 status_id, (int)sip->do_not_publish[activity]);
975 if (do_not_publish)
977 SIPE_DEBUG_INFO_NOFORMAT("sipe_set_status: publication was switched off, exiting.");
978 return;
981 g_free(sip->status);
982 sip->status = g_strdup(status_id);
984 /* hack to escape apostrof before comparison */
985 tmp = note ? sipe_utils_str_replace(note, "'", "&apos;") : NULL;
987 /* this will preserve OOF flag as well */
988 if (!sipe_strequal(tmp, sip->note)) {
989 sip->is_oof_note = FALSE;
990 g_free(sip->note);
991 sip->note = g_strdup(note);
992 sip->note_since = time(NULL);
994 g_free(tmp);
996 /* schedule 2 sec to capture idle flag */
997 action_name = g_strdup_printf("<%s>", "+set-status");
998 sipe_schedule_seconds(sipe_private,
999 action_name,
1000 NULL,
1001 SIPE_IDLE_SET_DELAY,
1002 send_presence_status,
1003 NULL);
1004 g_free(action_name);
1009 void
1010 sipe_set_idle(PurpleConnection * gc,
1011 int interval)
1013 SIPE_DEBUG_INFO("sipe_set_idle: interval=%d", interval);
1015 if (gc) {
1016 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
1017 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
1019 if (sip) {
1020 sip->idle_switch = time(NULL);
1021 SIPE_DEBUG_INFO("sipe_set_idle: sip->idle_switch : %s", asctime(localtime(&(sip->idle_switch))));
1026 void
1027 sipe_group_buddy(PurpleConnection *gc,
1028 const char *who,
1029 const char *old_group_name,
1030 const char *new_group_name)
1032 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
1033 struct sipe_buddy * buddy = g_hash_table_lookup(sipe_private->buddies, who);
1034 struct sipe_group * old_group = NULL;
1035 struct sipe_group * new_group;
1037 SIPE_DEBUG_INFO("sipe_group_buddy[CB]: who:%s old_group_name:%s new_group_name:%s",
1038 who ? who : "", old_group_name ? old_group_name : "", new_group_name ? new_group_name : "");
1040 if(!buddy) { // buddy not in roaming list
1041 return;
1044 if (old_group_name) {
1045 old_group = sipe_group_find_by_name(sipe_private, old_group_name);
1047 new_group = sipe_group_find_by_name(sipe_private, new_group_name);
1049 if (old_group) {
1050 buddy->groups = g_slist_remove(buddy->groups, old_group);
1051 SIPE_DEBUG_INFO("buddy %s removed from old group %s", who, old_group_name);
1054 if (!new_group) {
1055 sipe_group_create(sipe_private, new_group_name, who);
1056 } else {
1057 buddy->groups = slist_insert_unique_sorted(buddy->groups, new_group, (GCompareFunc)sipe_group_compare);
1058 sipe_core_group_set_user(SIPE_CORE_PUBLIC, who);
1062 void sipe_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
1064 SIPE_DEBUG_INFO("sipe_add_buddy[CB]: buddy:%s group:%s", buddy ? buddy->name : "", group ? group->name : "");
1066 /* libpurple can call us with undefined buddy or group */
1067 if (buddy && group) {
1068 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
1070 /* Buddy name must be lower case as we use purple_normalize_nocase() to compare */
1071 gchar *buddy_name = g_ascii_strdown(buddy->name, -1);
1072 purple_blist_rename_buddy(buddy, buddy_name);
1073 g_free(buddy_name);
1075 /* Prepend sip: if needed */
1076 if (!g_str_has_prefix(buddy->name, "sip:")) {
1077 gchar *buf = sip_uri_from_name(buddy->name);
1078 purple_blist_rename_buddy(buddy, buf);
1079 g_free(buf);
1082 if (!g_hash_table_lookup(sipe_private->buddies, buddy->name)) {
1083 struct sipe_buddy *b = g_new0(struct sipe_buddy, 1);
1084 SIPE_DEBUG_INFO("sipe_add_buddy: adding %s", buddy->name);
1085 b->name = g_strdup(buddy->name);
1086 b->just_added = TRUE;
1087 g_hash_table_insert(sipe_private->buddies, b->name, b);
1088 sipe_group_buddy(gc, b->name, NULL, group->name);
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);
1098 static void sipe_free_buddy(struct sipe_buddy *buddy)
1100 #ifndef _WIN32
1102 * We are calling g_hash_table_foreach_steal(). That means that no
1103 * key/value deallocation functions are called. Therefore the glib
1104 * hash code does not touch the key (buddy->name) or value (buddy)
1105 * of the to-be-deleted hash node at all. It follows that we
1107 * - MUST free the memory for the key ourselves and
1108 * - ARE allowed to do it in this function
1110 * Conclusion: glib must be broken on the Windows platform if sipe
1111 * crashes with SIGTRAP when closing. You'll have to live
1112 * with the memory leak until this is fixed.
1114 g_free(buddy->name);
1115 #endif
1116 g_free(buddy->activity);
1117 g_free(buddy->meeting_subject);
1118 g_free(buddy->meeting_location);
1119 g_free(buddy->note);
1121 g_free(buddy->cal_start_time);
1122 g_free(buddy->cal_free_busy_base64);
1123 g_free(buddy->cal_free_busy);
1124 g_free(buddy->last_non_cal_activity);
1126 sipe_cal_free_working_hours(buddy->cal_working_hours);
1128 g_free(buddy->device_name);
1129 g_slist_free(buddy->groups);
1130 g_free(buddy);
1134 * Unassociates buddy from group first.
1135 * Then see if no groups left, removes buddy completely.
1136 * Otherwise updates buddy groups on server.
1138 void sipe_remove_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
1140 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
1141 struct sipe_buddy *b;
1142 struct sipe_group *g = NULL;
1144 SIPE_DEBUG_INFO("sipe_remove_buddy[CB]: buddy:%s group:%s", buddy ? buddy->name : "", group ? group->name : "");
1145 if (!buddy) return;
1147 b = g_hash_table_lookup(sipe_private->buddies, buddy->name);
1148 if (!b) return;
1150 if (group) {
1151 g = sipe_group_find_by_name(sipe_private, group->name);
1154 if (g) {
1155 b->groups = g_slist_remove(b->groups, g);
1156 SIPE_DEBUG_INFO("buddy %s removed from group %s", buddy->name, g->name);
1159 if (g_slist_length(b->groups) < 1) {
1160 gchar *action_name = sipe_utils_presence_key(buddy->name);
1161 sipe_schedule_cancel(sipe_private, action_name);
1162 g_free(action_name);
1164 g_hash_table_remove(sipe_private->buddies, buddy->name);
1166 if (b->name) {
1167 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
1168 gchar * body = g_strdup_printf(SIPE_SOAP_DEL_CONTACT, b->name, sip->contacts_delta++);
1169 send_soap_request(sipe_private, body);
1170 g_free(body);
1173 sipe_free_buddy(b);
1174 } else {
1175 //updates groups on server
1176 sipe_core_group_set_user(SIPE_CORE_PUBLIC, b->name);
1181 void
1182 sipe_rename_group(PurpleConnection *gc,
1183 const char *old_name,
1184 PurpleGroup *group,
1185 SIPE_UNUSED_PARAMETER GList *moved_buddies)
1187 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
1188 struct sipe_group * s_group = sipe_group_find_by_name(sipe_private, old_name);
1189 if (s_group) {
1190 sipe_group_rename(sipe_private, s_group, group->name);
1191 } else {
1192 SIPE_DEBUG_INFO("Cannot find group %s to rename", old_name);
1196 void
1197 sipe_remove_group(PurpleConnection *gc, PurpleGroup *group)
1199 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
1200 struct sipe_group * s_group = sipe_group_find_by_name(sipe_private, group->name);
1201 if (s_group) {
1202 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
1203 gchar *body;
1204 SIPE_DEBUG_INFO("Deleting group %s", group->name);
1205 body = g_strdup_printf(SIPE_SOAP_DEL_GROUP, s_group->id, sip->contacts_delta++);
1206 send_soap_request(sipe_private, body);
1207 g_free(body);
1209 sip->groups = g_slist_remove(sip->groups, s_group);
1210 g_free(s_group->name);
1211 g_free(s_group);
1212 } else {
1213 SIPE_DEBUG_INFO("Cannot find group %s to delete", group->name);
1218 * A callback for g_hash_table_foreach
1220 static void
1221 sipe_buddy_subscribe_cb(char *buddy_name,
1222 SIPE_UNUSED_PARAMETER struct sipe_buddy *buddy,
1223 struct sipe_core_private *sipe_private)
1225 gchar *action_name = sipe_utils_presence_key(buddy_name);
1226 /* g_hash_table_size() can never return 0, otherwise this function wouldn't be called :-) */
1227 guint time_range = (g_hash_table_size(sipe_private->buddies) * 1000) / 25; /* time interval for 25 requests per sec. In msec. */
1228 guint timeout = ((guint) rand()) / (RAND_MAX / time_range) + 1; /* random period within the range but never 0! */
1230 sipe_schedule_mseconds(sipe_private,
1231 action_name,
1232 g_strdup(buddy_name),
1233 timeout,
1234 sipe_subscribe_presence_single,
1235 g_free);
1236 g_free(action_name);
1240 * Removes entries from purple buddy list
1241 * that does not correspond ones in the roaming contact list.
1243 static void sipe_cleanup_local_blist(struct sipe_core_private *sipe_private) {
1244 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
1245 GSList *buddies = purple_find_buddies(sip->account, NULL);
1246 GSList *entry = buddies;
1247 struct sipe_buddy *buddy;
1248 PurpleBuddy *b;
1249 PurpleGroup *g;
1251 SIPE_DEBUG_INFO("sipe_cleanup_local_blist: overall %d Purple buddies (including clones)", g_slist_length(buddies));
1252 SIPE_DEBUG_INFO("sipe_cleanup_local_blist: %d sipe buddies (unique)", g_hash_table_size(sipe_private->buddies));
1253 while (entry) {
1254 b = entry->data;
1255 g = purple_buddy_get_group(b);
1256 buddy = g_hash_table_lookup(sipe_private->buddies, b->name);
1257 if(buddy) {
1258 gboolean in_sipe_groups = FALSE;
1259 GSList *entry2 = buddy->groups;
1260 while (entry2) {
1261 struct sipe_group *group = entry2->data;
1262 if (sipe_strequal(group->name, g->name)) {
1263 in_sipe_groups = TRUE;
1264 break;
1266 entry2 = entry2->next;
1268 if(!in_sipe_groups) {
1269 SIPE_DEBUG_INFO("*** REMOVING %s from Purple group: %s as not having this group in roaming list", b->name, g->name);
1270 purple_blist_remove_buddy(b);
1272 } else {
1273 SIPE_DEBUG_INFO("*** REMOVING %s from Purple group: %s as this buddy not in roaming list", b->name, g->name);
1274 purple_blist_remove_buddy(b);
1276 entry = entry->next;
1278 g_slist_free(buddies);
1281 static int
1282 sipe_find_access_level(struct sipe_core_private *sipe_private,
1283 const gchar *type,
1284 const gchar *value,
1285 gboolean *is_group_access);
1287 static void
1288 sipe_refresh_blocked_status_cb(char *buddy_name,
1289 SIPE_UNUSED_PARAMETER struct sipe_buddy *buddy,
1290 struct sipe_core_private *sipe_private)
1292 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
1293 int container_id = sipe_find_access_level(sipe_private, "user", buddy_name, NULL);
1294 gboolean blocked = (container_id == 32000);
1295 gboolean blocked_in_blist = !purple_privacy_check(sip->account, buddy_name);
1297 /* SIPE_DEBUG_INFO("sipe_refresh_blocked_status_cb: buddy_name=%s, blocked=%s, blocked_in_blist=%s",
1298 buddy_name, blocked ? "T" : "F", blocked_in_blist ? "T" : "F"); */
1300 if (blocked != blocked_in_blist) {
1301 if (blocked) {
1302 purple_privacy_deny_add(sip->account, buddy_name, TRUE);
1303 } else {
1304 purple_privacy_deny_remove(sip->account, buddy_name, TRUE);
1307 /* stupid workaround to make pidgin re-render screen to reflect our changes */
1309 PurpleBuddy *pbuddy = purple_find_buddy(sip->account, buddy_name);
1310 const PurplePresence *presence = purple_buddy_get_presence(pbuddy);
1311 const PurpleStatus *pstatus = purple_presence_get_active_status(presence);
1313 SIPE_DEBUG_INFO_NOFORMAT("sipe_refresh_blocked_status_cb: forcefully refreshing screen.");
1314 sipe_got_user_status(sipe_private, buddy_name, purple_status_get_id(pstatus));
1320 static void
1321 sipe_refresh_blocked_status(struct sipe_core_private *sipe_private)
1323 g_hash_table_foreach(sipe_private->buddies,
1324 (GHFunc) sipe_refresh_blocked_status_cb,
1325 sipe_private);
1328 static gboolean sipe_process_roaming_contacts(struct sipe_core_private *sipe_private,
1329 struct sipmsg *msg)
1331 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
1332 int len = msg->bodylen;
1334 const gchar *tmp = sipmsg_find_header(msg, "Event");
1335 const sipe_xml *item;
1336 sipe_xml *isc;
1337 const gchar *contacts_delta;
1338 const sipe_xml *group_node;
1339 if (!g_str_has_prefix(tmp, "vnd-microsoft-roaming-contacts")) {
1340 return FALSE;
1343 /* Convert the contact from XML to Purple Buddies */
1344 isc = sipe_xml_parse(msg->body, len);
1345 if (!isc) {
1346 return FALSE;
1349 contacts_delta = sipe_xml_attribute(isc, "deltaNum");
1350 if (contacts_delta) {
1351 sip->contacts_delta = (int)g_ascii_strtod(contacts_delta, NULL);
1354 if (sipe_strequal(sipe_xml_name(isc), "contactList")) {
1356 /* Parse groups */
1357 for (group_node = sipe_xml_child(isc, "group"); group_node; group_node = sipe_xml_twin(group_node)) {
1358 struct sipe_group * group = g_new0(struct sipe_group, 1);
1359 const char *name = sipe_xml_attribute(group_node, "name");
1361 if (g_str_has_prefix(name, "~")) {
1362 name = _("Other Contacts");
1364 group->name = g_strdup(name);
1365 group->id = (int)g_ascii_strtod(sipe_xml_attribute(group_node, "id"), NULL);
1367 sipe_group_add(sipe_private, group);
1370 // Make sure we have at least one group
1371 if (g_slist_length(sip->groups) == 0) {
1372 struct sipe_group * group = g_new0(struct sipe_group, 1);
1373 PurpleGroup *purple_group;
1374 group->name = g_strdup(_("Other Contacts"));
1375 group->id = 1;
1376 purple_group = purple_group_new(group->name);
1377 purple_blist_add_group(purple_group, NULL);
1378 sip->groups = g_slist_append(sip->groups, group);
1381 /* Parse contacts */
1382 for (item = sipe_xml_child(isc, "contact"); item; item = sipe_xml_twin(item)) {
1383 const gchar *uri = sipe_xml_attribute(item, "uri");
1384 const gchar *name = sipe_xml_attribute(item, "name");
1385 gchar *buddy_name;
1386 struct sipe_buddy *buddy = NULL;
1387 gchar *tmp;
1388 gchar **item_groups;
1389 int i = 0;
1391 /* Buddy name must be lower case as we use purple_normalize_nocase() to compare */
1392 tmp = sip_uri_from_name(uri);
1393 buddy_name = g_ascii_strdown(tmp, -1);
1394 g_free(tmp);
1396 /* assign to group Other Contacts if nothing else received */
1397 tmp = g_strdup(sipe_xml_attribute(item, "groups"));
1398 if(is_empty(tmp)) {
1399 struct sipe_group *group = sipe_group_find_by_name(sipe_private, _("Other Contacts"));
1400 g_free(tmp);
1401 tmp = group ? g_strdup_printf("%d", group->id) : g_strdup("1");
1403 item_groups = g_strsplit(tmp, " ", 0);
1404 g_free(tmp);
1406 while (item_groups[i]) {
1407 struct sipe_group *group = sipe_group_find_by_id(sipe_private, g_ascii_strtod(item_groups[i], NULL));
1409 // If couldn't find the right group for this contact, just put them in the first group we have
1410 if (group == NULL && g_slist_length(sip->groups) > 0) {
1411 group = sip->groups->data;
1414 if (group != NULL) {
1415 PurpleBuddy *b = purple_find_buddy_in_group(sip->account, buddy_name, group->purple_group);
1416 if (!b){
1417 b = purple_buddy_new(sip->account, buddy_name, uri);
1418 purple_blist_add_buddy(b, NULL, group->purple_group, NULL);
1420 SIPE_DEBUG_INFO("Created new buddy %s with alias %s", buddy_name, uri);
1423 if (sipe_strcase_equal(uri, purple_buddy_get_alias(b))) {
1424 if (name != NULL && strlen(name) != 0) {
1425 purple_blist_alias_buddy(b, name);
1427 SIPE_DEBUG_INFO("Replaced buddy %s alias with %s", buddy_name, name);
1431 if (!buddy) {
1432 buddy = g_new0(struct sipe_buddy, 1);
1433 buddy->name = g_strdup(b->name);
1434 g_hash_table_insert(sipe_private->buddies, buddy->name, buddy);
1437 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
1439 SIPE_DEBUG_INFO("Added buddy %s to group %s", b->name, group->name);
1440 } else {
1441 SIPE_DEBUG_INFO("No group found for contact %s! Unable to add to buddy list",
1442 name);
1445 i++;
1446 } // while, contact groups
1447 g_strfreev(item_groups);
1448 g_free(buddy_name);
1450 } // for, contacts
1452 sipe_cleanup_local_blist(sipe_private);
1454 /* Add self-contact if not there yet. 2005 systems. */
1455 /* This will resemble subscription to roaming_self in 2007 systems */
1456 if (!SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
1457 gchar *self_uri = sip_uri_self(sipe_private);
1458 struct sipe_buddy *buddy = g_hash_table_lookup(sipe_private->buddies, self_uri);
1460 if (!buddy) {
1461 buddy = g_new0(struct sipe_buddy, 1);
1462 buddy->name = g_strdup(self_uri);
1463 g_hash_table_insert(sipe_private->buddies, buddy->name, buddy);
1465 g_free(self_uri);
1468 sipe_xml_free(isc);
1470 /* subscribe to buddies */
1471 if (!sip->subscribed_buddies) { //do it once, then count Expire field to schedule resubscribe.
1472 if (sip->batched_support) {
1473 sipe_subscribe_presence_batched(sipe_private, NULL);
1474 } else {
1475 g_hash_table_foreach(sipe_private->buddies,
1476 (GHFunc)sipe_buddy_subscribe_cb,
1477 sipe_private);
1479 sip->subscribed_buddies = TRUE;
1481 /* for 2005 systems schedule contacts' status update
1482 * based on their calendar information
1484 if (!SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
1485 sipe_sched_calendar_status_update(sipe_private, time(NULL));
1488 return 0;
1492 * Fires on deregistration event initiated by server.
1493 * [MS-SIPREGE] SIP extension.
1496 // 2007 Example
1498 // Content-Type: text/registration-event
1499 // subscription-state: terminated;expires=0
1500 // ms-diagnostics-public: 4141;reason="User disabled"
1502 // deregistered;event=rejected
1504 static void sipe_process_registration_notify(struct sipe_core_private *sipe_private,
1505 struct sipmsg *msg)
1507 const gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
1508 gchar *event = NULL;
1509 gchar *reason = NULL;
1510 const gchar *diagnostics = sipmsg_find_header(msg, "ms-diagnostics");
1511 gchar *warning;
1513 diagnostics = diagnostics ? diagnostics : sipmsg_find_header(msg, "ms-diagnostics-public");
1514 SIPE_DEBUG_INFO_NOFORMAT("sipe_process_registration_notify: deregistration received.");
1516 if (!g_ascii_strncasecmp(contenttype, "text/registration-event", 23)) {
1517 event = sipmsg_find_part_of_header(msg->body, "event=", NULL, NULL);
1518 //@TODO have proper parameter extraction _by_name_ func, case insesitive.
1519 event = event ? event : sipmsg_find_part_of_header(msg->body, "event=", ";", NULL);
1520 } else {
1521 SIPE_DEBUG_INFO_NOFORMAT("sipe_process_registration_notify: unknown content type, exiting.");
1522 return;
1525 if (diagnostics != NULL) {
1526 reason = sipmsg_find_part_of_header(diagnostics, "reason=\"", "\"", NULL);
1527 } else { // for LCS2005
1528 int error_id = 0;
1529 if (event && sipe_strcase_equal(event, "unregistered")) {
1530 error_id = 4140; // [MS-SIPREGE]
1531 //reason = g_strdup(_("User logged out")); // [MS-OCER]
1532 reason = g_strdup(_("you are already signed in at another location"));
1533 } else if (event && sipe_strcase_equal(event, "rejected")) {
1534 error_id = 4141;
1535 reason = g_strdup(_("user disabled")); // [MS-OCER]
1536 } else if (event && sipe_strcase_equal(event, "deactivated")) {
1537 error_id = 4142;
1538 reason = g_strdup(_("user moved")); // [MS-OCER]
1541 g_free(event);
1542 warning = g_strdup_printf(_("You have been rejected by the server: %s"), reason ? reason : _("no reason given"));
1543 g_free(reason);
1545 sipe_backend_connection_error(SIPE_CORE_PUBLIC,
1546 SIPE_CONNECTION_ERROR_INVALID_USERNAME,
1547 warning);
1548 g_free(warning);
1552 static void sipe_process_provisioning_v2(struct sipe_core_private *sipe_private,
1553 struct sipmsg *msg)
1555 sipe_xml *xn_provision_group_list;
1556 const sipe_xml *node;
1558 xn_provision_group_list = sipe_xml_parse(msg->body, msg->bodylen);
1560 /* provisionGroup */
1561 for (node = sipe_xml_child(xn_provision_group_list, "provisionGroup"); node; node = sipe_xml_twin(node)) {
1562 if (sipe_strequal("ServerConfiguration", sipe_xml_attribute(node, "name"))) {
1563 g_free(sipe_private->focus_factory_uri);
1564 sipe_private->focus_factory_uri = sipe_xml_data(sipe_xml_child(node, "focusFactoryUri"));
1565 SIPE_DEBUG_INFO("sipe_process_provisioning_v2: sipe_private->focus_factory_uri=%s",
1566 sipe_private->focus_factory_uri ? sipe_private->focus_factory_uri : "");
1567 break;
1570 sipe_xml_free(xn_provision_group_list);
1573 /** for 2005 system */
1574 static void
1575 sipe_process_provisioning(struct sipe_core_private *sipe_private,
1576 struct sipmsg *msg)
1578 sipe_xml *xn_provision;
1579 const sipe_xml *node;
1581 xn_provision = sipe_xml_parse(msg->body, msg->bodylen);
1582 if ((node = sipe_xml_child(xn_provision, "user"))) {
1583 SIPE_DEBUG_INFO("sipe_process_provisioning: uri=%s", sipe_xml_attribute(node, "uri"));
1584 if ((node = sipe_xml_child(node, "line"))) {
1585 const gchar *line_uri = sipe_xml_attribute(node, "uri");
1586 const gchar *server = sipe_xml_attribute(node, "server");
1587 SIPE_DEBUG_INFO("sipe_process_provisioning: line_uri=%s server=%s", line_uri, server);
1588 sip_csta_open(sipe_private, line_uri, server);
1591 sipe_xml_free(xn_provision);
1594 static void sipe_process_roaming_acl(struct sipe_core_private *sipe_private,
1595 struct sipmsg *msg)
1597 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
1598 const gchar *contacts_delta;
1599 sipe_xml *xml;
1601 xml = sipe_xml_parse(msg->body, msg->bodylen);
1602 if (!xml)
1604 return;
1607 contacts_delta = sipe_xml_attribute(xml, "deltaNum");
1608 if (contacts_delta)
1610 sip->acl_delta = (int)g_ascii_strtod(contacts_delta, NULL);
1613 sipe_xml_free(xml);
1616 static void
1617 free_container_member(struct sipe_container_member *member)
1619 if (!member) return;
1621 g_free(member->type);
1622 g_free(member->value);
1623 g_free(member);
1626 static void
1627 free_container(struct sipe_container *container)
1629 GSList *entry;
1631 if (!container) return;
1633 entry = container->members;
1634 while (entry) {
1635 void *data = entry->data;
1636 entry = g_slist_remove(entry, data);
1637 free_container_member((struct sipe_container_member *)data);
1639 g_free(container);
1642 static void
1643 sipe_send_container_members_prepare(const guint container_id,
1644 const guint container_version,
1645 const gchar *action,
1646 const gchar *type,
1647 const gchar *value,
1648 char **container_xmls)
1650 gchar *value_str = value ? g_strdup_printf(" value=\"%s\"", value) : g_strdup("");
1651 gchar *body;
1653 if (!container_xmls) return;
1655 body = g_strdup_printf(
1656 "<container id=\"%d\" version=\"%d\"><member action=\"%s\" type=\"%s\"%s/></container>",
1657 container_id,
1658 container_version,
1659 action,
1660 type,
1661 value_str);
1662 g_free(value_str);
1664 if ((*container_xmls) == NULL) {
1665 *container_xmls = body;
1666 } else {
1667 char *tmp = *container_xmls;
1669 *container_xmls = g_strconcat(*container_xmls, body, NULL);
1670 g_free(tmp);
1671 g_free(body);
1675 static void
1676 sipe_send_set_container_members(struct sipe_core_private *sipe_private,
1677 char *container_xmls)
1679 gchar *self;
1680 gchar *contact;
1681 gchar *hdr;
1682 gchar *body;
1684 if (!container_xmls) return;
1686 self = sip_uri_self(sipe_private);
1687 body = g_strdup_printf(
1688 "<setContainerMembers xmlns=\"http://schemas.microsoft.com/2006/09/sip/container-management\">"
1689 "%s"
1690 "</setContainerMembers>",
1691 container_xmls);
1693 contact = get_contact(sipe_private);
1694 hdr = g_strdup_printf("Contact: %s\r\n"
1695 "Content-Type: application/msrtc-setcontainermembers+xml\r\n", contact);
1696 g_free(contact);
1698 sip_transport_service(sipe_private,
1699 self,
1700 hdr,
1701 body,
1702 NULL);
1704 g_free(hdr);
1705 g_free(body);
1706 g_free(self);
1710 * Finds locally stored MS-PRES container member
1712 static struct sipe_container_member *
1713 sipe_find_container_member(struct sipe_container *container,
1714 const gchar *type,
1715 const gchar *value)
1717 struct sipe_container_member *member;
1718 GSList *entry;
1720 if (container == NULL || type == NULL) {
1721 return NULL;
1724 entry = container->members;
1725 while (entry) {
1726 member = entry->data;
1727 if (sipe_strcase_equal(member->type, type) &&
1728 sipe_strcase_equal(member->value, value))
1730 return member;
1732 entry = entry->next;
1734 return NULL;
1738 * Finds locally stored MS-PRES container by id
1740 static struct sipe_container *
1741 sipe_find_container(struct sipe_core_private *sipe_private,
1742 guint id)
1744 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
1745 struct sipe_container *container;
1746 GSList *entry;
1748 if (sip == NULL) {
1749 return NULL;
1752 entry = sip->containers;
1753 while (entry) {
1754 container = entry->data;
1755 if (id == container->id) {
1756 return container;
1758 entry = entry->next;
1760 return NULL;
1763 static GSList *
1764 sipe_get_access_domains(struct sipe_core_private *sipe_private)
1766 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
1767 struct sipe_container *container;
1768 struct sipe_container_member *member;
1769 GSList *entry;
1770 GSList *entry2;
1771 GSList *res = NULL;
1773 if (!sip) return NULL;
1775 entry = sip->containers;
1776 while (entry) {
1777 container = entry->data;
1779 entry2 = container->members;
1780 while (entry2) {
1781 member = entry2->data;
1782 if (sipe_strcase_equal(member->type, "domain"))
1784 res = slist_insert_unique_sorted(res, g_strdup(member->value), (GCompareFunc)g_ascii_strcasecmp);
1786 entry2 = entry2->next;
1788 entry = entry->next;
1790 return res;
1794 * Returns pointer to domain part in provided Email URL
1796 * @param email an email URL. Example: first.last@hq.company.com
1797 * @return pointer to domain part of email URL. Coresponding example: hq.company.com
1799 * Doesn't allocate memory
1801 static const char *
1802 sipe_get_domain(const char *email)
1804 char *tmp;
1806 if (!email) return NULL;
1808 tmp = strstr(email, "@");
1810 if (tmp && ((tmp+1) < (email + strlen(email)))) {
1811 return tmp+1;
1812 } else {
1813 return NULL;
1818 /* @TODO: replace with binary search for faster access? */
1819 /** source: http://support.microsoft.com/kb/897567 */
1820 static const char * const public_domains [] = {
1821 "aol.com", "icq.com", "love.com", "mac.com", "br.live.com",
1822 "hotmail.co.il", "hotmail.co.jp", "hotmail.co.th", "hotmail.co.uk",
1823 "hotmail.com", "hotmail.com.ar", "hotmail.com.tr", "hotmail.es",
1824 "hotmail.de", "hotmail.fr", "hotmail.it", "live.at", "live.be",
1825 "live.ca", "live.cl", "live.cn", "live.co.in", "live.co.kr",
1826 "live.co.uk", "live.co.za", "live.com", "live.com.ar", "live.com.au",
1827 "live.com.co", "live.com.mx", "live.com.my", "live.com.pe",
1828 "live.com.ph", "live.com.pk", "live.com.pt", "live.com.sg",
1829 "live.com.ve", "live.de", "live.dk", "live.fr", "live.hk", "live.ie",
1830 "live.in", "live.it", "live.jp", "live.nl", "live.no", "live.ph",
1831 "live.ru", "live.se", "livemail.com.br", "livemail.tw",
1832 "messengeruser.com", "msn.com", "passport.com", "sympatico.ca",
1833 "tw.live.com", "webtv.net", "windowslive.com", "windowslive.es",
1834 "yahoo.com",
1835 NULL};
1837 static gboolean
1838 sipe_is_public_domain(const char *domain)
1840 int i = 0;
1841 while (public_domains[i]) {
1842 if (sipe_strcase_equal(public_domains[i], domain)) {
1843 return TRUE;
1845 i++;
1847 return FALSE;
1851 * Access Levels
1852 * 32000 - Blocked
1853 * 400 - Personal
1854 * 300 - Team
1855 * 200 - Company
1856 * 100 - Public
1858 static const char *
1859 sipe_get_access_level_name(int container_id)
1861 switch(container_id) {
1862 case 32000: return _("Blocked");
1863 case 400: return _("Personal");
1864 case 300: return _("Team");
1865 case 200: return _("Company");
1866 case 100: return _("Public");
1868 return _("Unknown");
1871 static const guint containers[] = {32000, 400, 300, 200, 100};
1872 #define CONTAINERS_LEN (sizeof(containers) / sizeof(guint))
1875 static int
1876 sipe_find_member_access_level(struct sipe_core_private *sipe_private,
1877 const gchar *type,
1878 const gchar *value)
1880 unsigned int i = 0;
1881 const gchar *value_mod = value;
1883 if (!type) return -1;
1885 if (sipe_strequal("user", type)) {
1886 value_mod = sipe_get_no_sip_uri(value);
1889 for (i = 0; i < CONTAINERS_LEN; i++) {
1890 struct sipe_container_member *member;
1891 struct sipe_container *container = sipe_find_container(sipe_private, containers[i]);
1892 if (!container) continue;
1894 member = sipe_find_container_member(container, type, value_mod);
1895 if (member) return containers[i];
1898 return -1;
1901 /** Member type: user, domain, sameEnterprise, federated, publicCloud; everyone */
1902 static int
1903 sipe_find_access_level(struct sipe_core_private *sipe_private,
1904 const gchar *type,
1905 const gchar *value,
1906 gboolean *is_group_access)
1908 int container_id = -1;
1910 if (sipe_strequal("user", type)) {
1911 const char *domain;
1912 const char *no_sip_uri = sipe_get_no_sip_uri(value);
1914 container_id = sipe_find_member_access_level(sipe_private, "user", no_sip_uri);
1915 if (container_id >= 0) {
1916 if (is_group_access) *is_group_access = FALSE;
1917 return container_id;
1920 domain = sipe_get_domain(no_sip_uri);
1921 container_id = sipe_find_member_access_level(sipe_private, "domain", domain);
1922 if (container_id >= 0) {
1923 if (is_group_access) *is_group_access = TRUE;
1924 return container_id;
1927 container_id = sipe_find_member_access_level(sipe_private, "sameEnterprise", NULL);
1928 if ((container_id >= 0) && sipe_strcase_equal(sipe_private->public.sip_domain, domain)) {
1929 if (is_group_access) *is_group_access = TRUE;
1930 return container_id;
1933 container_id = sipe_find_member_access_level(sipe_private, "publicCloud", NULL);
1934 if ((container_id >= 0) && sipe_is_public_domain(domain)) {
1935 if (is_group_access) *is_group_access = TRUE;
1936 return container_id;
1939 container_id = sipe_find_member_access_level(sipe_private, "everyone", NULL);
1940 if ((container_id >= 0)) {
1941 if (is_group_access) *is_group_access = TRUE;
1942 return container_id;
1944 } else {
1945 container_id = sipe_find_member_access_level(sipe_private, type, value);
1946 if (is_group_access) *is_group_access = FALSE;
1949 return container_id;
1953 * @param container_id a new access level. If -1 then current access level
1954 * is just removed (I.e. the member is removed from all containers).
1955 * @param type a type of member. E.g. "user", "sameEnterprise", etc.
1956 * @param value a value for member. E.g. SIP URI for "user" member type.
1958 static void
1959 sipe_change_access_level(struct sipe_core_private *sipe_private,
1960 const int container_id,
1961 const gchar *type,
1962 const gchar *value)
1964 unsigned int i;
1965 int current_container_id = -1;
1966 char *container_xmls = NULL;
1968 /* for each container: find/delete */
1969 for (i = 0; i < CONTAINERS_LEN; i++) {
1970 struct sipe_container_member *member;
1971 struct sipe_container *container = sipe_find_container(sipe_private, containers[i]);
1973 if (!container) continue;
1975 member = sipe_find_container_member(container, type, value);
1976 if (member) {
1977 current_container_id = containers[i];
1978 /* delete/publish current access level */
1979 if (container_id < 0 || container_id != current_container_id) {
1980 sipe_send_container_members_prepare(current_container_id, container->version, "remove", type, value, &container_xmls);
1981 /* remove member from our cache, to be able to recalculate AL below */
1982 container->members = g_slist_remove(container->members, member);
1983 current_container_id = -1;
1988 /* recalculate AL below */
1989 current_container_id = sipe_find_access_level(sipe_private, type, value, NULL);
1991 /* assign/publish new access level */
1992 if (container_id != current_container_id && container_id >= 0) {
1993 struct sipe_container *container = sipe_find_container(sipe_private, container_id);
1994 guint version = container ? container->version : 0;
1996 sipe_send_container_members_prepare(container_id, version, "add", type, value, &container_xmls);
1999 if (container_xmls) {
2000 sipe_send_set_container_members(sipe_private, container_xmls);
2002 g_free(container_xmls);
2005 static void
2006 free_publication(struct sipe_publication *publication)
2008 g_free(publication->category);
2009 g_free(publication->cal_event_hash);
2010 g_free(publication->note);
2012 g_free(publication->working_hours_xml_str);
2013 g_free(publication->fb_start_str);
2014 g_free(publication->free_busy_base64);
2016 g_free(publication);
2019 /* key is <category><instance><container> */
2020 static gboolean
2021 sipe_is_our_publication(struct sipe_core_private *sipe_private,
2022 const gchar *key)
2024 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
2025 GSList *entry;
2027 /* filling keys for our publications if not yet cached */
2028 if (!sip->our_publication_keys) {
2029 guint device_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_DEVICE);
2030 guint machine_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_MACHINE);
2031 guint user_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_USER);
2032 guint calendar_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_CALENDAR);
2033 guint cal_oof_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_CALENDAR_OOF);
2034 guint cal_data_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_CALENDAR_DATA);
2035 guint note_oof_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_NOTE_OOF);
2037 SIPE_DEBUG_INFO_NOFORMAT("* Our Publication Instances *");
2038 SIPE_DEBUG_INFO("\tDevice : %u\t0x%08X", device_instance, device_instance);
2039 SIPE_DEBUG_INFO("\tMachine State : %u\t0x%08X", machine_instance, machine_instance);
2040 SIPE_DEBUG_INFO("\tUser Stare : %u\t0x%08X", user_instance, user_instance);
2041 SIPE_DEBUG_INFO("\tCalendar State : %u\t0x%08X", calendar_instance, calendar_instance);
2042 SIPE_DEBUG_INFO("\tCalendar OOF State : %u\t0x%08X", cal_oof_instance, cal_oof_instance);
2043 SIPE_DEBUG_INFO("\tCalendar FreeBusy : %u\t0x%08X", cal_data_instance, cal_data_instance);
2044 SIPE_DEBUG_INFO("\tOOF Note : %u\t0x%08X", note_oof_instance, note_oof_instance);
2045 SIPE_DEBUG_INFO("\tNote : %u", 0);
2046 SIPE_DEBUG_INFO("\tCalendar WorkingHours: %u", 0);
2048 /* device */
2049 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2050 g_strdup_printf("<%s><%u><%u>", "device", device_instance, 2));
2052 /* state:machineState */
2053 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2054 g_strdup_printf("<%s><%u><%u>", "state", machine_instance, 2));
2055 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2056 g_strdup_printf("<%s><%u><%u>", "state", machine_instance, 3));
2058 /* state:userState */
2059 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2060 g_strdup_printf("<%s><%u><%u>", "state", user_instance, 2));
2061 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2062 g_strdup_printf("<%s><%u><%u>", "state", user_instance, 3));
2064 /* state:calendarState */
2065 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2066 g_strdup_printf("<%s><%u><%u>", "state", calendar_instance, 2));
2067 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2068 g_strdup_printf("<%s><%u><%u>", "state", calendar_instance, 3));
2070 /* state:calendarState OOF */
2071 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2072 g_strdup_printf("<%s><%u><%u>", "state", cal_oof_instance, 2));
2073 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2074 g_strdup_printf("<%s><%u><%u>", "state", cal_oof_instance, 3));
2076 /* note */
2077 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2078 g_strdup_printf("<%s><%u><%u>", "note", 0, 200));
2079 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2080 g_strdup_printf("<%s><%u><%u>", "note", 0, 300));
2081 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2082 g_strdup_printf("<%s><%u><%u>", "note", 0, 400));
2084 /* note OOF */
2085 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2086 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 200));
2087 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2088 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 300));
2089 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2090 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 400));
2092 /* calendarData:WorkingHours */
2093 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2094 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 1));
2095 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2096 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 100));
2097 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2098 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 200));
2099 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2100 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 300));
2101 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2102 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 400));
2103 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2104 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 32000));
2106 /* calendarData:FreeBusy */
2107 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2108 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 1));
2109 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2110 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 100));
2111 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2112 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 200));
2113 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2114 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 300));
2115 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2116 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 400));
2117 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2118 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 32000));
2120 //SIPE_DEBUG_INFO("sipe_is_our_publication: sip->our_publication_keys length=%d",
2121 // sip->our_publication_keys ? (int) g_slist_length(sip->our_publication_keys) : -1);
2124 //SIPE_DEBUG_INFO("sipe_is_our_publication: key=%s", key);
2126 entry = sip->our_publication_keys;
2127 while (entry) {
2128 //SIPE_DEBUG_INFO(" sipe_is_our_publication: entry->data=%s", entry->data);
2129 if (sipe_strequal(entry->data, key)) {
2130 return TRUE;
2132 entry = entry->next;
2134 return FALSE;
2137 /** Property names to store in blist.xml */
2138 #define ALIAS_PROP "alias"
2139 #define EMAIL_PROP "email"
2140 #define PHONE_PROP "phone"
2141 #define PHONE_DISPLAY_PROP "phone-display"
2142 #define PHONE_MOBILE_PROP "phone-mobile"
2143 #define PHONE_MOBILE_DISPLAY_PROP "phone-mobile-display"
2144 #define PHONE_HOME_PROP "phone-home"
2145 #define PHONE_HOME_DISPLAY_PROP "phone-home-display"
2146 #define PHONE_OTHER_PROP "phone-other"
2147 #define PHONE_OTHER_DISPLAY_PROP "phone-other-display"
2148 #define PHONE_CUSTOM1_PROP "phone-custom1"
2149 #define PHONE_CUSTOM1_DISPLAY_PROP "phone-custom1-display"
2150 #define SITE_PROP "site"
2151 #define COMPANY_PROP "company"
2152 #define DEPARTMENT_PROP "department"
2153 #define TITLE_PROP "title"
2154 #define OFFICE_PROP "office"
2155 /** implies work address */
2156 #define ADDRESS_STREET_PROP "address-street"
2157 #define ADDRESS_CITY_PROP "address-city"
2158 #define ADDRESS_STATE_PROP "address-state"
2159 #define ADDRESS_ZIPCODE_PROP "address-zipcode"
2160 #define ADDRESS_COUNTRYCODE_PROP "address-country-code"
2163 * Tries to figure out user first and last name
2164 * based on Display Name and email properties.
2166 * Allocates memory - must be g_free()'d
2168 * Examples to parse:
2169 * First Last
2170 * First Last - Company Name
2171 * Last, First
2172 * Last, First M.
2173 * Last, First (C)(STP) (Company)
2174 * first.last@company.com (preprocessed as "first last")
2175 * first.last.company.com@reuters.net (preprocessed as "first last company com")
2177 * Unusable examples:
2178 * user@company.com (preprocessed as "user")
2179 * first.m.last@company.com (preprocessed as "first m last")
2180 * user.company.com@reuters.net (preprocessed as "user company com")
2182 static void
2183 sipe_get_first_last_names(struct sipe_core_private *sipe_private,
2184 const char *uri,
2185 char **first_name,
2186 char **last_name)
2188 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
2189 PurpleBuddy *p_buddy;
2190 char *display_name;
2191 const char *email;
2192 const char *first, *last;
2193 char *tmp;
2194 char **parts;
2195 gboolean has_comma = FALSE;
2197 if (!sip || !uri) return;
2199 p_buddy = purple_find_buddy(sip->account, uri);
2201 if (!p_buddy) return;
2203 display_name = g_strdup(purple_buddy_get_alias(p_buddy));
2204 email = purple_blist_node_get_string(&p_buddy->node, EMAIL_PROP);
2206 if (!display_name && !email) return;
2208 /* if no display name, make "first last anything_else" out of email */
2209 if (email && !display_name) {
2210 display_name = g_strndup(email, strstr(email, "@") - email);
2211 display_name = sipe_utils_str_replace((tmp = display_name), ".", " ");
2212 g_free(tmp);
2215 if (display_name) {
2216 has_comma = (strstr(display_name, ",") != NULL);
2217 display_name = sipe_utils_str_replace((tmp = display_name), ", ", " ");
2218 g_free(tmp);
2219 display_name = sipe_utils_str_replace((tmp = display_name), ",", " ");
2220 g_free(tmp);
2223 parts = g_strsplit(display_name, " ", 0);
2225 if (!parts[0] || !parts[1]) {
2226 g_free(display_name);
2227 g_strfreev(parts);
2228 return;
2231 if (has_comma) {
2232 last = parts[0];
2233 first = parts[1];
2234 } else {
2235 first = parts[0];
2236 last = parts[1];
2239 if (first_name) {
2240 *first_name = g_strstrip(g_strdup(first));
2243 if (last_name) {
2244 *last_name = g_strstrip(g_strdup(last));
2247 g_free(display_name);
2248 g_strfreev(parts);
2252 * Update user information
2254 * @param uri buddy SIP URI with 'sip:' prefix whose info we want to change.
2255 * @param property_name
2256 * @param property_value may be modified to strip white space
2258 static void
2259 sipe_update_user_info(struct sipe_core_private *sipe_private,
2260 const char *uri,
2261 const char *property_name,
2262 char *property_value)
2264 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
2265 GSList *buddies, *entry;
2267 if (!property_name || strlen(property_name) == 0) return;
2269 if (property_value)
2270 property_value = g_strstrip(property_value);
2272 entry = buddies = purple_find_buddies(sip->account, uri); /* all buddies in different groups */
2273 while (entry) {
2274 const char *prop_str;
2275 const char *server_alias;
2276 PurpleBuddy *p_buddy = entry->data;
2278 /* for Display Name */
2279 if (sipe_strequal(property_name, ALIAS_PROP)) {
2280 if (property_value && sipe_is_bad_alias(uri, purple_buddy_get_alias(p_buddy))) {
2281 SIPE_DEBUG_INFO("Replacing alias for %s with %s", uri, property_value);
2282 purple_blist_alias_buddy(p_buddy, property_value);
2285 server_alias = purple_buddy_get_server_alias(p_buddy);
2286 if (!is_empty(property_value) &&
2287 (!sipe_strequal(property_value, server_alias) || is_empty(server_alias)) )
2289 purple_blist_server_alias_buddy(p_buddy, property_value);
2292 /* for other properties */
2293 else {
2294 if (!is_empty(property_value)) {
2295 prop_str = purple_blist_node_get_string(&p_buddy->node, property_name);
2296 if (!prop_str || !sipe_strcase_equal(prop_str, property_value)) {
2297 purple_blist_node_set_string(&p_buddy->node, property_name, property_value);
2302 entry = entry->next;
2304 g_slist_free(buddies);
2308 * Update user phone
2309 * Suitable for both 2005 and 2007 systems.
2311 * @param uri buddy SIP URI with 'sip:' prefix whose info we want to change.
2312 * @param phone_type
2313 * @param phone may be modified to strip white space
2314 * @param phone_display_string may be modified to strip white space
2316 static void
2317 sipe_update_user_phone(struct sipe_core_private *sipe_private,
2318 const char *uri,
2319 const gchar *phone_type,
2320 gchar *phone,
2321 gchar *phone_display_string)
2323 const char *phone_node = PHONE_PROP; /* work phone by default */
2324 const char *phone_display_node = PHONE_DISPLAY_PROP; /* work phone by default */
2326 if(!phone || strlen(phone) == 0) return;
2328 if ((sipe_strequal(phone_type, "mobile") || sipe_strequal(phone_type, "cell"))) {
2329 phone_node = PHONE_MOBILE_PROP;
2330 phone_display_node = PHONE_MOBILE_DISPLAY_PROP;
2331 } else if (sipe_strequal(phone_type, "home")) {
2332 phone_node = PHONE_HOME_PROP;
2333 phone_display_node = PHONE_HOME_DISPLAY_PROP;
2334 } else if (sipe_strequal(phone_type, "other")) {
2335 phone_node = PHONE_OTHER_PROP;
2336 phone_display_node = PHONE_OTHER_DISPLAY_PROP;
2337 } else if (sipe_strequal(phone_type, "custom1")) {
2338 phone_node = PHONE_CUSTOM1_PROP;
2339 phone_display_node = PHONE_CUSTOM1_DISPLAY_PROP;
2342 sipe_update_user_info(sipe_private, uri, phone_node, phone);
2343 if (phone_display_string) {
2344 sipe_update_user_info(sipe_private, uri, phone_display_node, phone_display_string);
2348 void
2349 sipe_core_update_calendar(struct sipe_core_public *sipe_public)
2351 SIPE_DEBUG_INFO_NOFORMAT("sipe_core_update_calendar: started.");
2353 /* Do in parallel.
2354 * If failed, the branch will be disabled for subsequent calls.
2355 * Can't rely that user turned the functionality on in account settings.
2357 sipe_ews_update_calendar(SIPE_CORE_PRIVATE);
2358 sipe_domino_update_calendar(SIPE_CORE_PRIVATE);
2360 /* schedule repeat */
2361 sipe_schedule_seconds(SIPE_CORE_PRIVATE,
2362 "<+update-calendar>",
2363 NULL,
2364 UPDATE_CALENDAR_INTERVAL,
2365 (sipe_schedule_action)sipe_core_update_calendar,
2366 NULL);
2368 SIPE_DEBUG_INFO_NOFORMAT("sipe_core_update_calendar: finished.");
2372 * This method motivates Purple's Host (e.g. Pidgin) to update its UI
2373 * by using standard Purple's means of signals and saved statuses.
2375 * Thus all UI elements get updated: Status Button with Note, docklet.
2376 * This is ablolutely important as both our status and note can come
2377 * inbound (roaming) or be updated programmatically (e.g. based on our
2378 * calendar data).
2380 static void
2381 sipe_set_purple_account_status_and_note(const PurpleAccount *account,
2382 const char *status_id,
2383 const char *message,
2384 time_t do_not_publish[])
2386 PurpleStatus *status = purple_account_get_active_status(account);
2387 gboolean changed = TRUE;
2389 if (g_str_equal(status_id, purple_status_get_id(status)) &&
2390 sipe_strequal(message, purple_status_get_attr_string(status, SIPE_STATUS_ATTR_ID_MESSAGE)))
2392 changed = FALSE;
2395 if (purple_savedstatus_is_idleaway()) {
2396 changed = FALSE;
2399 if (changed) {
2400 PurpleSavedStatus *saved_status;
2401 const PurpleStatusType *acct_status_type =
2402 purple_status_type_find_with_id(account->status_types, status_id);
2403 PurpleStatusPrimitive primitive = purple_status_type_get_primitive(acct_status_type);
2404 sipe_activity activity = sipe_get_activity_by_token(status_id);
2406 saved_status = purple_savedstatus_find_transient_by_type_and_message(primitive, message);
2407 if (saved_status) {
2408 purple_savedstatus_set_substatus(saved_status, account, acct_status_type, message);
2411 /* If this type+message is unique then create a new transient saved status
2412 * Ref: gtkstatusbox.c
2414 if (!saved_status) {
2415 GList *tmp;
2416 GList *active_accts = purple_accounts_get_all_active();
2418 saved_status = purple_savedstatus_new(NULL, primitive);
2419 purple_savedstatus_set_message(saved_status, message);
2421 for (tmp = active_accts; tmp != NULL; tmp = tmp->next) {
2422 purple_savedstatus_set_substatus(saved_status,
2423 (PurpleAccount *)tmp->data, acct_status_type, message);
2425 g_list_free(active_accts);
2428 do_not_publish[activity] = time(NULL);
2429 SIPE_DEBUG_INFO("sipe_set_purple_account_status_and_note: do_not_publish[%s]=%d [now]",
2430 status_id, (int)do_not_publish[activity]);
2432 /* Set the status for each account */
2433 purple_savedstatus_activate(saved_status);
2437 struct hash_table_delete_payload {
2438 GHashTable *hash_table;
2439 guint container;
2442 static void
2443 sipe_remove_category_container_publications_cb(const char *name,
2444 struct sipe_publication *publication,
2445 struct hash_table_delete_payload *payload)
2447 if (publication->container == payload->container) {
2448 g_hash_table_remove(payload->hash_table, name);
2451 static void
2452 sipe_remove_category_container_publications(GHashTable *our_publications,
2453 const char *category,
2454 guint container)
2456 struct hash_table_delete_payload payload;
2457 payload.hash_table = g_hash_table_lookup(our_publications, category);
2459 if (!payload.hash_table) return;
2461 payload.container = container;
2462 g_hash_table_foreach(payload.hash_table, (GHFunc)sipe_remove_category_container_publications_cb, &payload);
2465 static void
2466 send_publish_category_initial(struct sipe_core_private *sipe_private);
2469 * When we receive some self (BE) NOTIFY with a new subscriber
2470 * we sends a setSubscribers request to him [SIP-PRES] 4.8
2473 static void sipe_process_roaming_self(struct sipe_core_private *sipe_private,
2474 struct sipmsg *msg)
2476 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
2477 gchar *contact;
2478 gchar *to;
2479 sipe_xml *xml;
2480 const sipe_xml *node;
2481 const sipe_xml *node2;
2482 char *display_name = NULL;
2483 char *uri;
2484 GSList *category_names = NULL;
2485 int aggreg_avail = 0;
2486 gboolean do_update_status = FALSE;
2487 gboolean has_note_cleaned = FALSE;
2489 SIPE_DEBUG_INFO_NOFORMAT("sipe_process_roaming_self");
2491 xml = sipe_xml_parse(msg->body, msg->bodylen);
2492 if (!xml) return;
2494 contact = get_contact(sipe_private);
2495 to = sip_uri_self(sipe_private);
2498 /* categories */
2499 /* set list of categories participating in this XML */
2500 for (node = sipe_xml_child(xml, "categories/category"); node; node = sipe_xml_twin(node)) {
2501 const gchar *name = sipe_xml_attribute(node, "name");
2502 category_names = slist_insert_unique_sorted(category_names, (gchar *)name, (GCompareFunc)strcmp);
2504 SIPE_DEBUG_INFO("sipe_process_roaming_self: category_names length=%d",
2505 category_names ? (int) g_slist_length(category_names) : -1);
2506 /* drop category information */
2507 if (category_names) {
2508 GSList *entry = category_names;
2509 while (entry) {
2510 GHashTable *cat_publications;
2511 const gchar *category = entry->data;
2512 entry = entry->next;
2513 SIPE_DEBUG_INFO("sipe_process_roaming_self: dropping category: %s", category);
2514 cat_publications = g_hash_table_lookup(sip->our_publications, category);
2515 if (cat_publications) {
2516 g_hash_table_remove(sip->our_publications, category);
2517 SIPE_DEBUG_INFO("sipe_process_roaming_self: dropped category: %s", category);
2521 g_slist_free(category_names);
2522 /* filling our categories reflected in roaming data */
2523 for (node = sipe_xml_child(xml, "categories/category"); node; node = sipe_xml_twin(node)) {
2524 const char *tmp;
2525 const gchar *name = sipe_xml_attribute(node, "name");
2526 guint container = sipe_xml_int_attribute(node, "container", -1);
2527 guint instance = sipe_xml_int_attribute(node, "instance", -1);
2528 guint version = sipe_xml_int_attribute(node, "version", 0);
2529 time_t publish_time = (tmp = sipe_xml_attribute(node, "publishTime")) ?
2530 sipe_utils_str_to_time(tmp) : 0;
2531 gchar *key;
2532 GHashTable *cat_publications = g_hash_table_lookup(sip->our_publications, name);
2534 /* Ex. clear note: <category name="note"/> */
2535 if (container == (guint)-1) {
2536 g_free(sip->note);
2537 sip->note = NULL;
2538 do_update_status = TRUE;
2539 continue;
2542 /* Ex. clear note: <category name="note" container="200"/> */
2543 if (instance == (guint)-1) {
2544 if (container == 200) {
2545 g_free(sip->note);
2546 sip->note = NULL;
2547 do_update_status = TRUE;
2549 SIPE_DEBUG_INFO("sipe_process_roaming_self: removing publications for: %s/%u", name, container);
2550 sipe_remove_category_container_publications(
2551 sip->our_publications, name, container);
2552 continue;
2555 /* key is <category><instance><container> */
2556 key = g_strdup_printf("<%s><%u><%u>", name, instance, container);
2557 SIPE_DEBUG_INFO("sipe_process_roaming_self: key=%s version=%d", key, version);
2559 /* capture all userState publication for later clean up if required */
2560 if (sipe_strequal(name, "state") && (container == 2 || container == 3)) {
2561 const sipe_xml *xn_state = sipe_xml_child(node, "state");
2563 if (xn_state && sipe_strequal(sipe_xml_attribute(xn_state, "type"), "userState")) {
2564 struct sipe_publication *publication = g_new0(struct sipe_publication, 1);
2565 publication->category = g_strdup(name);
2566 publication->instance = instance;
2567 publication->container = container;
2568 publication->version = version;
2570 if (!sip->user_state_publications) {
2571 sip->user_state_publications = g_hash_table_new_full(
2572 g_str_hash, g_str_equal,
2573 g_free, (GDestroyNotify)free_publication);
2575 g_hash_table_insert(sip->user_state_publications, g_strdup(key), publication);
2576 SIPE_DEBUG_INFO("sipe_process_roaming_self: added to user_state_publications key=%s version=%d",
2577 key, version);
2581 if (sipe_is_our_publication(sipe_private, key)) {
2582 struct sipe_publication *publication = g_new0(struct sipe_publication, 1);
2584 publication->category = g_strdup(name);
2585 publication->instance = instance;
2586 publication->container = container;
2587 publication->version = version;
2589 /* filling publication->availability */
2590 if (sipe_strequal(name, "state")) {
2591 const sipe_xml *xn_state = sipe_xml_child(node, "state");
2592 const sipe_xml *xn_avail = sipe_xml_child(xn_state, "availability");
2594 if (xn_avail) {
2595 gchar *avail_str = sipe_xml_data(xn_avail);
2596 if (avail_str) {
2597 publication->availability = atoi(avail_str);
2599 g_free(avail_str);
2601 /* for calendarState */
2602 if (xn_state && sipe_strequal(sipe_xml_attribute(xn_state, "type"), "calendarState")) {
2603 const sipe_xml *xn_activity = sipe_xml_child(xn_state, "activity");
2604 struct sipe_cal_event *event = g_new0(struct sipe_cal_event, 1);
2606 event->start_time = sipe_utils_str_to_time(sipe_xml_attribute(xn_state, "startTime"));
2607 if (xn_activity) {
2608 if (sipe_strequal(sipe_xml_attribute(xn_activity, "token"),
2609 sipe_activity_map[SIPE_ACTIVITY_IN_MEETING].token))
2611 event->is_meeting = TRUE;
2614 event->subject = sipe_xml_data(sipe_xml_child(xn_state, "meetingSubject"));
2615 event->location = sipe_xml_data(sipe_xml_child(xn_state, "meetingLocation"));
2617 publication->cal_event_hash = sipe_cal_event_hash(event);
2618 SIPE_DEBUG_INFO("sipe_process_roaming_self: hash=%s",
2619 publication->cal_event_hash);
2620 sipe_cal_event_free(event);
2623 /* filling publication->note */
2624 if (sipe_strequal(name, "note")) {
2625 const sipe_xml *xn_body = sipe_xml_child(node, "note/body");
2627 if (!has_note_cleaned) {
2628 has_note_cleaned = TRUE;
2630 g_free(sip->note);
2631 sip->note = NULL;
2632 sip->note_since = publish_time;
2634 do_update_status = TRUE;
2637 g_free(publication->note);
2638 publication->note = NULL;
2639 if (xn_body) {
2640 char *tmp;
2642 publication->note = g_markup_escape_text((tmp = sipe_xml_data(xn_body)), -1);
2643 g_free(tmp);
2644 if (publish_time >= sip->note_since) {
2645 g_free(sip->note);
2646 sip->note = g_strdup(publication->note);
2647 sip->note_since = publish_time;
2648 sip->is_oof_note = sipe_strequal(sipe_xml_attribute(xn_body, "type"), "OOF");
2650 do_update_status = TRUE;
2655 /* filling publication->fb_start_str, free_busy_base64, working_hours_xml_str */
2656 if (sipe_strequal(name, "calendarData") && (publication->container == 300)) {
2657 const sipe_xml *xn_free_busy = sipe_xml_child(node, "calendarData/freeBusy");
2658 const sipe_xml *xn_working_hours = sipe_xml_child(node, "calendarData/WorkingHours");
2659 if (xn_free_busy) {
2660 publication->fb_start_str = g_strdup(sipe_xml_attribute(xn_free_busy, "startTime"));
2661 publication->free_busy_base64 = sipe_xml_data(xn_free_busy);
2663 if (xn_working_hours) {
2664 publication->working_hours_xml_str = sipe_xml_stringify(xn_working_hours);
2668 if (!cat_publications) {
2669 cat_publications = g_hash_table_new_full(
2670 g_str_hash, g_str_equal,
2671 g_free, (GDestroyNotify)free_publication);
2672 g_hash_table_insert(sip->our_publications, g_strdup(name), cat_publications);
2673 SIPE_DEBUG_INFO("sipe_process_roaming_self: added GHashTable cat=%s", name);
2675 g_hash_table_insert(cat_publications, g_strdup(key), publication);
2676 SIPE_DEBUG_INFO("sipe_process_roaming_self: added key=%s version=%d", key, version);
2678 g_free(key);
2680 /* aggregateState (not an our publication) from 2-nd container */
2681 if (sipe_strequal(name, "state") && container == 2) {
2682 const sipe_xml *xn_state = sipe_xml_child(node, "state");
2684 if (xn_state && sipe_strequal(sipe_xml_attribute(xn_state, "type"), "aggregateState")) {
2685 const sipe_xml *xn_avail = sipe_xml_child(xn_state, "availability");
2687 if (xn_avail) {
2688 gchar *avail_str = sipe_xml_data(xn_avail);
2689 if (avail_str) {
2690 aggreg_avail = atoi(avail_str);
2692 g_free(avail_str);
2695 do_update_status = TRUE;
2699 /* userProperties published by server from AD */
2700 if (!sip->csta && sipe_strequal(name, "userProperties")) {
2701 const sipe_xml *line;
2702 /* line, for Remote Call Control (RCC) */
2703 for (line = sipe_xml_child(node, "userProperties/lines/line"); line; line = sipe_xml_twin(line)) {
2704 const gchar *line_server = sipe_xml_attribute(line, "lineServer");
2705 const gchar *line_type = sipe_xml_attribute(line, "lineType");
2706 gchar *line_uri;
2708 if (!line_server || !(sipe_strequal(line_type, "Rcc") || sipe_strequal(line_type, "Dual"))) continue;
2710 line_uri = sipe_xml_data(line);
2711 if (line_uri) {
2712 SIPE_DEBUG_INFO("sipe_process_roaming_self: line_uri=%s server=%s", line_uri, line_server);
2713 sip_csta_open(sipe_private, line_uri, line_server);
2715 g_free(line_uri);
2717 break;
2721 SIPE_DEBUG_INFO("sipe_process_roaming_self: sip->our_publications size=%d",
2722 sip->our_publications ? (int) g_hash_table_size(sip->our_publications) : -1);
2724 /* containers */
2725 for (node = sipe_xml_child(xml, "containers/container"); node; node = sipe_xml_twin(node)) {
2726 guint id = sipe_xml_int_attribute(node, "id", 0);
2727 struct sipe_container *container = sipe_find_container(sipe_private, id);
2729 if (container) {
2730 sip->containers = g_slist_remove(sip->containers, container);
2731 SIPE_DEBUG_INFO("sipe_process_roaming_self: removed existing container id=%d v%d", container->id, container->version);
2732 free_container(container);
2734 container = g_new0(struct sipe_container, 1);
2735 container->id = id;
2736 container->version = sipe_xml_int_attribute(node, "version", 0);
2737 sip->containers = g_slist_append(sip->containers, container);
2738 SIPE_DEBUG_INFO("sipe_process_roaming_self: added container id=%d v%d", container->id, container->version);
2740 for (node2 = sipe_xml_child(node, "member"); node2; node2 = sipe_xml_twin(node2)) {
2741 struct sipe_container_member *member = g_new0(struct sipe_container_member, 1);
2742 member->type = g_strdup(sipe_xml_attribute(node2, "type"));
2743 member->value = g_strdup(sipe_xml_attribute(node2, "value"));
2744 container->members = g_slist_append(container->members, member);
2745 SIPE_DEBUG_INFO("sipe_process_roaming_self: added container member type=%s value=%s",
2746 member->type, member->value ? member->value : "");
2750 SIPE_DEBUG_INFO("sipe_process_roaming_self: sip->access_level_set=%s", sip->access_level_set ? "TRUE" : "FALSE");
2751 if (!sip->access_level_set && sipe_xml_child(xml, "containers")) {
2752 char *container_xmls = NULL;
2753 int sameEnterpriseAL = sipe_find_access_level(sipe_private, "sameEnterprise", NULL, NULL);
2754 int federatedAL = sipe_find_access_level(sipe_private, "federated", NULL, NULL);
2756 SIPE_DEBUG_INFO("sipe_process_roaming_self: sameEnterpriseAL=%d", sameEnterpriseAL);
2757 SIPE_DEBUG_INFO("sipe_process_roaming_self: federatedAL=%d", federatedAL);
2758 /* initial set-up to let counterparties see your status */
2759 if (sameEnterpriseAL < 0) {
2760 struct sipe_container *container = sipe_find_container(sipe_private, 200);
2761 guint version = container ? container->version : 0;
2762 sipe_send_container_members_prepare(200, version, "add", "sameEnterprise", NULL, &container_xmls);
2764 if (federatedAL < 0) {
2765 struct sipe_container *container = sipe_find_container(sipe_private, 100);
2766 guint version = container ? container->version : 0;
2767 sipe_send_container_members_prepare(100, version, "add", "federated", NULL, &container_xmls);
2769 sip->access_level_set = TRUE;
2771 if (container_xmls) {
2772 sipe_send_set_container_members(sipe_private, container_xmls);
2774 g_free(container_xmls);
2777 /* Refresh contacts' blocked status */
2778 sipe_refresh_blocked_status(sipe_private);
2780 /* subscribers */
2781 for (node = sipe_xml_child(xml, "subscribers/subscriber"); node; node = sipe_xml_twin(node)) {
2782 const char *user;
2783 const char *acknowledged;
2784 gchar *hdr;
2785 gchar *body;
2787 user = sipe_xml_attribute(node, "user"); /* without 'sip:' prefix */
2788 if (!user) continue;
2789 SIPE_DEBUG_INFO("sipe_process_roaming_self: user %s", user);
2790 display_name = g_strdup(sipe_xml_attribute(node, "displayName"));
2791 uri = sip_uri_from_name(user);
2793 sipe_update_user_info(sipe_private, uri, ALIAS_PROP, display_name);
2795 acknowledged= sipe_xml_attribute(node, "acknowledged");
2796 if(sipe_strcase_equal(acknowledged,"false")){
2797 SIPE_DEBUG_INFO("sipe_process_roaming_self: user added you %s", user);
2798 if (!purple_find_buddy(sip->account, uri)) {
2799 purple_account_request_add(sip->account, uri, _("you"), display_name, NULL);
2802 hdr = g_strdup_printf(
2803 "Contact: %s\r\n"
2804 "Content-Type: application/msrtc-presence-setsubscriber+xml\r\n", contact);
2806 body = g_strdup_printf(
2807 "<setSubscribers xmlns=\"http://schemas.microsoft.com/2006/09/sip/presence-subscribers\">"
2808 "<subscriber user=\"%s\" acknowledged=\"true\"/>"
2809 "</setSubscribers>", user);
2811 sip_transport_service(sipe_private,
2813 hdr,
2814 body,
2815 NULL);
2816 g_free(body);
2817 g_free(hdr);
2819 g_free(display_name);
2820 g_free(uri);
2823 g_free(contact);
2824 sipe_xml_free(xml);
2826 /* Publish initial state if not yet.
2827 * Assuming this happens on initial responce to subscription to roaming-self
2828 * so we've already updated our roaming data in full.
2829 * Only for 2007+
2831 if (!sip->initial_state_published) {
2832 send_publish_category_initial(sipe_private);
2833 sip->initial_state_published = TRUE;
2834 /* dalayed run */
2835 sipe_schedule_seconds(sipe_private,
2836 "<+update-calendar>",
2837 NULL,
2838 UPDATE_CALENDAR_DELAY,
2839 (sipe_schedule_action)sipe_core_update_calendar,
2840 NULL);
2841 do_update_status = FALSE;
2842 } else if (aggreg_avail) {
2844 g_free(sip->status);
2845 if (aggreg_avail && aggreg_avail < 18000) { /* not offline */
2846 sip->status = g_strdup(sipe_get_status_by_availability(aggreg_avail, NULL));
2847 } else {
2848 sip->status = g_strdup(SIPE_STATUS_ID_INVISIBLE); /* not not let offline status switch us off */
2852 if (do_update_status) {
2853 SIPE_DEBUG_INFO("sipe_process_roaming_self: switch to '%s' for the account", sip->status);
2854 sipe_set_purple_account_status_and_note(sip->account, sip->status, sip->note, sip->do_not_publish);
2857 g_free(to);
2860 /* IM Session (INVITE and MESSAGE methods) */
2862 /* EndPoints: "alice alisson" <sip:alice@atlanta.local>, <sip:bob@atlanta.local>;epid=ebca82d94d, <sip:carol@atlanta.local> */
2863 static gchar *
2864 get_end_points (struct sipe_core_private *sipe_private,
2865 struct sip_session *session)
2867 gchar *res;
2869 if (session == NULL) {
2870 return NULL;
2873 res = g_strdup_printf("<sip:%s>", sipe_private->username);
2875 SIPE_DIALOG_FOREACH {
2876 gchar *tmp = res;
2877 res = g_strdup_printf("%s, <%s>", res, dialog->with);
2878 g_free(tmp);
2880 if (dialog->theirepid) {
2881 tmp = res;
2882 res = g_strdup_printf("%s;epid=%s", res, dialog->theirepid);
2883 g_free(tmp);
2885 } SIPE_DIALOG_FOREACH_END;
2887 return res;
2890 static gboolean
2891 process_options_response(SIPE_UNUSED_PARAMETER struct sipe_core_private *sipe_private,
2892 struct sipmsg *msg,
2893 SIPE_UNUSED_PARAMETER struct transaction *trans)
2895 gboolean ret = TRUE;
2897 if (msg->response != 200) {
2898 SIPE_DEBUG_INFO("process_options_response: OPTIONS response is %d", msg->response);
2899 return FALSE;
2902 SIPE_DEBUG_INFO("process_options_response: body:\n%s", msg->body ? msg->body : "");
2904 return ret;
2908 * Asks UA/proxy about its capabilities.
2910 static void sipe_options_request(struct sipe_core_private *sipe_private,
2911 const char *who)
2913 gchar *to = sip_uri(who);
2914 gchar *contact = get_contact(sipe_private);
2915 gchar *request = g_strdup_printf(
2916 "Accept: application/sdp\r\n"
2917 "Contact: %s\r\n", contact);
2918 g_free(contact);
2920 sip_transport_request(sipe_private,
2921 "OPTIONS",
2924 request,
2925 NULL,
2926 NULL,
2927 process_options_response);
2929 g_free(to);
2930 g_free(request);
2933 static void
2934 sipe_notify_user(struct sipe_core_private *sipe_private,
2935 struct sip_session *session,
2936 PurpleMessageFlags flags,
2937 const gchar *message)
2939 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
2940 PurpleConversation *conv;
2942 if (!session->backend_session) {
2943 conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_ANY, session->with, sip->account);
2944 } else {
2945 /* TEMPORARY HACK!! */
2946 conv = (PurpleConversation *) session->backend_session;
2948 purple_conversation_write(conv, NULL, message, flags, time(NULL));
2951 void
2952 sipe_present_info(struct sipe_core_private *sipe_private,
2953 struct sip_session *session,
2954 const gchar *message)
2956 sipe_notify_user(sipe_private, session, PURPLE_MESSAGE_SYSTEM, message);
2959 void
2960 sipe_present_err(struct sipe_core_private *sipe_private,
2961 struct sip_session *session,
2962 const gchar *message)
2964 sipe_notify_user(sipe_private, session, PURPLE_MESSAGE_ERROR, message);
2967 void
2968 sipe_present_message_undelivered_err(struct sipe_core_private *sipe_private,
2969 struct sip_session *session,
2970 int sip_error,
2971 int sip_warning,
2972 const gchar *who,
2973 const gchar *message)
2975 char *msg, *msg_tmp, *msg_tmp2;
2976 const char *label;
2978 msg_tmp = message ? sipe_backend_markup_strip_html(message) : NULL;
2979 msg = msg_tmp ? g_strdup_printf("<font color=\"#888888\"></b>%s<b></font>", msg_tmp) : NULL;
2980 g_free(msg_tmp);
2981 /* Service unavailable; Server Internal Error; Server Time-out */
2982 if (sip_error == 606 && sip_warning == 309) { /* Not acceptable all. */ /* Message contents not allowed by policy */
2983 label = _("Your message or invitation was not delivered, possibly because it contains a hyperlink or other content that the system administrator has blocked.");
2984 g_free(msg);
2985 msg = NULL;
2986 } else if (sip_error == 503 || sip_error == 500 || sip_error == 504) {
2987 label = _("This message was not delivered to %s because the service is not available");
2988 } else if (sip_error == 486) { /* Busy Here */
2989 label = _("This message was not delivered to %s because one or more recipients do not want to be disturbed");
2990 } else if (sip_error == 415) { /* Unsupported media type */
2991 label = _("This message was not delivered to %s because one or more recipients don't support this type of message");
2992 } else {
2993 label = _("This message was not delivered to %s because one or more recipients are offline");
2996 msg_tmp = g_strdup_printf( "%s%s\n%s" ,
2997 msg_tmp2 = g_strdup_printf(label, who ? who : ""),
2998 msg ? ":" : "",
2999 msg ? msg : "");
3000 sipe_present_err(sipe_private, session, msg_tmp);
3001 g_free(msg_tmp2);
3002 g_free(msg_tmp);
3003 g_free(msg);
3007 static gboolean
3008 process_message_response(struct sipe_core_private *sipe_private,
3009 struct sipmsg *msg,
3010 SIPE_UNUSED_PARAMETER struct transaction *trans)
3012 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
3013 gboolean ret = TRUE;
3014 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
3015 struct sip_session *session = sipe_session_find_im(sipe_private, with);
3016 struct sip_dialog *dialog;
3017 gchar *cseq;
3018 char *key;
3019 struct queued_message *message;
3021 if (!session) {
3022 SIPE_DEBUG_INFO_NOFORMAT("process_message_response: unable to find IM session");
3023 g_free(with);
3024 return FALSE;
3027 dialog = sipe_dialog_find(session, with);
3028 if (!dialog) {
3029 SIPE_DEBUG_INFO_NOFORMAT("process_message_response: session outgoing dialog is NULL");
3030 g_free(with);
3031 return FALSE;
3034 cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
3035 key = g_strdup_printf("<%s><%d><MESSAGE><%s>", sipmsg_find_header(msg, "Call-ID"), atoi(cseq), with);
3036 g_free(cseq);
3037 message = g_hash_table_lookup(session->unconfirmed_messages, key);
3039 if (msg->response >= 400) {
3040 PurpleBuddy *pbuddy;
3041 const char *alias = with;
3042 const char *warn_hdr = sipmsg_find_header(msg, "Warning");
3043 int warning = -1;
3045 SIPE_DEBUG_INFO_NOFORMAT("process_message_response: MESSAGE response >= 400");
3047 if (warn_hdr) {
3048 gchar **parts = g_strsplit(warn_hdr, " ", 2);
3049 if (parts[0]) {
3050 warning = atoi(parts[0]);
3052 g_strfreev(parts);
3055 /* cancel file transfer as rejected by server */
3056 if (msg->response == 606 && /* Not acceptable all. */
3057 warning == 309 && /* Message contents not allowed by policy */
3058 message && g_str_has_prefix(message->content_type, "text/x-msmsgsinvite"))
3060 GSList *parsed_body = sipe_ft_parse_msg_body(msg->body);
3061 sipe_ft_incoming_cancel(dialog, parsed_body);
3062 sipe_utils_nameval_free(parsed_body);
3065 if ((pbuddy = purple_find_buddy(sip->account, with))) {
3066 alias = purple_buddy_get_alias(pbuddy);
3069 sipe_present_message_undelivered_err(sipe_private, session, msg->response, warning, alias, (message ? message->body : NULL));
3071 /* drop dangling IM sessions: assume that BYE from remote never reached us */
3072 if (msg->response == 408 || /* Request timeout */
3073 msg->response == 480 || /* Temporarily Unavailable */
3074 msg->response == 481) { /* Call/Transaction Does Not Exist */
3075 SIPE_DEBUG_INFO_NOFORMAT("process_message_response: assuming dangling IM session, dropping it.");
3076 sip_transport_bye(sipe_private, dialog);
3079 ret = FALSE;
3080 } else {
3081 const gchar *message_id = sipmsg_find_header(msg, "Message-Id");
3082 if (message_id) {
3083 g_hash_table_insert(session->conf_unconfirmed_messages, g_strdup(message_id), g_strdup(message->body));
3084 SIPE_DEBUG_INFO("process_message_response: added message with id %s to conf_unconfirmed_messages(count=%d)",
3085 message_id, g_hash_table_size(session->conf_unconfirmed_messages));
3088 g_hash_table_remove(session->unconfirmed_messages, key);
3089 SIPE_DEBUG_INFO("process_message_response: removed message %s from unconfirmed_messages(count=%d)",
3090 key, g_hash_table_size(session->unconfirmed_messages));
3093 g_free(key);
3094 g_free(with);
3096 if (ret) sipe_im_process_queue(sipe_private, session);
3097 return ret;
3100 static void sipe_send_message(struct sipe_core_private *sipe_private,
3101 struct sip_dialog *dialog,
3102 const char *msg, const char *content_type)
3104 gchar *hdr;
3105 gchar *tmp;
3106 char *msgtext = NULL;
3107 const gchar *msgr = "";
3108 gchar *tmp2 = NULL;
3110 if (!g_str_has_prefix(content_type, "text/x-msmsgsinvite")) {
3111 char *msgformat;
3112 gchar *msgr_value;
3114 sipe_parse_html(msg, &msgformat, &msgtext);
3115 SIPE_DEBUG_INFO("sipe_send_message: msgformat=%s", msgformat);
3117 msgr_value = sipmsg_get_msgr_string(msgformat);
3118 g_free(msgformat);
3119 if (msgr_value) {
3120 msgr = tmp2 = g_strdup_printf(";msgr=%s", msgr_value);
3121 g_free(msgr_value);
3123 } else {
3124 msgtext = g_strdup(msg);
3127 tmp = get_contact(sipe_private);
3128 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8\r\n");
3129 //hdr = g_strdup("Content-Type: text/rtf\r\n");
3130 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8;msgr=WAAtAE0ATQBTAC....AoADQA\r\nSupported: timer\r\n");
3131 if (content_type == NULL)
3132 content_type = "text/plain";
3134 hdr = g_strdup_printf("Contact: %s\r\nContent-Type: %s; charset=UTF-8%s\r\n", tmp, content_type, msgr);
3135 g_free(tmp);
3136 g_free(tmp2);
3138 sip_transport_request(sipe_private,
3139 "MESSAGE",
3140 dialog->with,
3141 dialog->with,
3142 hdr,
3143 msgtext,
3144 dialog,
3145 process_message_response);
3146 g_free(msgtext);
3147 g_free(hdr);
3151 void
3152 sipe_im_process_queue (struct sipe_core_private *sipe_private,
3153 struct sip_session * session)
3155 GSList *entry2 = session->outgoing_message_queue;
3156 while (entry2) {
3157 struct queued_message *msg = entry2->data;
3159 /* for multiparty chat or conference */
3160 if (session->is_multiparty || session->focus_uri) {
3161 gchar *who = sip_uri_self(sipe_private);
3162 sipe_backend_chat_message(SIPE_CORE_PUBLIC,
3163 session->chat_id,
3164 who,
3165 msg->body);
3166 g_free(who);
3169 SIPE_DIALOG_FOREACH {
3170 char *key;
3171 struct queued_message *message;
3173 if (dialog->outgoing_invite) continue; /* do not send messages as INVITE is not responded. */
3175 message = g_new0(struct queued_message,1);
3176 message->body = g_strdup(msg->body);
3177 if (msg->content_type != NULL)
3178 message->content_type = g_strdup(msg->content_type);
3180 key = g_strdup_printf("<%s><%d><MESSAGE><%s>", dialog->callid, (dialog->cseq) + 1, dialog->with);
3181 g_hash_table_insert(session->unconfirmed_messages, g_strdup(key), message);
3182 SIPE_DEBUG_INFO("sipe_im_process_queue: added message %s to unconfirmed_messages(count=%d)",
3183 key, g_hash_table_size(session->unconfirmed_messages));
3184 g_free(key);
3186 sipe_send_message(sipe_private, dialog, msg->body, msg->content_type);
3187 } SIPE_DIALOG_FOREACH_END;
3189 entry2 = sipe_session_dequeue_message(session);
3193 static void
3194 sipe_refer_notify(struct sipe_core_private *sipe_private,
3195 struct sip_session *session,
3196 const gchar *who,
3197 int status,
3198 const gchar *desc)
3200 gchar *hdr;
3201 gchar *body;
3202 struct sip_dialog *dialog = sipe_dialog_find(session, who);
3204 hdr = g_strdup_printf(
3205 "Event: refer\r\n"
3206 "Subscription-State: %s\r\n"
3207 "Content-Type: message/sipfrag\r\n",
3208 status >= 200 ? "terminated" : "active");
3210 body = g_strdup_printf(
3211 "SIP/2.0 %d %s\r\n",
3212 status, desc);
3214 sip_transport_request(sipe_private,
3215 "NOTIFY",
3216 who,
3217 who,
3218 hdr,
3219 body,
3220 dialog,
3221 NULL);
3223 g_free(hdr);
3224 g_free(body);
3227 static gboolean
3228 process_invite_response(struct sipe_core_private *sipe_private,
3229 struct sipmsg *msg, struct transaction *trans)
3231 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
3232 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
3233 struct sip_session *session;
3234 struct sip_dialog *dialog;
3235 char *cseq;
3236 char *key;
3237 struct queued_message *message;
3238 struct sipmsg *request_msg = trans->msg;
3240 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
3241 gchar *referred_by;
3243 session = sipe_session_find_chat_or_im(sipe_private, callid, with);
3244 if (!session) {
3245 SIPE_DEBUG_INFO_NOFORMAT("process_invite_response: unable to find IM session");
3246 g_free(with);
3247 return FALSE;
3250 dialog = sipe_dialog_find(session, with);
3251 if (!dialog) {
3252 SIPE_DEBUG_INFO_NOFORMAT("process_invite_response: session outgoing dialog is NULL");
3253 g_free(with);
3254 return FALSE;
3257 sipe_dialog_parse(dialog, msg, TRUE);
3259 cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
3260 key = g_strdup_printf("<%s><%d><INVITE>", dialog->callid, atoi(cseq));
3261 g_free(cseq);
3262 message = g_hash_table_lookup(session->unconfirmed_messages, key);
3264 if (msg->response != 200) {
3265 PurpleBuddy *pbuddy;
3266 const char *alias = with;
3267 const char *warn_hdr = sipmsg_find_header(msg, "Warning");
3268 int warning = -1;
3270 SIPE_DEBUG_INFO_NOFORMAT("process_invite_response: INVITE response not 200");
3272 if (warn_hdr) {
3273 gchar **parts = g_strsplit(warn_hdr, " ", 2);
3274 if (parts[0]) {
3275 warning = atoi(parts[0]);
3277 g_strfreev(parts);
3280 /* cancel file transfer as rejected by server */
3281 if (msg->response == 606 && /* Not acceptable all. */
3282 warning == 309 && /* Message contents not allowed by policy */
3283 message && g_str_has_prefix(message->content_type, "text/x-msmsgsinvite"))
3285 GSList *parsed_body = sipe_ft_parse_msg_body(message->body);
3286 sipe_ft_incoming_cancel(dialog, parsed_body);
3287 sipe_utils_nameval_free(parsed_body);
3290 if ((pbuddy = purple_find_buddy(sip->account, with))) {
3291 alias = purple_buddy_get_alias(pbuddy);
3294 if (message) {
3295 sipe_present_message_undelivered_err(sipe_private, session, msg->response, warning, alias, message->body);
3296 } else {
3297 gchar *tmp_msg = g_strdup_printf(_("Failed to invite %s"), alias);
3298 sipe_present_err(sipe_private, session, tmp_msg);
3299 g_free(tmp_msg);
3302 sipe_dialog_remove(session, with);
3304 g_free(key);
3305 g_free(with);
3306 return FALSE;
3309 dialog->cseq = 0;
3310 sip_transport_ack(sipe_private, dialog);
3311 dialog->outgoing_invite = NULL;
3312 dialog->is_established = TRUE;
3314 referred_by = parse_from(sipmsg_find_header(request_msg, "Referred-By"));
3315 if (referred_by) {
3316 sipe_refer_notify(sipe_private, session, referred_by, 200, "OK");
3317 g_free(referred_by);
3320 /* add user to chat if it is a multiparty session */
3321 if (session->is_multiparty) {
3322 sipe_backend_chat_add(session->backend_session,
3323 with,
3324 TRUE);
3327 if(g_slist_find_custom(dialog->supported, "ms-text-format", (GCompareFunc)g_ascii_strcasecmp)) {
3328 SIPE_DEBUG_INFO_NOFORMAT("process_invite_response: remote system accepted message in INVITE");
3329 sipe_session_dequeue_message(session);
3332 sipe_im_process_queue(sipe_private, session);
3334 g_hash_table_remove(session->unconfirmed_messages, key);
3335 SIPE_DEBUG_INFO("process_invite_response: removed message %s from unconfirmed_messages(count=%d)",
3336 key, g_hash_table_size(session->unconfirmed_messages));
3338 g_free(key);
3339 g_free(with);
3340 return TRUE;
3344 void
3345 sipe_invite(struct sipe_core_private *sipe_private,
3346 struct sip_session *session,
3347 const gchar *who,
3348 const gchar *msg_body,
3349 const gchar *msg_content_type,
3350 const gchar *referred_by,
3351 const gboolean is_triggered)
3353 gchar *hdr;
3354 gchar *to;
3355 gchar *contact;
3356 gchar *body;
3357 gchar *self;
3358 char *ms_text_format = NULL;
3359 gchar *roster_manager;
3360 gchar *end_points;
3361 gchar *referred_by_str;
3362 struct sip_dialog *dialog = sipe_dialog_find(session, who);
3364 if (dialog && dialog->is_established) {
3365 SIPE_DEBUG_INFO("session with %s already has a dialog open", who);
3366 return;
3369 if (!dialog) {
3370 dialog = sipe_dialog_add(session);
3371 dialog->callid = session->callid ? g_strdup(session->callid) : gencallid();
3372 dialog->with = g_strdup(who);
3375 if (!(dialog->ourtag)) {
3376 dialog->ourtag = gentag();
3379 to = sip_uri(who);
3381 if (msg_body) {
3382 char *msgtext = NULL;
3383 char *base64_msg;
3384 const gchar *msgr = "";
3385 char *key;
3386 struct queued_message *message;
3387 gchar *tmp = NULL;
3389 if (!g_str_has_prefix(msg_content_type, "text/x-msmsgsinvite")) {
3390 char *msgformat;
3391 gchar *msgr_value;
3393 sipe_parse_html(msg_body, &msgformat, &msgtext);
3394 SIPE_DEBUG_INFO("sipe_invite: msgformat=%s", msgformat);
3396 msgr_value = sipmsg_get_msgr_string(msgformat);
3397 g_free(msgformat);
3398 if (msgr_value) {
3399 msgr = tmp = g_strdup_printf(";msgr=%s", msgr_value);
3400 g_free(msgr_value);
3402 } else {
3403 msgtext = g_strdup(msg_body);
3406 base64_msg = g_base64_encode((guchar*) msgtext, strlen(msgtext));
3407 ms_text_format = g_strdup_printf(SIPE_INVITE_TEXT,
3408 msg_content_type ? msg_content_type : "text/plain",
3409 msgr,
3410 base64_msg);
3411 g_free(msgtext);
3412 g_free(tmp);
3413 g_free(base64_msg);
3415 message = g_new0(struct queued_message,1);
3416 message->body = g_strdup(msg_body);
3417 if (msg_content_type != NULL)
3418 message->content_type = g_strdup(msg_content_type);
3420 key = g_strdup_printf("<%s><%d><INVITE>", dialog->callid, (dialog->cseq) + 1);
3421 g_hash_table_insert(session->unconfirmed_messages, g_strdup(key), message);
3422 SIPE_DEBUG_INFO("sipe_invite: added message %s to unconfirmed_messages(count=%d)",
3423 key, g_hash_table_size(session->unconfirmed_messages));
3424 g_free(key);
3427 contact = get_contact(sipe_private);
3428 end_points = get_end_points(sipe_private, session);
3429 self = sip_uri_self(sipe_private);
3430 roster_manager = g_strdup_printf(
3431 "Roster-Manager: %s\r\n"
3432 "EndPoints: %s\r\n",
3433 self,
3434 end_points);
3435 referred_by_str = referred_by ?
3436 g_strdup_printf(
3437 "Referred-By: %s\r\n",
3438 referred_by)
3439 : g_strdup("");
3440 hdr = g_strdup_printf(
3441 "Supported: ms-sender\r\n"
3442 "%s"
3443 "%s"
3444 "%s"
3445 "%s"
3446 "Contact: %s\r\n%s"
3447 "Content-Type: application/sdp\r\n",
3448 sipe_strcase_equal(session->roster_manager, self) ? roster_manager : "",
3449 referred_by_str,
3450 is_triggered ? "TriggeredInvite: TRUE\r\n" : "",
3451 is_triggered || session->is_multiparty ? "Require: com.microsoft.rtc-multiparty\r\n" : "",
3452 contact,
3453 ms_text_format ? ms_text_format : "");
3454 g_free(ms_text_format);
3455 g_free(self);
3457 body = g_strdup_printf(
3458 "v=0\r\n"
3459 "o=- 0 0 IN IP4 %s\r\n"
3460 "s=session\r\n"
3461 "c=IN IP4 %s\r\n"
3462 "t=0 0\r\n"
3463 "m=%s %d sip null\r\n"
3464 "a=accept-types:" SDP_ACCEPT_TYPES "\r\n",
3465 sipe_backend_network_ip_address(),
3466 sipe_backend_network_ip_address(),
3467 SIPE_CORE_PRIVATE_FLAG_IS(OCS2007) ? "message" : "x-ms-message",
3468 sip_transport_port(sipe_private));
3470 dialog->outgoing_invite = sip_transport_request(sipe_private,
3471 "INVITE",
3474 hdr,
3475 body,
3476 dialog,
3477 process_invite_response);
3479 g_free(to);
3480 g_free(roster_manager);
3481 g_free(end_points);
3482 g_free(referred_by_str);
3483 g_free(body);
3484 g_free(hdr);
3485 g_free(contact);
3488 void
3489 sipe_session_close(struct sipe_core_private *sipe_private,
3490 struct sip_session * session)
3492 if (session && session->focus_uri) {
3493 sipe_conf_immcu_closed(sipe_private, session);
3494 conf_session_close(sipe_private, session);
3497 if (session) {
3498 SIPE_DIALOG_FOREACH {
3499 /* @TODO slow down BYE message sending rate */
3500 /* @see single subscription code */
3501 sip_transport_bye(sipe_private, dialog);
3502 } SIPE_DIALOG_FOREACH_END;
3504 sipe_session_remove(sipe_private, session);
3508 static void
3509 sipe_session_close_all(struct sipe_core_private *sipe_private)
3511 GSList *entry;
3512 while ((entry = sipe_private->sessions) != NULL) {
3513 sipe_session_close(sipe_private, entry->data);
3517 void
3518 sipe_convo_closed(PurpleConnection * gc, const char *who)
3520 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
3522 SIPE_DEBUG_INFO("conversation with %s closed", who);
3523 sipe_session_close(sipe_private,
3524 sipe_session_find_im(sipe_private, who));
3527 void
3528 sipe_chat_leave (PurpleConnection *gc, int id)
3530 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
3531 struct sip_session *session = sipe_session_find_chat_by_id(sipe_private,
3532 id);
3534 sipe_session_close(sipe_private, session);
3537 int sipe_im_send(PurpleConnection *gc, const char *who, const char *what,
3538 SIPE_UNUSED_PARAMETER PurpleMessageFlags flags)
3540 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
3541 struct sip_session *session;
3542 struct sip_dialog *dialog;
3543 gchar *uri = sip_uri(who);
3545 SIPE_DEBUG_INFO("sipe_im_send what='%s'", what);
3547 session = sipe_session_find_or_add_im(sipe_private, uri);
3548 dialog = sipe_dialog_find(session, uri);
3550 // Queue the message
3551 sipe_session_enqueue_message(session, what, NULL);
3553 if (dialog && !dialog->outgoing_invite) {
3554 sipe_im_process_queue(sipe_private, session);
3555 } else if (!dialog || !dialog->outgoing_invite) {
3556 // Need to send the INVITE to get the outgoing dialog setup
3557 sipe_invite(sipe_private, session, uri, what, NULL, NULL, FALSE);
3560 g_free(uri);
3561 return 1;
3564 int sipe_chat_send(PurpleConnection *gc, int id, const char *what,
3565 SIPE_UNUSED_PARAMETER PurpleMessageFlags flags)
3567 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
3568 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
3569 struct sip_session *session;
3571 SIPE_DEBUG_INFO("sipe_chat_send what='%s'", what);
3573 session = sipe_session_find_chat_by_id(sipe_private, id);
3575 // Queue the message
3576 if (session && session->dialogs) {
3577 sipe_session_enqueue_message(session,what,NULL);
3578 sipe_im_process_queue(sipe_private, session);
3579 } else if (sip) {
3580 gchar *chat_name = purple_find_chat(sip->gc, id)->name;
3581 const gchar *proto_chat_id = sipe_chat_find_name(chat_name);
3583 SIPE_DEBUG_INFO("sipe_chat_send: chat_name='%s'", chat_name ? chat_name : "NULL");
3584 SIPE_DEBUG_INFO("sipe_chat_send: proto_chat_id='%s'", proto_chat_id ? proto_chat_id : "NULL");
3586 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
3587 struct sip_session *session = sipe_session_add_chat(sipe_private);
3589 session->is_multiparty = FALSE;
3590 session->focus_uri = g_strdup(proto_chat_id);
3591 sipe_session_enqueue_message(session, what, NULL);
3592 sipe_invite_conf_focus(sipe_private, session);
3596 return 1;
3600 * Returns 2005-style activity and Availability.
3602 * @param status Sipe statis id.
3604 static void
3605 sipe_get_act_avail_by_status_2005(const char *status,
3606 int *activity,
3607 int *availability)
3609 int avail = 300; /* online */
3610 int act = 400; /* Available */
3612 if (sipe_strequal(status, SIPE_STATUS_ID_AWAY)) {
3613 act = 100;
3614 //} else if (sipe_strequal(status, SIPE_STATUS_ID_LUNCH)) {
3615 // act = 150;
3616 } else if (sipe_strequal(status, SIPE_STATUS_ID_BRB)) {
3617 act = 300;
3618 } else if (sipe_strequal(status, SIPE_STATUS_ID_AVAILABLE)) {
3619 act = 400;
3620 //} else if (sipe_strequal(status, SIPE_STATUS_ID_ON_PHONE)) {
3621 // act = 500;
3622 } else if (sipe_strequal(status, SIPE_STATUS_ID_BUSY) ||
3623 sipe_strequal(status, SIPE_STATUS_ID_DND)) {
3624 act = 600;
3625 } else if (sipe_strequal(status, SIPE_STATUS_ID_INVISIBLE) ||
3626 sipe_strequal(status, SIPE_STATUS_ID_OFFLINE)) {
3627 avail = 0; /* offline */
3628 act = 100;
3629 } else {
3630 act = 400; /* Available */
3633 if (activity) *activity = act;
3634 if (availability) *availability = avail;
3638 * [MS-SIP] 2.2.1
3640 * @param activity 2005 aggregated activity. Ex.: 600
3641 * @param availablity 2005 aggregated availablity. Ex.: 300
3643 static const char *
3644 sipe_get_status_by_act_avail_2005(const int activity,
3645 const int availablity,
3646 char **activity_desc)
3648 const char *status_id = NULL;
3649 const char *act = NULL;
3651 if (activity < 150) {
3652 status_id = SIPE_STATUS_ID_AWAY;
3653 } else if (activity < 200) {
3654 //status_id = SIPE_STATUS_ID_LUNCH;
3655 status_id = SIPE_STATUS_ID_AWAY;
3656 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_LUNCH);
3657 } else if (activity < 300) {
3658 //status_id = SIPE_STATUS_ID_IDLE;
3659 status_id = SIPE_STATUS_ID_AWAY;
3660 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_INACTIVE);
3661 } else if (activity < 400) {
3662 status_id = SIPE_STATUS_ID_BRB;
3663 } else if (activity < 500) {
3664 status_id = SIPE_STATUS_ID_AVAILABLE;
3665 } else if (activity < 600) {
3666 //status_id = SIPE_STATUS_ID_ON_PHONE;
3667 status_id = SIPE_STATUS_ID_BUSY;
3668 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_ON_PHONE);
3669 } else if (activity < 700) {
3670 status_id = SIPE_STATUS_ID_BUSY;
3671 } else if (activity < 800) {
3672 status_id = SIPE_STATUS_ID_AWAY;
3673 } else {
3674 status_id = SIPE_STATUS_ID_AVAILABLE;
3677 if (availablity < 100)
3678 status_id = SIPE_STATUS_ID_OFFLINE;
3680 if (activity_desc && act) {
3681 g_free(*activity_desc);
3682 *activity_desc = g_strdup(act);
3685 return status_id;
3689 * [MS-PRES] Table 3: Conversion of legacyInterop elements and attributes to MSRTC elements and attributes.
3691 static const char*
3692 sipe_get_status_by_availability(int avail,
3693 char** activity_desc)
3695 const char *status;
3696 const char *act = NULL;
3698 if (avail < 3000) {
3699 status = SIPE_STATUS_ID_OFFLINE;
3700 } else if (avail < 4500) {
3701 status = SIPE_STATUS_ID_AVAILABLE;
3702 } else if (avail < 6000) {
3703 //status = SIPE_STATUS_ID_IDLE;
3704 status = SIPE_STATUS_ID_AVAILABLE;
3705 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_INACTIVE);
3706 } else if (avail < 7500) {
3707 status = SIPE_STATUS_ID_BUSY;
3708 } else if (avail < 9000) {
3709 //status = SIPE_STATUS_ID_BUSYIDLE;
3710 status = SIPE_STATUS_ID_BUSY;
3711 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_BUSYIDLE);
3712 } else if (avail < 12000) {
3713 status = SIPE_STATUS_ID_DND;
3714 } else if (avail < 15000) {
3715 status = SIPE_STATUS_ID_BRB;
3716 } else if (avail < 18000) {
3717 status = SIPE_STATUS_ID_AWAY;
3718 } else {
3719 status = SIPE_STATUS_ID_OFFLINE;
3722 if (activity_desc && act) {
3723 g_free(*activity_desc);
3724 *activity_desc = g_strdup(act);
3727 return status;
3731 * Returns 2007-style availability value
3733 * @param sipe_status_id (in)
3734 * @param activity_token (out) Must be g_free()'d after use if consumed.
3736 static int
3737 sipe_get_availability_by_status(const char* sipe_status_id, char** activity_token)
3739 int availability;
3740 sipe_activity activity = SIPE_ACTIVITY_UNSET;
3742 if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_AWAY)) {
3743 availability = 15500;
3744 if (!activity_token || !(*activity_token)) {
3745 activity = SIPE_ACTIVITY_AWAY;
3747 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_BRB)) {
3748 availability = 12500;
3749 activity = SIPE_ACTIVITY_BRB;
3750 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_DND)) {
3751 availability = 9500;
3752 activity = SIPE_ACTIVITY_DND;
3753 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_BUSY)) {
3754 availability = 6500;
3755 if (!activity_token || !(*activity_token)) {
3756 activity = SIPE_ACTIVITY_BUSY;
3758 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_AVAILABLE)) {
3759 availability = 3500;
3760 activity = SIPE_ACTIVITY_ONLINE;
3761 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_UNKNOWN)) {
3762 availability = 0;
3763 } else {
3764 // Offline or invisible
3765 availability = 18500;
3766 activity = SIPE_ACTIVITY_OFFLINE;
3769 if (activity_token) {
3770 *activity_token = g_strdup(sipe_activity_map[activity].token);
3772 return availability;
3775 static void process_incoming_notify_rlmi(struct sipe_core_private *sipe_private,
3776 const gchar *data, unsigned len)
3778 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
3779 const char *uri;
3780 sipe_xml *xn_categories;
3781 const sipe_xml *xn_category;
3782 const char *status = NULL;
3783 gboolean do_update_status = FALSE;
3784 gboolean has_note_cleaned = FALSE;
3785 gboolean has_free_busy_cleaned = FALSE;
3787 xn_categories = sipe_xml_parse(data, len);
3788 uri = sipe_xml_attribute(xn_categories, "uri"); /* with 'sip:' prefix */
3790 for (xn_category = sipe_xml_child(xn_categories, "category");
3791 xn_category ;
3792 xn_category = sipe_xml_twin(xn_category) )
3794 const sipe_xml *xn_node;
3795 const char *tmp;
3796 const char *attrVar = sipe_xml_attribute(xn_category, "name");
3797 time_t publish_time = (tmp = sipe_xml_attribute(xn_category, "publishTime")) ?
3798 sipe_utils_str_to_time(tmp) : 0;
3800 /* contactCard */
3801 if (sipe_strequal(attrVar, "contactCard"))
3803 const sipe_xml *card = sipe_xml_child(xn_category, "contactCard");
3805 if (card) {
3806 const sipe_xml *node;
3807 /* identity - Display Name and email */
3808 node = sipe_xml_child(card, "identity");
3809 if (node) {
3810 char* display_name = sipe_xml_data(
3811 sipe_xml_child(node, "name/displayName"));
3812 char* email = sipe_xml_data(
3813 sipe_xml_child(node, "email"));
3815 sipe_update_user_info(sipe_private, uri, ALIAS_PROP, display_name);
3816 sipe_update_user_info(sipe_private, uri, EMAIL_PROP, email);
3818 g_free(display_name);
3819 g_free(email);
3821 /* company */
3822 node = sipe_xml_child(card, "company");
3823 if (node) {
3824 char* company = sipe_xml_data(node);
3825 sipe_update_user_info(sipe_private, uri, COMPANY_PROP, company);
3826 g_free(company);
3828 /* department */
3829 node = sipe_xml_child(card, "department");
3830 if (node) {
3831 char* department = sipe_xml_data(node);
3832 sipe_update_user_info(sipe_private, uri, DEPARTMENT_PROP, department);
3833 g_free(department);
3835 /* title */
3836 node = sipe_xml_child(card, "title");
3837 if (node) {
3838 char* title = sipe_xml_data(node);
3839 sipe_update_user_info(sipe_private, uri, TITLE_PROP, title);
3840 g_free(title);
3842 /* office */
3843 node = sipe_xml_child(card, "office");
3844 if (node) {
3845 char* office = sipe_xml_data(node);
3846 sipe_update_user_info(sipe_private, uri, OFFICE_PROP, office);
3847 g_free(office);
3849 /* site (url) */
3850 node = sipe_xml_child(card, "url");
3851 if (node) {
3852 char* site = sipe_xml_data(node);
3853 sipe_update_user_info(sipe_private, uri, SITE_PROP, site);
3854 g_free(site);
3856 /* phone */
3857 for (node = sipe_xml_child(card, "phone");
3858 node;
3859 node = sipe_xml_twin(node))
3861 const char *phone_type = sipe_xml_attribute(node, "type");
3862 char* phone = sipe_xml_data(sipe_xml_child(node, "uri"));
3863 char* phone_display_string = sipe_xml_data(sipe_xml_child(node, "displayString"));
3865 sipe_update_user_phone(sipe_private, uri, phone_type, phone, phone_display_string);
3867 g_free(phone);
3868 g_free(phone_display_string);
3870 /* address */
3871 for (node = sipe_xml_child(card, "address");
3872 node;
3873 node = sipe_xml_twin(node))
3875 if (sipe_strequal(sipe_xml_attribute(node, "type"), "work")) {
3876 char* street = sipe_xml_data(sipe_xml_child(node, "street"));
3877 char* city = sipe_xml_data(sipe_xml_child(node, "city"));
3878 char* state = sipe_xml_data(sipe_xml_child(node, "state"));
3879 char* zipcode = sipe_xml_data(sipe_xml_child(node, "zipcode"));
3880 char* country_code = sipe_xml_data(sipe_xml_child(node, "countryCode"));
3882 sipe_update_user_info(sipe_private, uri, ADDRESS_STREET_PROP, street);
3883 sipe_update_user_info(sipe_private, uri, ADDRESS_CITY_PROP, city);
3884 sipe_update_user_info(sipe_private, uri, ADDRESS_STATE_PROP, state);
3885 sipe_update_user_info(sipe_private, uri, ADDRESS_ZIPCODE_PROP, zipcode);
3886 sipe_update_user_info(sipe_private, uri, ADDRESS_COUNTRYCODE_PROP, country_code);
3888 g_free(street);
3889 g_free(city);
3890 g_free(state);
3891 g_free(zipcode);
3892 g_free(country_code);
3894 break;
3899 /* note */
3900 else if (sipe_strequal(attrVar, "note"))
3902 if (uri) {
3903 struct sipe_buddy *sbuddy = g_hash_table_lookup(sipe_private->buddies, uri);
3905 if (!has_note_cleaned) {
3906 has_note_cleaned = TRUE;
3908 g_free(sbuddy->note);
3909 sbuddy->note = NULL;
3910 sbuddy->is_oof_note = FALSE;
3911 sbuddy->note_since = publish_time;
3913 do_update_status = TRUE;
3915 if (sbuddy && (publish_time >= sbuddy->note_since)) {
3916 /* clean up in case no 'note' element is supplied
3917 * which indicate note removal in client
3919 g_free(sbuddy->note);
3920 sbuddy->note = NULL;
3921 sbuddy->is_oof_note = FALSE;
3922 sbuddy->note_since = publish_time;
3924 xn_node = sipe_xml_child(xn_category, "note/body");
3925 if (xn_node) {
3926 char *tmp;
3927 sbuddy->note = g_markup_escape_text((tmp = sipe_xml_data(xn_node)), -1);
3928 g_free(tmp);
3929 sbuddy->is_oof_note = sipe_strequal(sipe_xml_attribute(xn_node, "type"), "OOF");
3930 sbuddy->note_since = publish_time;
3932 SIPE_DEBUG_INFO("process_incoming_notify_rlmi: uri(%s), note(%s)",
3933 uri, sbuddy->note ? sbuddy->note : "");
3935 /* to trigger UI refresh in case no status info is supplied in this update */
3936 do_update_status = TRUE;
3940 /* state */
3941 else if(sipe_strequal(attrVar, "state"))
3943 char *tmp;
3944 int availability;
3945 const sipe_xml *xn_availability;
3946 const sipe_xml *xn_activity;
3947 const sipe_xml *xn_meeting_subject;
3948 const sipe_xml *xn_meeting_location;
3949 struct sipe_buddy *sbuddy = uri ? g_hash_table_lookup(sipe_private->buddies, uri) : NULL;
3951 xn_node = sipe_xml_child(xn_category, "state");
3952 if (!xn_node) continue;
3953 xn_availability = sipe_xml_child(xn_node, "availability");
3954 if (!xn_availability) continue;
3955 xn_activity = sipe_xml_child(xn_node, "activity");
3956 xn_meeting_subject = sipe_xml_child(xn_node, "meetingSubject");
3957 xn_meeting_location = sipe_xml_child(xn_node, "meetingLocation");
3959 tmp = sipe_xml_data(xn_availability);
3960 availability = atoi(tmp);
3961 g_free(tmp);
3963 /* activity, meeting_subject, meeting_location */
3964 if (sbuddy) {
3965 char *tmp = NULL;
3967 /* activity */
3968 g_free(sbuddy->activity);
3969 sbuddy->activity = NULL;
3970 if (xn_activity) {
3971 const char *token = sipe_xml_attribute(xn_activity, "token");
3972 const sipe_xml *xn_custom = sipe_xml_child(xn_activity, "custom");
3974 /* from token */
3975 if (!is_empty(token)) {
3976 sbuddy->activity = g_strdup(sipe_get_activity_desc_by_token(token));
3978 /* from custom element */
3979 if (xn_custom) {
3980 char *custom = sipe_xml_data(xn_custom);
3982 if (!is_empty(custom)) {
3983 sbuddy->activity = custom;
3984 custom = NULL;
3986 g_free(custom);
3989 /* meeting_subject */
3990 g_free(sbuddy->meeting_subject);
3991 sbuddy->meeting_subject = NULL;
3992 if (xn_meeting_subject) {
3993 char *meeting_subject = sipe_xml_data(xn_meeting_subject);
3995 if (!is_empty(meeting_subject)) {
3996 sbuddy->meeting_subject = meeting_subject;
3997 meeting_subject = NULL;
3999 g_free(meeting_subject);
4001 /* meeting_location */
4002 g_free(sbuddy->meeting_location);
4003 sbuddy->meeting_location = NULL;
4004 if (xn_meeting_location) {
4005 char *meeting_location = sipe_xml_data(xn_meeting_location);
4007 if (!is_empty(meeting_location)) {
4008 sbuddy->meeting_location = meeting_location;
4009 meeting_location = NULL;
4011 g_free(meeting_location);
4014 status = sipe_get_status_by_availability(availability, &tmp);
4015 if (sbuddy->activity && tmp) {
4016 char *tmp2 = sbuddy->activity;
4018 sbuddy->activity = g_strdup_printf("%s, %s", sbuddy->activity, tmp);
4019 g_free(tmp);
4020 g_free(tmp2);
4021 } else if (tmp) {
4022 sbuddy->activity = tmp;
4026 do_update_status = TRUE;
4028 /* calendarData */
4029 else if(sipe_strequal(attrVar, "calendarData"))
4031 struct sipe_buddy *sbuddy = uri ? g_hash_table_lookup(sipe_private->buddies, uri) : NULL;
4032 const sipe_xml *xn_free_busy = sipe_xml_child(xn_category, "calendarData/freeBusy");
4033 const sipe_xml *xn_working_hours = sipe_xml_child(xn_category, "calendarData/WorkingHours");
4035 if (sbuddy && xn_free_busy) {
4036 if (!has_free_busy_cleaned) {
4037 has_free_busy_cleaned = TRUE;
4039 g_free(sbuddy->cal_start_time);
4040 sbuddy->cal_start_time = NULL;
4042 g_free(sbuddy->cal_free_busy_base64);
4043 sbuddy->cal_free_busy_base64 = NULL;
4045 g_free(sbuddy->cal_free_busy);
4046 sbuddy->cal_free_busy = NULL;
4048 sbuddy->cal_free_busy_published = publish_time;
4051 if (publish_time >= sbuddy->cal_free_busy_published) {
4052 g_free(sbuddy->cal_start_time);
4053 sbuddy->cal_start_time = g_strdup(sipe_xml_attribute(xn_free_busy, "startTime"));
4055 sbuddy->cal_granularity = sipe_strcase_equal(sipe_xml_attribute(xn_free_busy, "granularity"), "PT15M") ?
4056 15 : 0;
4058 g_free(sbuddy->cal_free_busy_base64);
4059 sbuddy->cal_free_busy_base64 = sipe_xml_data(xn_free_busy);
4061 g_free(sbuddy->cal_free_busy);
4062 sbuddy->cal_free_busy = NULL;
4064 sbuddy->cal_free_busy_published = publish_time;
4066 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);
4070 if (sbuddy && xn_working_hours) {
4071 sipe_cal_parse_working_hours(xn_working_hours, sbuddy);
4076 if (do_update_status) {
4077 if (!status) { /* no status category in this update, using contact's current status */
4078 PurpleBuddy *pbuddy = purple_find_buddy((PurpleAccount *)sip->account, uri);
4079 const PurplePresence *presence = purple_buddy_get_presence(pbuddy);
4080 const PurpleStatus *pstatus = purple_presence_get_active_status(presence);
4081 status = purple_status_get_id(pstatus);
4084 SIPE_DEBUG_INFO("process_incoming_notify_rlmi: %s", status);
4085 sipe_got_user_status(sipe_private, uri, status);
4088 sipe_xml_free(xn_categories);
4091 static void sipe_subscribe_poolfqdn_resource_uri(const char *host,
4092 GSList *server,
4093 struct sipe_core_private *sipe_private)
4095 struct presence_batched_routed *payload = g_malloc(sizeof(struct presence_batched_routed));
4096 SIPE_DEBUG_INFO("process_incoming_notify_rlmi_resub: pool(%s)", host);
4097 payload->host = g_strdup(host);
4098 payload->buddies = server;
4099 sipe_subscribe_presence_batched_routed(sipe_private,
4100 payload);
4101 sipe_subscribe_presence_batched_routed_free(payload);
4104 static void process_incoming_notify_rlmi_resub(struct sipe_core_private *sipe_private,
4105 const gchar *data, unsigned len)
4107 sipe_xml *xn_list;
4108 const sipe_xml *xn_resource;
4109 GHashTable *servers = g_hash_table_new_full(g_str_hash, g_str_equal,
4110 g_free, NULL);
4111 GSList *server;
4112 gchar *host;
4114 xn_list = sipe_xml_parse(data, len);
4116 for (xn_resource = sipe_xml_child(xn_list, "resource");
4117 xn_resource;
4118 xn_resource = sipe_xml_twin(xn_resource) )
4120 const char *uri, *state;
4121 const sipe_xml *xn_instance;
4123 xn_instance = sipe_xml_child(xn_resource, "instance");
4124 if (!xn_instance) continue;
4126 uri = sipe_xml_attribute(xn_resource, "uri");
4127 state = sipe_xml_attribute(xn_instance, "state");
4128 SIPE_DEBUG_INFO("process_incoming_notify_rlmi_resub: uri(%s),state(%s)", uri, state);
4130 if (strstr(state, "resubscribe")) {
4131 const char *poolFqdn = sipe_xml_attribute(xn_instance, "poolFqdn");
4133 if (poolFqdn) { //[MS-PRES] Section 3.4.5.1.3 Processing Details
4134 gchar *user = g_strdup(uri);
4135 host = g_strdup(poolFqdn);
4136 server = g_hash_table_lookup(servers, host);
4137 server = g_slist_append(server, user);
4138 g_hash_table_insert(servers, host, server);
4139 } else {
4140 sipe_subscribe_presence_single(sipe_private,
4141 (void *) uri);
4146 /* Send out any deferred poolFqdn subscriptions */
4147 g_hash_table_foreach(servers, (GHFunc) sipe_subscribe_poolfqdn_resource_uri, sipe_private);
4148 g_hash_table_destroy(servers);
4150 sipe_xml_free(xn_list);
4153 static void process_incoming_notify_pidf(struct sipe_core_private *sipe_private,
4154 const gchar *data, unsigned len)
4156 gchar *uri;
4157 gchar *getbasic;
4158 gchar *activity = NULL;
4159 sipe_xml *pidf;
4160 const sipe_xml *basicstatus = NULL, *tuple, *status;
4161 gboolean isonline = FALSE;
4162 const sipe_xml *display_name_node;
4164 pidf = sipe_xml_parse(data, len);
4165 if (!pidf) {
4166 SIPE_DEBUG_INFO("process_incoming_notify_pidf: no parseable pidf:%s", data);
4167 return;
4170 if ((tuple = sipe_xml_child(pidf, "tuple")))
4172 if ((status = sipe_xml_child(tuple, "status"))) {
4173 basicstatus = sipe_xml_child(status, "basic");
4177 if (!basicstatus) {
4178 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_notify_pidf: no basic found");
4179 sipe_xml_free(pidf);
4180 return;
4183 getbasic = sipe_xml_data(basicstatus);
4184 if (!getbasic) {
4185 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_notify_pidf: no basic data found");
4186 sipe_xml_free(pidf);
4187 return;
4190 SIPE_DEBUG_INFO("process_incoming_notify_pidf: basic-status(%s)", getbasic);
4191 if (strstr(getbasic, "open")) {
4192 isonline = TRUE;
4194 g_free(getbasic);
4196 uri = sip_uri(sipe_xml_attribute(pidf, "entity")); /* with 'sip:' prefix */ /* AOL comes without the prefix */
4198 display_name_node = sipe_xml_child(pidf, "display-name");
4199 if (display_name_node) {
4200 char * display_name = sipe_xml_data(display_name_node);
4202 sipe_update_user_info(sipe_private, uri, ALIAS_PROP, display_name);
4203 g_free(display_name);
4206 if ((tuple = sipe_xml_child(pidf, "tuple"))) {
4207 if ((status = sipe_xml_child(tuple, "status"))) {
4208 if ((basicstatus = sipe_xml_child(status, "activities"))) {
4209 if ((basicstatus = sipe_xml_child(basicstatus, "activity"))) {
4210 activity = sipe_xml_data(basicstatus);
4211 SIPE_DEBUG_INFO("process_incoming_notify_pidf: activity(%s)", activity);
4217 if (isonline) {
4218 const gchar * status_id = NULL;
4219 if (activity) {
4220 if (sipe_strequal(activity, sipe_activity_map[SIPE_ACTIVITY_BUSY].token)) {
4221 status_id = SIPE_STATUS_ID_BUSY;
4222 } else if (sipe_strequal(activity, sipe_activity_map[SIPE_ACTIVITY_AWAY].token)) {
4223 status_id = SIPE_STATUS_ID_AWAY;
4227 if (!status_id) {
4228 status_id = SIPE_STATUS_ID_AVAILABLE;
4231 SIPE_DEBUG_INFO("process_incoming_notify_pidf: status_id(%s)", status_id);
4232 sipe_got_user_status(sipe_private, uri, status_id);
4233 } else {
4234 sipe_got_user_status(sipe_private, uri, SIPE_STATUS_ID_OFFLINE);
4237 g_free(activity);
4238 g_free(uri);
4239 sipe_xml_free(pidf);
4242 /** 2005 */
4243 static void
4244 sipe_user_info_has_updated(struct sipe_core_private *sipe_private,
4245 const sipe_xml *xn_userinfo)
4247 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
4248 const sipe_xml *xn_states;
4250 g_free(sip->user_states);
4251 sip->user_states = NULL;
4252 if ((xn_states = sipe_xml_child(xn_userinfo, "states")) != NULL) {
4253 gchar *orig = sip->user_states = sipe_xml_stringify(xn_states);
4255 /* this is a hack-around to remove added newline after inner element,
4256 * state in this case, where it shouldn't be.
4257 * After several use of sipe_xml_stringify, amount of added newlines
4258 * grows significantly.
4260 if (orig) {
4261 gchar c, *stripped = orig;
4262 while ((c = *orig++)) {
4263 if ((c != '\n') /* && (c != '\r') */) {
4264 *stripped++ = c;
4267 *stripped = '\0';
4271 /* Publish initial state if not yet.
4272 * Assuming this happens on initial responce to self subscription
4273 * so we've already updated our UserInfo.
4275 if (!sip->initial_state_published) {
4276 send_presence_soap(sipe_private, FALSE);
4277 /* dalayed run */
4278 sipe_schedule_seconds(sipe_private,
4279 "<+update-calendar>",
4280 NULL,
4281 UPDATE_CALENDAR_DELAY,
4282 (sipe_schedule_action) sipe_core_update_calendar,
4283 NULL);
4287 static void process_incoming_notify_msrtc(struct sipe_core_private *sipe_private,
4288 const gchar *data, unsigned len)
4290 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
4291 char *activity = NULL;
4292 const char *epid;
4293 const char *status_id = NULL;
4294 const char *name;
4295 char *uri;
4296 char *self_uri = sip_uri_self(sipe_private);
4297 int avl;
4298 int act;
4299 const char *device_name = NULL;
4300 const char *cal_start_time = NULL;
4301 const char *cal_granularity = NULL;
4302 char *cal_free_busy_base64 = NULL;
4303 struct sipe_buddy *sbuddy;
4304 const sipe_xml *node;
4305 sipe_xml *xn_presentity;
4306 const sipe_xml *xn_availability;
4307 const sipe_xml *xn_activity;
4308 const sipe_xml *xn_display_name;
4309 const sipe_xml *xn_email;
4310 const sipe_xml *xn_phone_number;
4311 const sipe_xml *xn_userinfo;
4312 const sipe_xml *xn_note;
4313 const sipe_xml *xn_oof;
4314 const sipe_xml *xn_state;
4315 const sipe_xml *xn_contact;
4316 char *note;
4317 char *free_activity;
4318 int user_avail;
4319 const char *user_avail_nil;
4320 int res_avail;
4321 time_t user_avail_since = 0;
4322 time_t activity_since = 0;
4324 /* fix for Reuters environment on Linux */
4325 if (data && strstr(data, "encoding=\"utf-16\"")) {
4326 char *tmp_data;
4327 tmp_data = replace(data, "encoding=\"utf-16\"", "encoding=\"utf-8\"");
4328 xn_presentity = sipe_xml_parse(tmp_data, strlen(tmp_data));
4329 g_free(tmp_data);
4330 } else {
4331 xn_presentity = sipe_xml_parse(data, len);
4334 xn_availability = sipe_xml_child(xn_presentity, "availability");
4335 xn_activity = sipe_xml_child(xn_presentity, "activity");
4336 xn_display_name = sipe_xml_child(xn_presentity, "displayName");
4337 xn_email = sipe_xml_child(xn_presentity, "email");
4338 xn_phone_number = sipe_xml_child(xn_presentity, "phoneNumber");
4339 xn_userinfo = sipe_xml_child(xn_presentity, "userInfo");
4340 xn_oof = xn_userinfo ? sipe_xml_child(xn_userinfo, "oof") : NULL;
4341 xn_state = xn_userinfo ? sipe_xml_child(xn_userinfo, "states/state"): NULL;
4342 user_avail = xn_state ? sipe_xml_int_attribute(xn_state, "avail", 0) : 0;
4343 user_avail_since = xn_state ? sipe_utils_str_to_time(sipe_xml_attribute(xn_state, "since")) : 0;
4344 user_avail_nil = xn_state ? sipe_xml_attribute(xn_state, "nil") : NULL;
4345 xn_contact = xn_userinfo ? sipe_xml_child(xn_userinfo, "contact") : NULL;
4346 xn_note = xn_userinfo ? sipe_xml_child(xn_userinfo, "note") : NULL;
4347 note = xn_note ? sipe_xml_data(xn_note) : NULL;
4349 if (sipe_strequal(user_avail_nil, "true")) { /* null-ed */
4350 user_avail = 0;
4351 user_avail_since = 0;
4354 free_activity = NULL;
4356 name = sipe_xml_attribute(xn_presentity, "uri"); /* without 'sip:' prefix */
4357 uri = sip_uri_from_name(name);
4358 avl = sipe_xml_int_attribute(xn_availability, "aggregate", 0);
4359 epid = sipe_xml_attribute(xn_availability, "epid");
4360 act = sipe_xml_int_attribute(xn_activity, "aggregate", 0);
4362 status_id = sipe_get_status_by_act_avail_2005(act, avl, &activity);
4363 res_avail = sipe_get_availability_by_status(status_id, NULL);
4364 if (user_avail > res_avail) {
4365 res_avail = user_avail;
4366 status_id = sipe_get_status_by_availability(user_avail, NULL);
4369 if (xn_display_name) {
4370 char *display_name = g_strdup(sipe_xml_attribute(xn_display_name, "displayName"));
4371 char *email = xn_email ? g_strdup(sipe_xml_attribute(xn_email, "email")) : NULL;
4372 char *phone_label = xn_phone_number ? g_strdup(sipe_xml_attribute(xn_phone_number, "label")) : NULL;
4373 char *phone_number = xn_phone_number ? g_strdup(sipe_xml_attribute(xn_phone_number, "number")) : NULL;
4374 char *tel_uri = sip_to_tel_uri(phone_number);
4376 sipe_update_user_info(sipe_private, uri, ALIAS_PROP, display_name);
4377 sipe_update_user_info(sipe_private, uri, EMAIL_PROP, email);
4378 sipe_update_user_info(sipe_private, uri, PHONE_PROP, tel_uri);
4379 sipe_update_user_info(sipe_private, uri, PHONE_DISPLAY_PROP, !is_empty(phone_label) ? phone_label : phone_number);
4381 g_free(tel_uri);
4382 g_free(phone_label);
4383 g_free(phone_number);
4384 g_free(email);
4385 g_free(display_name);
4388 if (xn_contact) {
4389 /* tel */
4390 for (node = sipe_xml_child(xn_contact, "tel"); node; node = sipe_xml_twin(node))
4392 /* Ex.: <tel type="work">tel:+3222220000</tel> */
4393 const char *phone_type = sipe_xml_attribute(node, "type");
4394 char* phone = sipe_xml_data(node);
4396 sipe_update_user_phone(sipe_private, uri, phone_type, phone, NULL);
4398 g_free(phone);
4402 /* devicePresence */
4403 for (node = sipe_xml_child(xn_presentity, "devices/devicePresence"); node; node = sipe_xml_twin(node)) {
4404 const sipe_xml *xn_device_name;
4405 const sipe_xml *xn_calendar_info;
4406 const sipe_xml *xn_state;
4407 char *state;
4409 /* deviceName */
4410 if (sipe_strequal(sipe_xml_attribute(node, "epid"), epid)) {
4411 xn_device_name = sipe_xml_child(node, "deviceName");
4412 device_name = xn_device_name ? sipe_xml_attribute(xn_device_name, "name") : NULL;
4415 /* calendarInfo */
4416 xn_calendar_info = sipe_xml_child(node, "calendarInfo");
4417 if (xn_calendar_info) {
4418 const char *cal_start_time_tmp = sipe_xml_attribute(xn_calendar_info, "startTime");
4420 if (cal_start_time) {
4421 time_t cal_start_time_t = sipe_utils_str_to_time(cal_start_time);
4422 time_t cal_start_time_t_tmp = sipe_utils_str_to_time(cal_start_time_tmp);
4424 if (cal_start_time_t_tmp > cal_start_time_t) {
4425 cal_start_time = cal_start_time_tmp;
4426 cal_granularity = sipe_xml_attribute(xn_calendar_info, "granularity");
4427 g_free(cal_free_busy_base64);
4428 cal_free_busy_base64 = sipe_xml_data(xn_calendar_info);
4430 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);
4432 } else {
4433 cal_start_time = cal_start_time_tmp;
4434 cal_granularity = sipe_xml_attribute(xn_calendar_info, "granularity");
4435 g_free(cal_free_busy_base64);
4436 cal_free_busy_base64 = sipe_xml_data(xn_calendar_info);
4438 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);
4442 /* state */
4443 xn_state = sipe_xml_child(node, "states/state");
4444 if (xn_state) {
4445 int dev_avail = sipe_xml_int_attribute(xn_state, "avail", 0);
4446 time_t dev_avail_since = sipe_utils_str_to_time(sipe_xml_attribute(xn_state, "since"));
4448 state = sipe_xml_data(xn_state);
4449 if (dev_avail_since > user_avail_since &&
4450 dev_avail >= res_avail)
4452 res_avail = dev_avail;
4453 if (!is_empty(state))
4455 if (sipe_strequal(state, sipe_activity_map[SIPE_ACTIVITY_ON_PHONE].token)) {
4456 g_free(activity);
4457 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_ON_PHONE));
4458 } else if (sipe_strequal(state, "presenting")) {
4459 g_free(activity);
4460 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_IN_CONF));
4461 } else {
4462 activity = state;
4463 state = NULL;
4465 activity_since = dev_avail_since;
4467 status_id = sipe_get_status_by_availability(res_avail, &activity);
4469 g_free(state);
4473 /* oof */
4474 if (xn_oof && res_avail >= 15000) { /* 12000 in 2007 */
4475 g_free(activity);
4476 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_OOF));
4477 activity_since = 0;
4480 sbuddy = g_hash_table_lookup(sipe_private->buddies, uri);
4481 if (sbuddy)
4483 g_free(sbuddy->activity);
4484 sbuddy->activity = activity;
4485 activity = NULL;
4487 sbuddy->activity_since = activity_since;
4489 sbuddy->user_avail = user_avail;
4490 sbuddy->user_avail_since = user_avail_since;
4492 g_free(sbuddy->note);
4493 sbuddy->note = NULL;
4494 if (!is_empty(note)) { sbuddy->note = g_markup_escape_text(note, -1); }
4496 sbuddy->is_oof_note = (xn_oof != NULL);
4498 g_free(sbuddy->device_name);
4499 sbuddy->device_name = NULL;
4500 if (!is_empty(device_name)) { sbuddy->device_name = g_strdup(device_name); }
4502 if (!is_empty(cal_free_busy_base64)) {
4503 g_free(sbuddy->cal_start_time);
4504 sbuddy->cal_start_time = g_strdup(cal_start_time);
4506 sbuddy->cal_granularity = sipe_strcase_equal(cal_granularity, "PT15M") ? 15 : 0;
4508 g_free(sbuddy->cal_free_busy_base64);
4509 sbuddy->cal_free_busy_base64 = cal_free_busy_base64;
4510 cal_free_busy_base64 = NULL;
4512 g_free(sbuddy->cal_free_busy);
4513 sbuddy->cal_free_busy = NULL;
4516 sbuddy->last_non_cal_status_id = status_id;
4517 g_free(sbuddy->last_non_cal_activity);
4518 sbuddy->last_non_cal_activity = g_strdup(sbuddy->activity);
4520 if (sipe_strcase_equal(sbuddy->name, self_uri)) {
4521 if (!sipe_strequal(sbuddy->note, sip->note)) /* not same */
4523 sip->is_oof_note = sbuddy->is_oof_note;
4525 g_free(sip->note);
4526 sip->note = g_strdup(sbuddy->note);
4528 sip->note_since = time(NULL);
4531 g_free(sip->status);
4532 sip->status = g_strdup(sbuddy->last_non_cal_status_id);
4535 g_free(cal_free_busy_base64);
4536 g_free(activity);
4538 SIPE_DEBUG_INFO("process_incoming_notify_msrtc: status(%s)", status_id);
4539 sipe_got_user_status(sipe_private, uri, status_id);
4541 if (!SIPE_CORE_PRIVATE_FLAG_IS(OCS2007) && sipe_strcase_equal(self_uri, uri)) {
4542 sipe_user_info_has_updated(sipe_private, xn_userinfo);
4545 g_free(note);
4546 sipe_xml_free(xn_presentity);
4547 g_free(uri);
4548 g_free(self_uri);
4551 static void sipe_presence_mime_cb(gpointer user_data, /* sipe_core_private */
4552 const GSList *fields,
4553 const gchar *body,
4554 gsize length)
4556 const gchar *type = sipe_utils_nameval_find(fields, "Content-Type");
4558 if (strstr(type,"application/rlmi+xml")) {
4559 process_incoming_notify_rlmi_resub(user_data, body, length);
4560 } else if (strstr(type, "text/xml+msrtc.pidf")) {
4561 process_incoming_notify_msrtc(user_data, body, length);
4562 } else {
4563 process_incoming_notify_rlmi(user_data, body, length);
4567 static void sipe_process_presence(struct sipe_core_private *sipe_private,
4568 struct sipmsg *msg)
4570 const char *ctype = sipmsg_find_header(msg, "Content-Type");
4572 SIPE_DEBUG_INFO("sipe_process_presence: Content-Type: %s", ctype ? ctype : "");
4574 if (ctype &&
4575 (strstr(ctype, "application/rlmi+xml") ||
4576 strstr(ctype, "application/msrtc-event-categories+xml")))
4578 if (strstr(ctype, "multipart"))
4580 sipe_mime_parts_foreach(ctype, msg->body, sipe_presence_mime_cb, sipe_private);
4582 else if(strstr(ctype, "application/msrtc-event-categories+xml") )
4584 process_incoming_notify_rlmi(sipe_private, msg->body, msg->bodylen);
4586 else if(strstr(ctype, "application/rlmi+xml"))
4588 process_incoming_notify_rlmi_resub(sipe_private, msg->body, msg->bodylen);
4591 else if(ctype && strstr(ctype, "text/xml+msrtc.pidf"))
4593 process_incoming_notify_msrtc(sipe_private, msg->body, msg->bodylen);
4595 else
4597 process_incoming_notify_pidf(sipe_private, msg->body, msg->bodylen);
4601 static void sipe_presence_timeout_mime_cb(gpointer user_data,
4602 SIPE_UNUSED_PARAMETER const GSList *fields,
4603 const gchar *body,
4604 gsize length)
4606 GSList **buddies = user_data;
4607 sipe_xml *xml = sipe_xml_parse(body, length);
4609 if (xml && !sipe_strequal(sipe_xml_name(xml), "list")) {
4610 const gchar *uri = sipe_xml_attribute(xml, "uri");
4611 const sipe_xml *xn_category;
4614 * automaton: presence is never expected to change
4616 * see: http://msdn.microsoft.com/en-us/library/ee354295(office.13).aspx
4618 for (xn_category = sipe_xml_child(xml, "category");
4619 xn_category;
4620 xn_category = sipe_xml_twin(xn_category)) {
4621 if (sipe_strequal(sipe_xml_attribute(xn_category, "name"),
4622 "contactCard")) {
4623 const sipe_xml *node = sipe_xml_child(xn_category, "contactCard/automaton");
4624 if (node) {
4625 char *boolean = sipe_xml_data(node);
4626 if (sipe_strequal(boolean, "true")) {
4627 SIPE_DEBUG_INFO("sipe_process_presence_timeout: %s is an automaton: - not subscribing to presence updates",
4628 uri);
4629 uri = NULL;
4631 g_free(boolean);
4633 break;
4637 if (uri) {
4638 *buddies = g_slist_append(*buddies, sip_uri(uri));
4642 sipe_xml_free(xml);
4645 static void sipe_process_presence_timeout(struct sipe_core_private *sipe_private,
4646 struct sipmsg *msg, gchar *who,
4647 int timeout)
4649 const char *ctype = sipmsg_find_header(msg, "Content-Type");
4650 gchar *action_name = sipe_utils_presence_key(who);
4652 SIPE_DEBUG_INFO("sipe_process_presence_timeout: Content-Type: %s", ctype ? ctype : "");
4654 if (ctype &&
4655 strstr(ctype, "multipart") &&
4656 (strstr(ctype, "application/rlmi+xml") ||
4657 strstr(ctype, "application/msrtc-event-categories+xml"))) {
4658 GSList *buddies = NULL;
4660 sipe_mime_parts_foreach(ctype, msg->body, sipe_presence_timeout_mime_cb, &buddies);
4662 if (buddies) {
4663 struct presence_batched_routed *payload = g_malloc(sizeof(struct presence_batched_routed));
4664 payload->host = g_strdup(who);
4665 payload->buddies = buddies;
4666 sipe_schedule_seconds(sipe_private,
4667 action_name,
4668 payload,
4669 timeout,
4670 sipe_subscribe_presence_batched_routed,
4671 sipe_subscribe_presence_batched_routed_free);
4672 SIPE_DEBUG_INFO("Resubscription multiple contacts with batched support & route(%s) in %d", who, timeout);
4675 } else {
4676 sipe_schedule_seconds(sipe_private,
4677 action_name,
4678 g_strdup(who),
4679 timeout,
4680 sipe_subscribe_presence_single,
4681 g_free);
4682 SIPE_DEBUG_INFO("Resubscription single contact with batched support(%s) in %d", who, timeout);
4684 g_free(action_name);
4688 * Dispatcher for all incoming subscription information
4689 * whether it comes from NOTIFY, BENOTIFY requests or
4690 * piggy-backed to subscription's OK responce.
4692 * @param request whether initiated from BE/NOTIFY request or OK-response message.
4693 * @param benotify whether initiated from NOTIFY or BENOTIFY request.
4695 void process_incoming_notify(struct sipe_core_private *sipe_private,
4696 struct sipmsg *msg,
4697 gboolean request, gboolean benotify)
4699 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
4700 const gchar *content_type = sipmsg_find_header(msg, "Content-Type");
4701 const gchar *event = sipmsg_find_header(msg, "Event");
4702 const gchar *subscription_state = sipmsg_find_header(msg, "subscription-state");
4703 char *tmp;
4705 SIPE_DEBUG_INFO("process_incoming_notify: Event: %s\n\n%s",
4706 event ? event : "",
4707 tmp = fix_newlines(msg->body));
4708 g_free(tmp);
4709 SIPE_DEBUG_INFO("process_incoming_notify: subscription_state: %s", subscription_state ? subscription_state : "");
4711 /* implicit subscriptions */
4712 if (content_type && g_str_has_prefix(content_type, "application/ms-imdn+xml")) {
4713 sipe_process_imdn(sipe_private, msg);
4716 if (event) {
4717 /* for one off subscriptions (send with Expire: 0) */
4718 if (sipe_strcase_equal(event, "vnd-microsoft-provisioning-v2"))
4720 sipe_process_provisioning_v2(sipe_private, msg);
4722 else if (sipe_strcase_equal(event, "vnd-microsoft-provisioning"))
4724 sipe_process_provisioning(sipe_private, msg);
4726 else if (sipe_strcase_equal(event, "presence"))
4728 sipe_process_presence(sipe_private, msg);
4730 else if (sipe_strcase_equal(event, "registration-notify"))
4732 sipe_process_registration_notify(sipe_private, msg);
4735 if (!subscription_state || strstr(subscription_state, "active"))
4737 if (sipe_strcase_equal(event, "vnd-microsoft-roaming-contacts"))
4739 sipe_process_roaming_contacts(sipe_private, msg);
4741 else if (sipe_strcase_equal(event, "vnd-microsoft-roaming-self"))
4743 sipe_process_roaming_self(sipe_private, msg);
4745 else if (sipe_strcase_equal(event, "vnd-microsoft-roaming-ACL"))
4747 sipe_process_roaming_acl(sipe_private, msg);
4749 else if (sipe_strcase_equal(event, "presence.wpending"))
4751 sipe_process_presence_wpending(sipe_private, msg);
4753 else if (sipe_strcase_equal(event, "conference"))
4755 sipe_process_conference(sipe_private, msg);
4760 /* The server sends status 'terminated' */
4761 if (subscription_state && strstr(subscription_state, "terminated") ) {
4762 gchar *who = parse_from(sipmsg_find_header(msg, request ? "From" : "To"));
4763 gchar *key = sipe_utils_subscription_key(event, who);
4765 SIPE_DEBUG_INFO("process_incoming_notify: server says that subscription to %s was terminated.", who);
4766 g_free(who);
4768 sipe_subscriptions_remove(sipe_private, key);
4769 g_free(key);
4772 if (!request && event) {
4773 const gchar *expires_header = sipmsg_find_header(msg, "Expires");
4774 int timeout = expires_header ? strtol(expires_header, NULL, 10) : 0;
4775 SIPE_DEBUG_INFO("process_incoming_notify: subscription expires:%d", timeout);
4777 if (timeout) {
4778 /* 2 min ahead of expiration */
4779 timeout = (timeout - 120) > 120 ? (timeout - 120) : timeout;
4781 if (sipe_strcase_equal(event, "presence.wpending") &&
4782 g_slist_find_custom(sip->allow_events, "presence.wpending", (GCompareFunc)g_ascii_strcasecmp))
4784 gchar *action_name = g_strdup_printf("<%s>", "presence.wpending");
4785 sipe_schedule_seconds(sipe_private,
4786 action_name,
4787 NULL,
4788 timeout,
4789 sipe_subscribe_presence_wpending,
4790 NULL);
4791 g_free(action_name);
4793 else if (sipe_strcase_equal(event, "presence") &&
4794 g_slist_find_custom(sip->allow_events, "presence", (GCompareFunc)g_ascii_strcasecmp))
4796 gchar *who = parse_from(sipmsg_find_header(msg, "To"));
4797 gchar *action_name = sipe_utils_presence_key(who);
4799 if (sip->batched_support) {
4800 sipe_process_presence_timeout(sipe_private, msg, who, timeout);
4802 else {
4803 sipe_schedule_seconds(sipe_private,
4804 action_name,
4805 g_strdup(who),
4806 timeout,
4807 sipe_subscribe_presence_single,
4808 g_free);
4809 SIPE_DEBUG_INFO("Resubscription single contact (%s) in %d", who, timeout);
4811 g_free(action_name);
4812 g_free(who);
4817 /* The client responses on received a NOTIFY message */
4818 if (request && !benotify)
4820 sip_transport_response(sipe_private, msg, 200, "OK", NULL);
4825 * Whether user manually changed status or
4826 * it was changed automatically due to user
4827 * became inactive/active again
4829 static gboolean
4830 sipe_is_user_state(struct sipe_core_private *sipe_private)
4832 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
4833 gboolean res;
4834 time_t now = time(NULL);
4836 SIPE_DEBUG_INFO("sipe_is_user_state: sip->idle_switch : %s", asctime(localtime(&(sip->idle_switch))));
4837 SIPE_DEBUG_INFO("sipe_is_user_state: now : %s", asctime(localtime(&now)));
4839 res = ((now - SIPE_IDLE_SET_DELAY * 2) >= sip->idle_switch);
4841 SIPE_DEBUG_INFO("sipe_is_user_state: res = %s", res ? "USER" : "MACHINE");
4842 return res;
4845 static void
4846 send_presence_soap0(struct sipe_core_private *sipe_private,
4847 gboolean do_publish_calendar,
4848 gboolean do_reset_status)
4850 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
4851 struct sipe_calendar* cal = sip->cal;
4852 int availability = 0;
4853 int activity = 0;
4854 gchar *body;
4855 gchar *tmp;
4856 gchar *tmp2 = NULL;
4857 gchar *res_note = NULL;
4858 gchar *res_oof = NULL;
4859 const gchar *note_pub = NULL;
4860 gchar *states = NULL;
4861 gchar *calendar_data = NULL;
4862 gchar *epid = get_epid(sipe_private);
4863 time_t now = time(NULL);
4864 gchar *since_time_str = sipe_utils_time_to_str(now);
4865 const gchar *oof_note = cal ? sipe_ews_get_oof_note(cal) : NULL;
4866 const char *user_input;
4867 gboolean pub_oof = cal && oof_note && (!sip->note || cal->updated > sip->note_since);
4869 if (oof_note && sip->note) {
4870 SIPE_DEBUG_INFO("cal->oof_start : %s", asctime(localtime(&(cal->oof_start))));
4871 SIPE_DEBUG_INFO("sip->note_since : %s", asctime(localtime(&(sip->note_since))));
4874 SIPE_DEBUG_INFO("sip->note : %s", sip->note ? sip->note : "");
4876 if (!sip->initial_state_published ||
4877 do_reset_status)
4879 g_free(sip->status);
4880 sip->status = g_strdup(SIPE_STATUS_ID_AVAILABLE);
4883 sipe_get_act_avail_by_status_2005(sip->status, &activity, &availability);
4885 /* Note */
4886 if (pub_oof) {
4887 note_pub = oof_note;
4888 res_oof = SIPE_SOAP_SET_PRESENCE_OOF_XML;
4889 cal->published = TRUE;
4890 } else if (sip->note) {
4891 if (sip->is_oof_note && !oof_note) { /* stale OOF note, as it's not present in cal already */
4892 g_free(sip->note);
4893 sip->note = NULL;
4894 sip->is_oof_note = FALSE;
4895 sip->note_since = 0;
4896 } else {
4897 note_pub = sip->note;
4898 res_oof = sip->is_oof_note ? SIPE_SOAP_SET_PRESENCE_OOF_XML : "";
4902 if (note_pub)
4904 /* to protocol internal plain text format */
4905 tmp = sipe_backend_markup_strip_html(note_pub);
4906 res_note = g_markup_printf_escaped(SIPE_SOAP_SET_PRESENCE_NOTE_XML, tmp);
4907 g_free(tmp);
4910 /* User State */
4911 if (!do_reset_status) {
4912 if (sipe_is_user_state(sipe_private) && !do_publish_calendar && sip->initial_state_published)
4914 gchar *activity_token = NULL;
4915 int avail_2007 = sipe_get_availability_by_status(sip->status, &activity_token);
4917 states = g_strdup_printf(SIPE_SOAP_SET_PRESENCE_STATES,
4918 avail_2007,
4919 since_time_str,
4920 epid,
4921 activity_token);
4922 g_free(activity_token);
4924 else /* preserve existing publication */
4926 if (sip->user_states) {
4927 states = g_strdup(sip->user_states);
4930 } else {
4931 /* do nothing - then User state will be erased */
4933 sip->initial_state_published = TRUE;
4935 /* CalendarInfo */
4936 if (cal && (!is_empty(cal->legacy_dn) || !is_empty(cal->email)) && cal->fb_start && !is_empty(cal->free_busy))
4938 char *fb_start_str = sipe_utils_time_to_str(cal->fb_start);
4939 char *free_busy_base64 = sipe_cal_get_freebusy_base64(cal->free_busy);
4940 calendar_data = g_strdup_printf(SIPE_SOAP_SET_PRESENCE_CALENDAR,
4941 !is_empty(cal->legacy_dn) ? cal->legacy_dn : cal->email,
4942 fb_start_str,
4943 free_busy_base64);
4944 g_free(fb_start_str);
4945 g_free(free_busy_base64);
4948 user_input = !sipe_is_user_state(sipe_private) && sip->status != SIPE_STATUS_ID_AVAILABLE ? "idle" : "active";
4950 /* forming resulting XML */
4951 body = g_strdup_printf(SIPE_SOAP_SET_PRESENCE,
4952 sipe_private->username,
4953 availability,
4954 activity,
4955 (tmp = g_ascii_strup(g_get_host_name(), -1)),
4956 res_note ? res_note : "",
4957 res_oof ? res_oof : "",
4958 states ? states : "",
4959 calendar_data ? calendar_data : "",
4960 epid,
4961 since_time_str,
4962 since_time_str,
4963 user_input);
4964 g_free(tmp);
4965 g_free(tmp2);
4966 g_free(res_note);
4967 g_free(states);
4968 g_free(calendar_data);
4970 send_soap_request(sipe_private, body);
4972 g_free(body);
4973 g_free(since_time_str);
4974 g_free(epid);
4977 void
4978 send_presence_soap(struct sipe_core_private *sipe_private,
4979 gboolean do_publish_calendar)
4981 return send_presence_soap0(sipe_private, do_publish_calendar, FALSE);
4985 static gboolean
4986 process_send_presence_category_publish_response(struct sipe_core_private *sipe_private,
4987 struct sipmsg *msg,
4988 struct transaction *trans)
4990 const gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
4992 if (msg->response == 409 && g_str_has_prefix(contenttype, "application/msrtc-fault+xml")) {
4993 sipe_xml *xml;
4994 const sipe_xml *node;
4995 gchar *fault_code;
4996 GHashTable *faults;
4997 int index_our;
4998 gboolean has_device_publication = FALSE;
5000 xml = sipe_xml_parse(msg->body, msg->bodylen);
5002 /* test if version mismatch fault */
5003 fault_code = sipe_xml_data(sipe_xml_child(xml, "Faultcode"));
5004 if (!sipe_strequal(fault_code, "Client.BadCall.WrongDelta")) {
5005 SIPE_DEBUG_INFO("process_send_presence_category_publish_response: unsupported fault code:%s returning.", fault_code);
5006 g_free(fault_code);
5007 sipe_xml_free(xml);
5008 return TRUE;
5010 g_free(fault_code);
5012 /* accumulating information about faulty versions */
5013 faults = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
5014 for (node = sipe_xml_child(xml, "details/operation");
5015 node;
5016 node = sipe_xml_twin(node))
5018 const gchar *index = sipe_xml_attribute(node, "index");
5019 const gchar *curVersion = sipe_xml_attribute(node, "curVersion");
5021 g_hash_table_insert(faults, g_strdup(index), g_strdup(curVersion));
5022 SIPE_DEBUG_INFO("fault added: index:%s curVersion:%s", index, curVersion);
5024 sipe_xml_free(xml);
5026 /* here we are parsing own request to figure out what publication
5027 * referensed here only by index went wrong
5029 xml = sipe_xml_parse(trans->msg->body, trans->msg->bodylen);
5031 /* publication */
5032 for (node = sipe_xml_child(xml, "publications/publication"),
5033 index_our = 1; /* starts with 1 - our first publication */
5034 node;
5035 node = sipe_xml_twin(node), index_our++)
5037 gchar *idx = g_strdup_printf("%d", index_our);
5038 const gchar *curVersion = g_hash_table_lookup(faults, idx);
5039 const gchar *categoryName = sipe_xml_attribute(node, "categoryName");
5040 g_free(idx);
5042 if (sipe_strequal("device", categoryName)) {
5043 has_device_publication = TRUE;
5046 if (curVersion) { /* fault exist on this index */
5047 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5048 const gchar *container = sipe_xml_attribute(node, "container");
5049 const gchar *instance = sipe_xml_attribute(node, "instance");
5050 /* key is <category><instance><container> */
5051 gchar *key = g_strdup_printf("<%s><%s><%s>", categoryName, instance, container);
5052 GHashTable *category = g_hash_table_lookup(sip->our_publications, categoryName);
5054 if (category) {
5055 struct sipe_publication *publication =
5056 g_hash_table_lookup(category, key);
5058 SIPE_DEBUG_INFO("key is %s", key);
5060 if (publication) {
5061 SIPE_DEBUG_INFO("Updating %s with version %s. Was %d before.",
5062 key, curVersion, publication->version);
5063 /* updating publication's version to the correct one */
5064 publication->version = atoi(curVersion);
5066 } else {
5067 /* We somehow lost this category from our publications... */
5068 struct sipe_publication *publication = g_new0(struct sipe_publication, 1);
5069 publication->category = g_strdup(categoryName);
5070 publication->instance = atoi(instance);
5071 publication->container = atoi(container);
5072 publication->version = atoi(curVersion);
5073 category = g_hash_table_new_full(g_str_hash, g_str_equal,
5074 g_free, (GDestroyNotify)free_publication);
5075 g_hash_table_insert(category, g_strdup(key), publication);
5076 g_hash_table_insert(sip->our_publications, g_strdup(categoryName), category);
5077 SIPE_DEBUG_INFO("added lost category '%s' key '%s'", categoryName, key);
5079 g_free(key);
5082 sipe_xml_free(xml);
5083 g_hash_table_destroy(faults);
5085 /* rebublishing with right versions */
5086 if (has_device_publication) {
5087 send_publish_category_initial(sipe_private);
5088 } else {
5089 send_presence_status(sipe_private, NULL);
5092 return TRUE;
5096 * Returns 'device' XML part for publication.
5097 * Must be g_free'd after use.
5099 static gchar *
5100 sipe_publish_get_category_device(struct sipe_core_private *sipe_private)
5102 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5103 gchar *uri;
5104 gchar *doc;
5105 gchar *epid = get_epid(sipe_private);
5106 gchar *uuid = generateUUIDfromEPID(epid);
5107 guint device_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_DEVICE);
5108 /* key is <category><instance><container> */
5109 gchar *key = g_strdup_printf("<%s><%u><%u>", "device", device_instance, 2);
5110 struct sipe_publication *publication =
5111 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "device"), key);
5113 g_free(key);
5114 g_free(epid);
5116 uri = sip_uri_self(sipe_private);
5117 doc = g_strdup_printf(SIPE_PUB_XML_DEVICE,
5118 device_instance,
5119 publication ? publication->version : 0,
5120 uuid,
5121 uri,
5122 "00:00:00+01:00", /* @TODO make timezone real*/
5123 g_get_host_name()
5126 g_free(uri);
5127 g_free(uuid);
5129 return doc;
5133 * A service method - use
5134 * - send_publish_get_category_state_machine and
5135 * - send_publish_get_category_state_user instead.
5136 * Must be g_free'd after use.
5138 static gchar *
5139 sipe_publish_get_category_state(struct sipe_core_private *sipe_private,
5140 gboolean is_user_state)
5142 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5143 int availability = sipe_get_availability_by_status(sip->status, NULL);
5144 guint instance = is_user_state ? sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_USER) :
5145 sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_MACHINE);
5146 /* key is <category><instance><container> */
5147 gchar *key_2 = g_strdup_printf("<%s><%u><%u>", "state", instance, 2);
5148 gchar *key_3 = g_strdup_printf("<%s><%u><%u>", "state", instance, 3);
5149 struct sipe_publication *publication_2 =
5150 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_2);
5151 struct sipe_publication *publication_3 =
5152 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_3);
5154 g_free(key_2);
5155 g_free(key_3);
5157 if (publication_2 && (publication_2->availability == availability))
5159 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_state: state has NOT changed. Exiting.");
5160 return NULL; /* nothing to update */
5163 return g_strdup_printf( is_user_state ? SIPE_PUB_XML_STATE_USER : SIPE_PUB_XML_STATE_MACHINE,
5164 instance,
5165 publication_2 ? publication_2->version : 0,
5166 availability,
5167 instance,
5168 publication_3 ? publication_3->version : 0,
5169 availability);
5173 * Only Busy and OOF calendar event are published.
5174 * Different instances are used for that.
5176 * Must be g_free'd after use.
5178 static gchar *
5179 sipe_publish_get_category_state_calendar(struct sipe_core_private *sipe_private,
5180 struct sipe_cal_event *event,
5181 const char *uri,
5182 int cal_satus)
5184 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5185 gchar *start_time_str;
5186 int availability = 0;
5187 gchar *res;
5188 gchar *tmp = NULL;
5189 guint instance = (cal_satus == SIPE_CAL_OOF) ?
5190 sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_CALENDAR_OOF) :
5191 sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_CALENDAR);
5193 /* key is <category><instance><container> */
5194 gchar *key_2 = g_strdup_printf("<%s><%u><%u>", "state", instance, 2);
5195 gchar *key_3 = g_strdup_printf("<%s><%u><%u>", "state", instance, 3);
5196 struct sipe_publication *publication_2 =
5197 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_2);
5198 struct sipe_publication *publication_3 =
5199 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_3);
5201 g_free(key_2);
5202 g_free(key_3);
5204 if (!publication_3 && !event) { /* was nothing, have nothing, exiting */
5205 SIPE_DEBUG_INFO("sipe_publish_get_category_state_calendar: "
5206 "Exiting as no publication and no event for cal_satus:%d", cal_satus);
5207 return NULL;
5210 if (event &&
5211 publication_3 &&
5212 (publication_3->availability == availability) &&
5213 sipe_strequal(publication_3->cal_event_hash, (tmp = sipe_cal_event_hash(event))))
5215 g_free(tmp);
5216 SIPE_DEBUG_INFO("sipe_publish_get_category_state_calendar: "
5217 "cal state has NOT changed for cal_satus:%d. Exiting.", cal_satus);
5218 return NULL; /* nothing to update */
5220 g_free(tmp);
5222 if (event &&
5223 (event->cal_status == SIPE_CAL_BUSY ||
5224 event->cal_status == SIPE_CAL_OOF))
5226 gchar *availability_xml_str = NULL;
5227 gchar *activity_xml_str = NULL;
5229 if (event->cal_status == SIPE_CAL_BUSY) {
5230 availability_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_AVAIL, 6500);
5233 if (event->cal_status == SIPE_CAL_BUSY && event->is_meeting) {
5234 activity_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_ACTIVITY,
5235 sipe_activity_map[SIPE_ACTIVITY_IN_MEETING].token,
5236 "minAvailability=\"6500\"",
5237 "maxAvailability=\"8999\"");
5238 } else if (event->cal_status == SIPE_CAL_OOF) {
5239 activity_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_ACTIVITY,
5240 sipe_activity_map[SIPE_ACTIVITY_OOF].token,
5241 "minAvailability=\"12000\"",
5242 "");
5244 start_time_str = sipe_utils_time_to_str(event->start_time);
5246 res = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR,
5247 instance,
5248 publication_2 ? publication_2->version : 0,
5249 uri,
5250 start_time_str,
5251 availability_xml_str ? availability_xml_str : "",
5252 activity_xml_str ? activity_xml_str : "",
5253 event->subject ? event->subject : "",
5254 event->location ? event->location : "",
5256 instance,
5257 publication_3 ? publication_3->version : 0,
5258 uri,
5259 start_time_str,
5260 availability_xml_str ? availability_xml_str : "",
5261 activity_xml_str ? activity_xml_str : "",
5262 event->subject ? event->subject : "",
5263 event->location ? event->location : ""
5265 g_free(start_time_str);
5266 g_free(availability_xml_str);
5267 g_free(activity_xml_str);
5270 else /* including !event, SIPE_CAL_FREE, SIPE_CAL_TENTATIVE */
5272 res = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_CLEAR,
5273 instance,
5274 publication_2 ? publication_2->version : 0,
5276 instance,
5277 publication_3 ? publication_3->version : 0
5281 return res;
5285 * Returns 'machineState' XML part for publication.
5286 * Must be g_free'd after use.
5288 static gchar *
5289 sipe_publish_get_category_state_machine(struct sipe_core_private *sipe_private)
5291 return sipe_publish_get_category_state(sipe_private, FALSE);
5295 * Returns 'userState' XML part for publication.
5296 * Must be g_free'd after use.
5298 static gchar *
5299 sipe_publish_get_category_state_user(struct sipe_core_private *sipe_private)
5301 return sipe_publish_get_category_state(sipe_private, TRUE);
5305 * Returns 'note' XML part for publication.
5306 * Must be g_free'd after use.
5308 * Protocol format for Note is plain text.
5310 * @param note a note in Sipe internal HTML format
5311 * @param note_type either personal or OOF
5313 static gchar *
5314 sipe_publish_get_category_note(struct sipe_core_private *sipe_private,
5315 const char *note, /* html */
5316 const char *note_type,
5317 time_t note_start,
5318 time_t note_end)
5320 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5321 guint instance = sipe_strequal("OOF", note_type) ? sipe_get_pub_instance(sipe_private, SIPE_PUB_NOTE_OOF) : 0;
5322 /* key is <category><instance><container> */
5323 gchar *key_note_200 = g_strdup_printf("<%s><%u><%u>", "note", instance, 200);
5324 gchar *key_note_300 = g_strdup_printf("<%s><%u><%u>", "note", instance, 300);
5325 gchar *key_note_400 = g_strdup_printf("<%s><%u><%u>", "note", instance, 400);
5327 struct sipe_publication *publication_note_200 =
5328 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_200);
5329 struct sipe_publication *publication_note_300 =
5330 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_300);
5331 struct sipe_publication *publication_note_400 =
5332 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_400);
5334 char *tmp = note ? sipe_backend_markup_strip_html(note) : NULL;
5335 char *n1 = tmp ? g_markup_escape_text(tmp, -1) : NULL;
5336 const char *n2 = publication_note_200 ? publication_note_200->note : NULL;
5337 char *res, *tmp1, *tmp2, *tmp3;
5338 char *start_time_attr;
5339 char *end_time_attr;
5341 g_free(tmp);
5342 tmp = NULL;
5343 g_free(key_note_200);
5344 g_free(key_note_300);
5345 g_free(key_note_400);
5347 /* we even need to republish empty note */
5348 if (sipe_strequal(n1, n2))
5350 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_note: note has NOT changed. Exiting.");
5351 g_free(n1);
5352 return NULL; /* nothing to update */
5355 start_time_attr = note_start ? g_strdup_printf(" startTime=\"%s\"", (tmp = sipe_utils_time_to_str(note_start))) : NULL;
5356 g_free(tmp);
5357 tmp = NULL;
5358 end_time_attr = note_end ? g_strdup_printf(" endTime=\"%s\"", (tmp = sipe_utils_time_to_str(note_end))) : NULL;
5359 g_free(tmp);
5361 if (n1) {
5362 tmp1 = g_strdup_printf(SIPE_PUB_XML_NOTE,
5363 instance,
5364 200,
5365 publication_note_200 ? publication_note_200->version : 0,
5366 note_type,
5367 start_time_attr ? start_time_attr : "",
5368 end_time_attr ? end_time_attr : "",
5369 n1);
5371 tmp2 = g_strdup_printf(SIPE_PUB_XML_NOTE,
5372 instance,
5373 300,
5374 publication_note_300 ? publication_note_300->version : 0,
5375 note_type,
5376 start_time_attr ? start_time_attr : "",
5377 end_time_attr ? end_time_attr : "",
5378 n1);
5380 tmp3 = g_strdup_printf(SIPE_PUB_XML_NOTE,
5381 instance,
5382 400,
5383 publication_note_400 ? publication_note_400->version : 0,
5384 note_type,
5385 start_time_attr ? start_time_attr : "",
5386 end_time_attr ? end_time_attr : "",
5387 n1);
5388 } else {
5389 tmp1 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
5390 "note",
5391 instance,
5392 200,
5393 publication_note_200 ? publication_note_200->version : 0,
5394 "static");
5395 tmp2 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
5396 "note",
5397 instance,
5398 300,
5399 publication_note_200 ? publication_note_200->version : 0,
5400 "static");
5401 tmp3 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
5402 "note",
5403 instance,
5404 400,
5405 publication_note_200 ? publication_note_200->version : 0,
5406 "static");
5408 res = g_strconcat(tmp1, tmp2, tmp3, NULL);
5410 g_free(start_time_attr);
5411 g_free(end_time_attr);
5412 g_free(tmp1);
5413 g_free(tmp2);
5414 g_free(tmp3);
5415 g_free(n1);
5417 return res;
5421 * Returns 'calendarData' XML part with WorkingHours for publication.
5422 * Must be g_free'd after use.
5424 static gchar *
5425 sipe_publish_get_category_cal_working_hours(struct sipe_core_private *sipe_private)
5427 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5428 struct sipe_calendar* cal = sip->cal;
5430 /* key is <category><instance><container> */
5431 gchar *key_cal_1 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 1);
5432 gchar *key_cal_100 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 100);
5433 gchar *key_cal_200 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 200);
5434 gchar *key_cal_300 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 300);
5435 gchar *key_cal_400 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 400);
5436 gchar *key_cal_32000 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 32000);
5438 struct sipe_publication *publication_cal_1 =
5439 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_1);
5440 struct sipe_publication *publication_cal_100 =
5441 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_100);
5442 struct sipe_publication *publication_cal_200 =
5443 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_200);
5444 struct sipe_publication *publication_cal_300 =
5445 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_300);
5446 struct sipe_publication *publication_cal_400 =
5447 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_400);
5448 struct sipe_publication *publication_cal_32000 =
5449 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_32000);
5451 const char *n1 = cal ? cal->working_hours_xml_str : NULL;
5452 const char *n2 = publication_cal_300 ? publication_cal_300->working_hours_xml_str : NULL;
5454 g_free(key_cal_1);
5455 g_free(key_cal_100);
5456 g_free(key_cal_200);
5457 g_free(key_cal_300);
5458 g_free(key_cal_400);
5459 g_free(key_cal_32000);
5461 if (!cal || is_empty(cal->email) || is_empty(cal->working_hours_xml_str)) {
5462 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_cal_working_hours: no data to publish, exiting");
5463 return NULL;
5466 if (sipe_strequal(n1, n2))
5468 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_cal_working_hours: WorkingHours has NOT changed. Exiting.");
5469 return NULL; /* nothing to update */
5472 return g_strdup_printf(SIPE_PUB_XML_WORKING_HOURS,
5473 /* 1 */
5474 publication_cal_1 ? publication_cal_1->version : 0,
5475 cal->email,
5476 cal->working_hours_xml_str,
5477 /* 100 - Public */
5478 publication_cal_100 ? publication_cal_100->version : 0,
5479 /* 200 - Company */
5480 publication_cal_200 ? publication_cal_200->version : 0,
5481 cal->email,
5482 cal->working_hours_xml_str,
5483 /* 300 - Team */
5484 publication_cal_300 ? publication_cal_300->version : 0,
5485 cal->email,
5486 cal->working_hours_xml_str,
5487 /* 400 - Personal */
5488 publication_cal_400 ? publication_cal_400->version : 0,
5489 cal->email,
5490 cal->working_hours_xml_str,
5491 /* 32000 - Blocked */
5492 publication_cal_32000 ? publication_cal_32000->version : 0
5497 * Returns 'calendarData' XML part with FreeBusy for publication.
5498 * Must be g_free'd after use.
5500 static gchar *
5501 sipe_publish_get_category_cal_free_busy(struct sipe_core_private *sipe_private)
5503 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5504 struct sipe_calendar* cal = sip->cal;
5505 guint cal_data_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_CALENDAR_DATA);
5506 char *fb_start_str;
5507 char *free_busy_base64;
5508 const char *st;
5509 const char *fb;
5510 char *res;
5512 /* key is <category><instance><container> */
5513 gchar *key_cal_1 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 1);
5514 gchar *key_cal_100 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 100);
5515 gchar *key_cal_200 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 200);
5516 gchar *key_cal_300 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 300);
5517 gchar *key_cal_400 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 400);
5518 gchar *key_cal_32000 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 32000);
5520 struct sipe_publication *publication_cal_1 =
5521 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_1);
5522 struct sipe_publication *publication_cal_100 =
5523 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_100);
5524 struct sipe_publication *publication_cal_200 =
5525 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_200);
5526 struct sipe_publication *publication_cal_300 =
5527 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_300);
5528 struct sipe_publication *publication_cal_400 =
5529 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_400);
5530 struct sipe_publication *publication_cal_32000 =
5531 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_32000);
5533 g_free(key_cal_1);
5534 g_free(key_cal_100);
5535 g_free(key_cal_200);
5536 g_free(key_cal_300);
5537 g_free(key_cal_400);
5538 g_free(key_cal_32000);
5540 if (!cal || is_empty(cal->email) || !cal->fb_start || is_empty(cal->free_busy)) {
5541 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_cal_free_busy: no data to publish, exiting");
5542 return NULL;
5545 fb_start_str = sipe_utils_time_to_str(cal->fb_start);
5546 free_busy_base64 = sipe_cal_get_freebusy_base64(cal->free_busy);
5548 st = publication_cal_300 ? publication_cal_300->fb_start_str : NULL;
5549 fb = publication_cal_300 ? publication_cal_300->free_busy_base64 : NULL;
5551 /* we will rebuplish the same data to refresh publication time,
5552 * so if data from multiple sources, most recent will be choosen
5554 //if (sipe_strequal(st, fb_start_str) && sipe_strequal(fb, free_busy_base64))
5556 // SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_cal_free_busy: FreeBusy has NOT changed. Exiting.");
5557 // g_free(fb_start_str);
5558 // g_free(free_busy_base64);
5559 // return NULL; /* nothing to update */
5562 res = g_strdup_printf(SIPE_PUB_XML_FREE_BUSY,
5563 /* 1 */
5564 cal_data_instance,
5565 publication_cal_1 ? publication_cal_1->version : 0,
5566 /* 100 - Public */
5567 cal_data_instance,
5568 publication_cal_100 ? publication_cal_100->version : 0,
5569 /* 200 - Company */
5570 cal_data_instance,
5571 publication_cal_200 ? publication_cal_200->version : 0,
5572 cal->email,
5573 fb_start_str,
5574 free_busy_base64,
5575 /* 300 - Team */
5576 cal_data_instance,
5577 publication_cal_300 ? publication_cal_300->version : 0,
5578 cal->email,
5579 fb_start_str,
5580 free_busy_base64,
5581 /* 400 - Personal */
5582 cal_data_instance,
5583 publication_cal_400 ? publication_cal_400->version : 0,
5584 cal->email,
5585 fb_start_str,
5586 free_busy_base64,
5587 /* 32000 - Blocked */
5588 cal_data_instance,
5589 publication_cal_32000 ? publication_cal_32000->version : 0
5592 g_free(fb_start_str);
5593 g_free(free_busy_base64);
5594 return res;
5597 static void send_presence_publish(struct sipe_core_private *sipe_private,
5598 const char *publications)
5600 gchar *uri;
5601 gchar *doc;
5602 gchar *tmp;
5603 gchar *hdr;
5605 uri = sip_uri_self(sipe_private);
5606 doc = g_strdup_printf(SIPE_SEND_PRESENCE,
5607 uri,
5608 publications);
5610 tmp = get_contact(sipe_private);
5611 hdr = g_strdup_printf("Contact: %s\r\n"
5612 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp);
5614 sip_transport_service(sipe_private,
5615 uri,
5616 hdr,
5617 doc,
5618 process_send_presence_category_publish_response);
5620 g_free(tmp);
5621 g_free(hdr);
5622 g_free(uri);
5623 g_free(doc);
5626 static void
5627 send_publish_category_initial(struct sipe_core_private *sipe_private)
5629 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5630 gchar *pub_device = sipe_publish_get_category_device(sipe_private);
5631 gchar *pub_machine;
5632 gchar *publications;
5634 g_free(sip->status);
5635 sip->status = g_strdup(SIPE_STATUS_ID_AVAILABLE); /* our initial state */
5637 pub_machine = sipe_publish_get_category_state_machine(sipe_private);
5638 publications = g_strdup_printf("%s%s",
5639 pub_device,
5640 pub_machine ? pub_machine : "");
5641 g_free(pub_device);
5642 g_free(pub_machine);
5644 send_presence_publish(sipe_private, publications);
5645 g_free(publications);
5648 static void
5649 send_presence_category_publish(struct sipe_core_private *sipe_private)
5651 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5652 gchar *pub_state = sipe_is_user_state(sipe_private) ?
5653 sipe_publish_get_category_state_user(sipe_private) :
5654 sipe_publish_get_category_state_machine(sipe_private);
5655 gchar *pub_note = sipe_publish_get_category_note(sipe_private,
5656 sip->note,
5657 sip->is_oof_note ? "OOF" : "personal",
5660 gchar *publications;
5662 if (!pub_state && !pub_note) {
5663 SIPE_DEBUG_INFO_NOFORMAT("send_presence_category_publish: nothing has changed. Exiting.");
5664 return;
5667 publications = g_strdup_printf("%s%s",
5668 pub_state ? pub_state : "",
5669 pub_note ? pub_note : "");
5671 g_free(pub_state);
5672 g_free(pub_note);
5674 send_presence_publish(sipe_private, publications);
5675 g_free(publications);
5679 * Publishes self status
5680 * based on own calendar information.
5682 * For 2007+
5684 void
5685 publish_calendar_status_self(struct sipe_core_private *sipe_private,
5686 SIPE_UNUSED_PARAMETER void *unused)
5688 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5689 struct sipe_cal_event* event = NULL;
5690 gchar *pub_cal_working_hours = NULL;
5691 gchar *pub_cal_free_busy = NULL;
5692 gchar *pub_calendar = NULL;
5693 gchar *pub_calendar2 = NULL;
5694 gchar *pub_oof_note = NULL;
5695 const gchar *oof_note;
5696 time_t oof_start = 0;
5697 time_t oof_end = 0;
5699 if (!sip->cal) {
5700 SIPE_DEBUG_INFO_NOFORMAT("publish_calendar_status_self() no calendar data.");
5701 return;
5704 SIPE_DEBUG_INFO_NOFORMAT("publish_calendar_status_self() started.");
5705 if (sip->cal->cal_events) {
5706 event = sipe_cal_get_event(sip->cal->cal_events, time(NULL));
5709 if (!event) {
5710 SIPE_DEBUG_INFO_NOFORMAT("publish_calendar_status_self: current event is NULL");
5711 } else {
5712 char *desc = sipe_cal_event_describe(event);
5713 SIPE_DEBUG_INFO("publish_calendar_status_self: current event is:\n%s", desc ? desc : "");
5714 g_free(desc);
5717 /* Logic
5718 if OOF
5719 OOF publish, Busy clean
5720 ilse if Busy
5721 OOF clean, Busy publish
5722 else
5723 OOF clean, Busy clean
5725 if (event && event->cal_status == SIPE_CAL_OOF) {
5726 pub_calendar = sipe_publish_get_category_state_calendar(sipe_private, event, sip->cal->email, SIPE_CAL_OOF);
5727 pub_calendar2 = sipe_publish_get_category_state_calendar(sipe_private, NULL, sip->cal->email, SIPE_CAL_BUSY);
5728 } else if (event && event->cal_status == SIPE_CAL_BUSY) {
5729 pub_calendar = sipe_publish_get_category_state_calendar(sipe_private, NULL, sip->cal->email, SIPE_CAL_OOF);
5730 pub_calendar2 = sipe_publish_get_category_state_calendar(sipe_private, event, sip->cal->email, SIPE_CAL_BUSY);
5731 } else {
5732 pub_calendar = sipe_publish_get_category_state_calendar(sipe_private, NULL, sip->cal->email, SIPE_CAL_OOF);
5733 pub_calendar2 = sipe_publish_get_category_state_calendar(sipe_private, NULL, sip->cal->email, SIPE_CAL_BUSY);
5736 oof_note = sipe_ews_get_oof_note(sip->cal);
5737 if (sipe_strequal("Scheduled", sip->cal->oof_state)) {
5738 oof_start = sip->cal->oof_start;
5739 oof_end = sip->cal->oof_end;
5741 pub_oof_note = sipe_publish_get_category_note(sipe_private, oof_note, "OOF", oof_start, oof_end);
5743 pub_cal_working_hours = sipe_publish_get_category_cal_working_hours(sipe_private);
5744 pub_cal_free_busy = sipe_publish_get_category_cal_free_busy(sipe_private);
5746 if (!pub_cal_working_hours && !pub_cal_free_busy && !pub_calendar && !pub_calendar2 && !pub_oof_note) {
5747 SIPE_DEBUG_INFO_NOFORMAT("publish_calendar_status_self: nothing has changed.");
5748 } else {
5749 gchar *publications = g_strdup_printf("%s%s%s%s%s",
5750 pub_cal_working_hours ? pub_cal_working_hours : "",
5751 pub_cal_free_busy ? pub_cal_free_busy : "",
5752 pub_calendar ? pub_calendar : "",
5753 pub_calendar2 ? pub_calendar2 : "",
5754 pub_oof_note ? pub_oof_note : "");
5756 send_presence_publish(sipe_private, publications);
5757 g_free(publications);
5760 g_free(pub_cal_working_hours);
5761 g_free(pub_cal_free_busy);
5762 g_free(pub_calendar);
5763 g_free(pub_calendar2);
5764 g_free(pub_oof_note);
5766 /* repeat scheduling */
5767 sipe_sched_calendar_status_self_publish(sipe_private, time(NULL));
5770 static void send_presence_status(struct sipe_core_private *sipe_private,
5771 SIPE_UNUSED_PARAMETER void *unused)
5773 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5774 PurpleStatus * status = purple_account_get_active_status(sip->account);
5776 if (!status) return;
5778 SIPE_DEBUG_INFO("send_presence_status: status: %s (%s)",
5779 purple_status_get_id(status) ? purple_status_get_id(status) : "",
5780 sipe_is_user_state(sipe_private) ? "USER" : "MACHINE");
5782 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
5783 send_presence_category_publish(sipe_private);
5784 } else {
5785 send_presence_soap(sipe_private, FALSE);
5789 static guint sipe_ht_hash_nick(const char *nick)
5791 char *lc = g_utf8_strdown(nick, -1);
5792 guint bucket = g_str_hash(lc);
5793 g_free(lc);
5795 return bucket;
5798 static gboolean sipe_ht_equals_nick(const char *nick1, const char *nick2)
5800 char *nick1_norm = NULL;
5801 char *nick2_norm = NULL;
5802 gboolean equal;
5804 if (nick1 == NULL && nick2 == NULL) return TRUE;
5805 if (nick1 == NULL || nick2 == NULL ||
5806 !g_utf8_validate(nick1, -1, NULL) ||
5807 !g_utf8_validate(nick2, -1, NULL)) return FALSE;
5809 nick1_norm = g_utf8_casefold(nick1, -1);
5810 nick2_norm = g_utf8_casefold(nick2, -1);
5811 equal = g_utf8_collate(nick2_norm, nick2_norm) == 0;
5812 g_free(nick2_norm);
5813 g_free(nick1_norm);
5815 return equal;
5818 /* temporary function */
5819 void sipe_purple_setup(struct sipe_core_public *sipe_public,
5820 PurpleConnection *gc)
5822 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA;
5823 sip->gc = gc;
5824 sip->account = purple_connection_get_account(gc);
5827 struct sipe_core_public *sipe_core_allocate(const gchar *signin_name,
5828 const gchar *login_domain,
5829 const gchar *login_account,
5830 const gchar *password,
5831 const gchar *email,
5832 const gchar *email_url,
5833 const gchar **errmsg)
5835 struct sipe_core_private *sipe_private;
5836 struct sipe_account_data *sip;
5837 gchar **user_domain;
5839 SIPE_DEBUG_INFO("sipe_core_allocate: signin_name '%s'", signin_name);
5841 /* ensure that sign-in name doesn't contain invalid characters */
5842 if (strpbrk(signin_name, "\t\v\r\n") != NULL) {
5843 *errmsg = _("SIP Exchange user name contains invalid characters");
5844 return NULL;
5847 /* ensure that sign-in name format is name@domain */
5848 if (!strchr(signin_name, '@') ||
5849 g_str_has_prefix(signin_name, "@") ||
5850 g_str_has_suffix(signin_name, "@")) {
5851 *errmsg = _("User name should be a valid SIP URI\nExample: user@company.com");
5852 return NULL;
5855 /* ensure that email format is name@domain (if provided) */
5856 if (!is_empty(email) &&
5857 (!strchr(email, '@') ||
5858 g_str_has_prefix(email, "@") ||
5859 g_str_has_suffix(email, "@")))
5861 *errmsg = _("Email address should be valid if provided\nExample: user@company.com");
5862 return NULL;
5865 /* ensure that user name doesn't contain spaces */
5866 user_domain = g_strsplit(signin_name, "@", 2);
5867 SIPE_DEBUG_INFO("sipe_core_allocate: user '%s' domain '%s'", user_domain[0], user_domain[1]);
5868 if (strchr(user_domain[0], ' ') != NULL) {
5869 g_strfreev(user_domain);
5870 *errmsg = _("SIP Exchange user name contains whitespace");
5871 return NULL;
5874 /* ensure that email_url is in proper format if enabled (if provided).
5875 * Example (Exchange): https://server.company.com/EWS/Exchange.asmx
5876 * Example (Domino) : https://[domino_server]/[mail_database_name].nsf
5878 if (!is_empty(email_url)) {
5879 char *tmp = g_ascii_strdown(email_url, -1);
5880 if (!g_str_has_prefix(tmp, "https://"))
5882 g_free(tmp);
5883 g_strfreev(user_domain);
5884 *errmsg = _("Email services URL should be valid if provided\n"
5885 "Example: https://exchange.corp.com/EWS/Exchange.asmx\n"
5886 "Example: https://domino.corp.com/maildatabase.nsf");
5887 return NULL;
5889 g_free(tmp);
5892 sipe_private = g_new0(struct sipe_core_private, 1);
5893 sipe_private->temporary = sip = g_new0(struct sipe_account_data, 1);
5894 sip->subscribed_buddies = FALSE;
5895 sip->initial_state_published = FALSE;
5896 sipe_private->username = g_strdup(signin_name);
5897 sip->email = is_empty(email) ? g_strdup(signin_name) : g_strdup(email);
5898 sip->authdomain = is_empty(login_domain) ? NULL : g_strdup(login_domain);
5899 sip->authuser = is_empty(login_account) ? NULL : g_strdup(login_account);
5900 sip->password = g_strdup(password);
5901 sipe_private->public.sip_name = g_strdup(user_domain[0]);
5902 sipe_private->public.sip_domain = g_strdup(user_domain[1]);
5903 g_strfreev(user_domain);
5905 sipe_private->buddies = g_hash_table_new((GHashFunc)sipe_ht_hash_nick, (GEqualFunc)sipe_ht_equals_nick);
5906 sip->our_publications = g_hash_table_new_full(g_str_hash, g_str_equal,
5907 g_free, (GDestroyNotify)g_hash_table_destroy);
5908 sipe_subscriptions_init(sipe_private);
5909 sip->status = g_strdup(SIPE_STATUS_ID_UNKNOWN);
5911 return((struct sipe_core_public *)sipe_private);
5914 void sipe_connection_cleanup(struct sipe_core_private *sipe_private)
5916 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5918 g_free(sipe_private->epid);
5919 sipe_private->epid = NULL;
5921 sip_transport_disconnect(sipe_private);
5923 sipe_schedule_cancel_all(sipe_private);
5925 if (sip->allow_events) {
5926 GSList *entry = sip->allow_events;
5927 while (entry) {
5928 g_free(entry->data);
5929 entry = entry->next;
5932 g_slist_free(sip->allow_events);
5934 if (sip->containers) {
5935 GSList *entry = sip->containers;
5936 while (entry) {
5937 free_container((struct sipe_container *)entry->data);
5938 entry = entry->next;
5941 g_slist_free(sip->containers);
5943 if (sipe_private->contact)
5944 g_free(sipe_private->contact);
5945 sipe_private->contact = NULL;
5946 if (sip->regcallid)
5947 g_free(sip->regcallid);
5948 sip->regcallid = NULL;
5950 if (sipe_private->focus_factory_uri)
5951 g_free(sipe_private->focus_factory_uri);
5952 sipe_private->focus_factory_uri = NULL;
5954 if (sip->cal) {
5955 sipe_cal_calendar_free(sip->cal);
5957 sip->cal = NULL;
5961 * A callback for g_hash_table_foreach_remove
5963 static gboolean sipe_buddy_remove(SIPE_UNUSED_PARAMETER gpointer key, gpointer buddy,
5964 SIPE_UNUSED_PARAMETER gpointer user_data)
5966 sipe_free_buddy((struct sipe_buddy *) buddy);
5968 /* We must return TRUE as the key/value have already been deleted */
5969 return(TRUE);
5972 void sipe_core_deallocate(struct sipe_core_public *sipe_public)
5974 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
5975 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5977 /* leave all conversations */
5978 sipe_session_close_all(sipe_private);
5979 sipe_session_remove_all(sipe_private);
5981 if (sip->csta) {
5982 sip_csta_close(sipe_private);
5985 if (PURPLE_CONNECTION_IS_CONNECTED(sip->gc)) {
5986 sipe_subscriptions_unsubscribe(sipe_private);
5987 sip_transport_deregister(sipe_private);
5990 sipe_connection_cleanup(sipe_private);
5991 g_free(sipe_private->public.sip_name);
5992 g_free(sipe_private->public.sip_domain);
5993 g_free(sipe_private->username);
5994 g_free(sip->email);
5995 g_free(sip->password);
5996 g_free(sip->authdomain);
5997 g_free(sip->authuser);
5998 g_free(sip->status);
5999 g_free(sip->note);
6000 g_free(sip->user_states);
6002 g_hash_table_foreach_steal(sipe_private->buddies, sipe_buddy_remove, NULL);
6003 g_hash_table_destroy(sipe_private->buddies);
6004 g_hash_table_destroy(sip->our_publications);
6005 g_hash_table_destroy(sip->user_state_publications);
6006 sipe_subscriptions_destroy(sipe_private);
6008 if (sip->groups) {
6009 GSList *entry = sip->groups;
6010 while (entry) {
6011 struct sipe_group *group = entry->data;
6012 g_free(group->name);
6013 g_free(group);
6014 entry = entry->next;
6017 g_slist_free(sip->groups);
6019 if (sip->our_publication_keys) {
6020 GSList *entry = sip->our_publication_keys;
6021 while (entry) {
6022 g_free(entry->data);
6023 entry = entry->next;
6026 g_slist_free(sip->our_publication_keys);
6028 g_free(sip);
6029 g_free(sipe_private);
6032 static void sipe_searchresults_im_buddy(PurpleConnection *gc, GList *row,
6033 SIPE_UNUSED_PARAMETER void *user_data)
6035 PurpleAccount *acct = purple_connection_get_account(gc);
6036 char *id = sip_uri_from_name((gchar *)g_list_nth_data(row, 0));
6037 PurpleConversation *conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, id, acct);
6038 if (conv == NULL)
6039 conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, acct, id);
6040 purple_conversation_present(conv);
6041 g_free(id);
6044 static void sipe_searchresults_add_buddy(PurpleConnection *gc, GList *row,
6045 SIPE_UNUSED_PARAMETER void *user_data)
6048 purple_blist_request_add_buddy(purple_connection_get_account(gc),
6049 g_list_nth_data(row, 0), _("Other Contacts"), g_list_nth_data(row, 1));
6052 static gboolean process_search_contact_response(struct sipe_core_private *sipe_private,
6053 struct sipmsg *msg,
6054 SIPE_UNUSED_PARAMETER struct transaction *trans)
6056 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
6057 PurpleNotifySearchResults *results;
6058 PurpleNotifySearchColumn *column;
6059 sipe_xml *searchResults;
6060 const sipe_xml *mrow;
6061 int match_count = 0;
6062 gboolean more = FALSE;
6063 gchar *secondary;
6065 SIPE_DEBUG_INFO("process_search_contact_response: body:\n%s", msg->body ? msg->body : "");
6067 searchResults = sipe_xml_parse(msg->body, msg->bodylen);
6068 if (!searchResults) {
6069 SIPE_DEBUG_INFO_NOFORMAT("process_search_contact_response: no parseable searchResults");
6070 return FALSE;
6073 results = purple_notify_searchresults_new();
6075 if (results == NULL) {
6076 SIPE_DEBUG_ERROR_NOFORMAT("purple_parse_searchreply: Unable to display the search results.");
6077 purple_notify_error(sip->gc, NULL, _("Unable to display the search results"), NULL);
6079 sipe_xml_free(searchResults);
6080 return FALSE;
6083 column = purple_notify_searchresults_column_new(_("User name"));
6084 purple_notify_searchresults_column_add(results, column);
6086 column = purple_notify_searchresults_column_new(_("Name"));
6087 purple_notify_searchresults_column_add(results, column);
6089 column = purple_notify_searchresults_column_new(_("Company"));
6090 purple_notify_searchresults_column_add(results, column);
6092 column = purple_notify_searchresults_column_new(_("Country"));
6093 purple_notify_searchresults_column_add(results, column);
6095 column = purple_notify_searchresults_column_new(_("Email"));
6096 purple_notify_searchresults_column_add(results, column);
6098 for (mrow = sipe_xml_child(searchResults, "Body/Array/row"); mrow; mrow = sipe_xml_twin(mrow)) {
6099 GList *row = NULL;
6101 gchar **uri_parts = g_strsplit(sipe_xml_attribute(mrow, "uri"), ":", 2);
6102 row = g_list_append(row, g_strdup(uri_parts[1]));
6103 g_strfreev(uri_parts);
6105 row = g_list_append(row, g_strdup(sipe_xml_attribute(mrow, "displayName")));
6106 row = g_list_append(row, g_strdup(sipe_xml_attribute(mrow, "company")));
6107 row = g_list_append(row, g_strdup(sipe_xml_attribute(mrow, "country")));
6108 row = g_list_append(row, g_strdup(sipe_xml_attribute(mrow, "email")));
6110 purple_notify_searchresults_row_add(results, row);
6111 match_count++;
6114 if ((mrow = sipe_xml_child(searchResults, "Body/directorySearch/moreAvailable")) != NULL) {
6115 char *data = sipe_xml_data(mrow);
6116 more = (g_strcasecmp(data, "true") == 0);
6117 g_free(data);
6120 secondary = g_strdup_printf(
6121 dngettext(PACKAGE_NAME,
6122 "Found %d contact%s:",
6123 "Found %d contacts%s:", match_count),
6124 match_count, more ? _(" (more matched your query)") : "");
6126 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_IM, sipe_searchresults_im_buddy);
6127 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_ADD, sipe_searchresults_add_buddy);
6128 purple_notify_searchresults(sip->gc, NULL, NULL, secondary, results, NULL, NULL);
6130 g_free(secondary);
6131 sipe_xml_free(searchResults);
6132 return TRUE;
6135 void sipe_search_contact_with_cb(PurpleConnection *gc, PurpleRequestFields *fields)
6137 GList *entries = purple_request_field_group_get_fields(purple_request_fields_get_groups(fields)->data);
6138 gchar **attrs = g_new(gchar *, g_list_length(entries) + 1);
6139 unsigned i = 0;
6141 if (!attrs) return;
6143 do {
6144 PurpleRequestField *field = entries->data;
6145 const char *id = purple_request_field_get_id(field);
6146 const char *value = purple_request_field_string_get_value(field);
6148 SIPE_DEBUG_INFO("sipe_search_contact_with_cb: %s = '%s'", id, value ? value : "");
6150 if (value != NULL) attrs[i++] = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, id, value);
6151 } while ((entries = g_list_next(entries)) != NULL);
6152 attrs[i] = NULL;
6154 if (i > 0) {
6155 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
6156 gchar *domain_uri = sip_uri_from_name(sipe_private->public.sip_domain);
6157 gchar *query = g_strjoinv(NULL, attrs);
6158 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 100, query);
6159 SIPE_DEBUG_INFO("sipe_search_contact_with_cb: body:\n%s", body ? body : "");
6160 send_soap_request_with_cb(sipe_private, domain_uri, body,
6161 process_search_contact_response, NULL);
6162 g_free(domain_uri);
6163 g_free(body);
6164 g_free(query);
6167 g_strfreev(attrs);
6170 static void sipe_publish_get_cat_state_user_to_clear(SIPE_UNUSED_PARAMETER const char *name,
6171 gpointer value,
6172 GString* str)
6174 struct sipe_publication *publication = value;
6176 g_string_append_printf( str,
6177 SIPE_PUB_XML_PUBLICATION_CLEAR,
6178 publication->category,
6179 publication->instance,
6180 publication->container,
6181 publication->version,
6182 "static");
6185 void sipe_core_reset_status(struct sipe_core_public *sipe_public)
6187 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
6188 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA;
6189 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) /* 2007+ */
6191 GString* str = g_string_new(NULL);
6192 gchar *publications;
6194 if (!sip->user_state_publications || g_hash_table_size(sip->user_state_publications) == 0) {
6195 SIPE_DEBUG_INFO_NOFORMAT("sipe_reset_status: no userState publications, exiting.");
6196 return;
6199 g_hash_table_foreach(sip->user_state_publications, (GHFunc)sipe_publish_get_cat_state_user_to_clear, str);
6200 publications = g_string_free(str, FALSE);
6202 send_presence_publish(sipe_private, publications);
6203 g_free(publications);
6205 else /* 2005 */
6207 send_presence_soap0(sipe_private, FALSE, TRUE);
6211 /** for Access levels menu */
6212 #define INDENT_FMT " %s"
6214 /** Member is directly placed to access level container.
6215 * For example SIP URI of user is in the container.
6217 #define INDENT_MARKED_FMT "* %s"
6219 /** Member is indirectly belong to access level container.
6220 * For example 'sameEnterprise' is in the container and user
6221 * belongs to that same enterprise.
6223 #define INDENT_MARKED_INHERITED_FMT "= %s"
6225 GSList *sipe_core_buddy_info(struct sipe_core_public *sipe_public,
6226 const gchar *name,
6227 const gchar *status_name,
6228 gboolean is_online)
6230 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
6231 gchar *note = NULL;
6232 gboolean is_oof_note = FALSE;
6233 gchar *activity = NULL;
6234 gchar *calendar = NULL;
6235 gchar *meeting_subject = NULL;
6236 gchar *meeting_location = NULL;
6237 gchar *access_text = NULL;
6238 GSList *info = NULL;
6240 #define SIPE_ADD_BUDDY_INFO(l, t) \
6242 struct sipe_buddy_info *sbi = g_malloc(sizeof(struct sipe_buddy_info)); \
6243 sbi->label = (l); \
6244 sbi->text = (t); \
6245 info = g_slist_append(info, sbi); \
6248 if (sipe_public) { //happens on pidgin exit
6249 struct sipe_buddy *sbuddy = g_hash_table_lookup(sipe_private->buddies, name);
6250 if (sbuddy) {
6251 note = sbuddy->note;
6252 is_oof_note = sbuddy->is_oof_note;
6253 activity = sbuddy->activity;
6254 calendar = sipe_cal_get_description(sbuddy);
6255 meeting_subject = sbuddy->meeting_subject;
6256 meeting_location = sbuddy->meeting_location;
6258 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
6259 gboolean is_group_access = FALSE;
6260 const int container_id = sipe_find_access_level(sipe_private, "user", sipe_get_no_sip_uri(name), &is_group_access);
6261 const char *access_level = sipe_get_access_level_name(container_id);
6262 access_text = is_group_access ?
6263 g_strdup(access_level) :
6264 g_strdup_printf(INDENT_MARKED_FMT, access_level);
6268 //Layout
6269 if (is_online)
6271 gchar *status_str = g_strdup(activity ? activity : status_name);
6273 SIPE_ADD_BUDDY_INFO(_("Status"), status_str);
6275 if (is_online && !is_empty(calendar))
6277 SIPE_ADD_BUDDY_INFO(_("Calendar"), calendar);
6278 calendar = NULL;
6280 g_free(calendar);
6281 if (!is_empty(meeting_location))
6283 SIPE_ADD_BUDDY_INFO(_("Meeting in"), g_strdup(meeting_location));
6285 if (!is_empty(meeting_subject))
6287 SIPE_ADD_BUDDY_INFO(_("Meeting about"), g_strdup(meeting_subject));
6289 if (note)
6291 SIPE_DEBUG_INFO("sipe_tooltip_text: %s note: '%s'", name, note);
6292 SIPE_ADD_BUDDY_INFO(is_oof_note ? _("Out of office note") : _("Note"),
6293 g_strdup_printf("<i>%s</i>", note));
6295 if (access_text) {
6296 SIPE_ADD_BUDDY_INFO(_("Access level"), access_text);
6299 return(info);
6302 static PurpleBuddy *
6303 purple_blist_add_buddy_clone(PurpleGroup * group, PurpleBuddy * buddy)
6305 PurpleBuddy *clone;
6306 const gchar *server_alias, *email;
6307 const PurpleStatus *status = purple_presence_get_active_status(purple_buddy_get_presence(buddy));
6309 clone = purple_buddy_new(buddy->account, buddy->name, buddy->alias);
6311 purple_blist_add_buddy(clone, NULL, group, NULL);
6313 server_alias = purple_buddy_get_server_alias(buddy);
6314 if (server_alias) {
6315 purple_blist_server_alias_buddy(clone, server_alias);
6318 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
6319 if (email) {
6320 purple_blist_node_set_string(&clone->node, EMAIL_PROP, email);
6323 purple_presence_set_status_active(purple_buddy_get_presence(clone), purple_status_get_id(status), TRUE);
6324 //for UI to update;
6325 purple_prpl_got_user_status(clone->account, clone->name, purple_status_get_id(status), NULL);
6326 return clone;
6329 static void
6330 sipe_buddy_menu_copy_to_cb(PurpleBlistNode *node, const char *group_name)
6332 PurpleBuddy *buddy, *b;
6333 PurpleConnection *gc;
6334 PurpleGroup * group = purple_find_group(group_name);
6336 g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node));
6338 buddy = (PurpleBuddy *)node;
6340 SIPE_DEBUG_INFO("sipe_buddy_menu_copy_to_cb: copying %s to %s", buddy->name, group_name);
6341 gc = purple_account_get_connection(buddy->account);
6343 b = purple_find_buddy_in_group(buddy->account, buddy->name, group);
6344 if (!b){
6345 purple_blist_add_buddy_clone(group, buddy);
6348 sipe_group_buddy(gc, buddy->name, NULL, group_name);
6351 static void
6352 sipe_buddy_menu_chat_new_cb(PurpleBuddy *buddy)
6354 struct sipe_core_private *sipe_private = PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE;
6356 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_new_cb: buddy->name=%s", buddy->name);
6358 /* 2007+ conference */
6359 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007))
6361 sipe_conf_add(sipe_private, buddy->name);
6363 else /* 2005- multiparty chat */
6365 gchar *self = sip_uri_self(sipe_private);
6366 struct sip_session *session;
6368 session = sipe_session_add_chat(sipe_private);
6369 session->chat_title = sipe_chat_get_name(session->callid);
6370 session->roster_manager = g_strdup(self);
6372 session->backend_session = sipe_backend_chat_create(SIPE_CORE_PUBLIC,
6373 session->chat_id,
6374 session->chat_title,
6375 self,
6376 FALSE);
6377 sipe_backend_chat_add(session->backend_session,
6378 self,
6379 FALSE);
6380 sipe_invite(sipe_private, session, buddy->name, NULL, NULL, NULL, FALSE);
6382 g_free(self);
6387 * For 2007+ conference only.
6389 static void
6390 sipe_buddy_menu_chat_make_leader_cb(PurpleBuddy *buddy, const char *chat_title)
6392 struct sipe_core_private *sipe_private = PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE;
6393 struct sip_session *session;
6395 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_make_leader_cb: buddy->name=%s", buddy->name);
6396 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_make_leader_cb: chat_title=%s", chat_title);
6398 session = sipe_session_find_chat_by_title(sipe_private, chat_title);
6400 sipe_conf_modify_user_role(sipe_private, session, buddy->name);
6404 * For 2007+ conference only.
6406 static void
6407 sipe_buddy_menu_chat_remove_cb(PurpleBuddy *buddy, const char *chat_title)
6409 struct sipe_core_private *sipe_private = PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE;
6410 struct sip_session *session;
6412 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_remove_cb: buddy->name=%s", buddy->name);
6413 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_remove_cb: chat_title=%s", chat_title);
6415 session = sipe_session_find_chat_by_title(sipe_private, chat_title);
6417 sipe_conf_delete_user(sipe_private, session, buddy->name);
6420 static void
6421 sipe_buddy_menu_chat_invite_cb(PurpleBuddy *buddy, char *chat_title)
6423 struct sipe_core_private *sipe_private = PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE;
6424 struct sip_session *session;
6426 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_invite_cb: buddy->name=%s", buddy->name);
6427 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_invite_cb: chat_title=%s", chat_title);
6429 session = sipe_session_find_chat_by_title(sipe_private, chat_title);
6431 sipe_invite_to_chat(sipe_private, session, buddy->name);
6434 static void
6435 sipe_buddy_menu_make_call_cb(PurpleBuddy *buddy, const char *phone)
6437 struct sipe_core_private *sipe_private = PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE;
6439 SIPE_DEBUG_INFO("sipe_buddy_menu_make_call_cb: buddy->name=%s", buddy->name);
6440 if (phone) {
6441 char *tel_uri = sip_to_tel_uri(phone);
6443 SIPE_DEBUG_INFO("sipe_buddy_menu_make_call_cb: going to call number: %s", tel_uri ? tel_uri : "");
6444 sip_csta_make_call(sipe_private, tel_uri);
6446 g_free(tel_uri);
6450 static void
6451 sipe_buddy_menu_access_level_help_cb(PurpleBuddy *buddy)
6453 /** Translators: replace with URL to localized page
6454 * If it doesn't exist copy the original URL */
6455 purple_notify_uri(buddy->account->gc, _("https://sourceforge.net/apps/mediawiki/sipe/index.php?title=Access_Levels"));
6458 static void
6459 sipe_buddy_menu_send_email_cb(PurpleBuddy *buddy)
6461 const gchar *email;
6462 SIPE_DEBUG_INFO("sipe_buddy_menu_send_email_cb: buddy->name=%s", buddy->name);
6464 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
6465 if (email)
6467 char *command_line = g_strdup_printf(
6468 #ifdef _WIN32
6469 "cmd /c start"
6470 #else
6471 "xdg-email"
6472 #endif
6473 " mailto:%s", email);
6474 SIPE_DEBUG_INFO("sipe_buddy_menu_send_email_cb: going to call email client: %s", command_line);
6476 g_spawn_command_line_async(command_line, NULL);
6477 g_free(command_line);
6479 else
6481 SIPE_DEBUG_INFO("sipe_buddy_menu_send_email_cb: no email address stored for buddy=%s", buddy->name);
6485 static void
6486 sipe_buddy_menu_access_level_cb(PurpleBuddy *buddy,
6487 struct sipe_container *container)
6489 struct sipe_core_private *sipe_private = PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE;
6490 struct sipe_container_member *member;
6492 if (!container || !container->members) return;
6494 member = ((struct sipe_container_member *)container->members->data);
6496 if (!member->type) return;
6498 SIPE_DEBUG_INFO("sipe_buddy_menu_access_level_cb: container->id=%d, member->type=%s, member->value=%s",
6499 container->id, member->type, member->value ? member->value : "");
6501 sipe_change_access_level(sipe_private, container->id, member->type, member->value);
6504 static GList *
6505 sipe_get_access_control_menu(struct sipe_core_private *sipe_private,
6506 const char* uri);
6509 * A menu which appear when right-clicking on buddy in contact list.
6511 GList *
6512 sipe_buddy_menu(PurpleBuddy *buddy)
6514 PurpleBlistNode *g_node;
6515 PurpleGroup *group, *gr_parent;
6516 PurpleMenuAction *act;
6517 GList *menu = NULL;
6518 GList *menu_groups = NULL;
6519 struct sipe_core_private *sipe_private = PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE;
6520 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
6521 const char *email;
6522 const char *phone;
6523 const char *phone_disp_str;
6524 gchar *self = sip_uri_self(sipe_private);
6526 SIPE_SESSION_FOREACH {
6527 if (!sipe_strcase_equal(self, buddy->name) && session->chat_title && session->backend_session)
6529 if (sipe_backend_chat_find(session->backend_session, buddy->name))
6531 gboolean conf_op = sipe_backend_chat_is_operator(session->backend_session, self);
6533 if (session->focus_uri
6534 && !sipe_backend_chat_is_operator(session->backend_session, buddy->name) /* Not conf OP */
6535 && conf_op) /* We are a conf OP */
6537 gchar *label = g_strdup_printf(_("Make leader of '%s'"), session->chat_title);
6538 act = purple_menu_action_new(label,
6539 PURPLE_CALLBACK(sipe_buddy_menu_chat_make_leader_cb),
6540 session->chat_title, NULL);
6541 g_free(label);
6542 menu = g_list_prepend(menu, act);
6545 if (session->focus_uri
6546 && conf_op) /* We are a conf OP */
6548 gchar *label = g_strdup_printf(_("Remove from '%s'"), session->chat_title);
6549 act = purple_menu_action_new(label,
6550 PURPLE_CALLBACK(sipe_buddy_menu_chat_remove_cb),
6551 session->chat_title, NULL);
6552 g_free(label);
6553 menu = g_list_prepend(menu, act);
6556 else
6558 if (!session->focus_uri
6559 || (session->focus_uri && !session->locked))
6561 gchar *label = g_strdup_printf(_("Invite to '%s'"), session->chat_title);
6562 act = purple_menu_action_new(label,
6563 PURPLE_CALLBACK(sipe_buddy_menu_chat_invite_cb),
6564 session->chat_title, NULL);
6565 g_free(label);
6566 menu = g_list_prepend(menu, act);
6570 } SIPE_SESSION_FOREACH_END;
6572 act = purple_menu_action_new(_("New chat"),
6573 PURPLE_CALLBACK(sipe_buddy_menu_chat_new_cb),
6574 NULL, NULL);
6575 menu = g_list_prepend(menu, act);
6577 if (sip->csta && !sip->csta->line_status) {
6578 gchar *tmp = NULL;
6579 /* work phone */
6580 phone = purple_blist_node_get_string(&buddy->node, PHONE_PROP);
6581 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_DISPLAY_PROP);
6582 if (phone) {
6583 gchar *label = g_strdup_printf(_("Work %s"),
6584 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
6585 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
6586 g_free(tmp);
6587 tmp = NULL;
6588 g_free(label);
6589 menu = g_list_prepend(menu, act);
6592 /* mobile phone */
6593 phone = purple_blist_node_get_string(&buddy->node, PHONE_MOBILE_PROP);
6594 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_MOBILE_DISPLAY_PROP);
6595 if (phone) {
6596 gchar *label = g_strdup_printf(_("Mobile %s"),
6597 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
6598 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
6599 g_free(tmp);
6600 tmp = NULL;
6601 g_free(label);
6602 menu = g_list_prepend(menu, act);
6605 /* home phone */
6606 phone = purple_blist_node_get_string(&buddy->node, PHONE_HOME_PROP);
6607 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_HOME_DISPLAY_PROP);
6608 if (phone) {
6609 gchar *label = g_strdup_printf(_("Home %s"),
6610 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
6611 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
6612 g_free(tmp);
6613 tmp = NULL;
6614 g_free(label);
6615 menu = g_list_prepend(menu, act);
6618 /* other phone */
6619 phone = purple_blist_node_get_string(&buddy->node, PHONE_OTHER_PROP);
6620 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_OTHER_DISPLAY_PROP);
6621 if (phone) {
6622 gchar *label = g_strdup_printf(_("Other %s"),
6623 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
6624 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
6625 g_free(tmp);
6626 tmp = NULL;
6627 g_free(label);
6628 menu = g_list_prepend(menu, act);
6631 /* custom1 phone */
6632 phone = purple_blist_node_get_string(&buddy->node, PHONE_CUSTOM1_PROP);
6633 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_CUSTOM1_DISPLAY_PROP);
6634 if (phone) {
6635 gchar *label = g_strdup_printf(_("Custom1 %s"),
6636 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
6637 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
6638 g_free(tmp);
6639 tmp = NULL;
6640 g_free(label);
6641 menu = g_list_prepend(menu, act);
6645 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
6646 if (email) {
6647 act = purple_menu_action_new(_("Send email..."),
6648 PURPLE_CALLBACK(sipe_buddy_menu_send_email_cb),
6649 NULL, NULL);
6650 menu = g_list_prepend(menu, act);
6653 /* Access Level */
6654 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
6655 GList *menu_access_levels = sipe_get_access_control_menu(sipe_private, buddy->name);
6657 act = purple_menu_action_new(_("Access level"),
6658 NULL,
6659 NULL, menu_access_levels);
6660 menu = g_list_prepend(menu, act);
6663 /* Copy to */
6664 gr_parent = purple_buddy_get_group(buddy);
6665 for (g_node = purple_blist_get_root(); g_node; g_node = g_node->next) {
6666 if (g_node->type != PURPLE_BLIST_GROUP_NODE)
6667 continue;
6669 group = (PurpleGroup *)g_node;
6670 if (group == gr_parent)
6671 continue;
6673 if (purple_find_buddy_in_group(buddy->account, buddy->name, group))
6674 continue;
6676 act = purple_menu_action_new(purple_group_get_name(group),
6677 PURPLE_CALLBACK(sipe_buddy_menu_copy_to_cb),
6678 group->name, NULL);
6679 menu_groups = g_list_prepend(menu_groups, act);
6681 menu_groups = g_list_reverse(menu_groups);
6683 act = purple_menu_action_new(_("Copy to"),
6684 NULL,
6685 NULL, menu_groups);
6686 menu = g_list_prepend(menu, act);
6688 menu = g_list_reverse(menu);
6690 g_free(self);
6691 return menu;
6694 static void
6695 sipe_ask_access_domain_cb(PurpleConnection *gc, PurpleRequestFields *fields)
6697 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
6698 const char *domain = purple_request_fields_get_string(fields, "access_domain");
6699 int index = purple_request_fields_get_choice(fields, "container_id");
6700 /* move Blocked first */
6701 int i = (index == 4) ? 0 : index + 1;
6702 int container_id = containers[i];
6704 SIPE_DEBUG_INFO("sipe_ask_access_domain_cb: domain=%s, container_id=(%d)%d", domain ? domain : "", index, container_id);
6706 sipe_change_access_level(sipe_private, container_id, "domain", domain);
6709 static void
6710 sipe_ask_access_domain(struct sipe_core_private *sipe_private)
6712 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
6713 PurpleAccount *account = sip->account;
6714 PurpleConnection *gc = sip->gc;
6715 PurpleRequestFields *fields;
6716 PurpleRequestFieldGroup *g;
6717 PurpleRequestField *f;
6719 fields = purple_request_fields_new();
6721 g = purple_request_field_group_new(NULL);
6722 f = purple_request_field_string_new("access_domain", _("Domain"), "partner-company.com", FALSE);
6723 purple_request_field_set_required(f, TRUE);
6724 purple_request_field_group_add_field(g, f);
6726 f = purple_request_field_choice_new("container_id", _("Access level"), 0);
6727 purple_request_field_choice_add(f, _("Personal")); /* index 0 */
6728 purple_request_field_choice_add(f, _("Team"));
6729 purple_request_field_choice_add(f, _("Company"));
6730 purple_request_field_choice_add(f, _("Public"));
6731 purple_request_field_choice_add(f, _("Blocked")); /* index 4 */
6732 purple_request_field_choice_set_default_value(f, 3); /* index */
6733 purple_request_field_set_required(f, TRUE);
6734 purple_request_field_group_add_field(g, f);
6736 purple_request_fields_add_group(fields, g);
6738 purple_request_fields(gc, _("Add new domain"),
6739 _("Add new domain"), NULL, fields,
6740 _("Add"), G_CALLBACK(sipe_ask_access_domain_cb),
6741 _("Cancel"), NULL,
6742 account, NULL, NULL, gc);
6745 static void
6746 sipe_buddy_menu_access_level_add_domain_cb(PurpleBuddy *buddy)
6748 sipe_ask_access_domain(PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE);
6751 static GList *
6752 sipe_get_access_levels_menu(struct sipe_core_private *sipe_private,
6753 const char* member_type,
6754 const char* member_value,
6755 const gboolean extra_menu)
6757 GList *menu_access_levels = NULL;
6758 unsigned int i;
6759 char *menu_name;
6760 PurpleMenuAction *act;
6761 struct sipe_container *container;
6762 struct sipe_container_member *member;
6763 gboolean is_group_access = FALSE;
6764 int container_id = sipe_find_access_level(sipe_private, member_type, member_value, &is_group_access);
6766 for (i = 1; i <= CONTAINERS_LEN; i++) {
6767 /* to put Blocked level last in menu list.
6768 * Blocked should remaim in the first place in the containers[] array.
6770 unsigned int j = (i == CONTAINERS_LEN) ? 0 : i;
6771 const char *acc_level_name = sipe_get_access_level_name(containers[j]);
6773 container = g_new0(struct sipe_container, 1);
6774 member = g_new0(struct sipe_container_member, 1);
6775 container->id = containers[j];
6776 container->members = g_slist_append(container->members, member);
6777 member->type = g_strdup(member_type);
6778 member->value = g_strdup(member_value);
6780 /* current container/access level */
6781 if (((int)containers[j]) == container_id) {
6782 menu_name = is_group_access ?
6783 g_strdup_printf(INDENT_MARKED_INHERITED_FMT, acc_level_name) :
6784 g_strdup_printf(INDENT_MARKED_FMT, acc_level_name);
6785 } else {
6786 menu_name = g_strdup_printf(INDENT_FMT, acc_level_name);
6789 act = purple_menu_action_new(menu_name,
6790 PURPLE_CALLBACK(sipe_buddy_menu_access_level_cb),
6791 container, NULL);
6792 g_free(menu_name);
6793 menu_access_levels = g_list_prepend(menu_access_levels, act);
6796 if (extra_menu && (container_id >= 0)) {
6797 /* separator */
6798 act = purple_menu_action_new(" --------------", NULL, NULL, NULL);
6799 menu_access_levels = g_list_prepend(menu_access_levels, act);
6801 if (!is_group_access) {
6802 container = g_new0(struct sipe_container, 1);
6803 member = g_new0(struct sipe_container_member, 1);
6804 container->id = -1;
6805 container->members = g_slist_append(container->members, member);
6806 member->type = g_strdup(member_type);
6807 member->value = g_strdup(member_value);
6809 /* Translators: remove (clear) previously assigned access level */
6810 menu_name = g_strdup_printf(INDENT_FMT, _("Unspecify"));
6811 act = purple_menu_action_new(menu_name,
6812 PURPLE_CALLBACK(sipe_buddy_menu_access_level_cb),
6813 container, NULL);
6814 g_free(menu_name);
6815 menu_access_levels = g_list_prepend(menu_access_levels, act);
6819 menu_access_levels = g_list_reverse(menu_access_levels);
6820 return menu_access_levels;
6823 static GList *
6824 sipe_get_access_groups_menu(struct sipe_core_private *sipe_private)
6826 GList *menu_access_groups = NULL;
6827 PurpleMenuAction *act;
6828 GSList *access_domains = NULL;
6829 GSList *entry;
6830 char *menu_name;
6831 char *domain;
6833 act = purple_menu_action_new(_("People in my company"),
6834 NULL,
6835 NULL, sipe_get_access_levels_menu(sipe_private, "sameEnterprise", NULL, FALSE));
6836 menu_access_groups = g_list_prepend(menu_access_groups, act);
6838 /* this is original name, don't edit */
6839 act = purple_menu_action_new(_("People in domains connected with my company"),
6840 NULL,
6841 NULL, sipe_get_access_levels_menu(sipe_private, "federated", NULL, FALSE));
6842 menu_access_groups = g_list_prepend(menu_access_groups, act);
6844 act = purple_menu_action_new(_("People in public domains"),
6845 NULL,
6846 NULL, sipe_get_access_levels_menu(sipe_private, "publicCloud", NULL, TRUE));
6847 menu_access_groups = g_list_prepend(menu_access_groups, act);
6849 access_domains = sipe_get_access_domains(sipe_private);
6850 entry = access_domains;
6851 while (entry) {
6852 domain = entry->data;
6854 menu_name = g_strdup_printf(_("People at %s"), domain);
6855 act = purple_menu_action_new(menu_name,
6856 NULL,
6857 NULL, sipe_get_access_levels_menu(sipe_private, "domain", g_strdup(domain), TRUE));
6858 menu_access_groups = g_list_prepend(menu_access_groups, act);
6859 g_free(menu_name);
6861 entry = entry->next;
6864 /* separator */
6865 /* People in domains connected with my company */
6866 act = purple_menu_action_new("-------------------------------------------", NULL, NULL, NULL);
6867 menu_access_groups = g_list_prepend(menu_access_groups, act);
6869 act = purple_menu_action_new(_("Add new domain..."),
6870 PURPLE_CALLBACK(sipe_buddy_menu_access_level_add_domain_cb),
6871 NULL, NULL);
6872 menu_access_groups = g_list_prepend(menu_access_groups, act);
6874 menu_access_groups = g_list_reverse(menu_access_groups);
6876 return menu_access_groups;
6879 static GList *
6880 sipe_get_access_control_menu(struct sipe_core_private *sipe_private,
6881 const char* uri)
6883 GList *menu_access_levels = NULL;
6884 GList *menu_access_groups = NULL;
6885 char *menu_name;
6886 PurpleMenuAction *act;
6888 menu_access_levels = sipe_get_access_levels_menu(sipe_private, "user", sipe_get_no_sip_uri(uri), TRUE);
6890 menu_access_groups = sipe_get_access_groups_menu(sipe_private);
6892 menu_name = g_strdup_printf(INDENT_FMT, _("Access groups"));
6893 act = purple_menu_action_new(menu_name,
6894 NULL,
6895 NULL, menu_access_groups);
6896 g_free(menu_name);
6897 menu_access_levels = g_list_append(menu_access_levels, act);
6899 menu_name = g_strdup_printf(INDENT_FMT, _("Online help..."));
6900 act = purple_menu_action_new(menu_name,
6901 PURPLE_CALLBACK(sipe_buddy_menu_access_level_help_cb),
6902 NULL, NULL);
6903 g_free(menu_name);
6904 menu_access_levels = g_list_append(menu_access_levels, act);
6906 return menu_access_levels;
6909 static void
6910 sipe_conf_modify_lock(PurpleChat *chat, gboolean locked)
6912 struct sipe_core_private *sipe_private = PURPLE_CHAT_TO_SIPE_CORE_PRIVATE;
6913 struct sip_session *session;
6915 session = sipe_session_find_chat_by_title(sipe_private,
6916 (gchar *)g_hash_table_lookup(chat->components, "channel"));
6917 sipe_conf_modify_conference_lock(sipe_private, session, locked);
6920 static void
6921 sipe_chat_menu_unlock_cb(PurpleChat *chat)
6923 SIPE_DEBUG_INFO_NOFORMAT("sipe_chat_menu_unlock_cb() called");
6924 sipe_conf_modify_lock(chat, FALSE);
6927 static void
6928 sipe_chat_menu_lock_cb(PurpleChat *chat)
6930 SIPE_DEBUG_INFO_NOFORMAT("sipe_chat_menu_lock_cb() called");
6931 sipe_conf_modify_lock(chat, TRUE);
6934 GList *
6935 sipe_chat_menu(PurpleChat *chat)
6937 PurpleMenuAction *act;
6938 GList *menu = NULL;
6939 struct sipe_core_private *sipe_private = PURPLE_CHAT_TO_SIPE_CORE_PRIVATE;
6940 struct sip_session *session;
6941 gchar *self;
6943 session = sipe_session_find_chat_by_title(sipe_private,
6944 (gchar *)g_hash_table_lookup(chat->components, "channel"));
6945 if (!session) return NULL;
6947 self = sip_uri_self(sipe_private);
6949 if (session->focus_uri &&
6950 sipe_backend_chat_is_operator(session->backend_session, self))
6952 if (session->locked) {
6953 act = purple_menu_action_new(_("Unlock"),
6954 PURPLE_CALLBACK(sipe_chat_menu_unlock_cb),
6955 NULL, NULL);
6956 menu = g_list_prepend(menu, act);
6957 } else {
6958 act = purple_menu_action_new(_("Lock"),
6959 PURPLE_CALLBACK(sipe_chat_menu_lock_cb),
6960 NULL, NULL);
6961 menu = g_list_prepend(menu, act);
6965 menu = g_list_reverse(menu);
6967 g_free(self);
6968 return menu;
6971 static gboolean
6972 process_get_info_response(struct sipe_core_private *sipe_private,
6973 struct sipmsg *msg, struct transaction *trans)
6975 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
6976 char *uri = trans->payload->data;
6978 PurpleNotifyUserInfo *info;
6979 PurpleBuddy *pbuddy = NULL;
6980 struct sipe_buddy *sbuddy;
6981 const char *alias = NULL;
6982 char *device_name = NULL;
6983 char *server_alias = NULL;
6984 char *phone_number = NULL;
6985 char *email = NULL;
6986 const char *site;
6987 char *first_name = NULL;
6988 char *last_name = NULL;
6990 if (!sip) return FALSE;
6992 SIPE_DEBUG_INFO("Fetching %s's user info for %s", uri, sipe_private->username);
6994 pbuddy = purple_find_buddy((PurpleAccount *)sip->account, uri);
6995 alias = purple_buddy_get_local_alias(pbuddy);
6997 //will query buddy UA's capabilities and send answer to log
6998 sipe_options_request(sipe_private, uri);
7000 sbuddy = g_hash_table_lookup(sipe_private->buddies, uri);
7001 if (sbuddy) {
7002 device_name = sbuddy->device_name ? g_strdup(sbuddy->device_name) : NULL;
7005 info = purple_notify_user_info_new();
7007 if (msg->response != 200) {
7008 SIPE_DEBUG_INFO("process_get_info_response: SERVICE response is %d", msg->response);
7009 } else {
7010 sipe_xml *searchResults;
7011 const sipe_xml *mrow;
7013 SIPE_DEBUG_INFO("process_get_info_response: body:\n%s", msg->body ? msg->body : "");
7014 searchResults = sipe_xml_parse(msg->body, msg->bodylen);
7015 if (!searchResults) {
7016 SIPE_DEBUG_INFO_NOFORMAT("process_get_info_response: no parseable searchResults");
7017 } else if ((mrow = sipe_xml_child(searchResults, "Body/Array/row"))) {
7018 const char *value;
7019 server_alias = g_strdup(sipe_xml_attribute(mrow, "displayName"));
7020 email = g_strdup(sipe_xml_attribute(mrow, "email"));
7021 phone_number = g_strdup(sipe_xml_attribute(mrow, "phone"));
7023 /* For 2007 system we will take this from ContactCard -
7024 * it has cleaner tel: URIs at least
7026 if (!SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
7027 char *tel_uri = sip_to_tel_uri(phone_number);
7028 /* trims its parameters, so call first */
7029 sipe_update_user_info(sipe_private, uri, ALIAS_PROP, server_alias);
7030 sipe_update_user_info(sipe_private, uri, EMAIL_PROP, email);
7031 sipe_update_user_info(sipe_private, uri, PHONE_PROP, tel_uri);
7032 sipe_update_user_info(sipe_private, uri, PHONE_DISPLAY_PROP, phone_number);
7033 g_free(tel_uri);
7036 if (server_alias && strlen(server_alias) > 0) {
7037 purple_notify_user_info_add_pair(info, _("Display name"), server_alias);
7039 if ((value = sipe_xml_attribute(mrow, "title")) && strlen(value) > 0) {
7040 purple_notify_user_info_add_pair(info, _("Job title"), value);
7042 if ((value = sipe_xml_attribute(mrow, "office")) && strlen(value) > 0) {
7043 purple_notify_user_info_add_pair(info, _("Office"), value);
7045 if (phone_number && strlen(phone_number) > 0) {
7046 purple_notify_user_info_add_pair(info, _("Business phone"), phone_number);
7048 if ((value = sipe_xml_attribute(mrow, "company")) && strlen(value) > 0) {
7049 purple_notify_user_info_add_pair(info, _("Company"), value);
7051 if ((value = sipe_xml_attribute(mrow, "city")) && strlen(value) > 0) {
7052 purple_notify_user_info_add_pair(info, _("City"), value);
7054 if ((value = sipe_xml_attribute(mrow, "state")) && strlen(value) > 0) {
7055 purple_notify_user_info_add_pair(info, _("State"), value);
7057 if ((value = sipe_xml_attribute(mrow, "country")) && strlen(value) > 0) {
7058 purple_notify_user_info_add_pair(info, _("Country"), value);
7060 if (email && strlen(email) > 0) {
7061 purple_notify_user_info_add_pair(info, _("Email address"), email);
7065 sipe_xml_free(searchResults);
7068 purple_notify_user_info_add_section_break(info);
7070 if (is_empty(server_alias)) {
7071 g_free(server_alias);
7072 server_alias = g_strdup(purple_buddy_get_server_alias(pbuddy));
7073 if (server_alias) {
7074 purple_notify_user_info_add_pair(info, _("Display name"), server_alias);
7078 /* present alias if it differs from server alias */
7079 if (alias && !sipe_strequal(alias, server_alias))
7081 purple_notify_user_info_add_pair(info, _("Alias"), alias);
7084 if (is_empty(email)) {
7085 g_free(email);
7086 email = g_strdup(purple_blist_node_get_string(&pbuddy->node, EMAIL_PROP));
7087 if (email) {
7088 purple_notify_user_info_add_pair(info, _("Email address"), email);
7092 site = purple_blist_node_get_string(&pbuddy->node, SITE_PROP);
7093 if (site) {
7094 purple_notify_user_info_add_pair(info, _("Site"), site);
7097 sipe_get_first_last_names(sipe_private, uri, &first_name, &last_name);
7098 if (first_name && last_name) {
7099 char *link = g_strconcat("http://www.linkedin.com/pub/dir/", first_name, "/", last_name, NULL);
7101 purple_notify_user_info_add_pair(info, _("Find on LinkedIn"), link);
7102 g_free(link);
7104 g_free(first_name);
7105 g_free(last_name);
7107 if (device_name) {
7108 purple_notify_user_info_add_pair(info, _("Device"), device_name);
7111 /* show a buddy's user info in a nice dialog box */
7112 purple_notify_userinfo(sip->gc, /* connection the buddy info came through */
7113 uri, /* buddy's URI */
7114 info, /* body */
7115 NULL, /* callback called when dialog closed */
7116 NULL); /* userdata for callback */
7118 g_free(phone_number);
7119 g_free(server_alias);
7120 g_free(email);
7121 g_free(device_name);
7123 return TRUE;
7127 * AD search first, LDAP based
7129 void sipe_get_info(PurpleConnection *gc, const char *username)
7131 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
7132 gchar *domain_uri = sip_uri_from_name(sipe_private->public.sip_domain);
7133 char *row = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, "msRTCSIP-PrimaryUserAddress", username);
7134 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 1, row);
7135 struct transaction_payload *payload = g_new0(struct transaction_payload, 1);
7137 payload->destroy = g_free;
7138 payload->data = g_strdup(username);
7140 SIPE_DEBUG_INFO("sipe_get_contact_data: body:\n%s", body ? body : "");
7141 send_soap_request_with_cb(sipe_private, domain_uri, body,
7142 process_get_info_response, payload);
7143 g_free(domain_uri);
7144 g_free(body);
7145 g_free(row);
7149 Local Variables:
7150 mode: c
7151 c-file-style: "bsd"
7152 indent-tabs-mode: t
7153 tab-width: 8
7154 End: