add "603 Decline" response to undelivered message handling
[siplcs.git] / src / core / sipe.c
blob29eb4775189d7b31eb33c3a130b216292619cb4f
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 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
296 sipe_xml *watchers;
297 const sipe_xml *watcher;
298 // Ensure it's either not a response (eg it's a BENOTIFY) or that it's a 200 OK response
299 if (msg->response != 0 && msg->response != 200) return;
301 if (msg->bodylen == 0 || msg->body == NULL || sipe_strequal(sipmsg_find_header(msg, "Event"), "msrtc.wpending")) return;
303 watchers = sipe_xml_parse(msg->body, msg->bodylen);
304 if (!watchers) return;
306 for (watcher = sipe_xml_child(watchers, "watcher"); watcher; watcher = sipe_xml_twin(watcher)) {
307 gchar * remote_user = g_strdup(sipe_xml_attribute(watcher, "uri"));
308 gchar * alias = g_strdup(sipe_xml_attribute(watcher, "displayName"));
309 gboolean on_list = g_hash_table_lookup(sipe_private->buddies, remote_user) != NULL;
311 // TODO pull out optional displayName to pass as alias
312 if (remote_user) {
313 struct sipe_auth_job * job = g_new0(struct sipe_auth_job, 1);
314 job->who = remote_user;
315 job->sipe_private = sipe_private;
316 purple_account_request_authorization(
317 sip->account,
318 remote_user,
319 _("you"), /* id */
320 alias,
321 NULL, /* message */
322 on_list,
323 sipe_auth_user_cb,
324 sipe_deny_user_cb,
325 (void *) job);
330 sipe_xml_free(watchers);
331 return;
334 static void
335 sipe_group_add(struct sipe_core_private *sipe_private,
336 struct sipe_group * group)
338 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
339 PurpleGroup * purple_group = purple_find_group(group->name);
340 if (!purple_group) {
341 purple_group = purple_group_new(group->name);
342 purple_blist_add_group(purple_group, NULL);
345 if (purple_group) {
346 group->purple_group = purple_group;
347 sip->groups = g_slist_append(sip->groups, group);
348 SIPE_DEBUG_INFO("added group %s (id %d)", group->name, group->id);
349 } else {
350 SIPE_DEBUG_INFO("did not add group %s", group->name ? group->name : "");
354 static struct sipe_group *sipe_group_find_by_id(struct sipe_core_private *sipe_private,
355 int id)
357 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
358 struct sipe_group *group;
359 GSList *entry;
360 if (sip == NULL) {
361 return NULL;
364 entry = sip->groups;
365 while (entry) {
366 group = entry->data;
367 if (group->id == id) {
368 return group;
370 entry = entry->next;
372 return NULL;
375 static struct sipe_group *sipe_group_find_by_name(struct sipe_core_private *sipe_private,
376 const gchar * name)
378 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
379 struct sipe_group *group;
380 GSList *entry;
381 if (!sip || !name) {
382 return NULL;
385 entry = sip->groups;
386 while (entry) {
387 group = entry->data;
388 if (sipe_strequal(group->name, name)) {
389 return group;
391 entry = entry->next;
393 return NULL;
396 static void
397 sipe_group_rename(struct sipe_core_private *sipe_private,
398 struct sipe_group *group,
399 gchar *name)
401 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
402 gchar *body;
403 SIPE_DEBUG_INFO("Renaming group %s to %s", group->name, name);
404 body = g_markup_printf_escaped(SIPE_SOAP_MOD_GROUP, group->id, name, sip->contacts_delta++);
405 send_soap_request(sipe_private, body);
406 g_free(body);
407 g_free(group->name);
408 group->name = g_strdup(name);
412 * Only appends if no such value already stored.
413 * Like Set in Java.
415 GSList * slist_insert_unique_sorted(GSList *list, gpointer data, GCompareFunc func) {
416 GSList * res = list;
417 if (!g_slist_find_custom(list, data, func)) {
418 res = g_slist_insert_sorted(list, data, func);
420 return res;
423 static int
424 sipe_group_compare(struct sipe_group *group1, struct sipe_group *group2) {
425 return group1->id - group2->id;
429 * Returns string like "2 4 7 8" - group ids buddy belong to.
431 static gchar *
432 sipe_get_buddy_groups_string (struct sipe_buddy *buddy) {
433 int i = 0;
434 gchar *res;
435 //creating array from GList, converting int to gchar*
436 gchar **ids_arr = g_new(gchar *, g_slist_length(buddy->groups) + 1);
437 GSList *entry = buddy->groups;
439 if (!ids_arr) return NULL;
441 while (entry) {
442 struct sipe_group * group = entry->data;
443 ids_arr[i] = g_strdup_printf("%d", group->id);
444 entry = entry->next;
445 i++;
447 ids_arr[i] = NULL;
448 res = g_strjoinv(" ", ids_arr);
449 g_strfreev(ids_arr);
450 return res;
454 * Sends buddy update to server
456 void
457 sipe_core_group_set_user(struct sipe_core_public *sipe_public, const gchar * who)
459 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA;
460 struct sipe_buddy *buddy = g_hash_table_lookup(SIPE_CORE_PRIVATE->buddies, who);
461 PurpleBuddy *purple_buddy = purple_find_buddy (sip->account, who);
463 if (buddy && purple_buddy) {
464 const char *alias = purple_buddy_get_alias(purple_buddy);
465 gchar *groups = sipe_get_buddy_groups_string(buddy);
466 if (groups) {
467 gchar *body;
468 SIPE_DEBUG_INFO("Saving buddy %s with alias %s and groups %s", who, alias, groups);
470 body = g_markup_printf_escaped(SIPE_SOAP_SET_CONTACT,
471 alias, groups, "true", buddy->name, sip->contacts_delta++
473 send_soap_request(SIPE_CORE_PRIVATE, body);
474 g_free(groups);
475 g_free(body);
480 static gboolean process_add_group_response(struct sipe_core_private *sipe_private,
481 struct sipmsg *msg,
482 struct transaction *trans)
484 if (msg->response == 200) {
485 struct sipe_group *group;
486 struct group_user_context *ctx = trans->payload->data;
487 sipe_xml *xml;
488 const sipe_xml *node;
489 char *group_id;
490 struct sipe_buddy *buddy;
492 xml = sipe_xml_parse(msg->body, msg->bodylen);
493 if (!xml) {
494 return FALSE;
497 node = sipe_xml_child(xml, "Body/addGroup/groupID");
498 if (!node) {
499 sipe_xml_free(xml);
500 return FALSE;
503 group_id = sipe_xml_data(node);
504 if (!group_id) {
505 sipe_xml_free(xml);
506 return FALSE;
509 group = g_new0(struct sipe_group, 1);
510 group->id = (int)g_ascii_strtod(group_id, NULL);
511 g_free(group_id);
512 group->name = g_strdup(ctx->group_name);
514 sipe_group_add(sipe_private, group);
516 if (ctx->user_name) {
517 buddy = g_hash_table_lookup(sipe_private->buddies, ctx->user_name);
518 if (buddy) {
519 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
522 sipe_core_group_set_user(SIPE_CORE_PUBLIC, ctx->user_name);
525 sipe_xml_free(xml);
526 return TRUE;
528 return FALSE;
531 static void sipe_group_context_destroy(gpointer data)
533 struct group_user_context *ctx = data;
534 g_free(ctx->group_name);
535 g_free(ctx->user_name);
536 g_free(ctx);
539 static void sipe_group_create (struct sipe_core_private *sipe_private,
540 const gchar *name, const gchar * who)
542 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
543 struct transaction_payload *payload = g_new0(struct transaction_payload, 1);
544 struct group_user_context *ctx = g_new0(struct group_user_context, 1);
545 const gchar *soap_name = sipe_strequal(name, _("Other Contacts")) ? "~" : name;
546 gchar *body;
547 ctx->group_name = g_strdup(name);
548 ctx->user_name = g_strdup(who);
549 payload->destroy = sipe_group_context_destroy;
550 payload->data = ctx;
552 body = g_markup_printf_escaped(SIPE_SOAP_ADD_GROUP, soap_name, sip->contacts_delta++);
553 send_soap_request_with_cb(sipe_private, NULL, body, process_add_group_response, payload);
554 g_free(body);
557 static void
558 sipe_sched_calendar_status_update(struct sipe_core_private *sipe_private,
559 time_t calculate_from);
561 static int
562 sipe_get_availability_by_status(const char* sipe_status_id, char** activity_token);
564 static const char*
565 sipe_get_status_by_availability(int avail,
566 char** activity);
568 static void
569 sipe_set_purple_account_status_and_note(const PurpleAccount *account,
570 const char *status_id,
571 const char *message,
572 time_t do_not_publish[]);
574 static void
575 sipe_apply_calendar_status(struct sipe_core_private *sipe_private,
576 struct sipe_buddy *sbuddy,
577 const char *status_id)
579 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
580 time_t cal_avail_since;
581 int cal_status = sipe_cal_get_status(sbuddy, time(NULL), &cal_avail_since);
582 int avail;
583 gchar *self_uri;
585 if (!sbuddy) return;
587 if (cal_status < SIPE_CAL_NO_DATA) {
588 SIPE_DEBUG_INFO("sipe_apply_calendar_status: cal_status : %d for %s", cal_status, sbuddy->name);
589 SIPE_DEBUG_INFO("sipe_apply_calendar_status: cal_avail_since : %s", asctime(localtime(&cal_avail_since)));
592 /* scheduled Cal update call */
593 if (!status_id) {
594 status_id = sbuddy->last_non_cal_status_id;
595 g_free(sbuddy->activity);
596 sbuddy->activity = g_strdup(sbuddy->last_non_cal_activity);
599 if (!status_id) {
600 SIPE_DEBUG_INFO("sipe_apply_calendar_status: status_id is NULL for %s, exiting.",
601 sbuddy->name ? sbuddy->name : "" );
602 return;
605 /* adjust to calendar status */
606 if (cal_status != SIPE_CAL_NO_DATA) {
607 SIPE_DEBUG_INFO("sipe_apply_calendar_status: user_avail_since: %s", asctime(localtime(&sbuddy->user_avail_since)));
609 if (cal_status == SIPE_CAL_BUSY
610 && cal_avail_since > sbuddy->user_avail_since
611 && 6500 >= sipe_get_availability_by_status(status_id, NULL))
613 status_id = SIPE_STATUS_ID_BUSY;
614 g_free(sbuddy->activity);
615 sbuddy->activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_IN_MEETING));
617 avail = sipe_get_availability_by_status(status_id, NULL);
619 SIPE_DEBUG_INFO("sipe_apply_calendar_status: activity_since : %s", asctime(localtime(&sbuddy->activity_since)));
620 if (cal_avail_since > sbuddy->activity_since) {
621 if (cal_status == SIPE_CAL_OOF
622 && avail >= 15000) /* 12000 in 2007 */
624 g_free(sbuddy->activity);
625 sbuddy->activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_OOF));
630 /* then set status_id actually */
631 SIPE_DEBUG_INFO("sipe_apply_calendar_status: to %s for %s", status_id, sbuddy->name ? sbuddy->name : "" );
632 purple_prpl_got_user_status(sip->account, sbuddy->name, status_id, NULL);
634 /* set our account state to the one in roaming (including calendar info) */
635 self_uri = sip_uri_self(sipe_private);
636 if (sip->initial_state_published && sipe_strcase_equal(sbuddy->name, self_uri)) {
637 if (sipe_strequal(status_id, SIPE_STATUS_ID_OFFLINE)) {
638 status_id = g_strdup(SIPE_STATUS_ID_INVISIBLE); /* not not let offline status switch us off */
641 SIPE_DEBUG_INFO("sipe_apply_calendar_status: switch to '%s' for the account", sip->status);
642 sipe_set_purple_account_status_and_note(sip->account, status_id, sip->note, sip->do_not_publish);
644 g_free(self_uri);
647 static void
648 sipe_got_user_status(struct sipe_core_private *sipe_private,
649 const char* uri,
650 const char *status_id)
652 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
653 struct sipe_buddy *sbuddy = g_hash_table_lookup(sipe_private->buddies, uri);
655 if (!sbuddy) return;
657 /* Check if on 2005 system contact's calendar,
658 * then set/preserve it.
660 if (!SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
661 sipe_apply_calendar_status(sipe_private, sbuddy, status_id);
662 } else {
663 purple_prpl_got_user_status(sip->account, uri, status_id, NULL);
667 static void
668 update_calendar_status_cb(SIPE_UNUSED_PARAMETER char *name,
669 struct sipe_buddy *sbuddy,
670 struct sipe_core_private *sipe_private)
672 sipe_apply_calendar_status(sipe_private, sbuddy, NULL);
676 * Updates contact's status
677 * based on their calendar information.
679 * Applicability: 2005 systems
681 static void
682 update_calendar_status(struct sipe_core_private *sipe_private,
683 SIPE_UNUSED_PARAMETER void *unused)
685 SIPE_DEBUG_INFO_NOFORMAT("update_calendar_status() started.");
686 g_hash_table_foreach(sipe_private->buddies, (GHFunc)update_calendar_status_cb, sipe_private);
688 /* repeat scheduling */
689 sipe_sched_calendar_status_update(sipe_private, time(NULL) + 3*60 /* 3 min */);
693 * Schedules process of contacts' status update
694 * based on their calendar information.
695 * Should be scheduled to the beginning of every
696 * 15 min interval, like:
697 * 13:00, 13:15, 13:30, 13:45, etc.
699 * Applicability: 2005 systems
701 static void
702 sipe_sched_calendar_status_update(struct sipe_core_private *sipe_private,
703 time_t calculate_from)
705 int interval = 15*60;
706 /** start of the beginning of closest 15 min interval. */
707 time_t next_start = ((time_t)((int)((int)calculate_from)/interval + 1)*interval);
709 SIPE_DEBUG_INFO("sipe_sched_calendar_status_update: calculate_from time: %s",
710 asctime(localtime(&calculate_from)));
711 SIPE_DEBUG_INFO("sipe_sched_calendar_status_update: next start time : %s",
712 asctime(localtime(&next_start)));
714 sipe_schedule_seconds(sipe_private,
715 "<+2005-cal-status>",
716 NULL,
717 next_start - time(NULL),
718 update_calendar_status,
719 NULL);
723 * Schedules process of self status publish
724 * based on own calendar information.
725 * Should be scheduled to the beginning of every
726 * 15 min interval, like:
727 * 13:00, 13:15, 13:30, 13:45, etc.
729 * Applicability: 2007+ systems
731 static void
732 sipe_sched_calendar_status_self_publish(struct sipe_core_private *sipe_private,
733 time_t calculate_from)
735 int interval = 5*60;
736 /** start of the beginning of closest 5 min interval. */
737 time_t next_start = ((time_t)((int)((int)calculate_from)/interval + 1)*interval);
739 SIPE_DEBUG_INFO("sipe_sched_calendar_status_self_publish: calculate_from time: %s",
740 asctime(localtime(&calculate_from)));
741 SIPE_DEBUG_INFO("sipe_sched_calendar_status_self_publish: next start time : %s",
742 asctime(localtime(&next_start)));
744 sipe_schedule_seconds(sipe_private,
745 "<+2007-cal-status>",
746 NULL,
747 next_start - time(NULL),
748 publish_calendar_status_self,
749 NULL);
752 static void sipe_subscribe_resource_uri(const char *name,
753 SIPE_UNUSED_PARAMETER gpointer value,
754 gchar **resources_uri)
756 gchar *tmp = *resources_uri;
757 *resources_uri = g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp, name);
758 g_free(tmp);
761 static void sipe_subscribe_resource_uri_with_context(const char *name, gpointer value, gchar **resources_uri)
763 struct sipe_buddy *sbuddy = (struct sipe_buddy *)value;
764 gchar *context = sbuddy && sbuddy->just_added ? "><context/></resource>" : "/>";
765 gchar *tmp = *resources_uri;
767 if (sbuddy) sbuddy->just_added = FALSE; /* should be enought to include context one time */
769 *resources_uri = g_strdup_printf("%s<resource uri=\"%s\"%s\n", tmp, name, context);
770 g_free(tmp);
774 * Support for Batch Category SUBSCRIBE [MS-PRES] - msrtc-event-categories+xml OCS 2007
775 * Support for Batch Category SUBSCRIBE [MS-SIP] - adrl+xml LCS 2005
776 * The user sends an initial batched category SUBSCRIBE request against all contacts on his roaming list in only a request
777 * A batch category SUBSCRIBE request MUST have the same To-URI and From-URI.
778 * This header will be send only if adhoclist there is a "Supported: adhoclist" in REGISTER answer else will be send a Single Category SUBSCRIBE
781 static void sipe_subscribe_presence_batched_to(struct sipe_core_private *sipe_private,
782 gchar *resources_uri,
783 gchar *to)
785 gchar *contact = get_contact(sipe_private);
786 gchar *request;
787 gchar *content;
788 gchar *require = "";
789 gchar *accept = "";
790 gchar *autoextend = "";
791 gchar *content_type;
793 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
794 require = ", categoryList";
795 accept = ", application/msrtc-event-categories+xml, application/xpidf+xml, application/pidf+xml";
796 content_type = "application/msrtc-adrl-categorylist+xml";
797 content = g_strdup_printf(
798 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
799 "<action name=\"subscribe\" id=\"63792024\">\n"
800 "<adhocList>\n%s</adhocList>\n"
801 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
802 "<category name=\"calendarData\"/>\n"
803 "<category name=\"contactCard\"/>\n"
804 "<category name=\"note\"/>\n"
805 "<category name=\"state\"/>\n"
806 "</categoryList>\n"
807 "</action>\n"
808 "</batchSub>", sipe_private->username, resources_uri);
809 } else {
810 autoextend = "Supported: com.microsoft.autoextend\r\n";
811 content_type = "application/adrl+xml";
812 content = g_strdup_printf(
813 "<adhoclist xmlns=\"urn:ietf:params:xml:ns:adrl\" uri=\"sip:%s\" name=\"sip:%s\">\n"
814 "<create xmlns=\"\">\n%s</create>\n"
815 "</adhoclist>\n", sipe_private->username, sipe_private->username, resources_uri);
817 g_free(resources_uri);
819 request = g_strdup_printf(
820 "Require: adhoclist%s\r\n"
821 "Supported: eventlist\r\n"
822 "Accept: application/rlmi+xml, multipart/related, text/xml+msrtc.pidf%s\r\n"
823 "Supported: ms-piggyback-first-notify\r\n"
824 "%sSupported: ms-benotify\r\n"
825 "Proxy-Require: ms-benotify\r\n"
826 "Event: presence\r\n"
827 "Content-Type: %s\r\n"
828 "Contact: %s\r\n", require, accept, autoextend, content_type, contact);
829 g_free(contact);
831 sipe_subscribe_presence_buddy(sipe_private, to, request, content);
833 g_free(content);
834 g_free(to);
835 g_free(request);
838 static void sipe_subscribe_presence_batched(struct sipe_core_private *sipe_private,
839 SIPE_UNUSED_PARAMETER void *unused)
841 gchar *to = sip_uri_self(sipe_private);
842 gchar *resources_uri = g_strdup("");
843 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
844 g_hash_table_foreach(sipe_private->buddies, (GHFunc) sipe_subscribe_resource_uri_with_context , &resources_uri);
845 } else {
846 g_hash_table_foreach(sipe_private->buddies, (GHFunc) sipe_subscribe_resource_uri, &resources_uri);
849 sipe_subscribe_presence_batched_to(sipe_private, resources_uri, to);
852 struct presence_batched_routed {
853 gchar *host;
854 GSList *buddies;
857 static void sipe_subscribe_presence_batched_routed_free(void *payload)
859 struct presence_batched_routed *data = payload;
860 GSList *buddies = data->buddies;
861 while (buddies) {
862 g_free(buddies->data);
863 buddies = buddies->next;
865 g_slist_free(data->buddies);
866 g_free(data->host);
867 g_free(payload);
870 static void sipe_subscribe_presence_batched_routed(struct sipe_core_private *sipe_private,
871 void *payload)
873 struct presence_batched_routed *data = payload;
874 GSList *buddies = data->buddies;
875 gchar *resources_uri = g_strdup("");
876 while (buddies) {
877 gchar *tmp = resources_uri;
878 resources_uri = g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp, (char *) buddies->data);
879 g_free(tmp);
880 buddies = buddies->next;
882 sipe_subscribe_presence_batched_to(sipe_private, resources_uri,
883 g_strdup(data->host));
887 * Single Category SUBSCRIBE [MS-PRES] ; To send when the server returns a 200 OK message with state="resubscribe" in response.
888 * The user sends a single SUBSCRIBE request to the subscribed contact.
889 * The To-URI and the URI listed in the resource list MUST be the same for a single category SUBSCRIBE request.
893 static void sipe_subscribe_presence_single(struct sipe_core_private *sipe_private,
894 void *buddy_name)
896 gchar *to = sip_uri((char *)buddy_name);
897 gchar *tmp = get_contact(sipe_private);
898 gchar *request;
899 gchar *content = NULL;
900 gchar *autoextend = "";
901 gchar *content_type = "";
902 struct sipe_buddy *sbuddy = g_hash_table_lookup(sipe_private->buddies, to);
903 gchar *context = sbuddy && sbuddy->just_added ? "><context/></resource>" : "/>";
905 if (sbuddy) sbuddy->just_added = FALSE;
907 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
908 content_type = "Content-Type: application/msrtc-adrl-categorylist+xml\r\n";
909 } else {
910 autoextend = "Supported: com.microsoft.autoextend\r\n";
913 request = g_strdup_printf(
914 "Accept: application/msrtc-event-categories+xml, text/xml+msrtc.pidf, application/xpidf+xml, application/pidf+xml, application/rlmi+xml, multipart/related\r\n"
915 "Supported: ms-piggyback-first-notify\r\n"
916 "%s%sSupported: ms-benotify\r\n"
917 "Proxy-Require: ms-benotify\r\n"
918 "Event: presence\r\n"
919 "Contact: %s\r\n", autoextend, content_type, tmp);
921 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
922 content = g_strdup_printf(
923 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
924 "<action name=\"subscribe\" id=\"63792024\"><adhocList>\n"
925 "<resource uri=\"%s\"%s\n"
926 "</adhocList>\n"
927 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
928 "<category name=\"calendarData\"/>\n"
929 "<category name=\"contactCard\"/>\n"
930 "<category name=\"note\"/>\n"
931 "<category name=\"state\"/>\n"
932 "</categoryList>\n"
933 "</action>\n"
934 "</batchSub>", sipe_private->username, to, context);
937 g_free(tmp);
939 sipe_subscribe_presence_buddy(sipe_private, to, request, content);
941 g_free(content);
942 g_free(to);
943 g_free(request);
946 void sipe_set_status(PurpleAccount *account, PurpleStatus *status)
948 SIPE_DEBUG_INFO("sipe_set_status: status=%s", purple_status_get_id(status));
950 if (!purple_status_is_active(status))
951 return;
953 if (account->gc) {
954 struct sipe_core_private *sipe_private = PURPLE_ACCOUNT_TO_SIPE_CORE_PRIVATE;
955 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
957 if (sip) {
958 gchar *action_name;
959 gchar *tmp;
960 time_t now = time(NULL);
961 const char *status_id = purple_status_get_id(status);
962 const char *note = purple_status_get_attr_string(status, SIPE_STATUS_ATTR_ID_MESSAGE);
963 sipe_activity activity = sipe_get_activity_by_token(status_id);
964 gboolean do_not_publish = ((now - sip->do_not_publish[activity]) <= 2);
966 /* when other point of presence clears note, but we are keeping
967 * state if OOF note.
969 if (do_not_publish && !note && sip->cal && sip->cal->oof_note) {
970 SIPE_DEBUG_INFO_NOFORMAT("sipe_set_status: enabling publication as OOF note keepers.");
971 do_not_publish = FALSE;
974 SIPE_DEBUG_INFO("sipe_set_status: was: sip->do_not_publish[%s]=%d [?] now(time)=%d",
975 status_id, (int)sip->do_not_publish[activity], (int)now);
977 sip->do_not_publish[activity] = 0;
978 SIPE_DEBUG_INFO("sipe_set_status: set: sip->do_not_publish[%s]=%d [0]",
979 status_id, (int)sip->do_not_publish[activity]);
981 if (do_not_publish)
983 SIPE_DEBUG_INFO_NOFORMAT("sipe_set_status: publication was switched off, exiting.");
984 return;
987 g_free(sip->status);
988 sip->status = g_strdup(status_id);
990 /* hack to escape apostrof before comparison */
991 tmp = note ? sipe_utils_str_replace(note, "'", "&apos;") : NULL;
993 /* this will preserve OOF flag as well */
994 if (!sipe_strequal(tmp, sip->note)) {
995 sip->is_oof_note = FALSE;
996 g_free(sip->note);
997 sip->note = g_strdup(note);
998 sip->note_since = time(NULL);
1000 g_free(tmp);
1002 /* schedule 2 sec to capture idle flag */
1003 action_name = g_strdup_printf("<%s>", "+set-status");
1004 sipe_schedule_seconds(sipe_private,
1005 action_name,
1006 NULL,
1007 SIPE_IDLE_SET_DELAY,
1008 send_presence_status,
1009 NULL);
1010 g_free(action_name);
1015 void
1016 sipe_set_idle(PurpleConnection * gc,
1017 int interval)
1019 SIPE_DEBUG_INFO("sipe_set_idle: interval=%d", interval);
1021 if (gc) {
1022 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
1023 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
1025 if (sip) {
1026 sip->idle_switch = time(NULL);
1027 SIPE_DEBUG_INFO("sipe_set_idle: sip->idle_switch : %s", asctime(localtime(&(sip->idle_switch))));
1032 void
1033 sipe_group_buddy(PurpleConnection *gc,
1034 const char *who,
1035 const char *old_group_name,
1036 const char *new_group_name)
1038 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
1039 struct sipe_buddy * buddy = g_hash_table_lookup(sipe_private->buddies, who);
1040 struct sipe_group * old_group = NULL;
1041 struct sipe_group * new_group;
1043 SIPE_DEBUG_INFO("sipe_group_buddy[CB]: who:%s old_group_name:%s new_group_name:%s",
1044 who ? who : "", old_group_name ? old_group_name : "", new_group_name ? new_group_name : "");
1046 if(!buddy) { // buddy not in roaming list
1047 return;
1050 if (old_group_name) {
1051 old_group = sipe_group_find_by_name(sipe_private, old_group_name);
1053 new_group = sipe_group_find_by_name(sipe_private, new_group_name);
1055 if (old_group) {
1056 buddy->groups = g_slist_remove(buddy->groups, old_group);
1057 SIPE_DEBUG_INFO("buddy %s removed from old group %s", who, old_group_name);
1060 if (!new_group) {
1061 sipe_group_create(sipe_private, new_group_name, who);
1062 } else {
1063 buddy->groups = slist_insert_unique_sorted(buddy->groups, new_group, (GCompareFunc)sipe_group_compare);
1064 sipe_core_group_set_user(SIPE_CORE_PUBLIC, who);
1068 void sipe_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
1070 SIPE_DEBUG_INFO("sipe_add_buddy[CB]: buddy:%s group:%s", buddy ? buddy->name : "", group ? group->name : "");
1072 /* libpurple can call us with undefined buddy or group */
1073 if (buddy && group) {
1074 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
1076 /* Buddy name must be lower case as we use purple_normalize_nocase() to compare */
1077 gchar *buddy_name = g_ascii_strdown(buddy->name, -1);
1078 purple_blist_rename_buddy(buddy, buddy_name);
1079 g_free(buddy_name);
1081 /* Prepend sip: if needed */
1082 if (!g_str_has_prefix(buddy->name, "sip:")) {
1083 gchar *buf = sip_uri_from_name(buddy->name);
1084 purple_blist_rename_buddy(buddy, buf);
1085 g_free(buf);
1088 if (!g_hash_table_lookup(sipe_private->buddies, buddy->name)) {
1089 struct sipe_buddy *b = g_new0(struct sipe_buddy, 1);
1090 SIPE_DEBUG_INFO("sipe_add_buddy: adding %s", buddy->name);
1091 b->name = g_strdup(buddy->name);
1092 b->just_added = TRUE;
1093 g_hash_table_insert(sipe_private->buddies, b->name, b);
1094 sipe_group_buddy(gc, b->name, NULL, group->name);
1095 /* @TODO should go to callback */
1096 sipe_subscribe_presence_single(sipe_private,
1097 b->name);
1098 } else {
1099 SIPE_DEBUG_INFO("sipe_add_buddy: buddy %s already in internal list", buddy->name);
1104 static void sipe_free_buddy(struct sipe_buddy *buddy)
1106 #ifndef _WIN32
1108 * We are calling g_hash_table_foreach_steal(). That means that no
1109 * key/value deallocation functions are called. Therefore the glib
1110 * hash code does not touch the key (buddy->name) or value (buddy)
1111 * of the to-be-deleted hash node at all. It follows that we
1113 * - MUST free the memory for the key ourselves and
1114 * - ARE allowed to do it in this function
1116 * Conclusion: glib must be broken on the Windows platform if sipe
1117 * crashes with SIGTRAP when closing. You'll have to live
1118 * with the memory leak until this is fixed.
1120 g_free(buddy->name);
1121 #endif
1122 g_free(buddy->activity);
1123 g_free(buddy->meeting_subject);
1124 g_free(buddy->meeting_location);
1125 g_free(buddy->note);
1127 g_free(buddy->cal_start_time);
1128 g_free(buddy->cal_free_busy_base64);
1129 g_free(buddy->cal_free_busy);
1130 g_free(buddy->last_non_cal_activity);
1132 sipe_cal_free_working_hours(buddy->cal_working_hours);
1134 g_free(buddy->device_name);
1135 g_slist_free(buddy->groups);
1136 g_free(buddy);
1140 * Unassociates buddy from group first.
1141 * Then see if no groups left, removes buddy completely.
1142 * Otherwise updates buddy groups on server.
1144 void sipe_remove_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
1146 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
1147 struct sipe_buddy *b;
1148 struct sipe_group *g = NULL;
1150 SIPE_DEBUG_INFO("sipe_remove_buddy[CB]: buddy:%s group:%s", buddy ? buddy->name : "", group ? group->name : "");
1151 if (!buddy) return;
1153 b = g_hash_table_lookup(sipe_private->buddies, buddy->name);
1154 if (!b) return;
1156 if (group) {
1157 g = sipe_group_find_by_name(sipe_private, group->name);
1160 if (g) {
1161 b->groups = g_slist_remove(b->groups, g);
1162 SIPE_DEBUG_INFO("buddy %s removed from group %s", buddy->name, g->name);
1165 if (g_slist_length(b->groups) < 1) {
1166 gchar *action_name = sipe_utils_presence_key(buddy->name);
1167 sipe_schedule_cancel(sipe_private, action_name);
1168 g_free(action_name);
1170 g_hash_table_remove(sipe_private->buddies, buddy->name);
1172 if (b->name) {
1173 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
1174 gchar * body = g_strdup_printf(SIPE_SOAP_DEL_CONTACT, b->name, sip->contacts_delta++);
1175 send_soap_request(sipe_private, body);
1176 g_free(body);
1179 sipe_free_buddy(b);
1180 } else {
1181 //updates groups on server
1182 sipe_core_group_set_user(SIPE_CORE_PUBLIC, b->name);
1187 void
1188 sipe_rename_group(PurpleConnection *gc,
1189 const char *old_name,
1190 PurpleGroup *group,
1191 SIPE_UNUSED_PARAMETER GList *moved_buddies)
1193 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
1194 struct sipe_group * s_group = sipe_group_find_by_name(sipe_private, old_name);
1195 if (s_group) {
1196 sipe_group_rename(sipe_private, s_group, group->name);
1197 } else {
1198 SIPE_DEBUG_INFO("Cannot find group %s to rename", old_name);
1202 void
1203 sipe_remove_group(PurpleConnection *gc, PurpleGroup *group)
1205 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
1206 struct sipe_group * s_group = sipe_group_find_by_name(sipe_private, group->name);
1207 if (s_group) {
1208 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
1209 gchar *body;
1210 SIPE_DEBUG_INFO("Deleting group %s", group->name);
1211 body = g_strdup_printf(SIPE_SOAP_DEL_GROUP, s_group->id, sip->contacts_delta++);
1212 send_soap_request(sipe_private, body);
1213 g_free(body);
1215 sip->groups = g_slist_remove(sip->groups, s_group);
1216 g_free(s_group->name);
1217 g_free(s_group);
1218 } else {
1219 SIPE_DEBUG_INFO("Cannot find group %s to delete", group->name);
1224 * A callback for g_hash_table_foreach
1226 static void
1227 sipe_buddy_subscribe_cb(char *buddy_name,
1228 SIPE_UNUSED_PARAMETER struct sipe_buddy *buddy,
1229 struct sipe_core_private *sipe_private)
1231 gchar *action_name = sipe_utils_presence_key(buddy_name);
1232 /* g_hash_table_size() can never return 0, otherwise this function wouldn't be called :-) */
1233 guint time_range = (g_hash_table_size(sipe_private->buddies) * 1000) / 25; /* time interval for 25 requests per sec. In msec. */
1234 guint timeout = ((guint) rand()) / (RAND_MAX / time_range) + 1; /* random period within the range but never 0! */
1236 sipe_schedule_mseconds(sipe_private,
1237 action_name,
1238 g_strdup(buddy_name),
1239 timeout,
1240 sipe_subscribe_presence_single,
1241 g_free);
1242 g_free(action_name);
1246 * Removes entries from purple buddy list
1247 * that does not correspond ones in the roaming contact list.
1249 static void sipe_cleanup_local_blist(struct sipe_core_private *sipe_private) {
1250 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
1251 GSList *buddies = purple_find_buddies(sip->account, NULL);
1252 GSList *entry = buddies;
1253 struct sipe_buddy *buddy;
1254 PurpleBuddy *b;
1255 PurpleGroup *g;
1257 SIPE_DEBUG_INFO("sipe_cleanup_local_blist: overall %d Purple buddies (including clones)", g_slist_length(buddies));
1258 SIPE_DEBUG_INFO("sipe_cleanup_local_blist: %d sipe buddies (unique)", g_hash_table_size(sipe_private->buddies));
1259 while (entry) {
1260 b = entry->data;
1261 g = purple_buddy_get_group(b);
1262 buddy = g_hash_table_lookup(sipe_private->buddies, b->name);
1263 if(buddy) {
1264 gboolean in_sipe_groups = FALSE;
1265 GSList *entry2 = buddy->groups;
1266 while (entry2) {
1267 struct sipe_group *group = entry2->data;
1268 if (sipe_strequal(group->name, g->name)) {
1269 in_sipe_groups = TRUE;
1270 break;
1272 entry2 = entry2->next;
1274 if(!in_sipe_groups) {
1275 SIPE_DEBUG_INFO("*** REMOVING %s from Purple group: %s as not having this group in roaming list", b->name, g->name);
1276 purple_blist_remove_buddy(b);
1278 } else {
1279 SIPE_DEBUG_INFO("*** REMOVING %s from Purple group: %s as this buddy not in roaming list", b->name, g->name);
1280 purple_blist_remove_buddy(b);
1282 entry = entry->next;
1284 g_slist_free(buddies);
1287 static int
1288 sipe_find_access_level(struct sipe_core_private *sipe_private,
1289 const gchar *type,
1290 const gchar *value,
1291 gboolean *is_group_access);
1293 static void
1294 sipe_refresh_blocked_status_cb(char *buddy_name,
1295 SIPE_UNUSED_PARAMETER struct sipe_buddy *buddy,
1296 struct sipe_core_private *sipe_private)
1298 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
1299 int container_id = sipe_find_access_level(sipe_private, "user", buddy_name, NULL);
1300 gboolean blocked = (container_id == 32000);
1301 gboolean blocked_in_blist = !purple_privacy_check(sip->account, buddy_name);
1303 /* SIPE_DEBUG_INFO("sipe_refresh_blocked_status_cb: buddy_name=%s, blocked=%s, blocked_in_blist=%s",
1304 buddy_name, blocked ? "T" : "F", blocked_in_blist ? "T" : "F"); */
1306 if (blocked != blocked_in_blist) {
1307 if (blocked) {
1308 purple_privacy_deny_add(sip->account, buddy_name, TRUE);
1309 } else {
1310 purple_privacy_deny_remove(sip->account, buddy_name, TRUE);
1313 /* stupid workaround to make pidgin re-render screen to reflect our changes */
1315 PurpleBuddy *pbuddy = purple_find_buddy(sip->account, buddy_name);
1316 const PurplePresence *presence = purple_buddy_get_presence(pbuddy);
1317 const PurpleStatus *pstatus = purple_presence_get_active_status(presence);
1319 SIPE_DEBUG_INFO_NOFORMAT("sipe_refresh_blocked_status_cb: forcefully refreshing screen.");
1320 sipe_got_user_status(sipe_private, buddy_name, purple_status_get_id(pstatus));
1326 static void
1327 sipe_refresh_blocked_status(struct sipe_core_private *sipe_private)
1329 g_hash_table_foreach(sipe_private->buddies,
1330 (GHFunc) sipe_refresh_blocked_status_cb,
1331 sipe_private);
1334 static gboolean sipe_process_roaming_contacts(struct sipe_core_private *sipe_private,
1335 struct sipmsg *msg)
1337 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
1338 int len = msg->bodylen;
1340 const gchar *tmp = sipmsg_find_header(msg, "Event");
1341 const sipe_xml *item;
1342 sipe_xml *isc;
1343 const gchar *contacts_delta;
1344 const sipe_xml *group_node;
1345 if (!g_str_has_prefix(tmp, "vnd-microsoft-roaming-contacts")) {
1346 return FALSE;
1349 /* Convert the contact from XML to Purple Buddies */
1350 isc = sipe_xml_parse(msg->body, len);
1351 if (!isc) {
1352 return FALSE;
1355 contacts_delta = sipe_xml_attribute(isc, "deltaNum");
1356 if (contacts_delta) {
1357 sip->contacts_delta = (int)g_ascii_strtod(contacts_delta, NULL);
1360 if (sipe_strequal(sipe_xml_name(isc), "contactList")) {
1362 /* Parse groups */
1363 for (group_node = sipe_xml_child(isc, "group"); group_node; group_node = sipe_xml_twin(group_node)) {
1364 struct sipe_group * group = g_new0(struct sipe_group, 1);
1365 const char *name = sipe_xml_attribute(group_node, "name");
1367 if (g_str_has_prefix(name, "~")) {
1368 name = _("Other Contacts");
1370 group->name = g_strdup(name);
1371 group->id = (int)g_ascii_strtod(sipe_xml_attribute(group_node, "id"), NULL);
1373 sipe_group_add(sipe_private, group);
1376 // Make sure we have at least one group
1377 if (g_slist_length(sip->groups) == 0) {
1378 sipe_group_create(sipe_private, _("Other Contacts"), NULL);
1381 /* Parse contacts */
1382 for (item = sipe_xml_child(isc, "contact"); item; item = sipe_xml_twin(item)) {
1383 const gchar *uri = sipe_xml_attribute(item, "uri");
1384 const gchar *name = sipe_xml_attribute(item, "name");
1385 gchar *buddy_name;
1386 struct sipe_buddy *buddy = NULL;
1387 gchar *tmp;
1388 gchar **item_groups;
1389 int i = 0;
1391 /* Buddy name must be lower case as we use purple_normalize_nocase() to compare */
1392 tmp = sip_uri_from_name(uri);
1393 buddy_name = g_ascii_strdown(tmp, -1);
1394 g_free(tmp);
1396 /* assign to group Other Contacts if nothing else received */
1397 tmp = g_strdup(sipe_xml_attribute(item, "groups"));
1398 if(is_empty(tmp)) {
1399 struct sipe_group *group = sipe_group_find_by_name(sipe_private, _("Other Contacts"));
1400 g_free(tmp);
1401 tmp = group ? g_strdup_printf("%d", group->id) : g_strdup("1");
1403 item_groups = g_strsplit(tmp, " ", 0);
1404 g_free(tmp);
1406 while (item_groups[i]) {
1407 struct sipe_group *group = sipe_group_find_by_id(sipe_private, g_ascii_strtod(item_groups[i], NULL));
1409 // If couldn't find the right group for this contact, just put them in the first group we have
1410 if (group == NULL && g_slist_length(sip->groups) > 0) {
1411 group = sip->groups->data;
1414 if (group != NULL) {
1415 PurpleBuddy *b = purple_find_buddy_in_group(sip->account, buddy_name, group->purple_group);
1416 if (!b){
1417 b = purple_buddy_new(sip->account, buddy_name, uri);
1418 purple_blist_add_buddy(b, NULL, group->purple_group, NULL);
1420 SIPE_DEBUG_INFO("Created new buddy %s with alias %s", buddy_name, uri);
1423 if (sipe_strcase_equal(uri, purple_buddy_get_alias(b))) {
1424 if (name != NULL && strlen(name) != 0) {
1425 purple_blist_alias_buddy(b, name);
1427 SIPE_DEBUG_INFO("Replaced buddy %s alias with %s", buddy_name, name);
1431 if (!buddy) {
1432 buddy = g_new0(struct sipe_buddy, 1);
1433 buddy->name = g_strdup(b->name);
1434 g_hash_table_insert(sipe_private->buddies, buddy->name, buddy);
1436 SIPE_DEBUG_INFO("Added SIPE buddy %s", buddy->name);
1439 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
1441 SIPE_DEBUG_INFO("Added buddy %s to group %s", b->name, group->name);
1442 } else {
1443 SIPE_DEBUG_INFO("No group found for contact %s! Unable to add to buddy list",
1444 name);
1447 i++;
1448 } // while, contact groups
1449 g_strfreev(item_groups);
1450 g_free(buddy_name);
1452 } // for, contacts
1454 sipe_cleanup_local_blist(sipe_private);
1456 /* Add self-contact if not there yet. 2005 systems. */
1457 /* This will resemble subscription to roaming_self in 2007 systems */
1458 if (!SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
1459 gchar *self_uri = sip_uri_self(sipe_private);
1460 struct sipe_buddy *buddy = g_hash_table_lookup(sipe_private->buddies, self_uri);
1462 if (!buddy) {
1463 buddy = g_new0(struct sipe_buddy, 1);
1464 buddy->name = g_strdup(self_uri);
1465 g_hash_table_insert(sipe_private->buddies, buddy->name, buddy);
1467 g_free(self_uri);
1470 sipe_xml_free(isc);
1472 /* subscribe to buddies */
1473 if (!sip->subscribed_buddies) { //do it once, then count Expire field to schedule resubscribe.
1474 if (sip->batched_support) {
1475 sipe_subscribe_presence_batched(sipe_private, NULL);
1476 } else {
1477 g_hash_table_foreach(sipe_private->buddies,
1478 (GHFunc)sipe_buddy_subscribe_cb,
1479 sipe_private);
1481 sip->subscribed_buddies = TRUE;
1483 /* for 2005 systems schedule contacts' status update
1484 * based on their calendar information
1486 if (!SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
1487 sipe_sched_calendar_status_update(sipe_private, time(NULL));
1490 return 0;
1494 * Fires on deregistration event initiated by server.
1495 * [MS-SIPREGE] SIP extension.
1498 // 2007 Example
1500 // Content-Type: text/registration-event
1501 // subscription-state: terminated;expires=0
1502 // ms-diagnostics-public: 4141;reason="User disabled"
1504 // deregistered;event=rejected
1506 static void sipe_process_registration_notify(struct sipe_core_private *sipe_private,
1507 struct sipmsg *msg)
1509 const gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
1510 gchar *event = NULL;
1511 gchar *reason = NULL;
1512 const gchar *diagnostics = sipmsg_find_header(msg, "ms-diagnostics");
1513 gchar *warning;
1515 diagnostics = diagnostics ? diagnostics : sipmsg_find_header(msg, "ms-diagnostics-public");
1516 SIPE_DEBUG_INFO_NOFORMAT("sipe_process_registration_notify: deregistration received.");
1518 if (!g_ascii_strncasecmp(contenttype, "text/registration-event", 23)) {
1519 event = sipmsg_find_part_of_header(msg->body, "event=", NULL, NULL);
1520 //@TODO have proper parameter extraction _by_name_ func, case insesitive.
1521 event = event ? event : sipmsg_find_part_of_header(msg->body, "event=", ";", NULL);
1522 } else {
1523 SIPE_DEBUG_INFO_NOFORMAT("sipe_process_registration_notify: unknown content type, exiting.");
1524 return;
1527 if (diagnostics != NULL) {
1528 reason = sipmsg_find_part_of_header(diagnostics, "reason=\"", "\"", NULL);
1529 } else { // for LCS2005
1530 int error_id = 0;
1531 if (event && sipe_strcase_equal(event, "unregistered")) {
1532 error_id = 4140; // [MS-SIPREGE]
1533 //reason = g_strdup(_("User logged out")); // [MS-OCER]
1534 reason = g_strdup(_("you are already signed in at another location"));
1535 } else if (event && sipe_strcase_equal(event, "rejected")) {
1536 error_id = 4141;
1537 reason = g_strdup(_("user disabled")); // [MS-OCER]
1538 } else if (event && sipe_strcase_equal(event, "deactivated")) {
1539 error_id = 4142;
1540 reason = g_strdup(_("user moved")); // [MS-OCER]
1543 g_free(event);
1544 warning = g_strdup_printf(_("You have been rejected by the server: %s"), reason ? reason : _("no reason given"));
1545 g_free(reason);
1547 sipe_backend_connection_error(SIPE_CORE_PUBLIC,
1548 SIPE_CONNECTION_ERROR_INVALID_USERNAME,
1549 warning);
1550 g_free(warning);
1554 static void sipe_process_provisioning_v2(struct sipe_core_private *sipe_private,
1555 struct sipmsg *msg)
1557 sipe_xml *xn_provision_group_list;
1558 const sipe_xml *node;
1560 xn_provision_group_list = sipe_xml_parse(msg->body, msg->bodylen);
1562 /* provisionGroup */
1563 for (node = sipe_xml_child(xn_provision_group_list, "provisionGroup"); node; node = sipe_xml_twin(node)) {
1564 if (sipe_strequal("ServerConfiguration", sipe_xml_attribute(node, "name"))) {
1565 g_free(sipe_private->focus_factory_uri);
1566 sipe_private->focus_factory_uri = sipe_xml_data(sipe_xml_child(node, "focusFactoryUri"));
1567 SIPE_DEBUG_INFO("sipe_process_provisioning_v2: sipe_private->focus_factory_uri=%s",
1568 sipe_private->focus_factory_uri ? sipe_private->focus_factory_uri : "");
1570 #ifdef HAVE_VV
1571 g_free(sipe_private->mras_uri);
1572 sipe_private->mras_uri = g_strstrip(sipe_xml_data(sipe_xml_child(node, "mrasUri")));
1573 SIPE_DEBUG_INFO("sipe_process_provisioning_v2: sipe_private->mras_uri=%s",
1574 sipe_private->mras_uri ? sipe_private->mras_uri : "");
1576 if (sipe_private->mras_uri)
1577 sipe_media_get_av_edge_credentials(sipe_private);
1578 #endif
1579 break;
1582 sipe_xml_free(xn_provision_group_list);
1585 /** for 2005 system */
1586 static void
1587 sipe_process_provisioning(struct sipe_core_private *sipe_private,
1588 struct sipmsg *msg)
1590 sipe_xml *xn_provision;
1591 const sipe_xml *node;
1593 xn_provision = sipe_xml_parse(msg->body, msg->bodylen);
1594 if ((node = sipe_xml_child(xn_provision, "user"))) {
1595 SIPE_DEBUG_INFO("sipe_process_provisioning: uri=%s", sipe_xml_attribute(node, "uri"));
1596 if ((node = sipe_xml_child(node, "line"))) {
1597 const gchar *line_uri = sipe_xml_attribute(node, "uri");
1598 const gchar *server = sipe_xml_attribute(node, "server");
1599 SIPE_DEBUG_INFO("sipe_process_provisioning: line_uri=%s server=%s", line_uri, server);
1600 sip_csta_open(sipe_private, line_uri, server);
1603 sipe_xml_free(xn_provision);
1606 static void sipe_process_roaming_acl(struct sipe_core_private *sipe_private,
1607 struct sipmsg *msg)
1609 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
1610 const gchar *contacts_delta;
1611 sipe_xml *xml;
1613 xml = sipe_xml_parse(msg->body, msg->bodylen);
1614 if (!xml)
1616 return;
1619 contacts_delta = sipe_xml_attribute(xml, "deltaNum");
1620 if (contacts_delta)
1622 sip->acl_delta = (int)g_ascii_strtod(contacts_delta, NULL);
1625 sipe_xml_free(xml);
1628 /** MS-PRES container */
1629 struct sipe_container {
1630 guint id;
1631 guint version;
1632 GSList *members;
1634 /** MS-PRES container member */
1635 struct sipe_container_member {
1636 /** user, domain, sameEnterprise, federated, publicCloud; everyone */
1637 gchar *type;
1638 gchar *value;
1641 static void
1642 free_container_member(struct sipe_container_member *member)
1644 if (!member) return;
1646 g_free(member->type);
1647 g_free(member->value);
1648 g_free(member);
1651 static void
1652 free_container(struct sipe_container *container)
1654 GSList *entry;
1656 if (!container) return;
1658 entry = container->members;
1659 while (entry) {
1660 void *data = entry->data;
1661 entry = g_slist_remove(entry, data);
1662 free_container_member((struct sipe_container_member *)data);
1664 g_free(container);
1667 static void
1668 sipe_send_container_members_prepare(const guint container_id,
1669 const guint container_version,
1670 const gchar *action,
1671 const gchar *type,
1672 const gchar *value,
1673 char **container_xmls)
1675 gchar *value_str = value ? g_strdup_printf(" value=\"%s\"", value) : g_strdup("");
1676 gchar *body;
1678 if (!container_xmls) return;
1680 body = g_strdup_printf(
1681 "<container id=\"%d\" version=\"%d\"><member action=\"%s\" type=\"%s\"%s/></container>",
1682 container_id,
1683 container_version,
1684 action,
1685 type,
1686 value_str);
1687 g_free(value_str);
1689 if ((*container_xmls) == NULL) {
1690 *container_xmls = body;
1691 } else {
1692 char *tmp = *container_xmls;
1694 *container_xmls = g_strconcat(*container_xmls, body, NULL);
1695 g_free(tmp);
1696 g_free(body);
1700 static void
1701 sipe_send_set_container_members(struct sipe_core_private *sipe_private,
1702 char *container_xmls)
1704 gchar *self;
1705 gchar *contact;
1706 gchar *hdr;
1707 gchar *body;
1709 if (!container_xmls) return;
1711 self = sip_uri_self(sipe_private);
1712 body = g_strdup_printf(
1713 "<setContainerMembers xmlns=\"http://schemas.microsoft.com/2006/09/sip/container-management\">"
1714 "%s"
1715 "</setContainerMembers>",
1716 container_xmls);
1718 contact = get_contact(sipe_private);
1719 hdr = g_strdup_printf("Contact: %s\r\n"
1720 "Content-Type: application/msrtc-setcontainermembers+xml\r\n", contact);
1721 g_free(contact);
1723 sip_transport_service(sipe_private,
1724 self,
1725 hdr,
1726 body,
1727 NULL);
1729 g_free(hdr);
1730 g_free(body);
1731 g_free(self);
1735 * Finds locally stored MS-PRES container member
1737 static struct sipe_container_member *
1738 sipe_find_container_member(struct sipe_container *container,
1739 const gchar *type,
1740 const gchar *value)
1742 struct sipe_container_member *member;
1743 GSList *entry;
1745 if (container == NULL || type == NULL) {
1746 return NULL;
1749 entry = container->members;
1750 while (entry) {
1751 member = entry->data;
1752 if (sipe_strcase_equal(member->type, type) &&
1753 sipe_strcase_equal(member->value, value))
1755 return member;
1757 entry = entry->next;
1759 return NULL;
1763 * Finds locally stored MS-PRES container by id
1765 static struct sipe_container *
1766 sipe_find_container(struct sipe_core_private *sipe_private,
1767 guint id)
1769 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
1770 struct sipe_container *container;
1771 GSList *entry;
1773 if (sip == NULL) {
1774 return NULL;
1777 entry = sip->containers;
1778 while (entry) {
1779 container = entry->data;
1780 if (id == container->id) {
1781 return container;
1783 entry = entry->next;
1785 return NULL;
1788 static GSList *
1789 sipe_get_access_domains(struct sipe_core_private *sipe_private)
1791 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
1792 struct sipe_container *container;
1793 struct sipe_container_member *member;
1794 GSList *entry;
1795 GSList *entry2;
1796 GSList *res = NULL;
1798 if (!sip) return NULL;
1800 entry = sip->containers;
1801 while (entry) {
1802 container = entry->data;
1804 entry2 = container->members;
1805 while (entry2) {
1806 member = entry2->data;
1807 if (sipe_strcase_equal(member->type, "domain"))
1809 res = slist_insert_unique_sorted(res, g_strdup(member->value), (GCompareFunc)g_ascii_strcasecmp);
1811 entry2 = entry2->next;
1813 entry = entry->next;
1815 return res;
1819 * Returns pointer to domain part in provided Email URL
1821 * @param email an email URL. Example: first.last@hq.company.com
1822 * @return pointer to domain part of email URL. Coresponding example: hq.company.com
1824 * Doesn't allocate memory
1826 static const char *
1827 sipe_get_domain(const char *email)
1829 char *tmp;
1831 if (!email) return NULL;
1833 tmp = strstr(email, "@");
1835 if (tmp && ((tmp+1) < (email + strlen(email)))) {
1836 return tmp+1;
1837 } else {
1838 return NULL;
1843 /* @TODO: replace with binary search for faster access? */
1844 /** source: http://support.microsoft.com/kb/897567 */
1845 static const char * const public_domains [] = {
1846 "aol.com", "icq.com", "love.com", "mac.com", "br.live.com",
1847 "hotmail.co.il", "hotmail.co.jp", "hotmail.co.th", "hotmail.co.uk",
1848 "hotmail.com", "hotmail.com.ar", "hotmail.com.tr", "hotmail.es",
1849 "hotmail.de", "hotmail.fr", "hotmail.it", "live.at", "live.be",
1850 "live.ca", "live.cl", "live.cn", "live.co.in", "live.co.kr",
1851 "live.co.uk", "live.co.za", "live.com", "live.com.ar", "live.com.au",
1852 "live.com.co", "live.com.mx", "live.com.my", "live.com.pe",
1853 "live.com.ph", "live.com.pk", "live.com.pt", "live.com.sg",
1854 "live.com.ve", "live.de", "live.dk", "live.fr", "live.hk", "live.ie",
1855 "live.in", "live.it", "live.jp", "live.nl", "live.no", "live.ph",
1856 "live.ru", "live.se", "livemail.com.br", "livemail.tw",
1857 "messengeruser.com", "msn.com", "passport.com", "sympatico.ca",
1858 "tw.live.com", "webtv.net", "windowslive.com", "windowslive.es",
1859 "yahoo.com",
1860 NULL};
1862 static gboolean
1863 sipe_is_public_domain(const char *domain)
1865 int i = 0;
1866 while (public_domains[i]) {
1867 if (sipe_strcase_equal(public_domains[i], domain)) {
1868 return TRUE;
1870 i++;
1872 return FALSE;
1876 * Access Levels
1877 * 32000 - Blocked
1878 * 400 - Personal
1879 * 300 - Team
1880 * 200 - Company
1881 * 100 - Public
1883 static const char *
1884 sipe_get_access_level_name(int container_id)
1886 switch(container_id) {
1887 case 32000: return _("Blocked");
1888 case 400: return _("Personal");
1889 case 300: return _("Team");
1890 case 200: return _("Company");
1891 case 100: return _("Public");
1893 return _("Unknown");
1896 static const guint containers[] = {32000, 400, 300, 200, 100};
1897 #define CONTAINERS_LEN (sizeof(containers) / sizeof(guint))
1900 static int
1901 sipe_find_member_access_level(struct sipe_core_private *sipe_private,
1902 const gchar *type,
1903 const gchar *value)
1905 unsigned int i = 0;
1906 const gchar *value_mod = value;
1908 if (!type) return -1;
1910 if (sipe_strequal("user", type)) {
1911 value_mod = sipe_get_no_sip_uri(value);
1914 for (i = 0; i < CONTAINERS_LEN; i++) {
1915 struct sipe_container_member *member;
1916 struct sipe_container *container = sipe_find_container(sipe_private, containers[i]);
1917 if (!container) continue;
1919 member = sipe_find_container_member(container, type, value_mod);
1920 if (member) return containers[i];
1923 return -1;
1926 /** Member type: user, domain, sameEnterprise, federated, publicCloud; everyone */
1927 static int
1928 sipe_find_access_level(struct sipe_core_private *sipe_private,
1929 const gchar *type,
1930 const gchar *value,
1931 gboolean *is_group_access)
1933 int container_id = -1;
1935 if (sipe_strequal("user", type)) {
1936 const char *domain;
1937 const char *no_sip_uri = sipe_get_no_sip_uri(value);
1939 container_id = sipe_find_member_access_level(sipe_private, "user", no_sip_uri);
1940 if (container_id >= 0) {
1941 if (is_group_access) *is_group_access = FALSE;
1942 return container_id;
1945 domain = sipe_get_domain(no_sip_uri);
1946 container_id = sipe_find_member_access_level(sipe_private, "domain", domain);
1947 if (container_id >= 0) {
1948 if (is_group_access) *is_group_access = TRUE;
1949 return container_id;
1952 container_id = sipe_find_member_access_level(sipe_private, "sameEnterprise", NULL);
1953 if ((container_id >= 0) && sipe_strcase_equal(sipe_private->public.sip_domain, domain)) {
1954 if (is_group_access) *is_group_access = TRUE;
1955 return container_id;
1958 container_id = sipe_find_member_access_level(sipe_private, "publicCloud", NULL);
1959 if ((container_id >= 0) && sipe_is_public_domain(domain)) {
1960 if (is_group_access) *is_group_access = TRUE;
1961 return container_id;
1964 container_id = sipe_find_member_access_level(sipe_private, "everyone", NULL);
1965 if ((container_id >= 0)) {
1966 if (is_group_access) *is_group_access = TRUE;
1967 return container_id;
1969 } else {
1970 container_id = sipe_find_member_access_level(sipe_private, type, value);
1971 if (is_group_access) *is_group_access = FALSE;
1974 return container_id;
1978 * @param container_id a new access level. If -1 then current access level
1979 * is just removed (I.e. the member is removed from all containers).
1980 * @param type a type of member. E.g. "user", "sameEnterprise", etc.
1981 * @param value a value for member. E.g. SIP URI for "user" member type.
1983 static void
1984 sipe_change_access_level(struct sipe_core_private *sipe_private,
1985 const int container_id,
1986 const gchar *type,
1987 const gchar *value)
1989 unsigned int i;
1990 int current_container_id = -1;
1991 char *container_xmls = NULL;
1993 /* for each container: find/delete */
1994 for (i = 0; i < CONTAINERS_LEN; i++) {
1995 struct sipe_container_member *member;
1996 struct sipe_container *container = sipe_find_container(sipe_private, containers[i]);
1998 if (!container) continue;
2000 member = sipe_find_container_member(container, type, value);
2001 if (member) {
2002 current_container_id = containers[i];
2003 /* delete/publish current access level */
2004 if (container_id < 0 || container_id != current_container_id) {
2005 sipe_send_container_members_prepare(current_container_id, container->version, "remove", type, value, &container_xmls);
2006 /* remove member from our cache, to be able to recalculate AL below */
2007 container->members = g_slist_remove(container->members, member);
2008 current_container_id = -1;
2013 /* recalculate AL below */
2014 current_container_id = sipe_find_access_level(sipe_private, type, value, NULL);
2016 /* assign/publish new access level */
2017 if (container_id != current_container_id && container_id >= 0) {
2018 struct sipe_container *container = sipe_find_container(sipe_private, container_id);
2019 guint version = container ? container->version : 0;
2021 sipe_send_container_members_prepare(container_id, version, "add", type, value, &container_xmls);
2024 if (container_xmls) {
2025 sipe_send_set_container_members(sipe_private, container_xmls);
2027 g_free(container_xmls);
2030 static void
2031 free_publication(struct sipe_publication *publication)
2033 g_free(publication->category);
2034 g_free(publication->cal_event_hash);
2035 g_free(publication->note);
2037 g_free(publication->working_hours_xml_str);
2038 g_free(publication->fb_start_str);
2039 g_free(publication->free_busy_base64);
2041 g_free(publication);
2044 /* key is <category><instance><container> */
2045 static gboolean
2046 sipe_is_our_publication(struct sipe_core_private *sipe_private,
2047 const gchar *key)
2049 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
2050 GSList *entry;
2052 /* filling keys for our publications if not yet cached */
2053 if (!sip->our_publication_keys) {
2054 guint device_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_DEVICE);
2055 guint machine_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_MACHINE);
2056 guint user_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_USER);
2057 guint calendar_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_CALENDAR);
2058 guint cal_oof_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_CALENDAR_OOF);
2059 guint cal_data_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_CALENDAR_DATA);
2060 guint note_oof_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_NOTE_OOF);
2062 SIPE_DEBUG_INFO_NOFORMAT("* Our Publication Instances *");
2063 SIPE_DEBUG_INFO("\tDevice : %u\t0x%08X", device_instance, device_instance);
2064 SIPE_DEBUG_INFO("\tMachine State : %u\t0x%08X", machine_instance, machine_instance);
2065 SIPE_DEBUG_INFO("\tUser Stare : %u\t0x%08X", user_instance, user_instance);
2066 SIPE_DEBUG_INFO("\tCalendar State : %u\t0x%08X", calendar_instance, calendar_instance);
2067 SIPE_DEBUG_INFO("\tCalendar OOF State : %u\t0x%08X", cal_oof_instance, cal_oof_instance);
2068 SIPE_DEBUG_INFO("\tCalendar FreeBusy : %u\t0x%08X", cal_data_instance, cal_data_instance);
2069 SIPE_DEBUG_INFO("\tOOF Note : %u\t0x%08X", note_oof_instance, note_oof_instance);
2070 SIPE_DEBUG_INFO("\tNote : %u", 0);
2071 SIPE_DEBUG_INFO("\tCalendar WorkingHours: %u", 0);
2073 /* device */
2074 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2075 g_strdup_printf("<%s><%u><%u>", "device", device_instance, 2));
2077 /* state:machineState */
2078 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2079 g_strdup_printf("<%s><%u><%u>", "state", machine_instance, 2));
2080 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2081 g_strdup_printf("<%s><%u><%u>", "state", machine_instance, 3));
2083 /* state:userState */
2084 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2085 g_strdup_printf("<%s><%u><%u>", "state", user_instance, 2));
2086 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2087 g_strdup_printf("<%s><%u><%u>", "state", user_instance, 3));
2089 /* state:calendarState */
2090 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2091 g_strdup_printf("<%s><%u><%u>", "state", calendar_instance, 2));
2092 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2093 g_strdup_printf("<%s><%u><%u>", "state", calendar_instance, 3));
2095 /* state:calendarState OOF */
2096 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2097 g_strdup_printf("<%s><%u><%u>", "state", cal_oof_instance, 2));
2098 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2099 g_strdup_printf("<%s><%u><%u>", "state", cal_oof_instance, 3));
2101 /* note */
2102 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2103 g_strdup_printf("<%s><%u><%u>", "note", 0, 200));
2104 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2105 g_strdup_printf("<%s><%u><%u>", "note", 0, 300));
2106 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2107 g_strdup_printf("<%s><%u><%u>", "note", 0, 400));
2109 /* note OOF */
2110 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2111 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 200));
2112 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2113 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 300));
2114 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2115 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 400));
2117 /* calendarData:WorkingHours */
2118 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2119 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 1));
2120 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2121 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 100));
2122 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2123 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 200));
2124 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2125 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 300));
2126 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2127 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 400));
2128 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2129 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 32000));
2131 /* calendarData:FreeBusy */
2132 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2133 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 1));
2134 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2135 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 100));
2136 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2137 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 200));
2138 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2139 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 300));
2140 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2141 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 400));
2142 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2143 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 32000));
2145 //SIPE_DEBUG_INFO("sipe_is_our_publication: sip->our_publication_keys length=%d",
2146 // sip->our_publication_keys ? (int) g_slist_length(sip->our_publication_keys) : -1);
2149 //SIPE_DEBUG_INFO("sipe_is_our_publication: key=%s", key);
2151 entry = sip->our_publication_keys;
2152 while (entry) {
2153 //SIPE_DEBUG_INFO(" sipe_is_our_publication: entry->data=%s", entry->data);
2154 if (sipe_strequal(entry->data, key)) {
2155 return TRUE;
2157 entry = entry->next;
2159 return FALSE;
2162 /** Property names to store in blist.xml */
2163 #define ALIAS_PROP "alias"
2164 #define EMAIL_PROP "email"
2165 #define PHONE_PROP "phone"
2166 #define PHONE_DISPLAY_PROP "phone-display"
2167 #define PHONE_MOBILE_PROP "phone-mobile"
2168 #define PHONE_MOBILE_DISPLAY_PROP "phone-mobile-display"
2169 #define PHONE_HOME_PROP "phone-home"
2170 #define PHONE_HOME_DISPLAY_PROP "phone-home-display"
2171 #define PHONE_OTHER_PROP "phone-other"
2172 #define PHONE_OTHER_DISPLAY_PROP "phone-other-display"
2173 #define PHONE_CUSTOM1_PROP "phone-custom1"
2174 #define PHONE_CUSTOM1_DISPLAY_PROP "phone-custom1-display"
2175 #define SITE_PROP "site"
2176 #define COMPANY_PROP "company"
2177 #define DEPARTMENT_PROP "department"
2178 #define TITLE_PROP "title"
2179 #define OFFICE_PROP "office"
2180 /** implies work address */
2181 #define ADDRESS_STREET_PROP "address-street"
2182 #define ADDRESS_CITY_PROP "address-city"
2183 #define ADDRESS_STATE_PROP "address-state"
2184 #define ADDRESS_ZIPCODE_PROP "address-zipcode"
2185 #define ADDRESS_COUNTRYCODE_PROP "address-country-code"
2188 * Tries to figure out user first and last name
2189 * based on Display Name and email properties.
2191 * Allocates memory - must be g_free()'d
2193 * Examples to parse:
2194 * First Last
2195 * First Last - Company Name
2196 * Last, First
2197 * Last, First M.
2198 * Last, First (C)(STP) (Company)
2199 * first.last@company.com (preprocessed as "first last")
2200 * first.last.company.com@reuters.net (preprocessed as "first last company com")
2202 * Unusable examples:
2203 * user@company.com (preprocessed as "user")
2204 * first.m.last@company.com (preprocessed as "first m last")
2205 * user.company.com@reuters.net (preprocessed as "user company com")
2207 static void
2208 sipe_get_first_last_names(struct sipe_core_private *sipe_private,
2209 const char *uri,
2210 char **first_name,
2211 char **last_name)
2213 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
2214 PurpleBuddy *p_buddy;
2215 char *display_name;
2216 const char *email;
2217 const char *first, *last;
2218 char *tmp;
2219 char **parts;
2220 gboolean has_comma = FALSE;
2222 if (!sip || !uri) return;
2224 p_buddy = purple_find_buddy(sip->account, uri);
2226 if (!p_buddy) return;
2228 display_name = g_strdup(purple_buddy_get_alias(p_buddy));
2229 email = purple_blist_node_get_string(&p_buddy->node, EMAIL_PROP);
2231 if (!display_name && !email) return;
2233 /* if no display name, make "first last anything_else" out of email */
2234 if (email && !display_name) {
2235 display_name = g_strndup(email, strstr(email, "@") - email);
2236 display_name = sipe_utils_str_replace((tmp = display_name), ".", " ");
2237 g_free(tmp);
2240 if (display_name) {
2241 has_comma = (strstr(display_name, ",") != NULL);
2242 display_name = sipe_utils_str_replace((tmp = display_name), ", ", " ");
2243 g_free(tmp);
2244 display_name = sipe_utils_str_replace((tmp = display_name), ",", " ");
2245 g_free(tmp);
2248 parts = g_strsplit(display_name, " ", 0);
2250 if (!parts[0] || !parts[1]) {
2251 g_free(display_name);
2252 g_strfreev(parts);
2253 return;
2256 if (has_comma) {
2257 last = parts[0];
2258 first = parts[1];
2259 } else {
2260 first = parts[0];
2261 last = parts[1];
2264 if (first_name) {
2265 *first_name = g_strstrip(g_strdup(first));
2268 if (last_name) {
2269 *last_name = g_strstrip(g_strdup(last));
2272 g_free(display_name);
2273 g_strfreev(parts);
2277 * Update user information
2279 * @param uri buddy SIP URI with 'sip:' prefix whose info we want to change.
2280 * @param property_name
2281 * @param property_value may be modified to strip white space
2283 static void
2284 sipe_update_user_info(struct sipe_core_private *sipe_private,
2285 const char *uri,
2286 const char *property_name,
2287 char *property_value)
2289 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
2290 GSList *buddies, *entry;
2292 if (!property_name || strlen(property_name) == 0) return;
2294 if (property_value)
2295 property_value = g_strstrip(property_value);
2297 entry = buddies = purple_find_buddies(sip->account, uri); /* all buddies in different groups */
2298 while (entry) {
2299 const char *prop_str;
2300 const char *server_alias;
2301 PurpleBuddy *p_buddy = entry->data;
2303 /* for Display Name */
2304 if (sipe_strequal(property_name, ALIAS_PROP)) {
2305 if (property_value && sipe_is_bad_alias(uri, purple_buddy_get_alias(p_buddy))) {
2306 SIPE_DEBUG_INFO("Replacing alias for %s with %s", uri, property_value);
2307 purple_blist_alias_buddy(p_buddy, property_value);
2310 server_alias = purple_buddy_get_server_alias(p_buddy);
2311 if (!is_empty(property_value) &&
2312 (!sipe_strequal(property_value, server_alias) || is_empty(server_alias)) )
2314 purple_blist_server_alias_buddy(p_buddy, property_value);
2317 /* for other properties */
2318 else {
2319 if (!is_empty(property_value)) {
2320 prop_str = purple_blist_node_get_string(&p_buddy->node, property_name);
2321 if (!prop_str || !sipe_strcase_equal(prop_str, property_value)) {
2322 purple_blist_node_set_string(&p_buddy->node, property_name, property_value);
2327 entry = entry->next;
2329 g_slist_free(buddies);
2333 * Update user phone
2334 * Suitable for both 2005 and 2007 systems.
2336 * @param uri buddy SIP URI with 'sip:' prefix whose info we want to change.
2337 * @param phone_type
2338 * @param phone may be modified to strip white space
2339 * @param phone_display_string may be modified to strip white space
2341 static void
2342 sipe_update_user_phone(struct sipe_core_private *sipe_private,
2343 const char *uri,
2344 const gchar *phone_type,
2345 gchar *phone,
2346 gchar *phone_display_string)
2348 const char *phone_node = PHONE_PROP; /* work phone by default */
2349 const char *phone_display_node = PHONE_DISPLAY_PROP; /* work phone by default */
2351 if(!phone || strlen(phone) == 0) return;
2353 if ((sipe_strequal(phone_type, "mobile") || sipe_strequal(phone_type, "cell"))) {
2354 phone_node = PHONE_MOBILE_PROP;
2355 phone_display_node = PHONE_MOBILE_DISPLAY_PROP;
2356 } else if (sipe_strequal(phone_type, "home")) {
2357 phone_node = PHONE_HOME_PROP;
2358 phone_display_node = PHONE_HOME_DISPLAY_PROP;
2359 } else if (sipe_strequal(phone_type, "other")) {
2360 phone_node = PHONE_OTHER_PROP;
2361 phone_display_node = PHONE_OTHER_DISPLAY_PROP;
2362 } else if (sipe_strequal(phone_type, "custom1")) {
2363 phone_node = PHONE_CUSTOM1_PROP;
2364 phone_display_node = PHONE_CUSTOM1_DISPLAY_PROP;
2367 sipe_update_user_info(sipe_private, uri, phone_node, phone);
2368 if (phone_display_string) {
2369 sipe_update_user_info(sipe_private, uri, phone_display_node, phone_display_string);
2373 void
2374 sipe_core_update_calendar(struct sipe_core_public *sipe_public)
2376 SIPE_DEBUG_INFO_NOFORMAT("sipe_core_update_calendar: started.");
2378 /* Do in parallel.
2379 * If failed, the branch will be disabled for subsequent calls.
2380 * Can't rely that user turned the functionality on in account settings.
2382 sipe_ews_update_calendar(SIPE_CORE_PRIVATE);
2383 sipe_domino_update_calendar(SIPE_CORE_PRIVATE);
2385 /* schedule repeat */
2386 sipe_schedule_seconds(SIPE_CORE_PRIVATE,
2387 "<+update-calendar>",
2388 NULL,
2389 UPDATE_CALENDAR_INTERVAL,
2390 (sipe_schedule_action)sipe_core_update_calendar,
2391 NULL);
2393 SIPE_DEBUG_INFO_NOFORMAT("sipe_core_update_calendar: finished.");
2397 * This method motivates Purple's Host (e.g. Pidgin) to update its UI
2398 * by using standard Purple's means of signals and saved statuses.
2400 * Thus all UI elements get updated: Status Button with Note, docklet.
2401 * This is ablolutely important as both our status and note can come
2402 * inbound (roaming) or be updated programmatically (e.g. based on our
2403 * calendar data).
2405 static void
2406 sipe_set_purple_account_status_and_note(const PurpleAccount *account,
2407 const char *status_id,
2408 const char *message,
2409 time_t do_not_publish[])
2411 PurpleStatus *status = purple_account_get_active_status(account);
2412 gboolean changed = TRUE;
2414 if (g_str_equal(status_id, purple_status_get_id(status)) &&
2415 sipe_strequal(message, purple_status_get_attr_string(status, SIPE_STATUS_ATTR_ID_MESSAGE)))
2417 changed = FALSE;
2420 if (purple_savedstatus_is_idleaway()) {
2421 changed = FALSE;
2424 if (changed) {
2425 PurpleSavedStatus *saved_status;
2426 const PurpleStatusType *acct_status_type =
2427 purple_status_type_find_with_id(account->status_types, status_id);
2428 PurpleStatusPrimitive primitive = purple_status_type_get_primitive(acct_status_type);
2429 sipe_activity activity = sipe_get_activity_by_token(status_id);
2431 saved_status = purple_savedstatus_find_transient_by_type_and_message(primitive, message);
2432 if (saved_status) {
2433 purple_savedstatus_set_substatus(saved_status, account, acct_status_type, message);
2436 /* If this type+message is unique then create a new transient saved status
2437 * Ref: gtkstatusbox.c
2439 if (!saved_status) {
2440 GList *tmp;
2441 GList *active_accts = purple_accounts_get_all_active();
2443 saved_status = purple_savedstatus_new(NULL, primitive);
2444 purple_savedstatus_set_message(saved_status, message);
2446 for (tmp = active_accts; tmp != NULL; tmp = tmp->next) {
2447 purple_savedstatus_set_substatus(saved_status,
2448 (PurpleAccount *)tmp->data, acct_status_type, message);
2450 g_list_free(active_accts);
2453 do_not_publish[activity] = time(NULL);
2454 SIPE_DEBUG_INFO("sipe_set_purple_account_status_and_note: do_not_publish[%s]=%d [now]",
2455 status_id, (int)do_not_publish[activity]);
2457 /* Set the status for each account */
2458 purple_savedstatus_activate(saved_status);
2462 struct hash_table_delete_payload {
2463 GHashTable *hash_table;
2464 guint container;
2467 static void
2468 sipe_remove_category_container_publications_cb(const char *name,
2469 struct sipe_publication *publication,
2470 struct hash_table_delete_payload *payload)
2472 if (publication->container == payload->container) {
2473 g_hash_table_remove(payload->hash_table, name);
2476 static void
2477 sipe_remove_category_container_publications(GHashTable *our_publications,
2478 const char *category,
2479 guint container)
2481 struct hash_table_delete_payload payload;
2482 payload.hash_table = g_hash_table_lookup(our_publications, category);
2484 if (!payload.hash_table) return;
2486 payload.container = container;
2487 g_hash_table_foreach(payload.hash_table, (GHFunc)sipe_remove_category_container_publications_cb, &payload);
2490 static void
2491 send_publish_category_initial(struct sipe_core_private *sipe_private);
2494 * When we receive some self (BE) NOTIFY with a new subscriber
2495 * we sends a setSubscribers request to him [SIP-PRES] 4.8
2498 static void sipe_process_roaming_self(struct sipe_core_private *sipe_private,
2499 struct sipmsg *msg)
2501 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
2502 gchar *contact;
2503 gchar *to;
2504 sipe_xml *xml;
2505 const sipe_xml *node;
2506 const sipe_xml *node2;
2507 char *display_name = NULL;
2508 char *uri;
2509 GSList *category_names = NULL;
2510 int aggreg_avail = 0;
2511 gboolean do_update_status = FALSE;
2512 gboolean has_note_cleaned = FALSE;
2514 SIPE_DEBUG_INFO_NOFORMAT("sipe_process_roaming_self");
2516 xml = sipe_xml_parse(msg->body, msg->bodylen);
2517 if (!xml) return;
2519 contact = get_contact(sipe_private);
2520 to = sip_uri_self(sipe_private);
2523 /* categories */
2524 /* set list of categories participating in this XML */
2525 for (node = sipe_xml_child(xml, "categories/category"); node; node = sipe_xml_twin(node)) {
2526 const gchar *name = sipe_xml_attribute(node, "name");
2527 category_names = slist_insert_unique_sorted(category_names, (gchar *)name, (GCompareFunc)strcmp);
2529 SIPE_DEBUG_INFO("sipe_process_roaming_self: category_names length=%d",
2530 category_names ? (int) g_slist_length(category_names) : -1);
2531 /* drop category information */
2532 if (category_names) {
2533 GSList *entry = category_names;
2534 while (entry) {
2535 GHashTable *cat_publications;
2536 const gchar *category = entry->data;
2537 entry = entry->next;
2538 SIPE_DEBUG_INFO("sipe_process_roaming_self: dropping category: %s", category);
2539 cat_publications = g_hash_table_lookup(sip->our_publications, category);
2540 if (cat_publications) {
2541 g_hash_table_remove(sip->our_publications, category);
2542 SIPE_DEBUG_INFO("sipe_process_roaming_self: dropped category: %s", category);
2546 g_slist_free(category_names);
2547 /* filling our categories reflected in roaming data */
2548 for (node = sipe_xml_child(xml, "categories/category"); node; node = sipe_xml_twin(node)) {
2549 const char *tmp;
2550 const gchar *name = sipe_xml_attribute(node, "name");
2551 guint container = sipe_xml_int_attribute(node, "container", -1);
2552 guint instance = sipe_xml_int_attribute(node, "instance", -1);
2553 guint version = sipe_xml_int_attribute(node, "version", 0);
2554 time_t publish_time = (tmp = sipe_xml_attribute(node, "publishTime")) ?
2555 sipe_utils_str_to_time(tmp) : 0;
2556 gchar *key;
2557 GHashTable *cat_publications = g_hash_table_lookup(sip->our_publications, name);
2559 /* Ex. clear note: <category name="note"/> */
2560 if (container == (guint)-1) {
2561 g_free(sip->note);
2562 sip->note = NULL;
2563 do_update_status = TRUE;
2564 continue;
2567 /* Ex. clear note: <category name="note" container="200"/> */
2568 if (instance == (guint)-1) {
2569 if (container == 200) {
2570 g_free(sip->note);
2571 sip->note = NULL;
2572 do_update_status = TRUE;
2574 SIPE_DEBUG_INFO("sipe_process_roaming_self: removing publications for: %s/%u", name, container);
2575 sipe_remove_category_container_publications(
2576 sip->our_publications, name, container);
2577 continue;
2580 /* key is <category><instance><container> */
2581 key = g_strdup_printf("<%s><%u><%u>", name, instance, container);
2582 SIPE_DEBUG_INFO("sipe_process_roaming_self: key=%s version=%d", key, version);
2584 /* capture all userState publication for later clean up if required */
2585 if (sipe_strequal(name, "state") && (container == 2 || container == 3)) {
2586 const sipe_xml *xn_state = sipe_xml_child(node, "state");
2588 if (xn_state && sipe_strequal(sipe_xml_attribute(xn_state, "type"), "userState")) {
2589 struct sipe_publication *publication = g_new0(struct sipe_publication, 1);
2590 publication->category = g_strdup(name);
2591 publication->instance = instance;
2592 publication->container = container;
2593 publication->version = version;
2595 if (!sip->user_state_publications) {
2596 sip->user_state_publications = g_hash_table_new_full(
2597 g_str_hash, g_str_equal,
2598 g_free, (GDestroyNotify)free_publication);
2600 g_hash_table_insert(sip->user_state_publications, g_strdup(key), publication);
2601 SIPE_DEBUG_INFO("sipe_process_roaming_self: added to user_state_publications key=%s version=%d",
2602 key, version);
2606 if (sipe_is_our_publication(sipe_private, key)) {
2607 struct sipe_publication *publication = g_new0(struct sipe_publication, 1);
2609 publication->category = g_strdup(name);
2610 publication->instance = instance;
2611 publication->container = container;
2612 publication->version = version;
2614 /* filling publication->availability */
2615 if (sipe_strequal(name, "state")) {
2616 const sipe_xml *xn_state = sipe_xml_child(node, "state");
2617 const sipe_xml *xn_avail = sipe_xml_child(xn_state, "availability");
2619 if (xn_avail) {
2620 gchar *avail_str = sipe_xml_data(xn_avail);
2621 if (avail_str) {
2622 publication->availability = atoi(avail_str);
2624 g_free(avail_str);
2626 /* for calendarState */
2627 if (xn_state && sipe_strequal(sipe_xml_attribute(xn_state, "type"), "calendarState")) {
2628 const sipe_xml *xn_activity = sipe_xml_child(xn_state, "activity");
2629 struct sipe_cal_event *event = g_new0(struct sipe_cal_event, 1);
2631 event->start_time = sipe_utils_str_to_time(sipe_xml_attribute(xn_state, "startTime"));
2632 if (xn_activity) {
2633 if (sipe_strequal(sipe_xml_attribute(xn_activity, "token"),
2634 sipe_activity_map[SIPE_ACTIVITY_IN_MEETING].token))
2636 event->is_meeting = TRUE;
2639 event->subject = sipe_xml_data(sipe_xml_child(xn_state, "meetingSubject"));
2640 event->location = sipe_xml_data(sipe_xml_child(xn_state, "meetingLocation"));
2642 publication->cal_event_hash = sipe_cal_event_hash(event);
2643 SIPE_DEBUG_INFO("sipe_process_roaming_self: hash=%s",
2644 publication->cal_event_hash);
2645 sipe_cal_event_free(event);
2648 /* filling publication->note */
2649 if (sipe_strequal(name, "note")) {
2650 const sipe_xml *xn_body = sipe_xml_child(node, "note/body");
2652 if (!has_note_cleaned) {
2653 has_note_cleaned = TRUE;
2655 g_free(sip->note);
2656 sip->note = NULL;
2657 sip->note_since = publish_time;
2659 do_update_status = TRUE;
2662 g_free(publication->note);
2663 publication->note = NULL;
2664 if (xn_body) {
2665 char *tmp;
2667 publication->note = g_markup_escape_text((tmp = sipe_xml_data(xn_body)), -1);
2668 g_free(tmp);
2669 if (publish_time >= sip->note_since) {
2670 g_free(sip->note);
2671 sip->note = g_strdup(publication->note);
2672 sip->note_since = publish_time;
2673 sip->is_oof_note = sipe_strequal(sipe_xml_attribute(xn_body, "type"), "OOF");
2675 do_update_status = TRUE;
2680 /* filling publication->fb_start_str, free_busy_base64, working_hours_xml_str */
2681 if (sipe_strequal(name, "calendarData") && (publication->container == 300)) {
2682 const sipe_xml *xn_free_busy = sipe_xml_child(node, "calendarData/freeBusy");
2683 const sipe_xml *xn_working_hours = sipe_xml_child(node, "calendarData/WorkingHours");
2684 if (xn_free_busy) {
2685 publication->fb_start_str = g_strdup(sipe_xml_attribute(xn_free_busy, "startTime"));
2686 publication->free_busy_base64 = sipe_xml_data(xn_free_busy);
2688 if (xn_working_hours) {
2689 publication->working_hours_xml_str = sipe_xml_stringify(xn_working_hours);
2693 if (!cat_publications) {
2694 cat_publications = g_hash_table_new_full(
2695 g_str_hash, g_str_equal,
2696 g_free, (GDestroyNotify)free_publication);
2697 g_hash_table_insert(sip->our_publications, g_strdup(name), cat_publications);
2698 SIPE_DEBUG_INFO("sipe_process_roaming_self: added GHashTable cat=%s", name);
2700 g_hash_table_insert(cat_publications, g_strdup(key), publication);
2701 SIPE_DEBUG_INFO("sipe_process_roaming_self: added key=%s version=%d", key, version);
2703 g_free(key);
2705 /* aggregateState (not an our publication) from 2-nd container */
2706 if (sipe_strequal(name, "state") && container == 2) {
2707 const sipe_xml *xn_state = sipe_xml_child(node, "state");
2709 if (xn_state && sipe_strequal(sipe_xml_attribute(xn_state, "type"), "aggregateState")) {
2710 const sipe_xml *xn_avail = sipe_xml_child(xn_state, "availability");
2712 if (xn_avail) {
2713 gchar *avail_str = sipe_xml_data(xn_avail);
2714 if (avail_str) {
2715 aggreg_avail = atoi(avail_str);
2717 g_free(avail_str);
2720 do_update_status = TRUE;
2724 /* userProperties published by server from AD */
2725 if (!sip->csta && sipe_strequal(name, "userProperties")) {
2726 const sipe_xml *line;
2727 /* line, for Remote Call Control (RCC) */
2728 for (line = sipe_xml_child(node, "userProperties/lines/line"); line; line = sipe_xml_twin(line)) {
2729 const gchar *line_server = sipe_xml_attribute(line, "lineServer");
2730 const gchar *line_type = sipe_xml_attribute(line, "lineType");
2731 gchar *line_uri;
2733 if (!line_server || !(sipe_strequal(line_type, "Rcc") || sipe_strequal(line_type, "Dual"))) continue;
2735 line_uri = sipe_xml_data(line);
2736 if (line_uri) {
2737 SIPE_DEBUG_INFO("sipe_process_roaming_self: line_uri=%s server=%s", line_uri, line_server);
2738 sip_csta_open(sipe_private, line_uri, line_server);
2740 g_free(line_uri);
2742 break;
2746 SIPE_DEBUG_INFO("sipe_process_roaming_self: sip->our_publications size=%d",
2747 sip->our_publications ? (int) g_hash_table_size(sip->our_publications) : -1);
2749 /* containers */
2750 for (node = sipe_xml_child(xml, "containers/container"); node; node = sipe_xml_twin(node)) {
2751 guint id = sipe_xml_int_attribute(node, "id", 0);
2752 struct sipe_container *container = sipe_find_container(sipe_private, id);
2754 if (container) {
2755 sip->containers = g_slist_remove(sip->containers, container);
2756 SIPE_DEBUG_INFO("sipe_process_roaming_self: removed existing container id=%d v%d", container->id, container->version);
2757 free_container(container);
2759 container = g_new0(struct sipe_container, 1);
2760 container->id = id;
2761 container->version = sipe_xml_int_attribute(node, "version", 0);
2762 sip->containers = g_slist_append(sip->containers, container);
2763 SIPE_DEBUG_INFO("sipe_process_roaming_self: added container id=%d v%d", container->id, container->version);
2765 for (node2 = sipe_xml_child(node, "member"); node2; node2 = sipe_xml_twin(node2)) {
2766 struct sipe_container_member *member = g_new0(struct sipe_container_member, 1);
2767 member->type = g_strdup(sipe_xml_attribute(node2, "type"));
2768 member->value = g_strdup(sipe_xml_attribute(node2, "value"));
2769 container->members = g_slist_append(container->members, member);
2770 SIPE_DEBUG_INFO("sipe_process_roaming_self: added container member type=%s value=%s",
2771 member->type, member->value ? member->value : "");
2775 SIPE_DEBUG_INFO("sipe_process_roaming_self: sip->access_level_set=%s", sip->access_level_set ? "TRUE" : "FALSE");
2776 if (!sip->access_level_set && sipe_xml_child(xml, "containers")) {
2777 char *container_xmls = NULL;
2778 int sameEnterpriseAL = sipe_find_access_level(sipe_private, "sameEnterprise", NULL, NULL);
2779 int federatedAL = sipe_find_access_level(sipe_private, "federated", NULL, NULL);
2781 SIPE_DEBUG_INFO("sipe_process_roaming_self: sameEnterpriseAL=%d", sameEnterpriseAL);
2782 SIPE_DEBUG_INFO("sipe_process_roaming_self: federatedAL=%d", federatedAL);
2783 /* initial set-up to let counterparties see your status */
2784 if (sameEnterpriseAL < 0) {
2785 struct sipe_container *container = sipe_find_container(sipe_private, 200);
2786 guint version = container ? container->version : 0;
2787 sipe_send_container_members_prepare(200, version, "add", "sameEnterprise", NULL, &container_xmls);
2789 if (federatedAL < 0) {
2790 struct sipe_container *container = sipe_find_container(sipe_private, 100);
2791 guint version = container ? container->version : 0;
2792 sipe_send_container_members_prepare(100, version, "add", "federated", NULL, &container_xmls);
2794 sip->access_level_set = TRUE;
2796 if (container_xmls) {
2797 sipe_send_set_container_members(sipe_private, container_xmls);
2799 g_free(container_xmls);
2802 /* Refresh contacts' blocked status */
2803 sipe_refresh_blocked_status(sipe_private);
2805 /* subscribers */
2806 for (node = sipe_xml_child(xml, "subscribers/subscriber"); node; node = sipe_xml_twin(node)) {
2807 const char *user;
2808 const char *acknowledged;
2809 gchar *hdr;
2810 gchar *body;
2812 user = sipe_xml_attribute(node, "user"); /* without 'sip:' prefix */
2813 if (!user) continue;
2814 SIPE_DEBUG_INFO("sipe_process_roaming_self: user %s", user);
2815 display_name = g_strdup(sipe_xml_attribute(node, "displayName"));
2816 uri = sip_uri_from_name(user);
2818 sipe_update_user_info(sipe_private, uri, ALIAS_PROP, display_name);
2820 acknowledged= sipe_xml_attribute(node, "acknowledged");
2821 if(sipe_strcase_equal(acknowledged,"false")){
2822 SIPE_DEBUG_INFO("sipe_process_roaming_self: user added you %s", user);
2823 if (!purple_find_buddy(sip->account, uri)) {
2824 purple_account_request_add(sip->account, uri, _("you"), display_name, NULL);
2827 hdr = g_strdup_printf(
2828 "Contact: %s\r\n"
2829 "Content-Type: application/msrtc-presence-setsubscriber+xml\r\n", contact);
2831 body = g_strdup_printf(
2832 "<setSubscribers xmlns=\"http://schemas.microsoft.com/2006/09/sip/presence-subscribers\">"
2833 "<subscriber user=\"%s\" acknowledged=\"true\"/>"
2834 "</setSubscribers>", user);
2836 sip_transport_service(sipe_private,
2838 hdr,
2839 body,
2840 NULL);
2841 g_free(body);
2842 g_free(hdr);
2844 g_free(display_name);
2845 g_free(uri);
2848 g_free(contact);
2849 sipe_xml_free(xml);
2851 /* Publish initial state if not yet.
2852 * Assuming this happens on initial responce to subscription to roaming-self
2853 * so we've already updated our roaming data in full.
2854 * Only for 2007+
2856 if (!sip->initial_state_published) {
2857 send_publish_category_initial(sipe_private);
2858 sipe_groupchat_init(sipe_private);
2859 sip->initial_state_published = TRUE;
2860 /* dalayed run */
2861 sipe_schedule_seconds(sipe_private,
2862 "<+update-calendar>",
2863 NULL,
2864 UPDATE_CALENDAR_DELAY,
2865 (sipe_schedule_action)sipe_core_update_calendar,
2866 NULL);
2867 do_update_status = FALSE;
2868 } else if (aggreg_avail) {
2870 g_free(sip->status);
2871 if (aggreg_avail && aggreg_avail < 18000) { /* not offline */
2872 sip->status = g_strdup(sipe_get_status_by_availability(aggreg_avail, NULL));
2873 } else {
2874 sip->status = g_strdup(SIPE_STATUS_ID_INVISIBLE); /* not not let offline status switch us off */
2878 if (do_update_status) {
2879 SIPE_DEBUG_INFO("sipe_process_roaming_self: switch to '%s' for the account", sip->status);
2880 sipe_set_purple_account_status_and_note(sip->account, sip->status, sip->note, sip->do_not_publish);
2883 g_free(to);
2886 /* IM Session (INVITE and MESSAGE methods) */
2888 /* EndPoints: "alice alisson" <sip:alice@atlanta.local>, <sip:bob@atlanta.local>;epid=ebca82d94d, <sip:carol@atlanta.local> */
2889 static gchar *
2890 get_end_points (struct sipe_core_private *sipe_private,
2891 struct sip_session *session)
2893 gchar *res;
2895 if (session == NULL) {
2896 return NULL;
2899 res = g_strdup_printf("<sip:%s>", sipe_private->username);
2901 SIPE_DIALOG_FOREACH {
2902 gchar *tmp = res;
2903 res = g_strdup_printf("%s, <%s>", res, dialog->with);
2904 g_free(tmp);
2906 if (dialog->theirepid) {
2907 tmp = res;
2908 res = g_strdup_printf("%s;epid=%s", res, dialog->theirepid);
2909 g_free(tmp);
2911 } SIPE_DIALOG_FOREACH_END;
2913 return res;
2916 static gboolean
2917 process_options_response(SIPE_UNUSED_PARAMETER struct sipe_core_private *sipe_private,
2918 struct sipmsg *msg,
2919 SIPE_UNUSED_PARAMETER struct transaction *trans)
2921 gboolean ret = TRUE;
2923 if (msg->response != 200) {
2924 SIPE_DEBUG_INFO("process_options_response: OPTIONS response is %d", msg->response);
2925 return FALSE;
2928 SIPE_DEBUG_INFO("process_options_response: body:\n%s", msg->body ? msg->body : "");
2930 return ret;
2934 * Asks UA/proxy about its capabilities.
2936 static void sipe_options_request(struct sipe_core_private *sipe_private,
2937 const char *who)
2939 gchar *to = sip_uri(who);
2940 gchar *contact = get_contact(sipe_private);
2941 gchar *request = g_strdup_printf(
2942 "Accept: application/sdp\r\n"
2943 "Contact: %s\r\n", contact);
2944 g_free(contact);
2946 sip_transport_request(sipe_private,
2947 "OPTIONS",
2950 request,
2951 NULL,
2952 NULL,
2953 process_options_response);
2955 g_free(to);
2956 g_free(request);
2959 void
2960 sipe_present_info(struct sipe_core_private *sipe_private,
2961 struct sip_session *session,
2962 const gchar *message)
2964 sipe_backend_notify_message_info(SIPE_CORE_PUBLIC,
2965 session->chat_session ? session->chat_session->backend : NULL,
2966 session->with,
2967 message);
2970 void
2971 sipe_present_err(struct sipe_core_private *sipe_private,
2972 struct sip_session *session,
2973 const gchar *message)
2975 sipe_backend_notify_message_error(SIPE_CORE_PUBLIC,
2976 session->chat_session ? session->chat_session->backend : NULL,
2977 session->with,
2978 message);
2981 void
2982 sipe_present_message_undelivered_err(struct sipe_core_private *sipe_private,
2983 struct sip_session *session,
2984 int sip_error,
2985 int sip_warning,
2986 const gchar *who,
2987 const gchar *message)
2989 char *msg, *msg_tmp, *msg_tmp2;
2990 const char *label;
2992 msg_tmp = message ? sipe_backend_markup_strip_html(message) : NULL;
2993 msg = msg_tmp ? g_strdup_printf("<font color=\"#888888\"></b>%s<b></font>", msg_tmp) : NULL;
2994 g_free(msg_tmp);
2995 /* Service unavailable; Server Internal Error; Server Time-out */
2996 if (sip_error == 606 && sip_warning == 309) { /* Not acceptable all. */ /* Message contents not allowed by policy */
2997 label = _("Your message or invitation was not delivered, possibly because it contains a hyperlink or other content that the system administrator has blocked.");
2998 g_free(msg);
2999 msg = NULL;
3000 } else if (sip_error == 500 || sip_error == 503 || sip_error == 504 || sip_error == 603) {
3001 label = _("This message was not delivered to %s because the service is not available");
3002 } else if (sip_error == 486) { /* Busy Here */
3003 label = _("This message was not delivered to %s because one or more recipients do not want to be disturbed");
3004 } else if (sip_error == 415) { /* Unsupported media type */
3005 label = _("This message was not delivered to %s because one or more recipients don't support this type of message");
3006 } else {
3007 label = _("This message was not delivered to %s because one or more recipients are offline");
3010 msg_tmp = g_strdup_printf( "%s%s\n%s" ,
3011 msg_tmp2 = g_strdup_printf(label, who ? who : ""),
3012 msg ? ":" : "",
3013 msg ? msg : "");
3014 sipe_present_err(sipe_private, session, msg_tmp);
3015 g_free(msg_tmp2);
3016 g_free(msg_tmp);
3017 g_free(msg);
3021 static gboolean
3022 process_message_response(struct sipe_core_private *sipe_private,
3023 struct sipmsg *msg,
3024 SIPE_UNUSED_PARAMETER struct transaction *trans)
3026 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
3027 gboolean ret = TRUE;
3028 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
3029 struct sip_session *session = sipe_session_find_im(sipe_private, with);
3030 struct sip_dialog *dialog;
3031 gchar *cseq;
3032 char *key;
3033 struct queued_message *message;
3035 if (!session) {
3036 SIPE_DEBUG_INFO_NOFORMAT("process_message_response: unable to find IM session");
3037 g_free(with);
3038 return FALSE;
3041 dialog = sipe_dialog_find(session, with);
3042 if (!dialog) {
3043 SIPE_DEBUG_INFO_NOFORMAT("process_message_response: session outgoing dialog is NULL");
3044 g_free(with);
3045 return FALSE;
3048 cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
3049 key = g_strdup_printf("<%s><%d><MESSAGE><%s>", sipmsg_find_header(msg, "Call-ID"), atoi(cseq), with);
3050 g_free(cseq);
3051 message = g_hash_table_lookup(session->unconfirmed_messages, key);
3053 if (msg->response >= 400) {
3054 PurpleBuddy *pbuddy;
3055 const char *alias = with;
3056 const char *warn_hdr = sipmsg_find_header(msg, "Warning");
3057 int warning = -1;
3059 SIPE_DEBUG_INFO_NOFORMAT("process_message_response: MESSAGE response >= 400");
3061 if (warn_hdr) {
3062 gchar **parts = g_strsplit(warn_hdr, " ", 2);
3063 if (parts[0]) {
3064 warning = atoi(parts[0]);
3066 g_strfreev(parts);
3069 /* cancel file transfer as rejected by server */
3070 if (msg->response == 606 && /* Not acceptable all. */
3071 warning == 309 && /* Message contents not allowed by policy */
3072 message && g_str_has_prefix(message->content_type, "text/x-msmsgsinvite"))
3074 GSList *parsed_body = sipe_ft_parse_msg_body(msg->body);
3075 sipe_ft_incoming_cancel(dialog, parsed_body);
3076 sipe_utils_nameval_free(parsed_body);
3079 if ((pbuddy = purple_find_buddy(sip->account, with))) {
3080 alias = purple_buddy_get_alias(pbuddy);
3083 sipe_present_message_undelivered_err(sipe_private, session, msg->response, warning, alias, (message ? message->body : NULL));
3085 /* drop dangling IM sessions: assume that BYE from remote never reached us */
3086 if (msg->response == 408 || /* Request timeout */
3087 msg->response == 480 || /* Temporarily Unavailable */
3088 msg->response == 481) { /* Call/Transaction Does Not Exist */
3089 SIPE_DEBUG_INFO_NOFORMAT("process_message_response: assuming dangling IM session, dropping it.");
3090 sip_transport_bye(sipe_private, dialog);
3092 /* We might not get a valid reply to our BYE,
3093 so make sure the dialog is removed for sure. */
3094 sipe_dialog_remove(session, with);
3095 dialog = NULL;
3098 ret = FALSE;
3099 } else {
3100 const gchar *message_id = sipmsg_find_header(msg, "Message-Id");
3101 if (message_id) {
3102 g_hash_table_insert(session->conf_unconfirmed_messages, g_strdup(message_id), g_strdup(message->body));
3103 SIPE_DEBUG_INFO("process_message_response: added message with id %s to conf_unconfirmed_messages(count=%d)",
3104 message_id, g_hash_table_size(session->conf_unconfirmed_messages));
3107 g_hash_table_remove(session->unconfirmed_messages, key);
3108 SIPE_DEBUG_INFO("process_message_response: removed message %s from unconfirmed_messages(count=%d)",
3109 key, g_hash_table_size(session->unconfirmed_messages));
3112 g_free(key);
3113 g_free(with);
3115 if (ret) sipe_im_process_queue(sipe_private, session);
3116 return ret;
3119 static void sipe_send_message(struct sipe_core_private *sipe_private,
3120 struct sip_dialog *dialog,
3121 const char *msg, const char *content_type)
3123 gchar *hdr;
3124 gchar *tmp;
3125 char *msgtext = NULL;
3126 const gchar *msgr = "";
3127 gchar *tmp2 = NULL;
3129 if (!g_str_has_prefix(content_type, "text/x-msmsgsinvite")) {
3130 char *msgformat;
3131 gchar *msgr_value;
3133 sipe_parse_html(msg, &msgformat, &msgtext);
3134 SIPE_DEBUG_INFO("sipe_send_message: msgformat=%s", msgformat);
3136 msgr_value = sipmsg_get_msgr_string(msgformat);
3137 g_free(msgformat);
3138 if (msgr_value) {
3139 msgr = tmp2 = g_strdup_printf(";msgr=%s", msgr_value);
3140 g_free(msgr_value);
3142 } else {
3143 msgtext = g_strdup(msg);
3146 tmp = get_contact(sipe_private);
3147 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8\r\n");
3148 //hdr = g_strdup("Content-Type: text/rtf\r\n");
3149 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8;msgr=WAAtAE0ATQBTAC....AoADQA\r\nSupported: timer\r\n");
3150 if (content_type == NULL)
3151 content_type = "text/plain";
3153 hdr = g_strdup_printf("Contact: %s\r\nContent-Type: %s; charset=UTF-8%s\r\n", tmp, content_type, msgr);
3154 g_free(tmp);
3155 g_free(tmp2);
3157 sip_transport_request(sipe_private,
3158 "MESSAGE",
3159 dialog->with,
3160 dialog->with,
3161 hdr,
3162 msgtext,
3163 dialog,
3164 process_message_response);
3165 g_free(msgtext);
3166 g_free(hdr);
3170 void
3171 sipe_im_process_queue (struct sipe_core_private *sipe_private,
3172 struct sip_session * session)
3174 GSList *entry2 = session->outgoing_message_queue;
3175 while (entry2) {
3176 struct queued_message *msg = entry2->data;
3178 /* for multiparty chat or conference */
3179 if (session->chat_session) {
3180 gchar *who = sip_uri_self(sipe_private);
3181 sipe_backend_chat_message(SIPE_CORE_PUBLIC,
3182 session->chat_session->backend,
3183 who,
3184 msg->body);
3185 g_free(who);
3188 SIPE_DIALOG_FOREACH {
3189 char *key;
3190 struct queued_message *message;
3192 if (dialog->outgoing_invite) continue; /* do not send messages as INVITE is not responded. */
3194 message = g_new0(struct queued_message,1);
3195 message->body = g_strdup(msg->body);
3196 if (msg->content_type != NULL)
3197 message->content_type = g_strdup(msg->content_type);
3199 key = g_strdup_printf("<%s><%d><MESSAGE><%s>", dialog->callid, (dialog->cseq) + 1, dialog->with);
3200 g_hash_table_insert(session->unconfirmed_messages, g_strdup(key), message);
3201 SIPE_DEBUG_INFO("sipe_im_process_queue: added message %s to unconfirmed_messages(count=%d)",
3202 key, g_hash_table_size(session->unconfirmed_messages));
3203 g_free(key);
3205 sipe_send_message(sipe_private, dialog, msg->body, msg->content_type);
3206 } SIPE_DIALOG_FOREACH_END;
3208 entry2 = sipe_session_dequeue_message(session);
3212 static void
3213 sipe_refer_notify(struct sipe_core_private *sipe_private,
3214 struct sip_session *session,
3215 const gchar *who,
3216 int status,
3217 const gchar *desc)
3219 gchar *hdr;
3220 gchar *body;
3221 struct sip_dialog *dialog = sipe_dialog_find(session, who);
3223 hdr = g_strdup_printf(
3224 "Event: refer\r\n"
3225 "Subscription-State: %s\r\n"
3226 "Content-Type: message/sipfrag\r\n",
3227 status >= 200 ? "terminated" : "active");
3229 body = g_strdup_printf(
3230 "SIP/2.0 %d %s\r\n",
3231 status, desc);
3233 sip_transport_request(sipe_private,
3234 "NOTIFY",
3235 who,
3236 who,
3237 hdr,
3238 body,
3239 dialog,
3240 NULL);
3242 g_free(hdr);
3243 g_free(body);
3246 static gboolean
3247 process_invite_response(struct sipe_core_private *sipe_private,
3248 struct sipmsg *msg, struct transaction *trans)
3250 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
3251 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
3252 struct sip_session *session;
3253 struct sip_dialog *dialog;
3254 char *cseq;
3255 char *key;
3256 struct queued_message *message;
3257 struct sipmsg *request_msg = trans->msg;
3259 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
3260 gchar *referred_by;
3262 session = sipe_session_find_chat_or_im(sipe_private, callid, with);
3263 if (!session) {
3264 SIPE_DEBUG_INFO_NOFORMAT("process_invite_response: unable to find IM session");
3265 g_free(with);
3266 return FALSE;
3269 dialog = sipe_dialog_find(session, with);
3270 if (!dialog) {
3271 SIPE_DEBUG_INFO_NOFORMAT("process_invite_response: session outgoing dialog is NULL");
3272 g_free(with);
3273 return FALSE;
3276 sipe_dialog_parse(dialog, msg, TRUE);
3278 cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
3279 key = g_strdup_printf("<%s><%d><INVITE>", dialog->callid, atoi(cseq));
3280 g_free(cseq);
3281 message = g_hash_table_lookup(session->unconfirmed_messages, key);
3283 if (msg->response != 200) {
3284 PurpleBuddy *pbuddy;
3285 const char *alias = with;
3286 const char *warn_hdr = sipmsg_find_header(msg, "Warning");
3287 int warning = -1;
3289 SIPE_DEBUG_INFO_NOFORMAT("process_invite_response: INVITE response not 200");
3291 if (warn_hdr) {
3292 gchar **parts = g_strsplit(warn_hdr, " ", 2);
3293 if (parts[0]) {
3294 warning = atoi(parts[0]);
3296 g_strfreev(parts);
3299 /* cancel file transfer as rejected by server */
3300 if (msg->response == 606 && /* Not acceptable all. */
3301 warning == 309 && /* Message contents not allowed by policy */
3302 message && g_str_has_prefix(message->content_type, "text/x-msmsgsinvite"))
3304 GSList *parsed_body = sipe_ft_parse_msg_body(message->body);
3305 sipe_ft_incoming_cancel(dialog, parsed_body);
3306 sipe_utils_nameval_free(parsed_body);
3309 if ((pbuddy = purple_find_buddy(sip->account, with))) {
3310 alias = purple_buddy_get_alias(pbuddy);
3313 if (message) {
3314 sipe_present_message_undelivered_err(sipe_private, session, msg->response, warning, alias, message->body);
3315 } else {
3316 gchar *tmp_msg = g_strdup_printf(_("Failed to invite %s"), alias);
3317 sipe_present_err(sipe_private, session, tmp_msg);
3318 g_free(tmp_msg);
3321 sipe_dialog_remove(session, with);
3323 if (session->is_groupchat) {
3324 sipe_groupchat_invite_failed(sipe_private, session);
3327 g_free(key);
3328 g_free(with);
3329 return FALSE;
3332 dialog->cseq = 0;
3333 sip_transport_ack(sipe_private, dialog);
3334 dialog->outgoing_invite = NULL;
3335 dialog->is_established = TRUE;
3337 referred_by = parse_from(sipmsg_find_header(request_msg, "Referred-By"));
3338 if (referred_by) {
3339 sipe_refer_notify(sipe_private, session, referred_by, 200, "OK");
3340 g_free(referred_by);
3343 /* add user to chat if it is a multiparty session */
3344 if (session->chat_session &&
3345 (session->chat_session->type == SIPE_CHAT_TYPE_MULTIPARTY)) {
3346 sipe_backend_chat_add(session->chat_session->backend,
3347 with,
3348 TRUE);
3351 if (session->is_groupchat) {
3352 sipe_groupchat_invite_response(sipe_private, dialog);
3355 if(g_slist_find_custom(dialog->supported, "ms-text-format", (GCompareFunc)g_ascii_strcasecmp)) {
3356 SIPE_DEBUG_INFO_NOFORMAT("process_invite_response: remote system accepted message in INVITE");
3357 sipe_session_dequeue_message(session);
3360 sipe_im_process_queue(sipe_private, session);
3362 g_hash_table_remove(session->unconfirmed_messages, key);
3363 SIPE_DEBUG_INFO("process_invite_response: removed message %s from unconfirmed_messages(count=%d)",
3364 key, g_hash_table_size(session->unconfirmed_messages));
3366 g_free(key);
3367 g_free(with);
3368 return TRUE;
3372 void
3373 sipe_invite(struct sipe_core_private *sipe_private,
3374 struct sip_session *session,
3375 const gchar *who,
3376 const gchar *msg_body,
3377 const gchar *msg_content_type,
3378 const gchar *referred_by,
3379 const gboolean is_triggered)
3381 gchar *hdr;
3382 gchar *to;
3383 gchar *contact;
3384 gchar *body;
3385 gchar *self;
3386 char *ms_text_format = NULL;
3387 gchar *roster_manager;
3388 gchar *end_points;
3389 gchar *referred_by_str;
3390 gboolean is_multiparty =
3391 session->chat_session &&
3392 (session->chat_session->type == SIPE_CHAT_TYPE_MULTIPARTY);
3393 struct sip_dialog *dialog = sipe_dialog_find(session, who);
3395 if (dialog && dialog->is_established) {
3396 SIPE_DEBUG_INFO("session with %s already has a dialog open", who);
3397 return;
3400 if (!dialog) {
3401 dialog = sipe_dialog_add(session);
3402 dialog->callid = session->callid ? g_strdup(session->callid) : gencallid();
3403 dialog->with = g_strdup(who);
3406 if (!(dialog->ourtag)) {
3407 dialog->ourtag = gentag();
3410 to = sip_uri(who);
3412 if (msg_body) {
3413 char *msgtext = NULL;
3414 char *base64_msg;
3415 const gchar *msgr = "";
3416 char *key;
3417 struct queued_message *message;
3418 gchar *tmp = NULL;
3420 if (!g_str_has_prefix(msg_content_type, "text/x-msmsgsinvite")) {
3421 char *msgformat;
3422 gchar *msgr_value;
3424 sipe_parse_html(msg_body, &msgformat, &msgtext);
3425 SIPE_DEBUG_INFO("sipe_invite: msgformat=%s", msgformat);
3427 msgr_value = sipmsg_get_msgr_string(msgformat);
3428 g_free(msgformat);
3429 if (msgr_value) {
3430 msgr = tmp = g_strdup_printf(";msgr=%s", msgr_value);
3431 g_free(msgr_value);
3433 } else {
3434 msgtext = g_strdup(msg_body);
3437 base64_msg = g_base64_encode((guchar*) msgtext, strlen(msgtext));
3438 ms_text_format = g_strdup_printf(SIPE_INVITE_TEXT,
3439 msg_content_type ? msg_content_type : "text/plain",
3440 msgr,
3441 base64_msg);
3442 g_free(msgtext);
3443 g_free(tmp);
3444 g_free(base64_msg);
3446 message = g_new0(struct queued_message,1);
3447 message->body = g_strdup(msg_body);
3448 if (msg_content_type != NULL)
3449 message->content_type = g_strdup(msg_content_type);
3451 key = g_strdup_printf("<%s><%d><INVITE>", dialog->callid, (dialog->cseq) + 1);
3452 g_hash_table_insert(session->unconfirmed_messages, g_strdup(key), message);
3453 SIPE_DEBUG_INFO("sipe_invite: added message %s to unconfirmed_messages(count=%d)",
3454 key, g_hash_table_size(session->unconfirmed_messages));
3455 g_free(key);
3458 contact = get_contact(sipe_private);
3459 end_points = get_end_points(sipe_private, session);
3460 self = sip_uri_self(sipe_private);
3461 roster_manager = g_strdup_printf(
3462 "Roster-Manager: %s\r\n"
3463 "EndPoints: %s\r\n",
3464 self,
3465 end_points);
3466 referred_by_str = referred_by ?
3467 g_strdup_printf(
3468 "Referred-By: %s\r\n",
3469 referred_by)
3470 : g_strdup("");
3471 hdr = g_strdup_printf(
3472 "Supported: ms-sender\r\n"
3473 "%s"
3474 "%s"
3475 "%s"
3476 "%s"
3477 "Contact: %s\r\n%s"
3478 "Content-Type: application/sdp\r\n",
3479 is_multiparty && sipe_strcase_equal(session->chat_session->id, self) ? roster_manager : "",
3480 referred_by_str,
3481 is_triggered ? "TriggeredInvite: TRUE\r\n" : "",
3482 is_triggered || is_multiparty ? "Require: com.microsoft.rtc-multiparty\r\n" : "",
3483 contact,
3484 ms_text_format ? ms_text_format : "");
3485 g_free(ms_text_format);
3486 g_free(self);
3488 body = g_strdup_printf(
3489 "v=0\r\n"
3490 "o=- 0 0 IN IP4 %s\r\n"
3491 "s=session\r\n"
3492 "c=IN IP4 %s\r\n"
3493 "t=0 0\r\n"
3494 "m=%s %d sip null\r\n"
3495 "a=accept-types:" SDP_ACCEPT_TYPES "\r\n",
3496 sipe_backend_network_ip_address(),
3497 sipe_backend_network_ip_address(),
3498 SIPE_CORE_PRIVATE_FLAG_IS(OCS2007) ? "message" : "x-ms-message",
3499 sip_transport_port(sipe_private));
3501 dialog->outgoing_invite = sip_transport_request(sipe_private,
3502 "INVITE",
3505 hdr,
3506 body,
3507 dialog,
3508 process_invite_response);
3510 g_free(to);
3511 g_free(roster_manager);
3512 g_free(end_points);
3513 g_free(referred_by_str);
3514 g_free(body);
3515 g_free(hdr);
3516 g_free(contact);
3519 void
3520 sipe_convo_closed(PurpleConnection * gc, const char *who)
3522 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
3524 SIPE_DEBUG_INFO("conversation with %s closed", who);
3525 sipe_session_close(sipe_private,
3526 sipe_session_find_im(sipe_private, who));
3529 int sipe_im_send(PurpleConnection *gc, const char *who, const char *what,
3530 SIPE_UNUSED_PARAMETER PurpleMessageFlags flags)
3532 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
3533 struct sip_session *session;
3534 struct sip_dialog *dialog;
3535 gchar *uri = sip_uri(who);
3537 SIPE_DEBUG_INFO("sipe_im_send what='%s'", what);
3539 session = sipe_session_find_or_add_im(sipe_private, uri);
3540 dialog = sipe_dialog_find(session, uri);
3542 // Queue the message
3543 sipe_session_enqueue_message(session, what, NULL);
3545 if (dialog && !dialog->outgoing_invite) {
3546 sipe_im_process_queue(sipe_private, session);
3547 } else if (!dialog || !dialog->outgoing_invite) {
3548 // Need to send the INVITE to get the outgoing dialog setup
3549 sipe_invite(sipe_private, session, uri, what, NULL, NULL, FALSE);
3552 g_free(uri);
3553 return 1;
3557 * Returns 2005-style activity and Availability.
3559 * @param status Sipe statis id.
3561 static void
3562 sipe_get_act_avail_by_status_2005(const char *status,
3563 int *activity,
3564 int *availability)
3566 int avail = 300; /* online */
3567 int act = 400; /* Available */
3569 if (sipe_strequal(status, SIPE_STATUS_ID_AWAY)) {
3570 act = 100;
3571 //} else if (sipe_strequal(status, SIPE_STATUS_ID_LUNCH)) {
3572 // act = 150;
3573 } else if (sipe_strequal(status, SIPE_STATUS_ID_BRB)) {
3574 act = 300;
3575 } else if (sipe_strequal(status, SIPE_STATUS_ID_AVAILABLE)) {
3576 act = 400;
3577 //} else if (sipe_strequal(status, SIPE_STATUS_ID_ON_PHONE)) {
3578 // act = 500;
3579 } else if (sipe_strequal(status, SIPE_STATUS_ID_BUSY) ||
3580 sipe_strequal(status, SIPE_STATUS_ID_DND)) {
3581 act = 600;
3582 } else if (sipe_strequal(status, SIPE_STATUS_ID_INVISIBLE) ||
3583 sipe_strequal(status, SIPE_STATUS_ID_OFFLINE)) {
3584 avail = 0; /* offline */
3585 act = 100;
3586 } else {
3587 act = 400; /* Available */
3590 if (activity) *activity = act;
3591 if (availability) *availability = avail;
3595 * [MS-SIP] 2.2.1
3597 * @param activity 2005 aggregated activity. Ex.: 600
3598 * @param availablity 2005 aggregated availablity. Ex.: 300
3600 static const char *
3601 sipe_get_status_by_act_avail_2005(const int activity,
3602 const int availablity,
3603 char **activity_desc)
3605 const char *status_id = NULL;
3606 const char *act = NULL;
3608 if (activity < 150) {
3609 status_id = SIPE_STATUS_ID_AWAY;
3610 } else if (activity < 200) {
3611 //status_id = SIPE_STATUS_ID_LUNCH;
3612 status_id = SIPE_STATUS_ID_AWAY;
3613 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_LUNCH);
3614 } else if (activity < 300) {
3615 //status_id = SIPE_STATUS_ID_IDLE;
3616 status_id = SIPE_STATUS_ID_AWAY;
3617 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_INACTIVE);
3618 } else if (activity < 400) {
3619 status_id = SIPE_STATUS_ID_BRB;
3620 } else if (activity < 500) {
3621 status_id = SIPE_STATUS_ID_AVAILABLE;
3622 } else if (activity < 600) {
3623 //status_id = SIPE_STATUS_ID_ON_PHONE;
3624 status_id = SIPE_STATUS_ID_BUSY;
3625 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_ON_PHONE);
3626 } else if (activity < 700) {
3627 status_id = SIPE_STATUS_ID_BUSY;
3628 } else if (activity < 800) {
3629 status_id = SIPE_STATUS_ID_AWAY;
3630 } else {
3631 status_id = SIPE_STATUS_ID_AVAILABLE;
3634 if (availablity < 100)
3635 status_id = SIPE_STATUS_ID_OFFLINE;
3637 if (activity_desc && act) {
3638 g_free(*activity_desc);
3639 *activity_desc = g_strdup(act);
3642 return status_id;
3646 * [MS-PRES] Table 3: Conversion of legacyInterop elements and attributes to MSRTC elements and attributes.
3648 static const char*
3649 sipe_get_status_by_availability(int avail,
3650 char** activity_desc)
3652 const char *status;
3653 const char *act = NULL;
3655 if (avail < 3000) {
3656 status = SIPE_STATUS_ID_OFFLINE;
3657 } else if (avail < 4500) {
3658 status = SIPE_STATUS_ID_AVAILABLE;
3659 } else if (avail < 6000) {
3660 //status = SIPE_STATUS_ID_IDLE;
3661 status = SIPE_STATUS_ID_AVAILABLE;
3662 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_INACTIVE);
3663 } else if (avail < 7500) {
3664 status = SIPE_STATUS_ID_BUSY;
3665 } else if (avail < 9000) {
3666 //status = SIPE_STATUS_ID_BUSYIDLE;
3667 status = SIPE_STATUS_ID_BUSY;
3668 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_BUSYIDLE);
3669 } else if (avail < 12000) {
3670 status = SIPE_STATUS_ID_DND;
3671 } else if (avail < 15000) {
3672 status = SIPE_STATUS_ID_BRB;
3673 } else if (avail < 18000) {
3674 status = SIPE_STATUS_ID_AWAY;
3675 } else {
3676 status = SIPE_STATUS_ID_OFFLINE;
3679 if (activity_desc && act) {
3680 g_free(*activity_desc);
3681 *activity_desc = g_strdup(act);
3684 return status;
3688 * Returns 2007-style availability value
3690 * @param sipe_status_id (in)
3691 * @param activity_token (out) Must be g_free()'d after use if consumed.
3693 static int
3694 sipe_get_availability_by_status(const char* sipe_status_id, char** activity_token)
3696 int availability;
3697 sipe_activity activity = SIPE_ACTIVITY_UNSET;
3699 if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_AWAY)) {
3700 availability = 15500;
3701 if (!activity_token || !(*activity_token)) {
3702 activity = SIPE_ACTIVITY_AWAY;
3704 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_BRB)) {
3705 availability = 12500;
3706 activity = SIPE_ACTIVITY_BRB;
3707 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_DND)) {
3708 availability = 9500;
3709 activity = SIPE_ACTIVITY_DND;
3710 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_BUSY)) {
3711 availability = 6500;
3712 if (!activity_token || !(*activity_token)) {
3713 activity = SIPE_ACTIVITY_BUSY;
3715 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_AVAILABLE)) {
3716 availability = 3500;
3717 activity = SIPE_ACTIVITY_ONLINE;
3718 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_UNKNOWN)) {
3719 availability = 0;
3720 } else {
3721 // Offline or invisible
3722 availability = 18500;
3723 activity = SIPE_ACTIVITY_OFFLINE;
3726 if (activity_token) {
3727 *activity_token = g_strdup(sipe_activity_map[activity].token);
3729 return availability;
3732 static void process_incoming_notify_rlmi(struct sipe_core_private *sipe_private,
3733 const gchar *data, unsigned len)
3735 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
3736 const char *uri;
3737 sipe_xml *xn_categories;
3738 const sipe_xml *xn_category;
3739 const char *status = NULL;
3740 gboolean do_update_status = FALSE;
3741 gboolean has_note_cleaned = FALSE;
3742 gboolean has_free_busy_cleaned = FALSE;
3744 xn_categories = sipe_xml_parse(data, len);
3745 uri = sipe_xml_attribute(xn_categories, "uri"); /* with 'sip:' prefix */
3747 for (xn_category = sipe_xml_child(xn_categories, "category");
3748 xn_category ;
3749 xn_category = sipe_xml_twin(xn_category) )
3751 const sipe_xml *xn_node;
3752 const char *tmp;
3753 const char *attrVar = sipe_xml_attribute(xn_category, "name");
3754 time_t publish_time = (tmp = sipe_xml_attribute(xn_category, "publishTime")) ?
3755 sipe_utils_str_to_time(tmp) : 0;
3757 /* contactCard */
3758 if (sipe_strequal(attrVar, "contactCard"))
3760 const sipe_xml *card = sipe_xml_child(xn_category, "contactCard");
3762 if (card) {
3763 const sipe_xml *node;
3764 /* identity - Display Name and email */
3765 node = sipe_xml_child(card, "identity");
3766 if (node) {
3767 char* display_name = sipe_xml_data(
3768 sipe_xml_child(node, "name/displayName"));
3769 char* email = sipe_xml_data(
3770 sipe_xml_child(node, "email"));
3772 sipe_update_user_info(sipe_private, uri, ALIAS_PROP, display_name);
3773 sipe_update_user_info(sipe_private, uri, EMAIL_PROP, email);
3775 g_free(display_name);
3776 g_free(email);
3778 /* company */
3779 node = sipe_xml_child(card, "company");
3780 if (node) {
3781 char* company = sipe_xml_data(node);
3782 sipe_update_user_info(sipe_private, uri, COMPANY_PROP, company);
3783 g_free(company);
3785 /* department */
3786 node = sipe_xml_child(card, "department");
3787 if (node) {
3788 char* department = sipe_xml_data(node);
3789 sipe_update_user_info(sipe_private, uri, DEPARTMENT_PROP, department);
3790 g_free(department);
3792 /* title */
3793 node = sipe_xml_child(card, "title");
3794 if (node) {
3795 char* title = sipe_xml_data(node);
3796 sipe_update_user_info(sipe_private, uri, TITLE_PROP, title);
3797 g_free(title);
3799 /* office */
3800 node = sipe_xml_child(card, "office");
3801 if (node) {
3802 char* office = sipe_xml_data(node);
3803 sipe_update_user_info(sipe_private, uri, OFFICE_PROP, office);
3804 g_free(office);
3806 /* site (url) */
3807 node = sipe_xml_child(card, "url");
3808 if (node) {
3809 char* site = sipe_xml_data(node);
3810 sipe_update_user_info(sipe_private, uri, SITE_PROP, site);
3811 g_free(site);
3813 /* phone */
3814 for (node = sipe_xml_child(card, "phone");
3815 node;
3816 node = sipe_xml_twin(node))
3818 const char *phone_type = sipe_xml_attribute(node, "type");
3819 char* phone = sipe_xml_data(sipe_xml_child(node, "uri"));
3820 char* phone_display_string = sipe_xml_data(sipe_xml_child(node, "displayString"));
3822 sipe_update_user_phone(sipe_private, uri, phone_type, phone, phone_display_string);
3824 g_free(phone);
3825 g_free(phone_display_string);
3827 /* address */
3828 for (node = sipe_xml_child(card, "address");
3829 node;
3830 node = sipe_xml_twin(node))
3832 if (sipe_strequal(sipe_xml_attribute(node, "type"), "work")) {
3833 char* street = sipe_xml_data(sipe_xml_child(node, "street"));
3834 char* city = sipe_xml_data(sipe_xml_child(node, "city"));
3835 char* state = sipe_xml_data(sipe_xml_child(node, "state"));
3836 char* zipcode = sipe_xml_data(sipe_xml_child(node, "zipcode"));
3837 char* country_code = sipe_xml_data(sipe_xml_child(node, "countryCode"));
3839 sipe_update_user_info(sipe_private, uri, ADDRESS_STREET_PROP, street);
3840 sipe_update_user_info(sipe_private, uri, ADDRESS_CITY_PROP, city);
3841 sipe_update_user_info(sipe_private, uri, ADDRESS_STATE_PROP, state);
3842 sipe_update_user_info(sipe_private, uri, ADDRESS_ZIPCODE_PROP, zipcode);
3843 sipe_update_user_info(sipe_private, uri, ADDRESS_COUNTRYCODE_PROP, country_code);
3845 g_free(street);
3846 g_free(city);
3847 g_free(state);
3848 g_free(zipcode);
3849 g_free(country_code);
3851 break;
3856 /* note */
3857 else if (sipe_strequal(attrVar, "note"))
3859 if (uri) {
3860 struct sipe_buddy *sbuddy = g_hash_table_lookup(sipe_private->buddies, uri);
3862 if (!has_note_cleaned) {
3863 has_note_cleaned = TRUE;
3865 g_free(sbuddy->note);
3866 sbuddy->note = NULL;
3867 sbuddy->is_oof_note = FALSE;
3868 sbuddy->note_since = publish_time;
3870 do_update_status = TRUE;
3872 if (sbuddy && (publish_time >= sbuddy->note_since)) {
3873 /* clean up in case no 'note' element is supplied
3874 * which indicate note removal in client
3876 g_free(sbuddy->note);
3877 sbuddy->note = NULL;
3878 sbuddy->is_oof_note = FALSE;
3879 sbuddy->note_since = publish_time;
3881 xn_node = sipe_xml_child(xn_category, "note/body");
3882 if (xn_node) {
3883 char *tmp;
3884 sbuddy->note = g_markup_escape_text((tmp = sipe_xml_data(xn_node)), -1);
3885 g_free(tmp);
3886 sbuddy->is_oof_note = sipe_strequal(sipe_xml_attribute(xn_node, "type"), "OOF");
3887 sbuddy->note_since = publish_time;
3889 SIPE_DEBUG_INFO("process_incoming_notify_rlmi: uri(%s), note(%s)",
3890 uri, sbuddy->note ? sbuddy->note : "");
3892 /* to trigger UI refresh in case no status info is supplied in this update */
3893 do_update_status = TRUE;
3897 /* state */
3898 else if(sipe_strequal(attrVar, "state"))
3900 char *tmp;
3901 int availability;
3902 const sipe_xml *xn_availability;
3903 const sipe_xml *xn_activity;
3904 const sipe_xml *xn_meeting_subject;
3905 const sipe_xml *xn_meeting_location;
3906 struct sipe_buddy *sbuddy = uri ? g_hash_table_lookup(sipe_private->buddies, uri) : NULL;
3908 xn_node = sipe_xml_child(xn_category, "state");
3909 if (!xn_node) continue;
3910 xn_availability = sipe_xml_child(xn_node, "availability");
3911 if (!xn_availability) continue;
3912 xn_activity = sipe_xml_child(xn_node, "activity");
3913 xn_meeting_subject = sipe_xml_child(xn_node, "meetingSubject");
3914 xn_meeting_location = sipe_xml_child(xn_node, "meetingLocation");
3916 tmp = sipe_xml_data(xn_availability);
3917 availability = atoi(tmp);
3918 g_free(tmp);
3920 /* activity, meeting_subject, meeting_location */
3921 if (sbuddy) {
3922 char *tmp = NULL;
3924 /* activity */
3925 g_free(sbuddy->activity);
3926 sbuddy->activity = NULL;
3927 if (xn_activity) {
3928 const char *token = sipe_xml_attribute(xn_activity, "token");
3929 const sipe_xml *xn_custom = sipe_xml_child(xn_activity, "custom");
3931 /* from token */
3932 if (!is_empty(token)) {
3933 sbuddy->activity = g_strdup(sipe_get_activity_desc_by_token(token));
3935 /* from custom element */
3936 if (xn_custom) {
3937 char *custom = sipe_xml_data(xn_custom);
3939 if (!is_empty(custom)) {
3940 sbuddy->activity = custom;
3941 custom = NULL;
3943 g_free(custom);
3946 /* meeting_subject */
3947 g_free(sbuddy->meeting_subject);
3948 sbuddy->meeting_subject = NULL;
3949 if (xn_meeting_subject) {
3950 char *meeting_subject = sipe_xml_data(xn_meeting_subject);
3952 if (!is_empty(meeting_subject)) {
3953 sbuddy->meeting_subject = meeting_subject;
3954 meeting_subject = NULL;
3956 g_free(meeting_subject);
3958 /* meeting_location */
3959 g_free(sbuddy->meeting_location);
3960 sbuddy->meeting_location = NULL;
3961 if (xn_meeting_location) {
3962 char *meeting_location = sipe_xml_data(xn_meeting_location);
3964 if (!is_empty(meeting_location)) {
3965 sbuddy->meeting_location = meeting_location;
3966 meeting_location = NULL;
3968 g_free(meeting_location);
3971 status = sipe_get_status_by_availability(availability, &tmp);
3972 if (sbuddy->activity && tmp) {
3973 char *tmp2 = sbuddy->activity;
3975 sbuddy->activity = g_strdup_printf("%s, %s", sbuddy->activity, tmp);
3976 g_free(tmp);
3977 g_free(tmp2);
3978 } else if (tmp) {
3979 sbuddy->activity = tmp;
3983 do_update_status = TRUE;
3985 /* calendarData */
3986 else if(sipe_strequal(attrVar, "calendarData"))
3988 struct sipe_buddy *sbuddy = uri ? g_hash_table_lookup(sipe_private->buddies, uri) : NULL;
3989 const sipe_xml *xn_free_busy = sipe_xml_child(xn_category, "calendarData/freeBusy");
3990 const sipe_xml *xn_working_hours = sipe_xml_child(xn_category, "calendarData/WorkingHours");
3992 if (sbuddy && xn_free_busy) {
3993 if (!has_free_busy_cleaned) {
3994 has_free_busy_cleaned = TRUE;
3996 g_free(sbuddy->cal_start_time);
3997 sbuddy->cal_start_time = NULL;
3999 g_free(sbuddy->cal_free_busy_base64);
4000 sbuddy->cal_free_busy_base64 = NULL;
4002 g_free(sbuddy->cal_free_busy);
4003 sbuddy->cal_free_busy = NULL;
4005 sbuddy->cal_free_busy_published = publish_time;
4008 if (publish_time >= sbuddy->cal_free_busy_published) {
4009 g_free(sbuddy->cal_start_time);
4010 sbuddy->cal_start_time = g_strdup(sipe_xml_attribute(xn_free_busy, "startTime"));
4012 sbuddy->cal_granularity = sipe_strcase_equal(sipe_xml_attribute(xn_free_busy, "granularity"), "PT15M") ?
4013 15 : 0;
4015 g_free(sbuddy->cal_free_busy_base64);
4016 sbuddy->cal_free_busy_base64 = sipe_xml_data(xn_free_busy);
4018 g_free(sbuddy->cal_free_busy);
4019 sbuddy->cal_free_busy = NULL;
4021 sbuddy->cal_free_busy_published = publish_time;
4023 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);
4027 if (sbuddy && xn_working_hours) {
4028 sipe_cal_parse_working_hours(xn_working_hours, sbuddy);
4033 if (do_update_status) {
4034 if (!status) { /* no status category in this update, using contact's current status */
4035 PurpleBuddy *pbuddy = purple_find_buddy((PurpleAccount *)sip->account, uri);
4036 const PurplePresence *presence = purple_buddy_get_presence(pbuddy);
4037 const PurpleStatus *pstatus = purple_presence_get_active_status(presence);
4038 status = purple_status_get_id(pstatus);
4041 SIPE_DEBUG_INFO("process_incoming_notify_rlmi: %s", status);
4042 sipe_got_user_status(sipe_private, uri, status);
4045 sipe_xml_free(xn_categories);
4048 static void sipe_subscribe_poolfqdn_resource_uri(const char *host,
4049 GSList *server,
4050 struct sipe_core_private *sipe_private)
4052 struct presence_batched_routed *payload = g_malloc(sizeof(struct presence_batched_routed));
4053 SIPE_DEBUG_INFO("process_incoming_notify_rlmi_resub: pool(%s)", host);
4054 payload->host = g_strdup(host);
4055 payload->buddies = server;
4056 sipe_subscribe_presence_batched_routed(sipe_private,
4057 payload);
4058 sipe_subscribe_presence_batched_routed_free(payload);
4061 static void process_incoming_notify_rlmi_resub(struct sipe_core_private *sipe_private,
4062 const gchar *data, unsigned len)
4064 sipe_xml *xn_list;
4065 const sipe_xml *xn_resource;
4066 GHashTable *servers = g_hash_table_new_full(g_str_hash, g_str_equal,
4067 g_free, NULL);
4068 GSList *server;
4069 gchar *host;
4071 xn_list = sipe_xml_parse(data, len);
4073 for (xn_resource = sipe_xml_child(xn_list, "resource");
4074 xn_resource;
4075 xn_resource = sipe_xml_twin(xn_resource) )
4077 const char *uri, *state;
4078 const sipe_xml *xn_instance;
4080 xn_instance = sipe_xml_child(xn_resource, "instance");
4081 if (!xn_instance) continue;
4083 uri = sipe_xml_attribute(xn_resource, "uri");
4084 state = sipe_xml_attribute(xn_instance, "state");
4085 SIPE_DEBUG_INFO("process_incoming_notify_rlmi_resub: uri(%s),state(%s)", uri, state);
4087 if (strstr(state, "resubscribe")) {
4088 const char *poolFqdn = sipe_xml_attribute(xn_instance, "poolFqdn");
4090 if (poolFqdn) { //[MS-PRES] Section 3.4.5.1.3 Processing Details
4091 gchar *user = g_strdup(uri);
4092 host = g_strdup(poolFqdn);
4093 server = g_hash_table_lookup(servers, host);
4094 server = g_slist_append(server, user);
4095 g_hash_table_insert(servers, host, server);
4096 } else {
4097 sipe_subscribe_presence_single(sipe_private,
4098 (void *) uri);
4103 /* Send out any deferred poolFqdn subscriptions */
4104 g_hash_table_foreach(servers, (GHFunc) sipe_subscribe_poolfqdn_resource_uri, sipe_private);
4105 g_hash_table_destroy(servers);
4107 sipe_xml_free(xn_list);
4110 static void process_incoming_notify_pidf(struct sipe_core_private *sipe_private,
4111 const gchar *data, unsigned len)
4113 gchar *uri;
4114 gchar *getbasic;
4115 gchar *activity = NULL;
4116 sipe_xml *pidf;
4117 const sipe_xml *basicstatus = NULL, *tuple, *status;
4118 gboolean isonline = FALSE;
4119 const sipe_xml *display_name_node;
4121 pidf = sipe_xml_parse(data, len);
4122 if (!pidf) {
4123 SIPE_DEBUG_INFO("process_incoming_notify_pidf: no parseable pidf:%s", data);
4124 return;
4127 if ((tuple = sipe_xml_child(pidf, "tuple")))
4129 if ((status = sipe_xml_child(tuple, "status"))) {
4130 basicstatus = sipe_xml_child(status, "basic");
4134 if (!basicstatus) {
4135 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_notify_pidf: no basic found");
4136 sipe_xml_free(pidf);
4137 return;
4140 getbasic = sipe_xml_data(basicstatus);
4141 if (!getbasic) {
4142 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_notify_pidf: no basic data found");
4143 sipe_xml_free(pidf);
4144 return;
4147 SIPE_DEBUG_INFO("process_incoming_notify_pidf: basic-status(%s)", getbasic);
4148 if (strstr(getbasic, "open")) {
4149 isonline = TRUE;
4151 g_free(getbasic);
4153 uri = sip_uri(sipe_xml_attribute(pidf, "entity")); /* with 'sip:' prefix */ /* AOL comes without the prefix */
4155 display_name_node = sipe_xml_child(pidf, "display-name");
4156 if (display_name_node) {
4157 char * display_name = sipe_xml_data(display_name_node);
4159 sipe_update_user_info(sipe_private, uri, ALIAS_PROP, display_name);
4160 g_free(display_name);
4163 if ((tuple = sipe_xml_child(pidf, "tuple"))) {
4164 if ((status = sipe_xml_child(tuple, "status"))) {
4165 if ((basicstatus = sipe_xml_child(status, "activities"))) {
4166 if ((basicstatus = sipe_xml_child(basicstatus, "activity"))) {
4167 activity = sipe_xml_data(basicstatus);
4168 SIPE_DEBUG_INFO("process_incoming_notify_pidf: activity(%s)", activity);
4174 if (isonline) {
4175 const gchar * status_id = NULL;
4176 if (activity) {
4177 if (sipe_strequal(activity, sipe_activity_map[SIPE_ACTIVITY_BUSY].token)) {
4178 status_id = SIPE_STATUS_ID_BUSY;
4179 } else if (sipe_strequal(activity, sipe_activity_map[SIPE_ACTIVITY_AWAY].token)) {
4180 status_id = SIPE_STATUS_ID_AWAY;
4184 if (!status_id) {
4185 status_id = SIPE_STATUS_ID_AVAILABLE;
4188 SIPE_DEBUG_INFO("process_incoming_notify_pidf: status_id(%s)", status_id);
4189 sipe_got_user_status(sipe_private, uri, status_id);
4190 } else {
4191 sipe_got_user_status(sipe_private, uri, SIPE_STATUS_ID_OFFLINE);
4194 g_free(activity);
4195 g_free(uri);
4196 sipe_xml_free(pidf);
4199 /** 2005 */
4200 static void
4201 sipe_user_info_has_updated(struct sipe_core_private *sipe_private,
4202 const sipe_xml *xn_userinfo)
4204 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
4205 const sipe_xml *xn_states;
4207 g_free(sip->user_states);
4208 sip->user_states = NULL;
4209 if ((xn_states = sipe_xml_child(xn_userinfo, "states")) != NULL) {
4210 gchar *orig = sip->user_states = sipe_xml_stringify(xn_states);
4212 /* this is a hack-around to remove added newline after inner element,
4213 * state in this case, where it shouldn't be.
4214 * After several use of sipe_xml_stringify, amount of added newlines
4215 * grows significantly.
4217 if (orig) {
4218 gchar c, *stripped = orig;
4219 while ((c = *orig++)) {
4220 if ((c != '\n') /* && (c != '\r') */) {
4221 *stripped++ = c;
4224 *stripped = '\0';
4228 /* Publish initial state if not yet.
4229 * Assuming this happens on initial responce to self subscription
4230 * so we've already updated our UserInfo.
4232 if (!sip->initial_state_published) {
4233 send_presence_soap(sipe_private, FALSE);
4234 /* dalayed run */
4235 sipe_schedule_seconds(sipe_private,
4236 "<+update-calendar>",
4237 NULL,
4238 UPDATE_CALENDAR_DELAY,
4239 (sipe_schedule_action) sipe_core_update_calendar,
4240 NULL);
4244 static void process_incoming_notify_msrtc(struct sipe_core_private *sipe_private,
4245 const gchar *data, unsigned len)
4247 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
4248 char *activity = NULL;
4249 const char *epid;
4250 const char *status_id = NULL;
4251 const char *name;
4252 char *uri;
4253 char *self_uri = sip_uri_self(sipe_private);
4254 int avl;
4255 int act;
4256 const char *device_name = NULL;
4257 const char *cal_start_time = NULL;
4258 const char *cal_granularity = NULL;
4259 char *cal_free_busy_base64 = NULL;
4260 struct sipe_buddy *sbuddy;
4261 const sipe_xml *node;
4262 sipe_xml *xn_presentity;
4263 const sipe_xml *xn_availability;
4264 const sipe_xml *xn_activity;
4265 const sipe_xml *xn_display_name;
4266 const sipe_xml *xn_email;
4267 const sipe_xml *xn_phone_number;
4268 const sipe_xml *xn_userinfo;
4269 const sipe_xml *xn_note;
4270 const sipe_xml *xn_oof;
4271 const sipe_xml *xn_state;
4272 const sipe_xml *xn_contact;
4273 char *note;
4274 char *free_activity;
4275 int user_avail;
4276 const char *user_avail_nil;
4277 int res_avail;
4278 time_t user_avail_since = 0;
4279 time_t activity_since = 0;
4281 /* fix for Reuters environment on Linux */
4282 if (data && strstr(data, "encoding=\"utf-16\"")) {
4283 char *tmp_data;
4284 tmp_data = replace(data, "encoding=\"utf-16\"", "encoding=\"utf-8\"");
4285 xn_presentity = sipe_xml_parse(tmp_data, strlen(tmp_data));
4286 g_free(tmp_data);
4287 } else {
4288 xn_presentity = sipe_xml_parse(data, len);
4291 xn_availability = sipe_xml_child(xn_presentity, "availability");
4292 xn_activity = sipe_xml_child(xn_presentity, "activity");
4293 xn_display_name = sipe_xml_child(xn_presentity, "displayName");
4294 xn_email = sipe_xml_child(xn_presentity, "email");
4295 xn_phone_number = sipe_xml_child(xn_presentity, "phoneNumber");
4296 xn_userinfo = sipe_xml_child(xn_presentity, "userInfo");
4297 xn_oof = xn_userinfo ? sipe_xml_child(xn_userinfo, "oof") : NULL;
4298 xn_state = xn_userinfo ? sipe_xml_child(xn_userinfo, "states/state"): NULL;
4299 user_avail = xn_state ? sipe_xml_int_attribute(xn_state, "avail", 0) : 0;
4300 user_avail_since = xn_state ? sipe_utils_str_to_time(sipe_xml_attribute(xn_state, "since")) : 0;
4301 user_avail_nil = xn_state ? sipe_xml_attribute(xn_state, "nil") : NULL;
4302 xn_contact = xn_userinfo ? sipe_xml_child(xn_userinfo, "contact") : NULL;
4303 xn_note = xn_userinfo ? sipe_xml_child(xn_userinfo, "note") : NULL;
4304 note = xn_note ? sipe_xml_data(xn_note) : NULL;
4306 if (sipe_strequal(user_avail_nil, "true")) { /* null-ed */
4307 user_avail = 0;
4308 user_avail_since = 0;
4311 free_activity = NULL;
4313 name = sipe_xml_attribute(xn_presentity, "uri"); /* without 'sip:' prefix */
4314 uri = sip_uri_from_name(name);
4315 avl = sipe_xml_int_attribute(xn_availability, "aggregate", 0);
4316 epid = sipe_xml_attribute(xn_availability, "epid");
4317 act = sipe_xml_int_attribute(xn_activity, "aggregate", 0);
4319 status_id = sipe_get_status_by_act_avail_2005(act, avl, &activity);
4320 res_avail = sipe_get_availability_by_status(status_id, NULL);
4321 if (user_avail > res_avail) {
4322 res_avail = user_avail;
4323 status_id = sipe_get_status_by_availability(user_avail, NULL);
4326 if (xn_display_name) {
4327 char *display_name = g_strdup(sipe_xml_attribute(xn_display_name, "displayName"));
4328 char *email = xn_email ? g_strdup(sipe_xml_attribute(xn_email, "email")) : NULL;
4329 char *phone_label = xn_phone_number ? g_strdup(sipe_xml_attribute(xn_phone_number, "label")) : NULL;
4330 char *phone_number = xn_phone_number ? g_strdup(sipe_xml_attribute(xn_phone_number, "number")) : NULL;
4331 char *tel_uri = sip_to_tel_uri(phone_number);
4333 sipe_update_user_info(sipe_private, uri, ALIAS_PROP, display_name);
4334 sipe_update_user_info(sipe_private, uri, EMAIL_PROP, email);
4335 sipe_update_user_info(sipe_private, uri, PHONE_PROP, tel_uri);
4336 sipe_update_user_info(sipe_private, uri, PHONE_DISPLAY_PROP, !is_empty(phone_label) ? phone_label : phone_number);
4338 g_free(tel_uri);
4339 g_free(phone_label);
4340 g_free(phone_number);
4341 g_free(email);
4342 g_free(display_name);
4345 if (xn_contact) {
4346 /* tel */
4347 for (node = sipe_xml_child(xn_contact, "tel"); node; node = sipe_xml_twin(node))
4349 /* Ex.: <tel type="work">tel:+3222220000</tel> */
4350 const char *phone_type = sipe_xml_attribute(node, "type");
4351 char* phone = sipe_xml_data(node);
4353 sipe_update_user_phone(sipe_private, uri, phone_type, phone, NULL);
4355 g_free(phone);
4359 /* devicePresence */
4360 for (node = sipe_xml_child(xn_presentity, "devices/devicePresence"); node; node = sipe_xml_twin(node)) {
4361 const sipe_xml *xn_device_name;
4362 const sipe_xml *xn_calendar_info;
4363 const sipe_xml *xn_state;
4364 char *state;
4366 /* deviceName */
4367 if (sipe_strequal(sipe_xml_attribute(node, "epid"), epid)) {
4368 xn_device_name = sipe_xml_child(node, "deviceName");
4369 device_name = xn_device_name ? sipe_xml_attribute(xn_device_name, "name") : NULL;
4372 /* calendarInfo */
4373 xn_calendar_info = sipe_xml_child(node, "calendarInfo");
4374 if (xn_calendar_info) {
4375 const char *cal_start_time_tmp = sipe_xml_attribute(xn_calendar_info, "startTime");
4377 if (cal_start_time) {
4378 time_t cal_start_time_t = sipe_utils_str_to_time(cal_start_time);
4379 time_t cal_start_time_t_tmp = sipe_utils_str_to_time(cal_start_time_tmp);
4381 if (cal_start_time_t_tmp > cal_start_time_t) {
4382 cal_start_time = cal_start_time_tmp;
4383 cal_granularity = sipe_xml_attribute(xn_calendar_info, "granularity");
4384 g_free(cal_free_busy_base64);
4385 cal_free_busy_base64 = sipe_xml_data(xn_calendar_info);
4387 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);
4389 } else {
4390 cal_start_time = cal_start_time_tmp;
4391 cal_granularity = sipe_xml_attribute(xn_calendar_info, "granularity");
4392 g_free(cal_free_busy_base64);
4393 cal_free_busy_base64 = sipe_xml_data(xn_calendar_info);
4395 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);
4399 /* state */
4400 xn_state = sipe_xml_child(node, "states/state");
4401 if (xn_state) {
4402 int dev_avail = sipe_xml_int_attribute(xn_state, "avail", 0);
4403 time_t dev_avail_since = sipe_utils_str_to_time(sipe_xml_attribute(xn_state, "since"));
4405 state = sipe_xml_data(xn_state);
4406 if (dev_avail_since > user_avail_since &&
4407 dev_avail >= res_avail)
4409 res_avail = dev_avail;
4410 if (!is_empty(state))
4412 if (sipe_strequal(state, sipe_activity_map[SIPE_ACTIVITY_ON_PHONE].token)) {
4413 g_free(activity);
4414 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_ON_PHONE));
4415 } else if (sipe_strequal(state, "presenting")) {
4416 g_free(activity);
4417 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_IN_CONF));
4418 } else {
4419 activity = state;
4420 state = NULL;
4422 activity_since = dev_avail_since;
4424 status_id = sipe_get_status_by_availability(res_avail, &activity);
4426 g_free(state);
4430 /* oof */
4431 if (xn_oof && res_avail >= 15000) { /* 12000 in 2007 */
4432 g_free(activity);
4433 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_OOF));
4434 activity_since = 0;
4437 sbuddy = g_hash_table_lookup(sipe_private->buddies, uri);
4438 if (sbuddy)
4440 g_free(sbuddy->activity);
4441 sbuddy->activity = activity;
4442 activity = NULL;
4444 sbuddy->activity_since = activity_since;
4446 sbuddy->user_avail = user_avail;
4447 sbuddy->user_avail_since = user_avail_since;
4449 g_free(sbuddy->note);
4450 sbuddy->note = NULL;
4451 if (!is_empty(note)) { sbuddy->note = g_markup_escape_text(note, -1); }
4453 sbuddy->is_oof_note = (xn_oof != NULL);
4455 g_free(sbuddy->device_name);
4456 sbuddy->device_name = NULL;
4457 if (!is_empty(device_name)) { sbuddy->device_name = g_strdup(device_name); }
4459 if (!is_empty(cal_free_busy_base64)) {
4460 g_free(sbuddy->cal_start_time);
4461 sbuddy->cal_start_time = g_strdup(cal_start_time);
4463 sbuddy->cal_granularity = sipe_strcase_equal(cal_granularity, "PT15M") ? 15 : 0;
4465 g_free(sbuddy->cal_free_busy_base64);
4466 sbuddy->cal_free_busy_base64 = cal_free_busy_base64;
4467 cal_free_busy_base64 = NULL;
4469 g_free(sbuddy->cal_free_busy);
4470 sbuddy->cal_free_busy = NULL;
4473 sbuddy->last_non_cal_status_id = status_id;
4474 g_free(sbuddy->last_non_cal_activity);
4475 sbuddy->last_non_cal_activity = g_strdup(sbuddy->activity);
4477 if (sipe_strcase_equal(sbuddy->name, self_uri)) {
4478 if (!sipe_strequal(sbuddy->note, sip->note)) /* not same */
4480 sip->is_oof_note = sbuddy->is_oof_note;
4482 g_free(sip->note);
4483 sip->note = g_strdup(sbuddy->note);
4485 sip->note_since = time(NULL);
4488 g_free(sip->status);
4489 sip->status = g_strdup(sbuddy->last_non_cal_status_id);
4492 g_free(cal_free_busy_base64);
4493 g_free(activity);
4495 SIPE_DEBUG_INFO("process_incoming_notify_msrtc: status(%s)", status_id);
4496 sipe_got_user_status(sipe_private, uri, status_id);
4498 if (!SIPE_CORE_PRIVATE_FLAG_IS(OCS2007) && sipe_strcase_equal(self_uri, uri)) {
4499 sipe_user_info_has_updated(sipe_private, xn_userinfo);
4502 g_free(note);
4503 sipe_xml_free(xn_presentity);
4504 g_free(uri);
4505 g_free(self_uri);
4508 static void sipe_presence_mime_cb(gpointer user_data, /* sipe_core_private */
4509 const GSList *fields,
4510 const gchar *body,
4511 gsize length)
4513 const gchar *type = sipe_utils_nameval_find(fields, "Content-Type");
4515 if (strstr(type,"application/rlmi+xml")) {
4516 process_incoming_notify_rlmi_resub(user_data, body, length);
4517 } else if (strstr(type, "text/xml+msrtc.pidf")) {
4518 process_incoming_notify_msrtc(user_data, body, length);
4519 } else {
4520 process_incoming_notify_rlmi(user_data, body, length);
4524 static void sipe_process_presence(struct sipe_core_private *sipe_private,
4525 struct sipmsg *msg)
4527 const char *ctype = sipmsg_find_header(msg, "Content-Type");
4529 SIPE_DEBUG_INFO("sipe_process_presence: Content-Type: %s", ctype ? ctype : "");
4531 if (ctype &&
4532 (strstr(ctype, "application/rlmi+xml") ||
4533 strstr(ctype, "application/msrtc-event-categories+xml")))
4535 if (strstr(ctype, "multipart"))
4537 sipe_mime_parts_foreach(ctype, msg->body, sipe_presence_mime_cb, sipe_private);
4539 else if(strstr(ctype, "application/msrtc-event-categories+xml") )
4541 process_incoming_notify_rlmi(sipe_private, msg->body, msg->bodylen);
4543 else if(strstr(ctype, "application/rlmi+xml"))
4545 process_incoming_notify_rlmi_resub(sipe_private, msg->body, msg->bodylen);
4548 else if(ctype && strstr(ctype, "text/xml+msrtc.pidf"))
4550 process_incoming_notify_msrtc(sipe_private, msg->body, msg->bodylen);
4552 else
4554 process_incoming_notify_pidf(sipe_private, msg->body, msg->bodylen);
4558 static void sipe_presence_timeout_mime_cb(gpointer user_data,
4559 SIPE_UNUSED_PARAMETER const GSList *fields,
4560 const gchar *body,
4561 gsize length)
4563 GSList **buddies = user_data;
4564 sipe_xml *xml = sipe_xml_parse(body, length);
4566 if (xml && !sipe_strequal(sipe_xml_name(xml), "list")) {
4567 const gchar *uri = sipe_xml_attribute(xml, "uri");
4568 const sipe_xml *xn_category;
4571 * automaton: presence is never expected to change
4573 * see: http://msdn.microsoft.com/en-us/library/ee354295(office.13).aspx
4575 for (xn_category = sipe_xml_child(xml, "category");
4576 xn_category;
4577 xn_category = sipe_xml_twin(xn_category)) {
4578 if (sipe_strequal(sipe_xml_attribute(xn_category, "name"),
4579 "contactCard")) {
4580 const sipe_xml *node = sipe_xml_child(xn_category, "contactCard/automaton");
4581 if (node) {
4582 char *boolean = sipe_xml_data(node);
4583 if (sipe_strequal(boolean, "true")) {
4584 SIPE_DEBUG_INFO("sipe_process_presence_timeout: %s is an automaton: - not subscribing to presence updates",
4585 uri);
4586 uri = NULL;
4588 g_free(boolean);
4590 break;
4594 if (uri) {
4595 *buddies = g_slist_append(*buddies, sip_uri(uri));
4599 sipe_xml_free(xml);
4602 static void sipe_process_presence_timeout(struct sipe_core_private *sipe_private,
4603 struct sipmsg *msg, gchar *who,
4604 int timeout)
4606 const char *ctype = sipmsg_find_header(msg, "Content-Type");
4607 gchar *action_name = sipe_utils_presence_key(who);
4609 SIPE_DEBUG_INFO("sipe_process_presence_timeout: Content-Type: %s", ctype ? ctype : "");
4611 if (ctype &&
4612 strstr(ctype, "multipart") &&
4613 (strstr(ctype, "application/rlmi+xml") ||
4614 strstr(ctype, "application/msrtc-event-categories+xml"))) {
4615 GSList *buddies = NULL;
4617 sipe_mime_parts_foreach(ctype, msg->body, sipe_presence_timeout_mime_cb, &buddies);
4619 if (buddies) {
4620 struct presence_batched_routed *payload = g_malloc(sizeof(struct presence_batched_routed));
4621 payload->host = g_strdup(who);
4622 payload->buddies = buddies;
4623 sipe_schedule_seconds(sipe_private,
4624 action_name,
4625 payload,
4626 timeout,
4627 sipe_subscribe_presence_batched_routed,
4628 sipe_subscribe_presence_batched_routed_free);
4629 SIPE_DEBUG_INFO("Resubscription multiple contacts with batched support & route(%s) in %d", who, timeout);
4632 } else {
4633 sipe_schedule_seconds(sipe_private,
4634 action_name,
4635 g_strdup(who),
4636 timeout,
4637 sipe_subscribe_presence_single,
4638 g_free);
4639 SIPE_DEBUG_INFO("Resubscription single contact with batched support(%s) in %d", who, timeout);
4641 g_free(action_name);
4645 * Dispatcher for all incoming subscription information
4646 * whether it comes from NOTIFY, BENOTIFY requests or
4647 * piggy-backed to subscription's OK responce.
4649 * @param request whether initiated from BE/NOTIFY request or OK-response message.
4650 * @param benotify whether initiated from NOTIFY or BENOTIFY request.
4652 void process_incoming_notify(struct sipe_core_private *sipe_private,
4653 struct sipmsg *msg,
4654 gboolean request, gboolean benotify)
4656 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
4657 const gchar *content_type = sipmsg_find_header(msg, "Content-Type");
4658 const gchar *event = sipmsg_find_header(msg, "Event");
4659 const gchar *subscription_state = sipmsg_find_header(msg, "subscription-state");
4661 SIPE_DEBUG_INFO("process_incoming_notify: subscription_state: %s", subscription_state ? subscription_state : "");
4663 /* implicit subscriptions */
4664 if (content_type && g_str_has_prefix(content_type, "application/ms-imdn+xml")) {
4665 sipe_process_imdn(sipe_private, msg);
4668 if (event) {
4669 /* for one off subscriptions (send with Expire: 0) */
4670 if (sipe_strcase_equal(event, "vnd-microsoft-provisioning-v2"))
4672 sipe_process_provisioning_v2(sipe_private, msg);
4674 else if (sipe_strcase_equal(event, "vnd-microsoft-provisioning"))
4676 sipe_process_provisioning(sipe_private, msg);
4678 else if (sipe_strcase_equal(event, "presence"))
4680 sipe_process_presence(sipe_private, msg);
4682 else if (sipe_strcase_equal(event, "registration-notify"))
4684 sipe_process_registration_notify(sipe_private, msg);
4687 if (!subscription_state || strstr(subscription_state, "active"))
4689 if (sipe_strcase_equal(event, "vnd-microsoft-roaming-contacts"))
4691 sipe_process_roaming_contacts(sipe_private, msg);
4693 else if (sipe_strcase_equal(event, "vnd-microsoft-roaming-self"))
4695 sipe_process_roaming_self(sipe_private, msg);
4697 else if (sipe_strcase_equal(event, "vnd-microsoft-roaming-ACL"))
4699 sipe_process_roaming_acl(sipe_private, msg);
4701 else if (sipe_strcase_equal(event, "presence.wpending"))
4703 sipe_process_presence_wpending(sipe_private, msg);
4705 else if (sipe_strcase_equal(event, "conference"))
4707 sipe_process_conference(sipe_private, msg);
4712 /* The server sends status 'terminated' */
4713 if (subscription_state && strstr(subscription_state, "terminated") ) {
4714 gchar *who = parse_from(sipmsg_find_header(msg, request ? "From" : "To"));
4715 gchar *key = sipe_utils_subscription_key(event, who);
4717 SIPE_DEBUG_INFO("process_incoming_notify: server says that subscription to %s was terminated.", who);
4718 g_free(who);
4720 sipe_subscriptions_remove(sipe_private, key);
4721 g_free(key);
4724 if (!request && event) {
4725 const gchar *expires_header = sipmsg_find_header(msg, "Expires");
4726 int timeout = expires_header ? strtol(expires_header, NULL, 10) : 0;
4727 SIPE_DEBUG_INFO("process_incoming_notify: subscription expires:%d", timeout);
4729 if (timeout) {
4730 /* 2 min ahead of expiration */
4731 timeout = (timeout - 120) > 120 ? (timeout - 120) : timeout;
4733 if (sipe_strcase_equal(event, "presence.wpending") &&
4734 g_slist_find_custom(sip->allow_events, "presence.wpending", (GCompareFunc)g_ascii_strcasecmp))
4736 gchar *action_name = g_strdup_printf("<%s>", "presence.wpending");
4737 sipe_schedule_seconds(sipe_private,
4738 action_name,
4739 NULL,
4740 timeout,
4741 sipe_subscribe_presence_wpending,
4742 NULL);
4743 g_free(action_name);
4745 else if (sipe_strcase_equal(event, "presence") &&
4746 g_slist_find_custom(sip->allow_events, "presence", (GCompareFunc)g_ascii_strcasecmp))
4748 gchar *who = parse_from(sipmsg_find_header(msg, "To"));
4749 gchar *action_name = sipe_utils_presence_key(who);
4751 if (sip->batched_support) {
4752 sipe_process_presence_timeout(sipe_private, msg, who, timeout);
4754 else {
4755 sipe_schedule_seconds(sipe_private,
4756 action_name,
4757 g_strdup(who),
4758 timeout,
4759 sipe_subscribe_presence_single,
4760 g_free);
4761 SIPE_DEBUG_INFO("Resubscription single contact (%s) in %d", who, timeout);
4763 g_free(action_name);
4764 g_free(who);
4769 /* The client responses on received a NOTIFY message */
4770 if (request && !benotify)
4772 sip_transport_response(sipe_private, msg, 200, "OK", NULL);
4777 * Whether user manually changed status or
4778 * it was changed automatically due to user
4779 * became inactive/active again
4781 static gboolean
4782 sipe_is_user_state(struct sipe_core_private *sipe_private)
4784 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
4785 gboolean res;
4786 time_t now = time(NULL);
4788 SIPE_DEBUG_INFO("sipe_is_user_state: sip->idle_switch : %s", asctime(localtime(&(sip->idle_switch))));
4789 SIPE_DEBUG_INFO("sipe_is_user_state: now : %s", asctime(localtime(&now)));
4791 res = ((now - SIPE_IDLE_SET_DELAY * 2) >= sip->idle_switch);
4793 SIPE_DEBUG_INFO("sipe_is_user_state: res = %s", res ? "USER" : "MACHINE");
4794 return res;
4797 static void
4798 send_presence_soap0(struct sipe_core_private *sipe_private,
4799 gboolean do_publish_calendar,
4800 gboolean do_reset_status)
4802 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
4803 struct sipe_calendar* cal = sip->cal;
4804 int availability = 0;
4805 int activity = 0;
4806 gchar *body;
4807 gchar *tmp;
4808 gchar *tmp2 = NULL;
4809 gchar *res_note = NULL;
4810 gchar *res_oof = NULL;
4811 const gchar *note_pub = NULL;
4812 gchar *states = NULL;
4813 gchar *calendar_data = NULL;
4814 gchar *epid = get_epid(sipe_private);
4815 time_t now = time(NULL);
4816 gchar *since_time_str = sipe_utils_time_to_str(now);
4817 const gchar *oof_note = cal ? sipe_ews_get_oof_note(cal) : NULL;
4818 const char *user_input;
4819 gboolean pub_oof = cal && oof_note && (!sip->note || cal->updated > sip->note_since);
4821 if (oof_note && sip->note) {
4822 SIPE_DEBUG_INFO("cal->oof_start : %s", asctime(localtime(&(cal->oof_start))));
4823 SIPE_DEBUG_INFO("sip->note_since : %s", asctime(localtime(&(sip->note_since))));
4826 SIPE_DEBUG_INFO("sip->note : %s", sip->note ? sip->note : "");
4828 if (!sip->initial_state_published ||
4829 do_reset_status)
4831 g_free(sip->status);
4832 sip->status = g_strdup(SIPE_STATUS_ID_AVAILABLE);
4835 sipe_get_act_avail_by_status_2005(sip->status, &activity, &availability);
4837 /* Note */
4838 if (pub_oof) {
4839 note_pub = oof_note;
4840 res_oof = SIPE_SOAP_SET_PRESENCE_OOF_XML;
4841 cal->published = TRUE;
4842 } else if (sip->note) {
4843 if (sip->is_oof_note && !oof_note) { /* stale OOF note, as it's not present in cal already */
4844 g_free(sip->note);
4845 sip->note = NULL;
4846 sip->is_oof_note = FALSE;
4847 sip->note_since = 0;
4848 } else {
4849 note_pub = sip->note;
4850 res_oof = sip->is_oof_note ? SIPE_SOAP_SET_PRESENCE_OOF_XML : "";
4854 if (note_pub)
4856 /* to protocol internal plain text format */
4857 tmp = sipe_backend_markup_strip_html(note_pub);
4858 res_note = g_markup_printf_escaped(SIPE_SOAP_SET_PRESENCE_NOTE_XML, tmp);
4859 g_free(tmp);
4862 /* User State */
4863 if (!do_reset_status) {
4864 if (sipe_is_user_state(sipe_private) && !do_publish_calendar && sip->initial_state_published)
4866 gchar *activity_token = NULL;
4867 int avail_2007 = sipe_get_availability_by_status(sip->status, &activity_token);
4869 states = g_strdup_printf(SIPE_SOAP_SET_PRESENCE_STATES,
4870 avail_2007,
4871 since_time_str,
4872 epid,
4873 activity_token);
4874 g_free(activity_token);
4876 else /* preserve existing publication */
4878 if (sip->user_states) {
4879 states = g_strdup(sip->user_states);
4882 } else {
4883 /* do nothing - then User state will be erased */
4885 sip->initial_state_published = TRUE;
4887 /* CalendarInfo */
4888 if (cal && (!is_empty(cal->legacy_dn) || !is_empty(cal->email)) && cal->fb_start && !is_empty(cal->free_busy))
4890 char *fb_start_str = sipe_utils_time_to_str(cal->fb_start);
4891 char *free_busy_base64 = sipe_cal_get_freebusy_base64(cal->free_busy);
4892 calendar_data = g_strdup_printf(SIPE_SOAP_SET_PRESENCE_CALENDAR,
4893 !is_empty(cal->legacy_dn) ? cal->legacy_dn : cal->email,
4894 fb_start_str,
4895 free_busy_base64);
4896 g_free(fb_start_str);
4897 g_free(free_busy_base64);
4900 user_input = (sipe_is_user_state(sipe_private) ||
4901 sipe_strequal(sip->status, SIPE_STATUS_ID_AVAILABLE)) ?
4902 "active" : "idle";
4904 /* forming resulting XML */
4905 body = g_strdup_printf(SIPE_SOAP_SET_PRESENCE,
4906 sipe_private->username,
4907 availability,
4908 activity,
4909 (tmp = g_ascii_strup(g_get_host_name(), -1)),
4910 res_note ? res_note : "",
4911 res_oof ? res_oof : "",
4912 states ? states : "",
4913 calendar_data ? calendar_data : "",
4914 epid,
4915 since_time_str,
4916 since_time_str,
4917 user_input);
4918 g_free(tmp);
4919 g_free(tmp2);
4920 g_free(res_note);
4921 g_free(states);
4922 g_free(calendar_data);
4924 send_soap_request(sipe_private, body);
4926 g_free(body);
4927 g_free(since_time_str);
4928 g_free(epid);
4931 void
4932 send_presence_soap(struct sipe_core_private *sipe_private,
4933 gboolean do_publish_calendar)
4935 return send_presence_soap0(sipe_private, do_publish_calendar, FALSE);
4939 static gboolean
4940 process_send_presence_category_publish_response(struct sipe_core_private *sipe_private,
4941 struct sipmsg *msg,
4942 struct transaction *trans)
4944 const gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
4946 if (msg->response == 409 && g_str_has_prefix(contenttype, "application/msrtc-fault+xml")) {
4947 sipe_xml *xml;
4948 const sipe_xml *node;
4949 gchar *fault_code;
4950 GHashTable *faults;
4951 int index_our;
4952 gboolean has_device_publication = FALSE;
4954 xml = sipe_xml_parse(msg->body, msg->bodylen);
4956 /* test if version mismatch fault */
4957 fault_code = sipe_xml_data(sipe_xml_child(xml, "Faultcode"));
4958 if (!sipe_strequal(fault_code, "Client.BadCall.WrongDelta")) {
4959 SIPE_DEBUG_INFO("process_send_presence_category_publish_response: unsupported fault code:%s returning.", fault_code);
4960 g_free(fault_code);
4961 sipe_xml_free(xml);
4962 return TRUE;
4964 g_free(fault_code);
4966 /* accumulating information about faulty versions */
4967 faults = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
4968 for (node = sipe_xml_child(xml, "details/operation");
4969 node;
4970 node = sipe_xml_twin(node))
4972 const gchar *index = sipe_xml_attribute(node, "index");
4973 const gchar *curVersion = sipe_xml_attribute(node, "curVersion");
4975 g_hash_table_insert(faults, g_strdup(index), g_strdup(curVersion));
4976 SIPE_DEBUG_INFO("fault added: index:%s curVersion:%s", index, curVersion);
4978 sipe_xml_free(xml);
4980 /* here we are parsing own request to figure out what publication
4981 * referensed here only by index went wrong
4983 xml = sipe_xml_parse(trans->msg->body, trans->msg->bodylen);
4985 /* publication */
4986 for (node = sipe_xml_child(xml, "publications/publication"),
4987 index_our = 1; /* starts with 1 - our first publication */
4988 node;
4989 node = sipe_xml_twin(node), index_our++)
4991 gchar *idx = g_strdup_printf("%d", index_our);
4992 const gchar *curVersion = g_hash_table_lookup(faults, idx);
4993 const gchar *categoryName = sipe_xml_attribute(node, "categoryName");
4994 g_free(idx);
4996 if (sipe_strequal("device", categoryName)) {
4997 has_device_publication = TRUE;
5000 if (curVersion) { /* fault exist on this index */
5001 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5002 const gchar *container = sipe_xml_attribute(node, "container");
5003 const gchar *instance = sipe_xml_attribute(node, "instance");
5004 /* key is <category><instance><container> */
5005 gchar *key = g_strdup_printf("<%s><%s><%s>", categoryName, instance, container);
5006 GHashTable *category = g_hash_table_lookup(sip->our_publications, categoryName);
5008 if (category) {
5009 struct sipe_publication *publication =
5010 g_hash_table_lookup(category, key);
5012 SIPE_DEBUG_INFO("key is %s", key);
5014 if (publication) {
5015 SIPE_DEBUG_INFO("Updating %s with version %s. Was %d before.",
5016 key, curVersion, publication->version);
5017 /* updating publication's version to the correct one */
5018 publication->version = atoi(curVersion);
5020 } else {
5021 /* We somehow lost this category from our publications... */
5022 struct sipe_publication *publication = g_new0(struct sipe_publication, 1);
5023 publication->category = g_strdup(categoryName);
5024 publication->instance = atoi(instance);
5025 publication->container = atoi(container);
5026 publication->version = atoi(curVersion);
5027 category = g_hash_table_new_full(g_str_hash, g_str_equal,
5028 g_free, (GDestroyNotify)free_publication);
5029 g_hash_table_insert(category, g_strdup(key), publication);
5030 g_hash_table_insert(sip->our_publications, g_strdup(categoryName), category);
5031 SIPE_DEBUG_INFO("added lost category '%s' key '%s'", categoryName, key);
5033 g_free(key);
5036 sipe_xml_free(xml);
5037 g_hash_table_destroy(faults);
5039 /* rebublishing with right versions */
5040 if (has_device_publication) {
5041 send_publish_category_initial(sipe_private);
5042 } else {
5043 send_presence_status(sipe_private, NULL);
5046 return TRUE;
5050 * Returns 'device' XML part for publication.
5051 * Must be g_free'd after use.
5053 static gchar *
5054 sipe_publish_get_category_device(struct sipe_core_private *sipe_private)
5056 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5057 gchar *uri;
5058 gchar *doc;
5059 gchar *epid = get_epid(sipe_private);
5060 gchar *uuid = generateUUIDfromEPID(epid);
5061 guint device_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_DEVICE);
5062 /* key is <category><instance><container> */
5063 gchar *key = g_strdup_printf("<%s><%u><%u>", "device", device_instance, 2);
5064 struct sipe_publication *publication =
5065 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "device"), key);
5067 g_free(key);
5068 g_free(epid);
5070 uri = sip_uri_self(sipe_private);
5071 doc = g_strdup_printf(SIPE_PUB_XML_DEVICE,
5072 device_instance,
5073 publication ? publication->version : 0,
5074 uuid,
5075 uri,
5076 "00:00:00+01:00", /* @TODO make timezone real*/
5077 g_get_host_name()
5080 g_free(uri);
5081 g_free(uuid);
5083 return doc;
5087 * A service method - use
5088 * - send_publish_get_category_state_machine and
5089 * - send_publish_get_category_state_user instead.
5090 * Must be g_free'd after use.
5092 static gchar *
5093 sipe_publish_get_category_state(struct sipe_core_private *sipe_private,
5094 gboolean is_user_state)
5096 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5097 int availability = sipe_get_availability_by_status(sip->status, NULL);
5098 guint instance = is_user_state ? sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_USER) :
5099 sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_MACHINE);
5100 /* key is <category><instance><container> */
5101 gchar *key_2 = g_strdup_printf("<%s><%u><%u>", "state", instance, 2);
5102 gchar *key_3 = g_strdup_printf("<%s><%u><%u>", "state", instance, 3);
5103 struct sipe_publication *publication_2 =
5104 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_2);
5105 struct sipe_publication *publication_3 =
5106 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_3);
5108 g_free(key_2);
5109 g_free(key_3);
5111 if (publication_2 && (publication_2->availability == availability))
5113 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_state: state has NOT changed. Exiting.");
5114 return NULL; /* nothing to update */
5117 return g_strdup_printf( is_user_state ? SIPE_PUB_XML_STATE_USER : SIPE_PUB_XML_STATE_MACHINE,
5118 instance,
5119 publication_2 ? publication_2->version : 0,
5120 availability,
5121 instance,
5122 publication_3 ? publication_3->version : 0,
5123 availability);
5127 * Only Busy and OOF calendar event are published.
5128 * Different instances are used for that.
5130 * Must be g_free'd after use.
5132 static gchar *
5133 sipe_publish_get_category_state_calendar(struct sipe_core_private *sipe_private,
5134 struct sipe_cal_event *event,
5135 const char *uri,
5136 int cal_satus)
5138 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5139 gchar *start_time_str;
5140 int availability = 0;
5141 gchar *res;
5142 gchar *tmp = NULL;
5143 guint instance = (cal_satus == SIPE_CAL_OOF) ?
5144 sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_CALENDAR_OOF) :
5145 sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_CALENDAR);
5147 /* key is <category><instance><container> */
5148 gchar *key_2 = g_strdup_printf("<%s><%u><%u>", "state", instance, 2);
5149 gchar *key_3 = g_strdup_printf("<%s><%u><%u>", "state", instance, 3);
5150 struct sipe_publication *publication_2 =
5151 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_2);
5152 struct sipe_publication *publication_3 =
5153 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_3);
5155 g_free(key_2);
5156 g_free(key_3);
5158 if (!publication_3 && !event) { /* was nothing, have nothing, exiting */
5159 SIPE_DEBUG_INFO("sipe_publish_get_category_state_calendar: "
5160 "Exiting as no publication and no event for cal_satus:%d", cal_satus);
5161 return NULL;
5164 if (event &&
5165 publication_3 &&
5166 (publication_3->availability == availability) &&
5167 sipe_strequal(publication_3->cal_event_hash, (tmp = sipe_cal_event_hash(event))))
5169 g_free(tmp);
5170 SIPE_DEBUG_INFO("sipe_publish_get_category_state_calendar: "
5171 "cal state has NOT changed for cal_satus:%d. Exiting.", cal_satus);
5172 return NULL; /* nothing to update */
5174 g_free(tmp);
5176 if (event &&
5177 (event->cal_status == SIPE_CAL_BUSY ||
5178 event->cal_status == SIPE_CAL_OOF))
5180 gchar *availability_xml_str = NULL;
5181 gchar *activity_xml_str = NULL;
5183 if (event->cal_status == SIPE_CAL_BUSY) {
5184 availability_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_AVAIL, 6500);
5187 if (event->cal_status == SIPE_CAL_BUSY && event->is_meeting) {
5188 activity_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_ACTIVITY,
5189 sipe_activity_map[SIPE_ACTIVITY_IN_MEETING].token,
5190 "minAvailability=\"6500\"",
5191 "maxAvailability=\"8999\"");
5192 } else if (event->cal_status == SIPE_CAL_OOF) {
5193 activity_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_ACTIVITY,
5194 sipe_activity_map[SIPE_ACTIVITY_OOF].token,
5195 "minAvailability=\"12000\"",
5196 "");
5198 start_time_str = sipe_utils_time_to_str(event->start_time);
5200 res = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR,
5201 instance,
5202 publication_2 ? publication_2->version : 0,
5203 uri,
5204 start_time_str,
5205 availability_xml_str ? availability_xml_str : "",
5206 activity_xml_str ? activity_xml_str : "",
5207 event->subject ? event->subject : "",
5208 event->location ? event->location : "",
5210 instance,
5211 publication_3 ? publication_3->version : 0,
5212 uri,
5213 start_time_str,
5214 availability_xml_str ? availability_xml_str : "",
5215 activity_xml_str ? activity_xml_str : "",
5216 event->subject ? event->subject : "",
5217 event->location ? event->location : ""
5219 g_free(start_time_str);
5220 g_free(availability_xml_str);
5221 g_free(activity_xml_str);
5224 else /* including !event, SIPE_CAL_FREE, SIPE_CAL_TENTATIVE */
5226 res = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_CLEAR,
5227 instance,
5228 publication_2 ? publication_2->version : 0,
5230 instance,
5231 publication_3 ? publication_3->version : 0
5235 return res;
5239 * Returns 'machineState' XML part for publication.
5240 * Must be g_free'd after use.
5242 static gchar *
5243 sipe_publish_get_category_state_machine(struct sipe_core_private *sipe_private)
5245 return sipe_publish_get_category_state(sipe_private, FALSE);
5249 * Returns 'userState' XML part for publication.
5250 * Must be g_free'd after use.
5252 static gchar *
5253 sipe_publish_get_category_state_user(struct sipe_core_private *sipe_private)
5255 return sipe_publish_get_category_state(sipe_private, TRUE);
5259 * Returns 'note' XML part for publication.
5260 * Must be g_free'd after use.
5262 * Protocol format for Note is plain text.
5264 * @param note a note in Sipe internal HTML format
5265 * @param note_type either personal or OOF
5267 static gchar *
5268 sipe_publish_get_category_note(struct sipe_core_private *sipe_private,
5269 const char *note, /* html */
5270 const char *note_type,
5271 time_t note_start,
5272 time_t note_end)
5274 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5275 guint instance = sipe_strequal("OOF", note_type) ? sipe_get_pub_instance(sipe_private, SIPE_PUB_NOTE_OOF) : 0;
5276 /* key is <category><instance><container> */
5277 gchar *key_note_200 = g_strdup_printf("<%s><%u><%u>", "note", instance, 200);
5278 gchar *key_note_300 = g_strdup_printf("<%s><%u><%u>", "note", instance, 300);
5279 gchar *key_note_400 = g_strdup_printf("<%s><%u><%u>", "note", instance, 400);
5281 struct sipe_publication *publication_note_200 =
5282 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_200);
5283 struct sipe_publication *publication_note_300 =
5284 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_300);
5285 struct sipe_publication *publication_note_400 =
5286 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_400);
5288 char *tmp = note ? sipe_backend_markup_strip_html(note) : NULL;
5289 char *n1 = tmp ? g_markup_escape_text(tmp, -1) : NULL;
5290 const char *n2 = publication_note_200 ? publication_note_200->note : NULL;
5291 char *res, *tmp1, *tmp2, *tmp3;
5292 char *start_time_attr;
5293 char *end_time_attr;
5295 g_free(tmp);
5296 tmp = NULL;
5297 g_free(key_note_200);
5298 g_free(key_note_300);
5299 g_free(key_note_400);
5301 /* we even need to republish empty note */
5302 if (sipe_strequal(n1, n2))
5304 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_note: note has NOT changed. Exiting.");
5305 g_free(n1);
5306 return NULL; /* nothing to update */
5309 start_time_attr = note_start ? g_strdup_printf(" startTime=\"%s\"", (tmp = sipe_utils_time_to_str(note_start))) : NULL;
5310 g_free(tmp);
5311 tmp = NULL;
5312 end_time_attr = note_end ? g_strdup_printf(" endTime=\"%s\"", (tmp = sipe_utils_time_to_str(note_end))) : NULL;
5313 g_free(tmp);
5315 if (n1) {
5316 tmp1 = g_strdup_printf(SIPE_PUB_XML_NOTE,
5317 instance,
5318 200,
5319 publication_note_200 ? publication_note_200->version : 0,
5320 note_type,
5321 start_time_attr ? start_time_attr : "",
5322 end_time_attr ? end_time_attr : "",
5323 n1);
5325 tmp2 = g_strdup_printf(SIPE_PUB_XML_NOTE,
5326 instance,
5327 300,
5328 publication_note_300 ? publication_note_300->version : 0,
5329 note_type,
5330 start_time_attr ? start_time_attr : "",
5331 end_time_attr ? end_time_attr : "",
5332 n1);
5334 tmp3 = g_strdup_printf(SIPE_PUB_XML_NOTE,
5335 instance,
5336 400,
5337 publication_note_400 ? publication_note_400->version : 0,
5338 note_type,
5339 start_time_attr ? start_time_attr : "",
5340 end_time_attr ? end_time_attr : "",
5341 n1);
5342 } else {
5343 tmp1 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
5344 "note",
5345 instance,
5346 200,
5347 publication_note_200 ? publication_note_200->version : 0,
5348 "static");
5349 tmp2 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
5350 "note",
5351 instance,
5352 300,
5353 publication_note_200 ? publication_note_200->version : 0,
5354 "static");
5355 tmp3 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
5356 "note",
5357 instance,
5358 400,
5359 publication_note_200 ? publication_note_200->version : 0,
5360 "static");
5362 res = g_strconcat(tmp1, tmp2, tmp3, NULL);
5364 g_free(start_time_attr);
5365 g_free(end_time_attr);
5366 g_free(tmp1);
5367 g_free(tmp2);
5368 g_free(tmp3);
5369 g_free(n1);
5371 return res;
5375 * Returns 'calendarData' XML part with WorkingHours for publication.
5376 * Must be g_free'd after use.
5378 static gchar *
5379 sipe_publish_get_category_cal_working_hours(struct sipe_core_private *sipe_private)
5381 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5382 struct sipe_calendar* cal = sip->cal;
5384 /* key is <category><instance><container> */
5385 gchar *key_cal_1 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 1);
5386 gchar *key_cal_100 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 100);
5387 gchar *key_cal_200 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 200);
5388 gchar *key_cal_300 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 300);
5389 gchar *key_cal_400 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 400);
5390 gchar *key_cal_32000 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 32000);
5392 struct sipe_publication *publication_cal_1 =
5393 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_1);
5394 struct sipe_publication *publication_cal_100 =
5395 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_100);
5396 struct sipe_publication *publication_cal_200 =
5397 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_200);
5398 struct sipe_publication *publication_cal_300 =
5399 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_300);
5400 struct sipe_publication *publication_cal_400 =
5401 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_400);
5402 struct sipe_publication *publication_cal_32000 =
5403 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_32000);
5405 const char *n1 = cal ? cal->working_hours_xml_str : NULL;
5406 const char *n2 = publication_cal_300 ? publication_cal_300->working_hours_xml_str : NULL;
5408 g_free(key_cal_1);
5409 g_free(key_cal_100);
5410 g_free(key_cal_200);
5411 g_free(key_cal_300);
5412 g_free(key_cal_400);
5413 g_free(key_cal_32000);
5415 if (!cal || is_empty(cal->email) || is_empty(cal->working_hours_xml_str)) {
5416 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_cal_working_hours: no data to publish, exiting");
5417 return NULL;
5420 if (sipe_strequal(n1, n2))
5422 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_cal_working_hours: WorkingHours has NOT changed. Exiting.");
5423 return NULL; /* nothing to update */
5426 return g_strdup_printf(SIPE_PUB_XML_WORKING_HOURS,
5427 /* 1 */
5428 publication_cal_1 ? publication_cal_1->version : 0,
5429 cal->email,
5430 cal->working_hours_xml_str,
5431 /* 100 - Public */
5432 publication_cal_100 ? publication_cal_100->version : 0,
5433 /* 200 - Company */
5434 publication_cal_200 ? publication_cal_200->version : 0,
5435 cal->email,
5436 cal->working_hours_xml_str,
5437 /* 300 - Team */
5438 publication_cal_300 ? publication_cal_300->version : 0,
5439 cal->email,
5440 cal->working_hours_xml_str,
5441 /* 400 - Personal */
5442 publication_cal_400 ? publication_cal_400->version : 0,
5443 cal->email,
5444 cal->working_hours_xml_str,
5445 /* 32000 - Blocked */
5446 publication_cal_32000 ? publication_cal_32000->version : 0
5451 * Returns 'calendarData' XML part with FreeBusy for publication.
5452 * Must be g_free'd after use.
5454 static gchar *
5455 sipe_publish_get_category_cal_free_busy(struct sipe_core_private *sipe_private)
5457 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5458 struct sipe_calendar* cal = sip->cal;
5459 guint cal_data_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_CALENDAR_DATA);
5460 char *fb_start_str;
5461 char *free_busy_base64;
5462 const char *st;
5463 const char *fb;
5464 char *res;
5466 /* key is <category><instance><container> */
5467 gchar *key_cal_1 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 1);
5468 gchar *key_cal_100 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 100);
5469 gchar *key_cal_200 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 200);
5470 gchar *key_cal_300 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 300);
5471 gchar *key_cal_400 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 400);
5472 gchar *key_cal_32000 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 32000);
5474 struct sipe_publication *publication_cal_1 =
5475 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_1);
5476 struct sipe_publication *publication_cal_100 =
5477 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_100);
5478 struct sipe_publication *publication_cal_200 =
5479 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_200);
5480 struct sipe_publication *publication_cal_300 =
5481 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_300);
5482 struct sipe_publication *publication_cal_400 =
5483 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_400);
5484 struct sipe_publication *publication_cal_32000 =
5485 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_32000);
5487 g_free(key_cal_1);
5488 g_free(key_cal_100);
5489 g_free(key_cal_200);
5490 g_free(key_cal_300);
5491 g_free(key_cal_400);
5492 g_free(key_cal_32000);
5494 if (!cal || is_empty(cal->email) || !cal->fb_start || is_empty(cal->free_busy)) {
5495 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_cal_free_busy: no data to publish, exiting");
5496 return NULL;
5499 fb_start_str = sipe_utils_time_to_str(cal->fb_start);
5500 free_busy_base64 = sipe_cal_get_freebusy_base64(cal->free_busy);
5502 st = publication_cal_300 ? publication_cal_300->fb_start_str : NULL;
5503 fb = publication_cal_300 ? publication_cal_300->free_busy_base64 : NULL;
5505 /* we will rebuplish the same data to refresh publication time,
5506 * so if data from multiple sources, most recent will be choosen
5508 //if (sipe_strequal(st, fb_start_str) && sipe_strequal(fb, free_busy_base64))
5510 // SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_cal_free_busy: FreeBusy has NOT changed. Exiting.");
5511 // g_free(fb_start_str);
5512 // g_free(free_busy_base64);
5513 // return NULL; /* nothing to update */
5516 res = g_strdup_printf(SIPE_PUB_XML_FREE_BUSY,
5517 /* 1 */
5518 cal_data_instance,
5519 publication_cal_1 ? publication_cal_1->version : 0,
5520 /* 100 - Public */
5521 cal_data_instance,
5522 publication_cal_100 ? publication_cal_100->version : 0,
5523 /* 200 - Company */
5524 cal_data_instance,
5525 publication_cal_200 ? publication_cal_200->version : 0,
5526 cal->email,
5527 fb_start_str,
5528 free_busy_base64,
5529 /* 300 - Team */
5530 cal_data_instance,
5531 publication_cal_300 ? publication_cal_300->version : 0,
5532 cal->email,
5533 fb_start_str,
5534 free_busy_base64,
5535 /* 400 - Personal */
5536 cal_data_instance,
5537 publication_cal_400 ? publication_cal_400->version : 0,
5538 cal->email,
5539 fb_start_str,
5540 free_busy_base64,
5541 /* 32000 - Blocked */
5542 cal_data_instance,
5543 publication_cal_32000 ? publication_cal_32000->version : 0
5546 g_free(fb_start_str);
5547 g_free(free_busy_base64);
5548 return res;
5551 static void send_presence_publish(struct sipe_core_private *sipe_private,
5552 const char *publications)
5554 gchar *uri;
5555 gchar *doc;
5556 gchar *tmp;
5557 gchar *hdr;
5559 uri = sip_uri_self(sipe_private);
5560 doc = g_strdup_printf(SIPE_SEND_PRESENCE,
5561 uri,
5562 publications);
5564 tmp = get_contact(sipe_private);
5565 hdr = g_strdup_printf("Contact: %s\r\n"
5566 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp);
5568 sip_transport_service(sipe_private,
5569 uri,
5570 hdr,
5571 doc,
5572 process_send_presence_category_publish_response);
5574 g_free(tmp);
5575 g_free(hdr);
5576 g_free(uri);
5577 g_free(doc);
5580 static void
5581 send_publish_category_initial(struct sipe_core_private *sipe_private)
5583 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5584 gchar *pub_device = sipe_publish_get_category_device(sipe_private);
5585 gchar *pub_machine;
5586 gchar *publications;
5588 g_free(sip->status);
5589 sip->status = g_strdup(SIPE_STATUS_ID_AVAILABLE); /* our initial state */
5591 pub_machine = sipe_publish_get_category_state_machine(sipe_private);
5592 publications = g_strdup_printf("%s%s",
5593 pub_device,
5594 pub_machine ? pub_machine : "");
5595 g_free(pub_device);
5596 g_free(pub_machine);
5598 send_presence_publish(sipe_private, publications);
5599 g_free(publications);
5602 static void
5603 send_presence_category_publish(struct sipe_core_private *sipe_private)
5605 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5606 gchar *pub_state = sipe_is_user_state(sipe_private) ?
5607 sipe_publish_get_category_state_user(sipe_private) :
5608 sipe_publish_get_category_state_machine(sipe_private);
5609 gchar *pub_note = sipe_publish_get_category_note(sipe_private,
5610 sip->note,
5611 sip->is_oof_note ? "OOF" : "personal",
5614 gchar *publications;
5616 if (!pub_state && !pub_note) {
5617 SIPE_DEBUG_INFO_NOFORMAT("send_presence_category_publish: nothing has changed. Exiting.");
5618 return;
5621 publications = g_strdup_printf("%s%s",
5622 pub_state ? pub_state : "",
5623 pub_note ? pub_note : "");
5625 g_free(pub_state);
5626 g_free(pub_note);
5628 send_presence_publish(sipe_private, publications);
5629 g_free(publications);
5633 * Publishes self status
5634 * based on own calendar information.
5636 * For 2007+
5638 void
5639 publish_calendar_status_self(struct sipe_core_private *sipe_private,
5640 SIPE_UNUSED_PARAMETER void *unused)
5642 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5643 struct sipe_cal_event* event = NULL;
5644 gchar *pub_cal_working_hours = NULL;
5645 gchar *pub_cal_free_busy = NULL;
5646 gchar *pub_calendar = NULL;
5647 gchar *pub_calendar2 = NULL;
5648 gchar *pub_oof_note = NULL;
5649 const gchar *oof_note;
5650 time_t oof_start = 0;
5651 time_t oof_end = 0;
5653 if (!sip->cal) {
5654 SIPE_DEBUG_INFO_NOFORMAT("publish_calendar_status_self() no calendar data.");
5655 return;
5658 SIPE_DEBUG_INFO_NOFORMAT("publish_calendar_status_self() started.");
5659 if (sip->cal->cal_events) {
5660 event = sipe_cal_get_event(sip->cal->cal_events, time(NULL));
5663 if (!event) {
5664 SIPE_DEBUG_INFO_NOFORMAT("publish_calendar_status_self: current event is NULL");
5665 } else {
5666 char *desc = sipe_cal_event_describe(event);
5667 SIPE_DEBUG_INFO("publish_calendar_status_self: current event is:\n%s", desc ? desc : "");
5668 g_free(desc);
5671 /* Logic
5672 if OOF
5673 OOF publish, Busy clean
5674 ilse if Busy
5675 OOF clean, Busy publish
5676 else
5677 OOF clean, Busy clean
5679 if (event && event->cal_status == SIPE_CAL_OOF) {
5680 pub_calendar = sipe_publish_get_category_state_calendar(sipe_private, event, sip->cal->email, SIPE_CAL_OOF);
5681 pub_calendar2 = sipe_publish_get_category_state_calendar(sipe_private, NULL, sip->cal->email, SIPE_CAL_BUSY);
5682 } else if (event && event->cal_status == SIPE_CAL_BUSY) {
5683 pub_calendar = sipe_publish_get_category_state_calendar(sipe_private, NULL, sip->cal->email, SIPE_CAL_OOF);
5684 pub_calendar2 = sipe_publish_get_category_state_calendar(sipe_private, event, sip->cal->email, SIPE_CAL_BUSY);
5685 } else {
5686 pub_calendar = sipe_publish_get_category_state_calendar(sipe_private, NULL, sip->cal->email, SIPE_CAL_OOF);
5687 pub_calendar2 = sipe_publish_get_category_state_calendar(sipe_private, NULL, sip->cal->email, SIPE_CAL_BUSY);
5690 oof_note = sipe_ews_get_oof_note(sip->cal);
5691 if (sipe_strequal("Scheduled", sip->cal->oof_state)) {
5692 oof_start = sip->cal->oof_start;
5693 oof_end = sip->cal->oof_end;
5695 pub_oof_note = sipe_publish_get_category_note(sipe_private, oof_note, "OOF", oof_start, oof_end);
5697 pub_cal_working_hours = sipe_publish_get_category_cal_working_hours(sipe_private);
5698 pub_cal_free_busy = sipe_publish_get_category_cal_free_busy(sipe_private);
5700 if (!pub_cal_working_hours && !pub_cal_free_busy && !pub_calendar && !pub_calendar2 && !pub_oof_note) {
5701 SIPE_DEBUG_INFO_NOFORMAT("publish_calendar_status_self: nothing has changed.");
5702 } else {
5703 gchar *publications = g_strdup_printf("%s%s%s%s%s",
5704 pub_cal_working_hours ? pub_cal_working_hours : "",
5705 pub_cal_free_busy ? pub_cal_free_busy : "",
5706 pub_calendar ? pub_calendar : "",
5707 pub_calendar2 ? pub_calendar2 : "",
5708 pub_oof_note ? pub_oof_note : "");
5710 send_presence_publish(sipe_private, publications);
5711 g_free(publications);
5714 g_free(pub_cal_working_hours);
5715 g_free(pub_cal_free_busy);
5716 g_free(pub_calendar);
5717 g_free(pub_calendar2);
5718 g_free(pub_oof_note);
5720 /* repeat scheduling */
5721 sipe_sched_calendar_status_self_publish(sipe_private, time(NULL));
5724 static void send_presence_status(struct sipe_core_private *sipe_private,
5725 SIPE_UNUSED_PARAMETER void *unused)
5727 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5728 PurpleStatus * status = purple_account_get_active_status(sip->account);
5730 if (!status) return;
5732 SIPE_DEBUG_INFO("send_presence_status: status: %s (%s)",
5733 purple_status_get_id(status) ? purple_status_get_id(status) : "",
5734 sipe_is_user_state(sipe_private) ? "USER" : "MACHINE");
5736 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
5737 send_presence_category_publish(sipe_private);
5738 } else {
5739 send_presence_soap(sipe_private, FALSE);
5743 static guint sipe_ht_hash_nick(const char *nick)
5745 char *lc = g_utf8_strdown(nick, -1);
5746 guint bucket = g_str_hash(lc);
5747 g_free(lc);
5749 return bucket;
5752 static gboolean sipe_ht_equals_nick(const char *nick1, const char *nick2)
5754 char *nick1_norm = NULL;
5755 char *nick2_norm = NULL;
5756 gboolean equal;
5758 if (nick1 == NULL && nick2 == NULL) return TRUE;
5759 if (nick1 == NULL || nick2 == NULL ||
5760 !g_utf8_validate(nick1, -1, NULL) ||
5761 !g_utf8_validate(nick2, -1, NULL)) return FALSE;
5763 nick1_norm = g_utf8_casefold(nick1, -1);
5764 nick2_norm = g_utf8_casefold(nick2, -1);
5765 equal = g_utf8_collate(nick1_norm, nick2_norm) == 0;
5766 g_free(nick2_norm);
5767 g_free(nick1_norm);
5769 return equal;
5772 /* temporary function */
5773 void sipe_purple_setup(struct sipe_core_public *sipe_public,
5774 PurpleConnection *gc)
5776 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA;
5777 sip->gc = gc;
5778 sip->account = purple_connection_get_account(gc);
5781 struct sipe_core_public *sipe_core_allocate(const gchar *signin_name,
5782 const gchar *login_domain,
5783 const gchar *login_account,
5784 const gchar *password,
5785 const gchar *email,
5786 const gchar *email_url,
5787 const gchar **errmsg)
5789 struct sipe_core_private *sipe_private;
5790 struct sipe_account_data *sip;
5791 gchar **user_domain;
5793 SIPE_DEBUG_INFO("sipe_core_allocate: signin_name '%s'", signin_name);
5795 /* ensure that sign-in name doesn't contain invalid characters */
5796 if (strpbrk(signin_name, "\t\v\r\n") != NULL) {
5797 *errmsg = _("SIP Exchange user name contains invalid characters");
5798 return NULL;
5801 /* ensure that sign-in name format is name@domain */
5802 if (!strchr(signin_name, '@') ||
5803 g_str_has_prefix(signin_name, "@") ||
5804 g_str_has_suffix(signin_name, "@")) {
5805 *errmsg = _("User name should be a valid SIP URI\nExample: user@company.com");
5806 return NULL;
5809 /* ensure that email format is name@domain (if provided) */
5810 if (!is_empty(email) &&
5811 (!strchr(email, '@') ||
5812 g_str_has_prefix(email, "@") ||
5813 g_str_has_suffix(email, "@")))
5815 *errmsg = _("Email address should be valid if provided\nExample: user@company.com");
5816 return NULL;
5819 /* ensure that user name doesn't contain spaces */
5820 user_domain = g_strsplit(signin_name, "@", 2);
5821 SIPE_DEBUG_INFO("sipe_core_allocate: user '%s' domain '%s'", user_domain[0], user_domain[1]);
5822 if (strchr(user_domain[0], ' ') != NULL) {
5823 g_strfreev(user_domain);
5824 *errmsg = _("SIP Exchange user name contains whitespace");
5825 return NULL;
5828 /* ensure that email_url is in proper format if enabled (if provided).
5829 * Example (Exchange): https://server.company.com/EWS/Exchange.asmx
5830 * Example (Domino) : https://[domino_server]/[mail_database_name].nsf
5832 if (!is_empty(email_url)) {
5833 char *tmp = g_ascii_strdown(email_url, -1);
5834 if (!g_str_has_prefix(tmp, "https://"))
5836 g_free(tmp);
5837 g_strfreev(user_domain);
5838 *errmsg = _("Email services URL should be valid if provided\n"
5839 "Example: https://exchange.corp.com/EWS/Exchange.asmx\n"
5840 "Example: https://domino.corp.com/maildatabase.nsf");
5841 return NULL;
5843 g_free(tmp);
5846 sipe_private = g_new0(struct sipe_core_private, 1);
5847 sipe_private->temporary = sip = g_new0(struct sipe_account_data, 1);
5848 sip->subscribed_buddies = FALSE;
5849 sip->initial_state_published = FALSE;
5850 sipe_private->username = g_strdup(signin_name);
5851 sip->email = is_empty(email) ? g_strdup(signin_name) : g_strdup(email);
5852 sip->authdomain = is_empty(login_domain) ? NULL : g_strdup(login_domain);
5853 sip->authuser = is_empty(login_account) ? NULL : g_strdup(login_account);
5854 sip->password = g_strdup(password);
5855 sipe_private->public.sip_name = g_strdup(user_domain[0]);
5856 sipe_private->public.sip_domain = g_strdup(user_domain[1]);
5857 g_strfreev(user_domain);
5859 sipe_private->buddies = g_hash_table_new((GHashFunc)sipe_ht_hash_nick, (GEqualFunc)sipe_ht_equals_nick);
5860 sip->our_publications = g_hash_table_new_full(g_str_hash, g_str_equal,
5861 g_free, (GDestroyNotify)g_hash_table_destroy);
5862 sipe_subscriptions_init(sipe_private);
5863 sip->status = g_strdup(SIPE_STATUS_ID_UNKNOWN);
5865 return((struct sipe_core_public *)sipe_private);
5868 static void
5869 sipe_blist_menu_free_containers(struct sipe_core_private *sipe_private);
5871 void sipe_connection_cleanup(struct sipe_core_private *sipe_private)
5873 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5875 g_free(sipe_private->epid);
5876 sipe_private->epid = NULL;
5878 sip_transport_disconnect(sipe_private);
5880 sipe_schedule_cancel_all(sipe_private);
5882 if (sip->allow_events) {
5883 GSList *entry = sip->allow_events;
5884 while (entry) {
5885 g_free(entry->data);
5886 entry = entry->next;
5889 g_slist_free(sip->allow_events);
5891 if (sip->containers) {
5892 GSList *entry = sip->containers;
5893 while (entry) {
5894 free_container((struct sipe_container *)entry->data);
5895 entry = entry->next;
5898 g_slist_free(sip->containers);
5900 /* libpurple memory leak workaround */
5901 sipe_blist_menu_free_containers(sipe_private);
5903 if (sipe_private->contact)
5904 g_free(sipe_private->contact);
5905 sipe_private->contact = NULL;
5906 if (sip->regcallid)
5907 g_free(sip->regcallid);
5908 sip->regcallid = NULL;
5910 if (sipe_private->focus_factory_uri)
5911 g_free(sipe_private->focus_factory_uri);
5912 sipe_private->focus_factory_uri = NULL;
5914 if (sip->cal) {
5915 sipe_cal_calendar_free(sip->cal);
5917 sip->cal = NULL;
5919 sipe_groupchat_free(sipe_private);
5923 * A callback for g_hash_table_foreach_remove
5925 static gboolean sipe_buddy_remove(SIPE_UNUSED_PARAMETER gpointer key, gpointer buddy,
5926 SIPE_UNUSED_PARAMETER gpointer user_data)
5928 sipe_free_buddy((struct sipe_buddy *) buddy);
5930 /* We must return TRUE as the key/value have already been deleted */
5931 return(TRUE);
5934 void sipe_buddy_free_all(struct sipe_core_private *sipe_private)
5936 g_hash_table_foreach_steal(sipe_private->buddies, sipe_buddy_remove, NULL);
5939 static void sipe_searchresults_im_buddy(PurpleConnection *gc, GList *row,
5940 SIPE_UNUSED_PARAMETER void *user_data)
5942 PurpleAccount *acct = purple_connection_get_account(gc);
5943 char *id = sip_uri_from_name((gchar *)g_list_nth_data(row, 0));
5944 PurpleConversation *conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, id, acct);
5945 if (conv == NULL)
5946 conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, acct, id);
5947 purple_conversation_present(conv);
5948 g_free(id);
5951 static void sipe_searchresults_add_buddy(PurpleConnection *gc, GList *row,
5952 SIPE_UNUSED_PARAMETER void *user_data)
5955 purple_blist_request_add_buddy(purple_connection_get_account(gc),
5956 g_list_nth_data(row, 0), _("Other Contacts"), g_list_nth_data(row, 1));
5959 static gboolean process_search_contact_response(struct sipe_core_private *sipe_private,
5960 struct sipmsg *msg,
5961 SIPE_UNUSED_PARAMETER struct transaction *trans)
5963 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5964 PurpleNotifySearchResults *results;
5965 PurpleNotifySearchColumn *column;
5966 sipe_xml *searchResults;
5967 const sipe_xml *mrow;
5968 int match_count = 0;
5969 gboolean more = FALSE;
5970 gchar *secondary;
5972 SIPE_DEBUG_INFO("process_search_contact_response: body:\n%s", msg->body ? msg->body : "");
5974 searchResults = sipe_xml_parse(msg->body, msg->bodylen);
5975 if (!searchResults) {
5976 SIPE_DEBUG_INFO_NOFORMAT("process_search_contact_response: no parseable searchResults");
5977 return FALSE;
5980 results = purple_notify_searchresults_new();
5982 if (results == NULL) {
5983 SIPE_DEBUG_ERROR_NOFORMAT("purple_parse_searchreply: Unable to display the search results.");
5984 purple_notify_error(sip->gc, NULL, _("Unable to display the search results"), NULL);
5986 sipe_xml_free(searchResults);
5987 return FALSE;
5990 column = purple_notify_searchresults_column_new(_("User name"));
5991 purple_notify_searchresults_column_add(results, column);
5993 column = purple_notify_searchresults_column_new(_("Name"));
5994 purple_notify_searchresults_column_add(results, column);
5996 column = purple_notify_searchresults_column_new(_("Company"));
5997 purple_notify_searchresults_column_add(results, column);
5999 column = purple_notify_searchresults_column_new(_("Country"));
6000 purple_notify_searchresults_column_add(results, column);
6002 column = purple_notify_searchresults_column_new(_("Email"));
6003 purple_notify_searchresults_column_add(results, column);
6005 for (mrow = sipe_xml_child(searchResults, "Body/Array/row"); mrow; mrow = sipe_xml_twin(mrow)) {
6006 GList *row = NULL;
6008 gchar **uri_parts = g_strsplit(sipe_xml_attribute(mrow, "uri"), ":", 2);
6009 row = g_list_append(row, g_strdup(uri_parts[1]));
6010 g_strfreev(uri_parts);
6012 row = g_list_append(row, g_strdup(sipe_xml_attribute(mrow, "displayName")));
6013 row = g_list_append(row, g_strdup(sipe_xml_attribute(mrow, "company")));
6014 row = g_list_append(row, g_strdup(sipe_xml_attribute(mrow, "country")));
6015 row = g_list_append(row, g_strdup(sipe_xml_attribute(mrow, "email")));
6017 purple_notify_searchresults_row_add(results, row);
6018 match_count++;
6021 if ((mrow = sipe_xml_child(searchResults, "Body/directorySearch/moreAvailable")) != NULL) {
6022 char *data = sipe_xml_data(mrow);
6023 more = (g_strcasecmp(data, "true") == 0);
6024 g_free(data);
6027 secondary = g_strdup_printf(
6028 dngettext(PACKAGE_NAME,
6029 "Found %d contact%s:",
6030 "Found %d contacts%s:", match_count),
6031 match_count, more ? _(" (more matched your query)") : "");
6033 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_IM, sipe_searchresults_im_buddy);
6034 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_ADD, sipe_searchresults_add_buddy);
6035 purple_notify_searchresults(sip->gc, NULL, NULL, secondary, results, NULL, NULL);
6037 g_free(secondary);
6038 sipe_xml_free(searchResults);
6039 return TRUE;
6042 void sipe_search_contact_with_cb(PurpleConnection *gc, PurpleRequestFields *fields)
6044 GList *entries = purple_request_field_group_get_fields(purple_request_fields_get_groups(fields)->data);
6045 gchar **attrs = g_new(gchar *, g_list_length(entries) + 1);
6046 unsigned i = 0;
6048 if (!attrs) return;
6050 do {
6051 PurpleRequestField *field = entries->data;
6052 const char *id = purple_request_field_get_id(field);
6053 const char *value = purple_request_field_string_get_value(field);
6055 SIPE_DEBUG_INFO("sipe_search_contact_with_cb: %s = '%s'", id, value ? value : "");
6057 if (value != NULL) attrs[i++] = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, id, value);
6058 } while ((entries = g_list_next(entries)) != NULL);
6059 attrs[i] = NULL;
6061 if (i > 0) {
6062 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
6063 gchar *domain_uri = sip_uri_from_name(sipe_private->public.sip_domain);
6064 gchar *query = g_strjoinv(NULL, attrs);
6065 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 100, query);
6066 SIPE_DEBUG_INFO("sipe_search_contact_with_cb: body:\n%s", body ? body : "");
6067 send_soap_request_with_cb(sipe_private, domain_uri, body,
6068 process_search_contact_response, NULL);
6069 g_free(domain_uri);
6070 g_free(body);
6071 g_free(query);
6074 g_strfreev(attrs);
6077 static void sipe_publish_get_cat_state_user_to_clear(SIPE_UNUSED_PARAMETER const char *name,
6078 gpointer value,
6079 GString* str)
6081 struct sipe_publication *publication = value;
6083 g_string_append_printf( str,
6084 SIPE_PUB_XML_PUBLICATION_CLEAR,
6085 publication->category,
6086 publication->instance,
6087 publication->container,
6088 publication->version,
6089 "static");
6092 void sipe_core_reset_status(struct sipe_core_public *sipe_public)
6094 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
6095 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA;
6096 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) /* 2007+ */
6098 GString* str = g_string_new(NULL);
6099 gchar *publications;
6101 if (!sip->user_state_publications || g_hash_table_size(sip->user_state_publications) == 0) {
6102 SIPE_DEBUG_INFO_NOFORMAT("sipe_reset_status: no userState publications, exiting.");
6103 return;
6106 g_hash_table_foreach(sip->user_state_publications, (GHFunc)sipe_publish_get_cat_state_user_to_clear, str);
6107 publications = g_string_free(str, FALSE);
6109 send_presence_publish(sipe_private, publications);
6110 g_free(publications);
6112 else /* 2005 */
6114 send_presence_soap0(sipe_private, FALSE, TRUE);
6118 /** for Access levels menu */
6119 #define INDENT_FMT " %s"
6121 /** Member is directly placed to access level container.
6122 * For example SIP URI of user is in the container.
6124 #define INDENT_MARKED_FMT "* %s"
6126 /** Member is indirectly belong to access level container.
6127 * For example 'sameEnterprise' is in the container and user
6128 * belongs to that same enterprise.
6130 #define INDENT_MARKED_INHERITED_FMT "= %s"
6132 GSList *sipe_core_buddy_info(struct sipe_core_public *sipe_public,
6133 const gchar *name,
6134 const gchar *status_name,
6135 gboolean is_online)
6137 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
6138 gchar *note = NULL;
6139 gboolean is_oof_note = FALSE;
6140 gchar *activity = NULL;
6141 gchar *calendar = NULL;
6142 gchar *meeting_subject = NULL;
6143 gchar *meeting_location = NULL;
6144 gchar *access_text = NULL;
6145 GSList *info = NULL;
6147 #define SIPE_ADD_BUDDY_INFO(l, t) \
6149 struct sipe_buddy_info *sbi = g_malloc(sizeof(struct sipe_buddy_info)); \
6150 sbi->label = (l); \
6151 sbi->text = (t); \
6152 info = g_slist_append(info, sbi); \
6155 if (sipe_public) { //happens on pidgin exit
6156 struct sipe_buddy *sbuddy = g_hash_table_lookup(sipe_private->buddies, name);
6157 if (sbuddy) {
6158 note = sbuddy->note;
6159 is_oof_note = sbuddy->is_oof_note;
6160 activity = sbuddy->activity;
6161 calendar = sipe_cal_get_description(sbuddy);
6162 meeting_subject = sbuddy->meeting_subject;
6163 meeting_location = sbuddy->meeting_location;
6165 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
6166 gboolean is_group_access = FALSE;
6167 const int container_id = sipe_find_access_level(sipe_private, "user", sipe_get_no_sip_uri(name), &is_group_access);
6168 const char *access_level = sipe_get_access_level_name(container_id);
6169 access_text = is_group_access ?
6170 g_strdup(access_level) :
6171 g_strdup_printf(INDENT_MARKED_FMT, access_level);
6175 //Layout
6176 if (is_online)
6178 gchar *status_str = g_strdup(activity ? activity : status_name);
6180 SIPE_ADD_BUDDY_INFO(_("Status"), status_str);
6182 if (is_online && !is_empty(calendar))
6184 SIPE_ADD_BUDDY_INFO(_("Calendar"), calendar);
6185 calendar = NULL;
6187 g_free(calendar);
6188 if (!is_empty(meeting_location))
6190 SIPE_ADD_BUDDY_INFO(_("Meeting in"), g_strdup(meeting_location));
6192 if (!is_empty(meeting_subject))
6194 SIPE_ADD_BUDDY_INFO(_("Meeting about"), g_strdup(meeting_subject));
6196 if (note)
6198 SIPE_DEBUG_INFO("sipe_tooltip_text: %s note: '%s'", name, note);
6199 SIPE_ADD_BUDDY_INFO(is_oof_note ? _("Out of office note") : _("Note"),
6200 g_strdup_printf("<i>%s</i>", note));
6202 if (access_text) {
6203 SIPE_ADD_BUDDY_INFO(_("Access level"), access_text);
6206 return(info);
6209 static PurpleBuddy *
6210 purple_blist_add_buddy_clone(PurpleGroup * group, PurpleBuddy * buddy)
6212 PurpleBuddy *clone;
6213 const gchar *server_alias, *email;
6214 const PurpleStatus *status = purple_presence_get_active_status(purple_buddy_get_presence(buddy));
6216 clone = purple_buddy_new(buddy->account, buddy->name, buddy->alias);
6218 purple_blist_add_buddy(clone, NULL, group, NULL);
6220 server_alias = purple_buddy_get_server_alias(buddy);
6221 if (server_alias) {
6222 purple_blist_server_alias_buddy(clone, server_alias);
6225 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
6226 if (email) {
6227 purple_blist_node_set_string(&clone->node, EMAIL_PROP, email);
6230 purple_presence_set_status_active(purple_buddy_get_presence(clone), purple_status_get_id(status), TRUE);
6231 //for UI to update;
6232 purple_prpl_got_user_status(clone->account, clone->name, purple_status_get_id(status), NULL);
6233 return clone;
6236 static void
6237 sipe_buddy_menu_copy_to_cb(PurpleBlistNode *node, const char *group_name)
6239 PurpleBuddy *buddy, *b;
6240 PurpleConnection *gc;
6241 PurpleGroup * group = purple_find_group(group_name);
6243 g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node));
6245 buddy = (PurpleBuddy *)node;
6247 SIPE_DEBUG_INFO("sipe_buddy_menu_copy_to_cb: copying %s to %s", buddy->name, group_name);
6248 gc = purple_account_get_connection(buddy->account);
6250 b = purple_find_buddy_in_group(buddy->account, buddy->name, group);
6251 if (!b){
6252 purple_blist_add_buddy_clone(group, buddy);
6255 sipe_group_buddy(gc, buddy->name, NULL, group_name);
6258 static void
6259 sipe_buddy_menu_chat_new_cb(PurpleBuddy *buddy)
6261 struct sipe_core_private *sipe_private = PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE;
6263 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_new_cb: buddy->name=%s", buddy->name);
6265 /* 2007+ conference */
6266 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007))
6268 sipe_conf_add(sipe_private, buddy->name);
6270 else /* 2005- multiparty chat */
6272 gchar *self = sip_uri_self(sipe_private);
6273 struct sip_session *session;
6275 session = sipe_session_add_chat(sipe_private,
6276 NULL,
6277 TRUE,
6278 self);
6279 session->chat_session->backend = sipe_backend_chat_create(SIPE_CORE_PUBLIC,
6280 session->chat_session,
6281 session->chat_session->title,
6282 self);
6283 g_free(self);
6285 sipe_invite(sipe_private, session, buddy->name, NULL, NULL, NULL, FALSE);
6290 * For 2007+ conference only.
6292 static void
6293 sipe_buddy_menu_chat_make_leader_cb(PurpleBuddy *buddy,
6294 struct sipe_chat_session *chat_session)
6296 struct sipe_core_private *sipe_private = PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE;
6297 struct sip_session *session;
6299 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_make_leader_cb: buddy->name=%s", buddy->name);
6300 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_make_leader_cb: chat_title=%s", chat_session->title);
6302 session = sipe_session_find_chat(sipe_private, chat_session);
6304 sipe_conf_modify_user_role(sipe_private, session, buddy->name);
6308 * For 2007+ conference only.
6310 static void
6311 sipe_buddy_menu_chat_remove_cb(PurpleBuddy *buddy,
6312 struct sipe_chat_session *chat_session)
6314 struct sipe_core_private *sipe_private = PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE;
6315 struct sip_session *session;
6317 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_remove_cb: buddy->name=%s", buddy->name);
6318 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_remove_cb: chat_title=%s", chat_session->title);
6320 session = sipe_session_find_chat(sipe_private, chat_session);
6322 sipe_conf_delete_user(sipe_private, session, buddy->name);
6325 static void
6326 sipe_buddy_menu_chat_invite_cb(PurpleBuddy *buddy,
6327 struct sipe_chat_session *chat_session)
6329 struct sipe_core_private *sipe_private = PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE;
6331 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_invite_cb: buddy->name=%s", buddy->name);
6332 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_invite_cb: chat_title=%s", chat_session->title);
6334 sipe_core_chat_invite(SIPE_CORE_PUBLIC, chat_session, buddy->name);
6337 static void
6338 sipe_buddy_menu_make_call_cb(PurpleBuddy *buddy, const char *phone)
6340 struct sipe_core_private *sipe_private = PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE;
6342 SIPE_DEBUG_INFO("sipe_buddy_menu_make_call_cb: buddy->name=%s", buddy->name);
6343 if (phone) {
6344 char *tel_uri = sip_to_tel_uri(phone);
6346 SIPE_DEBUG_INFO("sipe_buddy_menu_make_call_cb: going to call number: %s", tel_uri ? tel_uri : "");
6347 sip_csta_make_call(sipe_private, tel_uri);
6349 g_free(tel_uri);
6353 static void
6354 sipe_buddy_menu_access_level_help_cb(PurpleBuddy *buddy)
6356 /** Translators: replace with URL to localized page
6357 * If it doesn't exist copy the original URL */
6358 purple_notify_uri(buddy->account->gc, _("https://sourceforge.net/apps/mediawiki/sipe/index.php?title=Access_Levels"));
6361 static void
6362 sipe_buddy_menu_send_email_cb(PurpleBuddy *buddy)
6364 const gchar *email;
6365 SIPE_DEBUG_INFO("sipe_buddy_menu_send_email_cb: buddy->name=%s", buddy->name);
6367 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
6368 if (email)
6370 char *command_line = g_strdup_printf(
6371 #ifdef _WIN32
6372 "cmd /c start"
6373 #else
6374 "xdg-email"
6375 #endif
6376 " mailto:%s", email);
6377 SIPE_DEBUG_INFO("sipe_buddy_menu_send_email_cb: going to call email client: %s", command_line);
6379 g_spawn_command_line_async(command_line, NULL);
6380 g_free(command_line);
6382 else
6384 SIPE_DEBUG_INFO("sipe_buddy_menu_send_email_cb: no email address stored for buddy=%s", buddy->name);
6388 static void
6389 sipe_buddy_menu_access_level_cb(PurpleBuddy *buddy,
6390 struct sipe_container *container)
6392 struct sipe_core_private *sipe_private = PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE;
6393 struct sipe_container_member *member;
6395 if (!container || !container->members) return;
6397 member = ((struct sipe_container_member *)container->members->data);
6399 if (!member->type) return;
6401 SIPE_DEBUG_INFO("sipe_buddy_menu_access_level_cb: container->id=%d, member->type=%s, member->value=%s",
6402 container->id, member->type, member->value ? member->value : "");
6404 sipe_change_access_level(sipe_private, container->id, member->type, member->value);
6407 static GList *
6408 sipe_get_access_control_menu(struct sipe_core_private *sipe_private,
6409 const char* uri);
6412 * A menu which appear when right-clicking on buddy in contact list.
6414 GList *
6415 sipe_buddy_menu(PurpleBuddy *buddy)
6417 PurpleBlistNode *g_node;
6418 PurpleGroup *gr_parent;
6419 PurpleMenuAction *act;
6420 GList *menu = NULL;
6421 GList *menu_groups = NULL;
6422 struct sipe_core_private *sipe_private = PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE;
6423 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
6424 const char *email;
6425 gchar *self = sip_uri_self(sipe_private);
6427 SIPE_SESSION_FOREACH {
6428 if (!sipe_strcase_equal(self, buddy->name) && session->chat_session)
6430 struct sipe_chat_session *chat_session = session->chat_session;
6431 gboolean is_conf = (chat_session->type == SIPE_CHAT_TYPE_CONFERENCE);
6433 if (sipe_backend_chat_find(chat_session->backend, buddy->name))
6435 gboolean conf_op = sipe_backend_chat_is_operator(chat_session->backend, self);
6437 if (is_conf
6438 && !sipe_backend_chat_is_operator(chat_session->backend, buddy->name) /* Not conf OP */
6439 && conf_op) /* We are a conf OP */
6441 gchar *label = g_strdup_printf(_("Make leader of '%s'"),
6442 chat_session->title);
6443 act = purple_menu_action_new(label,
6444 PURPLE_CALLBACK(sipe_buddy_menu_chat_make_leader_cb),
6445 chat_session, NULL);
6446 g_free(label);
6447 menu = g_list_prepend(menu, act);
6450 if (is_conf
6451 && conf_op) /* We are a conf OP */
6453 gchar *label = g_strdup_printf(_("Remove from '%s'"),
6454 chat_session->title);
6455 act = purple_menu_action_new(label,
6456 PURPLE_CALLBACK(sipe_buddy_menu_chat_remove_cb),
6457 chat_session, NULL);
6458 g_free(label);
6459 menu = g_list_prepend(menu, act);
6462 else
6464 if (!is_conf
6465 || (is_conf && !session->locked))
6467 gchar *label = g_strdup_printf(_("Invite to '%s'"),
6468 chat_session->title);
6469 act = purple_menu_action_new(label,
6470 PURPLE_CALLBACK(sipe_buddy_menu_chat_invite_cb),
6471 chat_session, NULL);
6472 g_free(label);
6473 menu = g_list_prepend(menu, act);
6477 } SIPE_SESSION_FOREACH_END;
6479 act = purple_menu_action_new(_("New chat"),
6480 PURPLE_CALLBACK(sipe_buddy_menu_chat_new_cb),
6481 NULL, NULL);
6482 menu = g_list_prepend(menu, act);
6484 if (sip->csta && !sip->csta->line_status) {
6485 const char *phone;
6486 const char *phone_disp_str;
6487 gchar *tmp = NULL;
6488 /* work phone */
6489 phone = purple_blist_node_get_string(&buddy->node, PHONE_PROP);
6490 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_DISPLAY_PROP);
6491 if (phone) {
6492 gchar *label = g_strdup_printf(_("Work %s"),
6493 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
6494 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
6495 g_free(tmp);
6496 tmp = NULL;
6497 g_free(label);
6498 menu = g_list_prepend(menu, act);
6501 /* mobile phone */
6502 phone = purple_blist_node_get_string(&buddy->node, PHONE_MOBILE_PROP);
6503 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_MOBILE_DISPLAY_PROP);
6504 if (phone) {
6505 gchar *label = g_strdup_printf(_("Mobile %s"),
6506 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
6507 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
6508 g_free(tmp);
6509 tmp = NULL;
6510 g_free(label);
6511 menu = g_list_prepend(menu, act);
6514 /* home phone */
6515 phone = purple_blist_node_get_string(&buddy->node, PHONE_HOME_PROP);
6516 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_HOME_DISPLAY_PROP);
6517 if (phone) {
6518 gchar *label = g_strdup_printf(_("Home %s"),
6519 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
6520 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
6521 g_free(tmp);
6522 tmp = NULL;
6523 g_free(label);
6524 menu = g_list_prepend(menu, act);
6527 /* other phone */
6528 phone = purple_blist_node_get_string(&buddy->node, PHONE_OTHER_PROP);
6529 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_OTHER_DISPLAY_PROP);
6530 if (phone) {
6531 gchar *label = g_strdup_printf(_("Other %s"),
6532 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
6533 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
6534 g_free(tmp);
6535 tmp = NULL;
6536 g_free(label);
6537 menu = g_list_prepend(menu, act);
6540 /* custom1 phone */
6541 phone = purple_blist_node_get_string(&buddy->node, PHONE_CUSTOM1_PROP);
6542 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_CUSTOM1_DISPLAY_PROP);
6543 if (phone) {
6544 gchar *label = g_strdup_printf(_("Custom1 %s"),
6545 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
6546 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
6547 g_free(tmp);
6548 tmp = NULL;
6549 g_free(label);
6550 menu = g_list_prepend(menu, act);
6554 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
6555 if (email) {
6556 act = purple_menu_action_new(_("Send email..."),
6557 PURPLE_CALLBACK(sipe_buddy_menu_send_email_cb),
6558 NULL, NULL);
6559 menu = g_list_prepend(menu, act);
6562 /* Access Level */
6563 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
6564 GList *menu_access_levels = sipe_get_access_control_menu(sipe_private, buddy->name);
6566 act = purple_menu_action_new(_("Access level"),
6567 NULL,
6568 NULL, menu_access_levels);
6569 menu = g_list_prepend(menu, act);
6572 /* Copy to */
6573 gr_parent = purple_buddy_get_group(buddy);
6574 for (g_node = purple_blist_get_root(); g_node; g_node = g_node->next) {
6575 PurpleGroup *group;
6577 if (g_node->type != PURPLE_BLIST_GROUP_NODE)
6578 continue;
6580 group = (PurpleGroup *)g_node;
6581 if (group == gr_parent)
6582 continue;
6584 if (purple_find_buddy_in_group(buddy->account, buddy->name, group))
6585 continue;
6587 act = purple_menu_action_new(purple_group_get_name(group),
6588 PURPLE_CALLBACK(sipe_buddy_menu_copy_to_cb),
6589 group->name, NULL);
6590 menu_groups = g_list_prepend(menu_groups, act);
6592 menu_groups = g_list_reverse(menu_groups);
6594 act = purple_menu_action_new(_("Copy to"),
6595 NULL,
6596 NULL, menu_groups);
6597 menu = g_list_prepend(menu, act);
6599 menu = g_list_reverse(menu);
6601 g_free(self);
6602 return menu;
6605 static void
6606 sipe_ask_access_domain_cb(PurpleConnection *gc, PurpleRequestFields *fields)
6608 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
6609 const char *domain = purple_request_fields_get_string(fields, "access_domain");
6610 int index = purple_request_fields_get_choice(fields, "container_id");
6611 /* move Blocked first */
6612 int i = (index == 4) ? 0 : index + 1;
6613 int container_id = containers[i];
6615 SIPE_DEBUG_INFO("sipe_ask_access_domain_cb: domain=%s, container_id=(%d)%d", domain ? domain : "", index, container_id);
6617 sipe_change_access_level(sipe_private, container_id, "domain", domain);
6620 static void
6621 sipe_ask_access_domain(struct sipe_core_private *sipe_private)
6623 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
6624 PurpleAccount *account = sip->account;
6625 PurpleConnection *gc = sip->gc;
6626 PurpleRequestFields *fields;
6627 PurpleRequestFieldGroup *g;
6628 PurpleRequestField *f;
6630 fields = purple_request_fields_new();
6632 g = purple_request_field_group_new(NULL);
6633 f = purple_request_field_string_new("access_domain", _("Domain"), "partner-company.com", FALSE);
6634 purple_request_field_set_required(f, TRUE);
6635 purple_request_field_group_add_field(g, f);
6637 f = purple_request_field_choice_new("container_id", _("Access level"), 0);
6638 purple_request_field_choice_add(f, _("Personal")); /* index 0 */
6639 purple_request_field_choice_add(f, _("Team"));
6640 purple_request_field_choice_add(f, _("Company"));
6641 purple_request_field_choice_add(f, _("Public"));
6642 purple_request_field_choice_add(f, _("Blocked")); /* index 4 */
6643 purple_request_field_choice_set_default_value(f, 3); /* index */
6644 purple_request_field_set_required(f, TRUE);
6645 purple_request_field_group_add_field(g, f);
6647 purple_request_fields_add_group(fields, g);
6649 purple_request_fields(gc, _("Add new domain"),
6650 _("Add new domain"), NULL, fields,
6651 _("Add"), G_CALLBACK(sipe_ask_access_domain_cb),
6652 _("Cancel"), NULL,
6653 account, NULL, NULL, gc);
6656 static void
6657 sipe_buddy_menu_access_level_add_domain_cb(PurpleBuddy *buddy)
6659 sipe_ask_access_domain(PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE);
6663 * Workaround for missing libpurple API to release resources allocated
6664 * during blist_node_menu() callback. See also:
6666 * <http://developer.pidgin.im/ticket/12597>
6668 * We remember all memory blocks in a list and deallocate them when
6670 * - the next time we enter the callback, or
6671 * - the account is disconnected
6673 * That means that after the buddy menu has been closed we have unused
6674 * resources but at least we don't leak them anymore...
6676 static void
6677 sipe_blist_menu_free_containers(struct sipe_core_private *sipe_private)
6679 GSList *entry = sipe_private->blist_menu_containers;
6680 while (entry) {
6681 free_container(entry->data);
6682 entry = entry->next;
6684 g_slist_free(sipe_private->blist_menu_containers);
6685 sipe_private->blist_menu_containers = NULL;
6688 static void
6689 sipe_blist_menu_remember_container(struct sipe_core_private *sipe_private,
6690 struct sipe_container *container)
6692 sipe_private->blist_menu_containers = g_slist_prepend(sipe_private->blist_menu_containers,
6693 container);
6696 static GList *
6697 sipe_get_access_levels_menu(struct sipe_core_private *sipe_private,
6698 const char* member_type,
6699 const char* member_value,
6700 const gboolean extra_menu)
6702 GList *menu_access_levels = NULL;
6703 unsigned int i;
6704 char *menu_name;
6705 PurpleMenuAction *act;
6706 struct sipe_container *container;
6707 struct sipe_container_member *member;
6708 gboolean is_group_access = FALSE;
6709 int container_id = sipe_find_access_level(sipe_private, member_type, member_value, &is_group_access);
6711 for (i = 1; i <= CONTAINERS_LEN; i++) {
6712 /* to put Blocked level last in menu list.
6713 * Blocked should remaim in the first place in the containers[] array.
6715 unsigned int j = (i == CONTAINERS_LEN) ? 0 : i;
6716 const char *acc_level_name = sipe_get_access_level_name(containers[j]);
6718 container = g_new0(struct sipe_container, 1);
6719 member = g_new0(struct sipe_container_member, 1);
6720 container->id = containers[j];
6721 container->members = g_slist_append(container->members, member);
6722 member->type = g_strdup(member_type);
6723 member->value = g_strdup(member_value);
6725 /* libpurple memory leak workaround */
6726 sipe_blist_menu_remember_container(sipe_private, container);
6728 /* current container/access level */
6729 if (((int)containers[j]) == container_id) {
6730 menu_name = is_group_access ?
6731 g_strdup_printf(INDENT_MARKED_INHERITED_FMT, acc_level_name) :
6732 g_strdup_printf(INDENT_MARKED_FMT, acc_level_name);
6733 } else {
6734 menu_name = g_strdup_printf(INDENT_FMT, acc_level_name);
6737 act = purple_menu_action_new(menu_name,
6738 PURPLE_CALLBACK(sipe_buddy_menu_access_level_cb),
6739 container, NULL);
6740 g_free(menu_name);
6741 menu_access_levels = g_list_prepend(menu_access_levels, act);
6744 if (extra_menu && (container_id >= 0)) {
6745 /* separator */
6746 act = purple_menu_action_new(" --------------", NULL, NULL, NULL);
6747 menu_access_levels = g_list_prepend(menu_access_levels, act);
6749 if (!is_group_access) {
6750 container = g_new0(struct sipe_container, 1);
6751 member = g_new0(struct sipe_container_member, 1);
6752 container->id = -1;
6753 container->members = g_slist_append(container->members, member);
6754 member->type = g_strdup(member_type);
6755 member->value = g_strdup(member_value);
6757 /* libpurple memory leak workaround */
6758 sipe_blist_menu_remember_container(sipe_private, container);
6760 /* Translators: remove (clear) previously assigned access level */
6761 menu_name = g_strdup_printf(INDENT_FMT, _("Unspecify"));
6762 act = purple_menu_action_new(menu_name,
6763 PURPLE_CALLBACK(sipe_buddy_menu_access_level_cb),
6764 container, NULL);
6765 g_free(menu_name);
6766 menu_access_levels = g_list_prepend(menu_access_levels, act);
6770 menu_access_levels = g_list_reverse(menu_access_levels);
6771 return menu_access_levels;
6774 static GList *
6775 sipe_get_access_groups_menu(struct sipe_core_private *sipe_private)
6777 GList *menu_access_groups = NULL;
6778 PurpleMenuAction *act;
6779 GSList *access_domains = NULL;
6780 GSList *entry;
6781 char *menu_name;
6782 char *domain;
6784 act = purple_menu_action_new(_("People in my company"),
6785 NULL,
6786 NULL, sipe_get_access_levels_menu(sipe_private, "sameEnterprise", NULL, FALSE));
6787 menu_access_groups = g_list_prepend(menu_access_groups, act);
6789 /* this is original name, don't edit */
6790 act = purple_menu_action_new(_("People in domains connected with my company"),
6791 NULL,
6792 NULL, sipe_get_access_levels_menu(sipe_private, "federated", NULL, FALSE));
6793 menu_access_groups = g_list_prepend(menu_access_groups, act);
6795 act = purple_menu_action_new(_("People in public domains"),
6796 NULL,
6797 NULL, sipe_get_access_levels_menu(sipe_private, "publicCloud", NULL, TRUE));
6798 menu_access_groups = g_list_prepend(menu_access_groups, act);
6800 access_domains = sipe_get_access_domains(sipe_private);
6801 entry = access_domains;
6802 while (entry) {
6803 domain = entry->data;
6805 menu_name = g_strdup_printf(_("People at %s"), domain);
6806 act = purple_menu_action_new(menu_name,
6807 NULL,
6808 NULL, sipe_get_access_levels_menu(sipe_private, "domain", g_strdup(domain), TRUE));
6809 menu_access_groups = g_list_prepend(menu_access_groups, act);
6810 g_free(menu_name);
6812 entry = entry->next;
6815 /* separator */
6816 /* People in domains connected with my company */
6817 act = purple_menu_action_new("-------------------------------------------", NULL, NULL, NULL);
6818 menu_access_groups = g_list_prepend(menu_access_groups, act);
6820 act = purple_menu_action_new(_("Add new domain..."),
6821 PURPLE_CALLBACK(sipe_buddy_menu_access_level_add_domain_cb),
6822 NULL, NULL);
6823 menu_access_groups = g_list_prepend(menu_access_groups, act);
6825 menu_access_groups = g_list_reverse(menu_access_groups);
6827 return menu_access_groups;
6830 static GList *
6831 sipe_get_access_control_menu(struct sipe_core_private *sipe_private,
6832 const char* uri)
6834 GList *menu_access_levels = NULL;
6835 GList *menu_access_groups = NULL;
6836 char *menu_name;
6837 PurpleMenuAction *act;
6839 /* libpurple memory leak workaround */
6840 sipe_blist_menu_free_containers(sipe_private);
6842 menu_access_levels = sipe_get_access_levels_menu(sipe_private, "user", sipe_get_no_sip_uri(uri), TRUE);
6844 menu_access_groups = sipe_get_access_groups_menu(sipe_private);
6846 menu_name = g_strdup_printf(INDENT_FMT, _("Access groups"));
6847 act = purple_menu_action_new(menu_name,
6848 NULL,
6849 NULL, menu_access_groups);
6850 g_free(menu_name);
6851 menu_access_levels = g_list_append(menu_access_levels, act);
6853 menu_name = g_strdup_printf(INDENT_FMT, _("Online help..."));
6854 act = purple_menu_action_new(menu_name,
6855 PURPLE_CALLBACK(sipe_buddy_menu_access_level_help_cb),
6856 NULL, NULL);
6857 g_free(menu_name);
6858 menu_access_levels = g_list_append(menu_access_levels, act);
6860 return menu_access_levels;
6863 static gboolean
6864 process_get_info_response(struct sipe_core_private *sipe_private,
6865 struct sipmsg *msg, struct transaction *trans)
6867 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
6868 char *uri = trans->payload->data;
6870 PurpleNotifyUserInfo *info;
6871 PurpleBuddy *pbuddy = NULL;
6872 struct sipe_buddy *sbuddy;
6873 const char *alias = NULL;
6874 char *device_name = NULL;
6875 char *server_alias = NULL;
6876 char *phone_number = NULL;
6877 char *email = NULL;
6878 const char *site;
6879 char *first_name = NULL;
6880 char *last_name = NULL;
6882 if (!sip) return FALSE;
6884 SIPE_DEBUG_INFO("Fetching %s's user info for %s", uri, sipe_private->username);
6886 pbuddy = purple_find_buddy((PurpleAccount *)sip->account, uri);
6887 alias = purple_buddy_get_local_alias(pbuddy);
6889 //will query buddy UA's capabilities and send answer to log
6890 sipe_options_request(sipe_private, uri);
6892 sbuddy = g_hash_table_lookup(sipe_private->buddies, uri);
6893 if (sbuddy) {
6894 device_name = sbuddy->device_name ? g_strdup(sbuddy->device_name) : NULL;
6897 info = purple_notify_user_info_new();
6899 if (msg->response != 200) {
6900 SIPE_DEBUG_INFO("process_get_info_response: SERVICE response is %d", msg->response);
6901 } else {
6902 sipe_xml *searchResults;
6903 const sipe_xml *mrow;
6905 SIPE_DEBUG_INFO("process_get_info_response: body:\n%s", msg->body ? msg->body : "");
6906 searchResults = sipe_xml_parse(msg->body, msg->bodylen);
6907 if (!searchResults) {
6908 SIPE_DEBUG_INFO_NOFORMAT("process_get_info_response: no parseable searchResults");
6909 } else if ((mrow = sipe_xml_child(searchResults, "Body/Array/row"))) {
6910 const char *value;
6911 server_alias = g_strdup(sipe_xml_attribute(mrow, "displayName"));
6912 email = g_strdup(sipe_xml_attribute(mrow, "email"));
6913 phone_number = g_strdup(sipe_xml_attribute(mrow, "phone"));
6915 /* For 2007 system we will take this from ContactCard -
6916 * it has cleaner tel: URIs at least
6918 if (!SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
6919 char *tel_uri = sip_to_tel_uri(phone_number);
6920 /* trims its parameters, so call first */
6921 sipe_update_user_info(sipe_private, uri, ALIAS_PROP, server_alias);
6922 sipe_update_user_info(sipe_private, uri, EMAIL_PROP, email);
6923 sipe_update_user_info(sipe_private, uri, PHONE_PROP, tel_uri);
6924 sipe_update_user_info(sipe_private, uri, PHONE_DISPLAY_PROP, phone_number);
6925 g_free(tel_uri);
6928 if (server_alias && strlen(server_alias) > 0) {
6929 purple_notify_user_info_add_pair(info, _("Display name"), server_alias);
6931 if ((value = sipe_xml_attribute(mrow, "title")) && strlen(value) > 0) {
6932 purple_notify_user_info_add_pair(info, _("Job title"), value);
6934 if ((value = sipe_xml_attribute(mrow, "office")) && strlen(value) > 0) {
6935 purple_notify_user_info_add_pair(info, _("Office"), value);
6937 if (phone_number && strlen(phone_number) > 0) {
6938 purple_notify_user_info_add_pair(info, _("Business phone"), phone_number);
6940 if ((value = sipe_xml_attribute(mrow, "company")) && strlen(value) > 0) {
6941 purple_notify_user_info_add_pair(info, _("Company"), value);
6943 if ((value = sipe_xml_attribute(mrow, "city")) && strlen(value) > 0) {
6944 purple_notify_user_info_add_pair(info, _("City"), value);
6946 if ((value = sipe_xml_attribute(mrow, "state")) && strlen(value) > 0) {
6947 purple_notify_user_info_add_pair(info, _("State"), value);
6949 if ((value = sipe_xml_attribute(mrow, "country")) && strlen(value) > 0) {
6950 purple_notify_user_info_add_pair(info, _("Country"), value);
6952 if (email && strlen(email) > 0) {
6953 purple_notify_user_info_add_pair(info, _("Email address"), email);
6957 sipe_xml_free(searchResults);
6960 purple_notify_user_info_add_section_break(info);
6962 if (is_empty(server_alias)) {
6963 g_free(server_alias);
6964 server_alias = g_strdup(purple_buddy_get_server_alias(pbuddy));
6965 if (server_alias) {
6966 purple_notify_user_info_add_pair(info, _("Display name"), server_alias);
6970 /* present alias if it differs from server alias */
6971 if (alias && !sipe_strequal(alias, server_alias))
6973 purple_notify_user_info_add_pair(info, _("Alias"), alias);
6976 if (is_empty(email)) {
6977 g_free(email);
6978 email = g_strdup(purple_blist_node_get_string(&pbuddy->node, EMAIL_PROP));
6979 if (email) {
6980 purple_notify_user_info_add_pair(info, _("Email address"), email);
6984 site = purple_blist_node_get_string(&pbuddy->node, SITE_PROP);
6985 if (site) {
6986 purple_notify_user_info_add_pair(info, _("Site"), site);
6989 sipe_get_first_last_names(sipe_private, uri, &first_name, &last_name);
6990 if (first_name && last_name) {
6991 char *link = g_strconcat("http://www.linkedin.com/pub/dir/", first_name, "/", last_name, NULL);
6993 purple_notify_user_info_add_pair(info, _("Find on LinkedIn"), link);
6994 g_free(link);
6996 g_free(first_name);
6997 g_free(last_name);
6999 if (device_name) {
7000 purple_notify_user_info_add_pair(info, _("Device"), device_name);
7003 /* show a buddy's user info in a nice dialog box */
7004 purple_notify_userinfo(sip->gc, /* connection the buddy info came through */
7005 uri, /* buddy's URI */
7006 info, /* body */
7007 NULL, /* callback called when dialog closed */
7008 NULL); /* userdata for callback */
7010 g_free(phone_number);
7011 g_free(server_alias);
7012 g_free(email);
7013 g_free(device_name);
7015 return TRUE;
7019 * AD search first, LDAP based
7021 void sipe_get_info(PurpleConnection *gc, const char *username)
7023 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
7024 gchar *domain_uri = sip_uri_from_name(sipe_private->public.sip_domain);
7025 char *row = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, "msRTCSIP-PrimaryUserAddress", username);
7026 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 1, row);
7027 struct transaction_payload *payload = g_new0(struct transaction_payload, 1);
7029 payload->destroy = g_free;
7030 payload->data = g_strdup(username);
7032 SIPE_DEBUG_INFO("sipe_get_contact_data: body:\n%s", body ? body : "");
7033 send_soap_request_with_cb(sipe_private, domain_uri, body,
7034 process_get_info_response, payload);
7035 g_free(domain_uri);
7036 g_free(body);
7037 g_free(row);
7041 Local Variables:
7042 mode: c
7043 c-file-style: "bsd"
7044 indent-tabs-mode: t
7045 tab-width: 8
7046 End: