refactor unconfirmed message list code
[siplcs.git] / src / core / sipe.c
blob69fe6872d627cd62f029a6cc8d5576c4d0c2b030
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);
3007 /* key must be g_free()'d */
3008 static gchar *
3009 get_unconfirmed_message_key(const gchar *callid,
3010 unsigned int cseq,
3011 const gchar *with)
3013 return(g_strdup_printf("<%s><%d><%s><%s>", callid, cseq,
3014 with ? "MESSAGE" : "INVITE",
3015 with ? with : ""));
3018 static void
3019 insert_unconfirmed_message(struct sip_session *session,
3020 struct sip_dialog *dialog,
3021 const gchar *with,
3022 struct queued_message *message)
3024 gchar *key = get_unconfirmed_message_key(dialog->callid, dialog->cseq + 1, with);
3025 g_hash_table_insert(session->unconfirmed_messages, key, message);
3026 SIPE_DEBUG_INFO("insert_confirmed_message: added %s to list (count=%d)",
3027 key, g_hash_table_size(session->unconfirmed_messages));
3030 static void
3031 remove_unconfirmed_message(struct sip_session *session,
3032 const gchar *key)
3034 g_hash_table_remove(session->unconfirmed_messages, key);
3035 SIPE_DEBUG_INFO("remove_unconfirmed_message: removed %s from list (count=%d)",
3036 key, g_hash_table_size(session->unconfirmed_messages));
3039 static gboolean
3040 process_message_response(struct sipe_core_private *sipe_private,
3041 struct sipmsg *msg,
3042 SIPE_UNUSED_PARAMETER struct transaction *trans)
3044 gboolean ret = TRUE;
3045 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
3046 struct sip_session *session = sipe_session_find_im(sipe_private, with);
3047 struct sip_dialog *dialog;
3048 gchar *cseq;
3049 gchar *key;
3050 struct queued_message *message;
3052 if (!session) {
3053 SIPE_DEBUG_INFO_NOFORMAT("process_message_response: unable to find IM session");
3054 g_free(with);
3055 return FALSE;
3058 dialog = sipe_dialog_find(session, with);
3059 if (!dialog) {
3060 SIPE_DEBUG_INFO_NOFORMAT("process_message_response: session outgoing dialog is NULL");
3061 g_free(with);
3062 return FALSE;
3065 cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
3066 key = get_unconfirmed_message_key(sipmsg_find_header(msg, "Call-ID"), atoi(cseq), with);
3067 g_free(cseq);
3068 message = g_hash_table_lookup(session->unconfirmed_messages, key);
3070 if (msg->response >= 400) {
3071 sipe_backend_buddy pbuddy;
3072 gchar *alias = NULL;
3073 const char *warn_hdr = sipmsg_find_header(msg, "Warning");
3074 int warning = -1;
3076 SIPE_DEBUG_INFO_NOFORMAT("process_message_response: MESSAGE response >= 400");
3078 if (warn_hdr) {
3079 gchar **parts = g_strsplit(warn_hdr, " ", 2);
3080 if (parts[0]) {
3081 warning = atoi(parts[0]);
3083 g_strfreev(parts);
3086 /* cancel file transfer as rejected by server */
3087 if (msg->response == 606 && /* Not acceptable all. */
3088 warning == 309 && /* Message contents not allowed by policy */
3089 message && g_str_has_prefix(message->content_type, "text/x-msmsgsinvite"))
3091 GSList *parsed_body = sipe_ft_parse_msg_body(msg->body);
3092 sipe_ft_incoming_cancel(dialog, parsed_body);
3093 sipe_utils_nameval_free(parsed_body);
3096 if ((pbuddy = sipe_backend_buddy_find(SIPE_CORE_PUBLIC, with, NULL))) {
3097 alias = sipe_backend_buddy_get_alias(SIPE_CORE_PUBLIC,pbuddy);
3100 sipe_present_message_undelivered_err(sipe_private, session,
3101 msg->response, warning,
3102 alias ? alias : with,
3103 message ? message->body : NULL);
3105 /* drop dangling IM sessions: assume that BYE from remote never reached us */
3106 if (msg->response == 408 || /* Request timeout */
3107 msg->response == 480 || /* Temporarily Unavailable */
3108 msg->response == 481) { /* Call/Transaction Does Not Exist */
3109 SIPE_DEBUG_INFO_NOFORMAT("process_message_response: assuming dangling IM session, dropping it.");
3110 sip_transport_bye(sipe_private, dialog);
3112 /* We might not get a valid reply to our BYE,
3113 so make sure the dialog is removed for sure. */
3114 sipe_dialog_remove(session, with);
3115 dialog = NULL;
3118 g_free(alias);
3119 ret = FALSE;
3120 } else {
3121 const gchar *message_id = sipmsg_find_header(msg, "Message-Id");
3122 if (message_id) {
3123 g_hash_table_insert(session->conf_unconfirmed_messages, g_strdup(message_id), g_strdup(message->body));
3124 SIPE_DEBUG_INFO("process_message_response: added message with id %s to conf_unconfirmed_messages(count=%d)",
3125 message_id, g_hash_table_size(session->conf_unconfirmed_messages));
3130 remove_unconfirmed_message(session, key);
3131 g_free(key);
3132 g_free(with);
3134 if (ret) sipe_im_process_queue(sipe_private, session);
3135 return ret;
3138 static gboolean
3139 process_message_timeout(struct sipe_core_private *sipe_private,
3140 struct sipmsg *msg,
3141 SIPE_UNUSED_PARAMETER struct transaction *trans)
3143 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
3144 struct sip_session *session = sipe_session_find_im(sipe_private, with);
3145 gchar *cseq;
3146 gchar *key;
3147 sipe_backend_buddy pbuddy;
3148 gchar *alias = NULL;
3150 if (!session) {
3151 SIPE_DEBUG_INFO_NOFORMAT("process_message_timeout: unable to find IM session");
3152 g_free(with);
3153 return TRUE;
3156 /* Remove timed-out message from unconfirmed list */
3157 cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
3158 key = get_unconfirmed_message_key(sipmsg_find_header(msg, "Call-ID"), atoi(cseq), with);
3159 g_free(cseq);
3160 remove_unconfirmed_message(session, key);
3161 g_free(key);
3163 if ((pbuddy = sipe_backend_buddy_find(SIPE_CORE_PUBLIC, with, NULL))) {
3164 alias = sipe_backend_buddy_get_alias(SIPE_CORE_PUBLIC,pbuddy);
3167 sipe_present_message_undelivered_err(sipe_private, session, -1, -1,
3168 alias ? alias : with,
3169 msg->body);
3171 g_free(alias);
3172 g_free(with);
3173 return TRUE;
3176 static void sipe_send_message(struct sipe_core_private *sipe_private,
3177 struct sip_dialog *dialog,
3178 const char *msg, const char *content_type)
3180 gchar *hdr;
3181 gchar *tmp;
3182 char *msgtext = NULL;
3183 const gchar *msgr = "";
3184 gchar *tmp2 = NULL;
3186 if (content_type == NULL)
3187 content_type = "text/plain";
3189 if (!g_str_has_prefix(content_type, "text/x-msmsgsinvite")) {
3190 char *msgformat;
3191 gchar *msgr_value;
3193 sipe_parse_html(msg, &msgformat, &msgtext);
3194 SIPE_DEBUG_INFO("sipe_send_message: msgformat=%s", msgformat);
3196 msgr_value = sipmsg_get_msgr_string(msgformat);
3197 g_free(msgformat);
3198 if (msgr_value) {
3199 msgr = tmp2 = g_strdup_printf(";msgr=%s", msgr_value);
3200 g_free(msgr_value);
3202 } else {
3203 msgtext = g_strdup(msg);
3206 tmp = get_contact(sipe_private);
3207 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8\r\n");
3208 //hdr = g_strdup("Content-Type: text/rtf\r\n");
3209 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8;msgr=WAAtAE0ATQBTAC....AoADQA\r\nSupported: timer\r\n");
3211 hdr = g_strdup_printf("Contact: %s\r\nContent-Type: %s; charset=UTF-8%s\r\n", tmp, content_type, msgr);
3212 g_free(tmp);
3213 g_free(tmp2);
3215 sip_transport_request_timeout(sipe_private,
3216 "MESSAGE",
3217 dialog->with,
3218 dialog->with,
3219 hdr,
3220 msgtext,
3221 dialog,
3222 process_message_response,
3224 process_message_timeout);
3225 g_free(msgtext);
3226 g_free(hdr);
3230 void
3231 sipe_im_process_queue (struct sipe_core_private *sipe_private,
3232 struct sip_session * session)
3234 GSList *entry2 = session->outgoing_message_queue;
3235 while (entry2) {
3236 struct queued_message *msg = entry2->data;
3238 /* for multiparty chat or conference */
3239 if (session->chat_session) {
3240 gchar *who = sip_uri_self(sipe_private);
3241 sipe_backend_chat_message(SIPE_CORE_PUBLIC,
3242 session->chat_session->backend,
3243 who,
3244 msg->body);
3245 g_free(who);
3248 SIPE_DIALOG_FOREACH {
3249 struct queued_message *message;
3251 if (dialog->outgoing_invite) continue; /* do not send messages as INVITE is not responded. */
3253 message = g_new0(struct queued_message,1);
3254 message->body = g_strdup(msg->body);
3255 if (msg->content_type != NULL)
3256 message->content_type = g_strdup(msg->content_type);
3258 insert_unconfirmed_message(session, dialog, dialog->with, message);
3260 sipe_send_message(sipe_private, dialog, msg->body, msg->content_type);
3261 } SIPE_DIALOG_FOREACH_END;
3263 entry2 = sipe_session_dequeue_message(session);
3267 static void
3268 sipe_refer_notify(struct sipe_core_private *sipe_private,
3269 struct sip_session *session,
3270 const gchar *who,
3271 int status,
3272 const gchar *desc)
3274 gchar *hdr;
3275 gchar *body;
3276 struct sip_dialog *dialog = sipe_dialog_find(session, who);
3278 hdr = g_strdup_printf(
3279 "Event: refer\r\n"
3280 "Subscription-State: %s\r\n"
3281 "Content-Type: message/sipfrag\r\n",
3282 status >= 200 ? "terminated" : "active");
3284 body = g_strdup_printf(
3285 "SIP/2.0 %d %s\r\n",
3286 status, desc);
3288 sip_transport_request(sipe_private,
3289 "NOTIFY",
3290 who,
3291 who,
3292 hdr,
3293 body,
3294 dialog,
3295 NULL);
3297 g_free(hdr);
3298 g_free(body);
3301 static gboolean
3302 process_invite_response(struct sipe_core_private *sipe_private,
3303 struct sipmsg *msg, struct transaction *trans)
3305 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
3306 struct sip_session *session;
3307 struct sip_dialog *dialog;
3308 gchar *cseq;
3309 gchar *key;
3310 struct queued_message *message;
3311 struct sipmsg *request_msg = trans->msg;
3313 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
3314 gchar *referred_by;
3316 session = sipe_session_find_chat_or_im(sipe_private, callid, with);
3317 if (!session) {
3318 SIPE_DEBUG_INFO_NOFORMAT("process_invite_response: unable to find IM session");
3319 g_free(with);
3320 return FALSE;
3323 dialog = sipe_dialog_find(session, with);
3324 if (!dialog) {
3325 SIPE_DEBUG_INFO_NOFORMAT("process_invite_response: session outgoing dialog is NULL");
3326 g_free(with);
3327 return FALSE;
3330 sipe_dialog_parse(dialog, msg, TRUE);
3332 cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
3333 key = get_unconfirmed_message_key(dialog->callid, atoi(cseq), NULL);
3334 g_free(cseq);
3335 message = g_hash_table_lookup(session->unconfirmed_messages, key);
3337 if (msg->response != 200) {
3338 sipe_backend_buddy pbuddy;
3339 gchar *alias = NULL;
3340 const char *warn_hdr = sipmsg_find_header(msg, "Warning");
3341 int warning = -1;
3343 SIPE_DEBUG_INFO_NOFORMAT("process_invite_response: INVITE response not 200");
3345 remove_unconfirmed_message(session, key);
3346 g_free(key);
3348 if (warn_hdr) {
3349 gchar **parts = g_strsplit(warn_hdr, " ", 2);
3350 if (parts[0]) {
3351 warning = atoi(parts[0]);
3353 g_strfreev(parts);
3356 /* cancel file transfer as rejected by server */
3357 if (msg->response == 606 && /* Not acceptable all. */
3358 warning == 309 && /* Message contents not allowed by policy */
3359 message && g_str_has_prefix(message->content_type, "text/x-msmsgsinvite"))
3361 GSList *parsed_body = sipe_ft_parse_msg_body(message->body);
3362 sipe_ft_incoming_cancel(dialog, parsed_body);
3363 sipe_utils_nameval_free(parsed_body);
3366 if ((pbuddy = sipe_backend_buddy_find(SIPE_CORE_PUBLIC, with, NULL))) {
3367 alias = sipe_backend_buddy_get_alias(SIPE_CORE_PUBLIC, pbuddy);
3370 if (message) {
3371 sipe_present_message_undelivered_err(sipe_private, session, msg->response, warning, alias ? alias : with, message->body);
3372 } else {
3373 gchar *tmp_msg = g_strdup_printf(_("Failed to invite %s"), alias);
3374 sipe_present_err(sipe_private, session, tmp_msg);
3375 g_free(tmp_msg);
3377 g_free(alias);
3379 sipe_dialog_remove(session, with);
3380 g_free(with);
3382 if (session->is_groupchat) {
3383 sipe_groupchat_invite_failed(sipe_private, session);
3384 /* session is no longer valid */
3387 return FALSE;
3390 dialog->cseq = 0;
3391 sip_transport_ack(sipe_private, dialog);
3392 dialog->outgoing_invite = NULL;
3393 dialog->is_established = TRUE;
3395 referred_by = parse_from(sipmsg_find_header(request_msg, "Referred-By"));
3396 if (referred_by) {
3397 sipe_refer_notify(sipe_private, session, referred_by, 200, "OK");
3398 g_free(referred_by);
3401 /* add user to chat if it is a multiparty session */
3402 if (session->chat_session &&
3403 (session->chat_session->type == SIPE_CHAT_TYPE_MULTIPARTY)) {
3404 sipe_backend_chat_add(session->chat_session->backend,
3405 with,
3406 TRUE);
3409 if (session->is_groupchat) {
3410 sipe_groupchat_invite_response(sipe_private, dialog);
3413 if(g_slist_find_custom(dialog->supported, "ms-text-format", (GCompareFunc)g_ascii_strcasecmp)) {
3414 SIPE_DEBUG_INFO_NOFORMAT("process_invite_response: remote system accepted message in INVITE");
3415 sipe_session_dequeue_message(session);
3418 sipe_im_process_queue(sipe_private, session);
3420 remove_unconfirmed_message(session, key);
3422 g_free(key);
3423 g_free(with);
3424 return TRUE;
3428 void
3429 sipe_invite(struct sipe_core_private *sipe_private,
3430 struct sip_session *session,
3431 const gchar *who,
3432 const gchar *msg_body,
3433 const gchar *msg_content_type,
3434 const gchar *referred_by,
3435 const gboolean is_triggered)
3437 gchar *hdr;
3438 gchar *to;
3439 gchar *contact;
3440 gchar *body;
3441 gchar *self;
3442 char *ms_text_format = NULL;
3443 char *ms_conversation_id = NULL;
3444 gchar *roster_manager;
3445 gchar *end_points;
3446 gchar *referred_by_str;
3447 gboolean is_multiparty =
3448 session->chat_session &&
3449 (session->chat_session->type == SIPE_CHAT_TYPE_MULTIPARTY);
3450 struct sip_dialog *dialog = sipe_dialog_find(session, who);
3452 if (dialog && dialog->is_established) {
3453 SIPE_DEBUG_INFO("session with %s already has a dialog open", who);
3454 return;
3457 if (!dialog) {
3458 dialog = sipe_dialog_add(session);
3459 dialog->callid = session->callid ? g_strdup(session->callid) : gencallid();
3460 dialog->with = g_strdup(who);
3463 if (!(dialog->ourtag)) {
3464 dialog->ourtag = gentag();
3467 to = sip_uri(who);
3469 if (msg_body) {
3470 char *msgtext = NULL;
3471 char *base64_msg;
3472 const gchar *msgr = "";
3473 struct queued_message *message;
3474 gchar *tmp = NULL;
3476 if (!g_str_has_prefix(msg_content_type, "text/x-msmsgsinvite")) {
3477 char *msgformat;
3478 gchar *msgr_value;
3480 sipe_parse_html(msg_body, &msgformat, &msgtext);
3481 SIPE_DEBUG_INFO("sipe_invite: msgformat=%s", msgformat);
3483 msgr_value = sipmsg_get_msgr_string(msgformat);
3484 g_free(msgformat);
3485 if (msgr_value) {
3486 msgr = tmp = g_strdup_printf(";msgr=%s", msgr_value);
3487 g_free(msgr_value);
3490 /* When Sipe reconnects after a crash, we are not able
3491 * to send messages to contacts with which we had open
3492 * conversations when the crash occured. Server sends
3493 * error response with reason="This client has an IM
3494 * session with the same conversation ID"
3496 * Setting random Ms-Conversation-ID prevents this problem
3497 * so we can continue the conversation. */
3498 ms_conversation_id = g_strdup_printf("Ms-Conversation-ID: %u\r\n",
3499 rand() % 1000000000);
3500 } else {
3501 msgtext = g_strdup(msg_body);
3504 base64_msg = g_base64_encode((guchar*) msgtext, strlen(msgtext));
3505 ms_text_format = g_strdup_printf(SIPE_INVITE_TEXT,
3506 msg_content_type ? msg_content_type : "text/plain",
3507 msgr,
3508 base64_msg);
3509 g_free(msgtext);
3510 g_free(tmp);
3511 g_free(base64_msg);
3513 message = g_new0(struct queued_message,1);
3514 message->body = g_strdup(msg_body);
3515 if (msg_content_type != NULL)
3516 message->content_type = g_strdup(msg_content_type);
3518 insert_unconfirmed_message(session, dialog, NULL, message);
3521 contact = get_contact(sipe_private);
3522 end_points = get_end_points(sipe_private, session);
3523 self = sip_uri_self(sipe_private);
3524 roster_manager = g_strdup_printf(
3525 "Roster-Manager: %s\r\n"
3526 "EndPoints: %s\r\n",
3527 self,
3528 end_points);
3529 referred_by_str = referred_by ?
3530 g_strdup_printf(
3531 "Referred-By: %s\r\n",
3532 referred_by)
3533 : g_strdup("");
3534 hdr = g_strdup_printf(
3535 "Supported: ms-sender\r\n"
3536 "%s"
3537 "%s"
3538 "%s"
3539 "%s"
3540 "Contact: %s\r\n%s"
3541 "%s"
3542 "Content-Type: application/sdp\r\n",
3543 is_multiparty && sipe_strcase_equal(session->chat_session->id, self) ? roster_manager : "",
3544 referred_by_str,
3545 is_triggered ? "TriggeredInvite: TRUE\r\n" : "",
3546 is_triggered || is_multiparty ? "Require: com.microsoft.rtc-multiparty\r\n" : "",
3547 contact,
3548 ms_text_format ? ms_text_format : "",
3549 ms_conversation_id ? ms_conversation_id : "");
3550 g_free(ms_text_format);
3551 g_free(ms_conversation_id);
3552 g_free(self);
3554 body = g_strdup_printf(
3555 "v=0\r\n"
3556 "o=- 0 0 IN IP4 %s\r\n"
3557 "s=session\r\n"
3558 "c=IN IP4 %s\r\n"
3559 "t=0 0\r\n"
3560 "m=%s %d sip null\r\n"
3561 "a=accept-types:" SDP_ACCEPT_TYPES "\r\n",
3562 sipe_backend_network_ip_address(),
3563 sipe_backend_network_ip_address(),
3564 SIPE_CORE_PRIVATE_FLAG_IS(OCS2007) ? "message" : "x-ms-message",
3565 sip_transport_port(sipe_private));
3567 dialog->outgoing_invite = sip_transport_request(sipe_private,
3568 "INVITE",
3571 hdr,
3572 body,
3573 dialog,
3574 process_invite_response);
3576 g_free(to);
3577 g_free(roster_manager);
3578 g_free(end_points);
3579 g_free(referred_by_str);
3580 g_free(body);
3581 g_free(hdr);
3582 g_free(contact);
3585 void
3586 sipe_convo_closed(PurpleConnection * gc, const char *who)
3588 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
3590 SIPE_DEBUG_INFO("conversation with %s closed", who);
3591 sipe_session_close(sipe_private,
3592 sipe_session_find_im(sipe_private, who));
3595 int sipe_im_send(PurpleConnection *gc, const char *who, const char *what,
3596 SIPE_UNUSED_PARAMETER PurpleMessageFlags flags)
3598 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
3599 struct sip_session *session;
3600 struct sip_dialog *dialog;
3601 gchar *uri = sip_uri(who);
3603 SIPE_DEBUG_INFO("sipe_im_send what='%s'", what);
3605 session = sipe_session_find_or_add_im(sipe_private, uri);
3606 dialog = sipe_dialog_find(session, uri);
3608 // Queue the message
3609 sipe_session_enqueue_message(session, what, NULL);
3611 if (dialog && !dialog->outgoing_invite) {
3612 sipe_im_process_queue(sipe_private, session);
3613 } else if (!dialog || !dialog->outgoing_invite) {
3614 // Need to send the INVITE to get the outgoing dialog setup
3615 sipe_invite(sipe_private, session, uri, what, NULL, NULL, FALSE);
3618 g_free(uri);
3619 return 1;
3623 * Returns 2005-style activity and Availability.
3625 * @param status Sipe statis id.
3627 static void
3628 sipe_get_act_avail_by_status_2005(const char *status,
3629 int *activity,
3630 int *availability)
3632 int avail = 300; /* online */
3633 int act = 400; /* Available */
3635 if (sipe_strequal(status, SIPE_STATUS_ID_AWAY)) {
3636 act = 100;
3637 //} else if (sipe_strequal(status, SIPE_STATUS_ID_LUNCH)) {
3638 // act = 150;
3639 } else if (sipe_strequal(status, SIPE_STATUS_ID_BRB)) {
3640 act = 300;
3641 } else if (sipe_strequal(status, SIPE_STATUS_ID_AVAILABLE)) {
3642 act = 400;
3643 //} else if (sipe_strequal(status, SIPE_STATUS_ID_ON_PHONE)) {
3644 // act = 500;
3645 } else if (sipe_strequal(status, SIPE_STATUS_ID_BUSY) ||
3646 sipe_strequal(status, SIPE_STATUS_ID_DND)) {
3647 act = 600;
3648 } else if (sipe_strequal(status, SIPE_STATUS_ID_INVISIBLE) ||
3649 sipe_strequal(status, SIPE_STATUS_ID_OFFLINE)) {
3650 avail = 0; /* offline */
3651 act = 100;
3652 } else {
3653 act = 400; /* Available */
3656 if (activity) *activity = act;
3657 if (availability) *availability = avail;
3661 * [MS-SIP] 2.2.1
3663 * @param activity 2005 aggregated activity. Ex.: 600
3664 * @param availablity 2005 aggregated availablity. Ex.: 300
3666 static const char *
3667 sipe_get_status_by_act_avail_2005(const int activity,
3668 const int availablity,
3669 char **activity_desc)
3671 const char *status_id = NULL;
3672 const char *act = NULL;
3674 if (activity < 150) {
3675 status_id = SIPE_STATUS_ID_AWAY;
3676 } else if (activity < 200) {
3677 //status_id = SIPE_STATUS_ID_LUNCH;
3678 status_id = SIPE_STATUS_ID_AWAY;
3679 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_LUNCH);
3680 } else if (activity < 300) {
3681 //status_id = SIPE_STATUS_ID_IDLE;
3682 status_id = SIPE_STATUS_ID_AWAY;
3683 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_INACTIVE);
3684 } else if (activity < 400) {
3685 status_id = SIPE_STATUS_ID_BRB;
3686 } else if (activity < 500) {
3687 status_id = SIPE_STATUS_ID_AVAILABLE;
3688 } else if (activity < 600) {
3689 //status_id = SIPE_STATUS_ID_ON_PHONE;
3690 status_id = SIPE_STATUS_ID_BUSY;
3691 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_ON_PHONE);
3692 } else if (activity < 700) {
3693 status_id = SIPE_STATUS_ID_BUSY;
3694 } else if (activity < 800) {
3695 status_id = SIPE_STATUS_ID_AWAY;
3696 } else {
3697 status_id = SIPE_STATUS_ID_AVAILABLE;
3700 if (availablity < 100)
3701 status_id = SIPE_STATUS_ID_OFFLINE;
3703 if (activity_desc && act) {
3704 g_free(*activity_desc);
3705 *activity_desc = g_strdup(act);
3708 return status_id;
3712 * [MS-PRES] Table 3: Conversion of legacyInterop elements and attributes to MSRTC elements and attributes.
3714 static const char*
3715 sipe_get_status_by_availability(int avail,
3716 char** activity_desc)
3718 const char *status;
3719 const char *act = NULL;
3721 if (avail < 3000) {
3722 status = SIPE_STATUS_ID_OFFLINE;
3723 } else if (avail < 4500) {
3724 status = SIPE_STATUS_ID_AVAILABLE;
3725 } else if (avail < 6000) {
3726 //status = SIPE_STATUS_ID_IDLE;
3727 status = SIPE_STATUS_ID_AVAILABLE;
3728 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_INACTIVE);
3729 } else if (avail < 7500) {
3730 status = SIPE_STATUS_ID_BUSY;
3731 } else if (avail < 9000) {
3732 //status = SIPE_STATUS_ID_BUSYIDLE;
3733 status = SIPE_STATUS_ID_BUSY;
3734 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_BUSYIDLE);
3735 } else if (avail < 12000) {
3736 status = SIPE_STATUS_ID_DND;
3737 } else if (avail < 15000) {
3738 status = SIPE_STATUS_ID_BRB;
3739 } else if (avail < 18000) {
3740 status = SIPE_STATUS_ID_AWAY;
3741 } else {
3742 status = SIPE_STATUS_ID_OFFLINE;
3745 if (activity_desc && act) {
3746 g_free(*activity_desc);
3747 *activity_desc = g_strdup(act);
3750 return status;
3754 * Returns 2007-style availability value
3756 * @param sipe_status_id (in)
3757 * @param activity_token (out) Must be g_free()'d after use if consumed.
3759 static int
3760 sipe_get_availability_by_status(const char* sipe_status_id, char** activity_token)
3762 int availability;
3763 sipe_activity activity = SIPE_ACTIVITY_UNSET;
3765 if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_AWAY)) {
3766 availability = 15500;
3767 if (!activity_token || !(*activity_token)) {
3768 activity = SIPE_ACTIVITY_AWAY;
3770 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_BRB)) {
3771 availability = 12500;
3772 activity = SIPE_ACTIVITY_BRB;
3773 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_DND)) {
3774 availability = 9500;
3775 activity = SIPE_ACTIVITY_DND;
3776 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_BUSY)) {
3777 availability = 6500;
3778 if (!activity_token || !(*activity_token)) {
3779 activity = SIPE_ACTIVITY_BUSY;
3781 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_AVAILABLE)) {
3782 availability = 3500;
3783 activity = SIPE_ACTIVITY_ONLINE;
3784 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_UNKNOWN)) {
3785 availability = 0;
3786 } else {
3787 // Offline or invisible
3788 availability = 18500;
3789 activity = SIPE_ACTIVITY_OFFLINE;
3792 if (activity_token) {
3793 *activity_token = g_strdup(sipe_activity_map[activity].token);
3795 return availability;
3798 static void process_incoming_notify_rlmi(struct sipe_core_private *sipe_private,
3799 const gchar *data, unsigned len)
3801 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
3802 const char *uri;
3803 sipe_xml *xn_categories;
3804 const sipe_xml *xn_category;
3805 const char *status = NULL;
3806 gboolean do_update_status = FALSE;
3807 gboolean has_note_cleaned = FALSE;
3808 gboolean has_free_busy_cleaned = FALSE;
3810 xn_categories = sipe_xml_parse(data, len);
3811 uri = sipe_xml_attribute(xn_categories, "uri"); /* with 'sip:' prefix */
3813 for (xn_category = sipe_xml_child(xn_categories, "category");
3814 xn_category ;
3815 xn_category = sipe_xml_twin(xn_category) )
3817 const sipe_xml *xn_node;
3818 const char *tmp;
3819 const char *attrVar = sipe_xml_attribute(xn_category, "name");
3820 time_t publish_time = (tmp = sipe_xml_attribute(xn_category, "publishTime")) ?
3821 sipe_utils_str_to_time(tmp) : 0;
3823 /* contactCard */
3824 if (sipe_strequal(attrVar, "contactCard"))
3826 const sipe_xml *card = sipe_xml_child(xn_category, "contactCard");
3828 if (card) {
3829 const sipe_xml *node;
3830 /* identity - Display Name and email */
3831 node = sipe_xml_child(card, "identity");
3832 if (node) {
3833 char* display_name = sipe_xml_data(
3834 sipe_xml_child(node, "name/displayName"));
3835 char* email = sipe_xml_data(
3836 sipe_xml_child(node, "email"));
3838 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_DISPLAY_NAME, display_name);
3839 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_EMAIL, email);
3841 g_free(display_name);
3842 g_free(email);
3844 /* company */
3845 node = sipe_xml_child(card, "company");
3846 if (node) {
3847 char* company = sipe_xml_data(node);
3848 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_COMPANY, company);
3849 g_free(company);
3851 /* department */
3852 node = sipe_xml_child(card, "department");
3853 if (node) {
3854 char* department = sipe_xml_data(node);
3855 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_DEPARTMENT, department);
3856 g_free(department);
3858 /* title */
3859 node = sipe_xml_child(card, "title");
3860 if (node) {
3861 char* title = sipe_xml_data(node);
3862 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_JOB_TITLE, title);
3863 g_free(title);
3865 /* office */
3866 node = sipe_xml_child(card, "office");
3867 if (node) {
3868 char* office = sipe_xml_data(node);
3869 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_OFFICE, office);
3870 g_free(office);
3872 /* site (url) */
3873 node = sipe_xml_child(card, "url");
3874 if (node) {
3875 char* site = sipe_xml_data(node);
3876 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_SITE, site);
3877 g_free(site);
3879 /* phone */
3880 for (node = sipe_xml_child(card, "phone");
3881 node;
3882 node = sipe_xml_twin(node))
3884 const char *phone_type = sipe_xml_attribute(node, "type");
3885 char* phone = sipe_xml_data(sipe_xml_child(node, "uri"));
3886 char* phone_display_string = sipe_xml_data(sipe_xml_child(node, "displayString"));
3888 sipe_update_user_phone(sipe_private, uri, phone_type, phone, phone_display_string);
3890 g_free(phone);
3891 g_free(phone_display_string);
3893 /* address */
3894 for (node = sipe_xml_child(card, "address");
3895 node;
3896 node = sipe_xml_twin(node))
3898 if (sipe_strequal(sipe_xml_attribute(node, "type"), "work")) {
3899 char* street = sipe_xml_data(sipe_xml_child(node, "street"));
3900 char* city = sipe_xml_data(sipe_xml_child(node, "city"));
3901 char* state = sipe_xml_data(sipe_xml_child(node, "state"));
3902 char* zipcode = sipe_xml_data(sipe_xml_child(node, "zipcode"));
3903 char* country_code = sipe_xml_data(sipe_xml_child(node, "countryCode"));
3905 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_STREET, street);
3906 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_CITY, city);
3907 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_STATE, state);
3908 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_ZIPCODE, zipcode);
3909 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_COUNTRY, country_code);
3911 g_free(street);
3912 g_free(city);
3913 g_free(state);
3914 g_free(zipcode);
3915 g_free(country_code);
3917 break;
3922 /* note */
3923 else if (sipe_strequal(attrVar, "note"))
3925 if (uri) {
3926 struct sipe_buddy *sbuddy = g_hash_table_lookup(sipe_private->buddies, uri);
3928 if (!has_note_cleaned) {
3929 has_note_cleaned = TRUE;
3931 g_free(sbuddy->note);
3932 sbuddy->note = NULL;
3933 sbuddy->is_oof_note = FALSE;
3934 sbuddy->note_since = publish_time;
3936 do_update_status = TRUE;
3938 if (sbuddy && (publish_time >= sbuddy->note_since)) {
3939 /* clean up in case no 'note' element is supplied
3940 * which indicate note removal in client
3942 g_free(sbuddy->note);
3943 sbuddy->note = NULL;
3944 sbuddy->is_oof_note = FALSE;
3945 sbuddy->note_since = publish_time;
3947 xn_node = sipe_xml_child(xn_category, "note/body");
3948 if (xn_node) {
3949 char *tmp;
3950 sbuddy->note = g_markup_escape_text((tmp = sipe_xml_data(xn_node)), -1);
3951 g_free(tmp);
3952 sbuddy->is_oof_note = sipe_strequal(sipe_xml_attribute(xn_node, "type"), "OOF");
3953 sbuddy->note_since = publish_time;
3955 SIPE_DEBUG_INFO("process_incoming_notify_rlmi: uri(%s), note(%s)",
3956 uri, sbuddy->note ? sbuddy->note : "");
3958 /* to trigger UI refresh in case no status info is supplied in this update */
3959 do_update_status = TRUE;
3963 /* state */
3964 else if(sipe_strequal(attrVar, "state"))
3966 char *tmp;
3967 int availability;
3968 const sipe_xml *xn_availability;
3969 const sipe_xml *xn_activity;
3970 const sipe_xml *xn_meeting_subject;
3971 const sipe_xml *xn_meeting_location;
3972 struct sipe_buddy *sbuddy = uri ? g_hash_table_lookup(sipe_private->buddies, uri) : NULL;
3974 xn_node = sipe_xml_child(xn_category, "state");
3975 if (!xn_node) continue;
3976 xn_availability = sipe_xml_child(xn_node, "availability");
3977 if (!xn_availability) continue;
3978 xn_activity = sipe_xml_child(xn_node, "activity");
3979 xn_meeting_subject = sipe_xml_child(xn_node, "meetingSubject");
3980 xn_meeting_location = sipe_xml_child(xn_node, "meetingLocation");
3982 tmp = sipe_xml_data(xn_availability);
3983 availability = atoi(tmp);
3984 g_free(tmp);
3986 /* activity, meeting_subject, meeting_location */
3987 if (sbuddy) {
3988 char *tmp = NULL;
3990 /* activity */
3991 g_free(sbuddy->activity);
3992 sbuddy->activity = NULL;
3993 if (xn_activity) {
3994 const char *token = sipe_xml_attribute(xn_activity, "token");
3995 const sipe_xml *xn_custom = sipe_xml_child(xn_activity, "custom");
3997 /* from token */
3998 if (!is_empty(token)) {
3999 sbuddy->activity = g_strdup(sipe_get_activity_desc_by_token(token));
4001 /* from custom element */
4002 if (xn_custom) {
4003 char *custom = sipe_xml_data(xn_custom);
4005 if (!is_empty(custom)) {
4006 sbuddy->activity = custom;
4007 custom = NULL;
4009 g_free(custom);
4012 /* meeting_subject */
4013 g_free(sbuddy->meeting_subject);
4014 sbuddy->meeting_subject = NULL;
4015 if (xn_meeting_subject) {
4016 char *meeting_subject = sipe_xml_data(xn_meeting_subject);
4018 if (!is_empty(meeting_subject)) {
4019 sbuddy->meeting_subject = meeting_subject;
4020 meeting_subject = NULL;
4022 g_free(meeting_subject);
4024 /* meeting_location */
4025 g_free(sbuddy->meeting_location);
4026 sbuddy->meeting_location = NULL;
4027 if (xn_meeting_location) {
4028 char *meeting_location = sipe_xml_data(xn_meeting_location);
4030 if (!is_empty(meeting_location)) {
4031 sbuddy->meeting_location = meeting_location;
4032 meeting_location = NULL;
4034 g_free(meeting_location);
4037 status = sipe_get_status_by_availability(availability, &tmp);
4038 if (sbuddy->activity && tmp) {
4039 char *tmp2 = sbuddy->activity;
4041 sbuddy->activity = g_strdup_printf("%s, %s", sbuddy->activity, tmp);
4042 g_free(tmp);
4043 g_free(tmp2);
4044 } else if (tmp) {
4045 sbuddy->activity = tmp;
4049 do_update_status = TRUE;
4051 /* calendarData */
4052 else if(sipe_strequal(attrVar, "calendarData"))
4054 struct sipe_buddy *sbuddy = uri ? g_hash_table_lookup(sipe_private->buddies, uri) : NULL;
4055 const sipe_xml *xn_free_busy = sipe_xml_child(xn_category, "calendarData/freeBusy");
4056 const sipe_xml *xn_working_hours = sipe_xml_child(xn_category, "calendarData/WorkingHours");
4058 if (sbuddy && xn_free_busy) {
4059 if (!has_free_busy_cleaned) {
4060 has_free_busy_cleaned = TRUE;
4062 g_free(sbuddy->cal_start_time);
4063 sbuddy->cal_start_time = NULL;
4065 g_free(sbuddy->cal_free_busy_base64);
4066 sbuddy->cal_free_busy_base64 = NULL;
4068 g_free(sbuddy->cal_free_busy);
4069 sbuddy->cal_free_busy = NULL;
4071 sbuddy->cal_free_busy_published = publish_time;
4074 if (publish_time >= sbuddy->cal_free_busy_published) {
4075 g_free(sbuddy->cal_start_time);
4076 sbuddy->cal_start_time = g_strdup(sipe_xml_attribute(xn_free_busy, "startTime"));
4078 sbuddy->cal_granularity = sipe_strcase_equal(sipe_xml_attribute(xn_free_busy, "granularity"), "PT15M") ?
4079 15 : 0;
4081 g_free(sbuddy->cal_free_busy_base64);
4082 sbuddy->cal_free_busy_base64 = sipe_xml_data(xn_free_busy);
4084 g_free(sbuddy->cal_free_busy);
4085 sbuddy->cal_free_busy = NULL;
4087 sbuddy->cal_free_busy_published = publish_time;
4089 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);
4093 if (sbuddy && xn_working_hours) {
4094 sipe_cal_parse_working_hours(xn_working_hours, sbuddy);
4099 if (do_update_status) {
4100 if (!status) { /* no status category in this update, using contact's current status */
4101 PurpleBuddy *pbuddy = purple_find_buddy((PurpleAccount *)sip->account, uri);
4102 const PurplePresence *presence = purple_buddy_get_presence(pbuddy);
4103 const PurpleStatus *pstatus = purple_presence_get_active_status(presence);
4104 status = purple_status_get_id(pstatus);
4107 SIPE_DEBUG_INFO("process_incoming_notify_rlmi: %s", status);
4108 sipe_core_buddy_got_status(SIPE_CORE_PUBLIC, uri, status);
4111 sipe_xml_free(xn_categories);
4114 static void sipe_subscribe_poolfqdn_resource_uri(const char *host,
4115 GSList *server,
4116 struct sipe_core_private *sipe_private)
4118 struct presence_batched_routed *payload = g_malloc(sizeof(struct presence_batched_routed));
4119 SIPE_DEBUG_INFO("process_incoming_notify_rlmi_resub: pool(%s)", host);
4120 payload->host = g_strdup(host);
4121 payload->buddies = server;
4122 sipe_subscribe_presence_batched_routed(sipe_private,
4123 payload);
4124 sipe_subscribe_presence_batched_routed_free(payload);
4127 static void process_incoming_notify_rlmi_resub(struct sipe_core_private *sipe_private,
4128 const gchar *data, unsigned len)
4130 sipe_xml *xn_list;
4131 const sipe_xml *xn_resource;
4132 GHashTable *servers = g_hash_table_new_full(g_str_hash, g_str_equal,
4133 g_free, NULL);
4134 GSList *server;
4135 gchar *host;
4137 xn_list = sipe_xml_parse(data, len);
4139 for (xn_resource = sipe_xml_child(xn_list, "resource");
4140 xn_resource;
4141 xn_resource = sipe_xml_twin(xn_resource) )
4143 const char *uri, *state;
4144 const sipe_xml *xn_instance;
4146 xn_instance = sipe_xml_child(xn_resource, "instance");
4147 if (!xn_instance) continue;
4149 uri = sipe_xml_attribute(xn_resource, "uri");
4150 state = sipe_xml_attribute(xn_instance, "state");
4151 SIPE_DEBUG_INFO("process_incoming_notify_rlmi_resub: uri(%s),state(%s)", uri, state);
4153 if (strstr(state, "resubscribe")) {
4154 const char *poolFqdn = sipe_xml_attribute(xn_instance, "poolFqdn");
4156 if (poolFqdn) { //[MS-PRES] Section 3.4.5.1.3 Processing Details
4157 gchar *user = g_strdup(uri);
4158 host = g_strdup(poolFqdn);
4159 server = g_hash_table_lookup(servers, host);
4160 server = g_slist_append(server, user);
4161 g_hash_table_insert(servers, host, server);
4162 } else {
4163 sipe_subscribe_presence_single(sipe_private,
4164 (void *) uri);
4169 /* Send out any deferred poolFqdn subscriptions */
4170 g_hash_table_foreach(servers, (GHFunc) sipe_subscribe_poolfqdn_resource_uri, sipe_private);
4171 g_hash_table_destroy(servers);
4173 sipe_xml_free(xn_list);
4176 static void process_incoming_notify_pidf(struct sipe_core_private *sipe_private,
4177 const gchar *data, unsigned len)
4179 gchar *uri;
4180 gchar *getbasic;
4181 gchar *activity = NULL;
4182 sipe_xml *pidf;
4183 const sipe_xml *basicstatus = NULL, *tuple, *status;
4184 gboolean isonline = FALSE;
4185 const sipe_xml *display_name_node;
4187 pidf = sipe_xml_parse(data, len);
4188 if (!pidf) {
4189 SIPE_DEBUG_INFO("process_incoming_notify_pidf: no parseable pidf:%s", data);
4190 return;
4193 if ((tuple = sipe_xml_child(pidf, "tuple")))
4195 if ((status = sipe_xml_child(tuple, "status"))) {
4196 basicstatus = sipe_xml_child(status, "basic");
4200 if (!basicstatus) {
4201 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_notify_pidf: no basic found");
4202 sipe_xml_free(pidf);
4203 return;
4206 getbasic = sipe_xml_data(basicstatus);
4207 if (!getbasic) {
4208 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_notify_pidf: no basic data found");
4209 sipe_xml_free(pidf);
4210 return;
4213 SIPE_DEBUG_INFO("process_incoming_notify_pidf: basic-status(%s)", getbasic);
4214 if (strstr(getbasic, "open")) {
4215 isonline = TRUE;
4217 g_free(getbasic);
4219 uri = sip_uri(sipe_xml_attribute(pidf, "entity")); /* with 'sip:' prefix */ /* AOL comes without the prefix */
4221 display_name_node = sipe_xml_child(pidf, "display-name");
4222 if (display_name_node) {
4223 char * display_name = sipe_xml_data(display_name_node);
4225 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_DISPLAY_NAME, display_name);
4226 g_free(display_name);
4229 if ((tuple = sipe_xml_child(pidf, "tuple"))) {
4230 if ((status = sipe_xml_child(tuple, "status"))) {
4231 if ((basicstatus = sipe_xml_child(status, "activities"))) {
4232 if ((basicstatus = sipe_xml_child(basicstatus, "activity"))) {
4233 activity = sipe_xml_data(basicstatus);
4234 SIPE_DEBUG_INFO("process_incoming_notify_pidf: activity(%s)", activity);
4240 if (isonline) {
4241 const gchar * status_id = NULL;
4242 if (activity) {
4243 if (sipe_strequal(activity, sipe_activity_map[SIPE_ACTIVITY_BUSY].token)) {
4244 status_id = SIPE_STATUS_ID_BUSY;
4245 } else if (sipe_strequal(activity, sipe_activity_map[SIPE_ACTIVITY_AWAY].token)) {
4246 status_id = SIPE_STATUS_ID_AWAY;
4250 if (!status_id) {
4251 status_id = SIPE_STATUS_ID_AVAILABLE;
4254 SIPE_DEBUG_INFO("process_incoming_notify_pidf: status_id(%s)", status_id);
4255 sipe_core_buddy_got_status(SIPE_CORE_PUBLIC, uri, status_id);
4256 } else {
4257 sipe_core_buddy_got_status(SIPE_CORE_PUBLIC, uri, SIPE_STATUS_ID_OFFLINE);
4260 g_free(activity);
4261 g_free(uri);
4262 sipe_xml_free(pidf);
4265 /** 2005 */
4266 static void
4267 sipe_user_info_has_updated(struct sipe_core_private *sipe_private,
4268 const sipe_xml *xn_userinfo)
4270 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
4271 const sipe_xml *xn_states;
4273 g_free(sip->user_states);
4274 sip->user_states = NULL;
4275 if ((xn_states = sipe_xml_child(xn_userinfo, "states")) != NULL) {
4276 gchar *orig = sip->user_states = sipe_xml_stringify(xn_states);
4278 /* this is a hack-around to remove added newline after inner element,
4279 * state in this case, where it shouldn't be.
4280 * After several use of sipe_xml_stringify, amount of added newlines
4281 * grows significantly.
4283 if (orig) {
4284 gchar c, *stripped = orig;
4285 while ((c = *orig++)) {
4286 if ((c != '\n') /* && (c != '\r') */) {
4287 *stripped++ = c;
4290 *stripped = '\0';
4294 /* Publish initial state if not yet.
4295 * Assuming this happens on initial responce to self subscription
4296 * so we've already updated our UserInfo.
4298 if (!sip->initial_state_published) {
4299 send_presence_soap(sipe_private, FALSE);
4300 /* dalayed run */
4301 sipe_schedule_seconds(sipe_private,
4302 "<+update-calendar>",
4303 NULL,
4304 UPDATE_CALENDAR_DELAY,
4305 (sipe_schedule_action) sipe_core_update_calendar,
4306 NULL);
4310 static void process_incoming_notify_msrtc(struct sipe_core_private *sipe_private,
4311 const gchar *data, unsigned len)
4313 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
4314 char *activity = NULL;
4315 const char *epid;
4316 const char *status_id = NULL;
4317 const char *name;
4318 char *uri;
4319 char *self_uri = sip_uri_self(sipe_private);
4320 int avl;
4321 int act;
4322 const char *device_name = NULL;
4323 const char *cal_start_time = NULL;
4324 const char *cal_granularity = NULL;
4325 char *cal_free_busy_base64 = NULL;
4326 struct sipe_buddy *sbuddy;
4327 const sipe_xml *node;
4328 sipe_xml *xn_presentity;
4329 const sipe_xml *xn_availability;
4330 const sipe_xml *xn_activity;
4331 const sipe_xml *xn_display_name;
4332 const sipe_xml *xn_email;
4333 const sipe_xml *xn_phone_number;
4334 const sipe_xml *xn_userinfo;
4335 const sipe_xml *xn_note;
4336 const sipe_xml *xn_oof;
4337 const sipe_xml *xn_state;
4338 const sipe_xml *xn_contact;
4339 char *note;
4340 char *free_activity;
4341 int user_avail;
4342 const char *user_avail_nil;
4343 int res_avail;
4344 time_t user_avail_since = 0;
4345 time_t activity_since = 0;
4347 /* fix for Reuters environment on Linux */
4348 if (data && strstr(data, "encoding=\"utf-16\"")) {
4349 char *tmp_data;
4350 tmp_data = replace(data, "encoding=\"utf-16\"", "encoding=\"utf-8\"");
4351 xn_presentity = sipe_xml_parse(tmp_data, strlen(tmp_data));
4352 g_free(tmp_data);
4353 } else {
4354 xn_presentity = sipe_xml_parse(data, len);
4357 xn_availability = sipe_xml_child(xn_presentity, "availability");
4358 xn_activity = sipe_xml_child(xn_presentity, "activity");
4359 xn_display_name = sipe_xml_child(xn_presentity, "displayName");
4360 xn_email = sipe_xml_child(xn_presentity, "email");
4361 xn_phone_number = sipe_xml_child(xn_presentity, "phoneNumber");
4362 xn_userinfo = sipe_xml_child(xn_presentity, "userInfo");
4363 xn_oof = xn_userinfo ? sipe_xml_child(xn_userinfo, "oof") : NULL;
4364 xn_state = xn_userinfo ? sipe_xml_child(xn_userinfo, "states/state"): NULL;
4365 user_avail = xn_state ? sipe_xml_int_attribute(xn_state, "avail", 0) : 0;
4366 user_avail_since = xn_state ? sipe_utils_str_to_time(sipe_xml_attribute(xn_state, "since")) : 0;
4367 user_avail_nil = xn_state ? sipe_xml_attribute(xn_state, "nil") : NULL;
4368 xn_contact = xn_userinfo ? sipe_xml_child(xn_userinfo, "contact") : NULL;
4369 xn_note = xn_userinfo ? sipe_xml_child(xn_userinfo, "note") : NULL;
4370 note = xn_note ? sipe_xml_data(xn_note) : NULL;
4372 if (sipe_strequal(user_avail_nil, "true")) { /* null-ed */
4373 user_avail = 0;
4374 user_avail_since = 0;
4377 free_activity = NULL;
4379 name = sipe_xml_attribute(xn_presentity, "uri"); /* without 'sip:' prefix */
4380 uri = sip_uri_from_name(name);
4381 avl = sipe_xml_int_attribute(xn_availability, "aggregate", 0);
4382 epid = sipe_xml_attribute(xn_availability, "epid");
4383 act = sipe_xml_int_attribute(xn_activity, "aggregate", 0);
4385 status_id = sipe_get_status_by_act_avail_2005(act, avl, &activity);
4386 res_avail = sipe_get_availability_by_status(status_id, NULL);
4387 if (user_avail > res_avail) {
4388 res_avail = user_avail;
4389 status_id = sipe_get_status_by_availability(user_avail, NULL);
4392 if (xn_display_name) {
4393 char *display_name = g_strdup(sipe_xml_attribute(xn_display_name, "displayName"));
4394 char *email = xn_email ? g_strdup(sipe_xml_attribute(xn_email, "email")) : NULL;
4395 char *phone_label = xn_phone_number ? g_strdup(sipe_xml_attribute(xn_phone_number, "label")) : NULL;
4396 char *phone_number = xn_phone_number ? g_strdup(sipe_xml_attribute(xn_phone_number, "number")) : NULL;
4397 char *tel_uri = sip_to_tel_uri(phone_number);
4399 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_DISPLAY_NAME, display_name);
4400 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_EMAIL, email);
4401 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_WORK_PHONE, tel_uri);
4402 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_WORK_PHONE_DISPLAY, !is_empty(phone_label) ? phone_label : phone_number);
4404 g_free(tel_uri);
4405 g_free(phone_label);
4406 g_free(phone_number);
4407 g_free(email);
4408 g_free(display_name);
4411 if (xn_contact) {
4412 /* tel */
4413 for (node = sipe_xml_child(xn_contact, "tel"); node; node = sipe_xml_twin(node))
4415 /* Ex.: <tel type="work">tel:+3222220000</tel> */
4416 const char *phone_type = sipe_xml_attribute(node, "type");
4417 char* phone = sipe_xml_data(node);
4419 sipe_update_user_phone(sipe_private, uri, phone_type, phone, NULL);
4421 g_free(phone);
4425 /* devicePresence */
4426 for (node = sipe_xml_child(xn_presentity, "devices/devicePresence"); node; node = sipe_xml_twin(node)) {
4427 const sipe_xml *xn_device_name;
4428 const sipe_xml *xn_calendar_info;
4429 const sipe_xml *xn_state;
4430 char *state;
4432 /* deviceName */
4433 if (sipe_strequal(sipe_xml_attribute(node, "epid"), epid)) {
4434 xn_device_name = sipe_xml_child(node, "deviceName");
4435 device_name = xn_device_name ? sipe_xml_attribute(xn_device_name, "name") : NULL;
4438 /* calendarInfo */
4439 xn_calendar_info = sipe_xml_child(node, "calendarInfo");
4440 if (xn_calendar_info) {
4441 const char *cal_start_time_tmp = sipe_xml_attribute(xn_calendar_info, "startTime");
4443 if (cal_start_time) {
4444 time_t cal_start_time_t = sipe_utils_str_to_time(cal_start_time);
4445 time_t cal_start_time_t_tmp = sipe_utils_str_to_time(cal_start_time_tmp);
4447 if (cal_start_time_t_tmp > cal_start_time_t) {
4448 cal_start_time = cal_start_time_tmp;
4449 cal_granularity = sipe_xml_attribute(xn_calendar_info, "granularity");
4450 g_free(cal_free_busy_base64);
4451 cal_free_busy_base64 = sipe_xml_data(xn_calendar_info);
4453 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);
4455 } else {
4456 cal_start_time = cal_start_time_tmp;
4457 cal_granularity = sipe_xml_attribute(xn_calendar_info, "granularity");
4458 g_free(cal_free_busy_base64);
4459 cal_free_busy_base64 = sipe_xml_data(xn_calendar_info);
4461 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);
4465 /* state */
4466 xn_state = sipe_xml_child(node, "states/state");
4467 if (xn_state) {
4468 int dev_avail = sipe_xml_int_attribute(xn_state, "avail", 0);
4469 time_t dev_avail_since = sipe_utils_str_to_time(sipe_xml_attribute(xn_state, "since"));
4471 state = sipe_xml_data(xn_state);
4472 if (dev_avail_since > user_avail_since &&
4473 dev_avail >= res_avail)
4475 res_avail = dev_avail;
4476 if (!is_empty(state))
4478 if (sipe_strequal(state, sipe_activity_map[SIPE_ACTIVITY_ON_PHONE].token)) {
4479 g_free(activity);
4480 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_ON_PHONE));
4481 } else if (sipe_strequal(state, "presenting")) {
4482 g_free(activity);
4483 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_IN_CONF));
4484 } else {
4485 activity = state;
4486 state = NULL;
4488 activity_since = dev_avail_since;
4490 status_id = sipe_get_status_by_availability(res_avail, &activity);
4492 g_free(state);
4496 /* oof */
4497 if (xn_oof && res_avail >= 15000) { /* 12000 in 2007 */
4498 g_free(activity);
4499 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_OOF));
4500 activity_since = 0;
4503 sbuddy = g_hash_table_lookup(sipe_private->buddies, uri);
4504 if (sbuddy)
4506 g_free(sbuddy->activity);
4507 sbuddy->activity = activity;
4508 activity = NULL;
4510 sbuddy->activity_since = activity_since;
4512 sbuddy->user_avail = user_avail;
4513 sbuddy->user_avail_since = user_avail_since;
4515 g_free(sbuddy->note);
4516 sbuddy->note = NULL;
4517 if (!is_empty(note)) { sbuddy->note = g_markup_escape_text(note, -1); }
4519 sbuddy->is_oof_note = (xn_oof != NULL);
4521 g_free(sbuddy->device_name);
4522 sbuddy->device_name = NULL;
4523 if (!is_empty(device_name)) { sbuddy->device_name = g_strdup(device_name); }
4525 if (!is_empty(cal_free_busy_base64)) {
4526 g_free(sbuddy->cal_start_time);
4527 sbuddy->cal_start_time = g_strdup(cal_start_time);
4529 sbuddy->cal_granularity = sipe_strcase_equal(cal_granularity, "PT15M") ? 15 : 0;
4531 g_free(sbuddy->cal_free_busy_base64);
4532 sbuddy->cal_free_busy_base64 = cal_free_busy_base64;
4533 cal_free_busy_base64 = NULL;
4535 g_free(sbuddy->cal_free_busy);
4536 sbuddy->cal_free_busy = NULL;
4539 sbuddy->last_non_cal_status_id = status_id;
4540 g_free(sbuddy->last_non_cal_activity);
4541 sbuddy->last_non_cal_activity = g_strdup(sbuddy->activity);
4543 if (sipe_strcase_equal(sbuddy->name, self_uri)) {
4544 if (!sipe_strequal(sbuddy->note, sip->note)) /* not same */
4546 sip->is_oof_note = sbuddy->is_oof_note;
4548 g_free(sip->note);
4549 sip->note = g_strdup(sbuddy->note);
4551 sip->note_since = time(NULL);
4554 g_free(sip->status);
4555 sip->status = g_strdup(sbuddy->last_non_cal_status_id);
4558 g_free(cal_free_busy_base64);
4559 g_free(activity);
4561 SIPE_DEBUG_INFO("process_incoming_notify_msrtc: status(%s)", status_id);
4562 sipe_core_buddy_got_status(SIPE_CORE_PUBLIC, uri, status_id);
4564 if (!SIPE_CORE_PRIVATE_FLAG_IS(OCS2007) && sipe_strcase_equal(self_uri, uri)) {
4565 sipe_user_info_has_updated(sipe_private, xn_userinfo);
4568 g_free(note);
4569 sipe_xml_free(xn_presentity);
4570 g_free(uri);
4571 g_free(self_uri);
4574 static void sipe_presence_mime_cb(gpointer user_data, /* sipe_core_private */
4575 const GSList *fields,
4576 const gchar *body,
4577 gsize length)
4579 const gchar *type = sipe_utils_nameval_find(fields, "Content-Type");
4581 if (strstr(type,"application/rlmi+xml")) {
4582 process_incoming_notify_rlmi_resub(user_data, body, length);
4583 } else if (strstr(type, "text/xml+msrtc.pidf")) {
4584 process_incoming_notify_msrtc(user_data, body, length);
4585 } else {
4586 process_incoming_notify_rlmi(user_data, body, length);
4590 static void sipe_process_presence(struct sipe_core_private *sipe_private,
4591 struct sipmsg *msg)
4593 const char *ctype = sipmsg_find_header(msg, "Content-Type");
4595 SIPE_DEBUG_INFO("sipe_process_presence: Content-Type: %s", ctype ? ctype : "");
4597 if (ctype &&
4598 (strstr(ctype, "application/rlmi+xml") ||
4599 strstr(ctype, "application/msrtc-event-categories+xml")))
4601 if (strstr(ctype, "multipart"))
4603 sipe_mime_parts_foreach(ctype, msg->body, sipe_presence_mime_cb, sipe_private);
4605 else if(strstr(ctype, "application/msrtc-event-categories+xml") )
4607 process_incoming_notify_rlmi(sipe_private, msg->body, msg->bodylen);
4609 else if(strstr(ctype, "application/rlmi+xml"))
4611 process_incoming_notify_rlmi_resub(sipe_private, msg->body, msg->bodylen);
4614 else if(ctype && strstr(ctype, "text/xml+msrtc.pidf"))
4616 process_incoming_notify_msrtc(sipe_private, msg->body, msg->bodylen);
4618 else
4620 process_incoming_notify_pidf(sipe_private, msg->body, msg->bodylen);
4624 static void sipe_presence_timeout_mime_cb(gpointer user_data,
4625 SIPE_UNUSED_PARAMETER const GSList *fields,
4626 const gchar *body,
4627 gsize length)
4629 GSList **buddies = user_data;
4630 sipe_xml *xml = sipe_xml_parse(body, length);
4632 if (xml && !sipe_strequal(sipe_xml_name(xml), "list")) {
4633 const gchar *uri = sipe_xml_attribute(xml, "uri");
4634 const sipe_xml *xn_category;
4637 * automaton: presence is never expected to change
4639 * see: http://msdn.microsoft.com/en-us/library/ee354295(office.13).aspx
4641 for (xn_category = sipe_xml_child(xml, "category");
4642 xn_category;
4643 xn_category = sipe_xml_twin(xn_category)) {
4644 if (sipe_strequal(sipe_xml_attribute(xn_category, "name"),
4645 "contactCard")) {
4646 const sipe_xml *node = sipe_xml_child(xn_category, "contactCard/automaton");
4647 if (node) {
4648 char *boolean = sipe_xml_data(node);
4649 if (sipe_strequal(boolean, "true")) {
4650 SIPE_DEBUG_INFO("sipe_process_presence_timeout: %s is an automaton: - not subscribing to presence updates",
4651 uri);
4652 uri = NULL;
4654 g_free(boolean);
4656 break;
4660 if (uri) {
4661 *buddies = g_slist_append(*buddies, sip_uri(uri));
4665 sipe_xml_free(xml);
4668 static void sipe_process_presence_timeout(struct sipe_core_private *sipe_private,
4669 struct sipmsg *msg, gchar *who,
4670 int timeout)
4672 const char *ctype = sipmsg_find_header(msg, "Content-Type");
4673 gchar *action_name = sipe_utils_presence_key(who);
4675 SIPE_DEBUG_INFO("sipe_process_presence_timeout: Content-Type: %s", ctype ? ctype : "");
4677 if (ctype &&
4678 strstr(ctype, "multipart") &&
4679 (strstr(ctype, "application/rlmi+xml") ||
4680 strstr(ctype, "application/msrtc-event-categories+xml"))) {
4681 GSList *buddies = NULL;
4683 sipe_mime_parts_foreach(ctype, msg->body, sipe_presence_timeout_mime_cb, &buddies);
4685 if (buddies) {
4686 struct presence_batched_routed *payload = g_malloc(sizeof(struct presence_batched_routed));
4687 payload->host = g_strdup(who);
4688 payload->buddies = buddies;
4689 sipe_schedule_seconds(sipe_private,
4690 action_name,
4691 payload,
4692 timeout,
4693 sipe_subscribe_presence_batched_routed,
4694 sipe_subscribe_presence_batched_routed_free);
4695 SIPE_DEBUG_INFO("Resubscription multiple contacts with batched support & route(%s) in %d", who, timeout);
4698 } else {
4699 sipe_schedule_seconds(sipe_private,
4700 action_name,
4701 g_strdup(who),
4702 timeout,
4703 sipe_subscribe_presence_single,
4704 g_free);
4705 SIPE_DEBUG_INFO("Resubscription single contact with batched support(%s) in %d", who, timeout);
4707 g_free(action_name);
4711 * Dispatcher for all incoming subscription information
4712 * whether it comes from NOTIFY, BENOTIFY requests or
4713 * piggy-backed to subscription's OK responce.
4715 * @param request whether initiated from BE/NOTIFY request or OK-response message.
4716 * @param benotify whether initiated from NOTIFY or BENOTIFY request.
4718 void process_incoming_notify(struct sipe_core_private *sipe_private,
4719 struct sipmsg *msg,
4720 gboolean request, gboolean benotify)
4722 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
4723 const gchar *content_type = sipmsg_find_header(msg, "Content-Type");
4724 const gchar *event = sipmsg_find_header(msg, "Event");
4725 const gchar *subscription_state = sipmsg_find_header(msg, "subscription-state");
4727 SIPE_DEBUG_INFO("process_incoming_notify: subscription_state: %s", subscription_state ? subscription_state : "");
4729 /* implicit subscriptions */
4730 if (content_type && g_str_has_prefix(content_type, "application/ms-imdn+xml")) {
4731 sipe_process_imdn(sipe_private, msg);
4734 if (event) {
4735 /* for one off subscriptions (send with Expire: 0) */
4736 if (sipe_strcase_equal(event, "vnd-microsoft-provisioning-v2"))
4738 sipe_process_provisioning_v2(sipe_private, msg);
4740 else if (sipe_strcase_equal(event, "vnd-microsoft-provisioning"))
4742 sipe_process_provisioning(sipe_private, msg);
4744 else if (sipe_strcase_equal(event, "presence"))
4746 sipe_process_presence(sipe_private, msg);
4748 else if (sipe_strcase_equal(event, "registration-notify"))
4750 sipe_process_registration_notify(sipe_private, msg);
4753 if (!subscription_state || strstr(subscription_state, "active"))
4755 if (sipe_strcase_equal(event, "vnd-microsoft-roaming-contacts"))
4757 sipe_process_roaming_contacts(sipe_private, msg);
4759 else if (sipe_strcase_equal(event, "vnd-microsoft-roaming-self"))
4761 sipe_process_roaming_self(sipe_private, msg);
4763 else if (sipe_strcase_equal(event, "vnd-microsoft-roaming-ACL"))
4765 sipe_process_roaming_acl(sipe_private, msg);
4767 else if (sipe_strcase_equal(event, "presence.wpending"))
4769 sipe_process_presence_wpending(sipe_private, msg);
4771 else if (sipe_strcase_equal(event, "conference"))
4773 sipe_process_conference(sipe_private, msg);
4778 /* The server sends status 'terminated' */
4779 if (subscription_state && strstr(subscription_state, "terminated") ) {
4780 gchar *who = parse_from(sipmsg_find_header(msg, request ? "From" : "To"));
4781 gchar *key = sipe_utils_subscription_key(event, who);
4783 SIPE_DEBUG_INFO("process_incoming_notify: server says that subscription to %s was terminated.", who);
4784 g_free(who);
4786 sipe_subscriptions_remove(sipe_private, key);
4787 g_free(key);
4790 if (!request && event) {
4791 const gchar *expires_header = sipmsg_find_header(msg, "Expires");
4792 int timeout = expires_header ? strtol(expires_header, NULL, 10) : 0;
4793 SIPE_DEBUG_INFO("process_incoming_notify: subscription expires:%d", timeout);
4795 if (timeout) {
4796 /* 2 min ahead of expiration */
4797 timeout = (timeout - 120) > 120 ? (timeout - 120) : timeout;
4799 if (sipe_strcase_equal(event, "presence.wpending") &&
4800 g_slist_find_custom(sip->allow_events, "presence.wpending", (GCompareFunc)g_ascii_strcasecmp))
4802 gchar *action_name = g_strdup_printf("<%s>", "presence.wpending");
4803 sipe_schedule_seconds(sipe_private,
4804 action_name,
4805 NULL,
4806 timeout,
4807 sipe_subscribe_presence_wpending,
4808 NULL);
4809 g_free(action_name);
4811 else if (sipe_strcase_equal(event, "presence") &&
4812 g_slist_find_custom(sip->allow_events, "presence", (GCompareFunc)g_ascii_strcasecmp))
4814 gchar *who = parse_from(sipmsg_find_header(msg, "To"));
4815 gchar *action_name = sipe_utils_presence_key(who);
4817 if (sip->batched_support) {
4818 sipe_process_presence_timeout(sipe_private, msg, who, timeout);
4820 else {
4821 sipe_schedule_seconds(sipe_private,
4822 action_name,
4823 g_strdup(who),
4824 timeout,
4825 sipe_subscribe_presence_single,
4826 g_free);
4827 SIPE_DEBUG_INFO("Resubscription single contact (%s) in %d", who, timeout);
4829 g_free(action_name);
4830 g_free(who);
4835 /* The client responses on received a NOTIFY message */
4836 if (request && !benotify)
4838 sip_transport_response(sipe_private, msg, 200, "OK", NULL);
4843 * Whether user manually changed status or
4844 * it was changed automatically due to user
4845 * became inactive/active again
4847 static gboolean
4848 sipe_is_user_state(struct sipe_core_private *sipe_private)
4850 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
4851 gboolean res;
4852 time_t now = time(NULL);
4854 SIPE_DEBUG_INFO("sipe_is_user_state: sip->idle_switch : %s", asctime(localtime(&(sip->idle_switch))));
4855 SIPE_DEBUG_INFO("sipe_is_user_state: now : %s", asctime(localtime(&now)));
4857 res = ((now - SIPE_IDLE_SET_DELAY * 2) >= sip->idle_switch);
4859 SIPE_DEBUG_INFO("sipe_is_user_state: res = %s", res ? "USER" : "MACHINE");
4860 return res;
4863 static void
4864 send_presence_soap0(struct sipe_core_private *sipe_private,
4865 gboolean do_publish_calendar,
4866 gboolean do_reset_status)
4868 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
4869 struct sipe_calendar* cal = sip->cal;
4870 int availability = 0;
4871 int activity = 0;
4872 gchar *body;
4873 gchar *tmp;
4874 gchar *tmp2 = NULL;
4875 gchar *res_note = NULL;
4876 gchar *res_oof = NULL;
4877 const gchar *note_pub = NULL;
4878 gchar *states = NULL;
4879 gchar *calendar_data = NULL;
4880 gchar *epid = get_epid(sipe_private);
4881 time_t now = time(NULL);
4882 gchar *since_time_str = sipe_utils_time_to_str(now);
4883 const gchar *oof_note = cal ? sipe_ews_get_oof_note(cal) : NULL;
4884 const char *user_input;
4885 gboolean pub_oof = cal && oof_note && (!sip->note || cal->updated > sip->note_since);
4887 if (oof_note && sip->note) {
4888 SIPE_DEBUG_INFO("cal->oof_start : %s", asctime(localtime(&(cal->oof_start))));
4889 SIPE_DEBUG_INFO("sip->note_since : %s", asctime(localtime(&(sip->note_since))));
4892 SIPE_DEBUG_INFO("sip->note : %s", sip->note ? sip->note : "");
4894 if (!sip->initial_state_published ||
4895 do_reset_status)
4897 g_free(sip->status);
4898 sip->status = g_strdup(SIPE_STATUS_ID_AVAILABLE);
4901 sipe_get_act_avail_by_status_2005(sip->status, &activity, &availability);
4903 /* Note */
4904 if (pub_oof) {
4905 note_pub = oof_note;
4906 res_oof = SIPE_SOAP_SET_PRESENCE_OOF_XML;
4907 cal->published = TRUE;
4908 } else if (sip->note) {
4909 if (sip->is_oof_note && !oof_note) { /* stale OOF note, as it's not present in cal already */
4910 g_free(sip->note);
4911 sip->note = NULL;
4912 sip->is_oof_note = FALSE;
4913 sip->note_since = 0;
4914 } else {
4915 note_pub = sip->note;
4916 res_oof = sip->is_oof_note ? SIPE_SOAP_SET_PRESENCE_OOF_XML : "";
4920 if (note_pub)
4922 /* to protocol internal plain text format */
4923 tmp = sipe_backend_markup_strip_html(note_pub);
4924 res_note = g_markup_printf_escaped(SIPE_SOAP_SET_PRESENCE_NOTE_XML, tmp);
4925 g_free(tmp);
4928 /* User State */
4929 if (!do_reset_status) {
4930 if (sipe_is_user_state(sipe_private) && !do_publish_calendar && sip->initial_state_published)
4932 gchar *activity_token = NULL;
4933 int avail_2007 = sipe_get_availability_by_status(sip->status, &activity_token);
4935 states = g_strdup_printf(SIPE_SOAP_SET_PRESENCE_STATES,
4936 avail_2007,
4937 since_time_str,
4938 epid,
4939 activity_token);
4940 g_free(activity_token);
4942 else /* preserve existing publication */
4944 if (sip->user_states) {
4945 states = g_strdup(sip->user_states);
4948 } else {
4949 /* do nothing - then User state will be erased */
4951 sip->initial_state_published = TRUE;
4953 /* CalendarInfo */
4954 if (cal && (!is_empty(cal->legacy_dn) || !is_empty(cal->email)) && cal->fb_start && !is_empty(cal->free_busy))
4956 char *fb_start_str = sipe_utils_time_to_str(cal->fb_start);
4957 char *free_busy_base64 = sipe_cal_get_freebusy_base64(cal->free_busy);
4958 calendar_data = g_strdup_printf(SIPE_SOAP_SET_PRESENCE_CALENDAR,
4959 !is_empty(cal->legacy_dn) ? cal->legacy_dn : cal->email,
4960 fb_start_str,
4961 free_busy_base64);
4962 g_free(fb_start_str);
4963 g_free(free_busy_base64);
4966 user_input = (sipe_is_user_state(sipe_private) ||
4967 sipe_strequal(sip->status, SIPE_STATUS_ID_AVAILABLE)) ?
4968 "active" : "idle";
4970 /* forming resulting XML */
4971 body = g_strdup_printf(SIPE_SOAP_SET_PRESENCE,
4972 sipe_private->username,
4973 availability,
4974 activity,
4975 (tmp = g_ascii_strup(g_get_host_name(), -1)),
4976 res_note ? res_note : "",
4977 res_oof ? res_oof : "",
4978 states ? states : "",
4979 calendar_data ? calendar_data : "",
4980 epid,
4981 since_time_str,
4982 since_time_str,
4983 user_input);
4984 g_free(tmp);
4985 g_free(tmp2);
4986 g_free(res_note);
4987 g_free(states);
4988 g_free(calendar_data);
4990 send_soap_request(sipe_private, body);
4992 g_free(body);
4993 g_free(since_time_str);
4994 g_free(epid);
4997 void
4998 send_presence_soap(struct sipe_core_private *sipe_private,
4999 gboolean do_publish_calendar)
5001 return send_presence_soap0(sipe_private, do_publish_calendar, FALSE);
5005 static gboolean
5006 process_send_presence_category_publish_response(struct sipe_core_private *sipe_private,
5007 struct sipmsg *msg,
5008 struct transaction *trans)
5010 const gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
5012 if (msg->response == 409 && g_str_has_prefix(contenttype, "application/msrtc-fault+xml")) {
5013 sipe_xml *xml;
5014 const sipe_xml *node;
5015 gchar *fault_code;
5016 GHashTable *faults;
5017 int index_our;
5018 gboolean has_device_publication = FALSE;
5020 xml = sipe_xml_parse(msg->body, msg->bodylen);
5022 /* test if version mismatch fault */
5023 fault_code = sipe_xml_data(sipe_xml_child(xml, "Faultcode"));
5024 if (!sipe_strequal(fault_code, "Client.BadCall.WrongDelta")) {
5025 SIPE_DEBUG_INFO("process_send_presence_category_publish_response: unsupported fault code:%s returning.", fault_code);
5026 g_free(fault_code);
5027 sipe_xml_free(xml);
5028 return TRUE;
5030 g_free(fault_code);
5032 /* accumulating information about faulty versions */
5033 faults = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
5034 for (node = sipe_xml_child(xml, "details/operation");
5035 node;
5036 node = sipe_xml_twin(node))
5038 const gchar *index = sipe_xml_attribute(node, "index");
5039 const gchar *curVersion = sipe_xml_attribute(node, "curVersion");
5041 g_hash_table_insert(faults, g_strdup(index), g_strdup(curVersion));
5042 SIPE_DEBUG_INFO("fault added: index:%s curVersion:%s", index, curVersion);
5044 sipe_xml_free(xml);
5046 /* here we are parsing our own request to figure out what publication
5047 * referenced here only by index went wrong
5049 xml = sipe_xml_parse(trans->msg->body, trans->msg->bodylen);
5051 /* publication */
5052 for (node = sipe_xml_child(xml, "publications/publication"),
5053 index_our = 1; /* starts with 1 - our first publication */
5054 node;
5055 node = sipe_xml_twin(node), index_our++)
5057 gchar *idx = g_strdup_printf("%d", index_our);
5058 const gchar *curVersion = g_hash_table_lookup(faults, idx);
5059 const gchar *categoryName = sipe_xml_attribute(node, "categoryName");
5060 g_free(idx);
5062 if (sipe_strequal("device", categoryName)) {
5063 has_device_publication = TRUE;
5066 if (curVersion) { /* fault exist on this index */
5067 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5068 const gchar *container = sipe_xml_attribute(node, "container");
5069 const gchar *instance = sipe_xml_attribute(node, "instance");
5070 /* key is <category><instance><container> */
5071 gchar *key = g_strdup_printf("<%s><%s><%s>", categoryName, instance, container);
5072 GHashTable *category = g_hash_table_lookup(sip->our_publications, categoryName);
5074 if (category) {
5075 struct sipe_publication *publication =
5076 g_hash_table_lookup(category, key);
5078 SIPE_DEBUG_INFO("key is %s", key);
5080 if (publication) {
5081 SIPE_DEBUG_INFO("Updating %s with version %s. Was %d before.",
5082 key, curVersion, publication->version);
5083 /* updating publication's version to the correct one */
5084 publication->version = atoi(curVersion);
5086 } else {
5087 /* We somehow lost this category from our publications... */
5088 struct sipe_publication *publication = g_new0(struct sipe_publication, 1);
5089 publication->category = g_strdup(categoryName);
5090 publication->instance = atoi(instance);
5091 publication->container = atoi(container);
5092 publication->version = atoi(curVersion);
5093 category = g_hash_table_new_full(g_str_hash, g_str_equal,
5094 g_free, (GDestroyNotify)free_publication);
5095 g_hash_table_insert(category, g_strdup(key), publication);
5096 g_hash_table_insert(sip->our_publications, g_strdup(categoryName), category);
5097 SIPE_DEBUG_INFO("added lost category '%s' key '%s'", categoryName, key);
5099 g_free(key);
5102 sipe_xml_free(xml);
5103 g_hash_table_destroy(faults);
5105 /* rebublishing with right versions */
5106 if (has_device_publication) {
5107 send_publish_category_initial(sipe_private);
5108 } else {
5109 send_presence_status(sipe_private, NULL);
5112 return TRUE;
5116 * Returns 'device' XML part for publication.
5117 * Must be g_free'd after use.
5119 static gchar *
5120 sipe_publish_get_category_device(struct sipe_core_private *sipe_private)
5122 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5123 gchar *uri;
5124 gchar *doc;
5125 gchar *epid = get_epid(sipe_private);
5126 gchar *uuid = generateUUIDfromEPID(epid);
5127 guint device_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_DEVICE);
5128 /* key is <category><instance><container> */
5129 gchar *key = g_strdup_printf("<%s><%u><%u>", "device", device_instance, 2);
5130 struct sipe_publication *publication =
5131 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "device"), key);
5133 g_free(key);
5134 g_free(epid);
5136 uri = sip_uri_self(sipe_private);
5137 doc = g_strdup_printf(SIPE_PUB_XML_DEVICE,
5138 device_instance,
5139 publication ? publication->version : 0,
5140 uuid,
5141 uri,
5142 "00:00:00+01:00", /* @TODO make timezone real*/
5143 g_get_host_name()
5146 g_free(uri);
5147 g_free(uuid);
5149 return doc;
5153 * A service method - use
5154 * - send_publish_get_category_state_machine and
5155 * - send_publish_get_category_state_user instead.
5156 * Must be g_free'd after use.
5158 static gchar *
5159 sipe_publish_get_category_state(struct sipe_core_private *sipe_private,
5160 gboolean is_user_state)
5162 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5163 int availability = sipe_get_availability_by_status(sip->status, NULL);
5164 guint instance = is_user_state ? sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_USER) :
5165 sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_MACHINE);
5166 /* key is <category><instance><container> */
5167 gchar *key_2 = g_strdup_printf("<%s><%u><%u>", "state", instance, 2);
5168 gchar *key_3 = g_strdup_printf("<%s><%u><%u>", "state", instance, 3);
5169 struct sipe_publication *publication_2 =
5170 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_2);
5171 struct sipe_publication *publication_3 =
5172 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_3);
5174 g_free(key_2);
5175 g_free(key_3);
5177 if (publication_2 && (publication_2->availability == availability))
5179 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_state: state has NOT changed. Exiting.");
5180 return NULL; /* nothing to update */
5183 return g_strdup_printf( is_user_state ? SIPE_PUB_XML_STATE_USER : SIPE_PUB_XML_STATE_MACHINE,
5184 instance,
5185 publication_2 ? publication_2->version : 0,
5186 availability,
5187 instance,
5188 publication_3 ? publication_3->version : 0,
5189 availability);
5193 * Only Busy and OOF calendar event are published.
5194 * Different instances are used for that.
5196 * Must be g_free'd after use.
5198 static gchar *
5199 sipe_publish_get_category_state_calendar(struct sipe_core_private *sipe_private,
5200 struct sipe_cal_event *event,
5201 const char *uri,
5202 int cal_satus)
5204 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5205 gchar *start_time_str;
5206 int availability = 0;
5207 gchar *res;
5208 gchar *tmp = NULL;
5209 guint instance = (cal_satus == SIPE_CAL_OOF) ?
5210 sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_CALENDAR_OOF) :
5211 sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_CALENDAR);
5213 /* key is <category><instance><container> */
5214 gchar *key_2 = g_strdup_printf("<%s><%u><%u>", "state", instance, 2);
5215 gchar *key_3 = g_strdup_printf("<%s><%u><%u>", "state", instance, 3);
5216 struct sipe_publication *publication_2 =
5217 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_2);
5218 struct sipe_publication *publication_3 =
5219 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_3);
5221 g_free(key_2);
5222 g_free(key_3);
5224 if (!publication_3 && !event) { /* was nothing, have nothing, exiting */
5225 SIPE_DEBUG_INFO("sipe_publish_get_category_state_calendar: "
5226 "Exiting as no publication and no event for cal_satus:%d", cal_satus);
5227 return NULL;
5230 if (event &&
5231 publication_3 &&
5232 (publication_3->availability == availability) &&
5233 sipe_strequal(publication_3->cal_event_hash, (tmp = sipe_cal_event_hash(event))))
5235 g_free(tmp);
5236 SIPE_DEBUG_INFO("sipe_publish_get_category_state_calendar: "
5237 "cal state has NOT changed for cal_satus:%d. Exiting.", cal_satus);
5238 return NULL; /* nothing to update */
5240 g_free(tmp);
5242 if (event &&
5243 (event->cal_status == SIPE_CAL_BUSY ||
5244 event->cal_status == SIPE_CAL_OOF))
5246 gchar *availability_xml_str = NULL;
5247 gchar *activity_xml_str = NULL;
5248 gchar *escaped_subject = event->subject ? g_markup_escape_text(event->subject, -1) : NULL;
5249 gchar *escaped_location = event->location ? g_markup_escape_text(event->location, -1) : NULL;
5251 if (event->cal_status == SIPE_CAL_BUSY) {
5252 availability_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_AVAIL, 6500);
5255 if (event->cal_status == SIPE_CAL_BUSY && event->is_meeting) {
5256 activity_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_ACTIVITY,
5257 sipe_activity_map[SIPE_ACTIVITY_IN_MEETING].token,
5258 "minAvailability=\"6500\"",
5259 "maxAvailability=\"8999\"");
5260 } else if (event->cal_status == SIPE_CAL_OOF) {
5261 activity_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_ACTIVITY,
5262 sipe_activity_map[SIPE_ACTIVITY_OOF].token,
5263 "minAvailability=\"12000\"",
5264 "");
5266 start_time_str = sipe_utils_time_to_str(event->start_time);
5268 res = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR,
5269 instance,
5270 publication_2 ? publication_2->version : 0,
5271 uri,
5272 start_time_str,
5273 availability_xml_str ? availability_xml_str : "",
5274 activity_xml_str ? activity_xml_str : "",
5275 escaped_subject ? escaped_subject : "",
5276 escaped_location ? escaped_location : "",
5278 instance,
5279 publication_3 ? publication_3->version : 0,
5280 uri,
5281 start_time_str,
5282 availability_xml_str ? availability_xml_str : "",
5283 activity_xml_str ? activity_xml_str : "",
5284 escaped_subject ? escaped_subject : "",
5285 escaped_location ? escaped_location : ""
5287 g_free(escaped_location);
5288 g_free(escaped_subject);
5289 g_free(start_time_str);
5290 g_free(availability_xml_str);
5291 g_free(activity_xml_str);
5294 else /* including !event, SIPE_CAL_FREE, SIPE_CAL_TENTATIVE */
5296 res = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_CLEAR,
5297 instance,
5298 publication_2 ? publication_2->version : 0,
5300 instance,
5301 publication_3 ? publication_3->version : 0
5305 return res;
5309 * Returns 'machineState' XML part for publication.
5310 * Must be g_free'd after use.
5312 static gchar *
5313 sipe_publish_get_category_state_machine(struct sipe_core_private *sipe_private)
5315 return sipe_publish_get_category_state(sipe_private, FALSE);
5319 * Returns 'userState' XML part for publication.
5320 * Must be g_free'd after use.
5322 static gchar *
5323 sipe_publish_get_category_state_user(struct sipe_core_private *sipe_private)
5325 return sipe_publish_get_category_state(sipe_private, TRUE);
5329 * Returns 'note' XML part for publication.
5330 * Must be g_free'd after use.
5332 * Protocol format for Note is plain text.
5334 * @param note a note in Sipe internal HTML format
5335 * @param note_type either personal or OOF
5337 static gchar *
5338 sipe_publish_get_category_note(struct sipe_core_private *sipe_private,
5339 const char *note, /* html */
5340 const char *note_type,
5341 time_t note_start,
5342 time_t note_end)
5344 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5345 guint instance = sipe_strequal("OOF", note_type) ? sipe_get_pub_instance(sipe_private, SIPE_PUB_NOTE_OOF) : 0;
5346 /* key is <category><instance><container> */
5347 gchar *key_note_200 = g_strdup_printf("<%s><%u><%u>", "note", instance, 200);
5348 gchar *key_note_300 = g_strdup_printf("<%s><%u><%u>", "note", instance, 300);
5349 gchar *key_note_400 = g_strdup_printf("<%s><%u><%u>", "note", instance, 400);
5351 struct sipe_publication *publication_note_200 =
5352 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_200);
5353 struct sipe_publication *publication_note_300 =
5354 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_300);
5355 struct sipe_publication *publication_note_400 =
5356 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_400);
5358 char *tmp = note ? sipe_backend_markup_strip_html(note) : NULL;
5359 char *n1 = tmp ? g_markup_escape_text(tmp, -1) : NULL;
5360 const char *n2 = publication_note_200 ? publication_note_200->note : NULL;
5361 char *res, *tmp1, *tmp2, *tmp3;
5362 char *start_time_attr;
5363 char *end_time_attr;
5365 g_free(tmp);
5366 tmp = NULL;
5367 g_free(key_note_200);
5368 g_free(key_note_300);
5369 g_free(key_note_400);
5371 /* we even need to republish empty note */
5372 if (sipe_strequal(n1, n2))
5374 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_note: note has NOT changed. Exiting.");
5375 g_free(n1);
5376 return NULL; /* nothing to update */
5379 start_time_attr = note_start ? g_strdup_printf(" startTime=\"%s\"", (tmp = sipe_utils_time_to_str(note_start))) : NULL;
5380 g_free(tmp);
5381 tmp = NULL;
5382 end_time_attr = note_end ? g_strdup_printf(" endTime=\"%s\"", (tmp = sipe_utils_time_to_str(note_end))) : NULL;
5383 g_free(tmp);
5385 if (n1) {
5386 tmp1 = g_strdup_printf(SIPE_PUB_XML_NOTE,
5387 instance,
5388 200,
5389 publication_note_200 ? publication_note_200->version : 0,
5390 note_type,
5391 start_time_attr ? start_time_attr : "",
5392 end_time_attr ? end_time_attr : "",
5393 n1);
5395 tmp2 = g_strdup_printf(SIPE_PUB_XML_NOTE,
5396 instance,
5397 300,
5398 publication_note_300 ? publication_note_300->version : 0,
5399 note_type,
5400 start_time_attr ? start_time_attr : "",
5401 end_time_attr ? end_time_attr : "",
5402 n1);
5404 tmp3 = g_strdup_printf(SIPE_PUB_XML_NOTE,
5405 instance,
5406 400,
5407 publication_note_400 ? publication_note_400->version : 0,
5408 note_type,
5409 start_time_attr ? start_time_attr : "",
5410 end_time_attr ? end_time_attr : "",
5411 n1);
5412 } else {
5413 tmp1 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
5414 "note",
5415 instance,
5416 200,
5417 publication_note_200 ? publication_note_200->version : 0,
5418 "static");
5419 tmp2 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
5420 "note",
5421 instance,
5422 300,
5423 publication_note_200 ? publication_note_200->version : 0,
5424 "static");
5425 tmp3 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
5426 "note",
5427 instance,
5428 400,
5429 publication_note_200 ? publication_note_200->version : 0,
5430 "static");
5432 res = g_strconcat(tmp1, tmp2, tmp3, NULL);
5434 g_free(start_time_attr);
5435 g_free(end_time_attr);
5436 g_free(tmp1);
5437 g_free(tmp2);
5438 g_free(tmp3);
5439 g_free(n1);
5441 return res;
5445 * Returns 'calendarData' XML part with WorkingHours for publication.
5446 * Must be g_free'd after use.
5448 static gchar *
5449 sipe_publish_get_category_cal_working_hours(struct sipe_core_private *sipe_private)
5451 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5452 struct sipe_calendar* cal = sip->cal;
5454 /* key is <category><instance><container> */
5455 gchar *key_cal_1 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 1);
5456 gchar *key_cal_100 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 100);
5457 gchar *key_cal_200 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 200);
5458 gchar *key_cal_300 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 300);
5459 gchar *key_cal_400 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 400);
5460 gchar *key_cal_32000 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 32000);
5462 struct sipe_publication *publication_cal_1 =
5463 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_1);
5464 struct sipe_publication *publication_cal_100 =
5465 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_100);
5466 struct sipe_publication *publication_cal_200 =
5467 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_200);
5468 struct sipe_publication *publication_cal_300 =
5469 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_300);
5470 struct sipe_publication *publication_cal_400 =
5471 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_400);
5472 struct sipe_publication *publication_cal_32000 =
5473 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_32000);
5475 const char *n1 = cal ? cal->working_hours_xml_str : NULL;
5476 const char *n2 = publication_cal_300 ? publication_cal_300->working_hours_xml_str : NULL;
5478 g_free(key_cal_1);
5479 g_free(key_cal_100);
5480 g_free(key_cal_200);
5481 g_free(key_cal_300);
5482 g_free(key_cal_400);
5483 g_free(key_cal_32000);
5485 if (!cal || is_empty(cal->email) || is_empty(cal->working_hours_xml_str)) {
5486 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_cal_working_hours: no data to publish, exiting");
5487 return NULL;
5490 if (sipe_strequal(n1, n2))
5492 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_cal_working_hours: WorkingHours has NOT changed. Exiting.");
5493 return NULL; /* nothing to update */
5496 return g_strdup_printf(SIPE_PUB_XML_WORKING_HOURS,
5497 /* 1 */
5498 publication_cal_1 ? publication_cal_1->version : 0,
5499 cal->email,
5500 cal->working_hours_xml_str,
5501 /* 100 - Public */
5502 publication_cal_100 ? publication_cal_100->version : 0,
5503 /* 200 - Company */
5504 publication_cal_200 ? publication_cal_200->version : 0,
5505 cal->email,
5506 cal->working_hours_xml_str,
5507 /* 300 - Team */
5508 publication_cal_300 ? publication_cal_300->version : 0,
5509 cal->email,
5510 cal->working_hours_xml_str,
5511 /* 400 - Personal */
5512 publication_cal_400 ? publication_cal_400->version : 0,
5513 cal->email,
5514 cal->working_hours_xml_str,
5515 /* 32000 - Blocked */
5516 publication_cal_32000 ? publication_cal_32000->version : 0
5521 * Returns 'calendarData' XML part with FreeBusy for publication.
5522 * Must be g_free'd after use.
5524 static gchar *
5525 sipe_publish_get_category_cal_free_busy(struct sipe_core_private *sipe_private)
5527 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5528 struct sipe_calendar* cal = sip->cal;
5529 guint cal_data_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_CALENDAR_DATA);
5530 char *fb_start_str;
5531 char *free_busy_base64;
5532 const char *st;
5533 const char *fb;
5534 char *res;
5536 /* key is <category><instance><container> */
5537 gchar *key_cal_1 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 1);
5538 gchar *key_cal_100 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 100);
5539 gchar *key_cal_200 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 200);
5540 gchar *key_cal_300 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 300);
5541 gchar *key_cal_400 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 400);
5542 gchar *key_cal_32000 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 32000);
5544 struct sipe_publication *publication_cal_1 =
5545 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_1);
5546 struct sipe_publication *publication_cal_100 =
5547 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_100);
5548 struct sipe_publication *publication_cal_200 =
5549 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_200);
5550 struct sipe_publication *publication_cal_300 =
5551 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_300);
5552 struct sipe_publication *publication_cal_400 =
5553 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_400);
5554 struct sipe_publication *publication_cal_32000 =
5555 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_32000);
5557 g_free(key_cal_1);
5558 g_free(key_cal_100);
5559 g_free(key_cal_200);
5560 g_free(key_cal_300);
5561 g_free(key_cal_400);
5562 g_free(key_cal_32000);
5564 if (!cal || is_empty(cal->email) || !cal->fb_start || is_empty(cal->free_busy)) {
5565 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_cal_free_busy: no data to publish, exiting");
5566 return NULL;
5569 fb_start_str = sipe_utils_time_to_str(cal->fb_start);
5570 free_busy_base64 = sipe_cal_get_freebusy_base64(cal->free_busy);
5572 st = publication_cal_300 ? publication_cal_300->fb_start_str : NULL;
5573 fb = publication_cal_300 ? publication_cal_300->free_busy_base64 : NULL;
5575 /* we will rebuplish the same data to refresh publication time,
5576 * so if data from multiple sources, most recent will be choosen
5578 //if (sipe_strequal(st, fb_start_str) && sipe_strequal(fb, free_busy_base64))
5580 // SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_cal_free_busy: FreeBusy has NOT changed. Exiting.");
5581 // g_free(fb_start_str);
5582 // g_free(free_busy_base64);
5583 // return NULL; /* nothing to update */
5586 res = g_strdup_printf(SIPE_PUB_XML_FREE_BUSY,
5587 /* 1 */
5588 cal_data_instance,
5589 publication_cal_1 ? publication_cal_1->version : 0,
5590 /* 100 - Public */
5591 cal_data_instance,
5592 publication_cal_100 ? publication_cal_100->version : 0,
5593 /* 200 - Company */
5594 cal_data_instance,
5595 publication_cal_200 ? publication_cal_200->version : 0,
5596 cal->email,
5597 fb_start_str,
5598 free_busy_base64,
5599 /* 300 - Team */
5600 cal_data_instance,
5601 publication_cal_300 ? publication_cal_300->version : 0,
5602 cal->email,
5603 fb_start_str,
5604 free_busy_base64,
5605 /* 400 - Personal */
5606 cal_data_instance,
5607 publication_cal_400 ? publication_cal_400->version : 0,
5608 cal->email,
5609 fb_start_str,
5610 free_busy_base64,
5611 /* 32000 - Blocked */
5612 cal_data_instance,
5613 publication_cal_32000 ? publication_cal_32000->version : 0
5616 g_free(fb_start_str);
5617 g_free(free_busy_base64);
5618 return res;
5621 static void send_presence_publish(struct sipe_core_private *sipe_private,
5622 const char *publications)
5624 gchar *uri;
5625 gchar *doc;
5626 gchar *tmp;
5627 gchar *hdr;
5629 uri = sip_uri_self(sipe_private);
5630 doc = g_strdup_printf(SIPE_SEND_PRESENCE,
5631 uri,
5632 publications);
5634 tmp = get_contact(sipe_private);
5635 hdr = g_strdup_printf("Contact: %s\r\n"
5636 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp);
5638 sip_transport_service(sipe_private,
5639 uri,
5640 hdr,
5641 doc,
5642 process_send_presence_category_publish_response);
5644 g_free(tmp);
5645 g_free(hdr);
5646 g_free(uri);
5647 g_free(doc);
5650 static void
5651 send_publish_category_initial(struct sipe_core_private *sipe_private)
5653 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5654 gchar *pub_device = sipe_publish_get_category_device(sipe_private);
5655 gchar *pub_machine;
5656 gchar *publications;
5658 g_free(sip->status);
5659 sip->status = g_strdup(SIPE_STATUS_ID_AVAILABLE); /* our initial state */
5661 pub_machine = sipe_publish_get_category_state_machine(sipe_private);
5662 publications = g_strdup_printf("%s%s",
5663 pub_device,
5664 pub_machine ? pub_machine : "");
5665 g_free(pub_device);
5666 g_free(pub_machine);
5668 send_presence_publish(sipe_private, publications);
5669 g_free(publications);
5672 static void
5673 send_presence_category_publish(struct sipe_core_private *sipe_private)
5675 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5676 gchar *pub_state = sipe_is_user_state(sipe_private) ?
5677 sipe_publish_get_category_state_user(sipe_private) :
5678 sipe_publish_get_category_state_machine(sipe_private);
5679 gchar *pub_note = sipe_publish_get_category_note(sipe_private,
5680 sip->note,
5681 sip->is_oof_note ? "OOF" : "personal",
5684 gchar *publications;
5686 if (!pub_state && !pub_note) {
5687 SIPE_DEBUG_INFO_NOFORMAT("send_presence_category_publish: nothing has changed. Exiting.");
5688 return;
5691 publications = g_strdup_printf("%s%s",
5692 pub_state ? pub_state : "",
5693 pub_note ? pub_note : "");
5695 g_free(pub_state);
5696 g_free(pub_note);
5698 send_presence_publish(sipe_private, publications);
5699 g_free(publications);
5703 * Publishes self status
5704 * based on own calendar information.
5706 * For 2007+
5708 void
5709 publish_calendar_status_self(struct sipe_core_private *sipe_private,
5710 SIPE_UNUSED_PARAMETER void *unused)
5712 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5713 struct sipe_cal_event* event = NULL;
5714 gchar *pub_cal_working_hours = NULL;
5715 gchar *pub_cal_free_busy = NULL;
5716 gchar *pub_calendar = NULL;
5717 gchar *pub_calendar2 = NULL;
5718 gchar *pub_oof_note = NULL;
5719 const gchar *oof_note;
5720 time_t oof_start = 0;
5721 time_t oof_end = 0;
5723 if (!sip->cal) {
5724 SIPE_DEBUG_INFO_NOFORMAT("publish_calendar_status_self() no calendar data.");
5725 return;
5728 SIPE_DEBUG_INFO_NOFORMAT("publish_calendar_status_self() started.");
5729 if (sip->cal->cal_events) {
5730 event = sipe_cal_get_event(sip->cal->cal_events, time(NULL));
5733 if (!event) {
5734 SIPE_DEBUG_INFO_NOFORMAT("publish_calendar_status_self: current event is NULL");
5735 } else {
5736 char *desc = sipe_cal_event_describe(event);
5737 SIPE_DEBUG_INFO("publish_calendar_status_self: current event is:\n%s", desc ? desc : "");
5738 g_free(desc);
5741 /* Logic
5742 if OOF
5743 OOF publish, Busy clean
5744 ilse if Busy
5745 OOF clean, Busy publish
5746 else
5747 OOF clean, Busy clean
5749 if (event && event->cal_status == SIPE_CAL_OOF) {
5750 pub_calendar = sipe_publish_get_category_state_calendar(sipe_private, event, sip->cal->email, SIPE_CAL_OOF);
5751 pub_calendar2 = sipe_publish_get_category_state_calendar(sipe_private, NULL, sip->cal->email, SIPE_CAL_BUSY);
5752 } else if (event && event->cal_status == SIPE_CAL_BUSY) {
5753 pub_calendar = sipe_publish_get_category_state_calendar(sipe_private, NULL, sip->cal->email, SIPE_CAL_OOF);
5754 pub_calendar2 = sipe_publish_get_category_state_calendar(sipe_private, event, sip->cal->email, SIPE_CAL_BUSY);
5755 } else {
5756 pub_calendar = sipe_publish_get_category_state_calendar(sipe_private, NULL, sip->cal->email, SIPE_CAL_OOF);
5757 pub_calendar2 = sipe_publish_get_category_state_calendar(sipe_private, NULL, sip->cal->email, SIPE_CAL_BUSY);
5760 oof_note = sipe_ews_get_oof_note(sip->cal);
5761 if (sipe_strequal("Scheduled", sip->cal->oof_state)) {
5762 oof_start = sip->cal->oof_start;
5763 oof_end = sip->cal->oof_end;
5765 pub_oof_note = sipe_publish_get_category_note(sipe_private, oof_note, "OOF", oof_start, oof_end);
5767 pub_cal_working_hours = sipe_publish_get_category_cal_working_hours(sipe_private);
5768 pub_cal_free_busy = sipe_publish_get_category_cal_free_busy(sipe_private);
5770 if (!pub_cal_working_hours && !pub_cal_free_busy && !pub_calendar && !pub_calendar2 && !pub_oof_note) {
5771 SIPE_DEBUG_INFO_NOFORMAT("publish_calendar_status_self: nothing has changed.");
5772 } else {
5773 gchar *publications = g_strdup_printf("%s%s%s%s%s",
5774 pub_cal_working_hours ? pub_cal_working_hours : "",
5775 pub_cal_free_busy ? pub_cal_free_busy : "",
5776 pub_calendar ? pub_calendar : "",
5777 pub_calendar2 ? pub_calendar2 : "",
5778 pub_oof_note ? pub_oof_note : "");
5780 send_presence_publish(sipe_private, publications);
5781 g_free(publications);
5784 g_free(pub_cal_working_hours);
5785 g_free(pub_cal_free_busy);
5786 g_free(pub_calendar);
5787 g_free(pub_calendar2);
5788 g_free(pub_oof_note);
5790 /* repeat scheduling */
5791 sipe_sched_calendar_status_self_publish(sipe_private, time(NULL));
5794 static void send_presence_status(struct sipe_core_private *sipe_private,
5795 SIPE_UNUSED_PARAMETER void *unused)
5797 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5798 PurpleStatus * status = purple_account_get_active_status(sip->account);
5800 if (!status) return;
5802 SIPE_DEBUG_INFO("send_presence_status: status: %s (%s)",
5803 purple_status_get_id(status) ? purple_status_get_id(status) : "",
5804 sipe_is_user_state(sipe_private) ? "USER" : "MACHINE");
5806 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
5807 send_presence_category_publish(sipe_private);
5808 } else {
5809 send_presence_soap(sipe_private, FALSE);
5813 static guint sipe_ht_hash_nick(const char *nick)
5815 char *lc = g_utf8_strdown(nick, -1);
5816 guint bucket = g_str_hash(lc);
5817 g_free(lc);
5819 return bucket;
5822 static gboolean sipe_ht_equals_nick(const char *nick1, const char *nick2)
5824 char *nick1_norm = NULL;
5825 char *nick2_norm = NULL;
5826 gboolean equal;
5828 if (nick1 == NULL && nick2 == NULL) return TRUE;
5829 if (nick1 == NULL || nick2 == NULL ||
5830 !g_utf8_validate(nick1, -1, NULL) ||
5831 !g_utf8_validate(nick2, -1, NULL)) return FALSE;
5833 nick1_norm = g_utf8_casefold(nick1, -1);
5834 nick2_norm = g_utf8_casefold(nick2, -1);
5835 equal = g_utf8_collate(nick1_norm, nick2_norm) == 0;
5836 g_free(nick2_norm);
5837 g_free(nick1_norm);
5839 return equal;
5842 /* temporary function */
5843 void sipe_purple_setup(struct sipe_core_public *sipe_public,
5844 PurpleConnection *gc)
5846 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA;
5847 sip->gc = gc;
5848 sip->account = purple_connection_get_account(gc);
5851 struct sipe_core_public *sipe_core_allocate(const gchar *signin_name,
5852 const gchar *login_domain,
5853 const gchar *login_account,
5854 const gchar *password,
5855 const gchar *email,
5856 const gchar *email_url,
5857 const gchar **errmsg)
5859 struct sipe_core_private *sipe_private;
5860 struct sipe_account_data *sip;
5861 gchar **user_domain;
5863 SIPE_DEBUG_INFO("sipe_core_allocate: signin_name '%s'", signin_name);
5865 /* ensure that sign-in name doesn't contain invalid characters */
5866 if (strpbrk(signin_name, "\t\v\r\n") != NULL) {
5867 *errmsg = _("SIP Exchange user name contains invalid characters");
5868 return NULL;
5871 /* ensure that sign-in name format is name@domain */
5872 if (!strchr(signin_name, '@') ||
5873 g_str_has_prefix(signin_name, "@") ||
5874 g_str_has_suffix(signin_name, "@")) {
5875 *errmsg = _("User name should be a valid SIP URI\nExample: user@company.com");
5876 return NULL;
5879 /* ensure that email format is name@domain (if provided) */
5880 if (!is_empty(email) &&
5881 (!strchr(email, '@') ||
5882 g_str_has_prefix(email, "@") ||
5883 g_str_has_suffix(email, "@")))
5885 *errmsg = _("Email address should be valid if provided\nExample: user@company.com");
5886 return NULL;
5889 /* ensure that user name doesn't contain spaces */
5890 user_domain = g_strsplit(signin_name, "@", 2);
5891 SIPE_DEBUG_INFO("sipe_core_allocate: user '%s' domain '%s'", user_domain[0], user_domain[1]);
5892 if (strchr(user_domain[0], ' ') != NULL) {
5893 g_strfreev(user_domain);
5894 *errmsg = _("SIP Exchange user name contains whitespace");
5895 return NULL;
5898 /* ensure that email_url is in proper format if enabled (if provided).
5899 * Example (Exchange): https://server.company.com/EWS/Exchange.asmx
5900 * Example (Domino) : https://[domino_server]/[mail_database_name].nsf
5902 if (!is_empty(email_url)) {
5903 char *tmp = g_ascii_strdown(email_url, -1);
5904 if (!g_str_has_prefix(tmp, "https://"))
5906 g_free(tmp);
5907 g_strfreev(user_domain);
5908 *errmsg = _("Email services URL should be valid if provided\n"
5909 "Example: https://exchange.corp.com/EWS/Exchange.asmx\n"
5910 "Example: https://domino.corp.com/maildatabase.nsf");
5911 return NULL;
5913 g_free(tmp);
5916 sipe_private = g_new0(struct sipe_core_private, 1);
5917 sipe_private->temporary = sip = g_new0(struct sipe_account_data, 1);
5918 sip->subscribed_buddies = FALSE;
5919 sip->initial_state_published = FALSE;
5920 sipe_private->username = g_strdup(signin_name);
5921 sip->email = is_empty(email) ? g_strdup(signin_name) : g_strdup(email);
5922 sip->authdomain = is_empty(login_domain) ? NULL : g_strdup(login_domain);
5923 sip->authuser = is_empty(login_account) ? NULL : g_strdup(login_account);
5924 sip->password = g_strdup(password);
5925 sipe_private->public.sip_name = g_strdup(user_domain[0]);
5926 sipe_private->public.sip_domain = g_strdup(user_domain[1]);
5927 g_strfreev(user_domain);
5929 sipe_private->buddies = g_hash_table_new((GHashFunc)sipe_ht_hash_nick, (GEqualFunc)sipe_ht_equals_nick);
5930 sip->our_publications = g_hash_table_new_full(g_str_hash, g_str_equal,
5931 g_free, (GDestroyNotify)g_hash_table_destroy);
5932 sipe_subscriptions_init(sipe_private);
5933 sip->status = g_strdup(SIPE_STATUS_ID_UNKNOWN);
5935 return((struct sipe_core_public *)sipe_private);
5938 static void
5939 sipe_blist_menu_free_containers(struct sipe_core_private *sipe_private);
5941 void sipe_connection_cleanup(struct sipe_core_private *sipe_private)
5943 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5945 g_free(sipe_private->epid);
5946 sipe_private->epid = NULL;
5948 sip_transport_disconnect(sipe_private);
5950 sipe_schedule_cancel_all(sipe_private);
5952 if (sip->allow_events) {
5953 GSList *entry = sip->allow_events;
5954 while (entry) {
5955 g_free(entry->data);
5956 entry = entry->next;
5959 g_slist_free(sip->allow_events);
5961 if (sip->containers) {
5962 GSList *entry = sip->containers;
5963 while (entry) {
5964 free_container((struct sipe_container *)entry->data);
5965 entry = entry->next;
5968 g_slist_free(sip->containers);
5970 /* libpurple memory leak workaround */
5971 sipe_blist_menu_free_containers(sipe_private);
5973 if (sipe_private->contact)
5974 g_free(sipe_private->contact);
5975 sipe_private->contact = NULL;
5976 if (sip->regcallid)
5977 g_free(sip->regcallid);
5978 sip->regcallid = NULL;
5980 if (sipe_private->focus_factory_uri)
5981 g_free(sipe_private->focus_factory_uri);
5982 sipe_private->focus_factory_uri = NULL;
5984 if (sip->cal) {
5985 sipe_cal_calendar_free(sip->cal);
5987 sip->cal = NULL;
5989 sipe_groupchat_free(sipe_private);
5993 * A callback for g_hash_table_foreach_remove
5995 static gboolean sipe_buddy_remove(SIPE_UNUSED_PARAMETER gpointer key, gpointer buddy,
5996 SIPE_UNUSED_PARAMETER gpointer user_data)
5998 sipe_free_buddy((struct sipe_buddy *) buddy);
6000 /* We must return TRUE as the key/value have already been deleted */
6001 return(TRUE);
6004 void sipe_buddy_free_all(struct sipe_core_private *sipe_private)
6006 g_hash_table_foreach_steal(sipe_private->buddies, sipe_buddy_remove, NULL);
6009 static void sipe_searchresults_im_buddy(PurpleConnection *gc, GList *row,
6010 SIPE_UNUSED_PARAMETER void *user_data)
6012 PurpleAccount *acct = purple_connection_get_account(gc);
6013 char *id = sip_uri_from_name((gchar *)g_list_nth_data(row, 0));
6014 PurpleConversation *conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, id, acct);
6015 if (conv == NULL)
6016 conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, acct, id);
6017 purple_conversation_present(conv);
6018 g_free(id);
6021 static void sipe_searchresults_add_buddy(PurpleConnection *gc, GList *row,
6022 SIPE_UNUSED_PARAMETER void *user_data)
6025 purple_blist_request_add_buddy(purple_connection_get_account(gc),
6026 g_list_nth_data(row, 0), _("Other Contacts"), g_list_nth_data(row, 1));
6029 static gboolean process_search_contact_response(struct sipe_core_private *sipe_private,
6030 struct sipmsg *msg,
6031 SIPE_UNUSED_PARAMETER struct transaction *trans)
6033 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
6034 PurpleNotifySearchResults *results;
6035 PurpleNotifySearchColumn *column;
6036 sipe_xml *searchResults;
6037 const sipe_xml *mrow;
6038 int match_count = 0;
6039 gboolean more = FALSE;
6040 gchar *secondary;
6042 SIPE_DEBUG_INFO("process_search_contact_response: body:\n%s", msg->body ? msg->body : "");
6044 searchResults = sipe_xml_parse(msg->body, msg->bodylen);
6045 if (!searchResults) {
6046 SIPE_DEBUG_INFO_NOFORMAT("process_search_contact_response: no parseable searchResults");
6047 return FALSE;
6050 results = purple_notify_searchresults_new();
6052 if (results == NULL) {
6053 SIPE_DEBUG_ERROR_NOFORMAT("purple_parse_searchreply: Unable to display the search results.");
6054 purple_notify_error(sip->gc, NULL, _("Unable to display the search results"), NULL);
6056 sipe_xml_free(searchResults);
6057 return FALSE;
6060 column = purple_notify_searchresults_column_new(_("User name"));
6061 purple_notify_searchresults_column_add(results, column);
6063 column = purple_notify_searchresults_column_new(_("Name"));
6064 purple_notify_searchresults_column_add(results, column);
6066 column = purple_notify_searchresults_column_new(_("Company"));
6067 purple_notify_searchresults_column_add(results, column);
6069 column = purple_notify_searchresults_column_new(_("Country"));
6070 purple_notify_searchresults_column_add(results, column);
6072 column = purple_notify_searchresults_column_new(_("Email"));
6073 purple_notify_searchresults_column_add(results, column);
6075 for (mrow = sipe_xml_child(searchResults, "Body/Array/row"); mrow; mrow = sipe_xml_twin(mrow)) {
6076 GList *row = NULL;
6078 gchar **uri_parts = g_strsplit(sipe_xml_attribute(mrow, "uri"), ":", 2);
6079 row = g_list_append(row, g_strdup(uri_parts[1]));
6080 g_strfreev(uri_parts);
6082 row = g_list_append(row, g_strdup(sipe_xml_attribute(mrow, "displayName")));
6083 row = g_list_append(row, g_strdup(sipe_xml_attribute(mrow, "company")));
6084 row = g_list_append(row, g_strdup(sipe_xml_attribute(mrow, "country")));
6085 row = g_list_append(row, g_strdup(sipe_xml_attribute(mrow, "email")));
6087 purple_notify_searchresults_row_add(results, row);
6088 match_count++;
6091 if ((mrow = sipe_xml_child(searchResults, "Body/directorySearch/moreAvailable")) != NULL) {
6092 char *data = sipe_xml_data(mrow);
6093 more = (g_strcasecmp(data, "true") == 0);
6094 g_free(data);
6097 secondary = g_strdup_printf(
6098 dngettext(PACKAGE_NAME,
6099 "Found %d contact%s:",
6100 "Found %d contacts%s:", match_count),
6101 match_count, more ? _(" (more matched your query)") : "");
6103 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_IM, sipe_searchresults_im_buddy);
6104 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_ADD, sipe_searchresults_add_buddy);
6105 purple_notify_searchresults(sip->gc, NULL, NULL, secondary, results, NULL, NULL);
6107 g_free(secondary);
6108 sipe_xml_free(searchResults);
6109 return TRUE;
6112 void sipe_search_contact_with_cb(PurpleConnection *gc, PurpleRequestFields *fields)
6114 GList *entries = purple_request_field_group_get_fields(purple_request_fields_get_groups(fields)->data);
6115 gchar **attrs = g_new(gchar *, g_list_length(entries) + 1);
6116 unsigned i = 0;
6118 if (!attrs) return;
6120 do {
6121 PurpleRequestField *field = entries->data;
6122 const char *id = purple_request_field_get_id(field);
6123 const char *value = purple_request_field_string_get_value(field);
6125 SIPE_DEBUG_INFO("sipe_search_contact_with_cb: %s = '%s'", id, value ? value : "");
6127 if (value != NULL) attrs[i++] = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, id, value);
6128 } while ((entries = g_list_next(entries)) != NULL);
6129 attrs[i] = NULL;
6131 if (i > 0) {
6132 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
6133 gchar *domain_uri = sip_uri_from_name(sipe_private->public.sip_domain);
6134 gchar *query = g_strjoinv(NULL, attrs);
6135 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 100, query);
6136 SIPE_DEBUG_INFO("sipe_search_contact_with_cb: body:\n%s", body ? body : "");
6137 send_soap_request_with_cb(sipe_private, domain_uri, body,
6138 process_search_contact_response, NULL);
6139 g_free(domain_uri);
6140 g_free(body);
6141 g_free(query);
6144 g_strfreev(attrs);
6147 static void sipe_publish_get_cat_state_user_to_clear(SIPE_UNUSED_PARAMETER const char *name,
6148 gpointer value,
6149 GString* str)
6151 struct sipe_publication *publication = value;
6153 g_string_append_printf( str,
6154 SIPE_PUB_XML_PUBLICATION_CLEAR,
6155 publication->category,
6156 publication->instance,
6157 publication->container,
6158 publication->version,
6159 "static");
6162 void sipe_core_reset_status(struct sipe_core_public *sipe_public)
6164 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
6165 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA;
6166 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) /* 2007+ */
6168 GString* str = g_string_new(NULL);
6169 gchar *publications;
6171 if (!sip->user_state_publications || g_hash_table_size(sip->user_state_publications) == 0) {
6172 SIPE_DEBUG_INFO_NOFORMAT("sipe_reset_status: no userState publications, exiting.");
6173 return;
6176 g_hash_table_foreach(sip->user_state_publications, (GHFunc)sipe_publish_get_cat_state_user_to_clear, str);
6177 publications = g_string_free(str, FALSE);
6179 send_presence_publish(sipe_private, publications);
6180 g_free(publications);
6182 else /* 2005 */
6184 send_presence_soap0(sipe_private, FALSE, TRUE);
6188 /** for Access levels menu */
6189 #define INDENT_FMT " %s"
6191 /** Member is directly placed to access level container.
6192 * For example SIP URI of user is in the container.
6194 #define INDENT_MARKED_FMT "* %s"
6196 /** Member is indirectly belong to access level container.
6197 * For example 'sameEnterprise' is in the container and user
6198 * belongs to that same enterprise.
6200 #define INDENT_MARKED_INHERITED_FMT "= %s"
6202 GSList *sipe_core_buddy_info(struct sipe_core_public *sipe_public,
6203 const gchar *name,
6204 const gchar *status_name,
6205 gboolean is_online)
6207 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
6208 gchar *note = NULL;
6209 gboolean is_oof_note = FALSE;
6210 const gchar *activity = NULL;
6211 gchar *calendar = NULL;
6212 const gchar *meeting_subject = NULL;
6213 const gchar *meeting_location = NULL;
6214 gchar *access_text = NULL;
6215 GSList *info = NULL;
6217 #define SIPE_ADD_BUDDY_INFO_COMMON(l, t) \
6219 struct sipe_buddy_info *sbi = g_malloc(sizeof(struct sipe_buddy_info)); \
6220 sbi->label = (l); \
6221 sbi->text = (t); \
6222 info = g_slist_append(info, sbi); \
6224 #define SIPE_ADD_BUDDY_INFO(l, t) SIPE_ADD_BUDDY_INFO_COMMON((l), g_markup_escape_text((t), -1))
6225 #define SIPE_ADD_BUDDY_INFO_NOESCAPE(l, t) SIPE_ADD_BUDDY_INFO_COMMON((l), (t))
6227 if (sipe_public) { //happens on pidgin exit
6228 struct sipe_buddy *sbuddy = g_hash_table_lookup(sipe_private->buddies, name);
6229 if (sbuddy) {
6230 note = sbuddy->note;
6231 is_oof_note = sbuddy->is_oof_note;
6232 activity = sbuddy->activity;
6233 calendar = sipe_cal_get_description(sbuddy);
6234 meeting_subject = sbuddy->meeting_subject;
6235 meeting_location = sbuddy->meeting_location;
6237 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
6238 gboolean is_group_access = FALSE;
6239 const int container_id = sipe_find_access_level(sipe_private, "user", sipe_get_no_sip_uri(name), &is_group_access);
6240 const char *access_level = sipe_get_access_level_name(container_id);
6241 access_text = is_group_access ?
6242 g_strdup(access_level) :
6243 g_strdup_printf(INDENT_MARKED_FMT, access_level);
6247 //Layout
6248 if (is_online)
6250 const gchar *status_str = activity ? activity : status_name;
6252 SIPE_ADD_BUDDY_INFO(_("Status"), status_str);
6254 if (is_online && !is_empty(calendar))
6256 SIPE_ADD_BUDDY_INFO(_("Calendar"), calendar);
6258 g_free(calendar);
6259 if (!is_empty(meeting_location))
6261 SIPE_DEBUG_INFO("sipe_tooltip_text: %s meeting location: '%s'", name, meeting_location);
6262 SIPE_ADD_BUDDY_INFO(_("Meeting in"), meeting_location);
6264 if (!is_empty(meeting_subject))
6266 SIPE_DEBUG_INFO("sipe_tooltip_text: %s meeting subject: '%s'", name, meeting_subject);
6267 SIPE_ADD_BUDDY_INFO(_("Meeting about"), meeting_subject);
6269 if (note)
6271 SIPE_DEBUG_INFO("sipe_tooltip_text: %s note: '%s'", name, note);
6272 SIPE_ADD_BUDDY_INFO_NOESCAPE(is_oof_note ? _("Out of office note") : _("Note"),
6273 g_strdup_printf("<i>%s</i>", note));
6275 if (access_text) {
6276 SIPE_ADD_BUDDY_INFO(_("Access level"), access_text);
6277 g_free(access_text);
6280 return(info);
6283 static PurpleBuddy *
6284 purple_blist_add_buddy_clone(PurpleGroup * group, PurpleBuddy * buddy)
6286 PurpleBuddy *clone;
6287 const gchar *server_alias, *email;
6288 const PurpleStatus *status = purple_presence_get_active_status(purple_buddy_get_presence(buddy));
6290 clone = purple_buddy_new(buddy->account, buddy->name, buddy->alias);
6292 purple_blist_add_buddy(clone, NULL, group, NULL);
6294 server_alias = purple_buddy_get_server_alias(buddy);
6295 if (server_alias) {
6296 purple_blist_server_alias_buddy(clone, server_alias);
6299 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
6300 if (email) {
6301 purple_blist_node_set_string(&clone->node, EMAIL_PROP, email);
6304 purple_presence_set_status_active(purple_buddy_get_presence(clone), purple_status_get_id(status), TRUE);
6305 //for UI to update;
6306 purple_prpl_got_user_status(clone->account, clone->name, purple_status_get_id(status), NULL);
6307 return clone;
6310 static void
6311 sipe_buddy_menu_copy_to_cb(PurpleBlistNode *node, const char *group_name)
6313 PurpleBuddy *buddy, *b;
6314 PurpleConnection *gc;
6315 PurpleGroup * group = purple_find_group(group_name);
6317 g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node));
6319 buddy = (PurpleBuddy *)node;
6321 SIPE_DEBUG_INFO("sipe_buddy_menu_copy_to_cb: copying %s to %s", buddy->name, group_name);
6322 gc = purple_account_get_connection(buddy->account);
6324 b = purple_find_buddy_in_group(buddy->account, buddy->name, group);
6325 if (!b){
6326 b = purple_blist_add_buddy_clone(group, buddy);
6329 sipe_add_buddy(gc, b, group);
6332 static void
6333 sipe_buddy_menu_chat_new_cb(PurpleBuddy *buddy)
6335 struct sipe_core_private *sipe_private = PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE;
6337 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_new_cb: buddy->name=%s", buddy->name);
6339 /* 2007+ conference */
6340 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007))
6342 sipe_conf_add(sipe_private, buddy->name);
6344 else /* 2005- multiparty chat */
6346 gchar *self = sip_uri_self(sipe_private);
6347 struct sip_session *session;
6349 session = sipe_session_add_chat(sipe_private,
6350 NULL,
6351 TRUE,
6352 self);
6353 session->chat_session->backend = sipe_backend_chat_create(SIPE_CORE_PUBLIC,
6354 session->chat_session,
6355 session->chat_session->title,
6356 self);
6357 g_free(self);
6359 sipe_invite(sipe_private, session, buddy->name, NULL, NULL, NULL, FALSE);
6364 * For 2007+ conference only.
6366 static void
6367 sipe_buddy_menu_chat_make_leader_cb(PurpleBuddy *buddy,
6368 struct sipe_chat_session *chat_session)
6370 struct sipe_core_private *sipe_private = PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE;
6371 struct sip_session *session;
6373 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_make_leader_cb: buddy->name=%s", buddy->name);
6374 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_make_leader_cb: chat_title=%s", chat_session->title);
6376 session = sipe_session_find_chat(sipe_private, chat_session);
6378 sipe_conf_modify_user_role(sipe_private, session, buddy->name);
6382 * For 2007+ conference only.
6384 static void
6385 sipe_buddy_menu_chat_remove_cb(PurpleBuddy *buddy,
6386 struct sipe_chat_session *chat_session)
6388 struct sipe_core_private *sipe_private = PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE;
6389 struct sip_session *session;
6391 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_remove_cb: buddy->name=%s", buddy->name);
6392 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_remove_cb: chat_title=%s", chat_session->title);
6394 session = sipe_session_find_chat(sipe_private, chat_session);
6396 sipe_conf_delete_user(sipe_private, session, buddy->name);
6399 static void
6400 sipe_buddy_menu_chat_invite_cb(PurpleBuddy *buddy,
6401 struct sipe_chat_session *chat_session)
6403 struct sipe_core_private *sipe_private = PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE;
6405 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_invite_cb: buddy->name=%s", buddy->name);
6406 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_invite_cb: chat_title=%s", chat_session->title);
6408 sipe_core_chat_invite(SIPE_CORE_PUBLIC, chat_session, buddy->name);
6411 static void
6412 sipe_buddy_menu_make_call_cb(PurpleBuddy *buddy, const char *phone)
6414 struct sipe_core_private *sipe_private = PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE;
6416 SIPE_DEBUG_INFO("sipe_buddy_menu_make_call_cb: buddy->name=%s", buddy->name);
6417 if (phone) {
6418 char *tel_uri = sip_to_tel_uri(phone);
6420 SIPE_DEBUG_INFO("sipe_buddy_menu_make_call_cb: going to call number: %s", tel_uri ? tel_uri : "");
6421 sip_csta_make_call(sipe_private, tel_uri);
6423 g_free(tel_uri);
6427 static void
6428 sipe_buddy_menu_access_level_help_cb(PurpleBuddy *buddy)
6430 /** Translators: replace with URL to localized page
6431 * If it doesn't exist copy the original URL */
6432 purple_notify_uri(buddy->account->gc, _("https://sourceforge.net/apps/mediawiki/sipe/index.php?title=Access_Levels"));
6435 static void
6436 sipe_buddy_menu_send_email_cb(PurpleBuddy *buddy)
6438 const gchar *email;
6439 SIPE_DEBUG_INFO("sipe_buddy_menu_send_email_cb: buddy->name=%s", buddy->name);
6441 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
6442 if (email)
6444 char *command_line = g_strdup_printf(
6445 #ifdef _WIN32
6446 "cmd /c start"
6447 #else
6448 "xdg-email"
6449 #endif
6450 " mailto:%s", email);
6451 SIPE_DEBUG_INFO("sipe_buddy_menu_send_email_cb: going to call email client: %s", command_line);
6453 g_spawn_command_line_async(command_line, NULL);
6454 g_free(command_line);
6456 else
6458 SIPE_DEBUG_INFO("sipe_buddy_menu_send_email_cb: no email address stored for buddy=%s", buddy->name);
6462 static void
6463 sipe_buddy_menu_access_level_cb(PurpleBuddy *buddy,
6464 struct sipe_container *container)
6466 struct sipe_core_private *sipe_private = PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE;
6467 struct sipe_container_member *member;
6469 if (!container || !container->members) return;
6471 member = ((struct sipe_container_member *)container->members->data);
6473 if (!member->type) return;
6475 SIPE_DEBUG_INFO("sipe_buddy_menu_access_level_cb: container->id=%d, member->type=%s, member->value=%s",
6476 container->id, member->type, member->value ? member->value : "");
6478 sipe_change_access_level(sipe_private, container->id, member->type, member->value);
6481 static GList *
6482 sipe_get_access_control_menu(struct sipe_core_private *sipe_private,
6483 const char* uri);
6486 * A menu which appear when right-clicking on buddy in contact list.
6488 GList *
6489 sipe_buddy_menu(PurpleBuddy *buddy)
6491 PurpleBlistNode *g_node;
6492 PurpleGroup *gr_parent;
6493 PurpleMenuAction *act;
6494 GList *menu = NULL;
6495 GList *menu_groups = NULL;
6496 struct sipe_core_private *sipe_private = PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE;
6497 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
6498 const char *email;
6499 gchar *self = sip_uri_self(sipe_private);
6501 SIPE_SESSION_FOREACH {
6502 if (!sipe_strcase_equal(self, buddy->name) && session->chat_session)
6504 struct sipe_chat_session *chat_session = session->chat_session;
6505 gboolean is_conf = (chat_session->type == SIPE_CHAT_TYPE_CONFERENCE);
6507 if (sipe_backend_chat_find(chat_session->backend, buddy->name))
6509 gboolean conf_op = sipe_backend_chat_is_operator(chat_session->backend, self);
6511 if (is_conf
6512 && !sipe_backend_chat_is_operator(chat_session->backend, buddy->name) /* Not conf OP */
6513 && conf_op) /* We are a conf OP */
6515 gchar *label = g_strdup_printf(_("Make leader of '%s'"),
6516 chat_session->title);
6517 act = purple_menu_action_new(label,
6518 PURPLE_CALLBACK(sipe_buddy_menu_chat_make_leader_cb),
6519 chat_session, NULL);
6520 g_free(label);
6521 menu = g_list_prepend(menu, act);
6524 if (is_conf
6525 && conf_op) /* We are a conf OP */
6527 gchar *label = g_strdup_printf(_("Remove from '%s'"),
6528 chat_session->title);
6529 act = purple_menu_action_new(label,
6530 PURPLE_CALLBACK(sipe_buddy_menu_chat_remove_cb),
6531 chat_session, NULL);
6532 g_free(label);
6533 menu = g_list_prepend(menu, act);
6536 else
6538 if (!is_conf
6539 || (is_conf && !session->locked))
6541 gchar *label = g_strdup_printf(_("Invite to '%s'"),
6542 chat_session->title);
6543 act = purple_menu_action_new(label,
6544 PURPLE_CALLBACK(sipe_buddy_menu_chat_invite_cb),
6545 chat_session, NULL);
6546 g_free(label);
6547 menu = g_list_prepend(menu, act);
6551 } SIPE_SESSION_FOREACH_END;
6553 act = purple_menu_action_new(_("New chat"),
6554 PURPLE_CALLBACK(sipe_buddy_menu_chat_new_cb),
6555 NULL, NULL);
6556 menu = g_list_prepend(menu, act);
6558 if (sip->csta && !sip->csta->line_status) {
6559 const char *phone;
6560 const char *phone_disp_str;
6561 gchar *tmp = NULL;
6562 /* work phone */
6563 phone = purple_blist_node_get_string(&buddy->node, PHONE_PROP);
6564 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_DISPLAY_PROP);
6565 if (phone) {
6566 gchar *label = g_strdup_printf(_("Work %s"),
6567 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
6568 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
6569 g_free(tmp);
6570 tmp = NULL;
6571 g_free(label);
6572 menu = g_list_prepend(menu, act);
6575 /* mobile phone */
6576 phone = purple_blist_node_get_string(&buddy->node, PHONE_MOBILE_PROP);
6577 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_MOBILE_DISPLAY_PROP);
6578 if (phone) {
6579 gchar *label = g_strdup_printf(_("Mobile %s"),
6580 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
6581 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
6582 g_free(tmp);
6583 tmp = NULL;
6584 g_free(label);
6585 menu = g_list_prepend(menu, act);
6588 /* home phone */
6589 phone = purple_blist_node_get_string(&buddy->node, PHONE_HOME_PROP);
6590 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_HOME_DISPLAY_PROP);
6591 if (phone) {
6592 gchar *label = g_strdup_printf(_("Home %s"),
6593 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
6594 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
6595 g_free(tmp);
6596 tmp = NULL;
6597 g_free(label);
6598 menu = g_list_prepend(menu, act);
6601 /* other phone */
6602 phone = purple_blist_node_get_string(&buddy->node, PHONE_OTHER_PROP);
6603 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_OTHER_DISPLAY_PROP);
6604 if (phone) {
6605 gchar *label = g_strdup_printf(_("Other %s"),
6606 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
6607 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
6608 g_free(tmp);
6609 tmp = NULL;
6610 g_free(label);
6611 menu = g_list_prepend(menu, act);
6614 /* custom1 phone */
6615 phone = purple_blist_node_get_string(&buddy->node, PHONE_CUSTOM1_PROP);
6616 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_CUSTOM1_DISPLAY_PROP);
6617 if (phone) {
6618 gchar *label = g_strdup_printf(_("Custom1 %s"),
6619 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
6620 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
6621 g_free(tmp);
6622 tmp = NULL;
6623 g_free(label);
6624 menu = g_list_prepend(menu, act);
6628 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
6629 if (email) {
6630 act = purple_menu_action_new(_("Send email..."),
6631 PURPLE_CALLBACK(sipe_buddy_menu_send_email_cb),
6632 NULL, NULL);
6633 menu = g_list_prepend(menu, act);
6636 /* Access Level */
6637 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
6638 GList *menu_access_levels = sipe_get_access_control_menu(sipe_private, buddy->name);
6640 act = purple_menu_action_new(_("Access level"),
6641 NULL,
6642 NULL, menu_access_levels);
6643 menu = g_list_prepend(menu, act);
6646 /* Copy to */
6647 gr_parent = purple_buddy_get_group(buddy);
6648 for (g_node = purple_blist_get_root(); g_node; g_node = g_node->next) {
6649 PurpleGroup *group;
6651 if (g_node->type != PURPLE_BLIST_GROUP_NODE)
6652 continue;
6654 group = (PurpleGroup *)g_node;
6655 if (group == gr_parent)
6656 continue;
6658 if (purple_find_buddy_in_group(buddy->account, buddy->name, group))
6659 continue;
6661 act = purple_menu_action_new(purple_group_get_name(group),
6662 PURPLE_CALLBACK(sipe_buddy_menu_copy_to_cb),
6663 group->name, NULL);
6664 menu_groups = g_list_prepend(menu_groups, act);
6666 menu_groups = g_list_reverse(menu_groups);
6668 act = purple_menu_action_new(_("Copy to"),
6669 NULL,
6670 NULL, menu_groups);
6671 menu = g_list_prepend(menu, act);
6673 menu = g_list_reverse(menu);
6675 g_free(self);
6676 return menu;
6679 static void
6680 sipe_ask_access_domain_cb(PurpleConnection *gc, PurpleRequestFields *fields)
6682 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
6683 const char *domain = purple_request_fields_get_string(fields, "access_domain");
6684 int index = purple_request_fields_get_choice(fields, "container_id");
6685 /* move Blocked first */
6686 int i = (index == 4) ? 0 : index + 1;
6687 int container_id = containers[i];
6689 SIPE_DEBUG_INFO("sipe_ask_access_domain_cb: domain=%s, container_id=(%d)%d", domain ? domain : "", index, container_id);
6691 sipe_change_access_level(sipe_private, container_id, "domain", domain);
6694 static void
6695 sipe_ask_access_domain(struct sipe_core_private *sipe_private)
6697 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
6698 PurpleAccount *account = sip->account;
6699 PurpleConnection *gc = sip->gc;
6700 PurpleRequestFields *fields;
6701 PurpleRequestFieldGroup *g;
6702 PurpleRequestField *f;
6704 fields = purple_request_fields_new();
6706 g = purple_request_field_group_new(NULL);
6707 f = purple_request_field_string_new("access_domain", _("Domain"), "partner-company.com", FALSE);
6708 purple_request_field_set_required(f, TRUE);
6709 purple_request_field_group_add_field(g, f);
6711 f = purple_request_field_choice_new("container_id", _("Access level"), 0);
6712 purple_request_field_choice_add(f, _("Personal")); /* index 0 */
6713 purple_request_field_choice_add(f, _("Team"));
6714 purple_request_field_choice_add(f, _("Company"));
6715 purple_request_field_choice_add(f, _("Public"));
6716 purple_request_field_choice_add(f, _("Blocked")); /* index 4 */
6717 purple_request_field_choice_set_default_value(f, 3); /* index */
6718 purple_request_field_set_required(f, TRUE);
6719 purple_request_field_group_add_field(g, f);
6721 purple_request_fields_add_group(fields, g);
6723 purple_request_fields(gc, _("Add new domain"),
6724 _("Add new domain"), NULL, fields,
6725 _("Add"), G_CALLBACK(sipe_ask_access_domain_cb),
6726 _("Cancel"), NULL,
6727 account, NULL, NULL, gc);
6730 static void
6731 sipe_buddy_menu_access_level_add_domain_cb(PurpleBuddy *buddy)
6733 sipe_ask_access_domain(PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE);
6737 * Workaround for missing libpurple API to release resources allocated
6738 * during blist_node_menu() callback. See also:
6740 * <http://developer.pidgin.im/ticket/12597>
6742 * We remember all memory blocks in a list and deallocate them when
6744 * - the next time we enter the callback, or
6745 * - the account is disconnected
6747 * That means that after the buddy menu has been closed we have unused
6748 * resources but at least we don't leak them anymore...
6750 static void
6751 sipe_blist_menu_free_containers(struct sipe_core_private *sipe_private)
6753 GSList *entry = sipe_private->blist_menu_containers;
6754 while (entry) {
6755 free_container(entry->data);
6756 entry = entry->next;
6758 g_slist_free(sipe_private->blist_menu_containers);
6759 sipe_private->blist_menu_containers = NULL;
6762 static void
6763 sipe_blist_menu_remember_container(struct sipe_core_private *sipe_private,
6764 struct sipe_container *container)
6766 sipe_private->blist_menu_containers = g_slist_prepend(sipe_private->blist_menu_containers,
6767 container);
6770 static GList *
6771 sipe_get_access_levels_menu(struct sipe_core_private *sipe_private,
6772 const char* member_type,
6773 const char* member_value,
6774 const gboolean extra_menu)
6776 GList *menu_access_levels = NULL;
6777 unsigned int i;
6778 char *menu_name;
6779 PurpleMenuAction *act;
6780 struct sipe_container *container;
6781 struct sipe_container_member *member;
6782 gboolean is_group_access = FALSE;
6783 int container_id = sipe_find_access_level(sipe_private, member_type, member_value, &is_group_access);
6785 for (i = 1; i <= CONTAINERS_LEN; i++) {
6786 /* to put Blocked level last in menu list.
6787 * Blocked should remaim in the first place in the containers[] array.
6789 unsigned int j = (i == CONTAINERS_LEN) ? 0 : i;
6790 const char *acc_level_name = sipe_get_access_level_name(containers[j]);
6792 container = g_new0(struct sipe_container, 1);
6793 member = g_new0(struct sipe_container_member, 1);
6794 container->id = containers[j];
6795 container->members = g_slist_append(container->members, member);
6796 member->type = g_strdup(member_type);
6797 member->value = g_strdup(member_value);
6799 /* libpurple memory leak workaround */
6800 sipe_blist_menu_remember_container(sipe_private, container);
6802 /* current container/access level */
6803 if (((int)containers[j]) == container_id) {
6804 menu_name = is_group_access ?
6805 g_strdup_printf(INDENT_MARKED_INHERITED_FMT, acc_level_name) :
6806 g_strdup_printf(INDENT_MARKED_FMT, acc_level_name);
6807 } else {
6808 menu_name = g_strdup_printf(INDENT_FMT, acc_level_name);
6811 act = purple_menu_action_new(menu_name,
6812 PURPLE_CALLBACK(sipe_buddy_menu_access_level_cb),
6813 container, NULL);
6814 g_free(menu_name);
6815 menu_access_levels = g_list_prepend(menu_access_levels, act);
6818 if (extra_menu && (container_id >= 0)) {
6819 /* separator */
6820 act = purple_menu_action_new(" --------------", NULL, NULL, NULL);
6821 menu_access_levels = g_list_prepend(menu_access_levels, act);
6823 if (!is_group_access) {
6824 container = g_new0(struct sipe_container, 1);
6825 member = g_new0(struct sipe_container_member, 1);
6826 container->id = -1;
6827 container->members = g_slist_append(container->members, member);
6828 member->type = g_strdup(member_type);
6829 member->value = g_strdup(member_value);
6831 /* libpurple memory leak workaround */
6832 sipe_blist_menu_remember_container(sipe_private, container);
6834 /* Translators: remove (clear) previously assigned access level */
6835 menu_name = g_strdup_printf(INDENT_FMT, _("Unspecify"));
6836 act = purple_menu_action_new(menu_name,
6837 PURPLE_CALLBACK(sipe_buddy_menu_access_level_cb),
6838 container, NULL);
6839 g_free(menu_name);
6840 menu_access_levels = g_list_prepend(menu_access_levels, act);
6844 menu_access_levels = g_list_reverse(menu_access_levels);
6845 return menu_access_levels;
6848 static GList *
6849 sipe_get_access_groups_menu(struct sipe_core_private *sipe_private)
6851 GList *menu_access_groups = NULL;
6852 PurpleMenuAction *act;
6853 GSList *access_domains = NULL;
6854 GSList *entry;
6855 char *menu_name;
6856 char *domain;
6858 act = purple_menu_action_new(_("People in my company"),
6859 NULL,
6860 NULL, sipe_get_access_levels_menu(sipe_private, "sameEnterprise", NULL, FALSE));
6861 menu_access_groups = g_list_prepend(menu_access_groups, act);
6863 /* this is original name, don't edit */
6864 act = purple_menu_action_new(_("People in domains connected with my company"),
6865 NULL,
6866 NULL, sipe_get_access_levels_menu(sipe_private, "federated", NULL, FALSE));
6867 menu_access_groups = g_list_prepend(menu_access_groups, act);
6869 act = purple_menu_action_new(_("People in public domains"),
6870 NULL,
6871 NULL, sipe_get_access_levels_menu(sipe_private, "publicCloud", NULL, TRUE));
6872 menu_access_groups = g_list_prepend(menu_access_groups, act);
6874 access_domains = sipe_get_access_domains(sipe_private);
6875 entry = access_domains;
6876 while (entry) {
6877 domain = entry->data;
6879 menu_name = g_strdup_printf(_("People at %s"), domain);
6880 act = purple_menu_action_new(menu_name,
6881 NULL,
6882 NULL, sipe_get_access_levels_menu(sipe_private, "domain", g_strdup(domain), TRUE));
6883 menu_access_groups = g_list_prepend(menu_access_groups, act);
6884 g_free(menu_name);
6886 entry = entry->next;
6889 /* separator */
6890 /* People in domains connected with my company */
6891 act = purple_menu_action_new("-------------------------------------------", NULL, NULL, NULL);
6892 menu_access_groups = g_list_prepend(menu_access_groups, act);
6894 act = purple_menu_action_new(_("Add new domain..."),
6895 PURPLE_CALLBACK(sipe_buddy_menu_access_level_add_domain_cb),
6896 NULL, NULL);
6897 menu_access_groups = g_list_prepend(menu_access_groups, act);
6899 menu_access_groups = g_list_reverse(menu_access_groups);
6901 return menu_access_groups;
6904 static GList *
6905 sipe_get_access_control_menu(struct sipe_core_private *sipe_private,
6906 const char* uri)
6908 GList *menu_access_levels = NULL;
6909 GList *menu_access_groups = NULL;
6910 char *menu_name;
6911 PurpleMenuAction *act;
6913 /* libpurple memory leak workaround */
6914 sipe_blist_menu_free_containers(sipe_private);
6916 menu_access_levels = sipe_get_access_levels_menu(sipe_private, "user", sipe_get_no_sip_uri(uri), TRUE);
6918 menu_access_groups = sipe_get_access_groups_menu(sipe_private);
6920 menu_name = g_strdup_printf(INDENT_FMT, _("Access groups"));
6921 act = purple_menu_action_new(menu_name,
6922 NULL,
6923 NULL, menu_access_groups);
6924 g_free(menu_name);
6925 menu_access_levels = g_list_append(menu_access_levels, act);
6927 menu_name = g_strdup_printf(INDENT_FMT, _("Online help..."));
6928 act = purple_menu_action_new(menu_name,
6929 PURPLE_CALLBACK(sipe_buddy_menu_access_level_help_cb),
6930 NULL, NULL);
6931 g_free(menu_name);
6932 menu_access_levels = g_list_append(menu_access_levels, act);
6934 return menu_access_levels;
6937 static gboolean
6938 process_get_info_response(struct sipe_core_private *sipe_private,
6939 struct sipmsg *msg, struct transaction *trans)
6941 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
6942 char *uri = trans->payload->data;
6944 PurpleNotifyUserInfo *info;
6945 PurpleBuddy *pbuddy = NULL;
6946 struct sipe_buddy *sbuddy;
6947 const char *alias = NULL;
6948 char *device_name = NULL;
6949 char *server_alias = NULL;
6950 char *phone_number = NULL;
6951 char *email = NULL;
6952 const char *site;
6953 char *first_name = NULL;
6954 char *last_name = NULL;
6956 if (!sip) return FALSE;
6958 SIPE_DEBUG_INFO("Fetching %s's user info for %s", uri, sipe_private->username);
6960 pbuddy = purple_find_buddy((PurpleAccount *)sip->account, uri);
6961 alias = purple_buddy_get_local_alias(pbuddy);
6963 //will query buddy UA's capabilities and send answer to log
6964 sipe_options_request(sipe_private, uri);
6966 sbuddy = g_hash_table_lookup(sipe_private->buddies, uri);
6967 if (sbuddy) {
6968 device_name = sbuddy->device_name ? g_strdup(sbuddy->device_name) : NULL;
6971 info = purple_notify_user_info_new();
6973 if (msg->response != 200) {
6974 SIPE_DEBUG_INFO("process_get_info_response: SERVICE response is %d", msg->response);
6975 } else {
6976 sipe_xml *searchResults;
6977 const sipe_xml *mrow;
6979 SIPE_DEBUG_INFO("process_get_info_response: body:\n%s", msg->body ? msg->body : "");
6980 searchResults = sipe_xml_parse(msg->body, msg->bodylen);
6981 if (!searchResults) {
6982 SIPE_DEBUG_INFO_NOFORMAT("process_get_info_response: no parseable searchResults");
6983 } else if ((mrow = sipe_xml_child(searchResults, "Body/Array/row"))) {
6984 const char *value;
6985 server_alias = g_strdup(sipe_xml_attribute(mrow, "displayName"));
6986 email = g_strdup(sipe_xml_attribute(mrow, "email"));
6987 phone_number = g_strdup(sipe_xml_attribute(mrow, "phone"));
6989 /* For 2007 system we will take this from ContactCard -
6990 * it has cleaner tel: URIs at least
6992 if (!SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
6993 char *tel_uri = sip_to_tel_uri(phone_number);
6994 /* trims its parameters, so call first */
6995 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_DISPLAY_NAME, server_alias);
6996 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_EMAIL, email);
6997 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_WORK_PHONE, tel_uri);
6998 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_WORK_PHONE_DISPLAY, phone_number);
6999 g_free(tel_uri);
7002 if (server_alias && strlen(server_alias) > 0) {
7003 purple_notify_user_info_add_pair(info, _("Display name"), server_alias);
7005 if ((value = sipe_xml_attribute(mrow, "title")) && strlen(value) > 0) {
7006 purple_notify_user_info_add_pair(info, _("Job title"), value);
7008 if ((value = sipe_xml_attribute(mrow, "office")) && strlen(value) > 0) {
7009 purple_notify_user_info_add_pair(info, _("Office"), value);
7011 if (phone_number && strlen(phone_number) > 0) {
7012 purple_notify_user_info_add_pair(info, _("Business phone"), phone_number);
7014 if ((value = sipe_xml_attribute(mrow, "company")) && strlen(value) > 0) {
7015 purple_notify_user_info_add_pair(info, _("Company"), value);
7017 if ((value = sipe_xml_attribute(mrow, "city")) && strlen(value) > 0) {
7018 purple_notify_user_info_add_pair(info, _("City"), value);
7020 if ((value = sipe_xml_attribute(mrow, "state")) && strlen(value) > 0) {
7021 purple_notify_user_info_add_pair(info, _("State"), value);
7023 if ((value = sipe_xml_attribute(mrow, "country")) && strlen(value) > 0) {
7024 purple_notify_user_info_add_pair(info, _("Country"), value);
7026 if (email && strlen(email) > 0) {
7027 purple_notify_user_info_add_pair(info, _("Email address"), email);
7031 sipe_xml_free(searchResults);
7034 purple_notify_user_info_add_section_break(info);
7036 if (is_empty(server_alias)) {
7037 g_free(server_alias);
7038 server_alias = g_strdup(purple_buddy_get_server_alias(pbuddy));
7039 if (server_alias) {
7040 purple_notify_user_info_add_pair(info, _("Display name"), server_alias);
7044 /* present alias if it differs from server alias */
7045 if (alias && !sipe_strequal(alias, server_alias))
7047 purple_notify_user_info_add_pair(info, _("Alias"), alias);
7050 if (is_empty(email)) {
7051 g_free(email);
7052 email = g_strdup(purple_blist_node_get_string(&pbuddy->node, EMAIL_PROP));
7053 if (email) {
7054 purple_notify_user_info_add_pair(info, _("Email address"), email);
7058 site = purple_blist_node_get_string(&pbuddy->node, SITE_PROP);
7059 if (site) {
7060 purple_notify_user_info_add_pair(info, _("Site"), site);
7063 sipe_get_first_last_names(sipe_private, uri, &first_name, &last_name);
7064 if (first_name && last_name) {
7065 char *link = g_strconcat("http://www.linkedin.com/pub/dir/", first_name, "/", last_name, NULL);
7067 purple_notify_user_info_add_pair(info, _("Find on LinkedIn"), link);
7068 g_free(link);
7070 g_free(first_name);
7071 g_free(last_name);
7073 if (device_name) {
7074 purple_notify_user_info_add_pair(info, _("Device"), device_name);
7077 /* show a buddy's user info in a nice dialog box */
7078 purple_notify_userinfo(sip->gc, /* connection the buddy info came through */
7079 uri, /* buddy's URI */
7080 info, /* body */
7081 NULL, /* callback called when dialog closed */
7082 NULL); /* userdata for callback */
7084 g_free(phone_number);
7085 g_free(server_alias);
7086 g_free(email);
7087 g_free(device_name);
7089 return TRUE;
7093 * AD search first, LDAP based
7095 void sipe_get_info(PurpleConnection *gc, const char *username)
7097 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
7098 gchar *domain_uri = sip_uri_from_name(sipe_private->public.sip_domain);
7099 char *row = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, "msRTCSIP-PrimaryUserAddress", username);
7100 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 1, row);
7101 struct transaction_payload *payload = g_new0(struct transaction_payload, 1);
7103 payload->destroy = g_free;
7104 payload->data = g_strdup(username);
7106 SIPE_DEBUG_INFO("sipe_get_contact_data: body:\n%s", body ? body : "");
7107 send_soap_request_with_cb(sipe_private, domain_uri, body,
7108 process_get_info_response, payload);
7109 g_free(domain_uri);
7110 g_free(body);
7111 g_free(row);
7115 Local Variables:
7116 mode: c
7117 c-file-style: "bsd"
7118 indent-tabs-mode: t
7119 tab-width: 8
7120 End: