Fix #3161273: Lost Connection Gives No Error Message (part I)
[siplcs.git] / src / core / sipe.c
blob1e78713f4435eccd7f185d44df8895c50f022059
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 gboolean
3108 process_message_timeout(struct sipe_core_private *sipe_private,
3109 struct sipmsg *msg,
3110 SIPE_UNUSED_PARAMETER struct transaction *trans)
3112 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
3113 struct sip_session *session = sipe_session_find_im(sipe_private, with);
3114 gchar *cseq;
3115 char *key;
3116 sipe_backend_buddy pbuddy;
3117 gchar *alias = NULL;
3119 if (!session) {
3120 SIPE_DEBUG_INFO_NOFORMAT("process_message_timeout: unable to find IM session");
3121 g_free(with);
3122 return TRUE;
3125 /* Remove timed-out message from unconfirmed list */
3126 cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
3127 key = g_strdup_printf("<%s><%d><MESSAGE><%s>", sipmsg_find_header(msg, "Call-ID"), atoi(cseq), with);
3128 g_free(cseq);
3129 g_hash_table_remove(session->unconfirmed_messages, key);
3130 SIPE_DEBUG_INFO("process_message_timeout: removed message %s from unconfirmed_messages(count=%d)",
3131 key, g_hash_table_size(session->unconfirmed_messages));
3132 g_free(key);
3134 if ((pbuddy = sipe_backend_buddy_find(SIPE_CORE_PUBLIC, with, NULL))) {
3135 alias = sipe_backend_buddy_get_alias(SIPE_CORE_PUBLIC,pbuddy);
3138 sipe_present_message_undelivered_err(sipe_private, session, -1, -1,
3139 alias ? alias : with,
3140 msg->body);
3142 g_free(alias);
3143 g_free(with);
3144 return TRUE;
3147 static void sipe_send_message(struct sipe_core_private *sipe_private,
3148 struct sip_dialog *dialog,
3149 const char *msg, const char *content_type)
3151 gchar *hdr;
3152 gchar *tmp;
3153 char *msgtext = NULL;
3154 const gchar *msgr = "";
3155 gchar *tmp2 = NULL;
3157 if (content_type == NULL)
3158 content_type = "text/plain";
3160 if (!g_str_has_prefix(content_type, "text/x-msmsgsinvite")) {
3161 char *msgformat;
3162 gchar *msgr_value;
3164 sipe_parse_html(msg, &msgformat, &msgtext);
3165 SIPE_DEBUG_INFO("sipe_send_message: msgformat=%s", msgformat);
3167 msgr_value = sipmsg_get_msgr_string(msgformat);
3168 g_free(msgformat);
3169 if (msgr_value) {
3170 msgr = tmp2 = g_strdup_printf(";msgr=%s", msgr_value);
3171 g_free(msgr_value);
3173 } else {
3174 msgtext = g_strdup(msg);
3177 tmp = get_contact(sipe_private);
3178 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8\r\n");
3179 //hdr = g_strdup("Content-Type: text/rtf\r\n");
3180 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8;msgr=WAAtAE0ATQBTAC....AoADQA\r\nSupported: timer\r\n");
3182 hdr = g_strdup_printf("Contact: %s\r\nContent-Type: %s; charset=UTF-8%s\r\n", tmp, content_type, msgr);
3183 g_free(tmp);
3184 g_free(tmp2);
3186 sip_transport_request_timeout(sipe_private,
3187 "MESSAGE",
3188 dialog->with,
3189 dialog->with,
3190 hdr,
3191 msgtext,
3192 dialog,
3193 process_message_response,
3195 process_message_timeout);
3196 g_free(msgtext);
3197 g_free(hdr);
3201 void
3202 sipe_im_process_queue (struct sipe_core_private *sipe_private,
3203 struct sip_session * session)
3205 GSList *entry2 = session->outgoing_message_queue;
3206 while (entry2) {
3207 struct queued_message *msg = entry2->data;
3209 /* for multiparty chat or conference */
3210 if (session->chat_session) {
3211 gchar *who = sip_uri_self(sipe_private);
3212 sipe_backend_chat_message(SIPE_CORE_PUBLIC,
3213 session->chat_session->backend,
3214 who,
3215 msg->body);
3216 g_free(who);
3219 SIPE_DIALOG_FOREACH {
3220 char *key;
3221 struct queued_message *message;
3223 if (dialog->outgoing_invite) continue; /* do not send messages as INVITE is not responded. */
3225 message = g_new0(struct queued_message,1);
3226 message->body = g_strdup(msg->body);
3227 if (msg->content_type != NULL)
3228 message->content_type = g_strdup(msg->content_type);
3230 key = g_strdup_printf("<%s><%d><MESSAGE><%s>", dialog->callid, (dialog->cseq) + 1, dialog->with);
3231 g_hash_table_insert(session->unconfirmed_messages, g_strdup(key), message);
3232 SIPE_DEBUG_INFO("sipe_im_process_queue: added message %s to unconfirmed_messages(count=%d)",
3233 key, g_hash_table_size(session->unconfirmed_messages));
3234 g_free(key);
3236 sipe_send_message(sipe_private, dialog, msg->body, msg->content_type);
3237 } SIPE_DIALOG_FOREACH_END;
3239 entry2 = sipe_session_dequeue_message(session);
3243 static void
3244 sipe_refer_notify(struct sipe_core_private *sipe_private,
3245 struct sip_session *session,
3246 const gchar *who,
3247 int status,
3248 const gchar *desc)
3250 gchar *hdr;
3251 gchar *body;
3252 struct sip_dialog *dialog = sipe_dialog_find(session, who);
3254 hdr = g_strdup_printf(
3255 "Event: refer\r\n"
3256 "Subscription-State: %s\r\n"
3257 "Content-Type: message/sipfrag\r\n",
3258 status >= 200 ? "terminated" : "active");
3260 body = g_strdup_printf(
3261 "SIP/2.0 %d %s\r\n",
3262 status, desc);
3264 sip_transport_request(sipe_private,
3265 "NOTIFY",
3266 who,
3267 who,
3268 hdr,
3269 body,
3270 dialog,
3271 NULL);
3273 g_free(hdr);
3274 g_free(body);
3277 static gboolean
3278 process_invite_response(struct sipe_core_private *sipe_private,
3279 struct sipmsg *msg, struct transaction *trans)
3281 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
3282 struct sip_session *session;
3283 struct sip_dialog *dialog;
3284 char *cseq;
3285 char *key;
3286 struct queued_message *message;
3287 struct sipmsg *request_msg = trans->msg;
3289 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
3290 gchar *referred_by;
3292 session = sipe_session_find_chat_or_im(sipe_private, callid, with);
3293 if (!session) {
3294 SIPE_DEBUG_INFO_NOFORMAT("process_invite_response: unable to find IM session");
3295 g_free(with);
3296 return FALSE;
3299 dialog = sipe_dialog_find(session, with);
3300 if (!dialog) {
3301 SIPE_DEBUG_INFO_NOFORMAT("process_invite_response: session outgoing dialog is NULL");
3302 g_free(with);
3303 return FALSE;
3306 sipe_dialog_parse(dialog, msg, TRUE);
3308 cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
3309 key = g_strdup_printf("<%s><%d><INVITE>", dialog->callid, atoi(cseq));
3310 g_free(cseq);
3311 message = g_hash_table_lookup(session->unconfirmed_messages, key);
3313 if (msg->response != 200) {
3314 sipe_backend_buddy pbuddy;
3315 gchar *alias = g_strdup(with);
3316 const char *warn_hdr = sipmsg_find_header(msg, "Warning");
3317 int warning = -1;
3319 SIPE_DEBUG_INFO_NOFORMAT("process_invite_response: INVITE response not 200");
3321 if (warn_hdr) {
3322 gchar **parts = g_strsplit(warn_hdr, " ", 2);
3323 if (parts[0]) {
3324 warning = atoi(parts[0]);
3326 g_strfreev(parts);
3329 /* cancel file transfer as rejected by server */
3330 if (msg->response == 606 && /* Not acceptable all. */
3331 warning == 309 && /* Message contents not allowed by policy */
3332 message && g_str_has_prefix(message->content_type, "text/x-msmsgsinvite"))
3334 GSList *parsed_body = sipe_ft_parse_msg_body(message->body);
3335 sipe_ft_incoming_cancel(dialog, parsed_body);
3336 sipe_utils_nameval_free(parsed_body);
3339 if ((pbuddy = sipe_backend_buddy_find(SIPE_CORE_PUBLIC, with, NULL))) {
3340 g_free(alias);
3341 alias = sipe_backend_buddy_get_alias(SIPE_CORE_PUBLIC, pbuddy);
3344 if (message) {
3345 sipe_present_message_undelivered_err(sipe_private, session, msg->response, warning, alias, message->body);
3346 } else {
3347 gchar *tmp_msg = g_strdup_printf(_("Failed to invite %s"), alias);
3348 sipe_present_err(sipe_private, session, tmp_msg);
3349 g_free(tmp_msg);
3352 sipe_dialog_remove(session, with);
3354 if (session->is_groupchat) {
3355 sipe_groupchat_invite_failed(sipe_private, session);
3358 g_free(key);
3359 g_free(with);
3360 g_free(alias);
3361 return FALSE;
3364 dialog->cseq = 0;
3365 sip_transport_ack(sipe_private, dialog);
3366 dialog->outgoing_invite = NULL;
3367 dialog->is_established = TRUE;
3369 referred_by = parse_from(sipmsg_find_header(request_msg, "Referred-By"));
3370 if (referred_by) {
3371 sipe_refer_notify(sipe_private, session, referred_by, 200, "OK");
3372 g_free(referred_by);
3375 /* add user to chat if it is a multiparty session */
3376 if (session->chat_session &&
3377 (session->chat_session->type == SIPE_CHAT_TYPE_MULTIPARTY)) {
3378 sipe_backend_chat_add(session->chat_session->backend,
3379 with,
3380 TRUE);
3383 if (session->is_groupchat) {
3384 sipe_groupchat_invite_response(sipe_private, dialog);
3387 if(g_slist_find_custom(dialog->supported, "ms-text-format", (GCompareFunc)g_ascii_strcasecmp)) {
3388 SIPE_DEBUG_INFO_NOFORMAT("process_invite_response: remote system accepted message in INVITE");
3389 sipe_session_dequeue_message(session);
3392 sipe_im_process_queue(sipe_private, session);
3394 g_hash_table_remove(session->unconfirmed_messages, key);
3395 SIPE_DEBUG_INFO("process_invite_response: removed message %s from unconfirmed_messages(count=%d)",
3396 key, g_hash_table_size(session->unconfirmed_messages));
3398 g_free(key);
3399 g_free(with);
3400 return TRUE;
3404 void
3405 sipe_invite(struct sipe_core_private *sipe_private,
3406 struct sip_session *session,
3407 const gchar *who,
3408 const gchar *msg_body,
3409 const gchar *msg_content_type,
3410 const gchar *referred_by,
3411 const gboolean is_triggered)
3413 gchar *hdr;
3414 gchar *to;
3415 gchar *contact;
3416 gchar *body;
3417 gchar *self;
3418 char *ms_text_format = NULL;
3419 char *ms_conversation_id = NULL;
3420 gchar *roster_manager;
3421 gchar *end_points;
3422 gchar *referred_by_str;
3423 gboolean is_multiparty =
3424 session->chat_session &&
3425 (session->chat_session->type == SIPE_CHAT_TYPE_MULTIPARTY);
3426 struct sip_dialog *dialog = sipe_dialog_find(session, who);
3428 if (dialog && dialog->is_established) {
3429 SIPE_DEBUG_INFO("session with %s already has a dialog open", who);
3430 return;
3433 if (!dialog) {
3434 dialog = sipe_dialog_add(session);
3435 dialog->callid = session->callid ? g_strdup(session->callid) : gencallid();
3436 dialog->with = g_strdup(who);
3439 if (!(dialog->ourtag)) {
3440 dialog->ourtag = gentag();
3443 to = sip_uri(who);
3445 if (msg_body) {
3446 char *msgtext = NULL;
3447 char *base64_msg;
3448 const gchar *msgr = "";
3449 char *key;
3450 struct queued_message *message;
3451 gchar *tmp = NULL;
3453 if (!g_str_has_prefix(msg_content_type, "text/x-msmsgsinvite")) {
3454 char *msgformat;
3455 gchar *msgr_value;
3457 sipe_parse_html(msg_body, &msgformat, &msgtext);
3458 SIPE_DEBUG_INFO("sipe_invite: msgformat=%s", msgformat);
3460 msgr_value = sipmsg_get_msgr_string(msgformat);
3461 g_free(msgformat);
3462 if (msgr_value) {
3463 msgr = tmp = g_strdup_printf(";msgr=%s", msgr_value);
3464 g_free(msgr_value);
3467 /* When Sipe reconnects after a crash, we are not able
3468 * to send messages to contacts with which we had open
3469 * conversations when the crash occured. Server sends
3470 * error response with reason="This client has an IM
3471 * session with the same conversation ID"
3473 * Setting random Ms-Conversation-ID prevents this problem
3474 * so we can continue the conversation. */
3475 ms_conversation_id = g_strdup_printf("Ms-Conversation-ID: %u\r\n",
3476 rand() % 1000000000);
3477 } else {
3478 msgtext = g_strdup(msg_body);
3481 base64_msg = g_base64_encode((guchar*) msgtext, strlen(msgtext));
3482 ms_text_format = g_strdup_printf(SIPE_INVITE_TEXT,
3483 msg_content_type ? msg_content_type : "text/plain",
3484 msgr,
3485 base64_msg);
3486 g_free(msgtext);
3487 g_free(tmp);
3488 g_free(base64_msg);
3490 message = g_new0(struct queued_message,1);
3491 message->body = g_strdup(msg_body);
3492 if (msg_content_type != NULL)
3493 message->content_type = g_strdup(msg_content_type);
3495 key = g_strdup_printf("<%s><%d><INVITE>", dialog->callid, (dialog->cseq) + 1);
3496 g_hash_table_insert(session->unconfirmed_messages, g_strdup(key), message);
3497 SIPE_DEBUG_INFO("sipe_invite: added message %s to unconfirmed_messages(count=%d)",
3498 key, g_hash_table_size(session->unconfirmed_messages));
3499 g_free(key);
3502 contact = get_contact(sipe_private);
3503 end_points = get_end_points(sipe_private, session);
3504 self = sip_uri_self(sipe_private);
3505 roster_manager = g_strdup_printf(
3506 "Roster-Manager: %s\r\n"
3507 "EndPoints: %s\r\n",
3508 self,
3509 end_points);
3510 referred_by_str = referred_by ?
3511 g_strdup_printf(
3512 "Referred-By: %s\r\n",
3513 referred_by)
3514 : g_strdup("");
3515 hdr = g_strdup_printf(
3516 "Supported: ms-sender\r\n"
3517 "%s"
3518 "%s"
3519 "%s"
3520 "%s"
3521 "Contact: %s\r\n%s"
3522 "%s"
3523 "Content-Type: application/sdp\r\n",
3524 is_multiparty && sipe_strcase_equal(session->chat_session->id, self) ? roster_manager : "",
3525 referred_by_str,
3526 is_triggered ? "TriggeredInvite: TRUE\r\n" : "",
3527 is_triggered || is_multiparty ? "Require: com.microsoft.rtc-multiparty\r\n" : "",
3528 contact,
3529 ms_text_format ? ms_text_format : "",
3530 ms_conversation_id ? ms_conversation_id : "");
3531 g_free(ms_text_format);
3532 g_free(ms_conversation_id);
3533 g_free(self);
3535 body = g_strdup_printf(
3536 "v=0\r\n"
3537 "o=- 0 0 IN IP4 %s\r\n"
3538 "s=session\r\n"
3539 "c=IN IP4 %s\r\n"
3540 "t=0 0\r\n"
3541 "m=%s %d sip null\r\n"
3542 "a=accept-types:" SDP_ACCEPT_TYPES "\r\n",
3543 sipe_backend_network_ip_address(),
3544 sipe_backend_network_ip_address(),
3545 SIPE_CORE_PRIVATE_FLAG_IS(OCS2007) ? "message" : "x-ms-message",
3546 sip_transport_port(sipe_private));
3548 dialog->outgoing_invite = sip_transport_request(sipe_private,
3549 "INVITE",
3552 hdr,
3553 body,
3554 dialog,
3555 process_invite_response);
3557 g_free(to);
3558 g_free(roster_manager);
3559 g_free(end_points);
3560 g_free(referred_by_str);
3561 g_free(body);
3562 g_free(hdr);
3563 g_free(contact);
3566 void
3567 sipe_convo_closed(PurpleConnection * gc, const char *who)
3569 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
3571 SIPE_DEBUG_INFO("conversation with %s closed", who);
3572 sipe_session_close(sipe_private,
3573 sipe_session_find_im(sipe_private, who));
3576 int sipe_im_send(PurpleConnection *gc, const char *who, const char *what,
3577 SIPE_UNUSED_PARAMETER PurpleMessageFlags flags)
3579 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
3580 struct sip_session *session;
3581 struct sip_dialog *dialog;
3582 gchar *uri = sip_uri(who);
3584 SIPE_DEBUG_INFO("sipe_im_send what='%s'", what);
3586 session = sipe_session_find_or_add_im(sipe_private, uri);
3587 dialog = sipe_dialog_find(session, uri);
3589 // Queue the message
3590 sipe_session_enqueue_message(session, what, NULL);
3592 if (dialog && !dialog->outgoing_invite) {
3593 sipe_im_process_queue(sipe_private, session);
3594 } else if (!dialog || !dialog->outgoing_invite) {
3595 // Need to send the INVITE to get the outgoing dialog setup
3596 sipe_invite(sipe_private, session, uri, what, NULL, NULL, FALSE);
3599 g_free(uri);
3600 return 1;
3604 * Returns 2005-style activity and Availability.
3606 * @param status Sipe statis id.
3608 static void
3609 sipe_get_act_avail_by_status_2005(const char *status,
3610 int *activity,
3611 int *availability)
3613 int avail = 300; /* online */
3614 int act = 400; /* Available */
3616 if (sipe_strequal(status, SIPE_STATUS_ID_AWAY)) {
3617 act = 100;
3618 //} else if (sipe_strequal(status, SIPE_STATUS_ID_LUNCH)) {
3619 // act = 150;
3620 } else if (sipe_strequal(status, SIPE_STATUS_ID_BRB)) {
3621 act = 300;
3622 } else if (sipe_strequal(status, SIPE_STATUS_ID_AVAILABLE)) {
3623 act = 400;
3624 //} else if (sipe_strequal(status, SIPE_STATUS_ID_ON_PHONE)) {
3625 // act = 500;
3626 } else if (sipe_strequal(status, SIPE_STATUS_ID_BUSY) ||
3627 sipe_strequal(status, SIPE_STATUS_ID_DND)) {
3628 act = 600;
3629 } else if (sipe_strequal(status, SIPE_STATUS_ID_INVISIBLE) ||
3630 sipe_strequal(status, SIPE_STATUS_ID_OFFLINE)) {
3631 avail = 0; /* offline */
3632 act = 100;
3633 } else {
3634 act = 400; /* Available */
3637 if (activity) *activity = act;
3638 if (availability) *availability = avail;
3642 * [MS-SIP] 2.2.1
3644 * @param activity 2005 aggregated activity. Ex.: 600
3645 * @param availablity 2005 aggregated availablity. Ex.: 300
3647 static const char *
3648 sipe_get_status_by_act_avail_2005(const int activity,
3649 const int availablity,
3650 char **activity_desc)
3652 const char *status_id = NULL;
3653 const char *act = NULL;
3655 if (activity < 150) {
3656 status_id = SIPE_STATUS_ID_AWAY;
3657 } else if (activity < 200) {
3658 //status_id = SIPE_STATUS_ID_LUNCH;
3659 status_id = SIPE_STATUS_ID_AWAY;
3660 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_LUNCH);
3661 } else if (activity < 300) {
3662 //status_id = SIPE_STATUS_ID_IDLE;
3663 status_id = SIPE_STATUS_ID_AWAY;
3664 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_INACTIVE);
3665 } else if (activity < 400) {
3666 status_id = SIPE_STATUS_ID_BRB;
3667 } else if (activity < 500) {
3668 status_id = SIPE_STATUS_ID_AVAILABLE;
3669 } else if (activity < 600) {
3670 //status_id = SIPE_STATUS_ID_ON_PHONE;
3671 status_id = SIPE_STATUS_ID_BUSY;
3672 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_ON_PHONE);
3673 } else if (activity < 700) {
3674 status_id = SIPE_STATUS_ID_BUSY;
3675 } else if (activity < 800) {
3676 status_id = SIPE_STATUS_ID_AWAY;
3677 } else {
3678 status_id = SIPE_STATUS_ID_AVAILABLE;
3681 if (availablity < 100)
3682 status_id = SIPE_STATUS_ID_OFFLINE;
3684 if (activity_desc && act) {
3685 g_free(*activity_desc);
3686 *activity_desc = g_strdup(act);
3689 return status_id;
3693 * [MS-PRES] Table 3: Conversion of legacyInterop elements and attributes to MSRTC elements and attributes.
3695 static const char*
3696 sipe_get_status_by_availability(int avail,
3697 char** activity_desc)
3699 const char *status;
3700 const char *act = NULL;
3702 if (avail < 3000) {
3703 status = SIPE_STATUS_ID_OFFLINE;
3704 } else if (avail < 4500) {
3705 status = SIPE_STATUS_ID_AVAILABLE;
3706 } else if (avail < 6000) {
3707 //status = SIPE_STATUS_ID_IDLE;
3708 status = SIPE_STATUS_ID_AVAILABLE;
3709 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_INACTIVE);
3710 } else if (avail < 7500) {
3711 status = SIPE_STATUS_ID_BUSY;
3712 } else if (avail < 9000) {
3713 //status = SIPE_STATUS_ID_BUSYIDLE;
3714 status = SIPE_STATUS_ID_BUSY;
3715 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_BUSYIDLE);
3716 } else if (avail < 12000) {
3717 status = SIPE_STATUS_ID_DND;
3718 } else if (avail < 15000) {
3719 status = SIPE_STATUS_ID_BRB;
3720 } else if (avail < 18000) {
3721 status = SIPE_STATUS_ID_AWAY;
3722 } else {
3723 status = SIPE_STATUS_ID_OFFLINE;
3726 if (activity_desc && act) {
3727 g_free(*activity_desc);
3728 *activity_desc = g_strdup(act);
3731 return status;
3735 * Returns 2007-style availability value
3737 * @param sipe_status_id (in)
3738 * @param activity_token (out) Must be g_free()'d after use if consumed.
3740 static int
3741 sipe_get_availability_by_status(const char* sipe_status_id, char** activity_token)
3743 int availability;
3744 sipe_activity activity = SIPE_ACTIVITY_UNSET;
3746 if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_AWAY)) {
3747 availability = 15500;
3748 if (!activity_token || !(*activity_token)) {
3749 activity = SIPE_ACTIVITY_AWAY;
3751 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_BRB)) {
3752 availability = 12500;
3753 activity = SIPE_ACTIVITY_BRB;
3754 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_DND)) {
3755 availability = 9500;
3756 activity = SIPE_ACTIVITY_DND;
3757 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_BUSY)) {
3758 availability = 6500;
3759 if (!activity_token || !(*activity_token)) {
3760 activity = SIPE_ACTIVITY_BUSY;
3762 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_AVAILABLE)) {
3763 availability = 3500;
3764 activity = SIPE_ACTIVITY_ONLINE;
3765 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_UNKNOWN)) {
3766 availability = 0;
3767 } else {
3768 // Offline or invisible
3769 availability = 18500;
3770 activity = SIPE_ACTIVITY_OFFLINE;
3773 if (activity_token) {
3774 *activity_token = g_strdup(sipe_activity_map[activity].token);
3776 return availability;
3779 static void process_incoming_notify_rlmi(struct sipe_core_private *sipe_private,
3780 const gchar *data, unsigned len)
3782 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
3783 const char *uri;
3784 sipe_xml *xn_categories;
3785 const sipe_xml *xn_category;
3786 const char *status = NULL;
3787 gboolean do_update_status = FALSE;
3788 gboolean has_note_cleaned = FALSE;
3789 gboolean has_free_busy_cleaned = FALSE;
3791 xn_categories = sipe_xml_parse(data, len);
3792 uri = sipe_xml_attribute(xn_categories, "uri"); /* with 'sip:' prefix */
3794 for (xn_category = sipe_xml_child(xn_categories, "category");
3795 xn_category ;
3796 xn_category = sipe_xml_twin(xn_category) )
3798 const sipe_xml *xn_node;
3799 const char *tmp;
3800 const char *attrVar = sipe_xml_attribute(xn_category, "name");
3801 time_t publish_time = (tmp = sipe_xml_attribute(xn_category, "publishTime")) ?
3802 sipe_utils_str_to_time(tmp) : 0;
3804 /* contactCard */
3805 if (sipe_strequal(attrVar, "contactCard"))
3807 const sipe_xml *card = sipe_xml_child(xn_category, "contactCard");
3809 if (card) {
3810 const sipe_xml *node;
3811 /* identity - Display Name and email */
3812 node = sipe_xml_child(card, "identity");
3813 if (node) {
3814 char* display_name = sipe_xml_data(
3815 sipe_xml_child(node, "name/displayName"));
3816 char* email = sipe_xml_data(
3817 sipe_xml_child(node, "email"));
3819 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_DISPLAY_NAME, display_name);
3820 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_EMAIL, email);
3822 g_free(display_name);
3823 g_free(email);
3825 /* company */
3826 node = sipe_xml_child(card, "company");
3827 if (node) {
3828 char* company = sipe_xml_data(node);
3829 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_COMPANY, company);
3830 g_free(company);
3832 /* department */
3833 node = sipe_xml_child(card, "department");
3834 if (node) {
3835 char* department = sipe_xml_data(node);
3836 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_DEPARTMENT, department);
3837 g_free(department);
3839 /* title */
3840 node = sipe_xml_child(card, "title");
3841 if (node) {
3842 char* title = sipe_xml_data(node);
3843 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_JOB_TITLE, title);
3844 g_free(title);
3846 /* office */
3847 node = sipe_xml_child(card, "office");
3848 if (node) {
3849 char* office = sipe_xml_data(node);
3850 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_OFFICE, office);
3851 g_free(office);
3853 /* site (url) */
3854 node = sipe_xml_child(card, "url");
3855 if (node) {
3856 char* site = sipe_xml_data(node);
3857 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_SITE, site);
3858 g_free(site);
3860 /* phone */
3861 for (node = sipe_xml_child(card, "phone");
3862 node;
3863 node = sipe_xml_twin(node))
3865 const char *phone_type = sipe_xml_attribute(node, "type");
3866 char* phone = sipe_xml_data(sipe_xml_child(node, "uri"));
3867 char* phone_display_string = sipe_xml_data(sipe_xml_child(node, "displayString"));
3869 sipe_update_user_phone(sipe_private, uri, phone_type, phone, phone_display_string);
3871 g_free(phone);
3872 g_free(phone_display_string);
3874 /* address */
3875 for (node = sipe_xml_child(card, "address");
3876 node;
3877 node = sipe_xml_twin(node))
3879 if (sipe_strequal(sipe_xml_attribute(node, "type"), "work")) {
3880 char* street = sipe_xml_data(sipe_xml_child(node, "street"));
3881 char* city = sipe_xml_data(sipe_xml_child(node, "city"));
3882 char* state = sipe_xml_data(sipe_xml_child(node, "state"));
3883 char* zipcode = sipe_xml_data(sipe_xml_child(node, "zipcode"));
3884 char* country_code = sipe_xml_data(sipe_xml_child(node, "countryCode"));
3886 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_STREET, street);
3887 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_CITY, city);
3888 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_STATE, state);
3889 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_ZIPCODE, zipcode);
3890 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_COUNTRY, country_code);
3892 g_free(street);
3893 g_free(city);
3894 g_free(state);
3895 g_free(zipcode);
3896 g_free(country_code);
3898 break;
3903 /* note */
3904 else if (sipe_strequal(attrVar, "note"))
3906 if (uri) {
3907 struct sipe_buddy *sbuddy = g_hash_table_lookup(sipe_private->buddies, uri);
3909 if (!has_note_cleaned) {
3910 has_note_cleaned = TRUE;
3912 g_free(sbuddy->note);
3913 sbuddy->note = NULL;
3914 sbuddy->is_oof_note = FALSE;
3915 sbuddy->note_since = publish_time;
3917 do_update_status = TRUE;
3919 if (sbuddy && (publish_time >= sbuddy->note_since)) {
3920 /* clean up in case no 'note' element is supplied
3921 * which indicate note removal in client
3923 g_free(sbuddy->note);
3924 sbuddy->note = NULL;
3925 sbuddy->is_oof_note = FALSE;
3926 sbuddy->note_since = publish_time;
3928 xn_node = sipe_xml_child(xn_category, "note/body");
3929 if (xn_node) {
3930 char *tmp;
3931 sbuddy->note = g_markup_escape_text((tmp = sipe_xml_data(xn_node)), -1);
3932 g_free(tmp);
3933 sbuddy->is_oof_note = sipe_strequal(sipe_xml_attribute(xn_node, "type"), "OOF");
3934 sbuddy->note_since = publish_time;
3936 SIPE_DEBUG_INFO("process_incoming_notify_rlmi: uri(%s), note(%s)",
3937 uri, sbuddy->note ? sbuddy->note : "");
3939 /* to trigger UI refresh in case no status info is supplied in this update */
3940 do_update_status = TRUE;
3944 /* state */
3945 else if(sipe_strequal(attrVar, "state"))
3947 char *tmp;
3948 int availability;
3949 const sipe_xml *xn_availability;
3950 const sipe_xml *xn_activity;
3951 const sipe_xml *xn_meeting_subject;
3952 const sipe_xml *xn_meeting_location;
3953 struct sipe_buddy *sbuddy = uri ? g_hash_table_lookup(sipe_private->buddies, uri) : NULL;
3955 xn_node = sipe_xml_child(xn_category, "state");
3956 if (!xn_node) continue;
3957 xn_availability = sipe_xml_child(xn_node, "availability");
3958 if (!xn_availability) continue;
3959 xn_activity = sipe_xml_child(xn_node, "activity");
3960 xn_meeting_subject = sipe_xml_child(xn_node, "meetingSubject");
3961 xn_meeting_location = sipe_xml_child(xn_node, "meetingLocation");
3963 tmp = sipe_xml_data(xn_availability);
3964 availability = atoi(tmp);
3965 g_free(tmp);
3967 /* activity, meeting_subject, meeting_location */
3968 if (sbuddy) {
3969 char *tmp = NULL;
3971 /* activity */
3972 g_free(sbuddy->activity);
3973 sbuddy->activity = NULL;
3974 if (xn_activity) {
3975 const char *token = sipe_xml_attribute(xn_activity, "token");
3976 const sipe_xml *xn_custom = sipe_xml_child(xn_activity, "custom");
3978 /* from token */
3979 if (!is_empty(token)) {
3980 sbuddy->activity = g_strdup(sipe_get_activity_desc_by_token(token));
3982 /* from custom element */
3983 if (xn_custom) {
3984 char *custom = sipe_xml_data(xn_custom);
3986 if (!is_empty(custom)) {
3987 sbuddy->activity = custom;
3988 custom = NULL;
3990 g_free(custom);
3993 /* meeting_subject */
3994 g_free(sbuddy->meeting_subject);
3995 sbuddy->meeting_subject = NULL;
3996 if (xn_meeting_subject) {
3997 char *meeting_subject = sipe_xml_data(xn_meeting_subject);
3999 if (!is_empty(meeting_subject)) {
4000 sbuddy->meeting_subject = meeting_subject;
4001 meeting_subject = NULL;
4003 g_free(meeting_subject);
4005 /* meeting_location */
4006 g_free(sbuddy->meeting_location);
4007 sbuddy->meeting_location = NULL;
4008 if (xn_meeting_location) {
4009 char *meeting_location = sipe_xml_data(xn_meeting_location);
4011 if (!is_empty(meeting_location)) {
4012 sbuddy->meeting_location = meeting_location;
4013 meeting_location = NULL;
4015 g_free(meeting_location);
4018 status = sipe_get_status_by_availability(availability, &tmp);
4019 if (sbuddy->activity && tmp) {
4020 char *tmp2 = sbuddy->activity;
4022 sbuddy->activity = g_strdup_printf("%s, %s", sbuddy->activity, tmp);
4023 g_free(tmp);
4024 g_free(tmp2);
4025 } else if (tmp) {
4026 sbuddy->activity = tmp;
4030 do_update_status = TRUE;
4032 /* calendarData */
4033 else if(sipe_strequal(attrVar, "calendarData"))
4035 struct sipe_buddy *sbuddy = uri ? g_hash_table_lookup(sipe_private->buddies, uri) : NULL;
4036 const sipe_xml *xn_free_busy = sipe_xml_child(xn_category, "calendarData/freeBusy");
4037 const sipe_xml *xn_working_hours = sipe_xml_child(xn_category, "calendarData/WorkingHours");
4039 if (sbuddy && xn_free_busy) {
4040 if (!has_free_busy_cleaned) {
4041 has_free_busy_cleaned = TRUE;
4043 g_free(sbuddy->cal_start_time);
4044 sbuddy->cal_start_time = NULL;
4046 g_free(sbuddy->cal_free_busy_base64);
4047 sbuddy->cal_free_busy_base64 = NULL;
4049 g_free(sbuddy->cal_free_busy);
4050 sbuddy->cal_free_busy = NULL;
4052 sbuddy->cal_free_busy_published = publish_time;
4055 if (publish_time >= sbuddy->cal_free_busy_published) {
4056 g_free(sbuddy->cal_start_time);
4057 sbuddy->cal_start_time = g_strdup(sipe_xml_attribute(xn_free_busy, "startTime"));
4059 sbuddy->cal_granularity = sipe_strcase_equal(sipe_xml_attribute(xn_free_busy, "granularity"), "PT15M") ?
4060 15 : 0;
4062 g_free(sbuddy->cal_free_busy_base64);
4063 sbuddy->cal_free_busy_base64 = sipe_xml_data(xn_free_busy);
4065 g_free(sbuddy->cal_free_busy);
4066 sbuddy->cal_free_busy = NULL;
4068 sbuddy->cal_free_busy_published = publish_time;
4070 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);
4074 if (sbuddy && xn_working_hours) {
4075 sipe_cal_parse_working_hours(xn_working_hours, sbuddy);
4080 if (do_update_status) {
4081 if (!status) { /* no status category in this update, using contact's current status */
4082 PurpleBuddy *pbuddy = purple_find_buddy((PurpleAccount *)sip->account, uri);
4083 const PurplePresence *presence = purple_buddy_get_presence(pbuddy);
4084 const PurpleStatus *pstatus = purple_presence_get_active_status(presence);
4085 status = purple_status_get_id(pstatus);
4088 SIPE_DEBUG_INFO("process_incoming_notify_rlmi: %s", status);
4089 sipe_core_buddy_got_status(SIPE_CORE_PUBLIC, uri, status);
4092 sipe_xml_free(xn_categories);
4095 static void sipe_subscribe_poolfqdn_resource_uri(const char *host,
4096 GSList *server,
4097 struct sipe_core_private *sipe_private)
4099 struct presence_batched_routed *payload = g_malloc(sizeof(struct presence_batched_routed));
4100 SIPE_DEBUG_INFO("process_incoming_notify_rlmi_resub: pool(%s)", host);
4101 payload->host = g_strdup(host);
4102 payload->buddies = server;
4103 sipe_subscribe_presence_batched_routed(sipe_private,
4104 payload);
4105 sipe_subscribe_presence_batched_routed_free(payload);
4108 static void process_incoming_notify_rlmi_resub(struct sipe_core_private *sipe_private,
4109 const gchar *data, unsigned len)
4111 sipe_xml *xn_list;
4112 const sipe_xml *xn_resource;
4113 GHashTable *servers = g_hash_table_new_full(g_str_hash, g_str_equal,
4114 g_free, NULL);
4115 GSList *server;
4116 gchar *host;
4118 xn_list = sipe_xml_parse(data, len);
4120 for (xn_resource = sipe_xml_child(xn_list, "resource");
4121 xn_resource;
4122 xn_resource = sipe_xml_twin(xn_resource) )
4124 const char *uri, *state;
4125 const sipe_xml *xn_instance;
4127 xn_instance = sipe_xml_child(xn_resource, "instance");
4128 if (!xn_instance) continue;
4130 uri = sipe_xml_attribute(xn_resource, "uri");
4131 state = sipe_xml_attribute(xn_instance, "state");
4132 SIPE_DEBUG_INFO("process_incoming_notify_rlmi_resub: uri(%s),state(%s)", uri, state);
4134 if (strstr(state, "resubscribe")) {
4135 const char *poolFqdn = sipe_xml_attribute(xn_instance, "poolFqdn");
4137 if (poolFqdn) { //[MS-PRES] Section 3.4.5.1.3 Processing Details
4138 gchar *user = g_strdup(uri);
4139 host = g_strdup(poolFqdn);
4140 server = g_hash_table_lookup(servers, host);
4141 server = g_slist_append(server, user);
4142 g_hash_table_insert(servers, host, server);
4143 } else {
4144 sipe_subscribe_presence_single(sipe_private,
4145 (void *) uri);
4150 /* Send out any deferred poolFqdn subscriptions */
4151 g_hash_table_foreach(servers, (GHFunc) sipe_subscribe_poolfqdn_resource_uri, sipe_private);
4152 g_hash_table_destroy(servers);
4154 sipe_xml_free(xn_list);
4157 static void process_incoming_notify_pidf(struct sipe_core_private *sipe_private,
4158 const gchar *data, unsigned len)
4160 gchar *uri;
4161 gchar *getbasic;
4162 gchar *activity = NULL;
4163 sipe_xml *pidf;
4164 const sipe_xml *basicstatus = NULL, *tuple, *status;
4165 gboolean isonline = FALSE;
4166 const sipe_xml *display_name_node;
4168 pidf = sipe_xml_parse(data, len);
4169 if (!pidf) {
4170 SIPE_DEBUG_INFO("process_incoming_notify_pidf: no parseable pidf:%s", data);
4171 return;
4174 if ((tuple = sipe_xml_child(pidf, "tuple")))
4176 if ((status = sipe_xml_child(tuple, "status"))) {
4177 basicstatus = sipe_xml_child(status, "basic");
4181 if (!basicstatus) {
4182 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_notify_pidf: no basic found");
4183 sipe_xml_free(pidf);
4184 return;
4187 getbasic = sipe_xml_data(basicstatus);
4188 if (!getbasic) {
4189 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_notify_pidf: no basic data found");
4190 sipe_xml_free(pidf);
4191 return;
4194 SIPE_DEBUG_INFO("process_incoming_notify_pidf: basic-status(%s)", getbasic);
4195 if (strstr(getbasic, "open")) {
4196 isonline = TRUE;
4198 g_free(getbasic);
4200 uri = sip_uri(sipe_xml_attribute(pidf, "entity")); /* with 'sip:' prefix */ /* AOL comes without the prefix */
4202 display_name_node = sipe_xml_child(pidf, "display-name");
4203 if (display_name_node) {
4204 char * display_name = sipe_xml_data(display_name_node);
4206 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_DISPLAY_NAME, display_name);
4207 g_free(display_name);
4210 if ((tuple = sipe_xml_child(pidf, "tuple"))) {
4211 if ((status = sipe_xml_child(tuple, "status"))) {
4212 if ((basicstatus = sipe_xml_child(status, "activities"))) {
4213 if ((basicstatus = sipe_xml_child(basicstatus, "activity"))) {
4214 activity = sipe_xml_data(basicstatus);
4215 SIPE_DEBUG_INFO("process_incoming_notify_pidf: activity(%s)", activity);
4221 if (isonline) {
4222 const gchar * status_id = NULL;
4223 if (activity) {
4224 if (sipe_strequal(activity, sipe_activity_map[SIPE_ACTIVITY_BUSY].token)) {
4225 status_id = SIPE_STATUS_ID_BUSY;
4226 } else if (sipe_strequal(activity, sipe_activity_map[SIPE_ACTIVITY_AWAY].token)) {
4227 status_id = SIPE_STATUS_ID_AWAY;
4231 if (!status_id) {
4232 status_id = SIPE_STATUS_ID_AVAILABLE;
4235 SIPE_DEBUG_INFO("process_incoming_notify_pidf: status_id(%s)", status_id);
4236 sipe_core_buddy_got_status(SIPE_CORE_PUBLIC, uri, status_id);
4237 } else {
4238 sipe_core_buddy_got_status(SIPE_CORE_PUBLIC, uri, SIPE_STATUS_ID_OFFLINE);
4241 g_free(activity);
4242 g_free(uri);
4243 sipe_xml_free(pidf);
4246 /** 2005 */
4247 static void
4248 sipe_user_info_has_updated(struct sipe_core_private *sipe_private,
4249 const sipe_xml *xn_userinfo)
4251 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
4252 const sipe_xml *xn_states;
4254 g_free(sip->user_states);
4255 sip->user_states = NULL;
4256 if ((xn_states = sipe_xml_child(xn_userinfo, "states")) != NULL) {
4257 gchar *orig = sip->user_states = sipe_xml_stringify(xn_states);
4259 /* this is a hack-around to remove added newline after inner element,
4260 * state in this case, where it shouldn't be.
4261 * After several use of sipe_xml_stringify, amount of added newlines
4262 * grows significantly.
4264 if (orig) {
4265 gchar c, *stripped = orig;
4266 while ((c = *orig++)) {
4267 if ((c != '\n') /* && (c != '\r') */) {
4268 *stripped++ = c;
4271 *stripped = '\0';
4275 /* Publish initial state if not yet.
4276 * Assuming this happens on initial responce to self subscription
4277 * so we've already updated our UserInfo.
4279 if (!sip->initial_state_published) {
4280 send_presence_soap(sipe_private, FALSE);
4281 /* dalayed run */
4282 sipe_schedule_seconds(sipe_private,
4283 "<+update-calendar>",
4284 NULL,
4285 UPDATE_CALENDAR_DELAY,
4286 (sipe_schedule_action) sipe_core_update_calendar,
4287 NULL);
4291 static void process_incoming_notify_msrtc(struct sipe_core_private *sipe_private,
4292 const gchar *data, unsigned len)
4294 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
4295 char *activity = NULL;
4296 const char *epid;
4297 const char *status_id = NULL;
4298 const char *name;
4299 char *uri;
4300 char *self_uri = sip_uri_self(sipe_private);
4301 int avl;
4302 int act;
4303 const char *device_name = NULL;
4304 const char *cal_start_time = NULL;
4305 const char *cal_granularity = NULL;
4306 char *cal_free_busy_base64 = NULL;
4307 struct sipe_buddy *sbuddy;
4308 const sipe_xml *node;
4309 sipe_xml *xn_presentity;
4310 const sipe_xml *xn_availability;
4311 const sipe_xml *xn_activity;
4312 const sipe_xml *xn_display_name;
4313 const sipe_xml *xn_email;
4314 const sipe_xml *xn_phone_number;
4315 const sipe_xml *xn_userinfo;
4316 const sipe_xml *xn_note;
4317 const sipe_xml *xn_oof;
4318 const sipe_xml *xn_state;
4319 const sipe_xml *xn_contact;
4320 char *note;
4321 char *free_activity;
4322 int user_avail;
4323 const char *user_avail_nil;
4324 int res_avail;
4325 time_t user_avail_since = 0;
4326 time_t activity_since = 0;
4328 /* fix for Reuters environment on Linux */
4329 if (data && strstr(data, "encoding=\"utf-16\"")) {
4330 char *tmp_data;
4331 tmp_data = replace(data, "encoding=\"utf-16\"", "encoding=\"utf-8\"");
4332 xn_presentity = sipe_xml_parse(tmp_data, strlen(tmp_data));
4333 g_free(tmp_data);
4334 } else {
4335 xn_presentity = sipe_xml_parse(data, len);
4338 xn_availability = sipe_xml_child(xn_presentity, "availability");
4339 xn_activity = sipe_xml_child(xn_presentity, "activity");
4340 xn_display_name = sipe_xml_child(xn_presentity, "displayName");
4341 xn_email = sipe_xml_child(xn_presentity, "email");
4342 xn_phone_number = sipe_xml_child(xn_presentity, "phoneNumber");
4343 xn_userinfo = sipe_xml_child(xn_presentity, "userInfo");
4344 xn_oof = xn_userinfo ? sipe_xml_child(xn_userinfo, "oof") : NULL;
4345 xn_state = xn_userinfo ? sipe_xml_child(xn_userinfo, "states/state"): NULL;
4346 user_avail = xn_state ? sipe_xml_int_attribute(xn_state, "avail", 0) : 0;
4347 user_avail_since = xn_state ? sipe_utils_str_to_time(sipe_xml_attribute(xn_state, "since")) : 0;
4348 user_avail_nil = xn_state ? sipe_xml_attribute(xn_state, "nil") : NULL;
4349 xn_contact = xn_userinfo ? sipe_xml_child(xn_userinfo, "contact") : NULL;
4350 xn_note = xn_userinfo ? sipe_xml_child(xn_userinfo, "note") : NULL;
4351 note = xn_note ? sipe_xml_data(xn_note) : NULL;
4353 if (sipe_strequal(user_avail_nil, "true")) { /* null-ed */
4354 user_avail = 0;
4355 user_avail_since = 0;
4358 free_activity = NULL;
4360 name = sipe_xml_attribute(xn_presentity, "uri"); /* without 'sip:' prefix */
4361 uri = sip_uri_from_name(name);
4362 avl = sipe_xml_int_attribute(xn_availability, "aggregate", 0);
4363 epid = sipe_xml_attribute(xn_availability, "epid");
4364 act = sipe_xml_int_attribute(xn_activity, "aggregate", 0);
4366 status_id = sipe_get_status_by_act_avail_2005(act, avl, &activity);
4367 res_avail = sipe_get_availability_by_status(status_id, NULL);
4368 if (user_avail > res_avail) {
4369 res_avail = user_avail;
4370 status_id = sipe_get_status_by_availability(user_avail, NULL);
4373 if (xn_display_name) {
4374 char *display_name = g_strdup(sipe_xml_attribute(xn_display_name, "displayName"));
4375 char *email = xn_email ? g_strdup(sipe_xml_attribute(xn_email, "email")) : NULL;
4376 char *phone_label = xn_phone_number ? g_strdup(sipe_xml_attribute(xn_phone_number, "label")) : NULL;
4377 char *phone_number = xn_phone_number ? g_strdup(sipe_xml_attribute(xn_phone_number, "number")) : NULL;
4378 char *tel_uri = sip_to_tel_uri(phone_number);
4380 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_DISPLAY_NAME, display_name);
4381 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_EMAIL, email);
4382 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_WORK_PHONE, tel_uri);
4383 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_WORK_PHONE_DISPLAY, !is_empty(phone_label) ? phone_label : phone_number);
4385 g_free(tel_uri);
4386 g_free(phone_label);
4387 g_free(phone_number);
4388 g_free(email);
4389 g_free(display_name);
4392 if (xn_contact) {
4393 /* tel */
4394 for (node = sipe_xml_child(xn_contact, "tel"); node; node = sipe_xml_twin(node))
4396 /* Ex.: <tel type="work">tel:+3222220000</tel> */
4397 const char *phone_type = sipe_xml_attribute(node, "type");
4398 char* phone = sipe_xml_data(node);
4400 sipe_update_user_phone(sipe_private, uri, phone_type, phone, NULL);
4402 g_free(phone);
4406 /* devicePresence */
4407 for (node = sipe_xml_child(xn_presentity, "devices/devicePresence"); node; node = sipe_xml_twin(node)) {
4408 const sipe_xml *xn_device_name;
4409 const sipe_xml *xn_calendar_info;
4410 const sipe_xml *xn_state;
4411 char *state;
4413 /* deviceName */
4414 if (sipe_strequal(sipe_xml_attribute(node, "epid"), epid)) {
4415 xn_device_name = sipe_xml_child(node, "deviceName");
4416 device_name = xn_device_name ? sipe_xml_attribute(xn_device_name, "name") : NULL;
4419 /* calendarInfo */
4420 xn_calendar_info = sipe_xml_child(node, "calendarInfo");
4421 if (xn_calendar_info) {
4422 const char *cal_start_time_tmp = sipe_xml_attribute(xn_calendar_info, "startTime");
4424 if (cal_start_time) {
4425 time_t cal_start_time_t = sipe_utils_str_to_time(cal_start_time);
4426 time_t cal_start_time_t_tmp = sipe_utils_str_to_time(cal_start_time_tmp);
4428 if (cal_start_time_t_tmp > cal_start_time_t) {
4429 cal_start_time = cal_start_time_tmp;
4430 cal_granularity = sipe_xml_attribute(xn_calendar_info, "granularity");
4431 g_free(cal_free_busy_base64);
4432 cal_free_busy_base64 = sipe_xml_data(xn_calendar_info);
4434 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);
4436 } else {
4437 cal_start_time = cal_start_time_tmp;
4438 cal_granularity = sipe_xml_attribute(xn_calendar_info, "granularity");
4439 g_free(cal_free_busy_base64);
4440 cal_free_busy_base64 = sipe_xml_data(xn_calendar_info);
4442 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);
4446 /* state */
4447 xn_state = sipe_xml_child(node, "states/state");
4448 if (xn_state) {
4449 int dev_avail = sipe_xml_int_attribute(xn_state, "avail", 0);
4450 time_t dev_avail_since = sipe_utils_str_to_time(sipe_xml_attribute(xn_state, "since"));
4452 state = sipe_xml_data(xn_state);
4453 if (dev_avail_since > user_avail_since &&
4454 dev_avail >= res_avail)
4456 res_avail = dev_avail;
4457 if (!is_empty(state))
4459 if (sipe_strequal(state, sipe_activity_map[SIPE_ACTIVITY_ON_PHONE].token)) {
4460 g_free(activity);
4461 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_ON_PHONE));
4462 } else if (sipe_strequal(state, "presenting")) {
4463 g_free(activity);
4464 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_IN_CONF));
4465 } else {
4466 activity = state;
4467 state = NULL;
4469 activity_since = dev_avail_since;
4471 status_id = sipe_get_status_by_availability(res_avail, &activity);
4473 g_free(state);
4477 /* oof */
4478 if (xn_oof && res_avail >= 15000) { /* 12000 in 2007 */
4479 g_free(activity);
4480 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_OOF));
4481 activity_since = 0;
4484 sbuddy = g_hash_table_lookup(sipe_private->buddies, uri);
4485 if (sbuddy)
4487 g_free(sbuddy->activity);
4488 sbuddy->activity = activity;
4489 activity = NULL;
4491 sbuddy->activity_since = activity_since;
4493 sbuddy->user_avail = user_avail;
4494 sbuddy->user_avail_since = user_avail_since;
4496 g_free(sbuddy->note);
4497 sbuddy->note = NULL;
4498 if (!is_empty(note)) { sbuddy->note = g_markup_escape_text(note, -1); }
4500 sbuddy->is_oof_note = (xn_oof != NULL);
4502 g_free(sbuddy->device_name);
4503 sbuddy->device_name = NULL;
4504 if (!is_empty(device_name)) { sbuddy->device_name = g_strdup(device_name); }
4506 if (!is_empty(cal_free_busy_base64)) {
4507 g_free(sbuddy->cal_start_time);
4508 sbuddy->cal_start_time = g_strdup(cal_start_time);
4510 sbuddy->cal_granularity = sipe_strcase_equal(cal_granularity, "PT15M") ? 15 : 0;
4512 g_free(sbuddy->cal_free_busy_base64);
4513 sbuddy->cal_free_busy_base64 = cal_free_busy_base64;
4514 cal_free_busy_base64 = NULL;
4516 g_free(sbuddy->cal_free_busy);
4517 sbuddy->cal_free_busy = NULL;
4520 sbuddy->last_non_cal_status_id = status_id;
4521 g_free(sbuddy->last_non_cal_activity);
4522 sbuddy->last_non_cal_activity = g_strdup(sbuddy->activity);
4524 if (sipe_strcase_equal(sbuddy->name, self_uri)) {
4525 if (!sipe_strequal(sbuddy->note, sip->note)) /* not same */
4527 sip->is_oof_note = sbuddy->is_oof_note;
4529 g_free(sip->note);
4530 sip->note = g_strdup(sbuddy->note);
4532 sip->note_since = time(NULL);
4535 g_free(sip->status);
4536 sip->status = g_strdup(sbuddy->last_non_cal_status_id);
4539 g_free(cal_free_busy_base64);
4540 g_free(activity);
4542 SIPE_DEBUG_INFO("process_incoming_notify_msrtc: status(%s)", status_id);
4543 sipe_core_buddy_got_status(SIPE_CORE_PUBLIC, uri, status_id);
4545 if (!SIPE_CORE_PRIVATE_FLAG_IS(OCS2007) && sipe_strcase_equal(self_uri, uri)) {
4546 sipe_user_info_has_updated(sipe_private, xn_userinfo);
4549 g_free(note);
4550 sipe_xml_free(xn_presentity);
4551 g_free(uri);
4552 g_free(self_uri);
4555 static void sipe_presence_mime_cb(gpointer user_data, /* sipe_core_private */
4556 const GSList *fields,
4557 const gchar *body,
4558 gsize length)
4560 const gchar *type = sipe_utils_nameval_find(fields, "Content-Type");
4562 if (strstr(type,"application/rlmi+xml")) {
4563 process_incoming_notify_rlmi_resub(user_data, body, length);
4564 } else if (strstr(type, "text/xml+msrtc.pidf")) {
4565 process_incoming_notify_msrtc(user_data, body, length);
4566 } else {
4567 process_incoming_notify_rlmi(user_data, body, length);
4571 static void sipe_process_presence(struct sipe_core_private *sipe_private,
4572 struct sipmsg *msg)
4574 const char *ctype = sipmsg_find_header(msg, "Content-Type");
4576 SIPE_DEBUG_INFO("sipe_process_presence: Content-Type: %s", ctype ? ctype : "");
4578 if (ctype &&
4579 (strstr(ctype, "application/rlmi+xml") ||
4580 strstr(ctype, "application/msrtc-event-categories+xml")))
4582 if (strstr(ctype, "multipart"))
4584 sipe_mime_parts_foreach(ctype, msg->body, sipe_presence_mime_cb, sipe_private);
4586 else if(strstr(ctype, "application/msrtc-event-categories+xml") )
4588 process_incoming_notify_rlmi(sipe_private, msg->body, msg->bodylen);
4590 else if(strstr(ctype, "application/rlmi+xml"))
4592 process_incoming_notify_rlmi_resub(sipe_private, msg->body, msg->bodylen);
4595 else if(ctype && strstr(ctype, "text/xml+msrtc.pidf"))
4597 process_incoming_notify_msrtc(sipe_private, msg->body, msg->bodylen);
4599 else
4601 process_incoming_notify_pidf(sipe_private, msg->body, msg->bodylen);
4605 static void sipe_presence_timeout_mime_cb(gpointer user_data,
4606 SIPE_UNUSED_PARAMETER const GSList *fields,
4607 const gchar *body,
4608 gsize length)
4610 GSList **buddies = user_data;
4611 sipe_xml *xml = sipe_xml_parse(body, length);
4613 if (xml && !sipe_strequal(sipe_xml_name(xml), "list")) {
4614 const gchar *uri = sipe_xml_attribute(xml, "uri");
4615 const sipe_xml *xn_category;
4618 * automaton: presence is never expected to change
4620 * see: http://msdn.microsoft.com/en-us/library/ee354295(office.13).aspx
4622 for (xn_category = sipe_xml_child(xml, "category");
4623 xn_category;
4624 xn_category = sipe_xml_twin(xn_category)) {
4625 if (sipe_strequal(sipe_xml_attribute(xn_category, "name"),
4626 "contactCard")) {
4627 const sipe_xml *node = sipe_xml_child(xn_category, "contactCard/automaton");
4628 if (node) {
4629 char *boolean = sipe_xml_data(node);
4630 if (sipe_strequal(boolean, "true")) {
4631 SIPE_DEBUG_INFO("sipe_process_presence_timeout: %s is an automaton: - not subscribing to presence updates",
4632 uri);
4633 uri = NULL;
4635 g_free(boolean);
4637 break;
4641 if (uri) {
4642 *buddies = g_slist_append(*buddies, sip_uri(uri));
4646 sipe_xml_free(xml);
4649 static void sipe_process_presence_timeout(struct sipe_core_private *sipe_private,
4650 struct sipmsg *msg, gchar *who,
4651 int timeout)
4653 const char *ctype = sipmsg_find_header(msg, "Content-Type");
4654 gchar *action_name = sipe_utils_presence_key(who);
4656 SIPE_DEBUG_INFO("sipe_process_presence_timeout: Content-Type: %s", ctype ? ctype : "");
4658 if (ctype &&
4659 strstr(ctype, "multipart") &&
4660 (strstr(ctype, "application/rlmi+xml") ||
4661 strstr(ctype, "application/msrtc-event-categories+xml"))) {
4662 GSList *buddies = NULL;
4664 sipe_mime_parts_foreach(ctype, msg->body, sipe_presence_timeout_mime_cb, &buddies);
4666 if (buddies) {
4667 struct presence_batched_routed *payload = g_malloc(sizeof(struct presence_batched_routed));
4668 payload->host = g_strdup(who);
4669 payload->buddies = buddies;
4670 sipe_schedule_seconds(sipe_private,
4671 action_name,
4672 payload,
4673 timeout,
4674 sipe_subscribe_presence_batched_routed,
4675 sipe_subscribe_presence_batched_routed_free);
4676 SIPE_DEBUG_INFO("Resubscription multiple contacts with batched support & route(%s) in %d", who, timeout);
4679 } else {
4680 sipe_schedule_seconds(sipe_private,
4681 action_name,
4682 g_strdup(who),
4683 timeout,
4684 sipe_subscribe_presence_single,
4685 g_free);
4686 SIPE_DEBUG_INFO("Resubscription single contact with batched support(%s) in %d", who, timeout);
4688 g_free(action_name);
4692 * Dispatcher for all incoming subscription information
4693 * whether it comes from NOTIFY, BENOTIFY requests or
4694 * piggy-backed to subscription's OK responce.
4696 * @param request whether initiated from BE/NOTIFY request or OK-response message.
4697 * @param benotify whether initiated from NOTIFY or BENOTIFY request.
4699 void process_incoming_notify(struct sipe_core_private *sipe_private,
4700 struct sipmsg *msg,
4701 gboolean request, gboolean benotify)
4703 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
4704 const gchar *content_type = sipmsg_find_header(msg, "Content-Type");
4705 const gchar *event = sipmsg_find_header(msg, "Event");
4706 const gchar *subscription_state = sipmsg_find_header(msg, "subscription-state");
4708 SIPE_DEBUG_INFO("process_incoming_notify: subscription_state: %s", subscription_state ? subscription_state : "");
4710 /* implicit subscriptions */
4711 if (content_type && g_str_has_prefix(content_type, "application/ms-imdn+xml")) {
4712 sipe_process_imdn(sipe_private, msg);
4715 if (event) {
4716 /* for one off subscriptions (send with Expire: 0) */
4717 if (sipe_strcase_equal(event, "vnd-microsoft-provisioning-v2"))
4719 sipe_process_provisioning_v2(sipe_private, msg);
4721 else if (sipe_strcase_equal(event, "vnd-microsoft-provisioning"))
4723 sipe_process_provisioning(sipe_private, msg);
4725 else if (sipe_strcase_equal(event, "presence"))
4727 sipe_process_presence(sipe_private, msg);
4729 else if (sipe_strcase_equal(event, "registration-notify"))
4731 sipe_process_registration_notify(sipe_private, msg);
4734 if (!subscription_state || strstr(subscription_state, "active"))
4736 if (sipe_strcase_equal(event, "vnd-microsoft-roaming-contacts"))
4738 sipe_process_roaming_contacts(sipe_private, msg);
4740 else if (sipe_strcase_equal(event, "vnd-microsoft-roaming-self"))
4742 sipe_process_roaming_self(sipe_private, msg);
4744 else if (sipe_strcase_equal(event, "vnd-microsoft-roaming-ACL"))
4746 sipe_process_roaming_acl(sipe_private, msg);
4748 else if (sipe_strcase_equal(event, "presence.wpending"))
4750 sipe_process_presence_wpending(sipe_private, msg);
4752 else if (sipe_strcase_equal(event, "conference"))
4754 sipe_process_conference(sipe_private, msg);
4759 /* The server sends status 'terminated' */
4760 if (subscription_state && strstr(subscription_state, "terminated") ) {
4761 gchar *who = parse_from(sipmsg_find_header(msg, request ? "From" : "To"));
4762 gchar *key = sipe_utils_subscription_key(event, who);
4764 SIPE_DEBUG_INFO("process_incoming_notify: server says that subscription to %s was terminated.", who);
4765 g_free(who);
4767 sipe_subscriptions_remove(sipe_private, key);
4768 g_free(key);
4771 if (!request && event) {
4772 const gchar *expires_header = sipmsg_find_header(msg, "Expires");
4773 int timeout = expires_header ? strtol(expires_header, NULL, 10) : 0;
4774 SIPE_DEBUG_INFO("process_incoming_notify: subscription expires:%d", timeout);
4776 if (timeout) {
4777 /* 2 min ahead of expiration */
4778 timeout = (timeout - 120) > 120 ? (timeout - 120) : timeout;
4780 if (sipe_strcase_equal(event, "presence.wpending") &&
4781 g_slist_find_custom(sip->allow_events, "presence.wpending", (GCompareFunc)g_ascii_strcasecmp))
4783 gchar *action_name = g_strdup_printf("<%s>", "presence.wpending");
4784 sipe_schedule_seconds(sipe_private,
4785 action_name,
4786 NULL,
4787 timeout,
4788 sipe_subscribe_presence_wpending,
4789 NULL);
4790 g_free(action_name);
4792 else if (sipe_strcase_equal(event, "presence") &&
4793 g_slist_find_custom(sip->allow_events, "presence", (GCompareFunc)g_ascii_strcasecmp))
4795 gchar *who = parse_from(sipmsg_find_header(msg, "To"));
4796 gchar *action_name = sipe_utils_presence_key(who);
4798 if (sip->batched_support) {
4799 sipe_process_presence_timeout(sipe_private, msg, who, timeout);
4801 else {
4802 sipe_schedule_seconds(sipe_private,
4803 action_name,
4804 g_strdup(who),
4805 timeout,
4806 sipe_subscribe_presence_single,
4807 g_free);
4808 SIPE_DEBUG_INFO("Resubscription single contact (%s) in %d", who, timeout);
4810 g_free(action_name);
4811 g_free(who);
4816 /* The client responses on received a NOTIFY message */
4817 if (request && !benotify)
4819 sip_transport_response(sipe_private, msg, 200, "OK", NULL);
4824 * Whether user manually changed status or
4825 * it was changed automatically due to user
4826 * became inactive/active again
4828 static gboolean
4829 sipe_is_user_state(struct sipe_core_private *sipe_private)
4831 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
4832 gboolean res;
4833 time_t now = time(NULL);
4835 SIPE_DEBUG_INFO("sipe_is_user_state: sip->idle_switch : %s", asctime(localtime(&(sip->idle_switch))));
4836 SIPE_DEBUG_INFO("sipe_is_user_state: now : %s", asctime(localtime(&now)));
4838 res = ((now - SIPE_IDLE_SET_DELAY * 2) >= sip->idle_switch);
4840 SIPE_DEBUG_INFO("sipe_is_user_state: res = %s", res ? "USER" : "MACHINE");
4841 return res;
4844 static void
4845 send_presence_soap0(struct sipe_core_private *sipe_private,
4846 gboolean do_publish_calendar,
4847 gboolean do_reset_status)
4849 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
4850 struct sipe_calendar* cal = sip->cal;
4851 int availability = 0;
4852 int activity = 0;
4853 gchar *body;
4854 gchar *tmp;
4855 gchar *tmp2 = NULL;
4856 gchar *res_note = NULL;
4857 gchar *res_oof = NULL;
4858 const gchar *note_pub = NULL;
4859 gchar *states = NULL;
4860 gchar *calendar_data = NULL;
4861 gchar *epid = get_epid(sipe_private);
4862 time_t now = time(NULL);
4863 gchar *since_time_str = sipe_utils_time_to_str(now);
4864 const gchar *oof_note = cal ? sipe_ews_get_oof_note(cal) : NULL;
4865 const char *user_input;
4866 gboolean pub_oof = cal && oof_note && (!sip->note || cal->updated > sip->note_since);
4868 if (oof_note && sip->note) {
4869 SIPE_DEBUG_INFO("cal->oof_start : %s", asctime(localtime(&(cal->oof_start))));
4870 SIPE_DEBUG_INFO("sip->note_since : %s", asctime(localtime(&(sip->note_since))));
4873 SIPE_DEBUG_INFO("sip->note : %s", sip->note ? sip->note : "");
4875 if (!sip->initial_state_published ||
4876 do_reset_status)
4878 g_free(sip->status);
4879 sip->status = g_strdup(SIPE_STATUS_ID_AVAILABLE);
4882 sipe_get_act_avail_by_status_2005(sip->status, &activity, &availability);
4884 /* Note */
4885 if (pub_oof) {
4886 note_pub = oof_note;
4887 res_oof = SIPE_SOAP_SET_PRESENCE_OOF_XML;
4888 cal->published = TRUE;
4889 } else if (sip->note) {
4890 if (sip->is_oof_note && !oof_note) { /* stale OOF note, as it's not present in cal already */
4891 g_free(sip->note);
4892 sip->note = NULL;
4893 sip->is_oof_note = FALSE;
4894 sip->note_since = 0;
4895 } else {
4896 note_pub = sip->note;
4897 res_oof = sip->is_oof_note ? SIPE_SOAP_SET_PRESENCE_OOF_XML : "";
4901 if (note_pub)
4903 /* to protocol internal plain text format */
4904 tmp = sipe_backend_markup_strip_html(note_pub);
4905 res_note = g_markup_printf_escaped(SIPE_SOAP_SET_PRESENCE_NOTE_XML, tmp);
4906 g_free(tmp);
4909 /* User State */
4910 if (!do_reset_status) {
4911 if (sipe_is_user_state(sipe_private) && !do_publish_calendar && sip->initial_state_published)
4913 gchar *activity_token = NULL;
4914 int avail_2007 = sipe_get_availability_by_status(sip->status, &activity_token);
4916 states = g_strdup_printf(SIPE_SOAP_SET_PRESENCE_STATES,
4917 avail_2007,
4918 since_time_str,
4919 epid,
4920 activity_token);
4921 g_free(activity_token);
4923 else /* preserve existing publication */
4925 if (sip->user_states) {
4926 states = g_strdup(sip->user_states);
4929 } else {
4930 /* do nothing - then User state will be erased */
4932 sip->initial_state_published = TRUE;
4934 /* CalendarInfo */
4935 if (cal && (!is_empty(cal->legacy_dn) || !is_empty(cal->email)) && cal->fb_start && !is_empty(cal->free_busy))
4937 char *fb_start_str = sipe_utils_time_to_str(cal->fb_start);
4938 char *free_busy_base64 = sipe_cal_get_freebusy_base64(cal->free_busy);
4939 calendar_data = g_strdup_printf(SIPE_SOAP_SET_PRESENCE_CALENDAR,
4940 !is_empty(cal->legacy_dn) ? cal->legacy_dn : cal->email,
4941 fb_start_str,
4942 free_busy_base64);
4943 g_free(fb_start_str);
4944 g_free(free_busy_base64);
4947 user_input = (sipe_is_user_state(sipe_private) ||
4948 sipe_strequal(sip->status, SIPE_STATUS_ID_AVAILABLE)) ?
4949 "active" : "idle";
4951 /* forming resulting XML */
4952 body = g_strdup_printf(SIPE_SOAP_SET_PRESENCE,
4953 sipe_private->username,
4954 availability,
4955 activity,
4956 (tmp = g_ascii_strup(g_get_host_name(), -1)),
4957 res_note ? res_note : "",
4958 res_oof ? res_oof : "",
4959 states ? states : "",
4960 calendar_data ? calendar_data : "",
4961 epid,
4962 since_time_str,
4963 since_time_str,
4964 user_input);
4965 g_free(tmp);
4966 g_free(tmp2);
4967 g_free(res_note);
4968 g_free(states);
4969 g_free(calendar_data);
4971 send_soap_request(sipe_private, body);
4973 g_free(body);
4974 g_free(since_time_str);
4975 g_free(epid);
4978 void
4979 send_presence_soap(struct sipe_core_private *sipe_private,
4980 gboolean do_publish_calendar)
4982 return send_presence_soap0(sipe_private, do_publish_calendar, FALSE);
4986 static gboolean
4987 process_send_presence_category_publish_response(struct sipe_core_private *sipe_private,
4988 struct sipmsg *msg,
4989 struct transaction *trans)
4991 const gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
4993 if (msg->response == 409 && g_str_has_prefix(contenttype, "application/msrtc-fault+xml")) {
4994 sipe_xml *xml;
4995 const sipe_xml *node;
4996 gchar *fault_code;
4997 GHashTable *faults;
4998 int index_our;
4999 gboolean has_device_publication = FALSE;
5001 xml = sipe_xml_parse(msg->body, msg->bodylen);
5003 /* test if version mismatch fault */
5004 fault_code = sipe_xml_data(sipe_xml_child(xml, "Faultcode"));
5005 if (!sipe_strequal(fault_code, "Client.BadCall.WrongDelta")) {
5006 SIPE_DEBUG_INFO("process_send_presence_category_publish_response: unsupported fault code:%s returning.", fault_code);
5007 g_free(fault_code);
5008 sipe_xml_free(xml);
5009 return TRUE;
5011 g_free(fault_code);
5013 /* accumulating information about faulty versions */
5014 faults = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
5015 for (node = sipe_xml_child(xml, "details/operation");
5016 node;
5017 node = sipe_xml_twin(node))
5019 const gchar *index = sipe_xml_attribute(node, "index");
5020 const gchar *curVersion = sipe_xml_attribute(node, "curVersion");
5022 g_hash_table_insert(faults, g_strdup(index), g_strdup(curVersion));
5023 SIPE_DEBUG_INFO("fault added: index:%s curVersion:%s", index, curVersion);
5025 sipe_xml_free(xml);
5027 /* here we are parsing our own request to figure out what publication
5028 * referenced here only by index went wrong
5030 xml = sipe_xml_parse(trans->msg->body, trans->msg->bodylen);
5032 /* publication */
5033 for (node = sipe_xml_child(xml, "publications/publication"),
5034 index_our = 1; /* starts with 1 - our first publication */
5035 node;
5036 node = sipe_xml_twin(node), index_our++)
5038 gchar *idx = g_strdup_printf("%d", index_our);
5039 const gchar *curVersion = g_hash_table_lookup(faults, idx);
5040 const gchar *categoryName = sipe_xml_attribute(node, "categoryName");
5041 g_free(idx);
5043 if (sipe_strequal("device", categoryName)) {
5044 has_device_publication = TRUE;
5047 if (curVersion) { /* fault exist on this index */
5048 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5049 const gchar *container = sipe_xml_attribute(node, "container");
5050 const gchar *instance = sipe_xml_attribute(node, "instance");
5051 /* key is <category><instance><container> */
5052 gchar *key = g_strdup_printf("<%s><%s><%s>", categoryName, instance, container);
5053 GHashTable *category = g_hash_table_lookup(sip->our_publications, categoryName);
5055 if (category) {
5056 struct sipe_publication *publication =
5057 g_hash_table_lookup(category, key);
5059 SIPE_DEBUG_INFO("key is %s", key);
5061 if (publication) {
5062 SIPE_DEBUG_INFO("Updating %s with version %s. Was %d before.",
5063 key, curVersion, publication->version);
5064 /* updating publication's version to the correct one */
5065 publication->version = atoi(curVersion);
5067 } else {
5068 /* We somehow lost this category from our publications... */
5069 struct sipe_publication *publication = g_new0(struct sipe_publication, 1);
5070 publication->category = g_strdup(categoryName);
5071 publication->instance = atoi(instance);
5072 publication->container = atoi(container);
5073 publication->version = atoi(curVersion);
5074 category = g_hash_table_new_full(g_str_hash, g_str_equal,
5075 g_free, (GDestroyNotify)free_publication);
5076 g_hash_table_insert(category, g_strdup(key), publication);
5077 g_hash_table_insert(sip->our_publications, g_strdup(categoryName), category);
5078 SIPE_DEBUG_INFO("added lost category '%s' key '%s'", categoryName, key);
5080 g_free(key);
5083 sipe_xml_free(xml);
5084 g_hash_table_destroy(faults);
5086 /* rebublishing with right versions */
5087 if (has_device_publication) {
5088 send_publish_category_initial(sipe_private);
5089 } else {
5090 send_presence_status(sipe_private, NULL);
5093 return TRUE;
5097 * Returns 'device' XML part for publication.
5098 * Must be g_free'd after use.
5100 static gchar *
5101 sipe_publish_get_category_device(struct sipe_core_private *sipe_private)
5103 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5104 gchar *uri;
5105 gchar *doc;
5106 gchar *epid = get_epid(sipe_private);
5107 gchar *uuid = generateUUIDfromEPID(epid);
5108 guint device_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_DEVICE);
5109 /* key is <category><instance><container> */
5110 gchar *key = g_strdup_printf("<%s><%u><%u>", "device", device_instance, 2);
5111 struct sipe_publication *publication =
5112 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "device"), key);
5114 g_free(key);
5115 g_free(epid);
5117 uri = sip_uri_self(sipe_private);
5118 doc = g_strdup_printf(SIPE_PUB_XML_DEVICE,
5119 device_instance,
5120 publication ? publication->version : 0,
5121 uuid,
5122 uri,
5123 "00:00:00+01:00", /* @TODO make timezone real*/
5124 g_get_host_name()
5127 g_free(uri);
5128 g_free(uuid);
5130 return doc;
5134 * A service method - use
5135 * - send_publish_get_category_state_machine and
5136 * - send_publish_get_category_state_user instead.
5137 * Must be g_free'd after use.
5139 static gchar *
5140 sipe_publish_get_category_state(struct sipe_core_private *sipe_private,
5141 gboolean is_user_state)
5143 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5144 int availability = sipe_get_availability_by_status(sip->status, NULL);
5145 guint instance = is_user_state ? sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_USER) :
5146 sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_MACHINE);
5147 /* key is <category><instance><container> */
5148 gchar *key_2 = g_strdup_printf("<%s><%u><%u>", "state", instance, 2);
5149 gchar *key_3 = g_strdup_printf("<%s><%u><%u>", "state", instance, 3);
5150 struct sipe_publication *publication_2 =
5151 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_2);
5152 struct sipe_publication *publication_3 =
5153 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_3);
5155 g_free(key_2);
5156 g_free(key_3);
5158 if (publication_2 && (publication_2->availability == availability))
5160 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_state: state has NOT changed. Exiting.");
5161 return NULL; /* nothing to update */
5164 return g_strdup_printf( is_user_state ? SIPE_PUB_XML_STATE_USER : SIPE_PUB_XML_STATE_MACHINE,
5165 instance,
5166 publication_2 ? publication_2->version : 0,
5167 availability,
5168 instance,
5169 publication_3 ? publication_3->version : 0,
5170 availability);
5174 * Only Busy and OOF calendar event are published.
5175 * Different instances are used for that.
5177 * Must be g_free'd after use.
5179 static gchar *
5180 sipe_publish_get_category_state_calendar(struct sipe_core_private *sipe_private,
5181 struct sipe_cal_event *event,
5182 const char *uri,
5183 int cal_satus)
5185 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5186 gchar *start_time_str;
5187 int availability = 0;
5188 gchar *res;
5189 gchar *tmp = NULL;
5190 guint instance = (cal_satus == SIPE_CAL_OOF) ?
5191 sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_CALENDAR_OOF) :
5192 sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_CALENDAR);
5194 /* key is <category><instance><container> */
5195 gchar *key_2 = g_strdup_printf("<%s><%u><%u>", "state", instance, 2);
5196 gchar *key_3 = g_strdup_printf("<%s><%u><%u>", "state", instance, 3);
5197 struct sipe_publication *publication_2 =
5198 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_2);
5199 struct sipe_publication *publication_3 =
5200 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_3);
5202 g_free(key_2);
5203 g_free(key_3);
5205 if (!publication_3 && !event) { /* was nothing, have nothing, exiting */
5206 SIPE_DEBUG_INFO("sipe_publish_get_category_state_calendar: "
5207 "Exiting as no publication and no event for cal_satus:%d", cal_satus);
5208 return NULL;
5211 if (event &&
5212 publication_3 &&
5213 (publication_3->availability == availability) &&
5214 sipe_strequal(publication_3->cal_event_hash, (tmp = sipe_cal_event_hash(event))))
5216 g_free(tmp);
5217 SIPE_DEBUG_INFO("sipe_publish_get_category_state_calendar: "
5218 "cal state has NOT changed for cal_satus:%d. Exiting.", cal_satus);
5219 return NULL; /* nothing to update */
5221 g_free(tmp);
5223 if (event &&
5224 (event->cal_status == SIPE_CAL_BUSY ||
5225 event->cal_status == SIPE_CAL_OOF))
5227 gchar *availability_xml_str = NULL;
5228 gchar *activity_xml_str = NULL;
5229 gchar *escaped_subject = event->subject ? g_markup_escape_text(event->subject, -1) : NULL;
5230 gchar *escaped_location = event->location ? g_markup_escape_text(event->location, -1) : NULL;
5232 if (event->cal_status == SIPE_CAL_BUSY) {
5233 availability_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_AVAIL, 6500);
5236 if (event->cal_status == SIPE_CAL_BUSY && event->is_meeting) {
5237 activity_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_ACTIVITY,
5238 sipe_activity_map[SIPE_ACTIVITY_IN_MEETING].token,
5239 "minAvailability=\"6500\"",
5240 "maxAvailability=\"8999\"");
5241 } else if (event->cal_status == SIPE_CAL_OOF) {
5242 activity_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_ACTIVITY,
5243 sipe_activity_map[SIPE_ACTIVITY_OOF].token,
5244 "minAvailability=\"12000\"",
5245 "");
5247 start_time_str = sipe_utils_time_to_str(event->start_time);
5249 res = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR,
5250 instance,
5251 publication_2 ? publication_2->version : 0,
5252 uri,
5253 start_time_str,
5254 availability_xml_str ? availability_xml_str : "",
5255 activity_xml_str ? activity_xml_str : "",
5256 escaped_subject ? escaped_subject : "",
5257 escaped_location ? escaped_location : "",
5259 instance,
5260 publication_3 ? publication_3->version : 0,
5261 uri,
5262 start_time_str,
5263 availability_xml_str ? availability_xml_str : "",
5264 activity_xml_str ? activity_xml_str : "",
5265 escaped_subject ? escaped_subject : "",
5266 escaped_location ? escaped_location : ""
5268 g_free(escaped_location);
5269 g_free(escaped_subject);
5270 g_free(start_time_str);
5271 g_free(availability_xml_str);
5272 g_free(activity_xml_str);
5275 else /* including !event, SIPE_CAL_FREE, SIPE_CAL_TENTATIVE */
5277 res = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_CLEAR,
5278 instance,
5279 publication_2 ? publication_2->version : 0,
5281 instance,
5282 publication_3 ? publication_3->version : 0
5286 return res;
5290 * Returns 'machineState' XML part for publication.
5291 * Must be g_free'd after use.
5293 static gchar *
5294 sipe_publish_get_category_state_machine(struct sipe_core_private *sipe_private)
5296 return sipe_publish_get_category_state(sipe_private, FALSE);
5300 * Returns 'userState' XML part for publication.
5301 * Must be g_free'd after use.
5303 static gchar *
5304 sipe_publish_get_category_state_user(struct sipe_core_private *sipe_private)
5306 return sipe_publish_get_category_state(sipe_private, TRUE);
5310 * Returns 'note' XML part for publication.
5311 * Must be g_free'd after use.
5313 * Protocol format for Note is plain text.
5315 * @param note a note in Sipe internal HTML format
5316 * @param note_type either personal or OOF
5318 static gchar *
5319 sipe_publish_get_category_note(struct sipe_core_private *sipe_private,
5320 const char *note, /* html */
5321 const char *note_type,
5322 time_t note_start,
5323 time_t note_end)
5325 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5326 guint instance = sipe_strequal("OOF", note_type) ? sipe_get_pub_instance(sipe_private, SIPE_PUB_NOTE_OOF) : 0;
5327 /* key is <category><instance><container> */
5328 gchar *key_note_200 = g_strdup_printf("<%s><%u><%u>", "note", instance, 200);
5329 gchar *key_note_300 = g_strdup_printf("<%s><%u><%u>", "note", instance, 300);
5330 gchar *key_note_400 = g_strdup_printf("<%s><%u><%u>", "note", instance, 400);
5332 struct sipe_publication *publication_note_200 =
5333 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_200);
5334 struct sipe_publication *publication_note_300 =
5335 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_300);
5336 struct sipe_publication *publication_note_400 =
5337 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_400);
5339 char *tmp = note ? sipe_backend_markup_strip_html(note) : NULL;
5340 char *n1 = tmp ? g_markup_escape_text(tmp, -1) : NULL;
5341 const char *n2 = publication_note_200 ? publication_note_200->note : NULL;
5342 char *res, *tmp1, *tmp2, *tmp3;
5343 char *start_time_attr;
5344 char *end_time_attr;
5346 g_free(tmp);
5347 tmp = NULL;
5348 g_free(key_note_200);
5349 g_free(key_note_300);
5350 g_free(key_note_400);
5352 /* we even need to republish empty note */
5353 if (sipe_strequal(n1, n2))
5355 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_note: note has NOT changed. Exiting.");
5356 g_free(n1);
5357 return NULL; /* nothing to update */
5360 start_time_attr = note_start ? g_strdup_printf(" startTime=\"%s\"", (tmp = sipe_utils_time_to_str(note_start))) : NULL;
5361 g_free(tmp);
5362 tmp = NULL;
5363 end_time_attr = note_end ? g_strdup_printf(" endTime=\"%s\"", (tmp = sipe_utils_time_to_str(note_end))) : NULL;
5364 g_free(tmp);
5366 if (n1) {
5367 tmp1 = g_strdup_printf(SIPE_PUB_XML_NOTE,
5368 instance,
5369 200,
5370 publication_note_200 ? publication_note_200->version : 0,
5371 note_type,
5372 start_time_attr ? start_time_attr : "",
5373 end_time_attr ? end_time_attr : "",
5374 n1);
5376 tmp2 = g_strdup_printf(SIPE_PUB_XML_NOTE,
5377 instance,
5378 300,
5379 publication_note_300 ? publication_note_300->version : 0,
5380 note_type,
5381 start_time_attr ? start_time_attr : "",
5382 end_time_attr ? end_time_attr : "",
5383 n1);
5385 tmp3 = g_strdup_printf(SIPE_PUB_XML_NOTE,
5386 instance,
5387 400,
5388 publication_note_400 ? publication_note_400->version : 0,
5389 note_type,
5390 start_time_attr ? start_time_attr : "",
5391 end_time_attr ? end_time_attr : "",
5392 n1);
5393 } else {
5394 tmp1 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
5395 "note",
5396 instance,
5397 200,
5398 publication_note_200 ? publication_note_200->version : 0,
5399 "static");
5400 tmp2 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
5401 "note",
5402 instance,
5403 300,
5404 publication_note_200 ? publication_note_200->version : 0,
5405 "static");
5406 tmp3 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
5407 "note",
5408 instance,
5409 400,
5410 publication_note_200 ? publication_note_200->version : 0,
5411 "static");
5413 res = g_strconcat(tmp1, tmp2, tmp3, NULL);
5415 g_free(start_time_attr);
5416 g_free(end_time_attr);
5417 g_free(tmp1);
5418 g_free(tmp2);
5419 g_free(tmp3);
5420 g_free(n1);
5422 return res;
5426 * Returns 'calendarData' XML part with WorkingHours for publication.
5427 * Must be g_free'd after use.
5429 static gchar *
5430 sipe_publish_get_category_cal_working_hours(struct sipe_core_private *sipe_private)
5432 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5433 struct sipe_calendar* cal = sip->cal;
5435 /* key is <category><instance><container> */
5436 gchar *key_cal_1 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 1);
5437 gchar *key_cal_100 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 100);
5438 gchar *key_cal_200 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 200);
5439 gchar *key_cal_300 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 300);
5440 gchar *key_cal_400 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 400);
5441 gchar *key_cal_32000 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 32000);
5443 struct sipe_publication *publication_cal_1 =
5444 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_1);
5445 struct sipe_publication *publication_cal_100 =
5446 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_100);
5447 struct sipe_publication *publication_cal_200 =
5448 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_200);
5449 struct sipe_publication *publication_cal_300 =
5450 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_300);
5451 struct sipe_publication *publication_cal_400 =
5452 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_400);
5453 struct sipe_publication *publication_cal_32000 =
5454 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_32000);
5456 const char *n1 = cal ? cal->working_hours_xml_str : NULL;
5457 const char *n2 = publication_cal_300 ? publication_cal_300->working_hours_xml_str : NULL;
5459 g_free(key_cal_1);
5460 g_free(key_cal_100);
5461 g_free(key_cal_200);
5462 g_free(key_cal_300);
5463 g_free(key_cal_400);
5464 g_free(key_cal_32000);
5466 if (!cal || is_empty(cal->email) || is_empty(cal->working_hours_xml_str)) {
5467 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_cal_working_hours: no data to publish, exiting");
5468 return NULL;
5471 if (sipe_strequal(n1, n2))
5473 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_cal_working_hours: WorkingHours has NOT changed. Exiting.");
5474 return NULL; /* nothing to update */
5477 return g_strdup_printf(SIPE_PUB_XML_WORKING_HOURS,
5478 /* 1 */
5479 publication_cal_1 ? publication_cal_1->version : 0,
5480 cal->email,
5481 cal->working_hours_xml_str,
5482 /* 100 - Public */
5483 publication_cal_100 ? publication_cal_100->version : 0,
5484 /* 200 - Company */
5485 publication_cal_200 ? publication_cal_200->version : 0,
5486 cal->email,
5487 cal->working_hours_xml_str,
5488 /* 300 - Team */
5489 publication_cal_300 ? publication_cal_300->version : 0,
5490 cal->email,
5491 cal->working_hours_xml_str,
5492 /* 400 - Personal */
5493 publication_cal_400 ? publication_cal_400->version : 0,
5494 cal->email,
5495 cal->working_hours_xml_str,
5496 /* 32000 - Blocked */
5497 publication_cal_32000 ? publication_cal_32000->version : 0
5502 * Returns 'calendarData' XML part with FreeBusy for publication.
5503 * Must be g_free'd after use.
5505 static gchar *
5506 sipe_publish_get_category_cal_free_busy(struct sipe_core_private *sipe_private)
5508 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5509 struct sipe_calendar* cal = sip->cal;
5510 guint cal_data_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_CALENDAR_DATA);
5511 char *fb_start_str;
5512 char *free_busy_base64;
5513 const char *st;
5514 const char *fb;
5515 char *res;
5517 /* key is <category><instance><container> */
5518 gchar *key_cal_1 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 1);
5519 gchar *key_cal_100 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 100);
5520 gchar *key_cal_200 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 200);
5521 gchar *key_cal_300 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 300);
5522 gchar *key_cal_400 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 400);
5523 gchar *key_cal_32000 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 32000);
5525 struct sipe_publication *publication_cal_1 =
5526 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_1);
5527 struct sipe_publication *publication_cal_100 =
5528 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_100);
5529 struct sipe_publication *publication_cal_200 =
5530 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_200);
5531 struct sipe_publication *publication_cal_300 =
5532 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_300);
5533 struct sipe_publication *publication_cal_400 =
5534 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_400);
5535 struct sipe_publication *publication_cal_32000 =
5536 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_32000);
5538 g_free(key_cal_1);
5539 g_free(key_cal_100);
5540 g_free(key_cal_200);
5541 g_free(key_cal_300);
5542 g_free(key_cal_400);
5543 g_free(key_cal_32000);
5545 if (!cal || is_empty(cal->email) || !cal->fb_start || is_empty(cal->free_busy)) {
5546 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_cal_free_busy: no data to publish, exiting");
5547 return NULL;
5550 fb_start_str = sipe_utils_time_to_str(cal->fb_start);
5551 free_busy_base64 = sipe_cal_get_freebusy_base64(cal->free_busy);
5553 st = publication_cal_300 ? publication_cal_300->fb_start_str : NULL;
5554 fb = publication_cal_300 ? publication_cal_300->free_busy_base64 : NULL;
5556 /* we will rebuplish the same data to refresh publication time,
5557 * so if data from multiple sources, most recent will be choosen
5559 //if (sipe_strequal(st, fb_start_str) && sipe_strequal(fb, free_busy_base64))
5561 // SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_cal_free_busy: FreeBusy has NOT changed. Exiting.");
5562 // g_free(fb_start_str);
5563 // g_free(free_busy_base64);
5564 // return NULL; /* nothing to update */
5567 res = g_strdup_printf(SIPE_PUB_XML_FREE_BUSY,
5568 /* 1 */
5569 cal_data_instance,
5570 publication_cal_1 ? publication_cal_1->version : 0,
5571 /* 100 - Public */
5572 cal_data_instance,
5573 publication_cal_100 ? publication_cal_100->version : 0,
5574 /* 200 - Company */
5575 cal_data_instance,
5576 publication_cal_200 ? publication_cal_200->version : 0,
5577 cal->email,
5578 fb_start_str,
5579 free_busy_base64,
5580 /* 300 - Team */
5581 cal_data_instance,
5582 publication_cal_300 ? publication_cal_300->version : 0,
5583 cal->email,
5584 fb_start_str,
5585 free_busy_base64,
5586 /* 400 - Personal */
5587 cal_data_instance,
5588 publication_cal_400 ? publication_cal_400->version : 0,
5589 cal->email,
5590 fb_start_str,
5591 free_busy_base64,
5592 /* 32000 - Blocked */
5593 cal_data_instance,
5594 publication_cal_32000 ? publication_cal_32000->version : 0
5597 g_free(fb_start_str);
5598 g_free(free_busy_base64);
5599 return res;
5602 static void send_presence_publish(struct sipe_core_private *sipe_private,
5603 const char *publications)
5605 gchar *uri;
5606 gchar *doc;
5607 gchar *tmp;
5608 gchar *hdr;
5610 uri = sip_uri_self(sipe_private);
5611 doc = g_strdup_printf(SIPE_SEND_PRESENCE,
5612 uri,
5613 publications);
5615 tmp = get_contact(sipe_private);
5616 hdr = g_strdup_printf("Contact: %s\r\n"
5617 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp);
5619 sip_transport_service(sipe_private,
5620 uri,
5621 hdr,
5622 doc,
5623 process_send_presence_category_publish_response);
5625 g_free(tmp);
5626 g_free(hdr);
5627 g_free(uri);
5628 g_free(doc);
5631 static void
5632 send_publish_category_initial(struct sipe_core_private *sipe_private)
5634 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5635 gchar *pub_device = sipe_publish_get_category_device(sipe_private);
5636 gchar *pub_machine;
5637 gchar *publications;
5639 g_free(sip->status);
5640 sip->status = g_strdup(SIPE_STATUS_ID_AVAILABLE); /* our initial state */
5642 pub_machine = sipe_publish_get_category_state_machine(sipe_private);
5643 publications = g_strdup_printf("%s%s",
5644 pub_device,
5645 pub_machine ? pub_machine : "");
5646 g_free(pub_device);
5647 g_free(pub_machine);
5649 send_presence_publish(sipe_private, publications);
5650 g_free(publications);
5653 static void
5654 send_presence_category_publish(struct sipe_core_private *sipe_private)
5656 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5657 gchar *pub_state = sipe_is_user_state(sipe_private) ?
5658 sipe_publish_get_category_state_user(sipe_private) :
5659 sipe_publish_get_category_state_machine(sipe_private);
5660 gchar *pub_note = sipe_publish_get_category_note(sipe_private,
5661 sip->note,
5662 sip->is_oof_note ? "OOF" : "personal",
5665 gchar *publications;
5667 if (!pub_state && !pub_note) {
5668 SIPE_DEBUG_INFO_NOFORMAT("send_presence_category_publish: nothing has changed. Exiting.");
5669 return;
5672 publications = g_strdup_printf("%s%s",
5673 pub_state ? pub_state : "",
5674 pub_note ? pub_note : "");
5676 g_free(pub_state);
5677 g_free(pub_note);
5679 send_presence_publish(sipe_private, publications);
5680 g_free(publications);
5684 * Publishes self status
5685 * based on own calendar information.
5687 * For 2007+
5689 void
5690 publish_calendar_status_self(struct sipe_core_private *sipe_private,
5691 SIPE_UNUSED_PARAMETER void *unused)
5693 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5694 struct sipe_cal_event* event = NULL;
5695 gchar *pub_cal_working_hours = NULL;
5696 gchar *pub_cal_free_busy = NULL;
5697 gchar *pub_calendar = NULL;
5698 gchar *pub_calendar2 = NULL;
5699 gchar *pub_oof_note = NULL;
5700 const gchar *oof_note;
5701 time_t oof_start = 0;
5702 time_t oof_end = 0;
5704 if (!sip->cal) {
5705 SIPE_DEBUG_INFO_NOFORMAT("publish_calendar_status_self() no calendar data.");
5706 return;
5709 SIPE_DEBUG_INFO_NOFORMAT("publish_calendar_status_self() started.");
5710 if (sip->cal->cal_events) {
5711 event = sipe_cal_get_event(sip->cal->cal_events, time(NULL));
5714 if (!event) {
5715 SIPE_DEBUG_INFO_NOFORMAT("publish_calendar_status_self: current event is NULL");
5716 } else {
5717 char *desc = sipe_cal_event_describe(event);
5718 SIPE_DEBUG_INFO("publish_calendar_status_self: current event is:\n%s", desc ? desc : "");
5719 g_free(desc);
5722 /* Logic
5723 if OOF
5724 OOF publish, Busy clean
5725 ilse if Busy
5726 OOF clean, Busy publish
5727 else
5728 OOF clean, Busy clean
5730 if (event && event->cal_status == SIPE_CAL_OOF) {
5731 pub_calendar = sipe_publish_get_category_state_calendar(sipe_private, event, sip->cal->email, SIPE_CAL_OOF);
5732 pub_calendar2 = sipe_publish_get_category_state_calendar(sipe_private, NULL, sip->cal->email, SIPE_CAL_BUSY);
5733 } else if (event && event->cal_status == SIPE_CAL_BUSY) {
5734 pub_calendar = sipe_publish_get_category_state_calendar(sipe_private, NULL, sip->cal->email, SIPE_CAL_OOF);
5735 pub_calendar2 = sipe_publish_get_category_state_calendar(sipe_private, event, sip->cal->email, SIPE_CAL_BUSY);
5736 } else {
5737 pub_calendar = sipe_publish_get_category_state_calendar(sipe_private, NULL, sip->cal->email, SIPE_CAL_OOF);
5738 pub_calendar2 = sipe_publish_get_category_state_calendar(sipe_private, NULL, sip->cal->email, SIPE_CAL_BUSY);
5741 oof_note = sipe_ews_get_oof_note(sip->cal);
5742 if (sipe_strequal("Scheduled", sip->cal->oof_state)) {
5743 oof_start = sip->cal->oof_start;
5744 oof_end = sip->cal->oof_end;
5746 pub_oof_note = sipe_publish_get_category_note(sipe_private, oof_note, "OOF", oof_start, oof_end);
5748 pub_cal_working_hours = sipe_publish_get_category_cal_working_hours(sipe_private);
5749 pub_cal_free_busy = sipe_publish_get_category_cal_free_busy(sipe_private);
5751 if (!pub_cal_working_hours && !pub_cal_free_busy && !pub_calendar && !pub_calendar2 && !pub_oof_note) {
5752 SIPE_DEBUG_INFO_NOFORMAT("publish_calendar_status_self: nothing has changed.");
5753 } else {
5754 gchar *publications = g_strdup_printf("%s%s%s%s%s",
5755 pub_cal_working_hours ? pub_cal_working_hours : "",
5756 pub_cal_free_busy ? pub_cal_free_busy : "",
5757 pub_calendar ? pub_calendar : "",
5758 pub_calendar2 ? pub_calendar2 : "",
5759 pub_oof_note ? pub_oof_note : "");
5761 send_presence_publish(sipe_private, publications);
5762 g_free(publications);
5765 g_free(pub_cal_working_hours);
5766 g_free(pub_cal_free_busy);
5767 g_free(pub_calendar);
5768 g_free(pub_calendar2);
5769 g_free(pub_oof_note);
5771 /* repeat scheduling */
5772 sipe_sched_calendar_status_self_publish(sipe_private, time(NULL));
5775 static void send_presence_status(struct sipe_core_private *sipe_private,
5776 SIPE_UNUSED_PARAMETER void *unused)
5778 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5779 PurpleStatus * status = purple_account_get_active_status(sip->account);
5781 if (!status) return;
5783 SIPE_DEBUG_INFO("send_presence_status: status: %s (%s)",
5784 purple_status_get_id(status) ? purple_status_get_id(status) : "",
5785 sipe_is_user_state(sipe_private) ? "USER" : "MACHINE");
5787 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
5788 send_presence_category_publish(sipe_private);
5789 } else {
5790 send_presence_soap(sipe_private, FALSE);
5794 static guint sipe_ht_hash_nick(const char *nick)
5796 char *lc = g_utf8_strdown(nick, -1);
5797 guint bucket = g_str_hash(lc);
5798 g_free(lc);
5800 return bucket;
5803 static gboolean sipe_ht_equals_nick(const char *nick1, const char *nick2)
5805 char *nick1_norm = NULL;
5806 char *nick2_norm = NULL;
5807 gboolean equal;
5809 if (nick1 == NULL && nick2 == NULL) return TRUE;
5810 if (nick1 == NULL || nick2 == NULL ||
5811 !g_utf8_validate(nick1, -1, NULL) ||
5812 !g_utf8_validate(nick2, -1, NULL)) return FALSE;
5814 nick1_norm = g_utf8_casefold(nick1, -1);
5815 nick2_norm = g_utf8_casefold(nick2, -1);
5816 equal = g_utf8_collate(nick1_norm, nick2_norm) == 0;
5817 g_free(nick2_norm);
5818 g_free(nick1_norm);
5820 return equal;
5823 /* temporary function */
5824 void sipe_purple_setup(struct sipe_core_public *sipe_public,
5825 PurpleConnection *gc)
5827 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA;
5828 sip->gc = gc;
5829 sip->account = purple_connection_get_account(gc);
5832 struct sipe_core_public *sipe_core_allocate(const gchar *signin_name,
5833 const gchar *login_domain,
5834 const gchar *login_account,
5835 const gchar *password,
5836 const gchar *email,
5837 const gchar *email_url,
5838 const gchar **errmsg)
5840 struct sipe_core_private *sipe_private;
5841 struct sipe_account_data *sip;
5842 gchar **user_domain;
5844 SIPE_DEBUG_INFO("sipe_core_allocate: signin_name '%s'", signin_name);
5846 /* ensure that sign-in name doesn't contain invalid characters */
5847 if (strpbrk(signin_name, "\t\v\r\n") != NULL) {
5848 *errmsg = _("SIP Exchange user name contains invalid characters");
5849 return NULL;
5852 /* ensure that sign-in name format is name@domain */
5853 if (!strchr(signin_name, '@') ||
5854 g_str_has_prefix(signin_name, "@") ||
5855 g_str_has_suffix(signin_name, "@")) {
5856 *errmsg = _("User name should be a valid SIP URI\nExample: user@company.com");
5857 return NULL;
5860 /* ensure that email format is name@domain (if provided) */
5861 if (!is_empty(email) &&
5862 (!strchr(email, '@') ||
5863 g_str_has_prefix(email, "@") ||
5864 g_str_has_suffix(email, "@")))
5866 *errmsg = _("Email address should be valid if provided\nExample: user@company.com");
5867 return NULL;
5870 /* ensure that user name doesn't contain spaces */
5871 user_domain = g_strsplit(signin_name, "@", 2);
5872 SIPE_DEBUG_INFO("sipe_core_allocate: user '%s' domain '%s'", user_domain[0], user_domain[1]);
5873 if (strchr(user_domain[0], ' ') != NULL) {
5874 g_strfreev(user_domain);
5875 *errmsg = _("SIP Exchange user name contains whitespace");
5876 return NULL;
5879 /* ensure that email_url is in proper format if enabled (if provided).
5880 * Example (Exchange): https://server.company.com/EWS/Exchange.asmx
5881 * Example (Domino) : https://[domino_server]/[mail_database_name].nsf
5883 if (!is_empty(email_url)) {
5884 char *tmp = g_ascii_strdown(email_url, -1);
5885 if (!g_str_has_prefix(tmp, "https://"))
5887 g_free(tmp);
5888 g_strfreev(user_domain);
5889 *errmsg = _("Email services URL should be valid if provided\n"
5890 "Example: https://exchange.corp.com/EWS/Exchange.asmx\n"
5891 "Example: https://domino.corp.com/maildatabase.nsf");
5892 return NULL;
5894 g_free(tmp);
5897 sipe_private = g_new0(struct sipe_core_private, 1);
5898 sipe_private->temporary = sip = g_new0(struct sipe_account_data, 1);
5899 sip->subscribed_buddies = FALSE;
5900 sip->initial_state_published = FALSE;
5901 sipe_private->username = g_strdup(signin_name);
5902 sip->email = is_empty(email) ? g_strdup(signin_name) : g_strdup(email);
5903 sip->authdomain = is_empty(login_domain) ? NULL : g_strdup(login_domain);
5904 sip->authuser = is_empty(login_account) ? NULL : g_strdup(login_account);
5905 sip->password = g_strdup(password);
5906 sipe_private->public.sip_name = g_strdup(user_domain[0]);
5907 sipe_private->public.sip_domain = g_strdup(user_domain[1]);
5908 g_strfreev(user_domain);
5910 sipe_private->buddies = g_hash_table_new((GHashFunc)sipe_ht_hash_nick, (GEqualFunc)sipe_ht_equals_nick);
5911 sip->our_publications = g_hash_table_new_full(g_str_hash, g_str_equal,
5912 g_free, (GDestroyNotify)g_hash_table_destroy);
5913 sipe_subscriptions_init(sipe_private);
5914 sip->status = g_strdup(SIPE_STATUS_ID_UNKNOWN);
5916 return((struct sipe_core_public *)sipe_private);
5919 static void
5920 sipe_blist_menu_free_containers(struct sipe_core_private *sipe_private);
5922 void sipe_connection_cleanup(struct sipe_core_private *sipe_private)
5924 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5926 g_free(sipe_private->epid);
5927 sipe_private->epid = NULL;
5929 sip_transport_disconnect(sipe_private);
5931 sipe_schedule_cancel_all(sipe_private);
5933 if (sip->allow_events) {
5934 GSList *entry = sip->allow_events;
5935 while (entry) {
5936 g_free(entry->data);
5937 entry = entry->next;
5940 g_slist_free(sip->allow_events);
5942 if (sip->containers) {
5943 GSList *entry = sip->containers;
5944 while (entry) {
5945 free_container((struct sipe_container *)entry->data);
5946 entry = entry->next;
5949 g_slist_free(sip->containers);
5951 /* libpurple memory leak workaround */
5952 sipe_blist_menu_free_containers(sipe_private);
5954 if (sipe_private->contact)
5955 g_free(sipe_private->contact);
5956 sipe_private->contact = NULL;
5957 if (sip->regcallid)
5958 g_free(sip->regcallid);
5959 sip->regcallid = NULL;
5961 if (sipe_private->focus_factory_uri)
5962 g_free(sipe_private->focus_factory_uri);
5963 sipe_private->focus_factory_uri = NULL;
5965 if (sip->cal) {
5966 sipe_cal_calendar_free(sip->cal);
5968 sip->cal = NULL;
5970 sipe_groupchat_free(sipe_private);
5974 * A callback for g_hash_table_foreach_remove
5976 static gboolean sipe_buddy_remove(SIPE_UNUSED_PARAMETER gpointer key, gpointer buddy,
5977 SIPE_UNUSED_PARAMETER gpointer user_data)
5979 sipe_free_buddy((struct sipe_buddy *) buddy);
5981 /* We must return TRUE as the key/value have already been deleted */
5982 return(TRUE);
5985 void sipe_buddy_free_all(struct sipe_core_private *sipe_private)
5987 g_hash_table_foreach_steal(sipe_private->buddies, sipe_buddy_remove, NULL);
5990 static void sipe_searchresults_im_buddy(PurpleConnection *gc, GList *row,
5991 SIPE_UNUSED_PARAMETER void *user_data)
5993 PurpleAccount *acct = purple_connection_get_account(gc);
5994 char *id = sip_uri_from_name((gchar *)g_list_nth_data(row, 0));
5995 PurpleConversation *conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, id, acct);
5996 if (conv == NULL)
5997 conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, acct, id);
5998 purple_conversation_present(conv);
5999 g_free(id);
6002 static void sipe_searchresults_add_buddy(PurpleConnection *gc, GList *row,
6003 SIPE_UNUSED_PARAMETER void *user_data)
6006 purple_blist_request_add_buddy(purple_connection_get_account(gc),
6007 g_list_nth_data(row, 0), _("Other Contacts"), g_list_nth_data(row, 1));
6010 static gboolean process_search_contact_response(struct sipe_core_private *sipe_private,
6011 struct sipmsg *msg,
6012 SIPE_UNUSED_PARAMETER struct transaction *trans)
6014 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
6015 PurpleNotifySearchResults *results;
6016 PurpleNotifySearchColumn *column;
6017 sipe_xml *searchResults;
6018 const sipe_xml *mrow;
6019 int match_count = 0;
6020 gboolean more = FALSE;
6021 gchar *secondary;
6023 SIPE_DEBUG_INFO("process_search_contact_response: body:\n%s", msg->body ? msg->body : "");
6025 searchResults = sipe_xml_parse(msg->body, msg->bodylen);
6026 if (!searchResults) {
6027 SIPE_DEBUG_INFO_NOFORMAT("process_search_contact_response: no parseable searchResults");
6028 return FALSE;
6031 results = purple_notify_searchresults_new();
6033 if (results == NULL) {
6034 SIPE_DEBUG_ERROR_NOFORMAT("purple_parse_searchreply: Unable to display the search results.");
6035 purple_notify_error(sip->gc, NULL, _("Unable to display the search results"), NULL);
6037 sipe_xml_free(searchResults);
6038 return FALSE;
6041 column = purple_notify_searchresults_column_new(_("User name"));
6042 purple_notify_searchresults_column_add(results, column);
6044 column = purple_notify_searchresults_column_new(_("Name"));
6045 purple_notify_searchresults_column_add(results, column);
6047 column = purple_notify_searchresults_column_new(_("Company"));
6048 purple_notify_searchresults_column_add(results, column);
6050 column = purple_notify_searchresults_column_new(_("Country"));
6051 purple_notify_searchresults_column_add(results, column);
6053 column = purple_notify_searchresults_column_new(_("Email"));
6054 purple_notify_searchresults_column_add(results, column);
6056 for (mrow = sipe_xml_child(searchResults, "Body/Array/row"); mrow; mrow = sipe_xml_twin(mrow)) {
6057 GList *row = NULL;
6059 gchar **uri_parts = g_strsplit(sipe_xml_attribute(mrow, "uri"), ":", 2);
6060 row = g_list_append(row, g_strdup(uri_parts[1]));
6061 g_strfreev(uri_parts);
6063 row = g_list_append(row, g_strdup(sipe_xml_attribute(mrow, "displayName")));
6064 row = g_list_append(row, g_strdup(sipe_xml_attribute(mrow, "company")));
6065 row = g_list_append(row, g_strdup(sipe_xml_attribute(mrow, "country")));
6066 row = g_list_append(row, g_strdup(sipe_xml_attribute(mrow, "email")));
6068 purple_notify_searchresults_row_add(results, row);
6069 match_count++;
6072 if ((mrow = sipe_xml_child(searchResults, "Body/directorySearch/moreAvailable")) != NULL) {
6073 char *data = sipe_xml_data(mrow);
6074 more = (g_strcasecmp(data, "true") == 0);
6075 g_free(data);
6078 secondary = g_strdup_printf(
6079 dngettext(PACKAGE_NAME,
6080 "Found %d contact%s:",
6081 "Found %d contacts%s:", match_count),
6082 match_count, more ? _(" (more matched your query)") : "");
6084 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_IM, sipe_searchresults_im_buddy);
6085 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_ADD, sipe_searchresults_add_buddy);
6086 purple_notify_searchresults(sip->gc, NULL, NULL, secondary, results, NULL, NULL);
6088 g_free(secondary);
6089 sipe_xml_free(searchResults);
6090 return TRUE;
6093 void sipe_search_contact_with_cb(PurpleConnection *gc, PurpleRequestFields *fields)
6095 GList *entries = purple_request_field_group_get_fields(purple_request_fields_get_groups(fields)->data);
6096 gchar **attrs = g_new(gchar *, g_list_length(entries) + 1);
6097 unsigned i = 0;
6099 if (!attrs) return;
6101 do {
6102 PurpleRequestField *field = entries->data;
6103 const char *id = purple_request_field_get_id(field);
6104 const char *value = purple_request_field_string_get_value(field);
6106 SIPE_DEBUG_INFO("sipe_search_contact_with_cb: %s = '%s'", id, value ? value : "");
6108 if (value != NULL) attrs[i++] = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, id, value);
6109 } while ((entries = g_list_next(entries)) != NULL);
6110 attrs[i] = NULL;
6112 if (i > 0) {
6113 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
6114 gchar *domain_uri = sip_uri_from_name(sipe_private->public.sip_domain);
6115 gchar *query = g_strjoinv(NULL, attrs);
6116 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 100, query);
6117 SIPE_DEBUG_INFO("sipe_search_contact_with_cb: body:\n%s", body ? body : "");
6118 send_soap_request_with_cb(sipe_private, domain_uri, body,
6119 process_search_contact_response, NULL);
6120 g_free(domain_uri);
6121 g_free(body);
6122 g_free(query);
6125 g_strfreev(attrs);
6128 static void sipe_publish_get_cat_state_user_to_clear(SIPE_UNUSED_PARAMETER const char *name,
6129 gpointer value,
6130 GString* str)
6132 struct sipe_publication *publication = value;
6134 g_string_append_printf( str,
6135 SIPE_PUB_XML_PUBLICATION_CLEAR,
6136 publication->category,
6137 publication->instance,
6138 publication->container,
6139 publication->version,
6140 "static");
6143 void sipe_core_reset_status(struct sipe_core_public *sipe_public)
6145 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
6146 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA;
6147 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) /* 2007+ */
6149 GString* str = g_string_new(NULL);
6150 gchar *publications;
6152 if (!sip->user_state_publications || g_hash_table_size(sip->user_state_publications) == 0) {
6153 SIPE_DEBUG_INFO_NOFORMAT("sipe_reset_status: no userState publications, exiting.");
6154 return;
6157 g_hash_table_foreach(sip->user_state_publications, (GHFunc)sipe_publish_get_cat_state_user_to_clear, str);
6158 publications = g_string_free(str, FALSE);
6160 send_presence_publish(sipe_private, publications);
6161 g_free(publications);
6163 else /* 2005 */
6165 send_presence_soap0(sipe_private, FALSE, TRUE);
6169 /** for Access levels menu */
6170 #define INDENT_FMT " %s"
6172 /** Member is directly placed to access level container.
6173 * For example SIP URI of user is in the container.
6175 #define INDENT_MARKED_FMT "* %s"
6177 /** Member is indirectly belong to access level container.
6178 * For example 'sameEnterprise' is in the container and user
6179 * belongs to that same enterprise.
6181 #define INDENT_MARKED_INHERITED_FMT "= %s"
6183 GSList *sipe_core_buddy_info(struct sipe_core_public *sipe_public,
6184 const gchar *name,
6185 const gchar *status_name,
6186 gboolean is_online)
6188 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
6189 gchar *note = NULL;
6190 gboolean is_oof_note = FALSE;
6191 const gchar *activity = NULL;
6192 gchar *calendar = NULL;
6193 const gchar *meeting_subject = NULL;
6194 const gchar *meeting_location = NULL;
6195 gchar *access_text = NULL;
6196 GSList *info = NULL;
6198 #define SIPE_ADD_BUDDY_INFO_COMMON(l, t) \
6200 struct sipe_buddy_info *sbi = g_malloc(sizeof(struct sipe_buddy_info)); \
6201 sbi->label = (l); \
6202 sbi->text = (t); \
6203 info = g_slist_append(info, sbi); \
6205 #define SIPE_ADD_BUDDY_INFO(l, t) SIPE_ADD_BUDDY_INFO_COMMON((l), g_markup_escape_text((t), -1))
6206 #define SIPE_ADD_BUDDY_INFO_NOESCAPE(l, t) SIPE_ADD_BUDDY_INFO_COMMON((l), (t))
6208 if (sipe_public) { //happens on pidgin exit
6209 struct sipe_buddy *sbuddy = g_hash_table_lookup(sipe_private->buddies, name);
6210 if (sbuddy) {
6211 note = sbuddy->note;
6212 is_oof_note = sbuddy->is_oof_note;
6213 activity = sbuddy->activity;
6214 calendar = sipe_cal_get_description(sbuddy);
6215 meeting_subject = sbuddy->meeting_subject;
6216 meeting_location = sbuddy->meeting_location;
6218 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
6219 gboolean is_group_access = FALSE;
6220 const int container_id = sipe_find_access_level(sipe_private, "user", sipe_get_no_sip_uri(name), &is_group_access);
6221 const char *access_level = sipe_get_access_level_name(container_id);
6222 access_text = is_group_access ?
6223 g_strdup(access_level) :
6224 g_strdup_printf(INDENT_MARKED_FMT, access_level);
6228 //Layout
6229 if (is_online)
6231 const gchar *status_str = activity ? activity : status_name;
6233 SIPE_ADD_BUDDY_INFO(_("Status"), status_str);
6235 if (is_online && !is_empty(calendar))
6237 SIPE_ADD_BUDDY_INFO(_("Calendar"), calendar);
6239 g_free(calendar);
6240 if (!is_empty(meeting_location))
6242 SIPE_DEBUG_INFO("sipe_tooltip_text: %s meeting location: '%s'", name, meeting_location);
6243 SIPE_ADD_BUDDY_INFO(_("Meeting in"), meeting_location);
6245 if (!is_empty(meeting_subject))
6247 SIPE_DEBUG_INFO("sipe_tooltip_text: %s meeting subject: '%s'", name, meeting_subject);
6248 SIPE_ADD_BUDDY_INFO(_("Meeting about"), meeting_subject);
6250 if (note)
6252 SIPE_DEBUG_INFO("sipe_tooltip_text: %s note: '%s'", name, note);
6253 SIPE_ADD_BUDDY_INFO_NOESCAPE(is_oof_note ? _("Out of office note") : _("Note"),
6254 g_strdup_printf("<i>%s</i>", note));
6256 if (access_text) {
6257 SIPE_ADD_BUDDY_INFO(_("Access level"), access_text);
6258 g_free(access_text);
6261 return(info);
6264 static PurpleBuddy *
6265 purple_blist_add_buddy_clone(PurpleGroup * group, PurpleBuddy * buddy)
6267 PurpleBuddy *clone;
6268 const gchar *server_alias, *email;
6269 const PurpleStatus *status = purple_presence_get_active_status(purple_buddy_get_presence(buddy));
6271 clone = purple_buddy_new(buddy->account, buddy->name, buddy->alias);
6273 purple_blist_add_buddy(clone, NULL, group, NULL);
6275 server_alias = purple_buddy_get_server_alias(buddy);
6276 if (server_alias) {
6277 purple_blist_server_alias_buddy(clone, server_alias);
6280 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
6281 if (email) {
6282 purple_blist_node_set_string(&clone->node, EMAIL_PROP, email);
6285 purple_presence_set_status_active(purple_buddy_get_presence(clone), purple_status_get_id(status), TRUE);
6286 //for UI to update;
6287 purple_prpl_got_user_status(clone->account, clone->name, purple_status_get_id(status), NULL);
6288 return clone;
6291 static void
6292 sipe_buddy_menu_copy_to_cb(PurpleBlistNode *node, const char *group_name)
6294 PurpleBuddy *buddy, *b;
6295 PurpleConnection *gc;
6296 PurpleGroup * group = purple_find_group(group_name);
6298 g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node));
6300 buddy = (PurpleBuddy *)node;
6302 SIPE_DEBUG_INFO("sipe_buddy_menu_copy_to_cb: copying %s to %s", buddy->name, group_name);
6303 gc = purple_account_get_connection(buddy->account);
6305 b = purple_find_buddy_in_group(buddy->account, buddy->name, group);
6306 if (!b){
6307 b = purple_blist_add_buddy_clone(group, buddy);
6310 sipe_add_buddy(gc, b, group);
6313 static void
6314 sipe_buddy_menu_chat_new_cb(PurpleBuddy *buddy)
6316 struct sipe_core_private *sipe_private = PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE;
6318 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_new_cb: buddy->name=%s", buddy->name);
6320 /* 2007+ conference */
6321 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007))
6323 sipe_conf_add(sipe_private, buddy->name);
6325 else /* 2005- multiparty chat */
6327 gchar *self = sip_uri_self(sipe_private);
6328 struct sip_session *session;
6330 session = sipe_session_add_chat(sipe_private,
6331 NULL,
6332 TRUE,
6333 self);
6334 session->chat_session->backend = sipe_backend_chat_create(SIPE_CORE_PUBLIC,
6335 session->chat_session,
6336 session->chat_session->title,
6337 self);
6338 g_free(self);
6340 sipe_invite(sipe_private, session, buddy->name, NULL, NULL, NULL, FALSE);
6345 * For 2007+ conference only.
6347 static void
6348 sipe_buddy_menu_chat_make_leader_cb(PurpleBuddy *buddy,
6349 struct sipe_chat_session *chat_session)
6351 struct sipe_core_private *sipe_private = PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE;
6352 struct sip_session *session;
6354 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_make_leader_cb: buddy->name=%s", buddy->name);
6355 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_make_leader_cb: chat_title=%s", chat_session->title);
6357 session = sipe_session_find_chat(sipe_private, chat_session);
6359 sipe_conf_modify_user_role(sipe_private, session, buddy->name);
6363 * For 2007+ conference only.
6365 static void
6366 sipe_buddy_menu_chat_remove_cb(PurpleBuddy *buddy,
6367 struct sipe_chat_session *chat_session)
6369 struct sipe_core_private *sipe_private = PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE;
6370 struct sip_session *session;
6372 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_remove_cb: buddy->name=%s", buddy->name);
6373 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_remove_cb: chat_title=%s", chat_session->title);
6375 session = sipe_session_find_chat(sipe_private, chat_session);
6377 sipe_conf_delete_user(sipe_private, session, buddy->name);
6380 static void
6381 sipe_buddy_menu_chat_invite_cb(PurpleBuddy *buddy,
6382 struct sipe_chat_session *chat_session)
6384 struct sipe_core_private *sipe_private = PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE;
6386 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_invite_cb: buddy->name=%s", buddy->name);
6387 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_invite_cb: chat_title=%s", chat_session->title);
6389 sipe_core_chat_invite(SIPE_CORE_PUBLIC, chat_session, buddy->name);
6392 static void
6393 sipe_buddy_menu_make_call_cb(PurpleBuddy *buddy, const char *phone)
6395 struct sipe_core_private *sipe_private = PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE;
6397 SIPE_DEBUG_INFO("sipe_buddy_menu_make_call_cb: buddy->name=%s", buddy->name);
6398 if (phone) {
6399 char *tel_uri = sip_to_tel_uri(phone);
6401 SIPE_DEBUG_INFO("sipe_buddy_menu_make_call_cb: going to call number: %s", tel_uri ? tel_uri : "");
6402 sip_csta_make_call(sipe_private, tel_uri);
6404 g_free(tel_uri);
6408 static void
6409 sipe_buddy_menu_access_level_help_cb(PurpleBuddy *buddy)
6411 /** Translators: replace with URL to localized page
6412 * If it doesn't exist copy the original URL */
6413 purple_notify_uri(buddy->account->gc, _("https://sourceforge.net/apps/mediawiki/sipe/index.php?title=Access_Levels"));
6416 static void
6417 sipe_buddy_menu_send_email_cb(PurpleBuddy *buddy)
6419 const gchar *email;
6420 SIPE_DEBUG_INFO("sipe_buddy_menu_send_email_cb: buddy->name=%s", buddy->name);
6422 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
6423 if (email)
6425 char *command_line = g_strdup_printf(
6426 #ifdef _WIN32
6427 "cmd /c start"
6428 #else
6429 "xdg-email"
6430 #endif
6431 " mailto:%s", email);
6432 SIPE_DEBUG_INFO("sipe_buddy_menu_send_email_cb: going to call email client: %s", command_line);
6434 g_spawn_command_line_async(command_line, NULL);
6435 g_free(command_line);
6437 else
6439 SIPE_DEBUG_INFO("sipe_buddy_menu_send_email_cb: no email address stored for buddy=%s", buddy->name);
6443 static void
6444 sipe_buddy_menu_access_level_cb(PurpleBuddy *buddy,
6445 struct sipe_container *container)
6447 struct sipe_core_private *sipe_private = PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE;
6448 struct sipe_container_member *member;
6450 if (!container || !container->members) return;
6452 member = ((struct sipe_container_member *)container->members->data);
6454 if (!member->type) return;
6456 SIPE_DEBUG_INFO("sipe_buddy_menu_access_level_cb: container->id=%d, member->type=%s, member->value=%s",
6457 container->id, member->type, member->value ? member->value : "");
6459 sipe_change_access_level(sipe_private, container->id, member->type, member->value);
6462 static GList *
6463 sipe_get_access_control_menu(struct sipe_core_private *sipe_private,
6464 const char* uri);
6467 * A menu which appear when right-clicking on buddy in contact list.
6469 GList *
6470 sipe_buddy_menu(PurpleBuddy *buddy)
6472 PurpleBlistNode *g_node;
6473 PurpleGroup *gr_parent;
6474 PurpleMenuAction *act;
6475 GList *menu = NULL;
6476 GList *menu_groups = NULL;
6477 struct sipe_core_private *sipe_private = PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE;
6478 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
6479 const char *email;
6480 gchar *self = sip_uri_self(sipe_private);
6482 SIPE_SESSION_FOREACH {
6483 if (!sipe_strcase_equal(self, buddy->name) && session->chat_session)
6485 struct sipe_chat_session *chat_session = session->chat_session;
6486 gboolean is_conf = (chat_session->type == SIPE_CHAT_TYPE_CONFERENCE);
6488 if (sipe_backend_chat_find(chat_session->backend, buddy->name))
6490 gboolean conf_op = sipe_backend_chat_is_operator(chat_session->backend, self);
6492 if (is_conf
6493 && !sipe_backend_chat_is_operator(chat_session->backend, buddy->name) /* Not conf OP */
6494 && conf_op) /* We are a conf OP */
6496 gchar *label = g_strdup_printf(_("Make leader of '%s'"),
6497 chat_session->title);
6498 act = purple_menu_action_new(label,
6499 PURPLE_CALLBACK(sipe_buddy_menu_chat_make_leader_cb),
6500 chat_session, NULL);
6501 g_free(label);
6502 menu = g_list_prepend(menu, act);
6505 if (is_conf
6506 && conf_op) /* We are a conf OP */
6508 gchar *label = g_strdup_printf(_("Remove from '%s'"),
6509 chat_session->title);
6510 act = purple_menu_action_new(label,
6511 PURPLE_CALLBACK(sipe_buddy_menu_chat_remove_cb),
6512 chat_session, NULL);
6513 g_free(label);
6514 menu = g_list_prepend(menu, act);
6517 else
6519 if (!is_conf
6520 || (is_conf && !session->locked))
6522 gchar *label = g_strdup_printf(_("Invite to '%s'"),
6523 chat_session->title);
6524 act = purple_menu_action_new(label,
6525 PURPLE_CALLBACK(sipe_buddy_menu_chat_invite_cb),
6526 chat_session, NULL);
6527 g_free(label);
6528 menu = g_list_prepend(menu, act);
6532 } SIPE_SESSION_FOREACH_END;
6534 act = purple_menu_action_new(_("New chat"),
6535 PURPLE_CALLBACK(sipe_buddy_menu_chat_new_cb),
6536 NULL, NULL);
6537 menu = g_list_prepend(menu, act);
6539 if (sip->csta && !sip->csta->line_status) {
6540 const char *phone;
6541 const char *phone_disp_str;
6542 gchar *tmp = NULL;
6543 /* work phone */
6544 phone = purple_blist_node_get_string(&buddy->node, PHONE_PROP);
6545 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_DISPLAY_PROP);
6546 if (phone) {
6547 gchar *label = g_strdup_printf(_("Work %s"),
6548 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
6549 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
6550 g_free(tmp);
6551 tmp = NULL;
6552 g_free(label);
6553 menu = g_list_prepend(menu, act);
6556 /* mobile phone */
6557 phone = purple_blist_node_get_string(&buddy->node, PHONE_MOBILE_PROP);
6558 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_MOBILE_DISPLAY_PROP);
6559 if (phone) {
6560 gchar *label = g_strdup_printf(_("Mobile %s"),
6561 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
6562 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
6563 g_free(tmp);
6564 tmp = NULL;
6565 g_free(label);
6566 menu = g_list_prepend(menu, act);
6569 /* home phone */
6570 phone = purple_blist_node_get_string(&buddy->node, PHONE_HOME_PROP);
6571 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_HOME_DISPLAY_PROP);
6572 if (phone) {
6573 gchar *label = g_strdup_printf(_("Home %s"),
6574 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
6575 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
6576 g_free(tmp);
6577 tmp = NULL;
6578 g_free(label);
6579 menu = g_list_prepend(menu, act);
6582 /* other phone */
6583 phone = purple_blist_node_get_string(&buddy->node, PHONE_OTHER_PROP);
6584 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_OTHER_DISPLAY_PROP);
6585 if (phone) {
6586 gchar *label = g_strdup_printf(_("Other %s"),
6587 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
6588 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
6589 g_free(tmp);
6590 tmp = NULL;
6591 g_free(label);
6592 menu = g_list_prepend(menu, act);
6595 /* custom1 phone */
6596 phone = purple_blist_node_get_string(&buddy->node, PHONE_CUSTOM1_PROP);
6597 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_CUSTOM1_DISPLAY_PROP);
6598 if (phone) {
6599 gchar *label = g_strdup_printf(_("Custom1 %s"),
6600 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
6601 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
6602 g_free(tmp);
6603 tmp = NULL;
6604 g_free(label);
6605 menu = g_list_prepend(menu, act);
6609 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
6610 if (email) {
6611 act = purple_menu_action_new(_("Send email..."),
6612 PURPLE_CALLBACK(sipe_buddy_menu_send_email_cb),
6613 NULL, NULL);
6614 menu = g_list_prepend(menu, act);
6617 /* Access Level */
6618 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
6619 GList *menu_access_levels = sipe_get_access_control_menu(sipe_private, buddy->name);
6621 act = purple_menu_action_new(_("Access level"),
6622 NULL,
6623 NULL, menu_access_levels);
6624 menu = g_list_prepend(menu, act);
6627 /* Copy to */
6628 gr_parent = purple_buddy_get_group(buddy);
6629 for (g_node = purple_blist_get_root(); g_node; g_node = g_node->next) {
6630 PurpleGroup *group;
6632 if (g_node->type != PURPLE_BLIST_GROUP_NODE)
6633 continue;
6635 group = (PurpleGroup *)g_node;
6636 if (group == gr_parent)
6637 continue;
6639 if (purple_find_buddy_in_group(buddy->account, buddy->name, group))
6640 continue;
6642 act = purple_menu_action_new(purple_group_get_name(group),
6643 PURPLE_CALLBACK(sipe_buddy_menu_copy_to_cb),
6644 group->name, NULL);
6645 menu_groups = g_list_prepend(menu_groups, act);
6647 menu_groups = g_list_reverse(menu_groups);
6649 act = purple_menu_action_new(_("Copy to"),
6650 NULL,
6651 NULL, menu_groups);
6652 menu = g_list_prepend(menu, act);
6654 menu = g_list_reverse(menu);
6656 g_free(self);
6657 return menu;
6660 static void
6661 sipe_ask_access_domain_cb(PurpleConnection *gc, PurpleRequestFields *fields)
6663 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
6664 const char *domain = purple_request_fields_get_string(fields, "access_domain");
6665 int index = purple_request_fields_get_choice(fields, "container_id");
6666 /* move Blocked first */
6667 int i = (index == 4) ? 0 : index + 1;
6668 int container_id = containers[i];
6670 SIPE_DEBUG_INFO("sipe_ask_access_domain_cb: domain=%s, container_id=(%d)%d", domain ? domain : "", index, container_id);
6672 sipe_change_access_level(sipe_private, container_id, "domain", domain);
6675 static void
6676 sipe_ask_access_domain(struct sipe_core_private *sipe_private)
6678 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
6679 PurpleAccount *account = sip->account;
6680 PurpleConnection *gc = sip->gc;
6681 PurpleRequestFields *fields;
6682 PurpleRequestFieldGroup *g;
6683 PurpleRequestField *f;
6685 fields = purple_request_fields_new();
6687 g = purple_request_field_group_new(NULL);
6688 f = purple_request_field_string_new("access_domain", _("Domain"), "partner-company.com", FALSE);
6689 purple_request_field_set_required(f, TRUE);
6690 purple_request_field_group_add_field(g, f);
6692 f = purple_request_field_choice_new("container_id", _("Access level"), 0);
6693 purple_request_field_choice_add(f, _("Personal")); /* index 0 */
6694 purple_request_field_choice_add(f, _("Team"));
6695 purple_request_field_choice_add(f, _("Company"));
6696 purple_request_field_choice_add(f, _("Public"));
6697 purple_request_field_choice_add(f, _("Blocked")); /* index 4 */
6698 purple_request_field_choice_set_default_value(f, 3); /* index */
6699 purple_request_field_set_required(f, TRUE);
6700 purple_request_field_group_add_field(g, f);
6702 purple_request_fields_add_group(fields, g);
6704 purple_request_fields(gc, _("Add new domain"),
6705 _("Add new domain"), NULL, fields,
6706 _("Add"), G_CALLBACK(sipe_ask_access_domain_cb),
6707 _("Cancel"), NULL,
6708 account, NULL, NULL, gc);
6711 static void
6712 sipe_buddy_menu_access_level_add_domain_cb(PurpleBuddy *buddy)
6714 sipe_ask_access_domain(PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE);
6718 * Workaround for missing libpurple API to release resources allocated
6719 * during blist_node_menu() callback. See also:
6721 * <http://developer.pidgin.im/ticket/12597>
6723 * We remember all memory blocks in a list and deallocate them when
6725 * - the next time we enter the callback, or
6726 * - the account is disconnected
6728 * That means that after the buddy menu has been closed we have unused
6729 * resources but at least we don't leak them anymore...
6731 static void
6732 sipe_blist_menu_free_containers(struct sipe_core_private *sipe_private)
6734 GSList *entry = sipe_private->blist_menu_containers;
6735 while (entry) {
6736 free_container(entry->data);
6737 entry = entry->next;
6739 g_slist_free(sipe_private->blist_menu_containers);
6740 sipe_private->blist_menu_containers = NULL;
6743 static void
6744 sipe_blist_menu_remember_container(struct sipe_core_private *sipe_private,
6745 struct sipe_container *container)
6747 sipe_private->blist_menu_containers = g_slist_prepend(sipe_private->blist_menu_containers,
6748 container);
6751 static GList *
6752 sipe_get_access_levels_menu(struct sipe_core_private *sipe_private,
6753 const char* member_type,
6754 const char* member_value,
6755 const gboolean extra_menu)
6757 GList *menu_access_levels = NULL;
6758 unsigned int i;
6759 char *menu_name;
6760 PurpleMenuAction *act;
6761 struct sipe_container *container;
6762 struct sipe_container_member *member;
6763 gboolean is_group_access = FALSE;
6764 int container_id = sipe_find_access_level(sipe_private, member_type, member_value, &is_group_access);
6766 for (i = 1; i <= CONTAINERS_LEN; i++) {
6767 /* to put Blocked level last in menu list.
6768 * Blocked should remaim in the first place in the containers[] array.
6770 unsigned int j = (i == CONTAINERS_LEN) ? 0 : i;
6771 const char *acc_level_name = sipe_get_access_level_name(containers[j]);
6773 container = g_new0(struct sipe_container, 1);
6774 member = g_new0(struct sipe_container_member, 1);
6775 container->id = containers[j];
6776 container->members = g_slist_append(container->members, member);
6777 member->type = g_strdup(member_type);
6778 member->value = g_strdup(member_value);
6780 /* libpurple memory leak workaround */
6781 sipe_blist_menu_remember_container(sipe_private, container);
6783 /* current container/access level */
6784 if (((int)containers[j]) == container_id) {
6785 menu_name = is_group_access ?
6786 g_strdup_printf(INDENT_MARKED_INHERITED_FMT, acc_level_name) :
6787 g_strdup_printf(INDENT_MARKED_FMT, acc_level_name);
6788 } else {
6789 menu_name = g_strdup_printf(INDENT_FMT, acc_level_name);
6792 act = purple_menu_action_new(menu_name,
6793 PURPLE_CALLBACK(sipe_buddy_menu_access_level_cb),
6794 container, NULL);
6795 g_free(menu_name);
6796 menu_access_levels = g_list_prepend(menu_access_levels, act);
6799 if (extra_menu && (container_id >= 0)) {
6800 /* separator */
6801 act = purple_menu_action_new(" --------------", NULL, NULL, NULL);
6802 menu_access_levels = g_list_prepend(menu_access_levels, act);
6804 if (!is_group_access) {
6805 container = g_new0(struct sipe_container, 1);
6806 member = g_new0(struct sipe_container_member, 1);
6807 container->id = -1;
6808 container->members = g_slist_append(container->members, member);
6809 member->type = g_strdup(member_type);
6810 member->value = g_strdup(member_value);
6812 /* libpurple memory leak workaround */
6813 sipe_blist_menu_remember_container(sipe_private, container);
6815 /* Translators: remove (clear) previously assigned access level */
6816 menu_name = g_strdup_printf(INDENT_FMT, _("Unspecify"));
6817 act = purple_menu_action_new(menu_name,
6818 PURPLE_CALLBACK(sipe_buddy_menu_access_level_cb),
6819 container, NULL);
6820 g_free(menu_name);
6821 menu_access_levels = g_list_prepend(menu_access_levels, act);
6825 menu_access_levels = g_list_reverse(menu_access_levels);
6826 return menu_access_levels;
6829 static GList *
6830 sipe_get_access_groups_menu(struct sipe_core_private *sipe_private)
6832 GList *menu_access_groups = NULL;
6833 PurpleMenuAction *act;
6834 GSList *access_domains = NULL;
6835 GSList *entry;
6836 char *menu_name;
6837 char *domain;
6839 act = purple_menu_action_new(_("People in my company"),
6840 NULL,
6841 NULL, sipe_get_access_levels_menu(sipe_private, "sameEnterprise", NULL, FALSE));
6842 menu_access_groups = g_list_prepend(menu_access_groups, act);
6844 /* this is original name, don't edit */
6845 act = purple_menu_action_new(_("People in domains connected with my company"),
6846 NULL,
6847 NULL, sipe_get_access_levels_menu(sipe_private, "federated", NULL, FALSE));
6848 menu_access_groups = g_list_prepend(menu_access_groups, act);
6850 act = purple_menu_action_new(_("People in public domains"),
6851 NULL,
6852 NULL, sipe_get_access_levels_menu(sipe_private, "publicCloud", NULL, TRUE));
6853 menu_access_groups = g_list_prepend(menu_access_groups, act);
6855 access_domains = sipe_get_access_domains(sipe_private);
6856 entry = access_domains;
6857 while (entry) {
6858 domain = entry->data;
6860 menu_name = g_strdup_printf(_("People at %s"), domain);
6861 act = purple_menu_action_new(menu_name,
6862 NULL,
6863 NULL, sipe_get_access_levels_menu(sipe_private, "domain", g_strdup(domain), TRUE));
6864 menu_access_groups = g_list_prepend(menu_access_groups, act);
6865 g_free(menu_name);
6867 entry = entry->next;
6870 /* separator */
6871 /* People in domains connected with my company */
6872 act = purple_menu_action_new("-------------------------------------------", NULL, NULL, NULL);
6873 menu_access_groups = g_list_prepend(menu_access_groups, act);
6875 act = purple_menu_action_new(_("Add new domain..."),
6876 PURPLE_CALLBACK(sipe_buddy_menu_access_level_add_domain_cb),
6877 NULL, NULL);
6878 menu_access_groups = g_list_prepend(menu_access_groups, act);
6880 menu_access_groups = g_list_reverse(menu_access_groups);
6882 return menu_access_groups;
6885 static GList *
6886 sipe_get_access_control_menu(struct sipe_core_private *sipe_private,
6887 const char* uri)
6889 GList *menu_access_levels = NULL;
6890 GList *menu_access_groups = NULL;
6891 char *menu_name;
6892 PurpleMenuAction *act;
6894 /* libpurple memory leak workaround */
6895 sipe_blist_menu_free_containers(sipe_private);
6897 menu_access_levels = sipe_get_access_levels_menu(sipe_private, "user", sipe_get_no_sip_uri(uri), TRUE);
6899 menu_access_groups = sipe_get_access_groups_menu(sipe_private);
6901 menu_name = g_strdup_printf(INDENT_FMT, _("Access groups"));
6902 act = purple_menu_action_new(menu_name,
6903 NULL,
6904 NULL, menu_access_groups);
6905 g_free(menu_name);
6906 menu_access_levels = g_list_append(menu_access_levels, act);
6908 menu_name = g_strdup_printf(INDENT_FMT, _("Online help..."));
6909 act = purple_menu_action_new(menu_name,
6910 PURPLE_CALLBACK(sipe_buddy_menu_access_level_help_cb),
6911 NULL, NULL);
6912 g_free(menu_name);
6913 menu_access_levels = g_list_append(menu_access_levels, act);
6915 return menu_access_levels;
6918 static gboolean
6919 process_get_info_response(struct sipe_core_private *sipe_private,
6920 struct sipmsg *msg, struct transaction *trans)
6922 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
6923 char *uri = trans->payload->data;
6925 PurpleNotifyUserInfo *info;
6926 PurpleBuddy *pbuddy = NULL;
6927 struct sipe_buddy *sbuddy;
6928 const char *alias = NULL;
6929 char *device_name = NULL;
6930 char *server_alias = NULL;
6931 char *phone_number = NULL;
6932 char *email = NULL;
6933 const char *site;
6934 char *first_name = NULL;
6935 char *last_name = NULL;
6937 if (!sip) return FALSE;
6939 SIPE_DEBUG_INFO("Fetching %s's user info for %s", uri, sipe_private->username);
6941 pbuddy = purple_find_buddy((PurpleAccount *)sip->account, uri);
6942 alias = purple_buddy_get_local_alias(pbuddy);
6944 //will query buddy UA's capabilities and send answer to log
6945 sipe_options_request(sipe_private, uri);
6947 sbuddy = g_hash_table_lookup(sipe_private->buddies, uri);
6948 if (sbuddy) {
6949 device_name = sbuddy->device_name ? g_strdup(sbuddy->device_name) : NULL;
6952 info = purple_notify_user_info_new();
6954 if (msg->response != 200) {
6955 SIPE_DEBUG_INFO("process_get_info_response: SERVICE response is %d", msg->response);
6956 } else {
6957 sipe_xml *searchResults;
6958 const sipe_xml *mrow;
6960 SIPE_DEBUG_INFO("process_get_info_response: body:\n%s", msg->body ? msg->body : "");
6961 searchResults = sipe_xml_parse(msg->body, msg->bodylen);
6962 if (!searchResults) {
6963 SIPE_DEBUG_INFO_NOFORMAT("process_get_info_response: no parseable searchResults");
6964 } else if ((mrow = sipe_xml_child(searchResults, "Body/Array/row"))) {
6965 const char *value;
6966 server_alias = g_strdup(sipe_xml_attribute(mrow, "displayName"));
6967 email = g_strdup(sipe_xml_attribute(mrow, "email"));
6968 phone_number = g_strdup(sipe_xml_attribute(mrow, "phone"));
6970 /* For 2007 system we will take this from ContactCard -
6971 * it has cleaner tel: URIs at least
6973 if (!SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
6974 char *tel_uri = sip_to_tel_uri(phone_number);
6975 /* trims its parameters, so call first */
6976 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_DISPLAY_NAME, server_alias);
6977 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_EMAIL, email);
6978 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_WORK_PHONE, tel_uri);
6979 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_WORK_PHONE_DISPLAY, phone_number);
6980 g_free(tel_uri);
6983 if (server_alias && strlen(server_alias) > 0) {
6984 purple_notify_user_info_add_pair(info, _("Display name"), server_alias);
6986 if ((value = sipe_xml_attribute(mrow, "title")) && strlen(value) > 0) {
6987 purple_notify_user_info_add_pair(info, _("Job title"), value);
6989 if ((value = sipe_xml_attribute(mrow, "office")) && strlen(value) > 0) {
6990 purple_notify_user_info_add_pair(info, _("Office"), value);
6992 if (phone_number && strlen(phone_number) > 0) {
6993 purple_notify_user_info_add_pair(info, _("Business phone"), phone_number);
6995 if ((value = sipe_xml_attribute(mrow, "company")) && strlen(value) > 0) {
6996 purple_notify_user_info_add_pair(info, _("Company"), value);
6998 if ((value = sipe_xml_attribute(mrow, "city")) && strlen(value) > 0) {
6999 purple_notify_user_info_add_pair(info, _("City"), value);
7001 if ((value = sipe_xml_attribute(mrow, "state")) && strlen(value) > 0) {
7002 purple_notify_user_info_add_pair(info, _("State"), value);
7004 if ((value = sipe_xml_attribute(mrow, "country")) && strlen(value) > 0) {
7005 purple_notify_user_info_add_pair(info, _("Country"), value);
7007 if (email && strlen(email) > 0) {
7008 purple_notify_user_info_add_pair(info, _("Email address"), email);
7012 sipe_xml_free(searchResults);
7015 purple_notify_user_info_add_section_break(info);
7017 if (is_empty(server_alias)) {
7018 g_free(server_alias);
7019 server_alias = g_strdup(purple_buddy_get_server_alias(pbuddy));
7020 if (server_alias) {
7021 purple_notify_user_info_add_pair(info, _("Display name"), server_alias);
7025 /* present alias if it differs from server alias */
7026 if (alias && !sipe_strequal(alias, server_alias))
7028 purple_notify_user_info_add_pair(info, _("Alias"), alias);
7031 if (is_empty(email)) {
7032 g_free(email);
7033 email = g_strdup(purple_blist_node_get_string(&pbuddy->node, EMAIL_PROP));
7034 if (email) {
7035 purple_notify_user_info_add_pair(info, _("Email address"), email);
7039 site = purple_blist_node_get_string(&pbuddy->node, SITE_PROP);
7040 if (site) {
7041 purple_notify_user_info_add_pair(info, _("Site"), site);
7044 sipe_get_first_last_names(sipe_private, uri, &first_name, &last_name);
7045 if (first_name && last_name) {
7046 char *link = g_strconcat("http://www.linkedin.com/pub/dir/", first_name, "/", last_name, NULL);
7048 purple_notify_user_info_add_pair(info, _("Find on LinkedIn"), link);
7049 g_free(link);
7051 g_free(first_name);
7052 g_free(last_name);
7054 if (device_name) {
7055 purple_notify_user_info_add_pair(info, _("Device"), device_name);
7058 /* show a buddy's user info in a nice dialog box */
7059 purple_notify_userinfo(sip->gc, /* connection the buddy info came through */
7060 uri, /* buddy's URI */
7061 info, /* body */
7062 NULL, /* callback called when dialog closed */
7063 NULL); /* userdata for callback */
7065 g_free(phone_number);
7066 g_free(server_alias);
7067 g_free(email);
7068 g_free(device_name);
7070 return TRUE;
7074 * AD search first, LDAP based
7076 void sipe_get_info(PurpleConnection *gc, const char *username)
7078 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
7079 gchar *domain_uri = sip_uri_from_name(sipe_private->public.sip_domain);
7080 char *row = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, "msRTCSIP-PrimaryUserAddress", username);
7081 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 1, row);
7082 struct transaction_payload *payload = g_new0(struct transaction_payload, 1);
7084 payload->destroy = g_free;
7085 payload->data = g_strdup(username);
7087 SIPE_DEBUG_INFO("sipe_get_contact_data: body:\n%s", body ? body : "");
7088 send_soap_request_with_cb(sipe_private, domain_uri, body,
7089 process_get_info_response, payload);
7090 g_free(domain_uri);
7091 g_free(body);
7092 g_free(row);
7096 Local Variables:
7097 mode: c
7098 c-file-style: "bsd"
7099 indent-tabs-mode: t
7100 tab-width: 8
7101 End: