Fix sipe_buddy_menu_copy_to_cb when copying buddy not yet in our list
[siplcs.git] / src / core / sipe.c
blobc764a0f7ff7f80a24f2201eb8b9d6c09c4340bfb
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-groupchat.h"
80 #include "sipe-mime.h"
81 #include "sipe-nls.h"
82 #include "sipe-schedule.h"
83 #include "sipe-session.h"
84 #include "sipe-subscriptions.h"
85 #ifdef HAVE_VV
86 #include "sipe-media.h"
87 #endif
88 #include "sipe-utils.h"
89 #include "sipe-xml.h"
90 #include "uuid.h"
91 #include "sipe.h"
93 #define SIPE_IDLE_SET_DELAY 1 /* 1 sec */
95 #define UPDATE_CALENDAR_DELAY 1*60 /* 1 min */
96 #define UPDATE_CALENDAR_INTERVAL 30*60 /* 30 min */
98 /* Status identifiers (see also: sipe_status_types()) */
99 #define SIPE_STATUS_ID_UNKNOWN purple_primitive_get_id_from_type(PURPLE_STATUS_UNSET) /* Unset (primitive) */
100 #define SIPE_STATUS_ID_OFFLINE purple_primitive_get_id_from_type(PURPLE_STATUS_OFFLINE) /* Offline (primitive) */
101 #define SIPE_STATUS_ID_AVAILABLE purple_primitive_get_id_from_type(PURPLE_STATUS_AVAILABLE) /* Online */
102 /* PURPLE_STATUS_UNAVAILABLE: */
103 #define SIPE_STATUS_ID_BUSY "busy" /* Busy */
104 #define SIPE_STATUS_ID_BUSYIDLE "busyidle" /* BusyIdle */
105 #define SIPE_STATUS_ID_DND "do-not-disturb" /* Do Not Disturb */
106 #define SIPE_STATUS_ID_IN_MEETING "in-a-meeting" /* In a meeting */
107 #define SIPE_STATUS_ID_IN_CONF "in-a-conference" /* In a conference */
108 #define SIPE_STATUS_ID_ON_PHONE "on-the-phone" /* On the phone */
109 #define SIPE_STATUS_ID_INVISIBLE purple_primitive_get_id_from_type(PURPLE_STATUS_INVISIBLE) /* Appear Offline */
110 /* PURPLE_STATUS_AWAY: */
111 #define SIPE_STATUS_ID_IDLE "idle" /* Idle/Inactive */
112 #define SIPE_STATUS_ID_BRB "be-right-back" /* Be Right Back */
113 #define SIPE_STATUS_ID_AWAY purple_primitive_get_id_from_type(PURPLE_STATUS_AWAY) /* Away (primitive) */
114 /** Reuters status (user settable) */
115 #define SIPE_STATUS_ID_LUNCH "out-to-lunch" /* Out To Lunch */
116 /* ??? PURPLE_STATUS_EXTENDED_AWAY */
117 /* ??? PURPLE_STATUS_MOBILE */
118 /* ??? PURPLE_STATUS_TUNE */
120 /* Status attributes (see also sipe_status_types() */
121 #define SIPE_STATUS_ATTR_ID_MESSAGE "message"
123 static struct sipe_activity_map_struct
125 sipe_activity type;
126 const char *token;
127 const char *desc;
128 const char *status_id;
130 } const sipe_activity_map[] =
132 /* This has nothing to do with Availability numbers, like 3500 (online).
133 * Just a mapping of Communicator Activities to Purple statuses to be able display them in Pidgin.
135 { SIPE_ACTIVITY_UNSET, "unset", NULL , NULL },
136 { SIPE_ACTIVITY_ONLINE, "online", NULL , NULL },
137 { SIPE_ACTIVITY_INACTIVE, SIPE_STATUS_ID_IDLE, N_("Inactive") , NULL },
138 { SIPE_ACTIVITY_BUSY, SIPE_STATUS_ID_BUSY, N_("Busy") , SIPE_STATUS_ID_BUSY },
139 { SIPE_ACTIVITY_BUSYIDLE, SIPE_STATUS_ID_BUSYIDLE, N_("Busy-Idle") , NULL },
140 { SIPE_ACTIVITY_DND, SIPE_STATUS_ID_DND, NULL , SIPE_STATUS_ID_DND },
141 { SIPE_ACTIVITY_BRB, SIPE_STATUS_ID_BRB, N_("Be right back") , SIPE_STATUS_ID_BRB },
142 { SIPE_ACTIVITY_AWAY, "away", NULL , NULL },
143 { SIPE_ACTIVITY_LUNCH, SIPE_STATUS_ID_LUNCH, N_("Out to lunch") , NULL },
144 { SIPE_ACTIVITY_OFFLINE, "offline", NULL , NULL },
145 { SIPE_ACTIVITY_ON_PHONE, SIPE_STATUS_ID_ON_PHONE, N_("In a call") , NULL },
146 { SIPE_ACTIVITY_IN_CONF, SIPE_STATUS_ID_IN_CONF, N_("In a conference") , NULL },
147 { SIPE_ACTIVITY_IN_MEETING, SIPE_STATUS_ID_IN_MEETING, N_("In a meeting") , NULL },
148 { SIPE_ACTIVITY_OOF, "out-of-office", N_("Out of office") , NULL },
149 { SIPE_ACTIVITY_URGENT_ONLY, "urgent-interruptions-only", N_("Urgent interruptions only") , NULL }
151 /** @param x is sipe_activity */
152 #define SIPE_ACTIVITY_I18N(x) gettext(sipe_activity_map[x].desc)
154 static sipe_activity
155 sipe_get_activity_by_token(const char *token)
157 int i;
159 for (i = 0; i < SIPE_ACTIVITY_NUM_TYPES; i++)
161 if (sipe_strequal(token, sipe_activity_map[i].token))
162 return sipe_activity_map[i].type;
165 return sipe_activity_map[0].type;
168 static const char *
169 sipe_get_activity_desc_by_token(const char *token)
171 if (!token) return NULL;
173 return SIPE_ACTIVITY_I18N(sipe_get_activity_by_token(token));
176 static void send_presence_status(struct sipe_core_private *sipe_private,
177 void *unused);
180 * @param from0 from URI (with 'sip:' prefix). Will be filled with self-URI if NULL passed.
182 static void
183 send_soap_request_with_cb(struct sipe_core_private *sipe_private,
184 gchar *from0,
185 gchar *body,
186 TransCallback callback,
187 struct transaction_payload *payload)
189 gchar *from = from0 ? g_strdup(from0) : sip_uri_self(sipe_private);
190 gchar *contact = get_contact(sipe_private);
191 gchar *hdr = g_strdup_printf("Contact: %s\r\n"
192 "Content-Type: application/SOAP+xml\r\n",contact);
194 struct transaction *trans = sip_transport_service(sipe_private,
195 from,
196 hdr,
197 body,
198 callback);
199 trans->payload = payload;
201 g_free(from);
202 g_free(contact);
203 g_free(hdr);
206 static void send_soap_request(struct sipe_core_private *sipe_private,
207 gchar *body)
209 send_soap_request_with_cb(sipe_private, NULL, body, NULL, NULL);
213 * Returns pointer to URI without sip: prefix if any
215 * @param sip_uri SIP URI possibly with sip: prefix. Example: sip:first.last@hq.company.com
216 * @return pointer to URL without sip: prefix. Coresponding example: first.last@hq.company.com
218 * Doesn't allocate memory
220 static const char *
221 sipe_get_no_sip_uri(const char *sip_uri)
223 const char *prefix = "sip:";
224 if (!sip_uri) return NULL;
226 if (g_str_has_prefix(sip_uri, prefix)) {
227 return (sip_uri+strlen(prefix));
228 } else {
229 return sip_uri;
233 static void
234 sipe_contact_set_acl (struct sipe_core_private *sipe_private,
235 const gchar *who,
236 gchar *rights)
238 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
239 gchar * body = g_strdup_printf(SIPE_SOAP_ALLOW_DENY, who, rights, sip->acl_delta++);
240 send_soap_request(sipe_private, body);
241 g_free(body);
244 static void
245 sipe_change_access_level(struct sipe_core_private *sipe_private,
246 const int container_id,
247 const gchar *type,
248 const gchar *value);
250 void
251 sipe_core_contact_allow_deny (struct sipe_core_public *sipe_public,
252 const gchar * who, gboolean allow)
254 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
256 if (allow) {
257 SIPE_DEBUG_INFO("Authorizing contact %s", who);
258 } else {
259 SIPE_DEBUG_INFO("Blocking contact %s", who);
262 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
263 sipe_change_access_level(sipe_private, (allow ? -1 : 32000), "user", sipe_get_no_sip_uri(who));
264 } else {
265 sipe_contact_set_acl(sipe_private, who, allow ? "AA" : "BD");
269 static
270 void sipe_auth_user_cb(void * data)
272 struct sipe_auth_job * job = (struct sipe_auth_job *) data;
273 if (!job) return;
275 sipe_core_contact_allow_deny((struct sipe_core_public *)job->sipe_private, job->who, TRUE);
276 g_free(job);
279 static
280 void sipe_deny_user_cb(void * data)
282 struct sipe_auth_job * job = (struct sipe_auth_job *) data;
283 if (!job) return;
285 sipe_core_contact_allow_deny((struct sipe_core_public *)job->sipe_private, job->who, FALSE);
286 g_free(job);
289 /** @applicable: 2005-
291 static void
292 sipe_process_presence_wpending (struct sipe_core_private *sipe_private,
293 struct sipmsg * msg)
295 sipe_xml *watchers;
296 const sipe_xml *watcher;
297 // Ensure it's either not a response (eg it's a BENOTIFY) or that it's a 200 OK response
298 if (msg->response != 0 && msg->response != 200) return;
300 if (msg->bodylen == 0 || msg->body == NULL || sipe_strequal(sipmsg_find_header(msg, "Event"), "msrtc.wpending")) return;
302 watchers = sipe_xml_parse(msg->body, msg->bodylen);
303 if (!watchers) return;
305 for (watcher = sipe_xml_child(watchers, "watcher"); watcher; watcher = sipe_xml_twin(watcher)) {
306 gchar * remote_user = g_strdup(sipe_xml_attribute(watcher, "uri"));
307 gchar * alias = g_strdup(sipe_xml_attribute(watcher, "displayName"));
308 gboolean on_list = g_hash_table_lookup(sipe_private->buddies, remote_user) != NULL;
310 // TODO pull out optional displayName to pass as alias
311 if (remote_user) {
312 struct sipe_auth_job * job = g_new0(struct sipe_auth_job, 1);
313 job->who = remote_user;
314 job->sipe_private = sipe_private;
315 sipe_backend_buddy_request_authorization(SIPE_CORE_PUBLIC,
316 remote_user,
317 alias,
318 on_list,
319 sipe_auth_user_cb,
320 sipe_deny_user_cb,
321 (gpointer)job);
326 sipe_xml_free(watchers);
327 return;
330 static void
331 sipe_group_add(struct sipe_core_private *sipe_private,
332 struct sipe_group * group)
334 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
335 if (sipe_backend_buddy_group_add(SIPE_CORE_PUBLIC,group->name))
337 SIPE_DEBUG_INFO("added group %s (id %d)", group->name, group->id);
338 sip->groups = g_slist_append(sip->groups, group);
340 else
342 SIPE_DEBUG_INFO("did not add group %s", group->name ? group->name : "");
346 static struct sipe_group *sipe_group_find_by_id(struct sipe_core_private *sipe_private,
347 int id)
349 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
350 struct sipe_group *group;
351 GSList *entry;
352 if (sip == NULL) {
353 return NULL;
356 entry = sip->groups;
357 while (entry) {
358 group = entry->data;
359 if (group->id == id) {
360 return group;
362 entry = entry->next;
364 return NULL;
367 static struct sipe_group *sipe_group_find_by_name(struct sipe_core_private *sipe_private,
368 const gchar * name)
370 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
371 struct sipe_group *group;
372 GSList *entry;
373 if (!sip || !name) {
374 return NULL;
377 entry = sip->groups;
378 while (entry) {
379 group = entry->data;
380 if (sipe_strequal(group->name, name)) {
381 return group;
383 entry = entry->next;
385 return NULL;
388 static void
389 sipe_group_rename(struct sipe_core_private *sipe_private,
390 struct sipe_group *group,
391 gchar *name)
393 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
394 gchar *body;
395 SIPE_DEBUG_INFO("Renaming group %s to %s", group->name, name);
396 body = g_markup_printf_escaped(SIPE_SOAP_MOD_GROUP, group->id, name, sip->contacts_delta++);
397 send_soap_request(sipe_private, body);
398 g_free(body);
399 g_free(group->name);
400 group->name = g_strdup(name);
404 * Only appends if no such value already stored.
405 * Like Set in Java.
407 GSList * slist_insert_unique_sorted(GSList *list, gpointer data, GCompareFunc func) {
408 GSList * res = list;
409 if (!g_slist_find_custom(list, data, func)) {
410 res = g_slist_insert_sorted(list, data, func);
412 return res;
415 static int
416 sipe_group_compare(struct sipe_group *group1, struct sipe_group *group2) {
417 return group1->id - group2->id;
421 * Returns string like "2 4 7 8" - group ids buddy belong to.
423 static gchar *
424 sipe_get_buddy_groups_string (struct sipe_buddy *buddy) {
425 int i = 0;
426 gchar *res;
427 //creating array from GList, converting int to gchar*
428 gchar **ids_arr = g_new(gchar *, g_slist_length(buddy->groups) + 1);
429 GSList *entry = buddy->groups;
431 if (!ids_arr) return NULL;
433 while (entry) {
434 struct sipe_group * group = entry->data;
435 ids_arr[i] = g_strdup_printf("%d", group->id);
436 entry = entry->next;
437 i++;
439 ids_arr[i] = NULL;
440 res = g_strjoinv(" ", ids_arr);
441 g_strfreev(ids_arr);
442 return res;
446 * Sends buddy update to server
448 void
449 sipe_core_group_set_user(struct sipe_core_public *sipe_public, const gchar * who)
451 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA;
452 struct sipe_buddy *buddy = g_hash_table_lookup(SIPE_CORE_PRIVATE->buddies, who);
453 sipe_backend_buddy backend_buddy = sipe_backend_buddy_find(sipe_public, who, NULL);
455 if (buddy && backend_buddy) {
456 gchar *alias = sipe_backend_buddy_get_alias(sipe_public, backend_buddy);
457 gchar *groups = sipe_get_buddy_groups_string(buddy);
458 if (groups) {
459 gchar *body;
460 SIPE_DEBUG_INFO("Saving buddy %s with alias %s and groups %s", who, alias, groups);
462 body = g_markup_printf_escaped(SIPE_SOAP_SET_CONTACT,
463 alias, groups, "true", buddy->name, sip->contacts_delta++
465 send_soap_request(SIPE_CORE_PRIVATE, body);
466 g_free(groups);
467 g_free(body);
469 g_free(alias);
473 static gboolean process_add_group_response(struct sipe_core_private *sipe_private,
474 struct sipmsg *msg,
475 struct transaction *trans)
477 if (msg->response == 200) {
478 struct sipe_group *group;
479 struct group_user_context *ctx = trans->payload->data;
480 sipe_xml *xml;
481 const sipe_xml *node;
482 char *group_id;
483 struct sipe_buddy *buddy;
485 xml = sipe_xml_parse(msg->body, msg->bodylen);
486 if (!xml) {
487 return FALSE;
490 node = sipe_xml_child(xml, "Body/addGroup/groupID");
491 if (!node) {
492 sipe_xml_free(xml);
493 return FALSE;
496 group_id = sipe_xml_data(node);
497 if (!group_id) {
498 sipe_xml_free(xml);
499 return FALSE;
502 group = g_new0(struct sipe_group, 1);
503 group->id = (int)g_ascii_strtod(group_id, NULL);
504 g_free(group_id);
505 group->name = g_strdup(ctx->group_name);
507 sipe_group_add(sipe_private, group);
509 if (ctx->user_name) {
510 buddy = g_hash_table_lookup(sipe_private->buddies, ctx->user_name);
511 if (buddy) {
512 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
515 sipe_core_group_set_user(SIPE_CORE_PUBLIC, ctx->user_name);
518 sipe_xml_free(xml);
519 return TRUE;
521 return FALSE;
524 static void sipe_group_context_destroy(gpointer data)
526 struct group_user_context *ctx = data;
527 g_free(ctx->group_name);
528 g_free(ctx->user_name);
529 g_free(ctx);
532 static void sipe_group_create (struct sipe_core_private *sipe_private,
533 const gchar *name, const gchar * who)
535 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
536 struct transaction_payload *payload = g_new0(struct transaction_payload, 1);
537 struct group_user_context *ctx = g_new0(struct group_user_context, 1);
538 const gchar *soap_name = sipe_strequal(name, _("Other Contacts")) ? "~" : name;
539 gchar *body;
540 ctx->group_name = g_strdup(name);
541 ctx->user_name = g_strdup(who);
542 payload->destroy = sipe_group_context_destroy;
543 payload->data = ctx;
545 body = g_markup_printf_escaped(SIPE_SOAP_ADD_GROUP, soap_name, sip->contacts_delta++);
546 send_soap_request_with_cb(sipe_private, NULL, body, process_add_group_response, payload);
547 g_free(body);
550 static void
551 sipe_sched_calendar_status_update(struct sipe_core_private *sipe_private,
552 time_t calculate_from);
554 static int
555 sipe_get_availability_by_status(const char* sipe_status_id, char** activity_token);
557 static const char*
558 sipe_get_status_by_availability(int avail,
559 char** activity);
561 static void
562 sipe_set_purple_account_status_and_note(const PurpleAccount *account,
563 const char *status_id,
564 const char *message,
565 time_t do_not_publish[]);
567 static void
568 sipe_apply_calendar_status(struct sipe_core_private *sipe_private,
569 struct sipe_buddy *sbuddy,
570 const char *status_id)
572 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
573 time_t cal_avail_since;
574 int cal_status = sipe_cal_get_status(sbuddy, time(NULL), &cal_avail_since);
575 int avail;
576 gchar *self_uri;
578 if (!sbuddy) return;
580 if (cal_status < SIPE_CAL_NO_DATA) {
581 SIPE_DEBUG_INFO("sipe_apply_calendar_status: cal_status : %d for %s", cal_status, sbuddy->name);
582 SIPE_DEBUG_INFO("sipe_apply_calendar_status: cal_avail_since : %s", asctime(localtime(&cal_avail_since)));
585 /* scheduled Cal update call */
586 if (!status_id) {
587 status_id = sbuddy->last_non_cal_status_id;
588 g_free(sbuddy->activity);
589 sbuddy->activity = g_strdup(sbuddy->last_non_cal_activity);
592 if (!status_id) {
593 SIPE_DEBUG_INFO("sipe_apply_calendar_status: status_id is NULL for %s, exiting.",
594 sbuddy->name ? sbuddy->name : "" );
595 return;
598 /* adjust to calendar status */
599 if (cal_status != SIPE_CAL_NO_DATA) {
600 SIPE_DEBUG_INFO("sipe_apply_calendar_status: user_avail_since: %s", asctime(localtime(&sbuddy->user_avail_since)));
602 if (cal_status == SIPE_CAL_BUSY
603 && cal_avail_since > sbuddy->user_avail_since
604 && 6500 >= sipe_get_availability_by_status(status_id, NULL))
606 status_id = SIPE_STATUS_ID_BUSY;
607 g_free(sbuddy->activity);
608 sbuddy->activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_IN_MEETING));
610 avail = sipe_get_availability_by_status(status_id, NULL);
612 SIPE_DEBUG_INFO("sipe_apply_calendar_status: activity_since : %s", asctime(localtime(&sbuddy->activity_since)));
613 if (cal_avail_since > sbuddy->activity_since) {
614 if (cal_status == SIPE_CAL_OOF
615 && avail >= 15000) /* 12000 in 2007 */
617 g_free(sbuddy->activity);
618 sbuddy->activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_OOF));
623 /* then set status_id actually */
624 SIPE_DEBUG_INFO("sipe_apply_calendar_status: to %s for %s", status_id, sbuddy->name ? sbuddy->name : "" );
625 sipe_backend_buddy_set_status(SIPE_CORE_PUBLIC, sbuddy->name, status_id);
627 /* set our account state to the one in roaming (including calendar info) */
628 self_uri = sip_uri_self(sipe_private);
629 if (sip->initial_state_published && sipe_strcase_equal(sbuddy->name, self_uri)) {
630 if (sipe_strequal(status_id, SIPE_STATUS_ID_OFFLINE)) {
631 status_id = g_strdup(SIPE_STATUS_ID_INVISIBLE); /* not not let offline status switch us off */
634 SIPE_DEBUG_INFO("sipe_apply_calendar_status: switch to '%s' for the account", sip->status);
635 sipe_set_purple_account_status_and_note(sip->account, status_id, sip->note, sip->do_not_publish);
637 g_free(self_uri);
640 void
641 sipe_core_buddy_got_status(struct sipe_core_public *sipe_public,
642 const gchar* uri,
643 const gchar *status_id)
645 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
646 struct sipe_buddy *sbuddy = g_hash_table_lookup(sipe_private->buddies, uri);
648 if (!sbuddy) return;
650 /* Check if on 2005 system contact's calendar,
651 * then set/preserve it.
653 if (!SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
654 sipe_apply_calendar_status(sipe_private, sbuddy, status_id);
655 } else {
656 sipe_backend_buddy_set_status(sipe_public, uri, status_id);
660 static void
661 update_calendar_status_cb(SIPE_UNUSED_PARAMETER char *name,
662 struct sipe_buddy *sbuddy,
663 struct sipe_core_private *sipe_private)
665 sipe_apply_calendar_status(sipe_private, sbuddy, NULL);
669 * Updates contact's status
670 * based on their calendar information.
672 * Applicability: 2005 systems
674 static void
675 update_calendar_status(struct sipe_core_private *sipe_private,
676 SIPE_UNUSED_PARAMETER void *unused)
678 SIPE_DEBUG_INFO_NOFORMAT("update_calendar_status() started.");
679 g_hash_table_foreach(sipe_private->buddies, (GHFunc)update_calendar_status_cb, sipe_private);
681 /* repeat scheduling */
682 sipe_sched_calendar_status_update(sipe_private, time(NULL) + 3*60 /* 3 min */);
686 * Schedules process of contacts' status update
687 * based on their calendar information.
688 * Should be scheduled to the beginning of every
689 * 15 min interval, like:
690 * 13:00, 13:15, 13:30, 13:45, etc.
692 * Applicability: 2005 systems
694 static void
695 sipe_sched_calendar_status_update(struct sipe_core_private *sipe_private,
696 time_t calculate_from)
698 int interval = 15*60;
699 /** start of the beginning of closest 15 min interval. */
700 time_t next_start = ((time_t)((int)((int)calculate_from)/interval + 1)*interval);
702 SIPE_DEBUG_INFO("sipe_sched_calendar_status_update: calculate_from time: %s",
703 asctime(localtime(&calculate_from)));
704 SIPE_DEBUG_INFO("sipe_sched_calendar_status_update: next start time : %s",
705 asctime(localtime(&next_start)));
707 sipe_schedule_seconds(sipe_private,
708 "<+2005-cal-status>",
709 NULL,
710 next_start - time(NULL),
711 update_calendar_status,
712 NULL);
716 * Schedules process of self status publish
717 * based on own calendar information.
718 * Should be scheduled to the beginning of every
719 * 15 min interval, like:
720 * 13:00, 13:15, 13:30, 13:45, etc.
722 * Applicability: 2007+ systems
724 static void
725 sipe_sched_calendar_status_self_publish(struct sipe_core_private *sipe_private,
726 time_t calculate_from)
728 int interval = 5*60;
729 /** start of the beginning of closest 5 min interval. */
730 time_t next_start = ((time_t)((int)((int)calculate_from)/interval + 1)*interval);
732 SIPE_DEBUG_INFO("sipe_sched_calendar_status_self_publish: calculate_from time: %s",
733 asctime(localtime(&calculate_from)));
734 SIPE_DEBUG_INFO("sipe_sched_calendar_status_self_publish: next start time : %s",
735 asctime(localtime(&next_start)));
737 sipe_schedule_seconds(sipe_private,
738 "<+2007-cal-status>",
739 NULL,
740 next_start - time(NULL),
741 publish_calendar_status_self,
742 NULL);
745 static void sipe_subscribe_resource_uri(const char *name,
746 SIPE_UNUSED_PARAMETER gpointer value,
747 gchar **resources_uri)
749 gchar *tmp = *resources_uri;
750 *resources_uri = g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp, name);
751 g_free(tmp);
754 static void sipe_subscribe_resource_uri_with_context(const char *name, gpointer value, gchar **resources_uri)
756 struct sipe_buddy *sbuddy = (struct sipe_buddy *)value;
757 gchar *context = sbuddy && sbuddy->just_added ? "><context/></resource>" : "/>";
758 gchar *tmp = *resources_uri;
760 if (sbuddy) sbuddy->just_added = FALSE; /* should be enought to include context one time */
762 *resources_uri = g_strdup_printf("%s<resource uri=\"%s\"%s\n", tmp, name, context);
763 g_free(tmp);
767 * Support for Batch Category SUBSCRIBE [MS-PRES] - msrtc-event-categories+xml OCS 2007
768 * Support for Batch Category SUBSCRIBE [MS-SIP] - adrl+xml LCS 2005
769 * The user sends an initial batched category SUBSCRIBE request against all contacts on his roaming list in only a request
770 * A batch category SUBSCRIBE request MUST have the same To-URI and From-URI.
771 * This header will be send only if adhoclist there is a "Supported: adhoclist" in REGISTER answer else will be send a Single Category SUBSCRIBE
774 static void sipe_subscribe_presence_batched_to(struct sipe_core_private *sipe_private,
775 gchar *resources_uri,
776 gchar *to)
778 gchar *contact = get_contact(sipe_private);
779 gchar *request;
780 gchar *content;
781 gchar *require = "";
782 gchar *accept = "";
783 gchar *autoextend = "";
784 gchar *content_type;
786 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
787 require = ", categoryList";
788 accept = ", application/msrtc-event-categories+xml, application/xpidf+xml, application/pidf+xml";
789 content_type = "application/msrtc-adrl-categorylist+xml";
790 content = g_strdup_printf(
791 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
792 "<action name=\"subscribe\" id=\"63792024\">\n"
793 "<adhocList>\n%s</adhocList>\n"
794 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
795 "<category name=\"calendarData\"/>\n"
796 "<category name=\"contactCard\"/>\n"
797 "<category name=\"note\"/>\n"
798 "<category name=\"state\"/>\n"
799 "</categoryList>\n"
800 "</action>\n"
801 "</batchSub>", sipe_private->username, resources_uri);
802 } else {
803 autoextend = "Supported: com.microsoft.autoextend\r\n";
804 content_type = "application/adrl+xml";
805 content = g_strdup_printf(
806 "<adhoclist xmlns=\"urn:ietf:params:xml:ns:adrl\" uri=\"sip:%s\" name=\"sip:%s\">\n"
807 "<create xmlns=\"\">\n%s</create>\n"
808 "</adhoclist>\n", sipe_private->username, sipe_private->username, resources_uri);
810 g_free(resources_uri);
812 request = g_strdup_printf(
813 "Require: adhoclist%s\r\n"
814 "Supported: eventlist\r\n"
815 "Accept: application/rlmi+xml, multipart/related, text/xml+msrtc.pidf%s\r\n"
816 "Supported: ms-piggyback-first-notify\r\n"
817 "%sSupported: ms-benotify\r\n"
818 "Proxy-Require: ms-benotify\r\n"
819 "Event: presence\r\n"
820 "Content-Type: %s\r\n"
821 "Contact: %s\r\n", require, accept, autoextend, content_type, contact);
822 g_free(contact);
824 sipe_subscribe_presence_buddy(sipe_private, to, request, content);
826 g_free(content);
827 g_free(to);
828 g_free(request);
831 static void sipe_subscribe_presence_batched(struct sipe_core_private *sipe_private,
832 SIPE_UNUSED_PARAMETER void *unused)
834 gchar *to = sip_uri_self(sipe_private);
835 gchar *resources_uri = g_strdup("");
836 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
837 g_hash_table_foreach(sipe_private->buddies, (GHFunc) sipe_subscribe_resource_uri_with_context , &resources_uri);
838 } else {
839 g_hash_table_foreach(sipe_private->buddies, (GHFunc) sipe_subscribe_resource_uri, &resources_uri);
842 sipe_subscribe_presence_batched_to(sipe_private, resources_uri, to);
845 struct presence_batched_routed {
846 gchar *host;
847 GSList *buddies;
850 static void sipe_subscribe_presence_batched_routed_free(void *payload)
852 struct presence_batched_routed *data = payload;
853 GSList *buddies = data->buddies;
854 while (buddies) {
855 g_free(buddies->data);
856 buddies = buddies->next;
858 g_slist_free(data->buddies);
859 g_free(data->host);
860 g_free(payload);
863 static void sipe_subscribe_presence_batched_routed(struct sipe_core_private *sipe_private,
864 void *payload)
866 struct presence_batched_routed *data = payload;
867 GSList *buddies = data->buddies;
868 gchar *resources_uri = g_strdup("");
869 while (buddies) {
870 gchar *tmp = resources_uri;
871 resources_uri = g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp, (char *) buddies->data);
872 g_free(tmp);
873 buddies = buddies->next;
875 sipe_subscribe_presence_batched_to(sipe_private, resources_uri,
876 g_strdup(data->host));
880 * Single Category SUBSCRIBE [MS-PRES] ; To send when the server returns a 200 OK message with state="resubscribe" in response.
881 * The user sends a single SUBSCRIBE request to the subscribed contact.
882 * The To-URI and the URI listed in the resource list MUST be the same for a single category SUBSCRIBE request.
886 static void sipe_subscribe_presence_single(struct sipe_core_private *sipe_private,
887 void *buddy_name)
889 gchar *to = sip_uri((char *)buddy_name);
890 gchar *tmp = get_contact(sipe_private);
891 gchar *request;
892 gchar *content = NULL;
893 gchar *autoextend = "";
894 gchar *content_type = "";
895 struct sipe_buddy *sbuddy = g_hash_table_lookup(sipe_private->buddies, to);
896 gchar *context = sbuddy && sbuddy->just_added ? "><context/></resource>" : "/>";
898 if (sbuddy) sbuddy->just_added = FALSE;
900 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
901 content_type = "Content-Type: application/msrtc-adrl-categorylist+xml\r\n";
902 } else {
903 autoextend = "Supported: com.microsoft.autoextend\r\n";
906 request = g_strdup_printf(
907 "Accept: application/msrtc-event-categories+xml, text/xml+msrtc.pidf, application/xpidf+xml, application/pidf+xml, application/rlmi+xml, multipart/related\r\n"
908 "Supported: ms-piggyback-first-notify\r\n"
909 "%s%sSupported: ms-benotify\r\n"
910 "Proxy-Require: ms-benotify\r\n"
911 "Event: presence\r\n"
912 "Contact: %s\r\n", autoextend, content_type, tmp);
914 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
915 content = g_strdup_printf(
916 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
917 "<action name=\"subscribe\" id=\"63792024\"><adhocList>\n"
918 "<resource uri=\"%s\"%s\n"
919 "</adhocList>\n"
920 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
921 "<category name=\"calendarData\"/>\n"
922 "<category name=\"contactCard\"/>\n"
923 "<category name=\"note\"/>\n"
924 "<category name=\"state\"/>\n"
925 "</categoryList>\n"
926 "</action>\n"
927 "</batchSub>", sipe_private->username, to, context);
930 g_free(tmp);
932 sipe_subscribe_presence_buddy(sipe_private, to, request, content);
934 g_free(content);
935 g_free(to);
936 g_free(request);
939 void sipe_set_status(PurpleAccount *account, PurpleStatus *status)
941 SIPE_DEBUG_INFO("sipe_set_status: status=%s", purple_status_get_id(status));
943 if (!purple_status_is_active(status))
944 return;
946 if (account->gc) {
947 struct sipe_core_private *sipe_private = PURPLE_ACCOUNT_TO_SIPE_CORE_PRIVATE;
948 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
950 if (sip) {
951 gchar *action_name;
952 gchar *tmp;
953 time_t now = time(NULL);
954 const char *status_id = purple_status_get_id(status);
955 const char *note = purple_status_get_attr_string(status, SIPE_STATUS_ATTR_ID_MESSAGE);
956 sipe_activity activity = sipe_get_activity_by_token(status_id);
957 gboolean do_not_publish = ((now - sip->do_not_publish[activity]) <= 2);
959 /* when other point of presence clears note, but we are keeping
960 * state if OOF note.
962 if (do_not_publish && !note && sip->cal && sip->cal->oof_note) {
963 SIPE_DEBUG_INFO_NOFORMAT("sipe_set_status: enabling publication as OOF note keepers.");
964 do_not_publish = FALSE;
967 SIPE_DEBUG_INFO("sipe_set_status: was: sip->do_not_publish[%s]=%d [?] now(time)=%d",
968 status_id, (int)sip->do_not_publish[activity], (int)now);
970 sip->do_not_publish[activity] = 0;
971 SIPE_DEBUG_INFO("sipe_set_status: set: sip->do_not_publish[%s]=%d [0]",
972 status_id, (int)sip->do_not_publish[activity]);
974 if (do_not_publish)
976 SIPE_DEBUG_INFO_NOFORMAT("sipe_set_status: publication was switched off, exiting.");
977 return;
980 g_free(sip->status);
981 sip->status = g_strdup(status_id);
983 /* hack to escape apostrof before comparison */
984 tmp = note ? sipe_utils_str_replace(note, "'", "&apos;") : NULL;
986 /* this will preserve OOF flag as well */
987 if (!sipe_strequal(tmp, sip->note)) {
988 sip->is_oof_note = FALSE;
989 g_free(sip->note);
990 sip->note = g_strdup(note);
991 sip->note_since = time(NULL);
993 g_free(tmp);
995 /* schedule 2 sec to capture idle flag */
996 action_name = g_strdup_printf("<%s>", "+set-status");
997 sipe_schedule_seconds(sipe_private,
998 action_name,
999 NULL,
1000 SIPE_IDLE_SET_DELAY,
1001 send_presence_status,
1002 NULL);
1003 g_free(action_name);
1008 void
1009 sipe_set_idle(PurpleConnection * gc,
1010 int interval)
1012 SIPE_DEBUG_INFO("sipe_set_idle: interval=%d", interval);
1014 if (gc) {
1015 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
1016 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
1018 if (sip) {
1019 sip->idle_switch = time(NULL);
1020 SIPE_DEBUG_INFO("sipe_set_idle: sip->idle_switch : %s", asctime(localtime(&(sip->idle_switch))));
1025 void
1026 sipe_group_buddy(PurpleConnection *gc,
1027 const char *who,
1028 const char *old_group_name,
1029 const char *new_group_name)
1031 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
1032 struct sipe_buddy * buddy = g_hash_table_lookup(sipe_private->buddies, who);
1033 struct sipe_group * old_group = NULL;
1034 struct sipe_group * new_group;
1036 SIPE_DEBUG_INFO("sipe_group_buddy[CB]: who:%s old_group_name:%s new_group_name:%s",
1037 who ? who : "", old_group_name ? old_group_name : "", new_group_name ? new_group_name : "");
1039 if(!buddy) { // buddy not in roaming list
1040 return;
1043 if (old_group_name) {
1044 old_group = sipe_group_find_by_name(sipe_private, old_group_name);
1046 new_group = sipe_group_find_by_name(sipe_private, new_group_name);
1048 if (old_group) {
1049 buddy->groups = g_slist_remove(buddy->groups, old_group);
1050 SIPE_DEBUG_INFO("buddy %s removed from old group %s", who, old_group_name);
1053 if (!new_group) {
1054 sipe_group_create(sipe_private, new_group_name, who);
1055 } else {
1056 buddy->groups = slist_insert_unique_sorted(buddy->groups, new_group, (GCompareFunc)sipe_group_compare);
1057 sipe_core_group_set_user(SIPE_CORE_PUBLIC, who);
1061 void sipe_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
1063 SIPE_DEBUG_INFO("sipe_add_buddy[CB]: buddy:%s group:%s", buddy ? buddy->name : "", group ? group->name : "");
1065 /* libpurple can call us with undefined buddy or group */
1066 if (buddy && group) {
1067 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
1069 /* Buddy name must be lower case as we use purple_normalize_nocase() to compare */
1070 gchar *buddy_name = g_ascii_strdown(buddy->name, -1);
1071 purple_blist_rename_buddy(buddy, buddy_name);
1072 g_free(buddy_name);
1074 /* Prepend sip: if needed */
1075 if (!g_str_has_prefix(buddy->name, "sip:")) {
1076 gchar *buf = sip_uri_from_name(buddy->name);
1077 purple_blist_rename_buddy(buddy, buf);
1078 g_free(buf);
1081 if (!g_hash_table_lookup(sipe_private->buddies, buddy->name)) {
1082 struct sipe_buddy *b = g_new0(struct sipe_buddy, 1);
1083 SIPE_DEBUG_INFO("sipe_add_buddy: adding %s", buddy->name);
1084 b->name = g_strdup(buddy->name);
1085 b->just_added = TRUE;
1086 g_hash_table_insert(sipe_private->buddies, b->name, b);
1087 /* @TODO should go to callback */
1088 sipe_subscribe_presence_single(sipe_private,
1089 b->name);
1090 } else {
1091 SIPE_DEBUG_INFO("sipe_add_buddy: buddy %s already in internal list", buddy->name);
1094 sipe_group_buddy(gc, buddy->name, NULL, group->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 local 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 GSList *buddies = sipe_backend_buddy_find_all(SIPE_CORE_PUBLIC, NULL, NULL);
1245 GSList *entry = buddies;
1246 struct sipe_buddy *buddy;
1247 sipe_backend_buddy b;
1248 gchar *bname;
1249 gchar *gname;
1251 SIPE_DEBUG_INFO("sipe_cleanup_local_blist: overall %d backend 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 gname = sipe_backend_buddy_get_group_name(SIPE_CORE_PUBLIC, b);
1256 bname = sipe_backend_buddy_get_name(SIPE_CORE_PUBLIC, b);
1257 buddy = g_hash_table_lookup(sipe_private->buddies, bname);
1258 g_free(bname);
1259 if(buddy) {
1260 gboolean in_sipe_groups = FALSE;
1261 GSList *entry2 = buddy->groups;
1262 while (entry2) {
1263 struct sipe_group *group = entry2->data;
1264 if (sipe_strequal(group->name, gname)) {
1265 in_sipe_groups = TRUE;
1266 break;
1268 entry2 = entry2->next;
1270 if(!in_sipe_groups) {
1271 SIPE_DEBUG_INFO("*** REMOVING %s from blist group: %s as not having this group in roaming list", bname, gname);
1272 sipe_backend_buddy_remove(SIPE_CORE_PUBLIC, b);
1274 } else {
1275 SIPE_DEBUG_INFO("*** REMOVING %s from blist group: %s as this buddy not in roaming list", bname, gname);
1276 sipe_backend_buddy_remove(SIPE_CORE_PUBLIC, b);
1278 g_free(gname);
1279 entry = entry->next;
1281 g_slist_free(buddies);
1284 static int
1285 sipe_find_access_level(struct sipe_core_private *sipe_private,
1286 const gchar *type,
1287 const gchar *value,
1288 gboolean *is_group_access);
1290 static void
1291 sipe_refresh_blocked_status_cb(char *buddy_name,
1292 SIPE_UNUSED_PARAMETER struct sipe_buddy *buddy,
1293 struct sipe_core_private *sipe_private)
1295 int container_id = sipe_find_access_level(sipe_private, "user", buddy_name, NULL);
1296 gboolean blocked = (container_id == 32000);
1297 gboolean blocked_in_blist = sipe_backend_buddy_is_blocked(SIPE_CORE_PUBLIC, buddy_name);
1299 /* SIPE_DEBUG_INFO("sipe_refresh_blocked_status_cb: buddy_name=%s, blocked=%s, blocked_in_blist=%s",
1300 buddy_name, blocked ? "T" : "F", blocked_in_blist ? "T" : "F"); */
1302 if (blocked != blocked_in_blist) {
1303 sipe_backend_buddy_set_blocked_status(SIPE_CORE_PUBLIC, buddy_name, blocked);
1307 static void
1308 sipe_refresh_blocked_status(struct sipe_core_private *sipe_private)
1310 g_hash_table_foreach(sipe_private->buddies,
1311 (GHFunc) sipe_refresh_blocked_status_cb,
1312 sipe_private);
1315 static gboolean sipe_process_roaming_contacts(struct sipe_core_private *sipe_private,
1316 struct sipmsg *msg)
1318 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
1319 int len = msg->bodylen;
1321 const gchar *tmp = sipmsg_find_header(msg, "Event");
1322 const sipe_xml *item;
1323 sipe_xml *isc;
1324 const gchar *contacts_delta;
1325 const sipe_xml *group_node;
1326 if (!g_str_has_prefix(tmp, "vnd-microsoft-roaming-contacts")) {
1327 return FALSE;
1330 /* Convert the contact from XML to backend Buddies */
1331 isc = sipe_xml_parse(msg->body, len);
1332 if (!isc) {
1333 return FALSE;
1336 contacts_delta = sipe_xml_attribute(isc, "deltaNum");
1337 if (contacts_delta) {
1338 sip->contacts_delta = (int)g_ascii_strtod(contacts_delta, NULL);
1341 if (sipe_strequal(sipe_xml_name(isc), "contactList")) {
1343 /* Parse groups */
1344 for (group_node = sipe_xml_child(isc, "group"); group_node; group_node = sipe_xml_twin(group_node)) {
1345 struct sipe_group * group = g_new0(struct sipe_group, 1);
1346 const char *name = sipe_xml_attribute(group_node, "name");
1348 if (g_str_has_prefix(name, "~")) {
1349 name = _("Other Contacts");
1351 group->name = g_strdup(name);
1352 group->id = (int)g_ascii_strtod(sipe_xml_attribute(group_node, "id"), NULL);
1354 sipe_group_add(sipe_private, group);
1357 // Make sure we have at least one group
1358 if (g_slist_length(sip->groups) == 0) {
1359 sipe_group_create(sipe_private, _("Other Contacts"), NULL);
1362 /* Parse contacts */
1363 for (item = sipe_xml_child(isc, "contact"); item; item = sipe_xml_twin(item)) {
1364 const gchar *uri = sipe_xml_attribute(item, "uri");
1365 const gchar *name = sipe_xml_attribute(item, "name");
1366 gchar *buddy_name;
1367 struct sipe_buddy *buddy = NULL;
1368 gchar *tmp;
1369 gchar **item_groups;
1370 int i = 0;
1372 /* Buddy name must be lower case as we use purple_normalize_nocase() to compare */
1373 tmp = sip_uri_from_name(uri);
1374 buddy_name = g_ascii_strdown(tmp, -1);
1375 g_free(tmp);
1377 /* assign to group Other Contacts if nothing else received */
1378 tmp = g_strdup(sipe_xml_attribute(item, "groups"));
1379 if(is_empty(tmp)) {
1380 struct sipe_group *group = sipe_group_find_by_name(sipe_private, _("Other Contacts"));
1381 g_free(tmp);
1382 tmp = group ? g_strdup_printf("%d", group->id) : g_strdup("1");
1384 item_groups = g_strsplit(tmp, " ", 0);
1385 g_free(tmp);
1387 while (item_groups[i]) {
1388 struct sipe_group *group = sipe_group_find_by_id(sipe_private, g_ascii_strtod(item_groups[i], NULL));
1390 // If couldn't find the right group for this contact, just put them in the first group we have
1391 if (group == NULL && g_slist_length(sip->groups) > 0) {
1392 group = sip->groups->data;
1395 if (group != NULL) {
1396 gchar *b_alias;
1397 sipe_backend_buddy b = sipe_backend_buddy_find(SIPE_CORE_PUBLIC, buddy_name, group->name);
1398 if (!b){
1399 b = sipe_backend_buddy_add(SIPE_CORE_PUBLIC, buddy_name, uri, group->name);
1400 SIPE_DEBUG_INFO("Created new buddy %s with alias %s", buddy_name, uri);
1403 b_alias = sipe_backend_buddy_get_alias(SIPE_CORE_PUBLIC, b);
1404 if (sipe_strcase_equal(uri, b_alias)) {
1405 if (name != NULL && strlen(name) != 0) {
1406 sipe_backend_buddy_set_alias(SIPE_CORE_PUBLIC, b, name);
1408 SIPE_DEBUG_INFO("Replaced buddy %s alias with %s", buddy_name, name);
1411 g_free(b_alias);
1413 if (!buddy) {
1414 buddy = g_new0(struct sipe_buddy, 1);
1415 buddy->name = sipe_backend_buddy_get_name(SIPE_CORE_PUBLIC, b);
1416 g_hash_table_insert(sipe_private->buddies, buddy->name, buddy);
1418 SIPE_DEBUG_INFO("Added SIPE buddy %s", buddy->name);
1421 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
1423 SIPE_DEBUG_INFO("Added buddy %s to group %s", buddy->name, group->name);
1424 } else {
1425 SIPE_DEBUG_INFO("No group found for contact %s! Unable to add to buddy list",
1426 name);
1429 i++;
1430 } // while, contact groups
1431 g_strfreev(item_groups);
1432 g_free(buddy_name);
1434 } // for, contacts
1436 sipe_cleanup_local_blist(sipe_private);
1438 /* Add self-contact if not there yet. 2005 systems. */
1439 /* This will resemble subscription to roaming_self in 2007 systems */
1440 if (!SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
1441 gchar *self_uri = sip_uri_self(sipe_private);
1442 struct sipe_buddy *buddy = g_hash_table_lookup(sipe_private->buddies, self_uri);
1444 if (!buddy) {
1445 buddy = g_new0(struct sipe_buddy, 1);
1446 buddy->name = g_strdup(self_uri);
1447 g_hash_table_insert(sipe_private->buddies, buddy->name, buddy);
1449 g_free(self_uri);
1452 sipe_xml_free(isc);
1454 /* subscribe to buddies */
1455 if (!sip->subscribed_buddies) { //do it once, then count Expire field to schedule resubscribe.
1456 if (sip->batched_support) {
1457 sipe_subscribe_presence_batched(sipe_private, NULL);
1458 } else {
1459 g_hash_table_foreach(sipe_private->buddies,
1460 (GHFunc)sipe_buddy_subscribe_cb,
1461 sipe_private);
1463 sip->subscribed_buddies = TRUE;
1465 /* for 2005 systems schedule contacts' status update
1466 * based on their calendar information
1468 if (!SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
1469 sipe_sched_calendar_status_update(sipe_private, time(NULL));
1472 return 0;
1476 * Fires on deregistration event initiated by server.
1477 * [MS-SIPREGE] SIP extension.
1480 // 2007 Example
1482 // Content-Type: text/registration-event
1483 // subscription-state: terminated;expires=0
1484 // ms-diagnostics-public: 4141;reason="User disabled"
1486 // deregistered;event=rejected
1488 static void sipe_process_registration_notify(struct sipe_core_private *sipe_private,
1489 struct sipmsg *msg)
1491 const gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
1492 gchar *event = NULL;
1493 gchar *reason = NULL;
1494 const gchar *diagnostics = sipmsg_find_header(msg, "ms-diagnostics");
1495 gchar *warning;
1497 diagnostics = diagnostics ? diagnostics : sipmsg_find_header(msg, "ms-diagnostics-public");
1498 SIPE_DEBUG_INFO_NOFORMAT("sipe_process_registration_notify: deregistration received.");
1500 if (!g_ascii_strncasecmp(contenttype, "text/registration-event", 23)) {
1501 event = sipmsg_find_part_of_header(msg->body, "event=", NULL, NULL);
1502 //@TODO have proper parameter extraction _by_name_ func, case insesitive.
1503 event = event ? event : sipmsg_find_part_of_header(msg->body, "event=", ";", NULL);
1504 } else {
1505 SIPE_DEBUG_INFO_NOFORMAT("sipe_process_registration_notify: unknown content type, exiting.");
1506 return;
1509 if (diagnostics != NULL) {
1510 reason = sipmsg_find_part_of_header(diagnostics, "reason=\"", "\"", NULL);
1511 } else { // for LCS2005
1512 int error_id = 0;
1513 if (event && sipe_strcase_equal(event, "unregistered")) {
1514 error_id = 4140; // [MS-SIPREGE]
1515 //reason = g_strdup(_("User logged out")); // [MS-OCER]
1516 reason = g_strdup(_("you are already signed in at another location"));
1517 } else if (event && sipe_strcase_equal(event, "rejected")) {
1518 error_id = 4141;
1519 reason = g_strdup(_("user disabled")); // [MS-OCER]
1520 } else if (event && sipe_strcase_equal(event, "deactivated")) {
1521 error_id = 4142;
1522 reason = g_strdup(_("user moved")); // [MS-OCER]
1525 g_free(event);
1526 warning = g_strdup_printf(_("You have been rejected by the server: %s"), reason ? reason : _("no reason given"));
1527 g_free(reason);
1529 sipe_backend_connection_error(SIPE_CORE_PUBLIC,
1530 SIPE_CONNECTION_ERROR_INVALID_USERNAME,
1531 warning);
1532 g_free(warning);
1536 static void sipe_process_provisioning_v2(struct sipe_core_private *sipe_private,
1537 struct sipmsg *msg)
1539 sipe_xml *xn_provision_group_list;
1540 const sipe_xml *node;
1542 xn_provision_group_list = sipe_xml_parse(msg->body, msg->bodylen);
1544 /* provisionGroup */
1545 for (node = sipe_xml_child(xn_provision_group_list, "provisionGroup"); node; node = sipe_xml_twin(node)) {
1546 if (sipe_strequal("ServerConfiguration", sipe_xml_attribute(node, "name"))) {
1547 g_free(sipe_private->focus_factory_uri);
1548 sipe_private->focus_factory_uri = sipe_xml_data(sipe_xml_child(node, "focusFactoryUri"));
1549 SIPE_DEBUG_INFO("sipe_process_provisioning_v2: sipe_private->focus_factory_uri=%s",
1550 sipe_private->focus_factory_uri ? sipe_private->focus_factory_uri : "");
1552 #ifdef HAVE_VV
1553 g_free(sipe_private->mras_uri);
1554 sipe_private->mras_uri = g_strstrip(sipe_xml_data(sipe_xml_child(node, "mrasUri")));
1555 SIPE_DEBUG_INFO("sipe_process_provisioning_v2: sipe_private->mras_uri=%s",
1556 sipe_private->mras_uri ? sipe_private->mras_uri : "");
1558 if (sipe_private->mras_uri)
1559 sipe_media_get_av_edge_credentials(sipe_private);
1560 #endif
1561 break;
1564 sipe_xml_free(xn_provision_group_list);
1567 /** for 2005 system */
1568 static void
1569 sipe_process_provisioning(struct sipe_core_private *sipe_private,
1570 struct sipmsg *msg)
1572 sipe_xml *xn_provision;
1573 const sipe_xml *node;
1575 xn_provision = sipe_xml_parse(msg->body, msg->bodylen);
1576 if ((node = sipe_xml_child(xn_provision, "user"))) {
1577 SIPE_DEBUG_INFO("sipe_process_provisioning: uri=%s", sipe_xml_attribute(node, "uri"));
1578 if ((node = sipe_xml_child(node, "line"))) {
1579 const gchar *line_uri = sipe_xml_attribute(node, "uri");
1580 const gchar *server = sipe_xml_attribute(node, "server");
1581 SIPE_DEBUG_INFO("sipe_process_provisioning: line_uri=%s server=%s", line_uri, server);
1582 sip_csta_open(sipe_private, line_uri, server);
1585 sipe_xml_free(xn_provision);
1588 static void sipe_process_roaming_acl(struct sipe_core_private *sipe_private,
1589 struct sipmsg *msg)
1591 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
1592 const gchar *contacts_delta;
1593 sipe_xml *xml;
1595 xml = sipe_xml_parse(msg->body, msg->bodylen);
1596 if (!xml)
1598 return;
1601 contacts_delta = sipe_xml_attribute(xml, "deltaNum");
1602 if (contacts_delta)
1604 sip->acl_delta = (int)g_ascii_strtod(contacts_delta, NULL);
1607 sipe_xml_free(xml);
1610 /** MS-PRES container */
1611 struct sipe_container {
1612 guint id;
1613 guint version;
1614 GSList *members;
1616 /** MS-PRES container member */
1617 struct sipe_container_member {
1618 /** user, domain, sameEnterprise, federated, publicCloud; everyone */
1619 gchar *type;
1620 gchar *value;
1623 static void
1624 free_container_member(struct sipe_container_member *member)
1626 if (!member) return;
1628 g_free(member->type);
1629 g_free(member->value);
1630 g_free(member);
1633 static void
1634 free_container(struct sipe_container *container)
1636 GSList *entry;
1638 if (!container) return;
1640 entry = container->members;
1641 while (entry) {
1642 void *data = entry->data;
1643 entry = g_slist_remove(entry, data);
1644 free_container_member((struct sipe_container_member *)data);
1646 g_free(container);
1649 static void
1650 sipe_send_container_members_prepare(const guint container_id,
1651 const guint container_version,
1652 const gchar *action,
1653 const gchar *type,
1654 const gchar *value,
1655 char **container_xmls)
1657 gchar *value_str = value ? g_strdup_printf(" value=\"%s\"", value) : g_strdup("");
1658 gchar *body;
1660 if (!container_xmls) return;
1662 body = g_strdup_printf(
1663 "<container id=\"%d\" version=\"%d\"><member action=\"%s\" type=\"%s\"%s/></container>",
1664 container_id,
1665 container_version,
1666 action,
1667 type,
1668 value_str);
1669 g_free(value_str);
1671 if ((*container_xmls) == NULL) {
1672 *container_xmls = body;
1673 } else {
1674 char *tmp = *container_xmls;
1676 *container_xmls = g_strconcat(*container_xmls, body, NULL);
1677 g_free(tmp);
1678 g_free(body);
1682 static void
1683 sipe_send_set_container_members(struct sipe_core_private *sipe_private,
1684 char *container_xmls)
1686 gchar *self;
1687 gchar *contact;
1688 gchar *hdr;
1689 gchar *body;
1691 if (!container_xmls) return;
1693 self = sip_uri_self(sipe_private);
1694 body = g_strdup_printf(
1695 "<setContainerMembers xmlns=\"http://schemas.microsoft.com/2006/09/sip/container-management\">"
1696 "%s"
1697 "</setContainerMembers>",
1698 container_xmls);
1700 contact = get_contact(sipe_private);
1701 hdr = g_strdup_printf("Contact: %s\r\n"
1702 "Content-Type: application/msrtc-setcontainermembers+xml\r\n", contact);
1703 g_free(contact);
1705 sip_transport_service(sipe_private,
1706 self,
1707 hdr,
1708 body,
1709 NULL);
1711 g_free(hdr);
1712 g_free(body);
1713 g_free(self);
1717 * Finds locally stored MS-PRES container member
1719 static struct sipe_container_member *
1720 sipe_find_container_member(struct sipe_container *container,
1721 const gchar *type,
1722 const gchar *value)
1724 struct sipe_container_member *member;
1725 GSList *entry;
1727 if (container == NULL || type == NULL) {
1728 return NULL;
1731 entry = container->members;
1732 while (entry) {
1733 member = entry->data;
1734 if (sipe_strcase_equal(member->type, type) &&
1735 sipe_strcase_equal(member->value, value))
1737 return member;
1739 entry = entry->next;
1741 return NULL;
1745 * Finds locally stored MS-PRES container by id
1747 static struct sipe_container *
1748 sipe_find_container(struct sipe_core_private *sipe_private,
1749 guint id)
1751 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
1752 struct sipe_container *container;
1753 GSList *entry;
1755 if (sip == NULL) {
1756 return NULL;
1759 entry = sip->containers;
1760 while (entry) {
1761 container = entry->data;
1762 if (id == container->id) {
1763 return container;
1765 entry = entry->next;
1767 return NULL;
1770 static GSList *
1771 sipe_get_access_domains(struct sipe_core_private *sipe_private)
1773 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
1774 struct sipe_container *container;
1775 struct sipe_container_member *member;
1776 GSList *entry;
1777 GSList *entry2;
1778 GSList *res = NULL;
1780 if (!sip) return NULL;
1782 entry = sip->containers;
1783 while (entry) {
1784 container = entry->data;
1786 entry2 = container->members;
1787 while (entry2) {
1788 member = entry2->data;
1789 if (sipe_strcase_equal(member->type, "domain"))
1791 res = slist_insert_unique_sorted(res, g_strdup(member->value), (GCompareFunc)g_ascii_strcasecmp);
1793 entry2 = entry2->next;
1795 entry = entry->next;
1797 return res;
1801 * Returns pointer to domain part in provided Email URL
1803 * @param email an email URL. Example: first.last@hq.company.com
1804 * @return pointer to domain part of email URL. Coresponding example: hq.company.com
1806 * Doesn't allocate memory
1808 static const char *
1809 sipe_get_domain(const char *email)
1811 char *tmp;
1813 if (!email) return NULL;
1815 tmp = strstr(email, "@");
1817 if (tmp && ((tmp+1) < (email + strlen(email)))) {
1818 return tmp+1;
1819 } else {
1820 return NULL;
1825 /* @TODO: replace with binary search for faster access? */
1826 /** source: http://support.microsoft.com/kb/897567 */
1827 static const char * const public_domains [] = {
1828 "aol.com", "icq.com", "love.com", "mac.com", "br.live.com",
1829 "hotmail.co.il", "hotmail.co.jp", "hotmail.co.th", "hotmail.co.uk",
1830 "hotmail.com", "hotmail.com.ar", "hotmail.com.tr", "hotmail.es",
1831 "hotmail.de", "hotmail.fr", "hotmail.it", "live.at", "live.be",
1832 "live.ca", "live.cl", "live.cn", "live.co.in", "live.co.kr",
1833 "live.co.uk", "live.co.za", "live.com", "live.com.ar", "live.com.au",
1834 "live.com.co", "live.com.mx", "live.com.my", "live.com.pe",
1835 "live.com.ph", "live.com.pk", "live.com.pt", "live.com.sg",
1836 "live.com.ve", "live.de", "live.dk", "live.fr", "live.hk", "live.ie",
1837 "live.in", "live.it", "live.jp", "live.nl", "live.no", "live.ph",
1838 "live.ru", "live.se", "livemail.com.br", "livemail.tw",
1839 "messengeruser.com", "msn.com", "passport.com", "sympatico.ca",
1840 "tw.live.com", "webtv.net", "windowslive.com", "windowslive.es",
1841 "yahoo.com",
1842 NULL};
1844 static gboolean
1845 sipe_is_public_domain(const char *domain)
1847 int i = 0;
1848 while (public_domains[i]) {
1849 if (sipe_strcase_equal(public_domains[i], domain)) {
1850 return TRUE;
1852 i++;
1854 return FALSE;
1858 * Access Levels
1859 * 32000 - Blocked
1860 * 400 - Personal
1861 * 300 - Team
1862 * 200 - Company
1863 * 100 - Public
1865 static const char *
1866 sipe_get_access_level_name(int container_id)
1868 switch(container_id) {
1869 case 32000: return _("Blocked");
1870 case 400: return _("Personal");
1871 case 300: return _("Team");
1872 case 200: return _("Company");
1873 case 100: return _("Public");
1875 return _("Unknown");
1878 static const guint containers[] = {32000, 400, 300, 200, 100};
1879 #define CONTAINERS_LEN (sizeof(containers) / sizeof(guint))
1882 static int
1883 sipe_find_member_access_level(struct sipe_core_private *sipe_private,
1884 const gchar *type,
1885 const gchar *value)
1887 unsigned int i = 0;
1888 const gchar *value_mod = value;
1890 if (!type) return -1;
1892 if (sipe_strequal("user", type)) {
1893 value_mod = sipe_get_no_sip_uri(value);
1896 for (i = 0; i < CONTAINERS_LEN; i++) {
1897 struct sipe_container_member *member;
1898 struct sipe_container *container = sipe_find_container(sipe_private, containers[i]);
1899 if (!container) continue;
1901 member = sipe_find_container_member(container, type, value_mod);
1902 if (member) return containers[i];
1905 return -1;
1908 /** Member type: user, domain, sameEnterprise, federated, publicCloud; everyone */
1909 static int
1910 sipe_find_access_level(struct sipe_core_private *sipe_private,
1911 const gchar *type,
1912 const gchar *value,
1913 gboolean *is_group_access)
1915 int container_id = -1;
1917 if (sipe_strequal("user", type)) {
1918 const char *domain;
1919 const char *no_sip_uri = sipe_get_no_sip_uri(value);
1921 container_id = sipe_find_member_access_level(sipe_private, "user", no_sip_uri);
1922 if (container_id >= 0) {
1923 if (is_group_access) *is_group_access = FALSE;
1924 return container_id;
1927 domain = sipe_get_domain(no_sip_uri);
1928 container_id = sipe_find_member_access_level(sipe_private, "domain", domain);
1929 if (container_id >= 0) {
1930 if (is_group_access) *is_group_access = TRUE;
1931 return container_id;
1934 container_id = sipe_find_member_access_level(sipe_private, "sameEnterprise", NULL);
1935 if ((container_id >= 0) && sipe_strcase_equal(sipe_private->public.sip_domain, domain)) {
1936 if (is_group_access) *is_group_access = TRUE;
1937 return container_id;
1940 container_id = sipe_find_member_access_level(sipe_private, "publicCloud", NULL);
1941 if ((container_id >= 0) && sipe_is_public_domain(domain)) {
1942 if (is_group_access) *is_group_access = TRUE;
1943 return container_id;
1946 container_id = sipe_find_member_access_level(sipe_private, "everyone", NULL);
1947 if ((container_id >= 0)) {
1948 if (is_group_access) *is_group_access = TRUE;
1949 return container_id;
1951 } else {
1952 container_id = sipe_find_member_access_level(sipe_private, type, value);
1953 if (is_group_access) *is_group_access = FALSE;
1956 return container_id;
1960 * @param container_id a new access level. If -1 then current access level
1961 * is just removed (I.e. the member is removed from all containers).
1962 * @param type a type of member. E.g. "user", "sameEnterprise", etc.
1963 * @param value a value for member. E.g. SIP URI for "user" member type.
1965 static void
1966 sipe_change_access_level(struct sipe_core_private *sipe_private,
1967 const int container_id,
1968 const gchar *type,
1969 const gchar *value)
1971 unsigned int i;
1972 int current_container_id = -1;
1973 char *container_xmls = NULL;
1975 /* for each container: find/delete */
1976 for (i = 0; i < CONTAINERS_LEN; i++) {
1977 struct sipe_container_member *member;
1978 struct sipe_container *container = sipe_find_container(sipe_private, containers[i]);
1980 if (!container) continue;
1982 member = sipe_find_container_member(container, type, value);
1983 if (member) {
1984 current_container_id = containers[i];
1985 /* delete/publish current access level */
1986 if (container_id < 0 || container_id != current_container_id) {
1987 sipe_send_container_members_prepare(current_container_id, container->version, "remove", type, value, &container_xmls);
1988 /* remove member from our cache, to be able to recalculate AL below */
1989 container->members = g_slist_remove(container->members, member);
1990 current_container_id = -1;
1995 /* recalculate AL below */
1996 current_container_id = sipe_find_access_level(sipe_private, type, value, NULL);
1998 /* assign/publish new access level */
1999 if (container_id != current_container_id && container_id >= 0) {
2000 struct sipe_container *container = sipe_find_container(sipe_private, container_id);
2001 guint version = container ? container->version : 0;
2003 sipe_send_container_members_prepare(container_id, version, "add", type, value, &container_xmls);
2006 if (container_xmls) {
2007 sipe_send_set_container_members(sipe_private, container_xmls);
2009 g_free(container_xmls);
2012 static void
2013 free_publication(struct sipe_publication *publication)
2015 g_free(publication->category);
2016 g_free(publication->cal_event_hash);
2017 g_free(publication->note);
2019 g_free(publication->working_hours_xml_str);
2020 g_free(publication->fb_start_str);
2021 g_free(publication->free_busy_base64);
2023 g_free(publication);
2026 /* key is <category><instance><container> */
2027 static gboolean
2028 sipe_is_our_publication(struct sipe_core_private *sipe_private,
2029 const gchar *key)
2031 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
2032 GSList *entry;
2034 /* filling keys for our publications if not yet cached */
2035 if (!sip->our_publication_keys) {
2036 guint device_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_DEVICE);
2037 guint machine_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_MACHINE);
2038 guint user_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_USER);
2039 guint calendar_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_CALENDAR);
2040 guint cal_oof_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_CALENDAR_OOF);
2041 guint cal_data_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_CALENDAR_DATA);
2042 guint note_oof_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_NOTE_OOF);
2044 SIPE_DEBUG_INFO_NOFORMAT("* Our Publication Instances *");
2045 SIPE_DEBUG_INFO("\tDevice : %u\t0x%08X", device_instance, device_instance);
2046 SIPE_DEBUG_INFO("\tMachine State : %u\t0x%08X", machine_instance, machine_instance);
2047 SIPE_DEBUG_INFO("\tUser Stare : %u\t0x%08X", user_instance, user_instance);
2048 SIPE_DEBUG_INFO("\tCalendar State : %u\t0x%08X", calendar_instance, calendar_instance);
2049 SIPE_DEBUG_INFO("\tCalendar OOF State : %u\t0x%08X", cal_oof_instance, cal_oof_instance);
2050 SIPE_DEBUG_INFO("\tCalendar FreeBusy : %u\t0x%08X", cal_data_instance, cal_data_instance);
2051 SIPE_DEBUG_INFO("\tOOF Note : %u\t0x%08X", note_oof_instance, note_oof_instance);
2052 SIPE_DEBUG_INFO("\tNote : %u", 0);
2053 SIPE_DEBUG_INFO("\tCalendar WorkingHours: %u", 0);
2055 /* device */
2056 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2057 g_strdup_printf("<%s><%u><%u>", "device", device_instance, 2));
2059 /* state:machineState */
2060 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2061 g_strdup_printf("<%s><%u><%u>", "state", machine_instance, 2));
2062 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2063 g_strdup_printf("<%s><%u><%u>", "state", machine_instance, 3));
2065 /* state:userState */
2066 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2067 g_strdup_printf("<%s><%u><%u>", "state", user_instance, 2));
2068 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2069 g_strdup_printf("<%s><%u><%u>", "state", user_instance, 3));
2071 /* state:calendarState */
2072 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2073 g_strdup_printf("<%s><%u><%u>", "state", calendar_instance, 2));
2074 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2075 g_strdup_printf("<%s><%u><%u>", "state", calendar_instance, 3));
2077 /* state:calendarState OOF */
2078 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2079 g_strdup_printf("<%s><%u><%u>", "state", cal_oof_instance, 2));
2080 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2081 g_strdup_printf("<%s><%u><%u>", "state", cal_oof_instance, 3));
2083 /* note */
2084 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2085 g_strdup_printf("<%s><%u><%u>", "note", 0, 200));
2086 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2087 g_strdup_printf("<%s><%u><%u>", "note", 0, 300));
2088 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2089 g_strdup_printf("<%s><%u><%u>", "note", 0, 400));
2091 /* note OOF */
2092 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2093 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 200));
2094 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2095 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 300));
2096 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2097 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 400));
2099 /* calendarData:WorkingHours */
2100 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2101 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 1));
2102 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2103 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 100));
2104 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2105 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 200));
2106 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2107 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 300));
2108 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2109 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 400));
2110 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2111 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 32000));
2113 /* calendarData:FreeBusy */
2114 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2115 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 1));
2116 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2117 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 100));
2118 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2119 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 200));
2120 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2121 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 300));
2122 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2123 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 400));
2124 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2125 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 32000));
2127 //SIPE_DEBUG_INFO("sipe_is_our_publication: sip->our_publication_keys length=%d",
2128 // sip->our_publication_keys ? (int) g_slist_length(sip->our_publication_keys) : -1);
2131 //SIPE_DEBUG_INFO("sipe_is_our_publication: key=%s", key);
2133 entry = sip->our_publication_keys;
2134 while (entry) {
2135 //SIPE_DEBUG_INFO(" sipe_is_our_publication: entry->data=%s", entry->data);
2136 if (sipe_strequal(entry->data, key)) {
2137 return TRUE;
2139 entry = entry->next;
2141 return FALSE;
2144 /** Property names to store in blist.xml */
2145 #define ALIAS_PROP "alias"
2146 #define EMAIL_PROP "email"
2147 #define PHONE_PROP "phone"
2148 #define PHONE_DISPLAY_PROP "phone-display"
2149 #define PHONE_MOBILE_PROP "phone-mobile"
2150 #define PHONE_MOBILE_DISPLAY_PROP "phone-mobile-display"
2151 #define PHONE_HOME_PROP "phone-home"
2152 #define PHONE_HOME_DISPLAY_PROP "phone-home-display"
2153 #define PHONE_OTHER_PROP "phone-other"
2154 #define PHONE_OTHER_DISPLAY_PROP "phone-other-display"
2155 #define PHONE_CUSTOM1_PROP "phone-custom1"
2156 #define PHONE_CUSTOM1_DISPLAY_PROP "phone-custom1-display"
2157 #define SITE_PROP "site"
2158 #define COMPANY_PROP "company"
2159 #define DEPARTMENT_PROP "department"
2160 #define TITLE_PROP "title"
2161 #define OFFICE_PROP "office"
2162 /** implies work address */
2163 #define ADDRESS_STREET_PROP "address-street"
2164 #define ADDRESS_CITY_PROP "address-city"
2165 #define ADDRESS_STATE_PROP "address-state"
2166 #define ADDRESS_ZIPCODE_PROP "address-zipcode"
2167 #define ADDRESS_COUNTRYCODE_PROP "address-country-code"
2170 * Tries to figure out user first and last name
2171 * based on Display Name and email properties.
2173 * Allocates memory - must be g_free()'d
2175 * Examples to parse:
2176 * First Last
2177 * First Last - Company Name
2178 * Last, First
2179 * Last, First M.
2180 * Last, First (C)(STP) (Company)
2181 * first.last@company.com (preprocessed as "first last")
2182 * first.last.company.com@reuters.net (preprocessed as "first last company com")
2184 * Unusable examples:
2185 * user@company.com (preprocessed as "user")
2186 * first.m.last@company.com (preprocessed as "first m last")
2187 * user.company.com@reuters.net (preprocessed as "user company com")
2189 static void
2190 sipe_get_first_last_names(struct sipe_core_private *sipe_private,
2191 const char *uri,
2192 char **first_name,
2193 char **last_name)
2195 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
2196 sipe_backend_buddy p_buddy;
2197 char *display_name;
2198 gchar *email;
2199 const char *first, *last;
2200 char *tmp;
2201 char **parts;
2202 gboolean has_comma = FALSE;
2204 if (!sip || !uri) return;
2206 p_buddy = sipe_backend_buddy_find(SIPE_CORE_PUBLIC, uri, NULL);
2208 if (!p_buddy) return;
2210 display_name = sipe_backend_buddy_get_alias(SIPE_CORE_PUBLIC, p_buddy);
2211 email = sipe_backend_buddy_get_string(SIPE_CORE_PUBLIC, p_buddy, SIPE_BUDDY_INFO_EMAIL);
2213 if (!display_name && !email) return;
2215 /* if no display name, make "first last anything_else" out of email */
2216 if (email && !display_name) {
2217 display_name = g_strndup(email, strstr(email, "@") - email);
2218 display_name = sipe_utils_str_replace((tmp = display_name), ".", " ");
2219 g_free(tmp);
2222 if (display_name) {
2223 has_comma = (strstr(display_name, ",") != NULL);
2224 display_name = sipe_utils_str_replace((tmp = display_name), ", ", " ");
2225 g_free(tmp);
2226 display_name = sipe_utils_str_replace((tmp = display_name), ",", " ");
2227 g_free(tmp);
2230 parts = g_strsplit(display_name, " ", 0);
2232 if (!parts[0] || !parts[1]) {
2233 g_free(email);
2234 g_free(display_name);
2235 g_strfreev(parts);
2236 return;
2239 if (has_comma) {
2240 last = parts[0];
2241 first = parts[1];
2242 } else {
2243 first = parts[0];
2244 last = parts[1];
2247 if (first_name) {
2248 *first_name = g_strstrip(g_strdup(first));
2251 if (last_name) {
2252 *last_name = g_strstrip(g_strdup(last));
2255 g_free(email);
2256 g_free(display_name);
2257 g_strfreev(parts);
2261 * Update user information
2263 * @param uri buddy SIP URI with 'sip:' prefix whose info we want to change.
2264 * @param property_name
2265 * @param property_value may be modified to strip white space
2267 static void
2268 sipe_update_user_info(struct sipe_core_private *sipe_private,
2269 const char *uri,
2270 sipe_buddy_info_fields propkey,
2271 char *property_value)
2273 GSList *buddies, *entry;
2275 if (property_value)
2276 property_value = g_strstrip(property_value);
2278 entry = buddies = sipe_backend_buddy_find_all(SIPE_CORE_PUBLIC, uri, NULL); /* all buddies in different groups */
2279 while (entry) {
2280 gchar *prop_str;
2281 gchar *server_alias;
2282 gchar *alias;
2283 sipe_backend_buddy p_buddy = entry->data;
2285 /* for Display Name */
2286 if (propkey == SIPE_BUDDY_INFO_DISPLAY_NAME) {
2287 alias = sipe_backend_buddy_get_alias(SIPE_CORE_PUBLIC, p_buddy);
2288 if (property_value && sipe_is_bad_alias(uri, alias)) {
2289 SIPE_DEBUG_INFO("Replacing alias for %s with %s", uri, property_value);
2290 sipe_backend_buddy_set_alias(SIPE_CORE_PUBLIC, p_buddy, property_value);
2292 g_free(alias);
2294 server_alias = sipe_backend_buddy_get_server_alias(SIPE_CORE_PUBLIC, p_buddy);
2295 if (!is_empty(property_value) &&
2296 (!sipe_strequal(property_value, server_alias) || is_empty(server_alias)) )
2298 SIPE_DEBUG_INFO("Replacing service alias for %s with %s", uri, property_value);
2299 sipe_backend_buddy_set_server_alias(SIPE_CORE_PUBLIC, p_buddy, property_value);
2301 g_free(server_alias);
2303 /* for other properties */
2304 else {
2305 if (!is_empty(property_value)) {
2306 prop_str = sipe_backend_buddy_get_string(SIPE_CORE_PUBLIC, p_buddy, propkey);
2307 if (!prop_str || !sipe_strcase_equal(prop_str, property_value)) {
2308 sipe_backend_buddy_set_string(SIPE_CORE_PUBLIC, p_buddy, propkey, property_value);
2310 g_free(prop_str);
2314 entry = entry->next;
2316 g_slist_free(buddies);
2320 * Update user phone
2321 * Suitable for both 2005 and 2007 systems.
2323 * @param uri buddy SIP URI with 'sip:' prefix whose info we want to change.
2324 * @param phone_type
2325 * @param phone may be modified to strip white space
2326 * @param phone_display_string may be modified to strip white space
2328 static void
2329 sipe_update_user_phone(struct sipe_core_private *sipe_private,
2330 const gchar *uri,
2331 const gchar *phone_type,
2332 gchar *phone,
2333 gchar *phone_display_string)
2335 sipe_buddy_info_fields phone_node = SIPE_BUDDY_INFO_WORK_PHONE; /* work phone by default */
2336 sipe_buddy_info_fields phone_display_node = SIPE_BUDDY_INFO_WORK_PHONE_DISPLAY; /* work phone by default */
2338 if(!phone || strlen(phone) == 0) return;
2340 if ((sipe_strequal(phone_type, "mobile") || sipe_strequal(phone_type, "cell"))) {
2341 phone_node = SIPE_BUDDY_INFO_MOBILE_PHONE;
2342 phone_display_node = SIPE_BUDDY_INFO_MOBILE_PHONE_DISPLAY;
2343 } else if (sipe_strequal(phone_type, "home")) {
2344 phone_node = SIPE_BUDDY_INFO_HOME_PHONE;
2345 phone_display_node = SIPE_BUDDY_INFO_HOME_PHONE_DISPLAY;
2346 } else if (sipe_strequal(phone_type, "other")) {
2347 phone_node = SIPE_BUDDY_INFO_OTHER_PHONE;
2348 phone_display_node = SIPE_BUDDY_INFO_OTHER_PHONE_DISPLAY;
2349 } else if (sipe_strequal(phone_type, "custom1")) {
2350 phone_node = SIPE_BUDDY_INFO_CUSTOM1_PHONE;
2351 phone_display_node = SIPE_BUDDY_INFO_CUSTOM1_PHONE_DISPLAY;
2354 sipe_update_user_info(sipe_private, uri, phone_node, phone);
2355 if (phone_display_string) {
2356 sipe_update_user_info(sipe_private, uri, phone_display_node, phone_display_string);
2360 void
2361 sipe_core_update_calendar(struct sipe_core_public *sipe_public)
2363 SIPE_DEBUG_INFO_NOFORMAT("sipe_core_update_calendar: started.");
2365 /* Do in parallel.
2366 * If failed, the branch will be disabled for subsequent calls.
2367 * Can't rely that user turned the functionality on in account settings.
2369 sipe_ews_update_calendar(SIPE_CORE_PRIVATE);
2370 sipe_domino_update_calendar(SIPE_CORE_PRIVATE);
2372 /* schedule repeat */
2373 sipe_schedule_seconds(SIPE_CORE_PRIVATE,
2374 "<+update-calendar>",
2375 NULL,
2376 UPDATE_CALENDAR_INTERVAL,
2377 (sipe_schedule_action)sipe_core_update_calendar,
2378 NULL);
2380 SIPE_DEBUG_INFO_NOFORMAT("sipe_core_update_calendar: finished.");
2384 * This method motivates Purple's Host (e.g. Pidgin) to update its UI
2385 * by using standard Purple's means of signals and saved statuses.
2387 * Thus all UI elements get updated: Status Button with Note, docklet.
2388 * This is ablolutely important as both our status and note can come
2389 * inbound (roaming) or be updated programmatically (e.g. based on our
2390 * calendar data).
2392 static void
2393 sipe_set_purple_account_status_and_note(const PurpleAccount *account,
2394 const char *status_id,
2395 const char *message,
2396 time_t do_not_publish[])
2398 PurpleStatus *status = purple_account_get_active_status(account);
2399 gboolean changed = TRUE;
2401 if (g_str_equal(status_id, purple_status_get_id(status)) &&
2402 sipe_strequal(message, purple_status_get_attr_string(status, SIPE_STATUS_ATTR_ID_MESSAGE)))
2404 changed = FALSE;
2407 if (purple_savedstatus_is_idleaway()) {
2408 changed = FALSE;
2411 if (changed) {
2412 PurpleSavedStatus *saved_status;
2413 const PurpleStatusType *acct_status_type =
2414 purple_status_type_find_with_id(account->status_types, status_id);
2415 PurpleStatusPrimitive primitive = purple_status_type_get_primitive(acct_status_type);
2416 sipe_activity activity = sipe_get_activity_by_token(status_id);
2418 saved_status = purple_savedstatus_find_transient_by_type_and_message(primitive, message);
2419 if (saved_status) {
2420 purple_savedstatus_set_substatus(saved_status, account, acct_status_type, message);
2423 /* If this type+message is unique then create a new transient saved status
2424 * Ref: gtkstatusbox.c
2426 if (!saved_status) {
2427 GList *tmp;
2428 GList *active_accts = purple_accounts_get_all_active();
2430 saved_status = purple_savedstatus_new(NULL, primitive);
2431 purple_savedstatus_set_message(saved_status, message);
2433 for (tmp = active_accts; tmp != NULL; tmp = tmp->next) {
2434 purple_savedstatus_set_substatus(saved_status,
2435 (PurpleAccount *)tmp->data, acct_status_type, message);
2437 g_list_free(active_accts);
2440 do_not_publish[activity] = time(NULL);
2441 SIPE_DEBUG_INFO("sipe_set_purple_account_status_and_note: do_not_publish[%s]=%d [now]",
2442 status_id, (int)do_not_publish[activity]);
2444 /* Set the status for each account */
2445 purple_savedstatus_activate(saved_status);
2449 struct hash_table_delete_payload {
2450 GHashTable *hash_table;
2451 guint container;
2454 static void
2455 sipe_remove_category_container_publications_cb(const char *name,
2456 struct sipe_publication *publication,
2457 struct hash_table_delete_payload *payload)
2459 if (publication->container == payload->container) {
2460 g_hash_table_remove(payload->hash_table, name);
2463 static void
2464 sipe_remove_category_container_publications(GHashTable *our_publications,
2465 const char *category,
2466 guint container)
2468 struct hash_table_delete_payload payload;
2469 payload.hash_table = g_hash_table_lookup(our_publications, category);
2471 if (!payload.hash_table) return;
2473 payload.container = container;
2474 g_hash_table_foreach(payload.hash_table, (GHFunc)sipe_remove_category_container_publications_cb, &payload);
2477 static void
2478 send_publish_category_initial(struct sipe_core_private *sipe_private);
2481 * When we receive some self (BE) NOTIFY with a new subscriber
2482 * we sends a setSubscribers request to him [SIP-PRES] 4.8
2485 static void sipe_process_roaming_self(struct sipe_core_private *sipe_private,
2486 struct sipmsg *msg)
2488 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
2489 gchar *contact;
2490 gchar *to;
2491 sipe_xml *xml;
2492 const sipe_xml *node;
2493 const sipe_xml *node2;
2494 char *display_name = NULL;
2495 char *uri;
2496 GSList *category_names = NULL;
2497 int aggreg_avail = 0;
2498 gboolean do_update_status = FALSE;
2499 gboolean has_note_cleaned = FALSE;
2501 SIPE_DEBUG_INFO_NOFORMAT("sipe_process_roaming_self");
2503 xml = sipe_xml_parse(msg->body, msg->bodylen);
2504 if (!xml) return;
2506 contact = get_contact(sipe_private);
2507 to = sip_uri_self(sipe_private);
2510 /* categories */
2511 /* set list of categories participating in this XML */
2512 for (node = sipe_xml_child(xml, "categories/category"); node; node = sipe_xml_twin(node)) {
2513 const gchar *name = sipe_xml_attribute(node, "name");
2514 category_names = slist_insert_unique_sorted(category_names, (gchar *)name, (GCompareFunc)strcmp);
2516 SIPE_DEBUG_INFO("sipe_process_roaming_self: category_names length=%d",
2517 category_names ? (int) g_slist_length(category_names) : -1);
2518 /* drop category information */
2519 if (category_names) {
2520 GSList *entry = category_names;
2521 while (entry) {
2522 GHashTable *cat_publications;
2523 const gchar *category = entry->data;
2524 entry = entry->next;
2525 SIPE_DEBUG_INFO("sipe_process_roaming_self: dropping category: %s", category);
2526 cat_publications = g_hash_table_lookup(sip->our_publications, category);
2527 if (cat_publications) {
2528 g_hash_table_remove(sip->our_publications, category);
2529 SIPE_DEBUG_INFO("sipe_process_roaming_self: dropped category: %s", category);
2533 g_slist_free(category_names);
2534 /* filling our categories reflected in roaming data */
2535 for (node = sipe_xml_child(xml, "categories/category"); node; node = sipe_xml_twin(node)) {
2536 const char *tmp;
2537 const gchar *name = sipe_xml_attribute(node, "name");
2538 guint container = sipe_xml_int_attribute(node, "container", -1);
2539 guint instance = sipe_xml_int_attribute(node, "instance", -1);
2540 guint version = sipe_xml_int_attribute(node, "version", 0);
2541 time_t publish_time = (tmp = sipe_xml_attribute(node, "publishTime")) ?
2542 sipe_utils_str_to_time(tmp) : 0;
2543 gchar *key;
2544 GHashTable *cat_publications = g_hash_table_lookup(sip->our_publications, name);
2546 /* Ex. clear note: <category name="note"/> */
2547 if (container == (guint)-1) {
2548 g_free(sip->note);
2549 sip->note = NULL;
2550 do_update_status = TRUE;
2551 continue;
2554 /* Ex. clear note: <category name="note" container="200"/> */
2555 if (instance == (guint)-1) {
2556 if (container == 200) {
2557 g_free(sip->note);
2558 sip->note = NULL;
2559 do_update_status = TRUE;
2561 SIPE_DEBUG_INFO("sipe_process_roaming_self: removing publications for: %s/%u", name, container);
2562 sipe_remove_category_container_publications(
2563 sip->our_publications, name, container);
2564 continue;
2567 /* key is <category><instance><container> */
2568 key = g_strdup_printf("<%s><%u><%u>", name, instance, container);
2569 SIPE_DEBUG_INFO("sipe_process_roaming_self: key=%s version=%d", key, version);
2571 /* capture all userState publication for later clean up if required */
2572 if (sipe_strequal(name, "state") && (container == 2 || container == 3)) {
2573 const sipe_xml *xn_state = sipe_xml_child(node, "state");
2575 if (xn_state && sipe_strequal(sipe_xml_attribute(xn_state, "type"), "userState")) {
2576 struct sipe_publication *publication = g_new0(struct sipe_publication, 1);
2577 publication->category = g_strdup(name);
2578 publication->instance = instance;
2579 publication->container = container;
2580 publication->version = version;
2582 if (!sip->user_state_publications) {
2583 sip->user_state_publications = g_hash_table_new_full(
2584 g_str_hash, g_str_equal,
2585 g_free, (GDestroyNotify)free_publication);
2587 g_hash_table_insert(sip->user_state_publications, g_strdup(key), publication);
2588 SIPE_DEBUG_INFO("sipe_process_roaming_self: added to user_state_publications key=%s version=%d",
2589 key, version);
2593 if (sipe_is_our_publication(sipe_private, key)) {
2594 struct sipe_publication *publication = g_new0(struct sipe_publication, 1);
2596 publication->category = g_strdup(name);
2597 publication->instance = instance;
2598 publication->container = container;
2599 publication->version = version;
2601 /* filling publication->availability */
2602 if (sipe_strequal(name, "state")) {
2603 const sipe_xml *xn_state = sipe_xml_child(node, "state");
2604 const sipe_xml *xn_avail = sipe_xml_child(xn_state, "availability");
2606 if (xn_avail) {
2607 gchar *avail_str = sipe_xml_data(xn_avail);
2608 if (avail_str) {
2609 publication->availability = atoi(avail_str);
2611 g_free(avail_str);
2613 /* for calendarState */
2614 if (xn_state && sipe_strequal(sipe_xml_attribute(xn_state, "type"), "calendarState")) {
2615 const sipe_xml *xn_activity = sipe_xml_child(xn_state, "activity");
2616 struct sipe_cal_event *event = g_new0(struct sipe_cal_event, 1);
2618 event->start_time = sipe_utils_str_to_time(sipe_xml_attribute(xn_state, "startTime"));
2619 if (xn_activity) {
2620 if (sipe_strequal(sipe_xml_attribute(xn_activity, "token"),
2621 sipe_activity_map[SIPE_ACTIVITY_IN_MEETING].token))
2623 event->is_meeting = TRUE;
2626 event->subject = sipe_xml_data(sipe_xml_child(xn_state, "meetingSubject"));
2627 event->location = sipe_xml_data(sipe_xml_child(xn_state, "meetingLocation"));
2629 publication->cal_event_hash = sipe_cal_event_hash(event);
2630 SIPE_DEBUG_INFO("sipe_process_roaming_self: hash=%s",
2631 publication->cal_event_hash);
2632 sipe_cal_event_free(event);
2635 /* filling publication->note */
2636 if (sipe_strequal(name, "note")) {
2637 const sipe_xml *xn_body = sipe_xml_child(node, "note/body");
2639 if (!has_note_cleaned) {
2640 has_note_cleaned = TRUE;
2642 g_free(sip->note);
2643 sip->note = NULL;
2644 sip->note_since = publish_time;
2646 do_update_status = TRUE;
2649 g_free(publication->note);
2650 publication->note = NULL;
2651 if (xn_body) {
2652 char *tmp;
2654 publication->note = g_markup_escape_text((tmp = sipe_xml_data(xn_body)), -1);
2655 g_free(tmp);
2656 if (publish_time >= sip->note_since) {
2657 g_free(sip->note);
2658 sip->note = g_strdup(publication->note);
2659 sip->note_since = publish_time;
2660 sip->is_oof_note = sipe_strequal(sipe_xml_attribute(xn_body, "type"), "OOF");
2662 do_update_status = TRUE;
2667 /* filling publication->fb_start_str, free_busy_base64, working_hours_xml_str */
2668 if (sipe_strequal(name, "calendarData") && (publication->container == 300)) {
2669 const sipe_xml *xn_free_busy = sipe_xml_child(node, "calendarData/freeBusy");
2670 const sipe_xml *xn_working_hours = sipe_xml_child(node, "calendarData/WorkingHours");
2671 if (xn_free_busy) {
2672 publication->fb_start_str = g_strdup(sipe_xml_attribute(xn_free_busy, "startTime"));
2673 publication->free_busy_base64 = sipe_xml_data(xn_free_busy);
2675 if (xn_working_hours) {
2676 publication->working_hours_xml_str = sipe_xml_stringify(xn_working_hours);
2680 if (!cat_publications) {
2681 cat_publications = g_hash_table_new_full(
2682 g_str_hash, g_str_equal,
2683 g_free, (GDestroyNotify)free_publication);
2684 g_hash_table_insert(sip->our_publications, g_strdup(name), cat_publications);
2685 SIPE_DEBUG_INFO("sipe_process_roaming_self: added GHashTable cat=%s", name);
2687 g_hash_table_insert(cat_publications, g_strdup(key), publication);
2688 SIPE_DEBUG_INFO("sipe_process_roaming_self: added key=%s version=%d", key, version);
2690 g_free(key);
2692 /* aggregateState (not an our publication) from 2-nd container */
2693 if (sipe_strequal(name, "state") && container == 2) {
2694 const sipe_xml *xn_state = sipe_xml_child(node, "state");
2696 if (xn_state && sipe_strequal(sipe_xml_attribute(xn_state, "type"), "aggregateState")) {
2697 const sipe_xml *xn_avail = sipe_xml_child(xn_state, "availability");
2699 if (xn_avail) {
2700 gchar *avail_str = sipe_xml_data(xn_avail);
2701 if (avail_str) {
2702 aggreg_avail = atoi(avail_str);
2704 g_free(avail_str);
2707 do_update_status = TRUE;
2711 /* userProperties published by server from AD */
2712 if (!sip->csta && sipe_strequal(name, "userProperties")) {
2713 const sipe_xml *line;
2714 /* line, for Remote Call Control (RCC) */
2715 for (line = sipe_xml_child(node, "userProperties/lines/line"); line; line = sipe_xml_twin(line)) {
2716 const gchar *line_server = sipe_xml_attribute(line, "lineServer");
2717 const gchar *line_type = sipe_xml_attribute(line, "lineType");
2718 gchar *line_uri;
2720 if (!line_server || !(sipe_strequal(line_type, "Rcc") || sipe_strequal(line_type, "Dual"))) continue;
2722 line_uri = sipe_xml_data(line);
2723 if (line_uri) {
2724 SIPE_DEBUG_INFO("sipe_process_roaming_self: line_uri=%s server=%s", line_uri, line_server);
2725 sip_csta_open(sipe_private, line_uri, line_server);
2727 g_free(line_uri);
2729 break;
2733 SIPE_DEBUG_INFO("sipe_process_roaming_self: sip->our_publications size=%d",
2734 sip->our_publications ? (int) g_hash_table_size(sip->our_publications) : -1);
2736 /* containers */
2737 for (node = sipe_xml_child(xml, "containers/container"); node; node = sipe_xml_twin(node)) {
2738 guint id = sipe_xml_int_attribute(node, "id", 0);
2739 struct sipe_container *container = sipe_find_container(sipe_private, id);
2741 if (container) {
2742 sip->containers = g_slist_remove(sip->containers, container);
2743 SIPE_DEBUG_INFO("sipe_process_roaming_self: removed existing container id=%d v%d", container->id, container->version);
2744 free_container(container);
2746 container = g_new0(struct sipe_container, 1);
2747 container->id = id;
2748 container->version = sipe_xml_int_attribute(node, "version", 0);
2749 sip->containers = g_slist_append(sip->containers, container);
2750 SIPE_DEBUG_INFO("sipe_process_roaming_self: added container id=%d v%d", container->id, container->version);
2752 for (node2 = sipe_xml_child(node, "member"); node2; node2 = sipe_xml_twin(node2)) {
2753 struct sipe_container_member *member = g_new0(struct sipe_container_member, 1);
2754 member->type = g_strdup(sipe_xml_attribute(node2, "type"));
2755 member->value = g_strdup(sipe_xml_attribute(node2, "value"));
2756 container->members = g_slist_append(container->members, member);
2757 SIPE_DEBUG_INFO("sipe_process_roaming_self: added container member type=%s value=%s",
2758 member->type, member->value ? member->value : "");
2762 SIPE_DEBUG_INFO("sipe_process_roaming_self: sip->access_level_set=%s", sip->access_level_set ? "TRUE" : "FALSE");
2763 if (!sip->access_level_set && sipe_xml_child(xml, "containers")) {
2764 char *container_xmls = NULL;
2765 int sameEnterpriseAL = sipe_find_access_level(sipe_private, "sameEnterprise", NULL, NULL);
2766 int federatedAL = sipe_find_access_level(sipe_private, "federated", NULL, NULL);
2768 SIPE_DEBUG_INFO("sipe_process_roaming_self: sameEnterpriseAL=%d", sameEnterpriseAL);
2769 SIPE_DEBUG_INFO("sipe_process_roaming_self: federatedAL=%d", federatedAL);
2770 /* initial set-up to let counterparties see your status */
2771 if (sameEnterpriseAL < 0) {
2772 struct sipe_container *container = sipe_find_container(sipe_private, 200);
2773 guint version = container ? container->version : 0;
2774 sipe_send_container_members_prepare(200, version, "add", "sameEnterprise", NULL, &container_xmls);
2776 if (federatedAL < 0) {
2777 struct sipe_container *container = sipe_find_container(sipe_private, 100);
2778 guint version = container ? container->version : 0;
2779 sipe_send_container_members_prepare(100, version, "add", "federated", NULL, &container_xmls);
2781 sip->access_level_set = TRUE;
2783 if (container_xmls) {
2784 sipe_send_set_container_members(sipe_private, container_xmls);
2786 g_free(container_xmls);
2789 /* Refresh contacts' blocked status */
2790 sipe_refresh_blocked_status(sipe_private);
2792 /* subscribers */
2793 for (node = sipe_xml_child(xml, "subscribers/subscriber"); node; node = sipe_xml_twin(node)) {
2794 const char *user;
2795 const char *acknowledged;
2796 gchar *hdr;
2797 gchar *body;
2799 user = sipe_xml_attribute(node, "user"); /* without 'sip:' prefix */
2800 if (!user) continue;
2801 SIPE_DEBUG_INFO("sipe_process_roaming_self: user %s", user);
2802 display_name = g_strdup(sipe_xml_attribute(node, "displayName"));
2803 uri = sip_uri_from_name(user);
2805 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_DISPLAY_NAME, display_name);
2807 acknowledged= sipe_xml_attribute(node, "acknowledged");
2808 if(sipe_strcase_equal(acknowledged,"false")){
2809 SIPE_DEBUG_INFO("sipe_process_roaming_self: user added you %s", user);
2810 if (!sipe_backend_buddy_find(SIPE_CORE_PUBLIC, uri, NULL)) {
2811 sipe_backend_buddy_request_add(SIPE_CORE_PUBLIC, uri, display_name);
2814 hdr = g_strdup_printf(
2815 "Contact: %s\r\n"
2816 "Content-Type: application/msrtc-presence-setsubscriber+xml\r\n", contact);
2818 body = g_strdup_printf(
2819 "<setSubscribers xmlns=\"http://schemas.microsoft.com/2006/09/sip/presence-subscribers\">"
2820 "<subscriber user=\"%s\" acknowledged=\"true\"/>"
2821 "</setSubscribers>", user);
2823 sip_transport_service(sipe_private,
2825 hdr,
2826 body,
2827 NULL);
2828 g_free(body);
2829 g_free(hdr);
2831 g_free(display_name);
2832 g_free(uri);
2835 g_free(contact);
2836 sipe_xml_free(xml);
2838 /* Publish initial state if not yet.
2839 * Assuming this happens on initial responce to subscription to roaming-self
2840 * so we've already updated our roaming data in full.
2841 * Only for 2007+
2843 if (!sip->initial_state_published) {
2844 send_publish_category_initial(sipe_private);
2845 sipe_groupchat_init(sipe_private);
2846 sip->initial_state_published = TRUE;
2847 /* dalayed run */
2848 sipe_schedule_seconds(sipe_private,
2849 "<+update-calendar>",
2850 NULL,
2851 UPDATE_CALENDAR_DELAY,
2852 (sipe_schedule_action)sipe_core_update_calendar,
2853 NULL);
2854 do_update_status = FALSE;
2855 } else if (aggreg_avail) {
2857 g_free(sip->status);
2858 if (aggreg_avail && aggreg_avail < 18000) { /* not offline */
2859 sip->status = g_strdup(sipe_get_status_by_availability(aggreg_avail, NULL));
2860 } else {
2861 sip->status = g_strdup(SIPE_STATUS_ID_INVISIBLE); /* not not let offline status switch us off */
2865 if (do_update_status) {
2866 SIPE_DEBUG_INFO("sipe_process_roaming_self: switch to '%s' for the account", sip->status);
2867 sipe_set_purple_account_status_and_note(sip->account, sip->status, sip->note, sip->do_not_publish);
2870 g_free(to);
2873 /* IM Session (INVITE and MESSAGE methods) */
2875 /* EndPoints: "alice alisson" <sip:alice@atlanta.local>, <sip:bob@atlanta.local>;epid=ebca82d94d, <sip:carol@atlanta.local> */
2876 static gchar *
2877 get_end_points (struct sipe_core_private *sipe_private,
2878 struct sip_session *session)
2880 gchar *res;
2882 if (session == NULL) {
2883 return NULL;
2886 res = g_strdup_printf("<sip:%s>", sipe_private->username);
2888 SIPE_DIALOG_FOREACH {
2889 gchar *tmp = res;
2890 res = g_strdup_printf("%s, <%s>", res, dialog->with);
2891 g_free(tmp);
2893 if (dialog->theirepid) {
2894 tmp = res;
2895 res = g_strdup_printf("%s;epid=%s", res, dialog->theirepid);
2896 g_free(tmp);
2898 } SIPE_DIALOG_FOREACH_END;
2900 return res;
2903 static gboolean
2904 process_options_response(SIPE_UNUSED_PARAMETER struct sipe_core_private *sipe_private,
2905 struct sipmsg *msg,
2906 SIPE_UNUSED_PARAMETER struct transaction *trans)
2908 gboolean ret = TRUE;
2910 if (msg->response != 200) {
2911 SIPE_DEBUG_INFO("process_options_response: OPTIONS response is %d", msg->response);
2912 return FALSE;
2915 SIPE_DEBUG_INFO("process_options_response: body:\n%s", msg->body ? msg->body : "");
2917 return ret;
2921 * Asks UA/proxy about its capabilities.
2923 static void sipe_options_request(struct sipe_core_private *sipe_private,
2924 const char *who)
2926 gchar *to = sip_uri(who);
2927 gchar *contact = get_contact(sipe_private);
2928 gchar *request = g_strdup_printf(
2929 "Accept: application/sdp\r\n"
2930 "Contact: %s\r\n", contact);
2931 g_free(contact);
2933 sip_transport_request(sipe_private,
2934 "OPTIONS",
2937 request,
2938 NULL,
2939 NULL,
2940 process_options_response);
2942 g_free(to);
2943 g_free(request);
2946 void
2947 sipe_present_info(struct sipe_core_private *sipe_private,
2948 struct sip_session *session,
2949 const gchar *message)
2951 sipe_backend_notify_message_info(SIPE_CORE_PUBLIC,
2952 session->chat_session ? session->chat_session->backend : NULL,
2953 session->with,
2954 message);
2957 void
2958 sipe_present_err(struct sipe_core_private *sipe_private,
2959 struct sip_session *session,
2960 const gchar *message)
2962 sipe_backend_notify_message_error(SIPE_CORE_PUBLIC,
2963 session->chat_session ? session->chat_session->backend : NULL,
2964 session->with,
2965 message);
2968 void
2969 sipe_present_message_undelivered_err(struct sipe_core_private *sipe_private,
2970 struct sip_session *session,
2971 int sip_error,
2972 int sip_warning,
2973 const gchar *who,
2974 const gchar *message)
2976 char *msg, *msg_tmp, *msg_tmp2;
2977 const char *label;
2979 msg_tmp = message ? sipe_backend_markup_strip_html(message) : NULL;
2980 msg = msg_tmp ? g_strdup_printf("<font color=\"#888888\"></b>%s<b></font>", msg_tmp) : NULL;
2981 g_free(msg_tmp);
2982 /* Service unavailable; Server Internal Error; Server Time-out */
2983 if (sip_error == 606 && sip_warning == 309) { /* Not acceptable all. */ /* Message contents not allowed by policy */
2984 label = _("Your message or invitation was not delivered, possibly because it contains a hyperlink or other content that the system administrator has blocked.");
2985 g_free(msg);
2986 msg = NULL;
2987 } else if (sip_error == 500 || sip_error == 503 || sip_error == 504 || sip_error == 603) {
2988 label = _("This message was not delivered to %s because the service is not available");
2989 } else if (sip_error == 486) { /* Busy Here */
2990 label = _("This message was not delivered to %s because one or more recipients do not want to be disturbed");
2991 } else if (sip_error == 415) { /* Unsupported media type */
2992 label = _("This message was not delivered to %s because one or more recipients don't support this type of message");
2993 } else {
2994 label = _("This message was not delivered to %s because one or more recipients are offline");
2997 msg_tmp = g_strdup_printf( "%s%s\n%s" ,
2998 msg_tmp2 = g_strdup_printf(label, who ? who : ""),
2999 msg ? ":" : "",
3000 msg ? msg : "");
3001 sipe_present_err(sipe_private, session, msg_tmp);
3002 g_free(msg_tmp2);
3003 g_free(msg_tmp);
3004 g_free(msg);
3008 static gboolean
3009 process_message_response(struct sipe_core_private *sipe_private,
3010 struct sipmsg *msg,
3011 SIPE_UNUSED_PARAMETER struct transaction *trans)
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 sipe_backend_buddy pbuddy;
3041 gchar *alias = g_strdup(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 = sipe_backend_buddy_find(SIPE_CORE_PUBLIC, with, NULL))) {
3066 g_free(alias);
3067 alias = sipe_backend_buddy_get_alias(SIPE_CORE_PUBLIC,pbuddy);
3070 sipe_present_message_undelivered_err(sipe_private, session, msg->response, warning, alias, (message ? message->body : NULL));
3072 /* drop dangling IM sessions: assume that BYE from remote never reached us */
3073 if (msg->response == 408 || /* Request timeout */
3074 msg->response == 480 || /* Temporarily Unavailable */
3075 msg->response == 481) { /* Call/Transaction Does Not Exist */
3076 SIPE_DEBUG_INFO_NOFORMAT("process_message_response: assuming dangling IM session, dropping it.");
3077 sip_transport_bye(sipe_private, dialog);
3079 /* We might not get a valid reply to our BYE,
3080 so make sure the dialog is removed for sure. */
3081 sipe_dialog_remove(session, with);
3082 dialog = NULL;
3085 g_free(alias);
3086 ret = FALSE;
3087 } else {
3088 const gchar *message_id = sipmsg_find_header(msg, "Message-Id");
3089 if (message_id) {
3090 g_hash_table_insert(session->conf_unconfirmed_messages, g_strdup(message_id), g_strdup(message->body));
3091 SIPE_DEBUG_INFO("process_message_response: added message with id %s to conf_unconfirmed_messages(count=%d)",
3092 message_id, g_hash_table_size(session->conf_unconfirmed_messages));
3095 g_hash_table_remove(session->unconfirmed_messages, key);
3096 SIPE_DEBUG_INFO("process_message_response: removed message %s from unconfirmed_messages(count=%d)",
3097 key, g_hash_table_size(session->unconfirmed_messages));
3100 g_free(key);
3101 g_free(with);
3103 if (ret) sipe_im_process_queue(sipe_private, session);
3104 return ret;
3107 static void sipe_send_message(struct sipe_core_private *sipe_private,
3108 struct sip_dialog *dialog,
3109 const char *msg, const char *content_type)
3111 gchar *hdr;
3112 gchar *tmp;
3113 char *msgtext = NULL;
3114 const gchar *msgr = "";
3115 gchar *tmp2 = NULL;
3117 if (content_type == NULL)
3118 content_type = "text/plain";
3120 if (!g_str_has_prefix(content_type, "text/x-msmsgsinvite")) {
3121 char *msgformat;
3122 gchar *msgr_value;
3124 sipe_parse_html(msg, &msgformat, &msgtext);
3125 SIPE_DEBUG_INFO("sipe_send_message: msgformat=%s", msgformat);
3127 msgr_value = sipmsg_get_msgr_string(msgformat);
3128 g_free(msgformat);
3129 if (msgr_value) {
3130 msgr = tmp2 = g_strdup_printf(";msgr=%s", msgr_value);
3131 g_free(msgr_value);
3133 } else {
3134 msgtext = g_strdup(msg);
3137 tmp = get_contact(sipe_private);
3138 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8\r\n");
3139 //hdr = g_strdup("Content-Type: text/rtf\r\n");
3140 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8;msgr=WAAtAE0ATQBTAC....AoADQA\r\nSupported: timer\r\n");
3142 hdr = g_strdup_printf("Contact: %s\r\nContent-Type: %s; charset=UTF-8%s\r\n", tmp, content_type, msgr);
3143 g_free(tmp);
3144 g_free(tmp2);
3146 sip_transport_request(sipe_private,
3147 "MESSAGE",
3148 dialog->with,
3149 dialog->with,
3150 hdr,
3151 msgtext,
3152 dialog,
3153 process_message_response);
3154 g_free(msgtext);
3155 g_free(hdr);
3159 void
3160 sipe_im_process_queue (struct sipe_core_private *sipe_private,
3161 struct sip_session * session)
3163 GSList *entry2 = session->outgoing_message_queue;
3164 while (entry2) {
3165 struct queued_message *msg = entry2->data;
3167 /* for multiparty chat or conference */
3168 if (session->chat_session) {
3169 gchar *who = sip_uri_self(sipe_private);
3170 sipe_backend_chat_message(SIPE_CORE_PUBLIC,
3171 session->chat_session->backend,
3172 who,
3173 msg->body);
3174 g_free(who);
3177 SIPE_DIALOG_FOREACH {
3178 char *key;
3179 struct queued_message *message;
3181 if (dialog->outgoing_invite) continue; /* do not send messages as INVITE is not responded. */
3183 message = g_new0(struct queued_message,1);
3184 message->body = g_strdup(msg->body);
3185 if (msg->content_type != NULL)
3186 message->content_type = g_strdup(msg->content_type);
3188 key = g_strdup_printf("<%s><%d><MESSAGE><%s>", dialog->callid, (dialog->cseq) + 1, dialog->with);
3189 g_hash_table_insert(session->unconfirmed_messages, g_strdup(key), message);
3190 SIPE_DEBUG_INFO("sipe_im_process_queue: added message %s to unconfirmed_messages(count=%d)",
3191 key, g_hash_table_size(session->unconfirmed_messages));
3192 g_free(key);
3194 sipe_send_message(sipe_private, dialog, msg->body, msg->content_type);
3195 } SIPE_DIALOG_FOREACH_END;
3197 entry2 = sipe_session_dequeue_message(session);
3201 static void
3202 sipe_refer_notify(struct sipe_core_private *sipe_private,
3203 struct sip_session *session,
3204 const gchar *who,
3205 int status,
3206 const gchar *desc)
3208 gchar *hdr;
3209 gchar *body;
3210 struct sip_dialog *dialog = sipe_dialog_find(session, who);
3212 hdr = g_strdup_printf(
3213 "Event: refer\r\n"
3214 "Subscription-State: %s\r\n"
3215 "Content-Type: message/sipfrag\r\n",
3216 status >= 200 ? "terminated" : "active");
3218 body = g_strdup_printf(
3219 "SIP/2.0 %d %s\r\n",
3220 status, desc);
3222 sip_transport_request(sipe_private,
3223 "NOTIFY",
3224 who,
3225 who,
3226 hdr,
3227 body,
3228 dialog,
3229 NULL);
3231 g_free(hdr);
3232 g_free(body);
3235 static gboolean
3236 process_invite_response(struct sipe_core_private *sipe_private,
3237 struct sipmsg *msg, struct transaction *trans)
3239 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
3240 struct sip_session *session;
3241 struct sip_dialog *dialog;
3242 char *cseq;
3243 char *key;
3244 struct queued_message *message;
3245 struct sipmsg *request_msg = trans->msg;
3247 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
3248 gchar *referred_by;
3250 session = sipe_session_find_chat_or_im(sipe_private, callid, with);
3251 if (!session) {
3252 SIPE_DEBUG_INFO_NOFORMAT("process_invite_response: unable to find IM session");
3253 g_free(with);
3254 return FALSE;
3257 dialog = sipe_dialog_find(session, with);
3258 if (!dialog) {
3259 SIPE_DEBUG_INFO_NOFORMAT("process_invite_response: session outgoing dialog is NULL");
3260 g_free(with);
3261 return FALSE;
3264 sipe_dialog_parse(dialog, msg, TRUE);
3266 cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
3267 key = g_strdup_printf("<%s><%d><INVITE>", dialog->callid, atoi(cseq));
3268 g_free(cseq);
3269 message = g_hash_table_lookup(session->unconfirmed_messages, key);
3271 if (msg->response != 200) {
3272 sipe_backend_buddy pbuddy;
3273 gchar *alias = g_strdup(with);
3274 const char *warn_hdr = sipmsg_find_header(msg, "Warning");
3275 int warning = -1;
3277 SIPE_DEBUG_INFO_NOFORMAT("process_invite_response: INVITE response not 200");
3279 if (warn_hdr) {
3280 gchar **parts = g_strsplit(warn_hdr, " ", 2);
3281 if (parts[0]) {
3282 warning = atoi(parts[0]);
3284 g_strfreev(parts);
3287 /* cancel file transfer as rejected by server */
3288 if (msg->response == 606 && /* Not acceptable all. */
3289 warning == 309 && /* Message contents not allowed by policy */
3290 message && g_str_has_prefix(message->content_type, "text/x-msmsgsinvite"))
3292 GSList *parsed_body = sipe_ft_parse_msg_body(message->body);
3293 sipe_ft_incoming_cancel(dialog, parsed_body);
3294 sipe_utils_nameval_free(parsed_body);
3297 if ((pbuddy = sipe_backend_buddy_find(SIPE_CORE_PUBLIC, with, NULL))) {
3298 g_free(alias);
3299 alias = sipe_backend_buddy_get_alias(SIPE_CORE_PUBLIC, pbuddy);
3302 if (message) {
3303 sipe_present_message_undelivered_err(sipe_private, session, msg->response, warning, alias, message->body);
3304 } else {
3305 gchar *tmp_msg = g_strdup_printf(_("Failed to invite %s"), alias);
3306 sipe_present_err(sipe_private, session, tmp_msg);
3307 g_free(tmp_msg);
3310 sipe_dialog_remove(session, with);
3312 if (session->is_groupchat) {
3313 sipe_groupchat_invite_failed(sipe_private, session);
3316 g_free(key);
3317 g_free(with);
3318 g_free(alias);
3319 return FALSE;
3322 dialog->cseq = 0;
3323 sip_transport_ack(sipe_private, dialog);
3324 dialog->outgoing_invite = NULL;
3325 dialog->is_established = TRUE;
3327 referred_by = parse_from(sipmsg_find_header(request_msg, "Referred-By"));
3328 if (referred_by) {
3329 sipe_refer_notify(sipe_private, session, referred_by, 200, "OK");
3330 g_free(referred_by);
3333 /* add user to chat if it is a multiparty session */
3334 if (session->chat_session &&
3335 (session->chat_session->type == SIPE_CHAT_TYPE_MULTIPARTY)) {
3336 sipe_backend_chat_add(session->chat_session->backend,
3337 with,
3338 TRUE);
3341 if (session->is_groupchat) {
3342 sipe_groupchat_invite_response(sipe_private, dialog);
3345 if(g_slist_find_custom(dialog->supported, "ms-text-format", (GCompareFunc)g_ascii_strcasecmp)) {
3346 SIPE_DEBUG_INFO_NOFORMAT("process_invite_response: remote system accepted message in INVITE");
3347 sipe_session_dequeue_message(session);
3350 sipe_im_process_queue(sipe_private, session);
3352 g_hash_table_remove(session->unconfirmed_messages, key);
3353 SIPE_DEBUG_INFO("process_invite_response: removed message %s from unconfirmed_messages(count=%d)",
3354 key, g_hash_table_size(session->unconfirmed_messages));
3356 g_free(key);
3357 g_free(with);
3358 return TRUE;
3362 void
3363 sipe_invite(struct sipe_core_private *sipe_private,
3364 struct sip_session *session,
3365 const gchar *who,
3366 const gchar *msg_body,
3367 const gchar *msg_content_type,
3368 const gchar *referred_by,
3369 const gboolean is_triggered)
3371 gchar *hdr;
3372 gchar *to;
3373 gchar *contact;
3374 gchar *body;
3375 gchar *self;
3376 char *ms_text_format = NULL;
3377 char *ms_conversation_id = NULL;
3378 gchar *roster_manager;
3379 gchar *end_points;
3380 gchar *referred_by_str;
3381 gboolean is_multiparty =
3382 session->chat_session &&
3383 (session->chat_session->type == SIPE_CHAT_TYPE_MULTIPARTY);
3384 struct sip_dialog *dialog = sipe_dialog_find(session, who);
3386 if (dialog && dialog->is_established) {
3387 SIPE_DEBUG_INFO("session with %s already has a dialog open", who);
3388 return;
3391 if (!dialog) {
3392 dialog = sipe_dialog_add(session);
3393 dialog->callid = session->callid ? g_strdup(session->callid) : gencallid();
3394 dialog->with = g_strdup(who);
3397 if (!(dialog->ourtag)) {
3398 dialog->ourtag = gentag();
3401 to = sip_uri(who);
3403 if (msg_body) {
3404 char *msgtext = NULL;
3405 char *base64_msg;
3406 const gchar *msgr = "";
3407 char *key;
3408 struct queued_message *message;
3409 gchar *tmp = NULL;
3411 if (!g_str_has_prefix(msg_content_type, "text/x-msmsgsinvite")) {
3412 char *msgformat;
3413 gchar *msgr_value;
3415 sipe_parse_html(msg_body, &msgformat, &msgtext);
3416 SIPE_DEBUG_INFO("sipe_invite: msgformat=%s", msgformat);
3418 msgr_value = sipmsg_get_msgr_string(msgformat);
3419 g_free(msgformat);
3420 if (msgr_value) {
3421 msgr = tmp = g_strdup_printf(";msgr=%s", msgr_value);
3422 g_free(msgr_value);
3425 /* When Sipe reconnects after a crash, we are not able
3426 * to send messages to contacts with which we had open
3427 * conversations when the crash occured. Server sends
3428 * error response with reason="This client has an IM
3429 * session with the same conversation ID"
3431 * Setting random Ms-Conversation-ID prevents this problem
3432 * so we can continue the conversation. */
3433 ms_conversation_id = g_strdup_printf("Ms-Conversation-ID: %u\r\n",
3434 rand() % 1000000000);
3435 } else {
3436 msgtext = g_strdup(msg_body);
3439 base64_msg = g_base64_encode((guchar*) msgtext, strlen(msgtext));
3440 ms_text_format = g_strdup_printf(SIPE_INVITE_TEXT,
3441 msg_content_type ? msg_content_type : "text/plain",
3442 msgr,
3443 base64_msg);
3444 g_free(msgtext);
3445 g_free(tmp);
3446 g_free(base64_msg);
3448 message = g_new0(struct queued_message,1);
3449 message->body = g_strdup(msg_body);
3450 if (msg_content_type != NULL)
3451 message->content_type = g_strdup(msg_content_type);
3453 key = g_strdup_printf("<%s><%d><INVITE>", dialog->callid, (dialog->cseq) + 1);
3454 g_hash_table_insert(session->unconfirmed_messages, g_strdup(key), message);
3455 SIPE_DEBUG_INFO("sipe_invite: added message %s to unconfirmed_messages(count=%d)",
3456 key, g_hash_table_size(session->unconfirmed_messages));
3457 g_free(key);
3460 contact = get_contact(sipe_private);
3461 end_points = get_end_points(sipe_private, session);
3462 self = sip_uri_self(sipe_private);
3463 roster_manager = g_strdup_printf(
3464 "Roster-Manager: %s\r\n"
3465 "EndPoints: %s\r\n",
3466 self,
3467 end_points);
3468 referred_by_str = referred_by ?
3469 g_strdup_printf(
3470 "Referred-By: %s\r\n",
3471 referred_by)
3472 : g_strdup("");
3473 hdr = g_strdup_printf(
3474 "Supported: ms-sender\r\n"
3475 "%s"
3476 "%s"
3477 "%s"
3478 "%s"
3479 "Contact: %s\r\n%s"
3480 "%s"
3481 "Content-Type: application/sdp\r\n",
3482 is_multiparty && sipe_strcase_equal(session->chat_session->id, self) ? roster_manager : "",
3483 referred_by_str,
3484 is_triggered ? "TriggeredInvite: TRUE\r\n" : "",
3485 is_triggered || is_multiparty ? "Require: com.microsoft.rtc-multiparty\r\n" : "",
3486 contact,
3487 ms_text_format ? ms_text_format : "",
3488 ms_conversation_id ? ms_conversation_id : "");
3489 g_free(ms_text_format);
3490 g_free(ms_conversation_id);
3491 g_free(self);
3493 body = g_strdup_printf(
3494 "v=0\r\n"
3495 "o=- 0 0 IN IP4 %s\r\n"
3496 "s=session\r\n"
3497 "c=IN IP4 %s\r\n"
3498 "t=0 0\r\n"
3499 "m=%s %d sip null\r\n"
3500 "a=accept-types:" SDP_ACCEPT_TYPES "\r\n",
3501 sipe_backend_network_ip_address(),
3502 sipe_backend_network_ip_address(),
3503 SIPE_CORE_PRIVATE_FLAG_IS(OCS2007) ? "message" : "x-ms-message",
3504 sip_transport_port(sipe_private));
3506 dialog->outgoing_invite = sip_transport_request(sipe_private,
3507 "INVITE",
3510 hdr,
3511 body,
3512 dialog,
3513 process_invite_response);
3515 g_free(to);
3516 g_free(roster_manager);
3517 g_free(end_points);
3518 g_free(referred_by_str);
3519 g_free(body);
3520 g_free(hdr);
3521 g_free(contact);
3524 void
3525 sipe_convo_closed(PurpleConnection * gc, const char *who)
3527 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
3529 SIPE_DEBUG_INFO("conversation with %s closed", who);
3530 sipe_session_close(sipe_private,
3531 sipe_session_find_im(sipe_private, who));
3534 int sipe_im_send(PurpleConnection *gc, const char *who, const char *what,
3535 SIPE_UNUSED_PARAMETER PurpleMessageFlags flags)
3537 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
3538 struct sip_session *session;
3539 struct sip_dialog *dialog;
3540 gchar *uri = sip_uri(who);
3542 SIPE_DEBUG_INFO("sipe_im_send what='%s'", what);
3544 session = sipe_session_find_or_add_im(sipe_private, uri);
3545 dialog = sipe_dialog_find(session, uri);
3547 // Queue the message
3548 sipe_session_enqueue_message(session, what, NULL);
3550 if (dialog && !dialog->outgoing_invite) {
3551 sipe_im_process_queue(sipe_private, session);
3552 } else if (!dialog || !dialog->outgoing_invite) {
3553 // Need to send the INVITE to get the outgoing dialog setup
3554 sipe_invite(sipe_private, session, uri, what, NULL, NULL, FALSE);
3557 g_free(uri);
3558 return 1;
3562 * Returns 2005-style activity and Availability.
3564 * @param status Sipe statis id.
3566 static void
3567 sipe_get_act_avail_by_status_2005(const char *status,
3568 int *activity,
3569 int *availability)
3571 int avail = 300; /* online */
3572 int act = 400; /* Available */
3574 if (sipe_strequal(status, SIPE_STATUS_ID_AWAY)) {
3575 act = 100;
3576 //} else if (sipe_strequal(status, SIPE_STATUS_ID_LUNCH)) {
3577 // act = 150;
3578 } else if (sipe_strequal(status, SIPE_STATUS_ID_BRB)) {
3579 act = 300;
3580 } else if (sipe_strequal(status, SIPE_STATUS_ID_AVAILABLE)) {
3581 act = 400;
3582 //} else if (sipe_strequal(status, SIPE_STATUS_ID_ON_PHONE)) {
3583 // act = 500;
3584 } else if (sipe_strequal(status, SIPE_STATUS_ID_BUSY) ||
3585 sipe_strequal(status, SIPE_STATUS_ID_DND)) {
3586 act = 600;
3587 } else if (sipe_strequal(status, SIPE_STATUS_ID_INVISIBLE) ||
3588 sipe_strequal(status, SIPE_STATUS_ID_OFFLINE)) {
3589 avail = 0; /* offline */
3590 act = 100;
3591 } else {
3592 act = 400; /* Available */
3595 if (activity) *activity = act;
3596 if (availability) *availability = avail;
3600 * [MS-SIP] 2.2.1
3602 * @param activity 2005 aggregated activity. Ex.: 600
3603 * @param availablity 2005 aggregated availablity. Ex.: 300
3605 static const char *
3606 sipe_get_status_by_act_avail_2005(const int activity,
3607 const int availablity,
3608 char **activity_desc)
3610 const char *status_id = NULL;
3611 const char *act = NULL;
3613 if (activity < 150) {
3614 status_id = SIPE_STATUS_ID_AWAY;
3615 } else if (activity < 200) {
3616 //status_id = SIPE_STATUS_ID_LUNCH;
3617 status_id = SIPE_STATUS_ID_AWAY;
3618 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_LUNCH);
3619 } else if (activity < 300) {
3620 //status_id = SIPE_STATUS_ID_IDLE;
3621 status_id = SIPE_STATUS_ID_AWAY;
3622 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_INACTIVE);
3623 } else if (activity < 400) {
3624 status_id = SIPE_STATUS_ID_BRB;
3625 } else if (activity < 500) {
3626 status_id = SIPE_STATUS_ID_AVAILABLE;
3627 } else if (activity < 600) {
3628 //status_id = SIPE_STATUS_ID_ON_PHONE;
3629 status_id = SIPE_STATUS_ID_BUSY;
3630 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_ON_PHONE);
3631 } else if (activity < 700) {
3632 status_id = SIPE_STATUS_ID_BUSY;
3633 } else if (activity < 800) {
3634 status_id = SIPE_STATUS_ID_AWAY;
3635 } else {
3636 status_id = SIPE_STATUS_ID_AVAILABLE;
3639 if (availablity < 100)
3640 status_id = SIPE_STATUS_ID_OFFLINE;
3642 if (activity_desc && act) {
3643 g_free(*activity_desc);
3644 *activity_desc = g_strdup(act);
3647 return status_id;
3651 * [MS-PRES] Table 3: Conversion of legacyInterop elements and attributes to MSRTC elements and attributes.
3653 static const char*
3654 sipe_get_status_by_availability(int avail,
3655 char** activity_desc)
3657 const char *status;
3658 const char *act = NULL;
3660 if (avail < 3000) {
3661 status = SIPE_STATUS_ID_OFFLINE;
3662 } else if (avail < 4500) {
3663 status = SIPE_STATUS_ID_AVAILABLE;
3664 } else if (avail < 6000) {
3665 //status = SIPE_STATUS_ID_IDLE;
3666 status = SIPE_STATUS_ID_AVAILABLE;
3667 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_INACTIVE);
3668 } else if (avail < 7500) {
3669 status = SIPE_STATUS_ID_BUSY;
3670 } else if (avail < 9000) {
3671 //status = SIPE_STATUS_ID_BUSYIDLE;
3672 status = SIPE_STATUS_ID_BUSY;
3673 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_BUSYIDLE);
3674 } else if (avail < 12000) {
3675 status = SIPE_STATUS_ID_DND;
3676 } else if (avail < 15000) {
3677 status = SIPE_STATUS_ID_BRB;
3678 } else if (avail < 18000) {
3679 status = SIPE_STATUS_ID_AWAY;
3680 } else {
3681 status = SIPE_STATUS_ID_OFFLINE;
3684 if (activity_desc && act) {
3685 g_free(*activity_desc);
3686 *activity_desc = g_strdup(act);
3689 return status;
3693 * Returns 2007-style availability value
3695 * @param sipe_status_id (in)
3696 * @param activity_token (out) Must be g_free()'d after use if consumed.
3698 static int
3699 sipe_get_availability_by_status(const char* sipe_status_id, char** activity_token)
3701 int availability;
3702 sipe_activity activity = SIPE_ACTIVITY_UNSET;
3704 if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_AWAY)) {
3705 availability = 15500;
3706 if (!activity_token || !(*activity_token)) {
3707 activity = SIPE_ACTIVITY_AWAY;
3709 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_BRB)) {
3710 availability = 12500;
3711 activity = SIPE_ACTIVITY_BRB;
3712 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_DND)) {
3713 availability = 9500;
3714 activity = SIPE_ACTIVITY_DND;
3715 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_BUSY)) {
3716 availability = 6500;
3717 if (!activity_token || !(*activity_token)) {
3718 activity = SIPE_ACTIVITY_BUSY;
3720 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_AVAILABLE)) {
3721 availability = 3500;
3722 activity = SIPE_ACTIVITY_ONLINE;
3723 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_UNKNOWN)) {
3724 availability = 0;
3725 } else {
3726 // Offline or invisible
3727 availability = 18500;
3728 activity = SIPE_ACTIVITY_OFFLINE;
3731 if (activity_token) {
3732 *activity_token = g_strdup(sipe_activity_map[activity].token);
3734 return availability;
3737 static void process_incoming_notify_rlmi(struct sipe_core_private *sipe_private,
3738 const gchar *data, unsigned len)
3740 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
3741 const char *uri;
3742 sipe_xml *xn_categories;
3743 const sipe_xml *xn_category;
3744 const char *status = NULL;
3745 gboolean do_update_status = FALSE;
3746 gboolean has_note_cleaned = FALSE;
3747 gboolean has_free_busy_cleaned = FALSE;
3749 xn_categories = sipe_xml_parse(data, len);
3750 uri = sipe_xml_attribute(xn_categories, "uri"); /* with 'sip:' prefix */
3752 for (xn_category = sipe_xml_child(xn_categories, "category");
3753 xn_category ;
3754 xn_category = sipe_xml_twin(xn_category) )
3756 const sipe_xml *xn_node;
3757 const char *tmp;
3758 const char *attrVar = sipe_xml_attribute(xn_category, "name");
3759 time_t publish_time = (tmp = sipe_xml_attribute(xn_category, "publishTime")) ?
3760 sipe_utils_str_to_time(tmp) : 0;
3762 /* contactCard */
3763 if (sipe_strequal(attrVar, "contactCard"))
3765 const sipe_xml *card = sipe_xml_child(xn_category, "contactCard");
3767 if (card) {
3768 const sipe_xml *node;
3769 /* identity - Display Name and email */
3770 node = sipe_xml_child(card, "identity");
3771 if (node) {
3772 char* display_name = sipe_xml_data(
3773 sipe_xml_child(node, "name/displayName"));
3774 char* email = sipe_xml_data(
3775 sipe_xml_child(node, "email"));
3777 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_DISPLAY_NAME, display_name);
3778 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_EMAIL, email);
3780 g_free(display_name);
3781 g_free(email);
3783 /* company */
3784 node = sipe_xml_child(card, "company");
3785 if (node) {
3786 char* company = sipe_xml_data(node);
3787 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_COMPANY, company);
3788 g_free(company);
3790 /* department */
3791 node = sipe_xml_child(card, "department");
3792 if (node) {
3793 char* department = sipe_xml_data(node);
3794 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_DEPARTMENT, department);
3795 g_free(department);
3797 /* title */
3798 node = sipe_xml_child(card, "title");
3799 if (node) {
3800 char* title = sipe_xml_data(node);
3801 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_JOB_TITLE, title);
3802 g_free(title);
3804 /* office */
3805 node = sipe_xml_child(card, "office");
3806 if (node) {
3807 char* office = sipe_xml_data(node);
3808 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_OFFICE, office);
3809 g_free(office);
3811 /* site (url) */
3812 node = sipe_xml_child(card, "url");
3813 if (node) {
3814 char* site = sipe_xml_data(node);
3815 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_SITE, site);
3816 g_free(site);
3818 /* phone */
3819 for (node = sipe_xml_child(card, "phone");
3820 node;
3821 node = sipe_xml_twin(node))
3823 const char *phone_type = sipe_xml_attribute(node, "type");
3824 char* phone = sipe_xml_data(sipe_xml_child(node, "uri"));
3825 char* phone_display_string = sipe_xml_data(sipe_xml_child(node, "displayString"));
3827 sipe_update_user_phone(sipe_private, uri, phone_type, phone, phone_display_string);
3829 g_free(phone);
3830 g_free(phone_display_string);
3832 /* address */
3833 for (node = sipe_xml_child(card, "address");
3834 node;
3835 node = sipe_xml_twin(node))
3837 if (sipe_strequal(sipe_xml_attribute(node, "type"), "work")) {
3838 char* street = sipe_xml_data(sipe_xml_child(node, "street"));
3839 char* city = sipe_xml_data(sipe_xml_child(node, "city"));
3840 char* state = sipe_xml_data(sipe_xml_child(node, "state"));
3841 char* zipcode = sipe_xml_data(sipe_xml_child(node, "zipcode"));
3842 char* country_code = sipe_xml_data(sipe_xml_child(node, "countryCode"));
3844 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_STREET, street);
3845 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_CITY, city);
3846 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_STATE, state);
3847 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_ZIPCODE, zipcode);
3848 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_COUNTRY, country_code);
3850 g_free(street);
3851 g_free(city);
3852 g_free(state);
3853 g_free(zipcode);
3854 g_free(country_code);
3856 break;
3861 /* note */
3862 else if (sipe_strequal(attrVar, "note"))
3864 if (uri) {
3865 struct sipe_buddy *sbuddy = g_hash_table_lookup(sipe_private->buddies, uri);
3867 if (!has_note_cleaned) {
3868 has_note_cleaned = TRUE;
3870 g_free(sbuddy->note);
3871 sbuddy->note = NULL;
3872 sbuddy->is_oof_note = FALSE;
3873 sbuddy->note_since = publish_time;
3875 do_update_status = TRUE;
3877 if (sbuddy && (publish_time >= sbuddy->note_since)) {
3878 /* clean up in case no 'note' element is supplied
3879 * which indicate note removal in client
3881 g_free(sbuddy->note);
3882 sbuddy->note = NULL;
3883 sbuddy->is_oof_note = FALSE;
3884 sbuddy->note_since = publish_time;
3886 xn_node = sipe_xml_child(xn_category, "note/body");
3887 if (xn_node) {
3888 char *tmp;
3889 sbuddy->note = g_markup_escape_text((tmp = sipe_xml_data(xn_node)), -1);
3890 g_free(tmp);
3891 sbuddy->is_oof_note = sipe_strequal(sipe_xml_attribute(xn_node, "type"), "OOF");
3892 sbuddy->note_since = publish_time;
3894 SIPE_DEBUG_INFO("process_incoming_notify_rlmi: uri(%s), note(%s)",
3895 uri, sbuddy->note ? sbuddy->note : "");
3897 /* to trigger UI refresh in case no status info is supplied in this update */
3898 do_update_status = TRUE;
3902 /* state */
3903 else if(sipe_strequal(attrVar, "state"))
3905 char *tmp;
3906 int availability;
3907 const sipe_xml *xn_availability;
3908 const sipe_xml *xn_activity;
3909 const sipe_xml *xn_meeting_subject;
3910 const sipe_xml *xn_meeting_location;
3911 struct sipe_buddy *sbuddy = uri ? g_hash_table_lookup(sipe_private->buddies, uri) : NULL;
3913 xn_node = sipe_xml_child(xn_category, "state");
3914 if (!xn_node) continue;
3915 xn_availability = sipe_xml_child(xn_node, "availability");
3916 if (!xn_availability) continue;
3917 xn_activity = sipe_xml_child(xn_node, "activity");
3918 xn_meeting_subject = sipe_xml_child(xn_node, "meetingSubject");
3919 xn_meeting_location = sipe_xml_child(xn_node, "meetingLocation");
3921 tmp = sipe_xml_data(xn_availability);
3922 availability = atoi(tmp);
3923 g_free(tmp);
3925 /* activity, meeting_subject, meeting_location */
3926 if (sbuddy) {
3927 char *tmp = NULL;
3929 /* activity */
3930 g_free(sbuddy->activity);
3931 sbuddy->activity = NULL;
3932 if (xn_activity) {
3933 const char *token = sipe_xml_attribute(xn_activity, "token");
3934 const sipe_xml *xn_custom = sipe_xml_child(xn_activity, "custom");
3936 /* from token */
3937 if (!is_empty(token)) {
3938 sbuddy->activity = g_strdup(sipe_get_activity_desc_by_token(token));
3940 /* from custom element */
3941 if (xn_custom) {
3942 char *custom = sipe_xml_data(xn_custom);
3944 if (!is_empty(custom)) {
3945 sbuddy->activity = custom;
3946 custom = NULL;
3948 g_free(custom);
3951 /* meeting_subject */
3952 g_free(sbuddy->meeting_subject);
3953 sbuddy->meeting_subject = NULL;
3954 if (xn_meeting_subject) {
3955 char *meeting_subject = sipe_xml_data(xn_meeting_subject);
3957 if (!is_empty(meeting_subject)) {
3958 sbuddy->meeting_subject = meeting_subject;
3959 meeting_subject = NULL;
3961 g_free(meeting_subject);
3963 /* meeting_location */
3964 g_free(sbuddy->meeting_location);
3965 sbuddy->meeting_location = NULL;
3966 if (xn_meeting_location) {
3967 char *meeting_location = sipe_xml_data(xn_meeting_location);
3969 if (!is_empty(meeting_location)) {
3970 sbuddy->meeting_location = meeting_location;
3971 meeting_location = NULL;
3973 g_free(meeting_location);
3976 status = sipe_get_status_by_availability(availability, &tmp);
3977 if (sbuddy->activity && tmp) {
3978 char *tmp2 = sbuddy->activity;
3980 sbuddy->activity = g_strdup_printf("%s, %s", sbuddy->activity, tmp);
3981 g_free(tmp);
3982 g_free(tmp2);
3983 } else if (tmp) {
3984 sbuddy->activity = tmp;
3988 do_update_status = TRUE;
3990 /* calendarData */
3991 else if(sipe_strequal(attrVar, "calendarData"))
3993 struct sipe_buddy *sbuddy = uri ? g_hash_table_lookup(sipe_private->buddies, uri) : NULL;
3994 const sipe_xml *xn_free_busy = sipe_xml_child(xn_category, "calendarData/freeBusy");
3995 const sipe_xml *xn_working_hours = sipe_xml_child(xn_category, "calendarData/WorkingHours");
3997 if (sbuddy && xn_free_busy) {
3998 if (!has_free_busy_cleaned) {
3999 has_free_busy_cleaned = TRUE;
4001 g_free(sbuddy->cal_start_time);
4002 sbuddy->cal_start_time = NULL;
4004 g_free(sbuddy->cal_free_busy_base64);
4005 sbuddy->cal_free_busy_base64 = NULL;
4007 g_free(sbuddy->cal_free_busy);
4008 sbuddy->cal_free_busy = NULL;
4010 sbuddy->cal_free_busy_published = publish_time;
4013 if (publish_time >= sbuddy->cal_free_busy_published) {
4014 g_free(sbuddy->cal_start_time);
4015 sbuddy->cal_start_time = g_strdup(sipe_xml_attribute(xn_free_busy, "startTime"));
4017 sbuddy->cal_granularity = sipe_strcase_equal(sipe_xml_attribute(xn_free_busy, "granularity"), "PT15M") ?
4018 15 : 0;
4020 g_free(sbuddy->cal_free_busy_base64);
4021 sbuddy->cal_free_busy_base64 = sipe_xml_data(xn_free_busy);
4023 g_free(sbuddy->cal_free_busy);
4024 sbuddy->cal_free_busy = NULL;
4026 sbuddy->cal_free_busy_published = publish_time;
4028 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);
4032 if (sbuddy && xn_working_hours) {
4033 sipe_cal_parse_working_hours(xn_working_hours, sbuddy);
4038 if (do_update_status) {
4039 if (!status) { /* no status category in this update, using contact's current status */
4040 PurpleBuddy *pbuddy = purple_find_buddy((PurpleAccount *)sip->account, uri);
4041 const PurplePresence *presence = purple_buddy_get_presence(pbuddy);
4042 const PurpleStatus *pstatus = purple_presence_get_active_status(presence);
4043 status = purple_status_get_id(pstatus);
4046 SIPE_DEBUG_INFO("process_incoming_notify_rlmi: %s", status);
4047 sipe_core_buddy_got_status(SIPE_CORE_PUBLIC, uri, status);
4050 sipe_xml_free(xn_categories);
4053 static void sipe_subscribe_poolfqdn_resource_uri(const char *host,
4054 GSList *server,
4055 struct sipe_core_private *sipe_private)
4057 struct presence_batched_routed *payload = g_malloc(sizeof(struct presence_batched_routed));
4058 SIPE_DEBUG_INFO("process_incoming_notify_rlmi_resub: pool(%s)", host);
4059 payload->host = g_strdup(host);
4060 payload->buddies = server;
4061 sipe_subscribe_presence_batched_routed(sipe_private,
4062 payload);
4063 sipe_subscribe_presence_batched_routed_free(payload);
4066 static void process_incoming_notify_rlmi_resub(struct sipe_core_private *sipe_private,
4067 const gchar *data, unsigned len)
4069 sipe_xml *xn_list;
4070 const sipe_xml *xn_resource;
4071 GHashTable *servers = g_hash_table_new_full(g_str_hash, g_str_equal,
4072 g_free, NULL);
4073 GSList *server;
4074 gchar *host;
4076 xn_list = sipe_xml_parse(data, len);
4078 for (xn_resource = sipe_xml_child(xn_list, "resource");
4079 xn_resource;
4080 xn_resource = sipe_xml_twin(xn_resource) )
4082 const char *uri, *state;
4083 const sipe_xml *xn_instance;
4085 xn_instance = sipe_xml_child(xn_resource, "instance");
4086 if (!xn_instance) continue;
4088 uri = sipe_xml_attribute(xn_resource, "uri");
4089 state = sipe_xml_attribute(xn_instance, "state");
4090 SIPE_DEBUG_INFO("process_incoming_notify_rlmi_resub: uri(%s),state(%s)", uri, state);
4092 if (strstr(state, "resubscribe")) {
4093 const char *poolFqdn = sipe_xml_attribute(xn_instance, "poolFqdn");
4095 if (poolFqdn) { //[MS-PRES] Section 3.4.5.1.3 Processing Details
4096 gchar *user = g_strdup(uri);
4097 host = g_strdup(poolFqdn);
4098 server = g_hash_table_lookup(servers, host);
4099 server = g_slist_append(server, user);
4100 g_hash_table_insert(servers, host, server);
4101 } else {
4102 sipe_subscribe_presence_single(sipe_private,
4103 (void *) uri);
4108 /* Send out any deferred poolFqdn subscriptions */
4109 g_hash_table_foreach(servers, (GHFunc) sipe_subscribe_poolfqdn_resource_uri, sipe_private);
4110 g_hash_table_destroy(servers);
4112 sipe_xml_free(xn_list);
4115 static void process_incoming_notify_pidf(struct sipe_core_private *sipe_private,
4116 const gchar *data, unsigned len)
4118 gchar *uri;
4119 gchar *getbasic;
4120 gchar *activity = NULL;
4121 sipe_xml *pidf;
4122 const sipe_xml *basicstatus = NULL, *tuple, *status;
4123 gboolean isonline = FALSE;
4124 const sipe_xml *display_name_node;
4126 pidf = sipe_xml_parse(data, len);
4127 if (!pidf) {
4128 SIPE_DEBUG_INFO("process_incoming_notify_pidf: no parseable pidf:%s", data);
4129 return;
4132 if ((tuple = sipe_xml_child(pidf, "tuple")))
4134 if ((status = sipe_xml_child(tuple, "status"))) {
4135 basicstatus = sipe_xml_child(status, "basic");
4139 if (!basicstatus) {
4140 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_notify_pidf: no basic found");
4141 sipe_xml_free(pidf);
4142 return;
4145 getbasic = sipe_xml_data(basicstatus);
4146 if (!getbasic) {
4147 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_notify_pidf: no basic data found");
4148 sipe_xml_free(pidf);
4149 return;
4152 SIPE_DEBUG_INFO("process_incoming_notify_pidf: basic-status(%s)", getbasic);
4153 if (strstr(getbasic, "open")) {
4154 isonline = TRUE;
4156 g_free(getbasic);
4158 uri = sip_uri(sipe_xml_attribute(pidf, "entity")); /* with 'sip:' prefix */ /* AOL comes without the prefix */
4160 display_name_node = sipe_xml_child(pidf, "display-name");
4161 if (display_name_node) {
4162 char * display_name = sipe_xml_data(display_name_node);
4164 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_DISPLAY_NAME, display_name);
4165 g_free(display_name);
4168 if ((tuple = sipe_xml_child(pidf, "tuple"))) {
4169 if ((status = sipe_xml_child(tuple, "status"))) {
4170 if ((basicstatus = sipe_xml_child(status, "activities"))) {
4171 if ((basicstatus = sipe_xml_child(basicstatus, "activity"))) {
4172 activity = sipe_xml_data(basicstatus);
4173 SIPE_DEBUG_INFO("process_incoming_notify_pidf: activity(%s)", activity);
4179 if (isonline) {
4180 const gchar * status_id = NULL;
4181 if (activity) {
4182 if (sipe_strequal(activity, sipe_activity_map[SIPE_ACTIVITY_BUSY].token)) {
4183 status_id = SIPE_STATUS_ID_BUSY;
4184 } else if (sipe_strequal(activity, sipe_activity_map[SIPE_ACTIVITY_AWAY].token)) {
4185 status_id = SIPE_STATUS_ID_AWAY;
4189 if (!status_id) {
4190 status_id = SIPE_STATUS_ID_AVAILABLE;
4193 SIPE_DEBUG_INFO("process_incoming_notify_pidf: status_id(%s)", status_id);
4194 sipe_core_buddy_got_status(SIPE_CORE_PUBLIC, uri, status_id);
4195 } else {
4196 sipe_core_buddy_got_status(SIPE_CORE_PUBLIC, uri, SIPE_STATUS_ID_OFFLINE);
4199 g_free(activity);
4200 g_free(uri);
4201 sipe_xml_free(pidf);
4204 /** 2005 */
4205 static void
4206 sipe_user_info_has_updated(struct sipe_core_private *sipe_private,
4207 const sipe_xml *xn_userinfo)
4209 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
4210 const sipe_xml *xn_states;
4212 g_free(sip->user_states);
4213 sip->user_states = NULL;
4214 if ((xn_states = sipe_xml_child(xn_userinfo, "states")) != NULL) {
4215 gchar *orig = sip->user_states = sipe_xml_stringify(xn_states);
4217 /* this is a hack-around to remove added newline after inner element,
4218 * state in this case, where it shouldn't be.
4219 * After several use of sipe_xml_stringify, amount of added newlines
4220 * grows significantly.
4222 if (orig) {
4223 gchar c, *stripped = orig;
4224 while ((c = *orig++)) {
4225 if ((c != '\n') /* && (c != '\r') */) {
4226 *stripped++ = c;
4229 *stripped = '\0';
4233 /* Publish initial state if not yet.
4234 * Assuming this happens on initial responce to self subscription
4235 * so we've already updated our UserInfo.
4237 if (!sip->initial_state_published) {
4238 send_presence_soap(sipe_private, FALSE);
4239 /* dalayed run */
4240 sipe_schedule_seconds(sipe_private,
4241 "<+update-calendar>",
4242 NULL,
4243 UPDATE_CALENDAR_DELAY,
4244 (sipe_schedule_action) sipe_core_update_calendar,
4245 NULL);
4249 static void process_incoming_notify_msrtc(struct sipe_core_private *sipe_private,
4250 const gchar *data, unsigned len)
4252 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
4253 char *activity = NULL;
4254 const char *epid;
4255 const char *status_id = NULL;
4256 const char *name;
4257 char *uri;
4258 char *self_uri = sip_uri_self(sipe_private);
4259 int avl;
4260 int act;
4261 const char *device_name = NULL;
4262 const char *cal_start_time = NULL;
4263 const char *cal_granularity = NULL;
4264 char *cal_free_busy_base64 = NULL;
4265 struct sipe_buddy *sbuddy;
4266 const sipe_xml *node;
4267 sipe_xml *xn_presentity;
4268 const sipe_xml *xn_availability;
4269 const sipe_xml *xn_activity;
4270 const sipe_xml *xn_display_name;
4271 const sipe_xml *xn_email;
4272 const sipe_xml *xn_phone_number;
4273 const sipe_xml *xn_userinfo;
4274 const sipe_xml *xn_note;
4275 const sipe_xml *xn_oof;
4276 const sipe_xml *xn_state;
4277 const sipe_xml *xn_contact;
4278 char *note;
4279 char *free_activity;
4280 int user_avail;
4281 const char *user_avail_nil;
4282 int res_avail;
4283 time_t user_avail_since = 0;
4284 time_t activity_since = 0;
4286 /* fix for Reuters environment on Linux */
4287 if (data && strstr(data, "encoding=\"utf-16\"")) {
4288 char *tmp_data;
4289 tmp_data = replace(data, "encoding=\"utf-16\"", "encoding=\"utf-8\"");
4290 xn_presentity = sipe_xml_parse(tmp_data, strlen(tmp_data));
4291 g_free(tmp_data);
4292 } else {
4293 xn_presentity = sipe_xml_parse(data, len);
4296 xn_availability = sipe_xml_child(xn_presentity, "availability");
4297 xn_activity = sipe_xml_child(xn_presentity, "activity");
4298 xn_display_name = sipe_xml_child(xn_presentity, "displayName");
4299 xn_email = sipe_xml_child(xn_presentity, "email");
4300 xn_phone_number = sipe_xml_child(xn_presentity, "phoneNumber");
4301 xn_userinfo = sipe_xml_child(xn_presentity, "userInfo");
4302 xn_oof = xn_userinfo ? sipe_xml_child(xn_userinfo, "oof") : NULL;
4303 xn_state = xn_userinfo ? sipe_xml_child(xn_userinfo, "states/state"): NULL;
4304 user_avail = xn_state ? sipe_xml_int_attribute(xn_state, "avail", 0) : 0;
4305 user_avail_since = xn_state ? sipe_utils_str_to_time(sipe_xml_attribute(xn_state, "since")) : 0;
4306 user_avail_nil = xn_state ? sipe_xml_attribute(xn_state, "nil") : NULL;
4307 xn_contact = xn_userinfo ? sipe_xml_child(xn_userinfo, "contact") : NULL;
4308 xn_note = xn_userinfo ? sipe_xml_child(xn_userinfo, "note") : NULL;
4309 note = xn_note ? sipe_xml_data(xn_note) : NULL;
4311 if (sipe_strequal(user_avail_nil, "true")) { /* null-ed */
4312 user_avail = 0;
4313 user_avail_since = 0;
4316 free_activity = NULL;
4318 name = sipe_xml_attribute(xn_presentity, "uri"); /* without 'sip:' prefix */
4319 uri = sip_uri_from_name(name);
4320 avl = sipe_xml_int_attribute(xn_availability, "aggregate", 0);
4321 epid = sipe_xml_attribute(xn_availability, "epid");
4322 act = sipe_xml_int_attribute(xn_activity, "aggregate", 0);
4324 status_id = sipe_get_status_by_act_avail_2005(act, avl, &activity);
4325 res_avail = sipe_get_availability_by_status(status_id, NULL);
4326 if (user_avail > res_avail) {
4327 res_avail = user_avail;
4328 status_id = sipe_get_status_by_availability(user_avail, NULL);
4331 if (xn_display_name) {
4332 char *display_name = g_strdup(sipe_xml_attribute(xn_display_name, "displayName"));
4333 char *email = xn_email ? g_strdup(sipe_xml_attribute(xn_email, "email")) : NULL;
4334 char *phone_label = xn_phone_number ? g_strdup(sipe_xml_attribute(xn_phone_number, "label")) : NULL;
4335 char *phone_number = xn_phone_number ? g_strdup(sipe_xml_attribute(xn_phone_number, "number")) : NULL;
4336 char *tel_uri = sip_to_tel_uri(phone_number);
4338 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_DISPLAY_NAME, display_name);
4339 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_EMAIL, email);
4340 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_WORK_PHONE, tel_uri);
4341 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_WORK_PHONE_DISPLAY, !is_empty(phone_label) ? phone_label : phone_number);
4343 g_free(tel_uri);
4344 g_free(phone_label);
4345 g_free(phone_number);
4346 g_free(email);
4347 g_free(display_name);
4350 if (xn_contact) {
4351 /* tel */
4352 for (node = sipe_xml_child(xn_contact, "tel"); node; node = sipe_xml_twin(node))
4354 /* Ex.: <tel type="work">tel:+3222220000</tel> */
4355 const char *phone_type = sipe_xml_attribute(node, "type");
4356 char* phone = sipe_xml_data(node);
4358 sipe_update_user_phone(sipe_private, uri, phone_type, phone, NULL);
4360 g_free(phone);
4364 /* devicePresence */
4365 for (node = sipe_xml_child(xn_presentity, "devices/devicePresence"); node; node = sipe_xml_twin(node)) {
4366 const sipe_xml *xn_device_name;
4367 const sipe_xml *xn_calendar_info;
4368 const sipe_xml *xn_state;
4369 char *state;
4371 /* deviceName */
4372 if (sipe_strequal(sipe_xml_attribute(node, "epid"), epid)) {
4373 xn_device_name = sipe_xml_child(node, "deviceName");
4374 device_name = xn_device_name ? sipe_xml_attribute(xn_device_name, "name") : NULL;
4377 /* calendarInfo */
4378 xn_calendar_info = sipe_xml_child(node, "calendarInfo");
4379 if (xn_calendar_info) {
4380 const char *cal_start_time_tmp = sipe_xml_attribute(xn_calendar_info, "startTime");
4382 if (cal_start_time) {
4383 time_t cal_start_time_t = sipe_utils_str_to_time(cal_start_time);
4384 time_t cal_start_time_t_tmp = sipe_utils_str_to_time(cal_start_time_tmp);
4386 if (cal_start_time_t_tmp > cal_start_time_t) {
4387 cal_start_time = cal_start_time_tmp;
4388 cal_granularity = sipe_xml_attribute(xn_calendar_info, "granularity");
4389 g_free(cal_free_busy_base64);
4390 cal_free_busy_base64 = sipe_xml_data(xn_calendar_info);
4392 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);
4394 } else {
4395 cal_start_time = cal_start_time_tmp;
4396 cal_granularity = sipe_xml_attribute(xn_calendar_info, "granularity");
4397 g_free(cal_free_busy_base64);
4398 cal_free_busy_base64 = sipe_xml_data(xn_calendar_info);
4400 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);
4404 /* state */
4405 xn_state = sipe_xml_child(node, "states/state");
4406 if (xn_state) {
4407 int dev_avail = sipe_xml_int_attribute(xn_state, "avail", 0);
4408 time_t dev_avail_since = sipe_utils_str_to_time(sipe_xml_attribute(xn_state, "since"));
4410 state = sipe_xml_data(xn_state);
4411 if (dev_avail_since > user_avail_since &&
4412 dev_avail >= res_avail)
4414 res_avail = dev_avail;
4415 if (!is_empty(state))
4417 if (sipe_strequal(state, sipe_activity_map[SIPE_ACTIVITY_ON_PHONE].token)) {
4418 g_free(activity);
4419 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_ON_PHONE));
4420 } else if (sipe_strequal(state, "presenting")) {
4421 g_free(activity);
4422 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_IN_CONF));
4423 } else {
4424 activity = state;
4425 state = NULL;
4427 activity_since = dev_avail_since;
4429 status_id = sipe_get_status_by_availability(res_avail, &activity);
4431 g_free(state);
4435 /* oof */
4436 if (xn_oof && res_avail >= 15000) { /* 12000 in 2007 */
4437 g_free(activity);
4438 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_OOF));
4439 activity_since = 0;
4442 sbuddy = g_hash_table_lookup(sipe_private->buddies, uri);
4443 if (sbuddy)
4445 g_free(sbuddy->activity);
4446 sbuddy->activity = activity;
4447 activity = NULL;
4449 sbuddy->activity_since = activity_since;
4451 sbuddy->user_avail = user_avail;
4452 sbuddy->user_avail_since = user_avail_since;
4454 g_free(sbuddy->note);
4455 sbuddy->note = NULL;
4456 if (!is_empty(note)) { sbuddy->note = g_markup_escape_text(note, -1); }
4458 sbuddy->is_oof_note = (xn_oof != NULL);
4460 g_free(sbuddy->device_name);
4461 sbuddy->device_name = NULL;
4462 if (!is_empty(device_name)) { sbuddy->device_name = g_strdup(device_name); }
4464 if (!is_empty(cal_free_busy_base64)) {
4465 g_free(sbuddy->cal_start_time);
4466 sbuddy->cal_start_time = g_strdup(cal_start_time);
4468 sbuddy->cal_granularity = sipe_strcase_equal(cal_granularity, "PT15M") ? 15 : 0;
4470 g_free(sbuddy->cal_free_busy_base64);
4471 sbuddy->cal_free_busy_base64 = cal_free_busy_base64;
4472 cal_free_busy_base64 = NULL;
4474 g_free(sbuddy->cal_free_busy);
4475 sbuddy->cal_free_busy = NULL;
4478 sbuddy->last_non_cal_status_id = status_id;
4479 g_free(sbuddy->last_non_cal_activity);
4480 sbuddy->last_non_cal_activity = g_strdup(sbuddy->activity);
4482 if (sipe_strcase_equal(sbuddy->name, self_uri)) {
4483 if (!sipe_strequal(sbuddy->note, sip->note)) /* not same */
4485 sip->is_oof_note = sbuddy->is_oof_note;
4487 g_free(sip->note);
4488 sip->note = g_strdup(sbuddy->note);
4490 sip->note_since = time(NULL);
4493 g_free(sip->status);
4494 sip->status = g_strdup(sbuddy->last_non_cal_status_id);
4497 g_free(cal_free_busy_base64);
4498 g_free(activity);
4500 SIPE_DEBUG_INFO("process_incoming_notify_msrtc: status(%s)", status_id);
4501 sipe_core_buddy_got_status(SIPE_CORE_PUBLIC, uri, status_id);
4503 if (!SIPE_CORE_PRIVATE_FLAG_IS(OCS2007) && sipe_strcase_equal(self_uri, uri)) {
4504 sipe_user_info_has_updated(sipe_private, xn_userinfo);
4507 g_free(note);
4508 sipe_xml_free(xn_presentity);
4509 g_free(uri);
4510 g_free(self_uri);
4513 static void sipe_presence_mime_cb(gpointer user_data, /* sipe_core_private */
4514 const GSList *fields,
4515 const gchar *body,
4516 gsize length)
4518 const gchar *type = sipe_utils_nameval_find(fields, "Content-Type");
4520 if (strstr(type,"application/rlmi+xml")) {
4521 process_incoming_notify_rlmi_resub(user_data, body, length);
4522 } else if (strstr(type, "text/xml+msrtc.pidf")) {
4523 process_incoming_notify_msrtc(user_data, body, length);
4524 } else {
4525 process_incoming_notify_rlmi(user_data, body, length);
4529 static void sipe_process_presence(struct sipe_core_private *sipe_private,
4530 struct sipmsg *msg)
4532 const char *ctype = sipmsg_find_header(msg, "Content-Type");
4534 SIPE_DEBUG_INFO("sipe_process_presence: Content-Type: %s", ctype ? ctype : "");
4536 if (ctype &&
4537 (strstr(ctype, "application/rlmi+xml") ||
4538 strstr(ctype, "application/msrtc-event-categories+xml")))
4540 if (strstr(ctype, "multipart"))
4542 sipe_mime_parts_foreach(ctype, msg->body, sipe_presence_mime_cb, sipe_private);
4544 else if(strstr(ctype, "application/msrtc-event-categories+xml") )
4546 process_incoming_notify_rlmi(sipe_private, msg->body, msg->bodylen);
4548 else if(strstr(ctype, "application/rlmi+xml"))
4550 process_incoming_notify_rlmi_resub(sipe_private, msg->body, msg->bodylen);
4553 else if(ctype && strstr(ctype, "text/xml+msrtc.pidf"))
4555 process_incoming_notify_msrtc(sipe_private, msg->body, msg->bodylen);
4557 else
4559 process_incoming_notify_pidf(sipe_private, msg->body, msg->bodylen);
4563 static void sipe_presence_timeout_mime_cb(gpointer user_data,
4564 SIPE_UNUSED_PARAMETER const GSList *fields,
4565 const gchar *body,
4566 gsize length)
4568 GSList **buddies = user_data;
4569 sipe_xml *xml = sipe_xml_parse(body, length);
4571 if (xml && !sipe_strequal(sipe_xml_name(xml), "list")) {
4572 const gchar *uri = sipe_xml_attribute(xml, "uri");
4573 const sipe_xml *xn_category;
4576 * automaton: presence is never expected to change
4578 * see: http://msdn.microsoft.com/en-us/library/ee354295(office.13).aspx
4580 for (xn_category = sipe_xml_child(xml, "category");
4581 xn_category;
4582 xn_category = sipe_xml_twin(xn_category)) {
4583 if (sipe_strequal(sipe_xml_attribute(xn_category, "name"),
4584 "contactCard")) {
4585 const sipe_xml *node = sipe_xml_child(xn_category, "contactCard/automaton");
4586 if (node) {
4587 char *boolean = sipe_xml_data(node);
4588 if (sipe_strequal(boolean, "true")) {
4589 SIPE_DEBUG_INFO("sipe_process_presence_timeout: %s is an automaton: - not subscribing to presence updates",
4590 uri);
4591 uri = NULL;
4593 g_free(boolean);
4595 break;
4599 if (uri) {
4600 *buddies = g_slist_append(*buddies, sip_uri(uri));
4604 sipe_xml_free(xml);
4607 static void sipe_process_presence_timeout(struct sipe_core_private *sipe_private,
4608 struct sipmsg *msg, gchar *who,
4609 int timeout)
4611 const char *ctype = sipmsg_find_header(msg, "Content-Type");
4612 gchar *action_name = sipe_utils_presence_key(who);
4614 SIPE_DEBUG_INFO("sipe_process_presence_timeout: Content-Type: %s", ctype ? ctype : "");
4616 if (ctype &&
4617 strstr(ctype, "multipart") &&
4618 (strstr(ctype, "application/rlmi+xml") ||
4619 strstr(ctype, "application/msrtc-event-categories+xml"))) {
4620 GSList *buddies = NULL;
4622 sipe_mime_parts_foreach(ctype, msg->body, sipe_presence_timeout_mime_cb, &buddies);
4624 if (buddies) {
4625 struct presence_batched_routed *payload = g_malloc(sizeof(struct presence_batched_routed));
4626 payload->host = g_strdup(who);
4627 payload->buddies = buddies;
4628 sipe_schedule_seconds(sipe_private,
4629 action_name,
4630 payload,
4631 timeout,
4632 sipe_subscribe_presence_batched_routed,
4633 sipe_subscribe_presence_batched_routed_free);
4634 SIPE_DEBUG_INFO("Resubscription multiple contacts with batched support & route(%s) in %d", who, timeout);
4637 } else {
4638 sipe_schedule_seconds(sipe_private,
4639 action_name,
4640 g_strdup(who),
4641 timeout,
4642 sipe_subscribe_presence_single,
4643 g_free);
4644 SIPE_DEBUG_INFO("Resubscription single contact with batched support(%s) in %d", who, timeout);
4646 g_free(action_name);
4650 * Dispatcher for all incoming subscription information
4651 * whether it comes from NOTIFY, BENOTIFY requests or
4652 * piggy-backed to subscription's OK responce.
4654 * @param request whether initiated from BE/NOTIFY request or OK-response message.
4655 * @param benotify whether initiated from NOTIFY or BENOTIFY request.
4657 void process_incoming_notify(struct sipe_core_private *sipe_private,
4658 struct sipmsg *msg,
4659 gboolean request, gboolean benotify)
4661 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
4662 const gchar *content_type = sipmsg_find_header(msg, "Content-Type");
4663 const gchar *event = sipmsg_find_header(msg, "Event");
4664 const gchar *subscription_state = sipmsg_find_header(msg, "subscription-state");
4666 SIPE_DEBUG_INFO("process_incoming_notify: subscription_state: %s", subscription_state ? subscription_state : "");
4668 /* implicit subscriptions */
4669 if (content_type && g_str_has_prefix(content_type, "application/ms-imdn+xml")) {
4670 sipe_process_imdn(sipe_private, msg);
4673 if (event) {
4674 /* for one off subscriptions (send with Expire: 0) */
4675 if (sipe_strcase_equal(event, "vnd-microsoft-provisioning-v2"))
4677 sipe_process_provisioning_v2(sipe_private, msg);
4679 else if (sipe_strcase_equal(event, "vnd-microsoft-provisioning"))
4681 sipe_process_provisioning(sipe_private, msg);
4683 else if (sipe_strcase_equal(event, "presence"))
4685 sipe_process_presence(sipe_private, msg);
4687 else if (sipe_strcase_equal(event, "registration-notify"))
4689 sipe_process_registration_notify(sipe_private, msg);
4692 if (!subscription_state || strstr(subscription_state, "active"))
4694 if (sipe_strcase_equal(event, "vnd-microsoft-roaming-contacts"))
4696 sipe_process_roaming_contacts(sipe_private, msg);
4698 else if (sipe_strcase_equal(event, "vnd-microsoft-roaming-self"))
4700 sipe_process_roaming_self(sipe_private, msg);
4702 else if (sipe_strcase_equal(event, "vnd-microsoft-roaming-ACL"))
4704 sipe_process_roaming_acl(sipe_private, msg);
4706 else if (sipe_strcase_equal(event, "presence.wpending"))
4708 sipe_process_presence_wpending(sipe_private, msg);
4710 else if (sipe_strcase_equal(event, "conference"))
4712 sipe_process_conference(sipe_private, msg);
4717 /* The server sends status 'terminated' */
4718 if (subscription_state && strstr(subscription_state, "terminated") ) {
4719 gchar *who = parse_from(sipmsg_find_header(msg, request ? "From" : "To"));
4720 gchar *key = sipe_utils_subscription_key(event, who);
4722 SIPE_DEBUG_INFO("process_incoming_notify: server says that subscription to %s was terminated.", who);
4723 g_free(who);
4725 sipe_subscriptions_remove(sipe_private, key);
4726 g_free(key);
4729 if (!request && event) {
4730 const gchar *expires_header = sipmsg_find_header(msg, "Expires");
4731 int timeout = expires_header ? strtol(expires_header, NULL, 10) : 0;
4732 SIPE_DEBUG_INFO("process_incoming_notify: subscription expires:%d", timeout);
4734 if (timeout) {
4735 /* 2 min ahead of expiration */
4736 timeout = (timeout - 120) > 120 ? (timeout - 120) : timeout;
4738 if (sipe_strcase_equal(event, "presence.wpending") &&
4739 g_slist_find_custom(sip->allow_events, "presence.wpending", (GCompareFunc)g_ascii_strcasecmp))
4741 gchar *action_name = g_strdup_printf("<%s>", "presence.wpending");
4742 sipe_schedule_seconds(sipe_private,
4743 action_name,
4744 NULL,
4745 timeout,
4746 sipe_subscribe_presence_wpending,
4747 NULL);
4748 g_free(action_name);
4750 else if (sipe_strcase_equal(event, "presence") &&
4751 g_slist_find_custom(sip->allow_events, "presence", (GCompareFunc)g_ascii_strcasecmp))
4753 gchar *who = parse_from(sipmsg_find_header(msg, "To"));
4754 gchar *action_name = sipe_utils_presence_key(who);
4756 if (sip->batched_support) {
4757 sipe_process_presence_timeout(sipe_private, msg, who, timeout);
4759 else {
4760 sipe_schedule_seconds(sipe_private,
4761 action_name,
4762 g_strdup(who),
4763 timeout,
4764 sipe_subscribe_presence_single,
4765 g_free);
4766 SIPE_DEBUG_INFO("Resubscription single contact (%s) in %d", who, timeout);
4768 g_free(action_name);
4769 g_free(who);
4774 /* The client responses on received a NOTIFY message */
4775 if (request && !benotify)
4777 sip_transport_response(sipe_private, msg, 200, "OK", NULL);
4782 * Whether user manually changed status or
4783 * it was changed automatically due to user
4784 * became inactive/active again
4786 static gboolean
4787 sipe_is_user_state(struct sipe_core_private *sipe_private)
4789 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
4790 gboolean res;
4791 time_t now = time(NULL);
4793 SIPE_DEBUG_INFO("sipe_is_user_state: sip->idle_switch : %s", asctime(localtime(&(sip->idle_switch))));
4794 SIPE_DEBUG_INFO("sipe_is_user_state: now : %s", asctime(localtime(&now)));
4796 res = ((now - SIPE_IDLE_SET_DELAY * 2) >= sip->idle_switch);
4798 SIPE_DEBUG_INFO("sipe_is_user_state: res = %s", res ? "USER" : "MACHINE");
4799 return res;
4802 static void
4803 send_presence_soap0(struct sipe_core_private *sipe_private,
4804 gboolean do_publish_calendar,
4805 gboolean do_reset_status)
4807 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
4808 struct sipe_calendar* cal = sip->cal;
4809 int availability = 0;
4810 int activity = 0;
4811 gchar *body;
4812 gchar *tmp;
4813 gchar *tmp2 = NULL;
4814 gchar *res_note = NULL;
4815 gchar *res_oof = NULL;
4816 const gchar *note_pub = NULL;
4817 gchar *states = NULL;
4818 gchar *calendar_data = NULL;
4819 gchar *epid = get_epid(sipe_private);
4820 time_t now = time(NULL);
4821 gchar *since_time_str = sipe_utils_time_to_str(now);
4822 const gchar *oof_note = cal ? sipe_ews_get_oof_note(cal) : NULL;
4823 const char *user_input;
4824 gboolean pub_oof = cal && oof_note && (!sip->note || cal->updated > sip->note_since);
4826 if (oof_note && sip->note) {
4827 SIPE_DEBUG_INFO("cal->oof_start : %s", asctime(localtime(&(cal->oof_start))));
4828 SIPE_DEBUG_INFO("sip->note_since : %s", asctime(localtime(&(sip->note_since))));
4831 SIPE_DEBUG_INFO("sip->note : %s", sip->note ? sip->note : "");
4833 if (!sip->initial_state_published ||
4834 do_reset_status)
4836 g_free(sip->status);
4837 sip->status = g_strdup(SIPE_STATUS_ID_AVAILABLE);
4840 sipe_get_act_avail_by_status_2005(sip->status, &activity, &availability);
4842 /* Note */
4843 if (pub_oof) {
4844 note_pub = oof_note;
4845 res_oof = SIPE_SOAP_SET_PRESENCE_OOF_XML;
4846 cal->published = TRUE;
4847 } else if (sip->note) {
4848 if (sip->is_oof_note && !oof_note) { /* stale OOF note, as it's not present in cal already */
4849 g_free(sip->note);
4850 sip->note = NULL;
4851 sip->is_oof_note = FALSE;
4852 sip->note_since = 0;
4853 } else {
4854 note_pub = sip->note;
4855 res_oof = sip->is_oof_note ? SIPE_SOAP_SET_PRESENCE_OOF_XML : "";
4859 if (note_pub)
4861 /* to protocol internal plain text format */
4862 tmp = sipe_backend_markup_strip_html(note_pub);
4863 res_note = g_markup_printf_escaped(SIPE_SOAP_SET_PRESENCE_NOTE_XML, tmp);
4864 g_free(tmp);
4867 /* User State */
4868 if (!do_reset_status) {
4869 if (sipe_is_user_state(sipe_private) && !do_publish_calendar && sip->initial_state_published)
4871 gchar *activity_token = NULL;
4872 int avail_2007 = sipe_get_availability_by_status(sip->status, &activity_token);
4874 states = g_strdup_printf(SIPE_SOAP_SET_PRESENCE_STATES,
4875 avail_2007,
4876 since_time_str,
4877 epid,
4878 activity_token);
4879 g_free(activity_token);
4881 else /* preserve existing publication */
4883 if (sip->user_states) {
4884 states = g_strdup(sip->user_states);
4887 } else {
4888 /* do nothing - then User state will be erased */
4890 sip->initial_state_published = TRUE;
4892 /* CalendarInfo */
4893 if (cal && (!is_empty(cal->legacy_dn) || !is_empty(cal->email)) && cal->fb_start && !is_empty(cal->free_busy))
4895 char *fb_start_str = sipe_utils_time_to_str(cal->fb_start);
4896 char *free_busy_base64 = sipe_cal_get_freebusy_base64(cal->free_busy);
4897 calendar_data = g_strdup_printf(SIPE_SOAP_SET_PRESENCE_CALENDAR,
4898 !is_empty(cal->legacy_dn) ? cal->legacy_dn : cal->email,
4899 fb_start_str,
4900 free_busy_base64);
4901 g_free(fb_start_str);
4902 g_free(free_busy_base64);
4905 user_input = (sipe_is_user_state(sipe_private) ||
4906 sipe_strequal(sip->status, SIPE_STATUS_ID_AVAILABLE)) ?
4907 "active" : "idle";
4909 /* forming resulting XML */
4910 body = g_strdup_printf(SIPE_SOAP_SET_PRESENCE,
4911 sipe_private->username,
4912 availability,
4913 activity,
4914 (tmp = g_ascii_strup(g_get_host_name(), -1)),
4915 res_note ? res_note : "",
4916 res_oof ? res_oof : "",
4917 states ? states : "",
4918 calendar_data ? calendar_data : "",
4919 epid,
4920 since_time_str,
4921 since_time_str,
4922 user_input);
4923 g_free(tmp);
4924 g_free(tmp2);
4925 g_free(res_note);
4926 g_free(states);
4927 g_free(calendar_data);
4929 send_soap_request(sipe_private, body);
4931 g_free(body);
4932 g_free(since_time_str);
4933 g_free(epid);
4936 void
4937 send_presence_soap(struct sipe_core_private *sipe_private,
4938 gboolean do_publish_calendar)
4940 return send_presence_soap0(sipe_private, do_publish_calendar, FALSE);
4944 static gboolean
4945 process_send_presence_category_publish_response(struct sipe_core_private *sipe_private,
4946 struct sipmsg *msg,
4947 struct transaction *trans)
4949 const gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
4951 if (msg->response == 409 && g_str_has_prefix(contenttype, "application/msrtc-fault+xml")) {
4952 sipe_xml *xml;
4953 const sipe_xml *node;
4954 gchar *fault_code;
4955 GHashTable *faults;
4956 int index_our;
4957 gboolean has_device_publication = FALSE;
4959 xml = sipe_xml_parse(msg->body, msg->bodylen);
4961 /* test if version mismatch fault */
4962 fault_code = sipe_xml_data(sipe_xml_child(xml, "Faultcode"));
4963 if (!sipe_strequal(fault_code, "Client.BadCall.WrongDelta")) {
4964 SIPE_DEBUG_INFO("process_send_presence_category_publish_response: unsupported fault code:%s returning.", fault_code);
4965 g_free(fault_code);
4966 sipe_xml_free(xml);
4967 return TRUE;
4969 g_free(fault_code);
4971 /* accumulating information about faulty versions */
4972 faults = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
4973 for (node = sipe_xml_child(xml, "details/operation");
4974 node;
4975 node = sipe_xml_twin(node))
4977 const gchar *index = sipe_xml_attribute(node, "index");
4978 const gchar *curVersion = sipe_xml_attribute(node, "curVersion");
4980 g_hash_table_insert(faults, g_strdup(index), g_strdup(curVersion));
4981 SIPE_DEBUG_INFO("fault added: index:%s curVersion:%s", index, curVersion);
4983 sipe_xml_free(xml);
4985 /* here we are parsing our own request to figure out what publication
4986 * referenced here only by index went wrong
4988 xml = sipe_xml_parse(trans->msg->body, trans->msg->bodylen);
4990 /* publication */
4991 for (node = sipe_xml_child(xml, "publications/publication"),
4992 index_our = 1; /* starts with 1 - our first publication */
4993 node;
4994 node = sipe_xml_twin(node), index_our++)
4996 gchar *idx = g_strdup_printf("%d", index_our);
4997 const gchar *curVersion = g_hash_table_lookup(faults, idx);
4998 const gchar *categoryName = sipe_xml_attribute(node, "categoryName");
4999 g_free(idx);
5001 if (sipe_strequal("device", categoryName)) {
5002 has_device_publication = TRUE;
5005 if (curVersion) { /* fault exist on this index */
5006 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5007 const gchar *container = sipe_xml_attribute(node, "container");
5008 const gchar *instance = sipe_xml_attribute(node, "instance");
5009 /* key is <category><instance><container> */
5010 gchar *key = g_strdup_printf("<%s><%s><%s>", categoryName, instance, container);
5011 GHashTable *category = g_hash_table_lookup(sip->our_publications, categoryName);
5013 if (category) {
5014 struct sipe_publication *publication =
5015 g_hash_table_lookup(category, key);
5017 SIPE_DEBUG_INFO("key is %s", key);
5019 if (publication) {
5020 SIPE_DEBUG_INFO("Updating %s with version %s. Was %d before.",
5021 key, curVersion, publication->version);
5022 /* updating publication's version to the correct one */
5023 publication->version = atoi(curVersion);
5025 } else {
5026 /* We somehow lost this category from our publications... */
5027 struct sipe_publication *publication = g_new0(struct sipe_publication, 1);
5028 publication->category = g_strdup(categoryName);
5029 publication->instance = atoi(instance);
5030 publication->container = atoi(container);
5031 publication->version = atoi(curVersion);
5032 category = g_hash_table_new_full(g_str_hash, g_str_equal,
5033 g_free, (GDestroyNotify)free_publication);
5034 g_hash_table_insert(category, g_strdup(key), publication);
5035 g_hash_table_insert(sip->our_publications, g_strdup(categoryName), category);
5036 SIPE_DEBUG_INFO("added lost category '%s' key '%s'", categoryName, key);
5038 g_free(key);
5041 sipe_xml_free(xml);
5042 g_hash_table_destroy(faults);
5044 /* rebublishing with right versions */
5045 if (has_device_publication) {
5046 send_publish_category_initial(sipe_private);
5047 } else {
5048 send_presence_status(sipe_private, NULL);
5051 return TRUE;
5055 * Returns 'device' XML part for publication.
5056 * Must be g_free'd after use.
5058 static gchar *
5059 sipe_publish_get_category_device(struct sipe_core_private *sipe_private)
5061 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5062 gchar *uri;
5063 gchar *doc;
5064 gchar *epid = get_epid(sipe_private);
5065 gchar *uuid = generateUUIDfromEPID(epid);
5066 guint device_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_DEVICE);
5067 /* key is <category><instance><container> */
5068 gchar *key = g_strdup_printf("<%s><%u><%u>", "device", device_instance, 2);
5069 struct sipe_publication *publication =
5070 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "device"), key);
5072 g_free(key);
5073 g_free(epid);
5075 uri = sip_uri_self(sipe_private);
5076 doc = g_strdup_printf(SIPE_PUB_XML_DEVICE,
5077 device_instance,
5078 publication ? publication->version : 0,
5079 uuid,
5080 uri,
5081 "00:00:00+01:00", /* @TODO make timezone real*/
5082 g_get_host_name()
5085 g_free(uri);
5086 g_free(uuid);
5088 return doc;
5092 * A service method - use
5093 * - send_publish_get_category_state_machine and
5094 * - send_publish_get_category_state_user instead.
5095 * Must be g_free'd after use.
5097 static gchar *
5098 sipe_publish_get_category_state(struct sipe_core_private *sipe_private,
5099 gboolean is_user_state)
5101 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5102 int availability = sipe_get_availability_by_status(sip->status, NULL);
5103 guint instance = is_user_state ? sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_USER) :
5104 sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_MACHINE);
5105 /* key is <category><instance><container> */
5106 gchar *key_2 = g_strdup_printf("<%s><%u><%u>", "state", instance, 2);
5107 gchar *key_3 = g_strdup_printf("<%s><%u><%u>", "state", instance, 3);
5108 struct sipe_publication *publication_2 =
5109 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_2);
5110 struct sipe_publication *publication_3 =
5111 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_3);
5113 g_free(key_2);
5114 g_free(key_3);
5116 if (publication_2 && (publication_2->availability == availability))
5118 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_state: state has NOT changed. Exiting.");
5119 return NULL; /* nothing to update */
5122 return g_strdup_printf( is_user_state ? SIPE_PUB_XML_STATE_USER : SIPE_PUB_XML_STATE_MACHINE,
5123 instance,
5124 publication_2 ? publication_2->version : 0,
5125 availability,
5126 instance,
5127 publication_3 ? publication_3->version : 0,
5128 availability);
5132 * Only Busy and OOF calendar event are published.
5133 * Different instances are used for that.
5135 * Must be g_free'd after use.
5137 static gchar *
5138 sipe_publish_get_category_state_calendar(struct sipe_core_private *sipe_private,
5139 struct sipe_cal_event *event,
5140 const char *uri,
5141 int cal_satus)
5143 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5144 gchar *start_time_str;
5145 int availability = 0;
5146 gchar *res;
5147 gchar *tmp = NULL;
5148 guint instance = (cal_satus == SIPE_CAL_OOF) ?
5149 sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_CALENDAR_OOF) :
5150 sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_CALENDAR);
5152 /* key is <category><instance><container> */
5153 gchar *key_2 = g_strdup_printf("<%s><%u><%u>", "state", instance, 2);
5154 gchar *key_3 = g_strdup_printf("<%s><%u><%u>", "state", instance, 3);
5155 struct sipe_publication *publication_2 =
5156 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_2);
5157 struct sipe_publication *publication_3 =
5158 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_3);
5160 g_free(key_2);
5161 g_free(key_3);
5163 if (!publication_3 && !event) { /* was nothing, have nothing, exiting */
5164 SIPE_DEBUG_INFO("sipe_publish_get_category_state_calendar: "
5165 "Exiting as no publication and no event for cal_satus:%d", cal_satus);
5166 return NULL;
5169 if (event &&
5170 publication_3 &&
5171 (publication_3->availability == availability) &&
5172 sipe_strequal(publication_3->cal_event_hash, (tmp = sipe_cal_event_hash(event))))
5174 g_free(tmp);
5175 SIPE_DEBUG_INFO("sipe_publish_get_category_state_calendar: "
5176 "cal state has NOT changed for cal_satus:%d. Exiting.", cal_satus);
5177 return NULL; /* nothing to update */
5179 g_free(tmp);
5181 if (event &&
5182 (event->cal_status == SIPE_CAL_BUSY ||
5183 event->cal_status == SIPE_CAL_OOF))
5185 gchar *availability_xml_str = NULL;
5186 gchar *activity_xml_str = NULL;
5187 gchar *escaped_subject = event->subject ? g_markup_escape_text(event->subject, -1) : NULL;
5188 gchar *escaped_location = event->location ? g_markup_escape_text(event->location, -1) : NULL;
5190 if (event->cal_status == SIPE_CAL_BUSY) {
5191 availability_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_AVAIL, 6500);
5194 if (event->cal_status == SIPE_CAL_BUSY && event->is_meeting) {
5195 activity_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_ACTIVITY,
5196 sipe_activity_map[SIPE_ACTIVITY_IN_MEETING].token,
5197 "minAvailability=\"6500\"",
5198 "maxAvailability=\"8999\"");
5199 } else if (event->cal_status == SIPE_CAL_OOF) {
5200 activity_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_ACTIVITY,
5201 sipe_activity_map[SIPE_ACTIVITY_OOF].token,
5202 "minAvailability=\"12000\"",
5203 "");
5205 start_time_str = sipe_utils_time_to_str(event->start_time);
5207 res = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR,
5208 instance,
5209 publication_2 ? publication_2->version : 0,
5210 uri,
5211 start_time_str,
5212 availability_xml_str ? availability_xml_str : "",
5213 activity_xml_str ? activity_xml_str : "",
5214 escaped_subject ? escaped_subject : "",
5215 escaped_location ? escaped_location : "",
5217 instance,
5218 publication_3 ? publication_3->version : 0,
5219 uri,
5220 start_time_str,
5221 availability_xml_str ? availability_xml_str : "",
5222 activity_xml_str ? activity_xml_str : "",
5223 escaped_subject ? escaped_subject : "",
5224 escaped_location ? escaped_location : ""
5226 g_free(escaped_location);
5227 g_free(escaped_subject);
5228 g_free(start_time_str);
5229 g_free(availability_xml_str);
5230 g_free(activity_xml_str);
5233 else /* including !event, SIPE_CAL_FREE, SIPE_CAL_TENTATIVE */
5235 res = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_CLEAR,
5236 instance,
5237 publication_2 ? publication_2->version : 0,
5239 instance,
5240 publication_3 ? publication_3->version : 0
5244 return res;
5248 * Returns 'machineState' XML part for publication.
5249 * Must be g_free'd after use.
5251 static gchar *
5252 sipe_publish_get_category_state_machine(struct sipe_core_private *sipe_private)
5254 return sipe_publish_get_category_state(sipe_private, FALSE);
5258 * Returns 'userState' XML part for publication.
5259 * Must be g_free'd after use.
5261 static gchar *
5262 sipe_publish_get_category_state_user(struct sipe_core_private *sipe_private)
5264 return sipe_publish_get_category_state(sipe_private, TRUE);
5268 * Returns 'note' XML part for publication.
5269 * Must be g_free'd after use.
5271 * Protocol format for Note is plain text.
5273 * @param note a note in Sipe internal HTML format
5274 * @param note_type either personal or OOF
5276 static gchar *
5277 sipe_publish_get_category_note(struct sipe_core_private *sipe_private,
5278 const char *note, /* html */
5279 const char *note_type,
5280 time_t note_start,
5281 time_t note_end)
5283 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5284 guint instance = sipe_strequal("OOF", note_type) ? sipe_get_pub_instance(sipe_private, SIPE_PUB_NOTE_OOF) : 0;
5285 /* key is <category><instance><container> */
5286 gchar *key_note_200 = g_strdup_printf("<%s><%u><%u>", "note", instance, 200);
5287 gchar *key_note_300 = g_strdup_printf("<%s><%u><%u>", "note", instance, 300);
5288 gchar *key_note_400 = g_strdup_printf("<%s><%u><%u>", "note", instance, 400);
5290 struct sipe_publication *publication_note_200 =
5291 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_200);
5292 struct sipe_publication *publication_note_300 =
5293 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_300);
5294 struct sipe_publication *publication_note_400 =
5295 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_400);
5297 char *tmp = note ? sipe_backend_markup_strip_html(note) : NULL;
5298 char *n1 = tmp ? g_markup_escape_text(tmp, -1) : NULL;
5299 const char *n2 = publication_note_200 ? publication_note_200->note : NULL;
5300 char *res, *tmp1, *tmp2, *tmp3;
5301 char *start_time_attr;
5302 char *end_time_attr;
5304 g_free(tmp);
5305 tmp = NULL;
5306 g_free(key_note_200);
5307 g_free(key_note_300);
5308 g_free(key_note_400);
5310 /* we even need to republish empty note */
5311 if (sipe_strequal(n1, n2))
5313 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_note: note has NOT changed. Exiting.");
5314 g_free(n1);
5315 return NULL; /* nothing to update */
5318 start_time_attr = note_start ? g_strdup_printf(" startTime=\"%s\"", (tmp = sipe_utils_time_to_str(note_start))) : NULL;
5319 g_free(tmp);
5320 tmp = NULL;
5321 end_time_attr = note_end ? g_strdup_printf(" endTime=\"%s\"", (tmp = sipe_utils_time_to_str(note_end))) : NULL;
5322 g_free(tmp);
5324 if (n1) {
5325 tmp1 = g_strdup_printf(SIPE_PUB_XML_NOTE,
5326 instance,
5327 200,
5328 publication_note_200 ? publication_note_200->version : 0,
5329 note_type,
5330 start_time_attr ? start_time_attr : "",
5331 end_time_attr ? end_time_attr : "",
5332 n1);
5334 tmp2 = g_strdup_printf(SIPE_PUB_XML_NOTE,
5335 instance,
5336 300,
5337 publication_note_300 ? publication_note_300->version : 0,
5338 note_type,
5339 start_time_attr ? start_time_attr : "",
5340 end_time_attr ? end_time_attr : "",
5341 n1);
5343 tmp3 = g_strdup_printf(SIPE_PUB_XML_NOTE,
5344 instance,
5345 400,
5346 publication_note_400 ? publication_note_400->version : 0,
5347 note_type,
5348 start_time_attr ? start_time_attr : "",
5349 end_time_attr ? end_time_attr : "",
5350 n1);
5351 } else {
5352 tmp1 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
5353 "note",
5354 instance,
5355 200,
5356 publication_note_200 ? publication_note_200->version : 0,
5357 "static");
5358 tmp2 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
5359 "note",
5360 instance,
5361 300,
5362 publication_note_200 ? publication_note_200->version : 0,
5363 "static");
5364 tmp3 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
5365 "note",
5366 instance,
5367 400,
5368 publication_note_200 ? publication_note_200->version : 0,
5369 "static");
5371 res = g_strconcat(tmp1, tmp2, tmp3, NULL);
5373 g_free(start_time_attr);
5374 g_free(end_time_attr);
5375 g_free(tmp1);
5376 g_free(tmp2);
5377 g_free(tmp3);
5378 g_free(n1);
5380 return res;
5384 * Returns 'calendarData' XML part with WorkingHours for publication.
5385 * Must be g_free'd after use.
5387 static gchar *
5388 sipe_publish_get_category_cal_working_hours(struct sipe_core_private *sipe_private)
5390 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5391 struct sipe_calendar* cal = sip->cal;
5393 /* key is <category><instance><container> */
5394 gchar *key_cal_1 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 1);
5395 gchar *key_cal_100 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 100);
5396 gchar *key_cal_200 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 200);
5397 gchar *key_cal_300 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 300);
5398 gchar *key_cal_400 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 400);
5399 gchar *key_cal_32000 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 32000);
5401 struct sipe_publication *publication_cal_1 =
5402 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_1);
5403 struct sipe_publication *publication_cal_100 =
5404 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_100);
5405 struct sipe_publication *publication_cal_200 =
5406 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_200);
5407 struct sipe_publication *publication_cal_300 =
5408 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_300);
5409 struct sipe_publication *publication_cal_400 =
5410 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_400);
5411 struct sipe_publication *publication_cal_32000 =
5412 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_32000);
5414 const char *n1 = cal ? cal->working_hours_xml_str : NULL;
5415 const char *n2 = publication_cal_300 ? publication_cal_300->working_hours_xml_str : NULL;
5417 g_free(key_cal_1);
5418 g_free(key_cal_100);
5419 g_free(key_cal_200);
5420 g_free(key_cal_300);
5421 g_free(key_cal_400);
5422 g_free(key_cal_32000);
5424 if (!cal || is_empty(cal->email) || is_empty(cal->working_hours_xml_str)) {
5425 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_cal_working_hours: no data to publish, exiting");
5426 return NULL;
5429 if (sipe_strequal(n1, n2))
5431 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_cal_working_hours: WorkingHours has NOT changed. Exiting.");
5432 return NULL; /* nothing to update */
5435 return g_strdup_printf(SIPE_PUB_XML_WORKING_HOURS,
5436 /* 1 */
5437 publication_cal_1 ? publication_cal_1->version : 0,
5438 cal->email,
5439 cal->working_hours_xml_str,
5440 /* 100 - Public */
5441 publication_cal_100 ? publication_cal_100->version : 0,
5442 /* 200 - Company */
5443 publication_cal_200 ? publication_cal_200->version : 0,
5444 cal->email,
5445 cal->working_hours_xml_str,
5446 /* 300 - Team */
5447 publication_cal_300 ? publication_cal_300->version : 0,
5448 cal->email,
5449 cal->working_hours_xml_str,
5450 /* 400 - Personal */
5451 publication_cal_400 ? publication_cal_400->version : 0,
5452 cal->email,
5453 cal->working_hours_xml_str,
5454 /* 32000 - Blocked */
5455 publication_cal_32000 ? publication_cal_32000->version : 0
5460 * Returns 'calendarData' XML part with FreeBusy for publication.
5461 * Must be g_free'd after use.
5463 static gchar *
5464 sipe_publish_get_category_cal_free_busy(struct sipe_core_private *sipe_private)
5466 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5467 struct sipe_calendar* cal = sip->cal;
5468 guint cal_data_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_CALENDAR_DATA);
5469 char *fb_start_str;
5470 char *free_busy_base64;
5471 const char *st;
5472 const char *fb;
5473 char *res;
5475 /* key is <category><instance><container> */
5476 gchar *key_cal_1 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 1);
5477 gchar *key_cal_100 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 100);
5478 gchar *key_cal_200 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 200);
5479 gchar *key_cal_300 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 300);
5480 gchar *key_cal_400 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 400);
5481 gchar *key_cal_32000 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 32000);
5483 struct sipe_publication *publication_cal_1 =
5484 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_1);
5485 struct sipe_publication *publication_cal_100 =
5486 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_100);
5487 struct sipe_publication *publication_cal_200 =
5488 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_200);
5489 struct sipe_publication *publication_cal_300 =
5490 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_300);
5491 struct sipe_publication *publication_cal_400 =
5492 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_400);
5493 struct sipe_publication *publication_cal_32000 =
5494 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_32000);
5496 g_free(key_cal_1);
5497 g_free(key_cal_100);
5498 g_free(key_cal_200);
5499 g_free(key_cal_300);
5500 g_free(key_cal_400);
5501 g_free(key_cal_32000);
5503 if (!cal || is_empty(cal->email) || !cal->fb_start || is_empty(cal->free_busy)) {
5504 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_cal_free_busy: no data to publish, exiting");
5505 return NULL;
5508 fb_start_str = sipe_utils_time_to_str(cal->fb_start);
5509 free_busy_base64 = sipe_cal_get_freebusy_base64(cal->free_busy);
5511 st = publication_cal_300 ? publication_cal_300->fb_start_str : NULL;
5512 fb = publication_cal_300 ? publication_cal_300->free_busy_base64 : NULL;
5514 /* we will rebuplish the same data to refresh publication time,
5515 * so if data from multiple sources, most recent will be choosen
5517 //if (sipe_strequal(st, fb_start_str) && sipe_strequal(fb, free_busy_base64))
5519 // SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_cal_free_busy: FreeBusy has NOT changed. Exiting.");
5520 // g_free(fb_start_str);
5521 // g_free(free_busy_base64);
5522 // return NULL; /* nothing to update */
5525 res = g_strdup_printf(SIPE_PUB_XML_FREE_BUSY,
5526 /* 1 */
5527 cal_data_instance,
5528 publication_cal_1 ? publication_cal_1->version : 0,
5529 /* 100 - Public */
5530 cal_data_instance,
5531 publication_cal_100 ? publication_cal_100->version : 0,
5532 /* 200 - Company */
5533 cal_data_instance,
5534 publication_cal_200 ? publication_cal_200->version : 0,
5535 cal->email,
5536 fb_start_str,
5537 free_busy_base64,
5538 /* 300 - Team */
5539 cal_data_instance,
5540 publication_cal_300 ? publication_cal_300->version : 0,
5541 cal->email,
5542 fb_start_str,
5543 free_busy_base64,
5544 /* 400 - Personal */
5545 cal_data_instance,
5546 publication_cal_400 ? publication_cal_400->version : 0,
5547 cal->email,
5548 fb_start_str,
5549 free_busy_base64,
5550 /* 32000 - Blocked */
5551 cal_data_instance,
5552 publication_cal_32000 ? publication_cal_32000->version : 0
5555 g_free(fb_start_str);
5556 g_free(free_busy_base64);
5557 return res;
5560 static void send_presence_publish(struct sipe_core_private *sipe_private,
5561 const char *publications)
5563 gchar *uri;
5564 gchar *doc;
5565 gchar *tmp;
5566 gchar *hdr;
5568 uri = sip_uri_self(sipe_private);
5569 doc = g_strdup_printf(SIPE_SEND_PRESENCE,
5570 uri,
5571 publications);
5573 tmp = get_contact(sipe_private);
5574 hdr = g_strdup_printf("Contact: %s\r\n"
5575 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp);
5577 sip_transport_service(sipe_private,
5578 uri,
5579 hdr,
5580 doc,
5581 process_send_presence_category_publish_response);
5583 g_free(tmp);
5584 g_free(hdr);
5585 g_free(uri);
5586 g_free(doc);
5589 static void
5590 send_publish_category_initial(struct sipe_core_private *sipe_private)
5592 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5593 gchar *pub_device = sipe_publish_get_category_device(sipe_private);
5594 gchar *pub_machine;
5595 gchar *publications;
5597 g_free(sip->status);
5598 sip->status = g_strdup(SIPE_STATUS_ID_AVAILABLE); /* our initial state */
5600 pub_machine = sipe_publish_get_category_state_machine(sipe_private);
5601 publications = g_strdup_printf("%s%s",
5602 pub_device,
5603 pub_machine ? pub_machine : "");
5604 g_free(pub_device);
5605 g_free(pub_machine);
5607 send_presence_publish(sipe_private, publications);
5608 g_free(publications);
5611 static void
5612 send_presence_category_publish(struct sipe_core_private *sipe_private)
5614 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5615 gchar *pub_state = sipe_is_user_state(sipe_private) ?
5616 sipe_publish_get_category_state_user(sipe_private) :
5617 sipe_publish_get_category_state_machine(sipe_private);
5618 gchar *pub_note = sipe_publish_get_category_note(sipe_private,
5619 sip->note,
5620 sip->is_oof_note ? "OOF" : "personal",
5623 gchar *publications;
5625 if (!pub_state && !pub_note) {
5626 SIPE_DEBUG_INFO_NOFORMAT("send_presence_category_publish: nothing has changed. Exiting.");
5627 return;
5630 publications = g_strdup_printf("%s%s",
5631 pub_state ? pub_state : "",
5632 pub_note ? pub_note : "");
5634 g_free(pub_state);
5635 g_free(pub_note);
5637 send_presence_publish(sipe_private, publications);
5638 g_free(publications);
5642 * Publishes self status
5643 * based on own calendar information.
5645 * For 2007+
5647 void
5648 publish_calendar_status_self(struct sipe_core_private *sipe_private,
5649 SIPE_UNUSED_PARAMETER void *unused)
5651 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5652 struct sipe_cal_event* event = NULL;
5653 gchar *pub_cal_working_hours = NULL;
5654 gchar *pub_cal_free_busy = NULL;
5655 gchar *pub_calendar = NULL;
5656 gchar *pub_calendar2 = NULL;
5657 gchar *pub_oof_note = NULL;
5658 const gchar *oof_note;
5659 time_t oof_start = 0;
5660 time_t oof_end = 0;
5662 if (!sip->cal) {
5663 SIPE_DEBUG_INFO_NOFORMAT("publish_calendar_status_self() no calendar data.");
5664 return;
5667 SIPE_DEBUG_INFO_NOFORMAT("publish_calendar_status_self() started.");
5668 if (sip->cal->cal_events) {
5669 event = sipe_cal_get_event(sip->cal->cal_events, time(NULL));
5672 if (!event) {
5673 SIPE_DEBUG_INFO_NOFORMAT("publish_calendar_status_self: current event is NULL");
5674 } else {
5675 char *desc = sipe_cal_event_describe(event);
5676 SIPE_DEBUG_INFO("publish_calendar_status_self: current event is:\n%s", desc ? desc : "");
5677 g_free(desc);
5680 /* Logic
5681 if OOF
5682 OOF publish, Busy clean
5683 ilse if Busy
5684 OOF clean, Busy publish
5685 else
5686 OOF clean, Busy clean
5688 if (event && event->cal_status == SIPE_CAL_OOF) {
5689 pub_calendar = sipe_publish_get_category_state_calendar(sipe_private, event, sip->cal->email, SIPE_CAL_OOF);
5690 pub_calendar2 = sipe_publish_get_category_state_calendar(sipe_private, NULL, sip->cal->email, SIPE_CAL_BUSY);
5691 } else if (event && event->cal_status == SIPE_CAL_BUSY) {
5692 pub_calendar = sipe_publish_get_category_state_calendar(sipe_private, NULL, sip->cal->email, SIPE_CAL_OOF);
5693 pub_calendar2 = sipe_publish_get_category_state_calendar(sipe_private, event, sip->cal->email, SIPE_CAL_BUSY);
5694 } else {
5695 pub_calendar = sipe_publish_get_category_state_calendar(sipe_private, NULL, sip->cal->email, SIPE_CAL_OOF);
5696 pub_calendar2 = sipe_publish_get_category_state_calendar(sipe_private, NULL, sip->cal->email, SIPE_CAL_BUSY);
5699 oof_note = sipe_ews_get_oof_note(sip->cal);
5700 if (sipe_strequal("Scheduled", sip->cal->oof_state)) {
5701 oof_start = sip->cal->oof_start;
5702 oof_end = sip->cal->oof_end;
5704 pub_oof_note = sipe_publish_get_category_note(sipe_private, oof_note, "OOF", oof_start, oof_end);
5706 pub_cal_working_hours = sipe_publish_get_category_cal_working_hours(sipe_private);
5707 pub_cal_free_busy = sipe_publish_get_category_cal_free_busy(sipe_private);
5709 if (!pub_cal_working_hours && !pub_cal_free_busy && !pub_calendar && !pub_calendar2 && !pub_oof_note) {
5710 SIPE_DEBUG_INFO_NOFORMAT("publish_calendar_status_self: nothing has changed.");
5711 } else {
5712 gchar *publications = g_strdup_printf("%s%s%s%s%s",
5713 pub_cal_working_hours ? pub_cal_working_hours : "",
5714 pub_cal_free_busy ? pub_cal_free_busy : "",
5715 pub_calendar ? pub_calendar : "",
5716 pub_calendar2 ? pub_calendar2 : "",
5717 pub_oof_note ? pub_oof_note : "");
5719 send_presence_publish(sipe_private, publications);
5720 g_free(publications);
5723 g_free(pub_cal_working_hours);
5724 g_free(pub_cal_free_busy);
5725 g_free(pub_calendar);
5726 g_free(pub_calendar2);
5727 g_free(pub_oof_note);
5729 /* repeat scheduling */
5730 sipe_sched_calendar_status_self_publish(sipe_private, time(NULL));
5733 static void send_presence_status(struct sipe_core_private *sipe_private,
5734 SIPE_UNUSED_PARAMETER void *unused)
5736 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5737 PurpleStatus * status = purple_account_get_active_status(sip->account);
5739 if (!status) return;
5741 SIPE_DEBUG_INFO("send_presence_status: status: %s (%s)",
5742 purple_status_get_id(status) ? purple_status_get_id(status) : "",
5743 sipe_is_user_state(sipe_private) ? "USER" : "MACHINE");
5745 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
5746 send_presence_category_publish(sipe_private);
5747 } else {
5748 send_presence_soap(sipe_private, FALSE);
5752 static guint sipe_ht_hash_nick(const char *nick)
5754 char *lc = g_utf8_strdown(nick, -1);
5755 guint bucket = g_str_hash(lc);
5756 g_free(lc);
5758 return bucket;
5761 static gboolean sipe_ht_equals_nick(const char *nick1, const char *nick2)
5763 char *nick1_norm = NULL;
5764 char *nick2_norm = NULL;
5765 gboolean equal;
5767 if (nick1 == NULL && nick2 == NULL) return TRUE;
5768 if (nick1 == NULL || nick2 == NULL ||
5769 !g_utf8_validate(nick1, -1, NULL) ||
5770 !g_utf8_validate(nick2, -1, NULL)) return FALSE;
5772 nick1_norm = g_utf8_casefold(nick1, -1);
5773 nick2_norm = g_utf8_casefold(nick2, -1);
5774 equal = g_utf8_collate(nick1_norm, nick2_norm) == 0;
5775 g_free(nick2_norm);
5776 g_free(nick1_norm);
5778 return equal;
5781 /* temporary function */
5782 void sipe_purple_setup(struct sipe_core_public *sipe_public,
5783 PurpleConnection *gc)
5785 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA;
5786 sip->gc = gc;
5787 sip->account = purple_connection_get_account(gc);
5790 struct sipe_core_public *sipe_core_allocate(const gchar *signin_name,
5791 const gchar *login_domain,
5792 const gchar *login_account,
5793 const gchar *password,
5794 const gchar *email,
5795 const gchar *email_url,
5796 const gchar **errmsg)
5798 struct sipe_core_private *sipe_private;
5799 struct sipe_account_data *sip;
5800 gchar **user_domain;
5802 SIPE_DEBUG_INFO("sipe_core_allocate: signin_name '%s'", signin_name);
5804 /* ensure that sign-in name doesn't contain invalid characters */
5805 if (strpbrk(signin_name, "\t\v\r\n") != NULL) {
5806 *errmsg = _("SIP Exchange user name contains invalid characters");
5807 return NULL;
5810 /* ensure that sign-in name format is name@domain */
5811 if (!strchr(signin_name, '@') ||
5812 g_str_has_prefix(signin_name, "@") ||
5813 g_str_has_suffix(signin_name, "@")) {
5814 *errmsg = _("User name should be a valid SIP URI\nExample: user@company.com");
5815 return NULL;
5818 /* ensure that email format is name@domain (if provided) */
5819 if (!is_empty(email) &&
5820 (!strchr(email, '@') ||
5821 g_str_has_prefix(email, "@") ||
5822 g_str_has_suffix(email, "@")))
5824 *errmsg = _("Email address should be valid if provided\nExample: user@company.com");
5825 return NULL;
5828 /* ensure that user name doesn't contain spaces */
5829 user_domain = g_strsplit(signin_name, "@", 2);
5830 SIPE_DEBUG_INFO("sipe_core_allocate: user '%s' domain '%s'", user_domain[0], user_domain[1]);
5831 if (strchr(user_domain[0], ' ') != NULL) {
5832 g_strfreev(user_domain);
5833 *errmsg = _("SIP Exchange user name contains whitespace");
5834 return NULL;
5837 /* ensure that email_url is in proper format if enabled (if provided).
5838 * Example (Exchange): https://server.company.com/EWS/Exchange.asmx
5839 * Example (Domino) : https://[domino_server]/[mail_database_name].nsf
5841 if (!is_empty(email_url)) {
5842 char *tmp = g_ascii_strdown(email_url, -1);
5843 if (!g_str_has_prefix(tmp, "https://"))
5845 g_free(tmp);
5846 g_strfreev(user_domain);
5847 *errmsg = _("Email services URL should be valid if provided\n"
5848 "Example: https://exchange.corp.com/EWS/Exchange.asmx\n"
5849 "Example: https://domino.corp.com/maildatabase.nsf");
5850 return NULL;
5852 g_free(tmp);
5855 sipe_private = g_new0(struct sipe_core_private, 1);
5856 sipe_private->temporary = sip = g_new0(struct sipe_account_data, 1);
5857 sip->subscribed_buddies = FALSE;
5858 sip->initial_state_published = FALSE;
5859 sipe_private->username = g_strdup(signin_name);
5860 sip->email = is_empty(email) ? g_strdup(signin_name) : g_strdup(email);
5861 sip->authdomain = is_empty(login_domain) ? NULL : g_strdup(login_domain);
5862 sip->authuser = is_empty(login_account) ? NULL : g_strdup(login_account);
5863 sip->password = g_strdup(password);
5864 sipe_private->public.sip_name = g_strdup(user_domain[0]);
5865 sipe_private->public.sip_domain = g_strdup(user_domain[1]);
5866 g_strfreev(user_domain);
5868 sipe_private->buddies = g_hash_table_new((GHashFunc)sipe_ht_hash_nick, (GEqualFunc)sipe_ht_equals_nick);
5869 sip->our_publications = g_hash_table_new_full(g_str_hash, g_str_equal,
5870 g_free, (GDestroyNotify)g_hash_table_destroy);
5871 sipe_subscriptions_init(sipe_private);
5872 sip->status = g_strdup(SIPE_STATUS_ID_UNKNOWN);
5874 return((struct sipe_core_public *)sipe_private);
5877 static void
5878 sipe_blist_menu_free_containers(struct sipe_core_private *sipe_private);
5880 void sipe_connection_cleanup(struct sipe_core_private *sipe_private)
5882 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5884 g_free(sipe_private->epid);
5885 sipe_private->epid = NULL;
5887 sip_transport_disconnect(sipe_private);
5889 sipe_schedule_cancel_all(sipe_private);
5891 if (sip->allow_events) {
5892 GSList *entry = sip->allow_events;
5893 while (entry) {
5894 g_free(entry->data);
5895 entry = entry->next;
5898 g_slist_free(sip->allow_events);
5900 if (sip->containers) {
5901 GSList *entry = sip->containers;
5902 while (entry) {
5903 free_container((struct sipe_container *)entry->data);
5904 entry = entry->next;
5907 g_slist_free(sip->containers);
5909 /* libpurple memory leak workaround */
5910 sipe_blist_menu_free_containers(sipe_private);
5912 if (sipe_private->contact)
5913 g_free(sipe_private->contact);
5914 sipe_private->contact = NULL;
5915 if (sip->regcallid)
5916 g_free(sip->regcallid);
5917 sip->regcallid = NULL;
5919 if (sipe_private->focus_factory_uri)
5920 g_free(sipe_private->focus_factory_uri);
5921 sipe_private->focus_factory_uri = NULL;
5923 if (sip->cal) {
5924 sipe_cal_calendar_free(sip->cal);
5926 sip->cal = NULL;
5928 sipe_groupchat_free(sipe_private);
5932 * A callback for g_hash_table_foreach_remove
5934 static gboolean sipe_buddy_remove(SIPE_UNUSED_PARAMETER gpointer key, gpointer buddy,
5935 SIPE_UNUSED_PARAMETER gpointer user_data)
5937 sipe_free_buddy((struct sipe_buddy *) buddy);
5939 /* We must return TRUE as the key/value have already been deleted */
5940 return(TRUE);
5943 void sipe_buddy_free_all(struct sipe_core_private *sipe_private)
5945 g_hash_table_foreach_steal(sipe_private->buddies, sipe_buddy_remove, NULL);
5948 static void sipe_searchresults_im_buddy(PurpleConnection *gc, GList *row,
5949 SIPE_UNUSED_PARAMETER void *user_data)
5951 PurpleAccount *acct = purple_connection_get_account(gc);
5952 char *id = sip_uri_from_name((gchar *)g_list_nth_data(row, 0));
5953 PurpleConversation *conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, id, acct);
5954 if (conv == NULL)
5955 conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, acct, id);
5956 purple_conversation_present(conv);
5957 g_free(id);
5960 static void sipe_searchresults_add_buddy(PurpleConnection *gc, GList *row,
5961 SIPE_UNUSED_PARAMETER void *user_data)
5964 purple_blist_request_add_buddy(purple_connection_get_account(gc),
5965 g_list_nth_data(row, 0), _("Other Contacts"), g_list_nth_data(row, 1));
5968 static gboolean process_search_contact_response(struct sipe_core_private *sipe_private,
5969 struct sipmsg *msg,
5970 SIPE_UNUSED_PARAMETER struct transaction *trans)
5972 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5973 PurpleNotifySearchResults *results;
5974 PurpleNotifySearchColumn *column;
5975 sipe_xml *searchResults;
5976 const sipe_xml *mrow;
5977 int match_count = 0;
5978 gboolean more = FALSE;
5979 gchar *secondary;
5981 SIPE_DEBUG_INFO("process_search_contact_response: body:\n%s", msg->body ? msg->body : "");
5983 searchResults = sipe_xml_parse(msg->body, msg->bodylen);
5984 if (!searchResults) {
5985 SIPE_DEBUG_INFO_NOFORMAT("process_search_contact_response: no parseable searchResults");
5986 return FALSE;
5989 results = purple_notify_searchresults_new();
5991 if (results == NULL) {
5992 SIPE_DEBUG_ERROR_NOFORMAT("purple_parse_searchreply: Unable to display the search results.");
5993 purple_notify_error(sip->gc, NULL, _("Unable to display the search results"), NULL);
5995 sipe_xml_free(searchResults);
5996 return FALSE;
5999 column = purple_notify_searchresults_column_new(_("User name"));
6000 purple_notify_searchresults_column_add(results, column);
6002 column = purple_notify_searchresults_column_new(_("Name"));
6003 purple_notify_searchresults_column_add(results, column);
6005 column = purple_notify_searchresults_column_new(_("Company"));
6006 purple_notify_searchresults_column_add(results, column);
6008 column = purple_notify_searchresults_column_new(_("Country"));
6009 purple_notify_searchresults_column_add(results, column);
6011 column = purple_notify_searchresults_column_new(_("Email"));
6012 purple_notify_searchresults_column_add(results, column);
6014 for (mrow = sipe_xml_child(searchResults, "Body/Array/row"); mrow; mrow = sipe_xml_twin(mrow)) {
6015 GList *row = NULL;
6017 gchar **uri_parts = g_strsplit(sipe_xml_attribute(mrow, "uri"), ":", 2);
6018 row = g_list_append(row, g_strdup(uri_parts[1]));
6019 g_strfreev(uri_parts);
6021 row = g_list_append(row, g_strdup(sipe_xml_attribute(mrow, "displayName")));
6022 row = g_list_append(row, g_strdup(sipe_xml_attribute(mrow, "company")));
6023 row = g_list_append(row, g_strdup(sipe_xml_attribute(mrow, "country")));
6024 row = g_list_append(row, g_strdup(sipe_xml_attribute(mrow, "email")));
6026 purple_notify_searchresults_row_add(results, row);
6027 match_count++;
6030 if ((mrow = sipe_xml_child(searchResults, "Body/directorySearch/moreAvailable")) != NULL) {
6031 char *data = sipe_xml_data(mrow);
6032 more = (g_strcasecmp(data, "true") == 0);
6033 g_free(data);
6036 secondary = g_strdup_printf(
6037 dngettext(PACKAGE_NAME,
6038 "Found %d contact%s:",
6039 "Found %d contacts%s:", match_count),
6040 match_count, more ? _(" (more matched your query)") : "");
6042 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_IM, sipe_searchresults_im_buddy);
6043 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_ADD, sipe_searchresults_add_buddy);
6044 purple_notify_searchresults(sip->gc, NULL, NULL, secondary, results, NULL, NULL);
6046 g_free(secondary);
6047 sipe_xml_free(searchResults);
6048 return TRUE;
6051 void sipe_search_contact_with_cb(PurpleConnection *gc, PurpleRequestFields *fields)
6053 GList *entries = purple_request_field_group_get_fields(purple_request_fields_get_groups(fields)->data);
6054 gchar **attrs = g_new(gchar *, g_list_length(entries) + 1);
6055 unsigned i = 0;
6057 if (!attrs) return;
6059 do {
6060 PurpleRequestField *field = entries->data;
6061 const char *id = purple_request_field_get_id(field);
6062 const char *value = purple_request_field_string_get_value(field);
6064 SIPE_DEBUG_INFO("sipe_search_contact_with_cb: %s = '%s'", id, value ? value : "");
6066 if (value != NULL) attrs[i++] = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, id, value);
6067 } while ((entries = g_list_next(entries)) != NULL);
6068 attrs[i] = NULL;
6070 if (i > 0) {
6071 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
6072 gchar *domain_uri = sip_uri_from_name(sipe_private->public.sip_domain);
6073 gchar *query = g_strjoinv(NULL, attrs);
6074 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 100, query);
6075 SIPE_DEBUG_INFO("sipe_search_contact_with_cb: body:\n%s", body ? body : "");
6076 send_soap_request_with_cb(sipe_private, domain_uri, body,
6077 process_search_contact_response, NULL);
6078 g_free(domain_uri);
6079 g_free(body);
6080 g_free(query);
6083 g_strfreev(attrs);
6086 static void sipe_publish_get_cat_state_user_to_clear(SIPE_UNUSED_PARAMETER const char *name,
6087 gpointer value,
6088 GString* str)
6090 struct sipe_publication *publication = value;
6092 g_string_append_printf( str,
6093 SIPE_PUB_XML_PUBLICATION_CLEAR,
6094 publication->category,
6095 publication->instance,
6096 publication->container,
6097 publication->version,
6098 "static");
6101 void sipe_core_reset_status(struct sipe_core_public *sipe_public)
6103 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
6104 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA;
6105 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) /* 2007+ */
6107 GString* str = g_string_new(NULL);
6108 gchar *publications;
6110 if (!sip->user_state_publications || g_hash_table_size(sip->user_state_publications) == 0) {
6111 SIPE_DEBUG_INFO_NOFORMAT("sipe_reset_status: no userState publications, exiting.");
6112 return;
6115 g_hash_table_foreach(sip->user_state_publications, (GHFunc)sipe_publish_get_cat_state_user_to_clear, str);
6116 publications = g_string_free(str, FALSE);
6118 send_presence_publish(sipe_private, publications);
6119 g_free(publications);
6121 else /* 2005 */
6123 send_presence_soap0(sipe_private, FALSE, TRUE);
6127 /** for Access levels menu */
6128 #define INDENT_FMT " %s"
6130 /** Member is directly placed to access level container.
6131 * For example SIP URI of user is in the container.
6133 #define INDENT_MARKED_FMT "* %s"
6135 /** Member is indirectly belong to access level container.
6136 * For example 'sameEnterprise' is in the container and user
6137 * belongs to that same enterprise.
6139 #define INDENT_MARKED_INHERITED_FMT "= %s"
6141 GSList *sipe_core_buddy_info(struct sipe_core_public *sipe_public,
6142 const gchar *name,
6143 const gchar *status_name,
6144 gboolean is_online)
6146 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
6147 gchar *note = NULL;
6148 gboolean is_oof_note = FALSE;
6149 const gchar *activity = NULL;
6150 gchar *calendar = NULL;
6151 const gchar *meeting_subject = NULL;
6152 const gchar *meeting_location = NULL;
6153 gchar *access_text = NULL;
6154 GSList *info = NULL;
6156 #define SIPE_ADD_BUDDY_INFO_COMMON(l, t) \
6158 struct sipe_buddy_info *sbi = g_malloc(sizeof(struct sipe_buddy_info)); \
6159 sbi->label = (l); \
6160 sbi->text = (t); \
6161 info = g_slist_append(info, sbi); \
6163 #define SIPE_ADD_BUDDY_INFO(l, t) SIPE_ADD_BUDDY_INFO_COMMON((l), g_markup_escape_text((t), -1))
6164 #define SIPE_ADD_BUDDY_INFO_NOESCAPE(l, t) SIPE_ADD_BUDDY_INFO_COMMON((l), (t))
6166 if (sipe_public) { //happens on pidgin exit
6167 struct sipe_buddy *sbuddy = g_hash_table_lookup(sipe_private->buddies, name);
6168 if (sbuddy) {
6169 note = sbuddy->note;
6170 is_oof_note = sbuddy->is_oof_note;
6171 activity = sbuddy->activity;
6172 calendar = sipe_cal_get_description(sbuddy);
6173 meeting_subject = sbuddy->meeting_subject;
6174 meeting_location = sbuddy->meeting_location;
6176 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
6177 gboolean is_group_access = FALSE;
6178 const int container_id = sipe_find_access_level(sipe_private, "user", sipe_get_no_sip_uri(name), &is_group_access);
6179 const char *access_level = sipe_get_access_level_name(container_id);
6180 access_text = is_group_access ?
6181 g_strdup(access_level) :
6182 g_strdup_printf(INDENT_MARKED_FMT, access_level);
6186 //Layout
6187 if (is_online)
6189 const gchar *status_str = activity ? activity : status_name;
6191 SIPE_ADD_BUDDY_INFO(_("Status"), status_str);
6193 if (is_online && !is_empty(calendar))
6195 SIPE_ADD_BUDDY_INFO(_("Calendar"), calendar);
6197 g_free(calendar);
6198 if (!is_empty(meeting_location))
6200 SIPE_DEBUG_INFO("sipe_tooltip_text: %s meeting location: '%s'", name, meeting_location);
6201 SIPE_ADD_BUDDY_INFO(_("Meeting in"), meeting_location);
6203 if (!is_empty(meeting_subject))
6205 SIPE_DEBUG_INFO("sipe_tooltip_text: %s meeting subject: '%s'", name, meeting_subject);
6206 SIPE_ADD_BUDDY_INFO(_("Meeting about"), meeting_subject);
6208 if (note)
6210 SIPE_DEBUG_INFO("sipe_tooltip_text: %s note: '%s'", name, note);
6211 SIPE_ADD_BUDDY_INFO_NOESCAPE(is_oof_note ? _("Out of office note") : _("Note"),
6212 g_strdup_printf("<i>%s</i>", note));
6214 if (access_text) {
6215 SIPE_ADD_BUDDY_INFO(_("Access level"), access_text);
6216 g_free(access_text);
6219 return(info);
6222 static PurpleBuddy *
6223 purple_blist_add_buddy_clone(PurpleGroup * group, PurpleBuddy * buddy)
6225 PurpleBuddy *clone;
6226 const gchar *server_alias, *email;
6227 const PurpleStatus *status = purple_presence_get_active_status(purple_buddy_get_presence(buddy));
6229 clone = purple_buddy_new(buddy->account, buddy->name, buddy->alias);
6231 purple_blist_add_buddy(clone, NULL, group, NULL);
6233 server_alias = purple_buddy_get_server_alias(buddy);
6234 if (server_alias) {
6235 purple_blist_server_alias_buddy(clone, server_alias);
6238 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
6239 if (email) {
6240 purple_blist_node_set_string(&clone->node, EMAIL_PROP, email);
6243 purple_presence_set_status_active(purple_buddy_get_presence(clone), purple_status_get_id(status), TRUE);
6244 //for UI to update;
6245 purple_prpl_got_user_status(clone->account, clone->name, purple_status_get_id(status), NULL);
6246 return clone;
6249 static void
6250 sipe_buddy_menu_copy_to_cb(PurpleBlistNode *node, const char *group_name)
6252 PurpleBuddy *buddy, *b;
6253 PurpleConnection *gc;
6254 PurpleGroup * group = purple_find_group(group_name);
6256 g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node));
6258 buddy = (PurpleBuddy *)node;
6260 SIPE_DEBUG_INFO("sipe_buddy_menu_copy_to_cb: copying %s to %s", buddy->name, group_name);
6261 gc = purple_account_get_connection(buddy->account);
6263 b = purple_find_buddy_in_group(buddy->account, buddy->name, group);
6264 if (!b){
6265 b = purple_blist_add_buddy_clone(group, buddy);
6268 sipe_add_buddy(gc, b, group);
6271 static void
6272 sipe_buddy_menu_chat_new_cb(PurpleBuddy *buddy)
6274 struct sipe_core_private *sipe_private = PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE;
6276 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_new_cb: buddy->name=%s", buddy->name);
6278 /* 2007+ conference */
6279 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007))
6281 sipe_conf_add(sipe_private, buddy->name);
6283 else /* 2005- multiparty chat */
6285 gchar *self = sip_uri_self(sipe_private);
6286 struct sip_session *session;
6288 session = sipe_session_add_chat(sipe_private,
6289 NULL,
6290 TRUE,
6291 self);
6292 session->chat_session->backend = sipe_backend_chat_create(SIPE_CORE_PUBLIC,
6293 session->chat_session,
6294 session->chat_session->title,
6295 self);
6296 g_free(self);
6298 sipe_invite(sipe_private, session, buddy->name, NULL, NULL, NULL, FALSE);
6303 * For 2007+ conference only.
6305 static void
6306 sipe_buddy_menu_chat_make_leader_cb(PurpleBuddy *buddy,
6307 struct sipe_chat_session *chat_session)
6309 struct sipe_core_private *sipe_private = PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE;
6310 struct sip_session *session;
6312 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_make_leader_cb: buddy->name=%s", buddy->name);
6313 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_make_leader_cb: chat_title=%s", chat_session->title);
6315 session = sipe_session_find_chat(sipe_private, chat_session);
6317 sipe_conf_modify_user_role(sipe_private, session, buddy->name);
6321 * For 2007+ conference only.
6323 static void
6324 sipe_buddy_menu_chat_remove_cb(PurpleBuddy *buddy,
6325 struct sipe_chat_session *chat_session)
6327 struct sipe_core_private *sipe_private = PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE;
6328 struct sip_session *session;
6330 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_remove_cb: buddy->name=%s", buddy->name);
6331 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_remove_cb: chat_title=%s", chat_session->title);
6333 session = sipe_session_find_chat(sipe_private, chat_session);
6335 sipe_conf_delete_user(sipe_private, session, buddy->name);
6338 static void
6339 sipe_buddy_menu_chat_invite_cb(PurpleBuddy *buddy,
6340 struct sipe_chat_session *chat_session)
6342 struct sipe_core_private *sipe_private = PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE;
6344 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_invite_cb: buddy->name=%s", buddy->name);
6345 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_invite_cb: chat_title=%s", chat_session->title);
6347 sipe_core_chat_invite(SIPE_CORE_PUBLIC, chat_session, buddy->name);
6350 static void
6351 sipe_buddy_menu_make_call_cb(PurpleBuddy *buddy, const char *phone)
6353 struct sipe_core_private *sipe_private = PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE;
6355 SIPE_DEBUG_INFO("sipe_buddy_menu_make_call_cb: buddy->name=%s", buddy->name);
6356 if (phone) {
6357 char *tel_uri = sip_to_tel_uri(phone);
6359 SIPE_DEBUG_INFO("sipe_buddy_menu_make_call_cb: going to call number: %s", tel_uri ? tel_uri : "");
6360 sip_csta_make_call(sipe_private, tel_uri);
6362 g_free(tel_uri);
6366 static void
6367 sipe_buddy_menu_access_level_help_cb(PurpleBuddy *buddy)
6369 /** Translators: replace with URL to localized page
6370 * If it doesn't exist copy the original URL */
6371 purple_notify_uri(buddy->account->gc, _("https://sourceforge.net/apps/mediawiki/sipe/index.php?title=Access_Levels"));
6374 static void
6375 sipe_buddy_menu_send_email_cb(PurpleBuddy *buddy)
6377 const gchar *email;
6378 SIPE_DEBUG_INFO("sipe_buddy_menu_send_email_cb: buddy->name=%s", buddy->name);
6380 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
6381 if (email)
6383 char *command_line = g_strdup_printf(
6384 #ifdef _WIN32
6385 "cmd /c start"
6386 #else
6387 "xdg-email"
6388 #endif
6389 " mailto:%s", email);
6390 SIPE_DEBUG_INFO("sipe_buddy_menu_send_email_cb: going to call email client: %s", command_line);
6392 g_spawn_command_line_async(command_line, NULL);
6393 g_free(command_line);
6395 else
6397 SIPE_DEBUG_INFO("sipe_buddy_menu_send_email_cb: no email address stored for buddy=%s", buddy->name);
6401 static void
6402 sipe_buddy_menu_access_level_cb(PurpleBuddy *buddy,
6403 struct sipe_container *container)
6405 struct sipe_core_private *sipe_private = PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE;
6406 struct sipe_container_member *member;
6408 if (!container || !container->members) return;
6410 member = ((struct sipe_container_member *)container->members->data);
6412 if (!member->type) return;
6414 SIPE_DEBUG_INFO("sipe_buddy_menu_access_level_cb: container->id=%d, member->type=%s, member->value=%s",
6415 container->id, member->type, member->value ? member->value : "");
6417 sipe_change_access_level(sipe_private, container->id, member->type, member->value);
6420 static GList *
6421 sipe_get_access_control_menu(struct sipe_core_private *sipe_private,
6422 const char* uri);
6425 * A menu which appear when right-clicking on buddy in contact list.
6427 GList *
6428 sipe_buddy_menu(PurpleBuddy *buddy)
6430 PurpleBlistNode *g_node;
6431 PurpleGroup *gr_parent;
6432 PurpleMenuAction *act;
6433 GList *menu = NULL;
6434 GList *menu_groups = NULL;
6435 struct sipe_core_private *sipe_private = PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE;
6436 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
6437 const char *email;
6438 gchar *self = sip_uri_self(sipe_private);
6440 SIPE_SESSION_FOREACH {
6441 if (!sipe_strcase_equal(self, buddy->name) && session->chat_session)
6443 struct sipe_chat_session *chat_session = session->chat_session;
6444 gboolean is_conf = (chat_session->type == SIPE_CHAT_TYPE_CONFERENCE);
6446 if (sipe_backend_chat_find(chat_session->backend, buddy->name))
6448 gboolean conf_op = sipe_backend_chat_is_operator(chat_session->backend, self);
6450 if (is_conf
6451 && !sipe_backend_chat_is_operator(chat_session->backend, buddy->name) /* Not conf OP */
6452 && conf_op) /* We are a conf OP */
6454 gchar *label = g_strdup_printf(_("Make leader of '%s'"),
6455 chat_session->title);
6456 act = purple_menu_action_new(label,
6457 PURPLE_CALLBACK(sipe_buddy_menu_chat_make_leader_cb),
6458 chat_session, NULL);
6459 g_free(label);
6460 menu = g_list_prepend(menu, act);
6463 if (is_conf
6464 && conf_op) /* We are a conf OP */
6466 gchar *label = g_strdup_printf(_("Remove from '%s'"),
6467 chat_session->title);
6468 act = purple_menu_action_new(label,
6469 PURPLE_CALLBACK(sipe_buddy_menu_chat_remove_cb),
6470 chat_session, NULL);
6471 g_free(label);
6472 menu = g_list_prepend(menu, act);
6475 else
6477 if (!is_conf
6478 || (is_conf && !session->locked))
6480 gchar *label = g_strdup_printf(_("Invite to '%s'"),
6481 chat_session->title);
6482 act = purple_menu_action_new(label,
6483 PURPLE_CALLBACK(sipe_buddy_menu_chat_invite_cb),
6484 chat_session, NULL);
6485 g_free(label);
6486 menu = g_list_prepend(menu, act);
6490 } SIPE_SESSION_FOREACH_END;
6492 act = purple_menu_action_new(_("New chat"),
6493 PURPLE_CALLBACK(sipe_buddy_menu_chat_new_cb),
6494 NULL, NULL);
6495 menu = g_list_prepend(menu, act);
6497 if (sip->csta && !sip->csta->line_status) {
6498 const char *phone;
6499 const char *phone_disp_str;
6500 gchar *tmp = NULL;
6501 /* work phone */
6502 phone = purple_blist_node_get_string(&buddy->node, PHONE_PROP);
6503 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_DISPLAY_PROP);
6504 if (phone) {
6505 gchar *label = g_strdup_printf(_("Work %s"),
6506 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
6507 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
6508 g_free(tmp);
6509 tmp = NULL;
6510 g_free(label);
6511 menu = g_list_prepend(menu, act);
6514 /* mobile phone */
6515 phone = purple_blist_node_get_string(&buddy->node, PHONE_MOBILE_PROP);
6516 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_MOBILE_DISPLAY_PROP);
6517 if (phone) {
6518 gchar *label = g_strdup_printf(_("Mobile %s"),
6519 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
6520 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
6521 g_free(tmp);
6522 tmp = NULL;
6523 g_free(label);
6524 menu = g_list_prepend(menu, act);
6527 /* home phone */
6528 phone = purple_blist_node_get_string(&buddy->node, PHONE_HOME_PROP);
6529 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_HOME_DISPLAY_PROP);
6530 if (phone) {
6531 gchar *label = g_strdup_printf(_("Home %s"),
6532 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
6533 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
6534 g_free(tmp);
6535 tmp = NULL;
6536 g_free(label);
6537 menu = g_list_prepend(menu, act);
6540 /* other phone */
6541 phone = purple_blist_node_get_string(&buddy->node, PHONE_OTHER_PROP);
6542 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_OTHER_DISPLAY_PROP);
6543 if (phone) {
6544 gchar *label = g_strdup_printf(_("Other %s"),
6545 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
6546 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
6547 g_free(tmp);
6548 tmp = NULL;
6549 g_free(label);
6550 menu = g_list_prepend(menu, act);
6553 /* custom1 phone */
6554 phone = purple_blist_node_get_string(&buddy->node, PHONE_CUSTOM1_PROP);
6555 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_CUSTOM1_DISPLAY_PROP);
6556 if (phone) {
6557 gchar *label = g_strdup_printf(_("Custom1 %s"),
6558 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
6559 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
6560 g_free(tmp);
6561 tmp = NULL;
6562 g_free(label);
6563 menu = g_list_prepend(menu, act);
6567 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
6568 if (email) {
6569 act = purple_menu_action_new(_("Send email..."),
6570 PURPLE_CALLBACK(sipe_buddy_menu_send_email_cb),
6571 NULL, NULL);
6572 menu = g_list_prepend(menu, act);
6575 /* Access Level */
6576 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
6577 GList *menu_access_levels = sipe_get_access_control_menu(sipe_private, buddy->name);
6579 act = purple_menu_action_new(_("Access level"),
6580 NULL,
6581 NULL, menu_access_levels);
6582 menu = g_list_prepend(menu, act);
6585 /* Copy to */
6586 gr_parent = purple_buddy_get_group(buddy);
6587 for (g_node = purple_blist_get_root(); g_node; g_node = g_node->next) {
6588 PurpleGroup *group;
6590 if (g_node->type != PURPLE_BLIST_GROUP_NODE)
6591 continue;
6593 group = (PurpleGroup *)g_node;
6594 if (group == gr_parent)
6595 continue;
6597 if (purple_find_buddy_in_group(buddy->account, buddy->name, group))
6598 continue;
6600 act = purple_menu_action_new(purple_group_get_name(group),
6601 PURPLE_CALLBACK(sipe_buddy_menu_copy_to_cb),
6602 group->name, NULL);
6603 menu_groups = g_list_prepend(menu_groups, act);
6605 menu_groups = g_list_reverse(menu_groups);
6607 act = purple_menu_action_new(_("Copy to"),
6608 NULL,
6609 NULL, menu_groups);
6610 menu = g_list_prepend(menu, act);
6612 menu = g_list_reverse(menu);
6614 g_free(self);
6615 return menu;
6618 static void
6619 sipe_ask_access_domain_cb(PurpleConnection *gc, PurpleRequestFields *fields)
6621 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
6622 const char *domain = purple_request_fields_get_string(fields, "access_domain");
6623 int index = purple_request_fields_get_choice(fields, "container_id");
6624 /* move Blocked first */
6625 int i = (index == 4) ? 0 : index + 1;
6626 int container_id = containers[i];
6628 SIPE_DEBUG_INFO("sipe_ask_access_domain_cb: domain=%s, container_id=(%d)%d", domain ? domain : "", index, container_id);
6630 sipe_change_access_level(sipe_private, container_id, "domain", domain);
6633 static void
6634 sipe_ask_access_domain(struct sipe_core_private *sipe_private)
6636 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
6637 PurpleAccount *account = sip->account;
6638 PurpleConnection *gc = sip->gc;
6639 PurpleRequestFields *fields;
6640 PurpleRequestFieldGroup *g;
6641 PurpleRequestField *f;
6643 fields = purple_request_fields_new();
6645 g = purple_request_field_group_new(NULL);
6646 f = purple_request_field_string_new("access_domain", _("Domain"), "partner-company.com", FALSE);
6647 purple_request_field_set_required(f, TRUE);
6648 purple_request_field_group_add_field(g, f);
6650 f = purple_request_field_choice_new("container_id", _("Access level"), 0);
6651 purple_request_field_choice_add(f, _("Personal")); /* index 0 */
6652 purple_request_field_choice_add(f, _("Team"));
6653 purple_request_field_choice_add(f, _("Company"));
6654 purple_request_field_choice_add(f, _("Public"));
6655 purple_request_field_choice_add(f, _("Blocked")); /* index 4 */
6656 purple_request_field_choice_set_default_value(f, 3); /* index */
6657 purple_request_field_set_required(f, TRUE);
6658 purple_request_field_group_add_field(g, f);
6660 purple_request_fields_add_group(fields, g);
6662 purple_request_fields(gc, _("Add new domain"),
6663 _("Add new domain"), NULL, fields,
6664 _("Add"), G_CALLBACK(sipe_ask_access_domain_cb),
6665 _("Cancel"), NULL,
6666 account, NULL, NULL, gc);
6669 static void
6670 sipe_buddy_menu_access_level_add_domain_cb(PurpleBuddy *buddy)
6672 sipe_ask_access_domain(PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE);
6676 * Workaround for missing libpurple API to release resources allocated
6677 * during blist_node_menu() callback. See also:
6679 * <http://developer.pidgin.im/ticket/12597>
6681 * We remember all memory blocks in a list and deallocate them when
6683 * - the next time we enter the callback, or
6684 * - the account is disconnected
6686 * That means that after the buddy menu has been closed we have unused
6687 * resources but at least we don't leak them anymore...
6689 static void
6690 sipe_blist_menu_free_containers(struct sipe_core_private *sipe_private)
6692 GSList *entry = sipe_private->blist_menu_containers;
6693 while (entry) {
6694 free_container(entry->data);
6695 entry = entry->next;
6697 g_slist_free(sipe_private->blist_menu_containers);
6698 sipe_private->blist_menu_containers = NULL;
6701 static void
6702 sipe_blist_menu_remember_container(struct sipe_core_private *sipe_private,
6703 struct sipe_container *container)
6705 sipe_private->blist_menu_containers = g_slist_prepend(sipe_private->blist_menu_containers,
6706 container);
6709 static GList *
6710 sipe_get_access_levels_menu(struct sipe_core_private *sipe_private,
6711 const char* member_type,
6712 const char* member_value,
6713 const gboolean extra_menu)
6715 GList *menu_access_levels = NULL;
6716 unsigned int i;
6717 char *menu_name;
6718 PurpleMenuAction *act;
6719 struct sipe_container *container;
6720 struct sipe_container_member *member;
6721 gboolean is_group_access = FALSE;
6722 int container_id = sipe_find_access_level(sipe_private, member_type, member_value, &is_group_access);
6724 for (i = 1; i <= CONTAINERS_LEN; i++) {
6725 /* to put Blocked level last in menu list.
6726 * Blocked should remaim in the first place in the containers[] array.
6728 unsigned int j = (i == CONTAINERS_LEN) ? 0 : i;
6729 const char *acc_level_name = sipe_get_access_level_name(containers[j]);
6731 container = g_new0(struct sipe_container, 1);
6732 member = g_new0(struct sipe_container_member, 1);
6733 container->id = containers[j];
6734 container->members = g_slist_append(container->members, member);
6735 member->type = g_strdup(member_type);
6736 member->value = g_strdup(member_value);
6738 /* libpurple memory leak workaround */
6739 sipe_blist_menu_remember_container(sipe_private, container);
6741 /* current container/access level */
6742 if (((int)containers[j]) == container_id) {
6743 menu_name = is_group_access ?
6744 g_strdup_printf(INDENT_MARKED_INHERITED_FMT, acc_level_name) :
6745 g_strdup_printf(INDENT_MARKED_FMT, acc_level_name);
6746 } else {
6747 menu_name = g_strdup_printf(INDENT_FMT, acc_level_name);
6750 act = purple_menu_action_new(menu_name,
6751 PURPLE_CALLBACK(sipe_buddy_menu_access_level_cb),
6752 container, NULL);
6753 g_free(menu_name);
6754 menu_access_levels = g_list_prepend(menu_access_levels, act);
6757 if (extra_menu && (container_id >= 0)) {
6758 /* separator */
6759 act = purple_menu_action_new(" --------------", NULL, NULL, NULL);
6760 menu_access_levels = g_list_prepend(menu_access_levels, act);
6762 if (!is_group_access) {
6763 container = g_new0(struct sipe_container, 1);
6764 member = g_new0(struct sipe_container_member, 1);
6765 container->id = -1;
6766 container->members = g_slist_append(container->members, member);
6767 member->type = g_strdup(member_type);
6768 member->value = g_strdup(member_value);
6770 /* libpurple memory leak workaround */
6771 sipe_blist_menu_remember_container(sipe_private, container);
6773 /* Translators: remove (clear) previously assigned access level */
6774 menu_name = g_strdup_printf(INDENT_FMT, _("Unspecify"));
6775 act = purple_menu_action_new(menu_name,
6776 PURPLE_CALLBACK(sipe_buddy_menu_access_level_cb),
6777 container, NULL);
6778 g_free(menu_name);
6779 menu_access_levels = g_list_prepend(menu_access_levels, act);
6783 menu_access_levels = g_list_reverse(menu_access_levels);
6784 return menu_access_levels;
6787 static GList *
6788 sipe_get_access_groups_menu(struct sipe_core_private *sipe_private)
6790 GList *menu_access_groups = NULL;
6791 PurpleMenuAction *act;
6792 GSList *access_domains = NULL;
6793 GSList *entry;
6794 char *menu_name;
6795 char *domain;
6797 act = purple_menu_action_new(_("People in my company"),
6798 NULL,
6799 NULL, sipe_get_access_levels_menu(sipe_private, "sameEnterprise", NULL, FALSE));
6800 menu_access_groups = g_list_prepend(menu_access_groups, act);
6802 /* this is original name, don't edit */
6803 act = purple_menu_action_new(_("People in domains connected with my company"),
6804 NULL,
6805 NULL, sipe_get_access_levels_menu(sipe_private, "federated", NULL, FALSE));
6806 menu_access_groups = g_list_prepend(menu_access_groups, act);
6808 act = purple_menu_action_new(_("People in public domains"),
6809 NULL,
6810 NULL, sipe_get_access_levels_menu(sipe_private, "publicCloud", NULL, TRUE));
6811 menu_access_groups = g_list_prepend(menu_access_groups, act);
6813 access_domains = sipe_get_access_domains(sipe_private);
6814 entry = access_domains;
6815 while (entry) {
6816 domain = entry->data;
6818 menu_name = g_strdup_printf(_("People at %s"), domain);
6819 act = purple_menu_action_new(menu_name,
6820 NULL,
6821 NULL, sipe_get_access_levels_menu(sipe_private, "domain", g_strdup(domain), TRUE));
6822 menu_access_groups = g_list_prepend(menu_access_groups, act);
6823 g_free(menu_name);
6825 entry = entry->next;
6828 /* separator */
6829 /* People in domains connected with my company */
6830 act = purple_menu_action_new("-------------------------------------------", NULL, NULL, NULL);
6831 menu_access_groups = g_list_prepend(menu_access_groups, act);
6833 act = purple_menu_action_new(_("Add new domain..."),
6834 PURPLE_CALLBACK(sipe_buddy_menu_access_level_add_domain_cb),
6835 NULL, NULL);
6836 menu_access_groups = g_list_prepend(menu_access_groups, act);
6838 menu_access_groups = g_list_reverse(menu_access_groups);
6840 return menu_access_groups;
6843 static GList *
6844 sipe_get_access_control_menu(struct sipe_core_private *sipe_private,
6845 const char* uri)
6847 GList *menu_access_levels = NULL;
6848 GList *menu_access_groups = NULL;
6849 char *menu_name;
6850 PurpleMenuAction *act;
6852 /* libpurple memory leak workaround */
6853 sipe_blist_menu_free_containers(sipe_private);
6855 menu_access_levels = sipe_get_access_levels_menu(sipe_private, "user", sipe_get_no_sip_uri(uri), TRUE);
6857 menu_access_groups = sipe_get_access_groups_menu(sipe_private);
6859 menu_name = g_strdup_printf(INDENT_FMT, _("Access groups"));
6860 act = purple_menu_action_new(menu_name,
6861 NULL,
6862 NULL, menu_access_groups);
6863 g_free(menu_name);
6864 menu_access_levels = g_list_append(menu_access_levels, act);
6866 menu_name = g_strdup_printf(INDENT_FMT, _("Online help..."));
6867 act = purple_menu_action_new(menu_name,
6868 PURPLE_CALLBACK(sipe_buddy_menu_access_level_help_cb),
6869 NULL, NULL);
6870 g_free(menu_name);
6871 menu_access_levels = g_list_append(menu_access_levels, act);
6873 return menu_access_levels;
6876 static gboolean
6877 process_get_info_response(struct sipe_core_private *sipe_private,
6878 struct sipmsg *msg, struct transaction *trans)
6880 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
6881 char *uri = trans->payload->data;
6883 PurpleNotifyUserInfo *info;
6884 PurpleBuddy *pbuddy = NULL;
6885 struct sipe_buddy *sbuddy;
6886 const char *alias = NULL;
6887 char *device_name = NULL;
6888 char *server_alias = NULL;
6889 char *phone_number = NULL;
6890 char *email = NULL;
6891 const char *site;
6892 char *first_name = NULL;
6893 char *last_name = NULL;
6895 if (!sip) return FALSE;
6897 SIPE_DEBUG_INFO("Fetching %s's user info for %s", uri, sipe_private->username);
6899 pbuddy = purple_find_buddy((PurpleAccount *)sip->account, uri);
6900 alias = purple_buddy_get_local_alias(pbuddy);
6902 //will query buddy UA's capabilities and send answer to log
6903 sipe_options_request(sipe_private, uri);
6905 sbuddy = g_hash_table_lookup(sipe_private->buddies, uri);
6906 if (sbuddy) {
6907 device_name = sbuddy->device_name ? g_strdup(sbuddy->device_name) : NULL;
6910 info = purple_notify_user_info_new();
6912 if (msg->response != 200) {
6913 SIPE_DEBUG_INFO("process_get_info_response: SERVICE response is %d", msg->response);
6914 } else {
6915 sipe_xml *searchResults;
6916 const sipe_xml *mrow;
6918 SIPE_DEBUG_INFO("process_get_info_response: body:\n%s", msg->body ? msg->body : "");
6919 searchResults = sipe_xml_parse(msg->body, msg->bodylen);
6920 if (!searchResults) {
6921 SIPE_DEBUG_INFO_NOFORMAT("process_get_info_response: no parseable searchResults");
6922 } else if ((mrow = sipe_xml_child(searchResults, "Body/Array/row"))) {
6923 const char *value;
6924 server_alias = g_strdup(sipe_xml_attribute(mrow, "displayName"));
6925 email = g_strdup(sipe_xml_attribute(mrow, "email"));
6926 phone_number = g_strdup(sipe_xml_attribute(mrow, "phone"));
6928 /* For 2007 system we will take this from ContactCard -
6929 * it has cleaner tel: URIs at least
6931 if (!SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
6932 char *tel_uri = sip_to_tel_uri(phone_number);
6933 /* trims its parameters, so call first */
6934 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_DISPLAY_NAME, server_alias);
6935 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_EMAIL, email);
6936 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_WORK_PHONE, tel_uri);
6937 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_WORK_PHONE_DISPLAY, phone_number);
6938 g_free(tel_uri);
6941 if (server_alias && strlen(server_alias) > 0) {
6942 purple_notify_user_info_add_pair(info, _("Display name"), server_alias);
6944 if ((value = sipe_xml_attribute(mrow, "title")) && strlen(value) > 0) {
6945 purple_notify_user_info_add_pair(info, _("Job title"), value);
6947 if ((value = sipe_xml_attribute(mrow, "office")) && strlen(value) > 0) {
6948 purple_notify_user_info_add_pair(info, _("Office"), value);
6950 if (phone_number && strlen(phone_number) > 0) {
6951 purple_notify_user_info_add_pair(info, _("Business phone"), phone_number);
6953 if ((value = sipe_xml_attribute(mrow, "company")) && strlen(value) > 0) {
6954 purple_notify_user_info_add_pair(info, _("Company"), value);
6956 if ((value = sipe_xml_attribute(mrow, "city")) && strlen(value) > 0) {
6957 purple_notify_user_info_add_pair(info, _("City"), value);
6959 if ((value = sipe_xml_attribute(mrow, "state")) && strlen(value) > 0) {
6960 purple_notify_user_info_add_pair(info, _("State"), value);
6962 if ((value = sipe_xml_attribute(mrow, "country")) && strlen(value) > 0) {
6963 purple_notify_user_info_add_pair(info, _("Country"), value);
6965 if (email && strlen(email) > 0) {
6966 purple_notify_user_info_add_pair(info, _("Email address"), email);
6970 sipe_xml_free(searchResults);
6973 purple_notify_user_info_add_section_break(info);
6975 if (is_empty(server_alias)) {
6976 g_free(server_alias);
6977 server_alias = g_strdup(purple_buddy_get_server_alias(pbuddy));
6978 if (server_alias) {
6979 purple_notify_user_info_add_pair(info, _("Display name"), server_alias);
6983 /* present alias if it differs from server alias */
6984 if (alias && !sipe_strequal(alias, server_alias))
6986 purple_notify_user_info_add_pair(info, _("Alias"), alias);
6989 if (is_empty(email)) {
6990 g_free(email);
6991 email = g_strdup(purple_blist_node_get_string(&pbuddy->node, EMAIL_PROP));
6992 if (email) {
6993 purple_notify_user_info_add_pair(info, _("Email address"), email);
6997 site = purple_blist_node_get_string(&pbuddy->node, SITE_PROP);
6998 if (site) {
6999 purple_notify_user_info_add_pair(info, _("Site"), site);
7002 sipe_get_first_last_names(sipe_private, uri, &first_name, &last_name);
7003 if (first_name && last_name) {
7004 char *link = g_strconcat("http://www.linkedin.com/pub/dir/", first_name, "/", last_name, NULL);
7006 purple_notify_user_info_add_pair(info, _("Find on LinkedIn"), link);
7007 g_free(link);
7009 g_free(first_name);
7010 g_free(last_name);
7012 if (device_name) {
7013 purple_notify_user_info_add_pair(info, _("Device"), device_name);
7016 /* show a buddy's user info in a nice dialog box */
7017 purple_notify_userinfo(sip->gc, /* connection the buddy info came through */
7018 uri, /* buddy's URI */
7019 info, /* body */
7020 NULL, /* callback called when dialog closed */
7021 NULL); /* userdata for callback */
7023 g_free(phone_number);
7024 g_free(server_alias);
7025 g_free(email);
7026 g_free(device_name);
7028 return TRUE;
7032 * AD search first, LDAP based
7034 void sipe_get_info(PurpleConnection *gc, const char *username)
7036 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
7037 gchar *domain_uri = sip_uri_from_name(sipe_private->public.sip_domain);
7038 char *row = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, "msRTCSIP-PrimaryUserAddress", username);
7039 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 1, row);
7040 struct transaction_payload *payload = g_new0(struct transaction_payload, 1);
7042 payload->destroy = g_free;
7043 payload->data = g_strdup(username);
7045 SIPE_DEBUG_INFO("sipe_get_contact_data: body:\n%s", body ? body : "");
7046 send_soap_request_with_cb(sipe_private, domain_uri, body,
7047 process_get_info_response, payload);
7048 g_free(domain_uri);
7049 g_free(body);
7050 g_free(row);
7054 Local Variables:
7055 mode: c
7056 c-file-style: "bsd"
7057 indent-tabs-mode: t
7058 tab-width: 8
7059 End: