Depurple group rename and group remove
[siplcs.git] / src / core / sipe.c
blob47454b521462a15cf06e8c6169fa8acbb5c97018
1 /**
2 * @file sipe.c
4 * pidgin-sipe
6 * Copyright (C) 2010-11 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 <libintl.h>
51 #include "sipe-common.h"
53 #include "account.h"
54 #include "blist.h"
55 #include "connection.h"
56 #include "conversation.h"
57 #include "ft.h"
58 #include "notify.h"
59 #include "plugin.h"
60 #include "privacy.h"
61 #include "request.h"
62 #include "savedstatuses.h"
64 #include "core-depurple.h" /* Temporary for the core de-purple transition */
66 #include "http-conn.h"
67 #include "sipmsg.h"
68 #include "sip-csta.h"
69 #include "sip-transport.h"
70 #include "sipe-backend.h"
71 #include "sipe-buddy.h"
72 #include "sipe-cal.h"
73 #include "sipe-chat.h"
74 #include "sipe-conf.h"
75 #include "sipe-core.h"
76 #include "sipe-core-private.h"
77 #include "sipe-group.h"
78 #include "sipe-dialog.h"
79 #include "sipe-ews.h"
80 #include "sipe-domino.h"
81 #include "sipe-groupchat.h"
82 #include "sipe-im.h"
83 #include "sipe-mime.h"
84 #include "sipe-nls.h"
85 #include "sipe-schedule.h"
86 #include "sipe-session.h"
87 #include "sipe-subscriptions.h"
88 #ifdef HAVE_VV
89 #include "sipe-media.h"
90 #endif
91 #include "sipe-utils.h"
92 #include "sipe-xml.h"
93 #include "uuid.h"
94 #include "sipe.h"
96 #define SIPE_IDLE_SET_DELAY 1 /* 1 sec */
98 #define UPDATE_CALENDAR_DELAY 1*60 /* 1 min */
99 #define UPDATE_CALENDAR_INTERVAL 30*60 /* 30 min */
101 /* Status identifiers (see also: sipe_status_types()) */
102 #define SIPE_STATUS_ID_UNKNOWN purple_primitive_get_id_from_type(PURPLE_STATUS_UNSET) /* Unset (primitive) */
103 #define SIPE_STATUS_ID_OFFLINE purple_primitive_get_id_from_type(PURPLE_STATUS_OFFLINE) /* Offline (primitive) */
104 #define SIPE_STATUS_ID_AVAILABLE purple_primitive_get_id_from_type(PURPLE_STATUS_AVAILABLE) /* Online */
105 /* PURPLE_STATUS_UNAVAILABLE: */
106 #define SIPE_STATUS_ID_BUSY "busy" /* Busy */
107 #define SIPE_STATUS_ID_BUSYIDLE "busyidle" /* BusyIdle */
108 #define SIPE_STATUS_ID_DND "do-not-disturb" /* Do Not Disturb */
109 #define SIPE_STATUS_ID_IN_MEETING "in-a-meeting" /* In a meeting */
110 #define SIPE_STATUS_ID_IN_CONF "in-a-conference" /* In a conference */
111 #define SIPE_STATUS_ID_ON_PHONE "on-the-phone" /* On the phone */
112 #define SIPE_STATUS_ID_INVISIBLE purple_primitive_get_id_from_type(PURPLE_STATUS_INVISIBLE) /* Appear Offline */
113 /* PURPLE_STATUS_AWAY: */
114 #define SIPE_STATUS_ID_IDLE "idle" /* Idle/Inactive */
115 #define SIPE_STATUS_ID_BRB "be-right-back" /* Be Right Back */
116 #define SIPE_STATUS_ID_AWAY purple_primitive_get_id_from_type(PURPLE_STATUS_AWAY) /* Away (primitive) */
117 /** Reuters status (user settable) */
118 #define SIPE_STATUS_ID_LUNCH "out-to-lunch" /* Out To Lunch */
119 /* ??? PURPLE_STATUS_EXTENDED_AWAY */
120 /* ??? PURPLE_STATUS_MOBILE */
121 /* ??? PURPLE_STATUS_TUNE */
123 /* Status attributes (see also sipe_status_types() */
124 #define SIPE_STATUS_ATTR_ID_MESSAGE "message"
126 static struct sipe_activity_map_struct
128 sipe_activity type;
129 const char *token;
130 const char *desc;
131 const char *status_id;
133 } const sipe_activity_map[] =
135 /* This has nothing to do with Availability numbers, like 3500 (online).
136 * Just a mapping of Communicator Activities to Purple statuses to be able display them in Pidgin.
138 { SIPE_ACTIVITY_UNSET, "unset", NULL , NULL },
139 { SIPE_ACTIVITY_ONLINE, "online", NULL , NULL },
140 { SIPE_ACTIVITY_INACTIVE, SIPE_STATUS_ID_IDLE, N_("Inactive") , NULL },
141 { SIPE_ACTIVITY_BUSY, SIPE_STATUS_ID_BUSY, N_("Busy") , SIPE_STATUS_ID_BUSY },
142 { SIPE_ACTIVITY_BUSYIDLE, SIPE_STATUS_ID_BUSYIDLE, N_("Busy-Idle") , NULL },
143 { SIPE_ACTIVITY_DND, SIPE_STATUS_ID_DND, NULL , SIPE_STATUS_ID_DND },
144 { SIPE_ACTIVITY_BRB, SIPE_STATUS_ID_BRB, N_("Be right back") , SIPE_STATUS_ID_BRB },
145 { SIPE_ACTIVITY_AWAY, "away", NULL , NULL },
146 { SIPE_ACTIVITY_LUNCH, SIPE_STATUS_ID_LUNCH, N_("Out to lunch") , NULL },
147 { SIPE_ACTIVITY_OFFLINE, "offline", NULL , NULL },
148 { SIPE_ACTIVITY_ON_PHONE, SIPE_STATUS_ID_ON_PHONE, N_("In a call") , NULL },
149 { SIPE_ACTIVITY_IN_CONF, SIPE_STATUS_ID_IN_CONF, N_("In a conference") , NULL },
150 { SIPE_ACTIVITY_IN_MEETING, SIPE_STATUS_ID_IN_MEETING, N_("In a meeting") , NULL },
151 { SIPE_ACTIVITY_OOF, "out-of-office", N_("Out of office") , NULL },
152 { SIPE_ACTIVITY_URGENT_ONLY, "urgent-interruptions-only", N_("Urgent interruptions only") , NULL }
154 /** @param x is sipe_activity */
155 #define SIPE_ACTIVITY_I18N(x) gettext(sipe_activity_map[x].desc)
157 static sipe_activity
158 sipe_get_activity_by_token(const char *token)
160 int i;
162 for (i = 0; i < SIPE_ACTIVITY_NUM_TYPES; i++)
164 if (sipe_strequal(token, sipe_activity_map[i].token))
165 return sipe_activity_map[i].type;
168 return sipe_activity_map[0].type;
171 static const char *
172 sipe_get_activity_desc_by_token(const char *token)
174 if (!token) return NULL;
176 return SIPE_ACTIVITY_I18N(sipe_get_activity_by_token(token));
179 static void send_presence_status(struct sipe_core_private *sipe_private,
180 void *unused);
183 * @param from0 from URI (with 'sip:' prefix). Will be filled with self-URI if NULL passed.
185 void
186 send_soap_request_with_cb(struct sipe_core_private *sipe_private,
187 gchar *from0,
188 gchar *body,
189 TransCallback callback,
190 struct transaction_payload *payload)
192 gchar *from = from0 ? g_strdup(from0) : sip_uri_self(sipe_private);
193 gchar *contact = get_contact(sipe_private);
194 gchar *hdr = g_strdup_printf("Contact: %s\r\n"
195 "Content-Type: application/SOAP+xml\r\n",contact);
197 struct transaction *trans = sip_transport_service(sipe_private,
198 from,
199 hdr,
200 body,
201 callback);
202 trans->payload = payload;
204 g_free(from);
205 g_free(contact);
206 g_free(hdr);
209 void
210 send_soap_request(struct sipe_core_private *sipe_private,
211 gchar *body)
213 send_soap_request_with_cb(sipe_private, NULL, body, NULL, NULL);
217 * Returns pointer to URI without sip: prefix if any
219 * @param sip_uri SIP URI possibly with sip: prefix. Example: sip:first.last@hq.company.com
220 * @return pointer to URL without sip: prefix. Coresponding example: first.last@hq.company.com
222 * Doesn't allocate memory
224 static const char *
225 sipe_get_no_sip_uri(const char *sip_uri)
227 const char *prefix = "sip:";
228 if (!sip_uri) return NULL;
230 if (g_str_has_prefix(sip_uri, prefix)) {
231 return (sip_uri+strlen(prefix));
232 } else {
233 return sip_uri;
237 static void
238 sipe_contact_set_acl (struct sipe_core_private *sipe_private,
239 const gchar *who,
240 gchar *rights)
242 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
243 gchar * body = g_strdup_printf(SIPE_SOAP_ALLOW_DENY, who, rights, sip->acl_delta++);
244 send_soap_request(sipe_private, body);
245 g_free(body);
248 static void
249 sipe_change_access_level(struct sipe_core_private *sipe_private,
250 const int container_id,
251 const gchar *type,
252 const gchar *value);
254 void
255 sipe_core_contact_allow_deny (struct sipe_core_public *sipe_public,
256 const gchar * who, gboolean allow)
258 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
260 if (allow) {
261 SIPE_DEBUG_INFO("Authorizing contact %s", who);
262 } else {
263 SIPE_DEBUG_INFO("Blocking contact %s", who);
266 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
267 sipe_change_access_level(sipe_private, (allow ? -1 : 32000), "user", sipe_get_no_sip_uri(who));
268 } else {
269 sipe_contact_set_acl(sipe_private, who, allow ? "AA" : "BD");
273 static
274 void sipe_auth_user_cb(void * data)
276 struct sipe_auth_job * job = (struct sipe_auth_job *) data;
277 if (!job) return;
279 sipe_core_contact_allow_deny((struct sipe_core_public *)job->sipe_private, job->who, TRUE);
280 g_free(job);
283 static
284 void sipe_deny_user_cb(void * data)
286 struct sipe_auth_job * job = (struct sipe_auth_job *) data;
287 if (!job) return;
289 sipe_core_contact_allow_deny((struct sipe_core_public *)job->sipe_private, job->who, FALSE);
290 g_free(job);
293 /** @applicable: 2005-
295 static void
296 sipe_process_presence_wpending (struct sipe_core_private *sipe_private,
297 struct sipmsg * msg)
299 sipe_xml *watchers;
300 const sipe_xml *watcher;
301 // Ensure it's either not a response (eg it's a BENOTIFY) or that it's a 200 OK response
302 if (msg->response != 0 && msg->response != 200) return;
304 if (msg->bodylen == 0 || msg->body == NULL || sipe_strequal(sipmsg_find_header(msg, "Event"), "msrtc.wpending")) return;
306 watchers = sipe_xml_parse(msg->body, msg->bodylen);
307 if (!watchers) return;
309 for (watcher = sipe_xml_child(watchers, "watcher"); watcher; watcher = sipe_xml_twin(watcher)) {
310 gchar * remote_user = g_strdup(sipe_xml_attribute(watcher, "uri"));
311 gchar * alias = g_strdup(sipe_xml_attribute(watcher, "displayName"));
312 gboolean on_list = g_hash_table_lookup(sipe_private->buddies, remote_user) != NULL;
314 // TODO pull out optional displayName to pass as alias
315 if (remote_user) {
316 struct sipe_auth_job * job = g_new0(struct sipe_auth_job, 1);
317 job->who = remote_user;
318 job->sipe_private = sipe_private;
319 sipe_backend_buddy_request_authorization(SIPE_CORE_PUBLIC,
320 remote_user,
321 alias,
322 on_list,
323 sipe_auth_user_cb,
324 sipe_deny_user_cb,
325 (gpointer)job);
330 sipe_xml_free(watchers);
331 return;
335 * Only appends if no such value already stored.
336 * Like Set in Java.
338 GSList * slist_insert_unique_sorted(GSList *list, gpointer data, GCompareFunc func) {
339 GSList * res = list;
340 if (!g_slist_find_custom(list, data, func)) {
341 res = g_slist_insert_sorted(list, data, func);
343 return res;
347 * Returns string like "2 4 7 8" - group ids buddy belong to.
349 static gchar *
350 sipe_get_buddy_groups_string (struct sipe_buddy *buddy) {
351 int i = 0;
352 gchar *res;
353 //creating array from GList, converting int to gchar*
354 gchar **ids_arr = g_new(gchar *, g_slist_length(buddy->groups) + 1);
355 GSList *entry = buddy->groups;
357 if (!ids_arr) return NULL;
359 while (entry) {
360 struct sipe_group * group = entry->data;
361 ids_arr[i] = g_strdup_printf("%d", group->id);
362 entry = entry->next;
363 i++;
365 ids_arr[i] = NULL;
366 res = g_strjoinv(" ", ids_arr);
367 g_strfreev(ids_arr);
368 return res;
372 * Sends buddy update to server
374 void
375 sipe_core_group_set_user(struct sipe_core_public *sipe_public, const gchar * who)
377 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA;
378 struct sipe_buddy *buddy = g_hash_table_lookup(SIPE_CORE_PRIVATE->buddies, who);
379 sipe_backend_buddy backend_buddy = sipe_backend_buddy_find(sipe_public, who, NULL);
381 if (buddy && backend_buddy) {
382 gchar *alias = sipe_backend_buddy_get_alias(sipe_public, backend_buddy);
383 gchar *groups = sipe_get_buddy_groups_string(buddy);
384 if (groups) {
385 gchar *body;
386 SIPE_DEBUG_INFO("Saving buddy %s with alias %s and groups %s", who, alias, groups);
388 body = g_markup_printf_escaped(SIPE_SOAP_SET_CONTACT,
389 alias, groups, "true", buddy->name, sip->contacts_delta++
391 send_soap_request(SIPE_CORE_PRIVATE, body);
392 g_free(groups);
393 g_free(body);
395 g_free(alias);
399 static void
400 sipe_sched_calendar_status_update(struct sipe_core_private *sipe_private,
401 time_t calculate_from);
403 static int
404 sipe_get_availability_by_status(const char* sipe_status_id, char** activity_token);
406 static const char*
407 sipe_get_status_by_availability(int avail,
408 char** activity);
410 static void
411 sipe_set_purple_account_status_and_note(const PurpleAccount *account,
412 const char *status_id,
413 const char *message,
414 time_t do_not_publish[]);
416 static void
417 sipe_apply_calendar_status(struct sipe_core_private *sipe_private,
418 struct sipe_buddy *sbuddy,
419 const char *status_id)
421 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
422 time_t cal_avail_since;
423 int cal_status = sipe_cal_get_status(sbuddy, time(NULL), &cal_avail_since);
424 int avail;
425 gchar *self_uri;
427 if (!sbuddy) return;
429 if (cal_status < SIPE_CAL_NO_DATA) {
430 SIPE_DEBUG_INFO("sipe_apply_calendar_status: cal_status : %d for %s", cal_status, sbuddy->name);
431 SIPE_DEBUG_INFO("sipe_apply_calendar_status: cal_avail_since : %s", asctime(localtime(&cal_avail_since)));
434 /* scheduled Cal update call */
435 if (!status_id) {
436 status_id = sbuddy->last_non_cal_status_id;
437 g_free(sbuddy->activity);
438 sbuddy->activity = g_strdup(sbuddy->last_non_cal_activity);
441 if (!status_id) {
442 SIPE_DEBUG_INFO("sipe_apply_calendar_status: status_id is NULL for %s, exiting.",
443 sbuddy->name ? sbuddy->name : "" );
444 return;
447 /* adjust to calendar status */
448 if (cal_status != SIPE_CAL_NO_DATA) {
449 SIPE_DEBUG_INFO("sipe_apply_calendar_status: user_avail_since: %s", asctime(localtime(&sbuddy->user_avail_since)));
451 if (cal_status == SIPE_CAL_BUSY
452 && cal_avail_since > sbuddy->user_avail_since
453 && 6500 >= sipe_get_availability_by_status(status_id, NULL))
455 status_id = SIPE_STATUS_ID_BUSY;
456 g_free(sbuddy->activity);
457 sbuddy->activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_IN_MEETING));
459 avail = sipe_get_availability_by_status(status_id, NULL);
461 SIPE_DEBUG_INFO("sipe_apply_calendar_status: activity_since : %s", asctime(localtime(&sbuddy->activity_since)));
462 if (cal_avail_since > sbuddy->activity_since) {
463 if (cal_status == SIPE_CAL_OOF
464 && avail >= 15000) /* 12000 in 2007 */
466 g_free(sbuddy->activity);
467 sbuddy->activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_OOF));
472 /* then set status_id actually */
473 SIPE_DEBUG_INFO("sipe_apply_calendar_status: to %s for %s", status_id, sbuddy->name ? sbuddy->name : "" );
474 sipe_backend_buddy_set_status(SIPE_CORE_PUBLIC, sbuddy->name, status_id);
476 /* set our account state to the one in roaming (including calendar info) */
477 self_uri = sip_uri_self(sipe_private);
478 if (sip->initial_state_published && sipe_strcase_equal(sbuddy->name, self_uri)) {
479 if (sipe_strequal(status_id, SIPE_STATUS_ID_OFFLINE)) {
480 status_id = g_strdup(SIPE_STATUS_ID_INVISIBLE); /* not not let offline status switch us off */
483 SIPE_DEBUG_INFO("sipe_apply_calendar_status: switch to '%s' for the account", sip->status);
484 sipe_set_purple_account_status_and_note(sip->account, status_id, sip->note, sip->do_not_publish);
486 g_free(self_uri);
489 void
490 sipe_core_buddy_got_status(struct sipe_core_public *sipe_public,
491 const gchar* uri,
492 const gchar *status_id)
494 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
495 struct sipe_buddy *sbuddy = g_hash_table_lookup(sipe_private->buddies, uri);
497 if (!sbuddy) return;
499 /* Check if on 2005 system contact's calendar,
500 * then set/preserve it.
502 if (!SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
503 sipe_apply_calendar_status(sipe_private, sbuddy, status_id);
504 } else {
505 sipe_backend_buddy_set_status(sipe_public, uri, status_id);
509 static void
510 update_calendar_status_cb(SIPE_UNUSED_PARAMETER char *name,
511 struct sipe_buddy *sbuddy,
512 struct sipe_core_private *sipe_private)
514 sipe_apply_calendar_status(sipe_private, sbuddy, NULL);
518 * Updates contact's status
519 * based on their calendar information.
521 * Applicability: 2005 systems
523 static void
524 update_calendar_status(struct sipe_core_private *sipe_private,
525 SIPE_UNUSED_PARAMETER void *unused)
527 SIPE_DEBUG_INFO_NOFORMAT("update_calendar_status() started.");
528 g_hash_table_foreach(sipe_private->buddies, (GHFunc)update_calendar_status_cb, sipe_private);
530 /* repeat scheduling */
531 sipe_sched_calendar_status_update(sipe_private, time(NULL) + 3*60 /* 3 min */);
535 * Schedules process of contacts' status update
536 * based on their calendar information.
537 * Should be scheduled to the beginning of every
538 * 15 min interval, like:
539 * 13:00, 13:15, 13:30, 13:45, etc.
541 * Applicability: 2005 systems
543 static void
544 sipe_sched_calendar_status_update(struct sipe_core_private *sipe_private,
545 time_t calculate_from)
547 int interval = 15*60;
548 /** start of the beginning of closest 15 min interval. */
549 time_t next_start = ((time_t)((int)((int)calculate_from)/interval + 1)*interval);
551 SIPE_DEBUG_INFO("sipe_sched_calendar_status_update: calculate_from time: %s",
552 asctime(localtime(&calculate_from)));
553 SIPE_DEBUG_INFO("sipe_sched_calendar_status_update: next start time : %s",
554 asctime(localtime(&next_start)));
556 sipe_schedule_seconds(sipe_private,
557 "<+2005-cal-status>",
558 NULL,
559 next_start - time(NULL),
560 update_calendar_status,
561 NULL);
565 * Schedules process of self status publish
566 * based on own calendar information.
567 * Should be scheduled to the beginning of every
568 * 15 min interval, like:
569 * 13:00, 13:15, 13:30, 13:45, etc.
571 * Applicability: 2007+ systems
573 static void
574 sipe_sched_calendar_status_self_publish(struct sipe_core_private *sipe_private,
575 time_t calculate_from)
577 int interval = 5*60;
578 /** start of the beginning of closest 5 min interval. */
579 time_t next_start = ((time_t)((int)((int)calculate_from)/interval + 1)*interval);
581 SIPE_DEBUG_INFO("sipe_sched_calendar_status_self_publish: calculate_from time: %s",
582 asctime(localtime(&calculate_from)));
583 SIPE_DEBUG_INFO("sipe_sched_calendar_status_self_publish: next start time : %s",
584 asctime(localtime(&next_start)));
586 sipe_schedule_seconds(sipe_private,
587 "<+2007-cal-status>",
588 NULL,
589 next_start - time(NULL),
590 publish_calendar_status_self,
591 NULL);
594 static void sipe_subscribe_resource_uri(const char *name,
595 SIPE_UNUSED_PARAMETER gpointer value,
596 gchar **resources_uri)
598 gchar *tmp = *resources_uri;
599 *resources_uri = g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp, name);
600 g_free(tmp);
603 static void sipe_subscribe_resource_uri_with_context(const char *name, gpointer value, gchar **resources_uri)
605 struct sipe_buddy *sbuddy = (struct sipe_buddy *)value;
606 gchar *context = sbuddy && sbuddy->just_added ? "><context/></resource>" : "/>";
607 gchar *tmp = *resources_uri;
609 if (sbuddy) sbuddy->just_added = FALSE; /* should be enought to include context one time */
611 *resources_uri = g_strdup_printf("%s<resource uri=\"%s\"%s\n", tmp, name, context);
612 g_free(tmp);
616 * Support for Batch Category SUBSCRIBE [MS-PRES] - msrtc-event-categories+xml OCS 2007
617 * Support for Batch Category SUBSCRIBE [MS-SIP] - adrl+xml LCS 2005
618 * The user sends an initial batched category SUBSCRIBE request against all contacts on his roaming list in only a request
619 * A batch category SUBSCRIBE request MUST have the same To-URI and From-URI.
620 * This header will be send only if adhoclist there is a "Supported: adhoclist" in REGISTER answer else will be send a Single Category SUBSCRIBE
623 static void sipe_subscribe_presence_batched_to(struct sipe_core_private *sipe_private,
624 gchar *resources_uri,
625 gchar *to)
627 gchar *contact = get_contact(sipe_private);
628 gchar *request;
629 gchar *content;
630 gchar *require = "";
631 gchar *accept = "";
632 gchar *autoextend = "";
633 gchar *content_type;
635 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
636 require = ", categoryList";
637 accept = ", application/msrtc-event-categories+xml, application/xpidf+xml, application/pidf+xml";
638 content_type = "application/msrtc-adrl-categorylist+xml";
639 content = g_strdup_printf(
640 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
641 "<action name=\"subscribe\" id=\"63792024\">\n"
642 "<adhocList>\n%s</adhocList>\n"
643 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
644 "<category name=\"calendarData\"/>\n"
645 "<category name=\"contactCard\"/>\n"
646 "<category name=\"note\"/>\n"
647 "<category name=\"state\"/>\n"
648 "</categoryList>\n"
649 "</action>\n"
650 "</batchSub>", sipe_private->username, resources_uri);
651 } else {
652 autoextend = "Supported: com.microsoft.autoextend\r\n";
653 content_type = "application/adrl+xml";
654 content = g_strdup_printf(
655 "<adhoclist xmlns=\"urn:ietf:params:xml:ns:adrl\" uri=\"sip:%s\" name=\"sip:%s\">\n"
656 "<create xmlns=\"\">\n%s</create>\n"
657 "</adhoclist>\n", sipe_private->username, sipe_private->username, resources_uri);
659 g_free(resources_uri);
661 request = g_strdup_printf(
662 "Require: adhoclist%s\r\n"
663 "Supported: eventlist\r\n"
664 "Accept: application/rlmi+xml, multipart/related, text/xml+msrtc.pidf%s\r\n"
665 "Supported: ms-piggyback-first-notify\r\n"
666 "%sSupported: ms-benotify\r\n"
667 "Proxy-Require: ms-benotify\r\n"
668 "Event: presence\r\n"
669 "Content-Type: %s\r\n"
670 "Contact: %s\r\n", require, accept, autoextend, content_type, contact);
671 g_free(contact);
673 sipe_subscribe_presence_buddy(sipe_private, to, request, content);
675 g_free(content);
676 g_free(to);
677 g_free(request);
680 static void sipe_subscribe_presence_batched(struct sipe_core_private *sipe_private,
681 SIPE_UNUSED_PARAMETER void *unused)
683 gchar *to = sip_uri_self(sipe_private);
684 gchar *resources_uri = g_strdup("");
685 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
686 g_hash_table_foreach(sipe_private->buddies, (GHFunc) sipe_subscribe_resource_uri_with_context , &resources_uri);
687 } else {
688 g_hash_table_foreach(sipe_private->buddies, (GHFunc) sipe_subscribe_resource_uri, &resources_uri);
691 sipe_subscribe_presence_batched_to(sipe_private, resources_uri, to);
694 struct presence_batched_routed {
695 gchar *host;
696 GSList *buddies;
699 static void sipe_subscribe_presence_batched_routed_free(void *payload)
701 struct presence_batched_routed *data = payload;
702 GSList *buddies = data->buddies;
703 while (buddies) {
704 g_free(buddies->data);
705 buddies = buddies->next;
707 g_slist_free(data->buddies);
708 g_free(data->host);
709 g_free(payload);
712 static void sipe_subscribe_presence_batched_routed(struct sipe_core_private *sipe_private,
713 void *payload)
715 struct presence_batched_routed *data = payload;
716 GSList *buddies = data->buddies;
717 gchar *resources_uri = g_strdup("");
718 while (buddies) {
719 gchar *tmp = resources_uri;
720 resources_uri = g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp, (char *) buddies->data);
721 g_free(tmp);
722 buddies = buddies->next;
724 sipe_subscribe_presence_batched_to(sipe_private, resources_uri,
725 g_strdup(data->host));
729 * Single Category SUBSCRIBE [MS-PRES] ; To send when the server returns a 200 OK message with state="resubscribe" in response.
730 * The user sends a single SUBSCRIBE request to the subscribed contact.
731 * The To-URI and the URI listed in the resource list MUST be the same for a single category SUBSCRIBE request.
735 static void sipe_subscribe_presence_single(struct sipe_core_private *sipe_private,
736 void *buddy_name)
738 gchar *to = sip_uri((char *)buddy_name);
739 gchar *tmp = get_contact(sipe_private);
740 gchar *request;
741 gchar *content = NULL;
742 gchar *autoextend = "";
743 gchar *content_type = "";
744 struct sipe_buddy *sbuddy = g_hash_table_lookup(sipe_private->buddies, to);
745 gchar *context = sbuddy && sbuddy->just_added ? "><context/></resource>" : "/>";
747 if (sbuddy) sbuddy->just_added = FALSE;
749 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
750 content_type = "Content-Type: application/msrtc-adrl-categorylist+xml\r\n";
751 } else {
752 autoextend = "Supported: com.microsoft.autoextend\r\n";
755 request = g_strdup_printf(
756 "Accept: application/msrtc-event-categories+xml, text/xml+msrtc.pidf, application/xpidf+xml, application/pidf+xml, application/rlmi+xml, multipart/related\r\n"
757 "Supported: ms-piggyback-first-notify\r\n"
758 "%s%sSupported: ms-benotify\r\n"
759 "Proxy-Require: ms-benotify\r\n"
760 "Event: presence\r\n"
761 "Contact: %s\r\n", autoextend, content_type, tmp);
763 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
764 content = g_strdup_printf(
765 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
766 "<action name=\"subscribe\" id=\"63792024\"><adhocList>\n"
767 "<resource uri=\"%s\"%s\n"
768 "</adhocList>\n"
769 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
770 "<category name=\"calendarData\"/>\n"
771 "<category name=\"contactCard\"/>\n"
772 "<category name=\"note\"/>\n"
773 "<category name=\"state\"/>\n"
774 "</categoryList>\n"
775 "</action>\n"
776 "</batchSub>", sipe_private->username, to, context);
779 g_free(tmp);
781 sipe_subscribe_presence_buddy(sipe_private, to, request, content);
783 g_free(content);
784 g_free(to);
785 g_free(request);
788 void sipe_set_status(PurpleAccount *account, PurpleStatus *status)
790 SIPE_DEBUG_INFO("sipe_set_status: status=%s", purple_status_get_id(status));
792 if (!purple_status_is_active(status))
793 return;
795 if (account->gc) {
796 struct sipe_core_private *sipe_private = PURPLE_ACCOUNT_TO_SIPE_CORE_PRIVATE;
797 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
799 if (sip) {
800 gchar *action_name;
801 gchar *tmp;
802 time_t now = time(NULL);
803 const char *status_id = purple_status_get_id(status);
804 const char *note = purple_status_get_attr_string(status, SIPE_STATUS_ATTR_ID_MESSAGE);
805 sipe_activity activity = sipe_get_activity_by_token(status_id);
806 gboolean do_not_publish = ((now - sip->do_not_publish[activity]) <= 2);
808 /* when other point of presence clears note, but we are keeping
809 * state if OOF note.
811 if (do_not_publish && !note && sip->cal && sip->cal->oof_note) {
812 SIPE_DEBUG_INFO_NOFORMAT("sipe_set_status: enabling publication as OOF note keepers.");
813 do_not_publish = FALSE;
816 SIPE_DEBUG_INFO("sipe_set_status: was: sip->do_not_publish[%s]=%d [?] now(time)=%d",
817 status_id, (int)sip->do_not_publish[activity], (int)now);
819 sip->do_not_publish[activity] = 0;
820 SIPE_DEBUG_INFO("sipe_set_status: set: sip->do_not_publish[%s]=%d [0]",
821 status_id, (int)sip->do_not_publish[activity]);
823 if (do_not_publish)
825 SIPE_DEBUG_INFO_NOFORMAT("sipe_set_status: publication was switched off, exiting.");
826 return;
829 g_free(sip->status);
830 sip->status = g_strdup(status_id);
832 /* hack to escape apostrof before comparison */
833 tmp = note ? sipe_utils_str_replace(note, "'", "&apos;") : NULL;
835 /* this will preserve OOF flag as well */
836 if (!sipe_strequal(tmp, sip->note)) {
837 sip->is_oof_note = FALSE;
838 g_free(sip->note);
839 sip->note = g_strdup(note);
840 sip->note_since = time(NULL);
842 g_free(tmp);
844 /* schedule 2 sec to capture idle flag */
845 action_name = g_strdup_printf("<%s>", "+set-status");
846 sipe_schedule_seconds(sipe_private,
847 action_name,
848 NULL,
849 SIPE_IDLE_SET_DELAY,
850 send_presence_status,
851 NULL);
852 g_free(action_name);
857 void
858 sipe_set_idle(PurpleConnection * gc,
859 int interval)
861 SIPE_DEBUG_INFO("sipe_set_idle: interval=%d", interval);
863 if (gc) {
864 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
865 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
867 if (sip) {
868 sip->idle_switch = time(NULL);
869 SIPE_DEBUG_INFO("sipe_set_idle: sip->idle_switch : %s", asctime(localtime(&(sip->idle_switch))));
874 void
875 sipe_group_buddy(PurpleConnection *gc,
876 const char *who,
877 const char *old_group_name,
878 const char *new_group_name)
880 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
881 struct sipe_buddy * buddy = g_hash_table_lookup(sipe_private->buddies, who);
882 struct sipe_group * old_group = NULL;
883 struct sipe_group * new_group;
885 SIPE_DEBUG_INFO("sipe_group_buddy[CB]: who:%s old_group_name:%s new_group_name:%s",
886 who ? who : "", old_group_name ? old_group_name : "", new_group_name ? new_group_name : "");
888 if(!buddy) { // buddy not in roaming list
889 return;
892 if (old_group_name) {
893 old_group = sipe_group_find_by_name(sipe_private, old_group_name);
895 new_group = sipe_group_find_by_name(sipe_private, new_group_name);
897 if (old_group) {
898 buddy->groups = g_slist_remove(buddy->groups, old_group);
899 SIPE_DEBUG_INFO("buddy %s removed from old group %s", who, old_group_name);
902 if (!new_group) {
903 sipe_group_create(sipe_private, new_group_name, who);
904 } else {
905 buddy->groups = slist_insert_unique_sorted(buddy->groups, new_group, (GCompareFunc)sipe_group_compare);
906 sipe_core_group_set_user(SIPE_CORE_PUBLIC, who);
910 void sipe_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
912 SIPE_DEBUG_INFO("sipe_add_buddy[CB]: buddy:%s group:%s", buddy ? buddy->name : "", group ? group->name : "");
914 /* libpurple can call us with undefined buddy or group */
915 if (buddy && group) {
916 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
918 /* Buddy name must be lower case as we use purple_normalize_nocase() to compare */
919 gchar *buddy_name = g_ascii_strdown(buddy->name, -1);
920 purple_blist_rename_buddy(buddy, buddy_name);
921 g_free(buddy_name);
923 /* Prepend sip: if needed */
924 if (!g_str_has_prefix(buddy->name, "sip:")) {
925 gchar *buf = sip_uri_from_name(buddy->name);
926 purple_blist_rename_buddy(buddy, buf);
927 g_free(buf);
930 if (!g_hash_table_lookup(sipe_private->buddies, buddy->name)) {
931 struct sipe_buddy *b = g_new0(struct sipe_buddy, 1);
932 SIPE_DEBUG_INFO("sipe_add_buddy: adding %s", buddy->name);
933 b->name = g_strdup(buddy->name);
934 b->just_added = TRUE;
935 g_hash_table_insert(sipe_private->buddies, b->name, b);
936 /* @TODO should go to callback */
937 sipe_subscribe_presence_single(sipe_private,
938 b->name);
939 } else {
940 SIPE_DEBUG_INFO("sipe_add_buddy: buddy %s already in internal list", buddy->name);
943 sipe_group_buddy(gc, buddy->name, NULL, group->name);
947 static void sipe_free_buddy(struct sipe_buddy *buddy)
949 #ifndef _WIN32
951 * We are calling g_hash_table_foreach_steal(). That means that no
952 * key/value deallocation functions are called. Therefore the glib
953 * hash code does not touch the key (buddy->name) or value (buddy)
954 * of the to-be-deleted hash node at all. It follows that we
956 * - MUST free the memory for the key ourselves and
957 * - ARE allowed to do it in this function
959 * Conclusion: glib must be broken on the Windows platform if sipe
960 * crashes with SIGTRAP when closing. You'll have to live
961 * with the memory leak until this is fixed.
963 g_free(buddy->name);
964 #endif
965 g_free(buddy->activity);
966 g_free(buddy->meeting_subject);
967 g_free(buddy->meeting_location);
968 g_free(buddy->note);
970 g_free(buddy->cal_start_time);
971 g_free(buddy->cal_free_busy_base64);
972 g_free(buddy->cal_free_busy);
973 g_free(buddy->last_non_cal_activity);
975 sipe_cal_free_working_hours(buddy->cal_working_hours);
977 g_free(buddy->device_name);
978 g_slist_free(buddy->groups);
979 g_free(buddy);
983 * Unassociates buddy from group first.
984 * Then see if no groups left, removes buddy completely.
985 * Otherwise updates buddy groups on server.
987 void sipe_remove_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
989 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
990 struct sipe_buddy *b;
991 struct sipe_group *g = NULL;
993 SIPE_DEBUG_INFO("sipe_remove_buddy[CB]: buddy:%s group:%s", buddy ? buddy->name : "", group ? group->name : "");
994 if (!buddy) return;
996 b = g_hash_table_lookup(sipe_private->buddies, buddy->name);
997 if (!b) return;
999 if (group) {
1000 g = sipe_group_find_by_name(sipe_private, group->name);
1003 if (g) {
1004 b->groups = g_slist_remove(b->groups, g);
1005 SIPE_DEBUG_INFO("buddy %s removed from group %s", buddy->name, g->name);
1008 if (g_slist_length(b->groups) < 1) {
1009 gchar *action_name = sipe_utils_presence_key(buddy->name);
1010 sipe_schedule_cancel(sipe_private, action_name);
1011 g_free(action_name);
1013 g_hash_table_remove(sipe_private->buddies, buddy->name);
1015 if (b->name) {
1016 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
1017 gchar * body = g_strdup_printf(SIPE_SOAP_DEL_CONTACT, b->name, sip->contacts_delta++);
1018 send_soap_request(sipe_private, body);
1019 g_free(body);
1022 sipe_free_buddy(b);
1023 } else {
1024 //updates groups on server
1025 sipe_core_group_set_user(SIPE_CORE_PUBLIC, b->name);
1031 * A callback for g_hash_table_foreach
1033 static void
1034 sipe_buddy_subscribe_cb(char *buddy_name,
1035 SIPE_UNUSED_PARAMETER struct sipe_buddy *buddy,
1036 struct sipe_core_private *sipe_private)
1038 gchar *action_name = sipe_utils_presence_key(buddy_name);
1039 /* g_hash_table_size() can never return 0, otherwise this function wouldn't be called :-) */
1040 guint time_range = (g_hash_table_size(sipe_private->buddies) * 1000) / 25; /* time interval for 25 requests per sec. In msec. */
1041 guint timeout = ((guint) rand()) / (RAND_MAX / time_range) + 1; /* random period within the range but never 0! */
1043 sipe_schedule_mseconds(sipe_private,
1044 action_name,
1045 g_strdup(buddy_name),
1046 timeout,
1047 sipe_subscribe_presence_single,
1048 g_free);
1049 g_free(action_name);
1053 * Removes entries from local buddy list
1054 * that does not correspond ones in the roaming contact list.
1056 static void sipe_cleanup_local_blist(struct sipe_core_private *sipe_private) {
1057 GSList *buddies = sipe_backend_buddy_find_all(SIPE_CORE_PUBLIC, NULL, NULL);
1058 GSList *entry = buddies;
1059 struct sipe_buddy *buddy;
1060 sipe_backend_buddy b;
1061 gchar *bname;
1062 gchar *gname;
1064 SIPE_DEBUG_INFO("sipe_cleanup_local_blist: overall %d backend buddies (including clones)", g_slist_length(buddies));
1065 SIPE_DEBUG_INFO("sipe_cleanup_local_blist: %d sipe buddies (unique)", g_hash_table_size(sipe_private->buddies));
1066 while (entry) {
1067 b = entry->data;
1068 gname = sipe_backend_buddy_get_group_name(SIPE_CORE_PUBLIC, b);
1069 bname = sipe_backend_buddy_get_name(SIPE_CORE_PUBLIC, b);
1070 buddy = g_hash_table_lookup(sipe_private->buddies, bname);
1071 g_free(bname);
1072 if(buddy) {
1073 gboolean in_sipe_groups = FALSE;
1074 GSList *entry2 = buddy->groups;
1075 while (entry2) {
1076 struct sipe_group *group = entry2->data;
1077 if (sipe_strequal(group->name, gname)) {
1078 in_sipe_groups = TRUE;
1079 break;
1081 entry2 = entry2->next;
1083 if(!in_sipe_groups) {
1084 SIPE_DEBUG_INFO("*** REMOVING %s from blist group: %s as not having this group in roaming list", bname, gname);
1085 sipe_backend_buddy_remove(SIPE_CORE_PUBLIC, b);
1087 } else {
1088 SIPE_DEBUG_INFO("*** REMOVING %s from blist group: %s as this buddy not in roaming list", bname, gname);
1089 sipe_backend_buddy_remove(SIPE_CORE_PUBLIC, b);
1091 g_free(gname);
1092 entry = entry->next;
1094 g_slist_free(buddies);
1097 static int
1098 sipe_find_access_level(struct sipe_core_private *sipe_private,
1099 const gchar *type,
1100 const gchar *value,
1101 gboolean *is_group_access);
1103 static void
1104 sipe_refresh_blocked_status_cb(char *buddy_name,
1105 SIPE_UNUSED_PARAMETER struct sipe_buddy *buddy,
1106 struct sipe_core_private *sipe_private)
1108 int container_id = sipe_find_access_level(sipe_private, "user", buddy_name, NULL);
1109 gboolean blocked = (container_id == 32000);
1110 gboolean blocked_in_blist = sipe_backend_buddy_is_blocked(SIPE_CORE_PUBLIC, buddy_name);
1112 /* SIPE_DEBUG_INFO("sipe_refresh_blocked_status_cb: buddy_name=%s, blocked=%s, blocked_in_blist=%s",
1113 buddy_name, blocked ? "T" : "F", blocked_in_blist ? "T" : "F"); */
1115 if (blocked != blocked_in_blist) {
1116 sipe_backend_buddy_set_blocked_status(SIPE_CORE_PUBLIC, buddy_name, blocked);
1120 static void
1121 sipe_refresh_blocked_status(struct sipe_core_private *sipe_private)
1123 g_hash_table_foreach(sipe_private->buddies,
1124 (GHFunc) sipe_refresh_blocked_status_cb,
1125 sipe_private);
1128 static gboolean sipe_process_roaming_contacts(struct sipe_core_private *sipe_private,
1129 struct sipmsg *msg)
1131 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
1132 int len = msg->bodylen;
1134 const gchar *tmp = sipmsg_find_header(msg, "Event");
1135 const sipe_xml *item;
1136 sipe_xml *isc;
1137 const gchar *contacts_delta;
1138 const sipe_xml *group_node;
1139 if (!g_str_has_prefix(tmp, "vnd-microsoft-roaming-contacts")) {
1140 return FALSE;
1143 /* Convert the contact from XML to backend Buddies */
1144 isc = sipe_xml_parse(msg->body, len);
1145 if (!isc) {
1146 return FALSE;
1149 contacts_delta = sipe_xml_attribute(isc, "deltaNum");
1150 if (contacts_delta) {
1151 sip->contacts_delta = (int)g_ascii_strtod(contacts_delta, NULL);
1154 if (sipe_strequal(sipe_xml_name(isc), "contactList")) {
1156 /* Parse groups */
1157 for (group_node = sipe_xml_child(isc, "group"); group_node; group_node = sipe_xml_twin(group_node)) {
1158 struct sipe_group * group = g_new0(struct sipe_group, 1);
1159 const char *name = sipe_xml_attribute(group_node, "name");
1161 if (g_str_has_prefix(name, "~")) {
1162 name = _("Other Contacts");
1164 group->name = g_strdup(name);
1165 group->id = (int)g_ascii_strtod(sipe_xml_attribute(group_node, "id"), NULL);
1167 sipe_group_add(sipe_private, group);
1170 // Make sure we have at least one group
1171 if (g_slist_length(sip->groups) == 0) {
1172 sipe_group_create(sipe_private, _("Other Contacts"), NULL);
1175 /* Parse contacts */
1176 for (item = sipe_xml_child(isc, "contact"); item; item = sipe_xml_twin(item)) {
1177 const gchar *uri = sipe_xml_attribute(item, "uri");
1178 const gchar *name = sipe_xml_attribute(item, "name");
1179 gchar *buddy_name;
1180 struct sipe_buddy *buddy = NULL;
1181 gchar *tmp;
1182 gchar **item_groups;
1183 int i = 0;
1185 /* Buddy name must be lower case as we use purple_normalize_nocase() to compare */
1186 tmp = sip_uri_from_name(uri);
1187 buddy_name = g_ascii_strdown(tmp, -1);
1188 g_free(tmp);
1190 /* assign to group Other Contacts if nothing else received */
1191 tmp = g_strdup(sipe_xml_attribute(item, "groups"));
1192 if(is_empty(tmp)) {
1193 struct sipe_group *group = sipe_group_find_by_name(sipe_private, _("Other Contacts"));
1194 g_free(tmp);
1195 tmp = group ? g_strdup_printf("%d", group->id) : g_strdup("1");
1197 item_groups = g_strsplit(tmp, " ", 0);
1198 g_free(tmp);
1200 while (item_groups[i]) {
1201 struct sipe_group *group = sipe_group_find_by_id(sipe_private, g_ascii_strtod(item_groups[i], NULL));
1203 // If couldn't find the right group for this contact, just put them in the first group we have
1204 if (group == NULL && g_slist_length(sip->groups) > 0) {
1205 group = sip->groups->data;
1208 if (group != NULL) {
1209 gchar *b_alias;
1210 sipe_backend_buddy b = sipe_backend_buddy_find(SIPE_CORE_PUBLIC, buddy_name, group->name);
1211 if (!b){
1212 b = sipe_backend_buddy_add(SIPE_CORE_PUBLIC, buddy_name, uri, group->name);
1213 SIPE_DEBUG_INFO("Created new buddy %s with alias %s", buddy_name, uri);
1216 b_alias = sipe_backend_buddy_get_alias(SIPE_CORE_PUBLIC, b);
1217 if (sipe_strcase_equal(uri, b_alias)) {
1218 if (name != NULL && strlen(name) != 0) {
1219 sipe_backend_buddy_set_alias(SIPE_CORE_PUBLIC, b, name);
1221 SIPE_DEBUG_INFO("Replaced buddy %s alias with %s", buddy_name, name);
1224 g_free(b_alias);
1226 if (!buddy) {
1227 buddy = g_new0(struct sipe_buddy, 1);
1228 buddy->name = sipe_backend_buddy_get_name(SIPE_CORE_PUBLIC, b);
1229 g_hash_table_insert(sipe_private->buddies, buddy->name, buddy);
1231 SIPE_DEBUG_INFO("Added SIPE buddy %s", buddy->name);
1234 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
1236 SIPE_DEBUG_INFO("Added buddy %s to group %s", buddy->name, group->name);
1237 } else {
1238 SIPE_DEBUG_INFO("No group found for contact %s! Unable to add to buddy list",
1239 name);
1242 i++;
1243 } // while, contact groups
1244 g_strfreev(item_groups);
1245 g_free(buddy_name);
1247 } // for, contacts
1249 sipe_cleanup_local_blist(sipe_private);
1251 /* Add self-contact if not there yet. 2005 systems. */
1252 /* This will resemble subscription to roaming_self in 2007 systems */
1253 if (!SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
1254 gchar *self_uri = sip_uri_self(sipe_private);
1255 struct sipe_buddy *buddy = g_hash_table_lookup(sipe_private->buddies, self_uri);
1257 if (!buddy) {
1258 buddy = g_new0(struct sipe_buddy, 1);
1259 buddy->name = g_strdup(self_uri);
1260 g_hash_table_insert(sipe_private->buddies, buddy->name, buddy);
1262 g_free(self_uri);
1265 sipe_xml_free(isc);
1267 /* subscribe to buddies */
1268 if (!sip->subscribed_buddies) { //do it once, then count Expire field to schedule resubscribe.
1269 if (sip->batched_support) {
1270 sipe_subscribe_presence_batched(sipe_private, NULL);
1271 } else {
1272 g_hash_table_foreach(sipe_private->buddies,
1273 (GHFunc)sipe_buddy_subscribe_cb,
1274 sipe_private);
1276 sip->subscribed_buddies = TRUE;
1278 /* for 2005 systems schedule contacts' status update
1279 * based on their calendar information
1281 if (!SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
1282 sipe_sched_calendar_status_update(sipe_private, time(NULL));
1285 return 0;
1289 * Fires on deregistration event initiated by server.
1290 * [MS-SIPREGE] SIP extension.
1293 // 2007 Example
1295 // Content-Type: text/registration-event
1296 // subscription-state: terminated;expires=0
1297 // ms-diagnostics-public: 4141;reason="User disabled"
1299 // deregistered;event=rejected
1301 static void sipe_process_registration_notify(struct sipe_core_private *sipe_private,
1302 struct sipmsg *msg)
1304 const gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
1305 gchar *event = NULL;
1306 gchar *reason = NULL;
1307 gchar *warning;
1309 SIPE_DEBUG_INFO_NOFORMAT("sipe_process_registration_notify: deregistration received.");
1311 if (!g_ascii_strncasecmp(contenttype, "text/registration-event", 23)) {
1312 event = sipmsg_find_part_of_header(msg->body, "event=", NULL, NULL);
1313 //@TODO have proper parameter extraction _by_name_ func, case insesitive.
1314 event = event ? event : sipmsg_find_part_of_header(msg->body, "event=", ";", NULL);
1315 } else {
1316 SIPE_DEBUG_INFO_NOFORMAT("sipe_process_registration_notify: unknown content type, exiting.");
1317 return;
1320 reason = sipmsg_get_ms_diagnostics_reason(msg);
1321 reason = reason ? reason : sipmsg_get_ms_diagnostics_public_reason(msg);
1322 if (!reason) { // for LCS2005
1323 if (event && sipe_strcase_equal(event, "unregistered")) {
1324 //reason = g_strdup(_("User logged out")); // [MS-OCER]
1325 reason = g_strdup(_("you are already signed in at another location"));
1326 } else if (event && sipe_strcase_equal(event, "rejected")) {
1327 reason = g_strdup(_("user disabled")); // [MS-OCER]
1328 } else if (event && sipe_strcase_equal(event, "deactivated")) {
1329 reason = g_strdup(_("user moved")); // [MS-OCER]
1332 g_free(event);
1333 warning = g_strdup_printf(_("You have been rejected by the server: %s"), reason ? reason : _("no reason given"));
1334 g_free(reason);
1336 sipe_backend_connection_error(SIPE_CORE_PUBLIC,
1337 SIPE_CONNECTION_ERROR_INVALID_USERNAME,
1338 warning);
1339 g_free(warning);
1343 static void sipe_process_provisioning_v2(struct sipe_core_private *sipe_private,
1344 struct sipmsg *msg)
1346 sipe_xml *xn_provision_group_list;
1347 const sipe_xml *node;
1349 xn_provision_group_list = sipe_xml_parse(msg->body, msg->bodylen);
1351 /* provisionGroup */
1352 for (node = sipe_xml_child(xn_provision_group_list, "provisionGroup"); node; node = sipe_xml_twin(node)) {
1353 if (sipe_strequal("ServerConfiguration", sipe_xml_attribute(node, "name"))) {
1354 g_free(sipe_private->focus_factory_uri);
1355 sipe_private->focus_factory_uri = sipe_xml_data(sipe_xml_child(node, "focusFactoryUri"));
1356 SIPE_DEBUG_INFO("sipe_process_provisioning_v2: sipe_private->focus_factory_uri=%s",
1357 sipe_private->focus_factory_uri ? sipe_private->focus_factory_uri : "");
1359 #ifdef HAVE_VV
1360 g_free(sipe_private->mras_uri);
1361 sipe_private->mras_uri = g_strstrip(sipe_xml_data(sipe_xml_child(node, "mrasUri")));
1362 SIPE_DEBUG_INFO("sipe_process_provisioning_v2: sipe_private->mras_uri=%s",
1363 sipe_private->mras_uri ? sipe_private->mras_uri : "");
1365 if (sipe_private->mras_uri)
1366 sipe_media_get_av_edge_credentials(sipe_private);
1367 #endif
1368 break;
1371 sipe_xml_free(xn_provision_group_list);
1374 /** for 2005 system */
1375 static void
1376 sipe_process_provisioning(struct sipe_core_private *sipe_private,
1377 struct sipmsg *msg)
1379 sipe_xml *xn_provision;
1380 const sipe_xml *node;
1382 xn_provision = sipe_xml_parse(msg->body, msg->bodylen);
1383 if ((node = sipe_xml_child(xn_provision, "user"))) {
1384 SIPE_DEBUG_INFO("sipe_process_provisioning: uri=%s", sipe_xml_attribute(node, "uri"));
1385 if ((node = sipe_xml_child(node, "line"))) {
1386 const gchar *line_uri = sipe_xml_attribute(node, "uri");
1387 const gchar *server = sipe_xml_attribute(node, "server");
1388 SIPE_DEBUG_INFO("sipe_process_provisioning: line_uri=%s server=%s", line_uri, server);
1389 sip_csta_open(sipe_private, line_uri, server);
1392 sipe_xml_free(xn_provision);
1395 static void sipe_process_roaming_acl(struct sipe_core_private *sipe_private,
1396 struct sipmsg *msg)
1398 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
1399 const gchar *contacts_delta;
1400 sipe_xml *xml;
1402 xml = sipe_xml_parse(msg->body, msg->bodylen);
1403 if (!xml)
1405 return;
1408 contacts_delta = sipe_xml_attribute(xml, "deltaNum");
1409 if (contacts_delta)
1411 sip->acl_delta = (int)g_ascii_strtod(contacts_delta, NULL);
1414 sipe_xml_free(xml);
1417 /** MS-PRES container */
1418 struct sipe_container {
1419 guint id;
1420 guint version;
1421 GSList *members;
1423 /** MS-PRES container member */
1424 struct sipe_container_member {
1425 /** user, domain, sameEnterprise, federated, publicCloud; everyone */
1426 gchar *type;
1427 gchar *value;
1430 static void
1431 free_container_member(struct sipe_container_member *member)
1433 if (!member) return;
1435 g_free(member->type);
1436 g_free(member->value);
1437 g_free(member);
1440 static void
1441 free_container(struct sipe_container *container)
1443 GSList *entry;
1445 if (!container) return;
1447 entry = container->members;
1448 while (entry) {
1449 void *data = entry->data;
1450 entry = g_slist_remove(entry, data);
1451 free_container_member((struct sipe_container_member *)data);
1453 g_free(container);
1456 static void
1457 sipe_send_container_members_prepare(const guint container_id,
1458 const guint container_version,
1459 const gchar *action,
1460 const gchar *type,
1461 const gchar *value,
1462 char **container_xmls)
1464 gchar *value_str = value ? g_strdup_printf(" value=\"%s\"", value) : g_strdup("");
1465 gchar *body;
1467 if (!container_xmls) return;
1469 body = g_strdup_printf(
1470 "<container id=\"%d\" version=\"%d\"><member action=\"%s\" type=\"%s\"%s/></container>",
1471 container_id,
1472 container_version,
1473 action,
1474 type,
1475 value_str);
1476 g_free(value_str);
1478 if ((*container_xmls) == NULL) {
1479 *container_xmls = body;
1480 } else {
1481 char *tmp = *container_xmls;
1483 *container_xmls = g_strconcat(*container_xmls, body, NULL);
1484 g_free(tmp);
1485 g_free(body);
1489 static void
1490 sipe_send_set_container_members(struct sipe_core_private *sipe_private,
1491 char *container_xmls)
1493 gchar *self;
1494 gchar *contact;
1495 gchar *hdr;
1496 gchar *body;
1498 if (!container_xmls) return;
1500 self = sip_uri_self(sipe_private);
1501 body = g_strdup_printf(
1502 "<setContainerMembers xmlns=\"http://schemas.microsoft.com/2006/09/sip/container-management\">"
1503 "%s"
1504 "</setContainerMembers>",
1505 container_xmls);
1507 contact = get_contact(sipe_private);
1508 hdr = g_strdup_printf("Contact: %s\r\n"
1509 "Content-Type: application/msrtc-setcontainermembers+xml\r\n", contact);
1510 g_free(contact);
1512 sip_transport_service(sipe_private,
1513 self,
1514 hdr,
1515 body,
1516 NULL);
1518 g_free(hdr);
1519 g_free(body);
1520 g_free(self);
1524 * Finds locally stored MS-PRES container member
1526 static struct sipe_container_member *
1527 sipe_find_container_member(struct sipe_container *container,
1528 const gchar *type,
1529 const gchar *value)
1531 struct sipe_container_member *member;
1532 GSList *entry;
1534 if (container == NULL || type == NULL) {
1535 return NULL;
1538 entry = container->members;
1539 while (entry) {
1540 member = entry->data;
1541 if (sipe_strcase_equal(member->type, type) &&
1542 sipe_strcase_equal(member->value, value))
1544 return member;
1546 entry = entry->next;
1548 return NULL;
1552 * Finds locally stored MS-PRES container by id
1554 static struct sipe_container *
1555 sipe_find_container(struct sipe_core_private *sipe_private,
1556 guint id)
1558 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
1559 struct sipe_container *container;
1560 GSList *entry;
1562 if (sip == NULL) {
1563 return NULL;
1566 entry = sip->containers;
1567 while (entry) {
1568 container = entry->data;
1569 if (id == container->id) {
1570 return container;
1572 entry = entry->next;
1574 return NULL;
1577 static GSList *
1578 sipe_get_access_domains(struct sipe_core_private *sipe_private)
1580 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
1581 struct sipe_container *container;
1582 struct sipe_container_member *member;
1583 GSList *entry;
1584 GSList *entry2;
1585 GSList *res = NULL;
1587 if (!sip) return NULL;
1589 entry = sip->containers;
1590 while (entry) {
1591 container = entry->data;
1593 entry2 = container->members;
1594 while (entry2) {
1595 member = entry2->data;
1596 if (sipe_strcase_equal(member->type, "domain"))
1598 res = slist_insert_unique_sorted(res, g_strdup(member->value), (GCompareFunc)g_ascii_strcasecmp);
1600 entry2 = entry2->next;
1602 entry = entry->next;
1604 return res;
1608 * Returns pointer to domain part in provided Email URL
1610 * @param email an email URL. Example: first.last@hq.company.com
1611 * @return pointer to domain part of email URL. Coresponding example: hq.company.com
1613 * Doesn't allocate memory
1615 static const char *
1616 sipe_get_domain(const char *email)
1618 char *tmp;
1620 if (!email) return NULL;
1622 tmp = strstr(email, "@");
1624 if (tmp && ((tmp+1) < (email + strlen(email)))) {
1625 return tmp+1;
1626 } else {
1627 return NULL;
1632 /* @TODO: replace with binary search for faster access? */
1633 /** source: http://support.microsoft.com/kb/897567 */
1634 static const char * const public_domains [] = {
1635 "aol.com", "icq.com", "love.com", "mac.com", "br.live.com",
1636 "hotmail.co.il", "hotmail.co.jp", "hotmail.co.th", "hotmail.co.uk",
1637 "hotmail.com", "hotmail.com.ar", "hotmail.com.tr", "hotmail.es",
1638 "hotmail.de", "hotmail.fr", "hotmail.it", "live.at", "live.be",
1639 "live.ca", "live.cl", "live.cn", "live.co.in", "live.co.kr",
1640 "live.co.uk", "live.co.za", "live.com", "live.com.ar", "live.com.au",
1641 "live.com.co", "live.com.mx", "live.com.my", "live.com.pe",
1642 "live.com.ph", "live.com.pk", "live.com.pt", "live.com.sg",
1643 "live.com.ve", "live.de", "live.dk", "live.fr", "live.hk", "live.ie",
1644 "live.in", "live.it", "live.jp", "live.nl", "live.no", "live.ph",
1645 "live.ru", "live.se", "livemail.com.br", "livemail.tw",
1646 "messengeruser.com", "msn.com", "passport.com", "sympatico.ca",
1647 "tw.live.com", "webtv.net", "windowslive.com", "windowslive.es",
1648 "yahoo.com",
1649 NULL};
1651 static gboolean
1652 sipe_is_public_domain(const char *domain)
1654 int i = 0;
1655 while (public_domains[i]) {
1656 if (sipe_strcase_equal(public_domains[i], domain)) {
1657 return TRUE;
1659 i++;
1661 return FALSE;
1665 * Access Levels
1666 * 32000 - Blocked
1667 * 400 - Personal
1668 * 300 - Team
1669 * 200 - Company
1670 * 100 - Public
1672 static const char *
1673 sipe_get_access_level_name(int container_id)
1675 switch(container_id) {
1676 case 32000: return _("Blocked");
1677 case 400: return _("Personal");
1678 case 300: return _("Team");
1679 case 200: return _("Company");
1680 case 100: return _("Public");
1682 return _("Unknown");
1685 static const guint containers[] = {32000, 400, 300, 200, 100};
1686 #define CONTAINERS_LEN (sizeof(containers) / sizeof(guint))
1689 static int
1690 sipe_find_member_access_level(struct sipe_core_private *sipe_private,
1691 const gchar *type,
1692 const gchar *value)
1694 unsigned int i = 0;
1695 const gchar *value_mod = value;
1697 if (!type) return -1;
1699 if (sipe_strequal("user", type)) {
1700 value_mod = sipe_get_no_sip_uri(value);
1703 for (i = 0; i < CONTAINERS_LEN; i++) {
1704 struct sipe_container_member *member;
1705 struct sipe_container *container = sipe_find_container(sipe_private, containers[i]);
1706 if (!container) continue;
1708 member = sipe_find_container_member(container, type, value_mod);
1709 if (member) return containers[i];
1712 return -1;
1715 /** Member type: user, domain, sameEnterprise, federated, publicCloud; everyone */
1716 static int
1717 sipe_find_access_level(struct sipe_core_private *sipe_private,
1718 const gchar *type,
1719 const gchar *value,
1720 gboolean *is_group_access)
1722 int container_id = -1;
1724 if (sipe_strequal("user", type)) {
1725 const char *domain;
1726 const char *no_sip_uri = sipe_get_no_sip_uri(value);
1728 container_id = sipe_find_member_access_level(sipe_private, "user", no_sip_uri);
1729 if (container_id >= 0) {
1730 if (is_group_access) *is_group_access = FALSE;
1731 return container_id;
1734 domain = sipe_get_domain(no_sip_uri);
1735 container_id = sipe_find_member_access_level(sipe_private, "domain", domain);
1736 if (container_id >= 0) {
1737 if (is_group_access) *is_group_access = TRUE;
1738 return container_id;
1741 container_id = sipe_find_member_access_level(sipe_private, "sameEnterprise", NULL);
1742 if ((container_id >= 0) && sipe_strcase_equal(sipe_private->public.sip_domain, domain)) {
1743 if (is_group_access) *is_group_access = TRUE;
1744 return container_id;
1747 container_id = sipe_find_member_access_level(sipe_private, "publicCloud", NULL);
1748 if ((container_id >= 0) && sipe_is_public_domain(domain)) {
1749 if (is_group_access) *is_group_access = TRUE;
1750 return container_id;
1753 container_id = sipe_find_member_access_level(sipe_private, "everyone", NULL);
1754 if ((container_id >= 0)) {
1755 if (is_group_access) *is_group_access = TRUE;
1756 return container_id;
1758 } else {
1759 container_id = sipe_find_member_access_level(sipe_private, type, value);
1760 if (is_group_access) *is_group_access = FALSE;
1763 return container_id;
1767 * @param container_id a new access level. If -1 then current access level
1768 * is just removed (I.e. the member is removed from all containers).
1769 * @param type a type of member. E.g. "user", "sameEnterprise", etc.
1770 * @param value a value for member. E.g. SIP URI for "user" member type.
1772 static void
1773 sipe_change_access_level(struct sipe_core_private *sipe_private,
1774 const int container_id,
1775 const gchar *type,
1776 const gchar *value)
1778 unsigned int i;
1779 int current_container_id = -1;
1780 char *container_xmls = NULL;
1782 /* for each container: find/delete */
1783 for (i = 0; i < CONTAINERS_LEN; i++) {
1784 struct sipe_container_member *member;
1785 struct sipe_container *container = sipe_find_container(sipe_private, containers[i]);
1787 if (!container) continue;
1789 member = sipe_find_container_member(container, type, value);
1790 if (member) {
1791 current_container_id = containers[i];
1792 /* delete/publish current access level */
1793 if (container_id < 0 || container_id != current_container_id) {
1794 sipe_send_container_members_prepare(current_container_id, container->version, "remove", type, value, &container_xmls);
1795 /* remove member from our cache, to be able to recalculate AL below */
1796 container->members = g_slist_remove(container->members, member);
1797 current_container_id = -1;
1802 /* recalculate AL below */
1803 current_container_id = sipe_find_access_level(sipe_private, type, value, NULL);
1805 /* assign/publish new access level */
1806 if (container_id != current_container_id && container_id >= 0) {
1807 struct sipe_container *container = sipe_find_container(sipe_private, container_id);
1808 guint version = container ? container->version : 0;
1810 sipe_send_container_members_prepare(container_id, version, "add", type, value, &container_xmls);
1813 if (container_xmls) {
1814 sipe_send_set_container_members(sipe_private, container_xmls);
1816 g_free(container_xmls);
1819 static void
1820 free_publication(struct sipe_publication *publication)
1822 g_free(publication->category);
1823 g_free(publication->cal_event_hash);
1824 g_free(publication->note);
1826 g_free(publication->working_hours_xml_str);
1827 g_free(publication->fb_start_str);
1828 g_free(publication->free_busy_base64);
1830 g_free(publication);
1833 /* key is <category><instance><container> */
1834 static gboolean
1835 sipe_is_our_publication(struct sipe_core_private *sipe_private,
1836 const gchar *key)
1838 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
1839 GSList *entry;
1841 /* filling keys for our publications if not yet cached */
1842 if (!sip->our_publication_keys) {
1843 guint device_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_DEVICE);
1844 guint machine_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_MACHINE);
1845 guint user_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_USER);
1846 guint calendar_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_CALENDAR);
1847 guint cal_oof_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_CALENDAR_OOF);
1848 guint cal_data_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_CALENDAR_DATA);
1849 guint note_oof_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_NOTE_OOF);
1851 SIPE_DEBUG_INFO_NOFORMAT("* Our Publication Instances *");
1852 SIPE_DEBUG_INFO("\tDevice : %u\t0x%08X", device_instance, device_instance);
1853 SIPE_DEBUG_INFO("\tMachine State : %u\t0x%08X", machine_instance, machine_instance);
1854 SIPE_DEBUG_INFO("\tUser Stare : %u\t0x%08X", user_instance, user_instance);
1855 SIPE_DEBUG_INFO("\tCalendar State : %u\t0x%08X", calendar_instance, calendar_instance);
1856 SIPE_DEBUG_INFO("\tCalendar OOF State : %u\t0x%08X", cal_oof_instance, cal_oof_instance);
1857 SIPE_DEBUG_INFO("\tCalendar FreeBusy : %u\t0x%08X", cal_data_instance, cal_data_instance);
1858 SIPE_DEBUG_INFO("\tOOF Note : %u\t0x%08X", note_oof_instance, note_oof_instance);
1859 SIPE_DEBUG_INFO("\tNote : %u", 0);
1860 SIPE_DEBUG_INFO("\tCalendar WorkingHours: %u", 0);
1862 /* device */
1863 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1864 g_strdup_printf("<%s><%u><%u>", "device", device_instance, 2));
1866 /* state:machineState */
1867 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1868 g_strdup_printf("<%s><%u><%u>", "state", machine_instance, 2));
1869 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1870 g_strdup_printf("<%s><%u><%u>", "state", machine_instance, 3));
1872 /* state:userState */
1873 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1874 g_strdup_printf("<%s><%u><%u>", "state", user_instance, 2));
1875 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1876 g_strdup_printf("<%s><%u><%u>", "state", user_instance, 3));
1878 /* state:calendarState */
1879 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1880 g_strdup_printf("<%s><%u><%u>", "state", calendar_instance, 2));
1881 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1882 g_strdup_printf("<%s><%u><%u>", "state", calendar_instance, 3));
1884 /* state:calendarState OOF */
1885 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1886 g_strdup_printf("<%s><%u><%u>", "state", cal_oof_instance, 2));
1887 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1888 g_strdup_printf("<%s><%u><%u>", "state", cal_oof_instance, 3));
1890 /* note */
1891 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1892 g_strdup_printf("<%s><%u><%u>", "note", 0, 200));
1893 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1894 g_strdup_printf("<%s><%u><%u>", "note", 0, 300));
1895 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1896 g_strdup_printf("<%s><%u><%u>", "note", 0, 400));
1898 /* note OOF */
1899 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1900 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 200));
1901 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1902 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 300));
1903 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1904 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 400));
1906 /* calendarData:WorkingHours */
1907 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1908 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 1));
1909 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1910 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 100));
1911 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1912 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 200));
1913 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1914 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 300));
1915 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1916 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 400));
1917 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1918 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 32000));
1920 /* calendarData:FreeBusy */
1921 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1922 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 1));
1923 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1924 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 100));
1925 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1926 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 200));
1927 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1928 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 300));
1929 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1930 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 400));
1931 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1932 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 32000));
1934 //SIPE_DEBUG_INFO("sipe_is_our_publication: sip->our_publication_keys length=%d",
1935 // sip->our_publication_keys ? (int) g_slist_length(sip->our_publication_keys) : -1);
1938 //SIPE_DEBUG_INFO("sipe_is_our_publication: key=%s", key);
1940 entry = sip->our_publication_keys;
1941 while (entry) {
1942 //SIPE_DEBUG_INFO(" sipe_is_our_publication: entry->data=%s", entry->data);
1943 if (sipe_strequal(entry->data, key)) {
1944 return TRUE;
1946 entry = entry->next;
1948 return FALSE;
1951 /** Property names to store in blist.xml */
1952 #define ALIAS_PROP "alias"
1953 #define EMAIL_PROP "email"
1954 #define PHONE_PROP "phone"
1955 #define PHONE_DISPLAY_PROP "phone-display"
1956 #define PHONE_MOBILE_PROP "phone-mobile"
1957 #define PHONE_MOBILE_DISPLAY_PROP "phone-mobile-display"
1958 #define PHONE_HOME_PROP "phone-home"
1959 #define PHONE_HOME_DISPLAY_PROP "phone-home-display"
1960 #define PHONE_OTHER_PROP "phone-other"
1961 #define PHONE_OTHER_DISPLAY_PROP "phone-other-display"
1962 #define PHONE_CUSTOM1_PROP "phone-custom1"
1963 #define PHONE_CUSTOM1_DISPLAY_PROP "phone-custom1-display"
1964 #define SITE_PROP "site"
1965 #define COMPANY_PROP "company"
1966 #define DEPARTMENT_PROP "department"
1967 #define TITLE_PROP "title"
1968 #define OFFICE_PROP "office"
1969 /** implies work address */
1970 #define ADDRESS_STREET_PROP "address-street"
1971 #define ADDRESS_CITY_PROP "address-city"
1972 #define ADDRESS_STATE_PROP "address-state"
1973 #define ADDRESS_ZIPCODE_PROP "address-zipcode"
1974 #define ADDRESS_COUNTRYCODE_PROP "address-country-code"
1977 * Tries to figure out user first and last name
1978 * based on Display Name and email properties.
1980 * Allocates memory - must be g_free()'d
1982 * Examples to parse:
1983 * First Last
1984 * First Last - Company Name
1985 * Last, First
1986 * Last, First M.
1987 * Last, First (C)(STP) (Company)
1988 * first.last@company.com (preprocessed as "first last")
1989 * first.last.company.com@reuters.net (preprocessed as "first last company com")
1991 * Unusable examples:
1992 * user@company.com (preprocessed as "user")
1993 * first.m.last@company.com (preprocessed as "first m last")
1994 * user.company.com@reuters.net (preprocessed as "user company com")
1996 static void
1997 sipe_get_first_last_names(struct sipe_core_private *sipe_private,
1998 const char *uri,
1999 char **first_name,
2000 char **last_name)
2002 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
2003 sipe_backend_buddy p_buddy;
2004 char *display_name;
2005 gchar *email;
2006 const char *first, *last;
2007 char *tmp;
2008 char **parts;
2009 gboolean has_comma = FALSE;
2011 if (!sip || !uri) return;
2013 p_buddy = sipe_backend_buddy_find(SIPE_CORE_PUBLIC, uri, NULL);
2015 if (!p_buddy) return;
2017 display_name = sipe_backend_buddy_get_alias(SIPE_CORE_PUBLIC, p_buddy);
2018 email = sipe_backend_buddy_get_string(SIPE_CORE_PUBLIC, p_buddy, SIPE_BUDDY_INFO_EMAIL);
2020 if (!display_name && !email) return;
2022 /* if no display name, make "first last anything_else" out of email */
2023 if (email && !display_name) {
2024 display_name = g_strndup(email, strstr(email, "@") - email);
2025 display_name = sipe_utils_str_replace((tmp = display_name), ".", " ");
2026 g_free(tmp);
2029 if (display_name) {
2030 has_comma = (strstr(display_name, ",") != NULL);
2031 display_name = sipe_utils_str_replace((tmp = display_name), ", ", " ");
2032 g_free(tmp);
2033 display_name = sipe_utils_str_replace((tmp = display_name), ",", " ");
2034 g_free(tmp);
2037 parts = g_strsplit(display_name, " ", 0);
2039 if (!parts[0] || !parts[1]) {
2040 g_free(email);
2041 g_free(display_name);
2042 g_strfreev(parts);
2043 return;
2046 if (has_comma) {
2047 last = parts[0];
2048 first = parts[1];
2049 } else {
2050 first = parts[0];
2051 last = parts[1];
2054 if (first_name) {
2055 *first_name = g_strstrip(g_strdup(first));
2058 if (last_name) {
2059 *last_name = g_strstrip(g_strdup(last));
2062 g_free(email);
2063 g_free(display_name);
2064 g_strfreev(parts);
2068 * Update user information
2070 * @param uri buddy SIP URI with 'sip:' prefix whose info we want to change.
2071 * @param property_name
2072 * @param property_value may be modified to strip white space
2074 static void
2075 sipe_update_user_info(struct sipe_core_private *sipe_private,
2076 const char *uri,
2077 sipe_buddy_info_fields propkey,
2078 char *property_value)
2080 GSList *buddies, *entry;
2082 if (property_value)
2083 property_value = g_strstrip(property_value);
2085 entry = buddies = sipe_backend_buddy_find_all(SIPE_CORE_PUBLIC, uri, NULL); /* all buddies in different groups */
2086 while (entry) {
2087 gchar *prop_str;
2088 gchar *server_alias;
2089 gchar *alias;
2090 sipe_backend_buddy p_buddy = entry->data;
2092 /* for Display Name */
2093 if (propkey == SIPE_BUDDY_INFO_DISPLAY_NAME) {
2094 alias = sipe_backend_buddy_get_alias(SIPE_CORE_PUBLIC, p_buddy);
2095 if (property_value && sipe_is_bad_alias(uri, alias)) {
2096 SIPE_DEBUG_INFO("Replacing alias for %s with %s", uri, property_value);
2097 sipe_backend_buddy_set_alias(SIPE_CORE_PUBLIC, p_buddy, property_value);
2099 g_free(alias);
2101 server_alias = sipe_backend_buddy_get_server_alias(SIPE_CORE_PUBLIC, p_buddy);
2102 if (!is_empty(property_value) &&
2103 (!sipe_strequal(property_value, server_alias) || is_empty(server_alias)) )
2105 SIPE_DEBUG_INFO("Replacing service alias for %s with %s", uri, property_value);
2106 sipe_backend_buddy_set_server_alias(SIPE_CORE_PUBLIC, p_buddy, property_value);
2108 g_free(server_alias);
2110 /* for other properties */
2111 else {
2112 if (!is_empty(property_value)) {
2113 prop_str = sipe_backend_buddy_get_string(SIPE_CORE_PUBLIC, p_buddy, propkey);
2114 if (!prop_str || !sipe_strcase_equal(prop_str, property_value)) {
2115 sipe_backend_buddy_set_string(SIPE_CORE_PUBLIC, p_buddy, propkey, property_value);
2117 g_free(prop_str);
2121 entry = entry->next;
2123 g_slist_free(buddies);
2127 * Update user phone
2128 * Suitable for both 2005 and 2007 systems.
2130 * @param uri buddy SIP URI with 'sip:' prefix whose info we want to change.
2131 * @param phone_type
2132 * @param phone may be modified to strip white space
2133 * @param phone_display_string may be modified to strip white space
2135 static void
2136 sipe_update_user_phone(struct sipe_core_private *sipe_private,
2137 const gchar *uri,
2138 const gchar *phone_type,
2139 gchar *phone,
2140 gchar *phone_display_string)
2142 sipe_buddy_info_fields phone_node = SIPE_BUDDY_INFO_WORK_PHONE; /* work phone by default */
2143 sipe_buddy_info_fields phone_display_node = SIPE_BUDDY_INFO_WORK_PHONE_DISPLAY; /* work phone by default */
2145 if(!phone || strlen(phone) == 0) return;
2147 if ((sipe_strequal(phone_type, "mobile") || sipe_strequal(phone_type, "cell"))) {
2148 phone_node = SIPE_BUDDY_INFO_MOBILE_PHONE;
2149 phone_display_node = SIPE_BUDDY_INFO_MOBILE_PHONE_DISPLAY;
2150 } else if (sipe_strequal(phone_type, "home")) {
2151 phone_node = SIPE_BUDDY_INFO_HOME_PHONE;
2152 phone_display_node = SIPE_BUDDY_INFO_HOME_PHONE_DISPLAY;
2153 } else if (sipe_strequal(phone_type, "other")) {
2154 phone_node = SIPE_BUDDY_INFO_OTHER_PHONE;
2155 phone_display_node = SIPE_BUDDY_INFO_OTHER_PHONE_DISPLAY;
2156 } else if (sipe_strequal(phone_type, "custom1")) {
2157 phone_node = SIPE_BUDDY_INFO_CUSTOM1_PHONE;
2158 phone_display_node = SIPE_BUDDY_INFO_CUSTOM1_PHONE_DISPLAY;
2161 sipe_update_user_info(sipe_private, uri, phone_node, phone);
2162 if (phone_display_string) {
2163 sipe_update_user_info(sipe_private, uri, phone_display_node, phone_display_string);
2167 void
2168 sipe_core_update_calendar(struct sipe_core_public *sipe_public)
2170 SIPE_DEBUG_INFO_NOFORMAT("sipe_core_update_calendar: started.");
2172 /* Do in parallel.
2173 * If failed, the branch will be disabled for subsequent calls.
2174 * Can't rely that user turned the functionality on in account settings.
2176 sipe_ews_update_calendar(SIPE_CORE_PRIVATE);
2177 sipe_domino_update_calendar(SIPE_CORE_PRIVATE);
2179 /* schedule repeat */
2180 sipe_schedule_seconds(SIPE_CORE_PRIVATE,
2181 "<+update-calendar>",
2182 NULL,
2183 UPDATE_CALENDAR_INTERVAL,
2184 (sipe_schedule_action)sipe_core_update_calendar,
2185 NULL);
2187 SIPE_DEBUG_INFO_NOFORMAT("sipe_core_update_calendar: finished.");
2191 * This method motivates Purple's Host (e.g. Pidgin) to update its UI
2192 * by using standard Purple's means of signals and saved statuses.
2194 * Thus all UI elements get updated: Status Button with Note, docklet.
2195 * This is ablolutely important as both our status and note can come
2196 * inbound (roaming) or be updated programmatically (e.g. based on our
2197 * calendar data).
2199 static void
2200 sipe_set_purple_account_status_and_note(const PurpleAccount *account,
2201 const char *status_id,
2202 const char *message,
2203 time_t do_not_publish[])
2205 PurpleStatus *status = purple_account_get_active_status(account);
2206 gboolean changed = TRUE;
2208 if (g_str_equal(status_id, purple_status_get_id(status)) &&
2209 sipe_strequal(message, purple_status_get_attr_string(status, SIPE_STATUS_ATTR_ID_MESSAGE)))
2211 changed = FALSE;
2214 if (purple_savedstatus_is_idleaway()) {
2215 changed = FALSE;
2218 if (changed) {
2219 PurpleSavedStatus *saved_status;
2220 const PurpleStatusType *acct_status_type =
2221 purple_status_type_find_with_id(account->status_types, status_id);
2222 PurpleStatusPrimitive primitive = purple_status_type_get_primitive(acct_status_type);
2223 sipe_activity activity = sipe_get_activity_by_token(status_id);
2225 saved_status = purple_savedstatus_find_transient_by_type_and_message(primitive, message);
2226 if (saved_status) {
2227 purple_savedstatus_set_substatus(saved_status, account, acct_status_type, message);
2230 /* If this type+message is unique then create a new transient saved status
2231 * Ref: gtkstatusbox.c
2233 if (!saved_status) {
2234 GList *tmp;
2235 GList *active_accts = purple_accounts_get_all_active();
2237 saved_status = purple_savedstatus_new(NULL, primitive);
2238 purple_savedstatus_set_message(saved_status, message);
2240 for (tmp = active_accts; tmp != NULL; tmp = tmp->next) {
2241 purple_savedstatus_set_substatus(saved_status,
2242 (PurpleAccount *)tmp->data, acct_status_type, message);
2244 g_list_free(active_accts);
2247 do_not_publish[activity] = time(NULL);
2248 SIPE_DEBUG_INFO("sipe_set_purple_account_status_and_note: do_not_publish[%s]=%d [now]",
2249 status_id, (int)do_not_publish[activity]);
2251 /* Set the status for each account */
2252 purple_savedstatus_activate(saved_status);
2256 struct hash_table_delete_payload {
2257 GHashTable *hash_table;
2258 guint container;
2261 static void
2262 sipe_remove_category_container_publications_cb(const char *name,
2263 struct sipe_publication *publication,
2264 struct hash_table_delete_payload *payload)
2266 if (publication->container == payload->container) {
2267 g_hash_table_remove(payload->hash_table, name);
2270 static void
2271 sipe_remove_category_container_publications(GHashTable *our_publications,
2272 const char *category,
2273 guint container)
2275 struct hash_table_delete_payload payload;
2276 payload.hash_table = g_hash_table_lookup(our_publications, category);
2278 if (!payload.hash_table) return;
2280 payload.container = container;
2281 g_hash_table_foreach(payload.hash_table, (GHFunc)sipe_remove_category_container_publications_cb, &payload);
2284 static void
2285 send_publish_category_initial(struct sipe_core_private *sipe_private);
2288 * When we receive some self (BE) NOTIFY with a new subscriber
2289 * we sends a setSubscribers request to him [SIP-PRES] 4.8
2292 static void sipe_process_roaming_self(struct sipe_core_private *sipe_private,
2293 struct sipmsg *msg)
2295 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
2296 gchar *contact;
2297 gchar *to;
2298 sipe_xml *xml;
2299 const sipe_xml *node;
2300 const sipe_xml *node2;
2301 char *display_name = NULL;
2302 char *uri;
2303 GSList *category_names = NULL;
2304 int aggreg_avail = 0;
2305 gboolean do_update_status = FALSE;
2306 gboolean has_note_cleaned = FALSE;
2307 guint clients = 0;
2309 SIPE_DEBUG_INFO_NOFORMAT("sipe_process_roaming_self");
2311 xml = sipe_xml_parse(msg->body, msg->bodylen);
2312 if (!xml) return;
2314 contact = get_contact(sipe_private);
2315 to = sip_uri_self(sipe_private);
2317 /* categories */
2318 /* set list of categories participating in this XML */
2319 for (node = sipe_xml_child(xml, "categories/category"); node; node = sipe_xml_twin(node)) {
2320 const gchar *name = sipe_xml_attribute(node, "name");
2321 category_names = slist_insert_unique_sorted(category_names, (gchar *)name, (GCompareFunc)strcmp);
2323 SIPE_DEBUG_INFO("sipe_process_roaming_self: category_names length=%d",
2324 category_names ? (int) g_slist_length(category_names) : -1);
2325 /* drop category information */
2326 if (category_names) {
2327 GSList *entry = category_names;
2328 while (entry) {
2329 GHashTable *cat_publications;
2330 const gchar *category = entry->data;
2331 entry = entry->next;
2332 SIPE_DEBUG_INFO("sipe_process_roaming_self: dropping category: %s", category);
2333 cat_publications = g_hash_table_lookup(sip->our_publications, category);
2334 if (cat_publications) {
2335 g_hash_table_remove(sip->our_publications, category);
2336 SIPE_DEBUG_INFO("sipe_process_roaming_self: dropped category: %s", category);
2340 g_slist_free(category_names);
2341 /* filling our categories reflected in roaming data */
2342 for (node = sipe_xml_child(xml, "categories/category"); node; node = sipe_xml_twin(node)) {
2343 const char *tmp;
2344 const gchar *name = sipe_xml_attribute(node, "name");
2345 guint container = sipe_xml_int_attribute(node, "container", -1);
2346 guint instance = sipe_xml_int_attribute(node, "instance", -1);
2347 guint version = sipe_xml_int_attribute(node, "version", 0);
2348 time_t publish_time = (tmp = sipe_xml_attribute(node, "publishTime")) ?
2349 sipe_utils_str_to_time(tmp) : 0;
2350 gchar *key;
2351 GHashTable *cat_publications = g_hash_table_lookup(sip->our_publications, name);
2353 /* Ex. clear note: <category name="note"/> */
2354 if (container == (guint)-1) {
2355 g_free(sip->note);
2356 sip->note = NULL;
2357 do_update_status = TRUE;
2358 continue;
2361 /* Ex. clear note: <category name="note" container="200"/> */
2362 if (instance == (guint)-1) {
2363 if (container == 200) {
2364 g_free(sip->note);
2365 sip->note = NULL;
2366 do_update_status = TRUE;
2368 SIPE_DEBUG_INFO("sipe_process_roaming_self: removing publications for: %s/%u", name, container);
2369 sipe_remove_category_container_publications(
2370 sip->our_publications, name, container);
2371 continue;
2374 /* key is <category><instance><container> */
2375 key = g_strdup_printf("<%s><%u><%u>", name, instance, container);
2376 SIPE_DEBUG_INFO("sipe_process_roaming_self: key=%s version=%d", key, version);
2378 /* capture all userState publication for later clean up if required */
2379 if (sipe_strequal(name, "state") && (container == 2 || container == 3)) {
2380 const sipe_xml *xn_state = sipe_xml_child(node, "state");
2382 if (xn_state && sipe_strequal(sipe_xml_attribute(xn_state, "type"), "userState")) {
2383 struct sipe_publication *publication = g_new0(struct sipe_publication, 1);
2384 publication->category = g_strdup(name);
2385 publication->instance = instance;
2386 publication->container = container;
2387 publication->version = version;
2389 if (!sip->user_state_publications) {
2390 sip->user_state_publications = g_hash_table_new_full(
2391 g_str_hash, g_str_equal,
2392 g_free, (GDestroyNotify)free_publication);
2394 g_hash_table_insert(sip->user_state_publications, g_strdup(key), publication);
2395 SIPE_DEBUG_INFO("sipe_process_roaming_self: added to user_state_publications key=%s version=%d",
2396 key, version);
2400 /* count clients */
2401 if (sipe_strequal(name, "device")) clients++;
2403 if (sipe_is_our_publication(sipe_private, key)) {
2404 struct sipe_publication *publication = g_new0(struct sipe_publication, 1);
2406 publication->category = g_strdup(name);
2407 publication->instance = instance;
2408 publication->container = container;
2409 publication->version = version;
2411 /* filling publication->availability */
2412 if (sipe_strequal(name, "state")) {
2413 const sipe_xml *xn_state = sipe_xml_child(node, "state");
2414 const sipe_xml *xn_avail = sipe_xml_child(xn_state, "availability");
2416 if (xn_avail) {
2417 gchar *avail_str = sipe_xml_data(xn_avail);
2418 if (avail_str) {
2419 publication->availability = atoi(avail_str);
2421 g_free(avail_str);
2423 /* for calendarState */
2424 if (xn_state && sipe_strequal(sipe_xml_attribute(xn_state, "type"), "calendarState")) {
2425 const sipe_xml *xn_activity = sipe_xml_child(xn_state, "activity");
2426 struct sipe_cal_event *event = g_new0(struct sipe_cal_event, 1);
2428 event->start_time = sipe_utils_str_to_time(sipe_xml_attribute(xn_state, "startTime"));
2429 if (xn_activity) {
2430 if (sipe_strequal(sipe_xml_attribute(xn_activity, "token"),
2431 sipe_activity_map[SIPE_ACTIVITY_IN_MEETING].token))
2433 event->is_meeting = TRUE;
2436 event->subject = sipe_xml_data(sipe_xml_child(xn_state, "meetingSubject"));
2437 event->location = sipe_xml_data(sipe_xml_child(xn_state, "meetingLocation"));
2439 publication->cal_event_hash = sipe_cal_event_hash(event);
2440 SIPE_DEBUG_INFO("sipe_process_roaming_self: hash=%s",
2441 publication->cal_event_hash);
2442 sipe_cal_event_free(event);
2445 /* filling publication->note */
2446 if (sipe_strequal(name, "note")) {
2447 const sipe_xml *xn_body = sipe_xml_child(node, "note/body");
2449 if (!has_note_cleaned) {
2450 has_note_cleaned = TRUE;
2452 g_free(sip->note);
2453 sip->note = NULL;
2454 sip->note_since = publish_time;
2456 do_update_status = TRUE;
2459 g_free(publication->note);
2460 publication->note = NULL;
2461 if (xn_body) {
2462 char *tmp;
2464 publication->note = g_markup_escape_text((tmp = sipe_xml_data(xn_body)), -1);
2465 g_free(tmp);
2466 if (publish_time >= sip->note_since) {
2467 g_free(sip->note);
2468 sip->note = g_strdup(publication->note);
2469 sip->note_since = publish_time;
2470 sip->is_oof_note = sipe_strequal(sipe_xml_attribute(xn_body, "type"), "OOF");
2472 do_update_status = TRUE;
2477 /* filling publication->fb_start_str, free_busy_base64, working_hours_xml_str */
2478 if (sipe_strequal(name, "calendarData") && (publication->container == 300)) {
2479 const sipe_xml *xn_free_busy = sipe_xml_child(node, "calendarData/freeBusy");
2480 const sipe_xml *xn_working_hours = sipe_xml_child(node, "calendarData/WorkingHours");
2481 if (xn_free_busy) {
2482 publication->fb_start_str = g_strdup(sipe_xml_attribute(xn_free_busy, "startTime"));
2483 publication->free_busy_base64 = sipe_xml_data(xn_free_busy);
2485 if (xn_working_hours) {
2486 publication->working_hours_xml_str = sipe_xml_stringify(xn_working_hours);
2490 if (!cat_publications) {
2491 cat_publications = g_hash_table_new_full(
2492 g_str_hash, g_str_equal,
2493 g_free, (GDestroyNotify)free_publication);
2494 g_hash_table_insert(sip->our_publications, g_strdup(name), cat_publications);
2495 SIPE_DEBUG_INFO("sipe_process_roaming_self: added GHashTable cat=%s", name);
2497 g_hash_table_insert(cat_publications, g_strdup(key), publication);
2498 SIPE_DEBUG_INFO("sipe_process_roaming_self: added key=%s version=%d", key, version);
2500 g_free(key);
2502 /* aggregateState (not an our publication) from 2-nd container */
2503 if (sipe_strequal(name, "state") && container == 2) {
2504 const sipe_xml *xn_state = sipe_xml_child(node, "state");
2506 if (xn_state && sipe_strequal(sipe_xml_attribute(xn_state, "type"), "aggregateState")) {
2507 const sipe_xml *xn_avail = sipe_xml_child(xn_state, "availability");
2509 if (xn_avail) {
2510 gchar *avail_str = sipe_xml_data(xn_avail);
2511 if (avail_str) {
2512 aggreg_avail = atoi(avail_str);
2514 g_free(avail_str);
2517 do_update_status = TRUE;
2521 /* userProperties published by server from AD */
2522 if (!sip->csta && sipe_strequal(name, "userProperties")) {
2523 const sipe_xml *line;
2524 /* line, for Remote Call Control (RCC) */
2525 for (line = sipe_xml_child(node, "userProperties/lines/line"); line; line = sipe_xml_twin(line)) {
2526 const gchar *line_server = sipe_xml_attribute(line, "lineServer");
2527 const gchar *line_type = sipe_xml_attribute(line, "lineType");
2528 gchar *line_uri;
2530 if (!line_server || !(sipe_strequal(line_type, "Rcc") || sipe_strequal(line_type, "Dual"))) continue;
2532 line_uri = sipe_xml_data(line);
2533 if (line_uri) {
2534 SIPE_DEBUG_INFO("sipe_process_roaming_self: line_uri=%s server=%s", line_uri, line_server);
2535 sip_csta_open(sipe_private, line_uri, line_server);
2537 g_free(line_uri);
2539 break;
2543 SIPE_DEBUG_INFO("sipe_process_roaming_self: sip->our_publications size=%d",
2544 sip->our_publications ? (int) g_hash_table_size(sip->our_publications) : -1);
2546 /* containers */
2547 for (node = sipe_xml_child(xml, "containers/container"); node; node = sipe_xml_twin(node)) {
2548 guint id = sipe_xml_int_attribute(node, "id", 0);
2549 struct sipe_container *container = sipe_find_container(sipe_private, id);
2551 if (container) {
2552 sip->containers = g_slist_remove(sip->containers, container);
2553 SIPE_DEBUG_INFO("sipe_process_roaming_self: removed existing container id=%d v%d", container->id, container->version);
2554 free_container(container);
2556 container = g_new0(struct sipe_container, 1);
2557 container->id = id;
2558 container->version = sipe_xml_int_attribute(node, "version", 0);
2559 sip->containers = g_slist_append(sip->containers, container);
2560 SIPE_DEBUG_INFO("sipe_process_roaming_self: added container id=%d v%d", container->id, container->version);
2562 for (node2 = sipe_xml_child(node, "member"); node2; node2 = sipe_xml_twin(node2)) {
2563 struct sipe_container_member *member = g_new0(struct sipe_container_member, 1);
2564 member->type = g_strdup(sipe_xml_attribute(node2, "type"));
2565 member->value = g_strdup(sipe_xml_attribute(node2, "value"));
2566 container->members = g_slist_append(container->members, member);
2567 SIPE_DEBUG_INFO("sipe_process_roaming_self: added container member type=%s value=%s",
2568 member->type, member->value ? member->value : "");
2572 SIPE_DEBUG_INFO("sipe_process_roaming_self: sip->access_level_set=%s", sip->access_level_set ? "TRUE" : "FALSE");
2573 if (!sip->access_level_set && sipe_xml_child(xml, "containers")) {
2574 char *container_xmls = NULL;
2575 int sameEnterpriseAL = sipe_find_access_level(sipe_private, "sameEnterprise", NULL, NULL);
2576 int federatedAL = sipe_find_access_level(sipe_private, "federated", NULL, NULL);
2578 SIPE_DEBUG_INFO("sipe_process_roaming_self: sameEnterpriseAL=%d", sameEnterpriseAL);
2579 SIPE_DEBUG_INFO("sipe_process_roaming_self: federatedAL=%d", federatedAL);
2580 /* initial set-up to let counterparties see your status */
2581 if (sameEnterpriseAL < 0) {
2582 struct sipe_container *container = sipe_find_container(sipe_private, 200);
2583 guint version = container ? container->version : 0;
2584 sipe_send_container_members_prepare(200, version, "add", "sameEnterprise", NULL, &container_xmls);
2586 if (federatedAL < 0) {
2587 struct sipe_container *container = sipe_find_container(sipe_private, 100);
2588 guint version = container ? container->version : 0;
2589 sipe_send_container_members_prepare(100, version, "add", "federated", NULL, &container_xmls);
2591 sip->access_level_set = TRUE;
2593 if (container_xmls) {
2594 sipe_send_set_container_members(sipe_private, container_xmls);
2596 g_free(container_xmls);
2599 /* Refresh contacts' blocked status */
2600 sipe_refresh_blocked_status(sipe_private);
2602 /* subscribers */
2603 for (node = sipe_xml_child(xml, "subscribers/subscriber"); node; node = sipe_xml_twin(node)) {
2604 const char *user;
2605 const char *acknowledged;
2606 gchar *hdr;
2607 gchar *body;
2609 user = sipe_xml_attribute(node, "user"); /* without 'sip:' prefix */
2610 if (!user) continue;
2611 SIPE_DEBUG_INFO("sipe_process_roaming_self: user %s", user);
2612 display_name = g_strdup(sipe_xml_attribute(node, "displayName"));
2613 uri = sip_uri_from_name(user);
2615 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_DISPLAY_NAME, display_name);
2617 acknowledged= sipe_xml_attribute(node, "acknowledged");
2618 if(sipe_strcase_equal(acknowledged,"false")){
2619 SIPE_DEBUG_INFO("sipe_process_roaming_self: user added you %s", user);
2620 if (!sipe_backend_buddy_find(SIPE_CORE_PUBLIC, uri, NULL)) {
2621 sipe_backend_buddy_request_add(SIPE_CORE_PUBLIC, uri, display_name);
2624 hdr = g_strdup_printf(
2625 "Contact: %s\r\n"
2626 "Content-Type: application/msrtc-presence-setsubscriber+xml\r\n", contact);
2628 body = g_strdup_printf(
2629 "<setSubscribers xmlns=\"http://schemas.microsoft.com/2006/09/sip/presence-subscribers\">"
2630 "<subscriber user=\"%s\" acknowledged=\"true\"/>"
2631 "</setSubscribers>", user);
2633 sip_transport_service(sipe_private,
2635 hdr,
2636 body,
2637 NULL);
2638 g_free(body);
2639 g_free(hdr);
2641 g_free(display_name);
2642 g_free(uri);
2645 g_free(contact);
2646 sipe_xml_free(xml);
2648 /* It seems that OCS always keeps a "virtual" client active,
2649 * i.e. the minimum client count is 2
2650 * @TODO: is this correct for all installations?
2652 if (clients > 2) {
2653 SIPE_CORE_PRIVATE_FLAG_SET(MPOP);
2654 SIPE_DEBUG_INFO("sipe_process_roaming_self: multiple clients detected (%d)",
2655 clients - 1);
2656 } else {
2657 SIPE_CORE_PRIVATE_FLAG_UNSET(MPOP);
2660 /* Publish initial state if not yet.
2661 * Assuming this happens on initial responce to subscription to roaming-self
2662 * so we've already updated our roaming data in full.
2663 * Only for 2007+
2665 if (!sip->initial_state_published) {
2666 send_publish_category_initial(sipe_private);
2667 sipe_groupchat_init(sipe_private);
2668 sip->initial_state_published = TRUE;
2669 /* dalayed run */
2670 sipe_schedule_seconds(sipe_private,
2671 "<+update-calendar>",
2672 NULL,
2673 UPDATE_CALENDAR_DELAY,
2674 (sipe_schedule_action)sipe_core_update_calendar,
2675 NULL);
2676 do_update_status = FALSE;
2677 } else if (aggreg_avail) {
2679 g_free(sip->status);
2680 if (aggreg_avail && aggreg_avail < 18000) { /* not offline */
2681 sip->status = g_strdup(sipe_get_status_by_availability(aggreg_avail, NULL));
2682 } else {
2683 sip->status = g_strdup(SIPE_STATUS_ID_INVISIBLE); /* not not let offline status switch us off */
2687 if (do_update_status) {
2688 SIPE_DEBUG_INFO("sipe_process_roaming_self: switch to '%s' for the account", sip->status);
2689 sipe_set_purple_account_status_and_note(sip->account, sip->status, sip->note, sip->do_not_publish);
2692 g_free(to);
2695 /* IM Session (INVITE and MESSAGE methods) */
2697 static gboolean
2698 process_options_response(SIPE_UNUSED_PARAMETER struct sipe_core_private *sipe_private,
2699 struct sipmsg *msg,
2700 SIPE_UNUSED_PARAMETER struct transaction *trans)
2702 gboolean ret = TRUE;
2704 if (msg->response != 200) {
2705 SIPE_DEBUG_INFO("process_options_response: OPTIONS response is %d", msg->response);
2706 return FALSE;
2709 SIPE_DEBUG_INFO("process_options_response: body:\n%s", msg->body ? msg->body : "");
2711 return ret;
2715 * Asks UA/proxy about its capabilities.
2717 static void sipe_options_request(struct sipe_core_private *sipe_private,
2718 const char *who)
2720 gchar *to = sip_uri(who);
2721 gchar *contact = get_contact(sipe_private);
2722 gchar *request = g_strdup_printf(
2723 "Accept: application/sdp\r\n"
2724 "Contact: %s\r\n", contact);
2725 g_free(contact);
2727 sip_transport_request(sipe_private,
2728 "OPTIONS",
2731 request,
2732 NULL,
2733 NULL,
2734 process_options_response);
2736 g_free(to);
2737 g_free(request);
2740 void
2741 sipe_convo_closed(PurpleConnection * gc, const char *who)
2743 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
2745 SIPE_DEBUG_INFO("conversation with %s closed", who);
2746 sipe_session_close(sipe_private,
2747 sipe_session_find_im(sipe_private, who));
2751 * Returns 2005-style activity and Availability.
2753 * @param status Sipe statis id.
2755 static void
2756 sipe_get_act_avail_by_status_2005(const char *status,
2757 int *activity,
2758 int *availability)
2760 int avail = 300; /* online */
2761 int act = 400; /* Available */
2763 if (sipe_strequal(status, SIPE_STATUS_ID_AWAY)) {
2764 act = 100;
2765 //} else if (sipe_strequal(status, SIPE_STATUS_ID_LUNCH)) {
2766 // act = 150;
2767 } else if (sipe_strequal(status, SIPE_STATUS_ID_BRB)) {
2768 act = 300;
2769 } else if (sipe_strequal(status, SIPE_STATUS_ID_AVAILABLE)) {
2770 act = 400;
2771 //} else if (sipe_strequal(status, SIPE_STATUS_ID_ON_PHONE)) {
2772 // act = 500;
2773 } else if (sipe_strequal(status, SIPE_STATUS_ID_BUSY) ||
2774 sipe_strequal(status, SIPE_STATUS_ID_DND)) {
2775 act = 600;
2776 } else if (sipe_strequal(status, SIPE_STATUS_ID_INVISIBLE) ||
2777 sipe_strequal(status, SIPE_STATUS_ID_OFFLINE)) {
2778 avail = 0; /* offline */
2779 act = 100;
2780 } else {
2781 act = 400; /* Available */
2784 if (activity) *activity = act;
2785 if (availability) *availability = avail;
2789 * [MS-SIP] 2.2.1
2791 * @param activity 2005 aggregated activity. Ex.: 600
2792 * @param availablity 2005 aggregated availablity. Ex.: 300
2794 static const char *
2795 sipe_get_status_by_act_avail_2005(const int activity,
2796 const int availablity,
2797 char **activity_desc)
2799 const char *status_id = NULL;
2800 const char *act = NULL;
2802 if (activity < 150) {
2803 status_id = SIPE_STATUS_ID_AWAY;
2804 } else if (activity < 200) {
2805 //status_id = SIPE_STATUS_ID_LUNCH;
2806 status_id = SIPE_STATUS_ID_AWAY;
2807 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_LUNCH);
2808 } else if (activity < 300) {
2809 //status_id = SIPE_STATUS_ID_IDLE;
2810 status_id = SIPE_STATUS_ID_AWAY;
2811 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_INACTIVE);
2812 } else if (activity < 400) {
2813 status_id = SIPE_STATUS_ID_BRB;
2814 } else if (activity < 500) {
2815 status_id = SIPE_STATUS_ID_AVAILABLE;
2816 } else if (activity < 600) {
2817 //status_id = SIPE_STATUS_ID_ON_PHONE;
2818 status_id = SIPE_STATUS_ID_BUSY;
2819 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_ON_PHONE);
2820 } else if (activity < 700) {
2821 status_id = SIPE_STATUS_ID_BUSY;
2822 } else if (activity < 800) {
2823 status_id = SIPE_STATUS_ID_AWAY;
2824 } else {
2825 status_id = SIPE_STATUS_ID_AVAILABLE;
2828 if (availablity < 100)
2829 status_id = SIPE_STATUS_ID_OFFLINE;
2831 if (activity_desc && act) {
2832 g_free(*activity_desc);
2833 *activity_desc = g_strdup(act);
2836 return status_id;
2840 * [MS-PRES] Table 3: Conversion of legacyInterop elements and attributes to MSRTC elements and attributes.
2842 static const char*
2843 sipe_get_status_by_availability(int avail,
2844 char** activity_desc)
2846 const char *status;
2847 const char *act = NULL;
2849 if (avail < 3000) {
2850 status = SIPE_STATUS_ID_OFFLINE;
2851 } else if (avail < 4500) {
2852 status = SIPE_STATUS_ID_AVAILABLE;
2853 } else if (avail < 6000) {
2854 //status = SIPE_STATUS_ID_IDLE;
2855 status = SIPE_STATUS_ID_AVAILABLE;
2856 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_INACTIVE);
2857 } else if (avail < 7500) {
2858 status = SIPE_STATUS_ID_BUSY;
2859 } else if (avail < 9000) {
2860 //status = SIPE_STATUS_ID_BUSYIDLE;
2861 status = SIPE_STATUS_ID_BUSY;
2862 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_BUSYIDLE);
2863 } else if (avail < 12000) {
2864 status = SIPE_STATUS_ID_DND;
2865 } else if (avail < 15000) {
2866 status = SIPE_STATUS_ID_BRB;
2867 } else if (avail < 18000) {
2868 status = SIPE_STATUS_ID_AWAY;
2869 } else {
2870 status = SIPE_STATUS_ID_OFFLINE;
2873 if (activity_desc && act) {
2874 g_free(*activity_desc);
2875 *activity_desc = g_strdup(act);
2878 return status;
2882 * Returns 2007-style availability value
2884 * @param sipe_status_id (in)
2885 * @param activity_token (out) Must be g_free()'d after use if consumed.
2887 static int
2888 sipe_get_availability_by_status(const char* sipe_status_id, char** activity_token)
2890 int availability;
2891 sipe_activity activity = SIPE_ACTIVITY_UNSET;
2893 if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_AWAY)) {
2894 availability = 15500;
2895 if (!activity_token || !(*activity_token)) {
2896 activity = SIPE_ACTIVITY_AWAY;
2898 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_BRB)) {
2899 availability = 12500;
2900 activity = SIPE_ACTIVITY_BRB;
2901 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_DND)) {
2902 availability = 9500;
2903 activity = SIPE_ACTIVITY_DND;
2904 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_BUSY)) {
2905 availability = 6500;
2906 if (!activity_token || !(*activity_token)) {
2907 activity = SIPE_ACTIVITY_BUSY;
2909 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_AVAILABLE)) {
2910 availability = 3500;
2911 activity = SIPE_ACTIVITY_ONLINE;
2912 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_UNKNOWN)) {
2913 availability = 0;
2914 } else {
2915 // Offline or invisible
2916 availability = 18500;
2917 activity = SIPE_ACTIVITY_OFFLINE;
2920 if (activity_token) {
2921 *activity_token = g_strdup(sipe_activity_map[activity].token);
2923 return availability;
2926 static void process_incoming_notify_rlmi(struct sipe_core_private *sipe_private,
2927 const gchar *data, unsigned len)
2929 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
2930 const char *uri;
2931 sipe_xml *xn_categories;
2932 const sipe_xml *xn_category;
2933 const char *status = NULL;
2934 gboolean do_update_status = FALSE;
2935 gboolean has_note_cleaned = FALSE;
2936 gboolean has_free_busy_cleaned = FALSE;
2938 xn_categories = sipe_xml_parse(data, len);
2939 uri = sipe_xml_attribute(xn_categories, "uri"); /* with 'sip:' prefix */
2941 for (xn_category = sipe_xml_child(xn_categories, "category");
2942 xn_category ;
2943 xn_category = sipe_xml_twin(xn_category) )
2945 const sipe_xml *xn_node;
2946 const char *tmp;
2947 const char *attrVar = sipe_xml_attribute(xn_category, "name");
2948 time_t publish_time = (tmp = sipe_xml_attribute(xn_category, "publishTime")) ?
2949 sipe_utils_str_to_time(tmp) : 0;
2951 /* contactCard */
2952 if (sipe_strequal(attrVar, "contactCard"))
2954 const sipe_xml *card = sipe_xml_child(xn_category, "contactCard");
2956 if (card) {
2957 const sipe_xml *node;
2958 /* identity - Display Name and email */
2959 node = sipe_xml_child(card, "identity");
2960 if (node) {
2961 char* display_name = sipe_xml_data(
2962 sipe_xml_child(node, "name/displayName"));
2963 char* email = sipe_xml_data(
2964 sipe_xml_child(node, "email"));
2966 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_DISPLAY_NAME, display_name);
2967 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_EMAIL, email);
2969 g_free(display_name);
2970 g_free(email);
2972 /* company */
2973 node = sipe_xml_child(card, "company");
2974 if (node) {
2975 char* company = sipe_xml_data(node);
2976 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_COMPANY, company);
2977 g_free(company);
2979 /* department */
2980 node = sipe_xml_child(card, "department");
2981 if (node) {
2982 char* department = sipe_xml_data(node);
2983 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_DEPARTMENT, department);
2984 g_free(department);
2986 /* title */
2987 node = sipe_xml_child(card, "title");
2988 if (node) {
2989 char* title = sipe_xml_data(node);
2990 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_JOB_TITLE, title);
2991 g_free(title);
2993 /* office */
2994 node = sipe_xml_child(card, "office");
2995 if (node) {
2996 char* office = sipe_xml_data(node);
2997 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_OFFICE, office);
2998 g_free(office);
3000 /* site (url) */
3001 node = sipe_xml_child(card, "url");
3002 if (node) {
3003 char* site = sipe_xml_data(node);
3004 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_SITE, site);
3005 g_free(site);
3007 /* phone */
3008 for (node = sipe_xml_child(card, "phone");
3009 node;
3010 node = sipe_xml_twin(node))
3012 const char *phone_type = sipe_xml_attribute(node, "type");
3013 char* phone = sipe_xml_data(sipe_xml_child(node, "uri"));
3014 char* phone_display_string = sipe_xml_data(sipe_xml_child(node, "displayString"));
3016 sipe_update_user_phone(sipe_private, uri, phone_type, phone, phone_display_string);
3018 g_free(phone);
3019 g_free(phone_display_string);
3021 /* address */
3022 for (node = sipe_xml_child(card, "address");
3023 node;
3024 node = sipe_xml_twin(node))
3026 if (sipe_strequal(sipe_xml_attribute(node, "type"), "work")) {
3027 char* street = sipe_xml_data(sipe_xml_child(node, "street"));
3028 char* city = sipe_xml_data(sipe_xml_child(node, "city"));
3029 char* state = sipe_xml_data(sipe_xml_child(node, "state"));
3030 char* zipcode = sipe_xml_data(sipe_xml_child(node, "zipcode"));
3031 char* country_code = sipe_xml_data(sipe_xml_child(node, "countryCode"));
3033 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_STREET, street);
3034 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_CITY, city);
3035 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_STATE, state);
3036 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_ZIPCODE, zipcode);
3037 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_COUNTRY, country_code);
3039 g_free(street);
3040 g_free(city);
3041 g_free(state);
3042 g_free(zipcode);
3043 g_free(country_code);
3045 break;
3050 /* note */
3051 else if (sipe_strequal(attrVar, "note"))
3053 if (uri) {
3054 struct sipe_buddy *sbuddy = g_hash_table_lookup(sipe_private->buddies, uri);
3056 if (!has_note_cleaned) {
3057 has_note_cleaned = TRUE;
3059 g_free(sbuddy->note);
3060 sbuddy->note = NULL;
3061 sbuddy->is_oof_note = FALSE;
3062 sbuddy->note_since = publish_time;
3064 do_update_status = TRUE;
3066 if (sbuddy && (publish_time >= sbuddy->note_since)) {
3067 /* clean up in case no 'note' element is supplied
3068 * which indicate note removal in client
3070 g_free(sbuddy->note);
3071 sbuddy->note = NULL;
3072 sbuddy->is_oof_note = FALSE;
3073 sbuddy->note_since = publish_time;
3075 xn_node = sipe_xml_child(xn_category, "note/body");
3076 if (xn_node) {
3077 char *tmp;
3078 sbuddy->note = g_markup_escape_text((tmp = sipe_xml_data(xn_node)), -1);
3079 g_free(tmp);
3080 sbuddy->is_oof_note = sipe_strequal(sipe_xml_attribute(xn_node, "type"), "OOF");
3081 sbuddy->note_since = publish_time;
3083 SIPE_DEBUG_INFO("process_incoming_notify_rlmi: uri(%s), note(%s)",
3084 uri, sbuddy->note ? sbuddy->note : "");
3086 /* to trigger UI refresh in case no status info is supplied in this update */
3087 do_update_status = TRUE;
3091 /* state */
3092 else if(sipe_strequal(attrVar, "state"))
3094 char *tmp;
3095 int availability;
3096 const sipe_xml *xn_availability;
3097 const sipe_xml *xn_activity;
3098 const sipe_xml *xn_meeting_subject;
3099 const sipe_xml *xn_meeting_location;
3100 struct sipe_buddy *sbuddy = uri ? g_hash_table_lookup(sipe_private->buddies, uri) : NULL;
3102 xn_node = sipe_xml_child(xn_category, "state");
3103 if (!xn_node) continue;
3104 xn_availability = sipe_xml_child(xn_node, "availability");
3105 if (!xn_availability) continue;
3106 xn_activity = sipe_xml_child(xn_node, "activity");
3107 xn_meeting_subject = sipe_xml_child(xn_node, "meetingSubject");
3108 xn_meeting_location = sipe_xml_child(xn_node, "meetingLocation");
3110 tmp = sipe_xml_data(xn_availability);
3111 availability = atoi(tmp);
3112 g_free(tmp);
3114 /* activity, meeting_subject, meeting_location */
3115 if (sbuddy) {
3116 char *tmp = NULL;
3118 /* activity */
3119 g_free(sbuddy->activity);
3120 sbuddy->activity = NULL;
3121 if (xn_activity) {
3122 const char *token = sipe_xml_attribute(xn_activity, "token");
3123 const sipe_xml *xn_custom = sipe_xml_child(xn_activity, "custom");
3125 /* from token */
3126 if (!is_empty(token)) {
3127 sbuddy->activity = g_strdup(sipe_get_activity_desc_by_token(token));
3129 /* from custom element */
3130 if (xn_custom) {
3131 char *custom = sipe_xml_data(xn_custom);
3133 if (!is_empty(custom)) {
3134 sbuddy->activity = custom;
3135 custom = NULL;
3137 g_free(custom);
3140 /* meeting_subject */
3141 g_free(sbuddy->meeting_subject);
3142 sbuddy->meeting_subject = NULL;
3143 if (xn_meeting_subject) {
3144 char *meeting_subject = sipe_xml_data(xn_meeting_subject);
3146 if (!is_empty(meeting_subject)) {
3147 sbuddy->meeting_subject = meeting_subject;
3148 meeting_subject = NULL;
3150 g_free(meeting_subject);
3152 /* meeting_location */
3153 g_free(sbuddy->meeting_location);
3154 sbuddy->meeting_location = NULL;
3155 if (xn_meeting_location) {
3156 char *meeting_location = sipe_xml_data(xn_meeting_location);
3158 if (!is_empty(meeting_location)) {
3159 sbuddy->meeting_location = meeting_location;
3160 meeting_location = NULL;
3162 g_free(meeting_location);
3165 status = sipe_get_status_by_availability(availability, &tmp);
3166 if (sbuddy->activity && tmp) {
3167 char *tmp2 = sbuddy->activity;
3169 sbuddy->activity = g_strdup_printf("%s, %s", sbuddy->activity, tmp);
3170 g_free(tmp);
3171 g_free(tmp2);
3172 } else if (tmp) {
3173 sbuddy->activity = tmp;
3177 do_update_status = TRUE;
3179 /* calendarData */
3180 else if(sipe_strequal(attrVar, "calendarData"))
3182 struct sipe_buddy *sbuddy = uri ? g_hash_table_lookup(sipe_private->buddies, uri) : NULL;
3183 const sipe_xml *xn_free_busy = sipe_xml_child(xn_category, "calendarData/freeBusy");
3184 const sipe_xml *xn_working_hours = sipe_xml_child(xn_category, "calendarData/WorkingHours");
3186 if (sbuddy && xn_free_busy) {
3187 if (!has_free_busy_cleaned) {
3188 has_free_busy_cleaned = TRUE;
3190 g_free(sbuddy->cal_start_time);
3191 sbuddy->cal_start_time = NULL;
3193 g_free(sbuddy->cal_free_busy_base64);
3194 sbuddy->cal_free_busy_base64 = NULL;
3196 g_free(sbuddy->cal_free_busy);
3197 sbuddy->cal_free_busy = NULL;
3199 sbuddy->cal_free_busy_published = publish_time;
3202 if (publish_time >= sbuddy->cal_free_busy_published) {
3203 g_free(sbuddy->cal_start_time);
3204 sbuddy->cal_start_time = g_strdup(sipe_xml_attribute(xn_free_busy, "startTime"));
3206 sbuddy->cal_granularity = sipe_strcase_equal(sipe_xml_attribute(xn_free_busy, "granularity"), "PT15M") ?
3207 15 : 0;
3209 g_free(sbuddy->cal_free_busy_base64);
3210 sbuddy->cal_free_busy_base64 = sipe_xml_data(xn_free_busy);
3212 g_free(sbuddy->cal_free_busy);
3213 sbuddy->cal_free_busy = NULL;
3215 sbuddy->cal_free_busy_published = publish_time;
3217 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);
3221 if (sbuddy && xn_working_hours) {
3222 sipe_cal_parse_working_hours(xn_working_hours, sbuddy);
3227 if (do_update_status) {
3228 if (!status) { /* no status category in this update, using contact's current status */
3229 PurpleBuddy *pbuddy = purple_find_buddy((PurpleAccount *)sip->account, uri);
3230 const PurplePresence *presence = purple_buddy_get_presence(pbuddy);
3231 const PurpleStatus *pstatus = purple_presence_get_active_status(presence);
3232 status = purple_status_get_id(pstatus);
3235 SIPE_DEBUG_INFO("process_incoming_notify_rlmi: %s", status);
3236 sipe_core_buddy_got_status(SIPE_CORE_PUBLIC, uri, status);
3239 sipe_xml_free(xn_categories);
3242 static void sipe_subscribe_poolfqdn_resource_uri(const char *host,
3243 GSList *server,
3244 struct sipe_core_private *sipe_private)
3246 struct presence_batched_routed *payload = g_malloc(sizeof(struct presence_batched_routed));
3247 SIPE_DEBUG_INFO("process_incoming_notify_rlmi_resub: pool(%s)", host);
3248 payload->host = g_strdup(host);
3249 payload->buddies = server;
3250 sipe_subscribe_presence_batched_routed(sipe_private,
3251 payload);
3252 sipe_subscribe_presence_batched_routed_free(payload);
3255 static void process_incoming_notify_rlmi_resub(struct sipe_core_private *sipe_private,
3256 const gchar *data, unsigned len)
3258 sipe_xml *xn_list;
3259 const sipe_xml *xn_resource;
3260 GHashTable *servers = g_hash_table_new_full(g_str_hash, g_str_equal,
3261 g_free, NULL);
3262 GSList *server;
3263 gchar *host;
3265 xn_list = sipe_xml_parse(data, len);
3267 for (xn_resource = sipe_xml_child(xn_list, "resource");
3268 xn_resource;
3269 xn_resource = sipe_xml_twin(xn_resource) )
3271 const char *uri, *state;
3272 const sipe_xml *xn_instance;
3274 xn_instance = sipe_xml_child(xn_resource, "instance");
3275 if (!xn_instance) continue;
3277 uri = sipe_xml_attribute(xn_resource, "uri");
3278 state = sipe_xml_attribute(xn_instance, "state");
3279 SIPE_DEBUG_INFO("process_incoming_notify_rlmi_resub: uri(%s),state(%s)", uri, state);
3281 if (strstr(state, "resubscribe")) {
3282 const char *poolFqdn = sipe_xml_attribute(xn_instance, "poolFqdn");
3284 if (poolFqdn) { //[MS-PRES] Section 3.4.5.1.3 Processing Details
3285 gchar *user = g_strdup(uri);
3286 host = g_strdup(poolFqdn);
3287 server = g_hash_table_lookup(servers, host);
3288 server = g_slist_append(server, user);
3289 g_hash_table_insert(servers, host, server);
3290 } else {
3291 sipe_subscribe_presence_single(sipe_private,
3292 (void *) uri);
3297 /* Send out any deferred poolFqdn subscriptions */
3298 g_hash_table_foreach(servers, (GHFunc) sipe_subscribe_poolfqdn_resource_uri, sipe_private);
3299 g_hash_table_destroy(servers);
3301 sipe_xml_free(xn_list);
3304 static void process_incoming_notify_pidf(struct sipe_core_private *sipe_private,
3305 const gchar *data, unsigned len)
3307 gchar *uri;
3308 gchar *getbasic;
3309 gchar *activity = NULL;
3310 sipe_xml *pidf;
3311 const sipe_xml *basicstatus = NULL, *tuple, *status;
3312 gboolean isonline = FALSE;
3313 const sipe_xml *display_name_node;
3315 pidf = sipe_xml_parse(data, len);
3316 if (!pidf) {
3317 SIPE_DEBUG_INFO("process_incoming_notify_pidf: no parseable pidf:%s", data);
3318 return;
3321 if ((tuple = sipe_xml_child(pidf, "tuple")))
3323 if ((status = sipe_xml_child(tuple, "status"))) {
3324 basicstatus = sipe_xml_child(status, "basic");
3328 if (!basicstatus) {
3329 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_notify_pidf: no basic found");
3330 sipe_xml_free(pidf);
3331 return;
3334 getbasic = sipe_xml_data(basicstatus);
3335 if (!getbasic) {
3336 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_notify_pidf: no basic data found");
3337 sipe_xml_free(pidf);
3338 return;
3341 SIPE_DEBUG_INFO("process_incoming_notify_pidf: basic-status(%s)", getbasic);
3342 if (strstr(getbasic, "open")) {
3343 isonline = TRUE;
3345 g_free(getbasic);
3347 uri = sip_uri(sipe_xml_attribute(pidf, "entity")); /* with 'sip:' prefix */ /* AOL comes without the prefix */
3349 display_name_node = sipe_xml_child(pidf, "display-name");
3350 if (display_name_node) {
3351 char * display_name = sipe_xml_data(display_name_node);
3353 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_DISPLAY_NAME, display_name);
3354 g_free(display_name);
3357 if ((tuple = sipe_xml_child(pidf, "tuple"))) {
3358 if ((status = sipe_xml_child(tuple, "status"))) {
3359 if ((basicstatus = sipe_xml_child(status, "activities"))) {
3360 if ((basicstatus = sipe_xml_child(basicstatus, "activity"))) {
3361 activity = sipe_xml_data(basicstatus);
3362 SIPE_DEBUG_INFO("process_incoming_notify_pidf: activity(%s)", activity);
3368 if (isonline) {
3369 const gchar * status_id = NULL;
3370 if (activity) {
3371 if (sipe_strequal(activity, sipe_activity_map[SIPE_ACTIVITY_BUSY].token)) {
3372 status_id = SIPE_STATUS_ID_BUSY;
3373 } else if (sipe_strequal(activity, sipe_activity_map[SIPE_ACTIVITY_AWAY].token)) {
3374 status_id = SIPE_STATUS_ID_AWAY;
3378 if (!status_id) {
3379 status_id = SIPE_STATUS_ID_AVAILABLE;
3382 SIPE_DEBUG_INFO("process_incoming_notify_pidf: status_id(%s)", status_id);
3383 sipe_core_buddy_got_status(SIPE_CORE_PUBLIC, uri, status_id);
3384 } else {
3385 sipe_core_buddy_got_status(SIPE_CORE_PUBLIC, uri, SIPE_STATUS_ID_OFFLINE);
3388 g_free(activity);
3389 g_free(uri);
3390 sipe_xml_free(pidf);
3393 /** 2005 */
3394 static void
3395 sipe_user_info_has_updated(struct sipe_core_private *sipe_private,
3396 const sipe_xml *xn_userinfo)
3398 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
3399 const sipe_xml *xn_states;
3401 g_free(sip->user_states);
3402 sip->user_states = NULL;
3403 if ((xn_states = sipe_xml_child(xn_userinfo, "states")) != NULL) {
3404 gchar *orig = sip->user_states = sipe_xml_stringify(xn_states);
3406 /* this is a hack-around to remove added newline after inner element,
3407 * state in this case, where it shouldn't be.
3408 * After several use of sipe_xml_stringify, amount of added newlines
3409 * grows significantly.
3411 if (orig) {
3412 gchar c, *stripped = orig;
3413 while ((c = *orig++)) {
3414 if ((c != '\n') /* && (c != '\r') */) {
3415 *stripped++ = c;
3418 *stripped = '\0';
3422 /* Publish initial state if not yet.
3423 * Assuming this happens on initial responce to self subscription
3424 * so we've already updated our UserInfo.
3426 if (!sip->initial_state_published) {
3427 send_presence_soap(sipe_private, FALSE);
3428 /* dalayed run */
3429 sipe_schedule_seconds(sipe_private,
3430 "<+update-calendar>",
3431 NULL,
3432 UPDATE_CALENDAR_DELAY,
3433 (sipe_schedule_action) sipe_core_update_calendar,
3434 NULL);
3438 static void process_incoming_notify_msrtc(struct sipe_core_private *sipe_private,
3439 const gchar *data, unsigned len)
3441 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
3442 char *activity = NULL;
3443 const char *epid;
3444 const char *status_id = NULL;
3445 const char *name;
3446 char *uri;
3447 char *self_uri = sip_uri_self(sipe_private);
3448 int avl;
3449 int act;
3450 const char *device_name = NULL;
3451 const char *cal_start_time = NULL;
3452 const char *cal_granularity = NULL;
3453 char *cal_free_busy_base64 = NULL;
3454 struct sipe_buddy *sbuddy;
3455 const sipe_xml *node;
3456 sipe_xml *xn_presentity;
3457 const sipe_xml *xn_availability;
3458 const sipe_xml *xn_activity;
3459 const sipe_xml *xn_display_name;
3460 const sipe_xml *xn_email;
3461 const sipe_xml *xn_phone_number;
3462 const sipe_xml *xn_userinfo;
3463 const sipe_xml *xn_note;
3464 const sipe_xml *xn_oof;
3465 const sipe_xml *xn_state;
3466 const sipe_xml *xn_contact;
3467 char *note;
3468 int user_avail;
3469 const char *user_avail_nil;
3470 int res_avail;
3471 time_t user_avail_since = 0;
3472 time_t activity_since = 0;
3474 /* fix for Reuters environment on Linux */
3475 if (data && strstr(data, "encoding=\"utf-16\"")) {
3476 char *tmp_data;
3477 tmp_data = replace(data, "encoding=\"utf-16\"", "encoding=\"utf-8\"");
3478 xn_presentity = sipe_xml_parse(tmp_data, strlen(tmp_data));
3479 g_free(tmp_data);
3480 } else {
3481 xn_presentity = sipe_xml_parse(data, len);
3484 xn_availability = sipe_xml_child(xn_presentity, "availability");
3485 xn_activity = sipe_xml_child(xn_presentity, "activity");
3486 xn_display_name = sipe_xml_child(xn_presentity, "displayName");
3487 xn_email = sipe_xml_child(xn_presentity, "email");
3488 xn_phone_number = sipe_xml_child(xn_presentity, "phoneNumber");
3489 xn_userinfo = sipe_xml_child(xn_presentity, "userInfo");
3490 xn_oof = xn_userinfo ? sipe_xml_child(xn_userinfo, "oof") : NULL;
3491 xn_state = xn_userinfo ? sipe_xml_child(xn_userinfo, "states/state"): NULL;
3492 user_avail = xn_state ? sipe_xml_int_attribute(xn_state, "avail", 0) : 0;
3493 user_avail_since = xn_state ? sipe_utils_str_to_time(sipe_xml_attribute(xn_state, "since")) : 0;
3494 user_avail_nil = xn_state ? sipe_xml_attribute(xn_state, "nil") : NULL;
3495 xn_contact = xn_userinfo ? sipe_xml_child(xn_userinfo, "contact") : NULL;
3496 xn_note = xn_userinfo ? sipe_xml_child(xn_userinfo, "note") : NULL;
3497 note = xn_note ? sipe_xml_data(xn_note) : NULL;
3499 if (sipe_strequal(user_avail_nil, "true")) { /* null-ed */
3500 user_avail = 0;
3501 user_avail_since = 0;
3504 name = sipe_xml_attribute(xn_presentity, "uri"); /* without 'sip:' prefix */
3505 uri = sip_uri_from_name(name);
3506 avl = sipe_xml_int_attribute(xn_availability, "aggregate", 0);
3507 epid = sipe_xml_attribute(xn_availability, "epid");
3508 act = sipe_xml_int_attribute(xn_activity, "aggregate", 0);
3510 status_id = sipe_get_status_by_act_avail_2005(act, avl, &activity);
3511 res_avail = sipe_get_availability_by_status(status_id, NULL);
3512 if (user_avail > res_avail) {
3513 res_avail = user_avail;
3514 status_id = sipe_get_status_by_availability(user_avail, NULL);
3517 if (xn_display_name) {
3518 char *display_name = g_strdup(sipe_xml_attribute(xn_display_name, "displayName"));
3519 char *email = xn_email ? g_strdup(sipe_xml_attribute(xn_email, "email")) : NULL;
3520 char *phone_label = xn_phone_number ? g_strdup(sipe_xml_attribute(xn_phone_number, "label")) : NULL;
3521 char *phone_number = xn_phone_number ? g_strdup(sipe_xml_attribute(xn_phone_number, "number")) : NULL;
3522 char *tel_uri = sip_to_tel_uri(phone_number);
3524 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_DISPLAY_NAME, display_name);
3525 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_EMAIL, email);
3526 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_WORK_PHONE, tel_uri);
3527 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_WORK_PHONE_DISPLAY, !is_empty(phone_label) ? phone_label : phone_number);
3529 g_free(tel_uri);
3530 g_free(phone_label);
3531 g_free(phone_number);
3532 g_free(email);
3533 g_free(display_name);
3536 if (xn_contact) {
3537 /* tel */
3538 for (node = sipe_xml_child(xn_contact, "tel"); node; node = sipe_xml_twin(node))
3540 /* Ex.: <tel type="work">tel:+3222220000</tel> */
3541 const char *phone_type = sipe_xml_attribute(node, "type");
3542 char* phone = sipe_xml_data(node);
3544 sipe_update_user_phone(sipe_private, uri, phone_type, phone, NULL);
3546 g_free(phone);
3550 /* devicePresence */
3551 for (node = sipe_xml_child(xn_presentity, "devices/devicePresence"); node; node = sipe_xml_twin(node)) {
3552 const sipe_xml *xn_device_name;
3553 const sipe_xml *xn_calendar_info;
3554 const sipe_xml *xn_state;
3555 char *state;
3557 /* deviceName */
3558 if (sipe_strequal(sipe_xml_attribute(node, "epid"), epid)) {
3559 xn_device_name = sipe_xml_child(node, "deviceName");
3560 device_name = xn_device_name ? sipe_xml_attribute(xn_device_name, "name") : NULL;
3563 /* calendarInfo */
3564 xn_calendar_info = sipe_xml_child(node, "calendarInfo");
3565 if (xn_calendar_info) {
3566 const char *cal_start_time_tmp = sipe_xml_attribute(xn_calendar_info, "startTime");
3568 if (cal_start_time) {
3569 time_t cal_start_time_t = sipe_utils_str_to_time(cal_start_time);
3570 time_t cal_start_time_t_tmp = sipe_utils_str_to_time(cal_start_time_tmp);
3572 if (cal_start_time_t_tmp > cal_start_time_t) {
3573 cal_start_time = cal_start_time_tmp;
3574 cal_granularity = sipe_xml_attribute(xn_calendar_info, "granularity");
3575 g_free(cal_free_busy_base64);
3576 cal_free_busy_base64 = sipe_xml_data(xn_calendar_info);
3578 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);
3580 } else {
3581 cal_start_time = cal_start_time_tmp;
3582 cal_granularity = sipe_xml_attribute(xn_calendar_info, "granularity");
3583 g_free(cal_free_busy_base64);
3584 cal_free_busy_base64 = sipe_xml_data(xn_calendar_info);
3586 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);
3590 /* state */
3591 xn_state = sipe_xml_child(node, "states/state");
3592 if (xn_state) {
3593 int dev_avail = sipe_xml_int_attribute(xn_state, "avail", 0);
3594 time_t dev_avail_since = sipe_utils_str_to_time(sipe_xml_attribute(xn_state, "since"));
3596 state = sipe_xml_data(xn_state);
3597 if (dev_avail_since > user_avail_since &&
3598 dev_avail >= res_avail)
3600 res_avail = dev_avail;
3601 if (!is_empty(state))
3603 if (sipe_strequal(state, sipe_activity_map[SIPE_ACTIVITY_ON_PHONE].token)) {
3604 g_free(activity);
3605 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_ON_PHONE));
3606 } else if (sipe_strequal(state, "presenting")) {
3607 g_free(activity);
3608 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_IN_CONF));
3609 } else {
3610 activity = state;
3611 state = NULL;
3613 activity_since = dev_avail_since;
3615 status_id = sipe_get_status_by_availability(res_avail, &activity);
3617 g_free(state);
3621 /* oof */
3622 if (xn_oof && res_avail >= 15000) { /* 12000 in 2007 */
3623 g_free(activity);
3624 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_OOF));
3625 activity_since = 0;
3628 sbuddy = g_hash_table_lookup(sipe_private->buddies, uri);
3629 if (sbuddy)
3631 g_free(sbuddy->activity);
3632 sbuddy->activity = activity;
3633 activity = NULL;
3635 sbuddy->activity_since = activity_since;
3637 sbuddy->user_avail = user_avail;
3638 sbuddy->user_avail_since = user_avail_since;
3640 g_free(sbuddy->note);
3641 sbuddy->note = NULL;
3642 if (!is_empty(note)) { sbuddy->note = g_markup_escape_text(note, -1); }
3644 sbuddy->is_oof_note = (xn_oof != NULL);
3646 g_free(sbuddy->device_name);
3647 sbuddy->device_name = NULL;
3648 if (!is_empty(device_name)) { sbuddy->device_name = g_strdup(device_name); }
3650 if (!is_empty(cal_free_busy_base64)) {
3651 g_free(sbuddy->cal_start_time);
3652 sbuddy->cal_start_time = g_strdup(cal_start_time);
3654 sbuddy->cal_granularity = sipe_strcase_equal(cal_granularity, "PT15M") ? 15 : 0;
3656 g_free(sbuddy->cal_free_busy_base64);
3657 sbuddy->cal_free_busy_base64 = cal_free_busy_base64;
3658 cal_free_busy_base64 = NULL;
3660 g_free(sbuddy->cal_free_busy);
3661 sbuddy->cal_free_busy = NULL;
3664 sbuddy->last_non_cal_status_id = status_id;
3665 g_free(sbuddy->last_non_cal_activity);
3666 sbuddy->last_non_cal_activity = g_strdup(sbuddy->activity);
3668 if (sipe_strcase_equal(sbuddy->name, self_uri)) {
3669 if (!sipe_strequal(sbuddy->note, sip->note)) /* not same */
3671 sip->is_oof_note = sbuddy->is_oof_note;
3673 g_free(sip->note);
3674 sip->note = g_strdup(sbuddy->note);
3676 sip->note_since = time(NULL);
3679 g_free(sip->status);
3680 sip->status = g_strdup(sbuddy->last_non_cal_status_id);
3683 g_free(cal_free_busy_base64);
3684 g_free(activity);
3686 SIPE_DEBUG_INFO("process_incoming_notify_msrtc: status(%s)", status_id);
3687 sipe_core_buddy_got_status(SIPE_CORE_PUBLIC, uri, status_id);
3689 if (!SIPE_CORE_PRIVATE_FLAG_IS(OCS2007) && sipe_strcase_equal(self_uri, uri)) {
3690 sipe_user_info_has_updated(sipe_private, xn_userinfo);
3693 g_free(note);
3694 sipe_xml_free(xn_presentity);
3695 g_free(uri);
3696 g_free(self_uri);
3699 static void sipe_presence_mime_cb(gpointer user_data, /* sipe_core_private */
3700 const GSList *fields,
3701 const gchar *body,
3702 gsize length)
3704 const gchar *type = sipe_utils_nameval_find(fields, "Content-Type");
3706 if (strstr(type,"application/rlmi+xml")) {
3707 process_incoming_notify_rlmi_resub(user_data, body, length);
3708 } else if (strstr(type, "text/xml+msrtc.pidf")) {
3709 process_incoming_notify_msrtc(user_data, body, length);
3710 } else {
3711 process_incoming_notify_rlmi(user_data, body, length);
3715 static void sipe_process_presence(struct sipe_core_private *sipe_private,
3716 struct sipmsg *msg)
3718 const char *ctype = sipmsg_find_header(msg, "Content-Type");
3720 SIPE_DEBUG_INFO("sipe_process_presence: Content-Type: %s", ctype ? ctype : "");
3722 if (ctype &&
3723 (strstr(ctype, "application/rlmi+xml") ||
3724 strstr(ctype, "application/msrtc-event-categories+xml")))
3726 if (strstr(ctype, "multipart"))
3728 sipe_mime_parts_foreach(ctype, msg->body, sipe_presence_mime_cb, sipe_private);
3730 else if(strstr(ctype, "application/msrtc-event-categories+xml") )
3732 process_incoming_notify_rlmi(sipe_private, msg->body, msg->bodylen);
3734 else if(strstr(ctype, "application/rlmi+xml"))
3736 process_incoming_notify_rlmi_resub(sipe_private, msg->body, msg->bodylen);
3739 else if(ctype && strstr(ctype, "text/xml+msrtc.pidf"))
3741 process_incoming_notify_msrtc(sipe_private, msg->body, msg->bodylen);
3743 else
3745 process_incoming_notify_pidf(sipe_private, msg->body, msg->bodylen);
3749 static void sipe_presence_timeout_mime_cb(gpointer user_data,
3750 SIPE_UNUSED_PARAMETER const GSList *fields,
3751 const gchar *body,
3752 gsize length)
3754 GSList **buddies = user_data;
3755 sipe_xml *xml = sipe_xml_parse(body, length);
3757 if (xml && !sipe_strequal(sipe_xml_name(xml), "list")) {
3758 const gchar *uri = sipe_xml_attribute(xml, "uri");
3759 const sipe_xml *xn_category;
3762 * automaton: presence is never expected to change
3764 * see: http://msdn.microsoft.com/en-us/library/ee354295(office.13).aspx
3766 for (xn_category = sipe_xml_child(xml, "category");
3767 xn_category;
3768 xn_category = sipe_xml_twin(xn_category)) {
3769 if (sipe_strequal(sipe_xml_attribute(xn_category, "name"),
3770 "contactCard")) {
3771 const sipe_xml *node = sipe_xml_child(xn_category, "contactCard/automaton");
3772 if (node) {
3773 char *boolean = sipe_xml_data(node);
3774 if (sipe_strequal(boolean, "true")) {
3775 SIPE_DEBUG_INFO("sipe_process_presence_timeout: %s is an automaton: - not subscribing to presence updates",
3776 uri);
3777 uri = NULL;
3779 g_free(boolean);
3781 break;
3785 if (uri) {
3786 *buddies = g_slist_append(*buddies, sip_uri(uri));
3790 sipe_xml_free(xml);
3793 static void sipe_process_presence_timeout(struct sipe_core_private *sipe_private,
3794 struct sipmsg *msg, gchar *who,
3795 int timeout)
3797 const char *ctype = sipmsg_find_header(msg, "Content-Type");
3798 gchar *action_name = sipe_utils_presence_key(who);
3800 SIPE_DEBUG_INFO("sipe_process_presence_timeout: Content-Type: %s", ctype ? ctype : "");
3802 if (ctype &&
3803 strstr(ctype, "multipart") &&
3804 (strstr(ctype, "application/rlmi+xml") ||
3805 strstr(ctype, "application/msrtc-event-categories+xml"))) {
3806 GSList *buddies = NULL;
3808 sipe_mime_parts_foreach(ctype, msg->body, sipe_presence_timeout_mime_cb, &buddies);
3810 if (buddies) {
3811 struct presence_batched_routed *payload = g_malloc(sizeof(struct presence_batched_routed));
3812 payload->host = g_strdup(who);
3813 payload->buddies = buddies;
3814 sipe_schedule_seconds(sipe_private,
3815 action_name,
3816 payload,
3817 timeout,
3818 sipe_subscribe_presence_batched_routed,
3819 sipe_subscribe_presence_batched_routed_free);
3820 SIPE_DEBUG_INFO("Resubscription multiple contacts with batched support & route(%s) in %d", who, timeout);
3823 } else {
3824 sipe_schedule_seconds(sipe_private,
3825 action_name,
3826 g_strdup(who),
3827 timeout,
3828 sipe_subscribe_presence_single,
3829 g_free);
3830 SIPE_DEBUG_INFO("Resubscription single contact with batched support(%s) in %d", who, timeout);
3832 g_free(action_name);
3836 * Dispatcher for all incoming subscription information
3837 * whether it comes from NOTIFY, BENOTIFY requests or
3838 * piggy-backed to subscription's OK responce.
3840 * @param request whether initiated from BE/NOTIFY request or OK-response message.
3841 * @param benotify whether initiated from NOTIFY or BENOTIFY request.
3843 void process_incoming_notify(struct sipe_core_private *sipe_private,
3844 struct sipmsg *msg,
3845 gboolean request, gboolean benotify)
3847 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
3848 const gchar *content_type = sipmsg_find_header(msg, "Content-Type");
3849 const gchar *event = sipmsg_find_header(msg, "Event");
3850 const gchar *subscription_state = sipmsg_find_header(msg, "subscription-state");
3852 SIPE_DEBUG_INFO("process_incoming_notify: subscription_state: %s", subscription_state ? subscription_state : "");
3854 /* implicit subscriptions */
3855 if (content_type && g_str_has_prefix(content_type, "application/ms-imdn+xml")) {
3856 sipe_process_imdn(sipe_private, msg);
3859 if (event) {
3860 /* for one off subscriptions (send with Expire: 0) */
3861 if (sipe_strcase_equal(event, "vnd-microsoft-provisioning-v2"))
3863 sipe_process_provisioning_v2(sipe_private, msg);
3865 else if (sipe_strcase_equal(event, "vnd-microsoft-provisioning"))
3867 sipe_process_provisioning(sipe_private, msg);
3869 else if (sipe_strcase_equal(event, "presence"))
3871 sipe_process_presence(sipe_private, msg);
3873 else if (sipe_strcase_equal(event, "registration-notify"))
3875 sipe_process_registration_notify(sipe_private, msg);
3878 if (!subscription_state || strstr(subscription_state, "active"))
3880 if (sipe_strcase_equal(event, "vnd-microsoft-roaming-contacts"))
3882 sipe_process_roaming_contacts(sipe_private, msg);
3884 else if (sipe_strcase_equal(event, "vnd-microsoft-roaming-self"))
3886 sipe_process_roaming_self(sipe_private, msg);
3888 else if (sipe_strcase_equal(event, "vnd-microsoft-roaming-ACL"))
3890 sipe_process_roaming_acl(sipe_private, msg);
3892 else if (sipe_strcase_equal(event, "presence.wpending"))
3894 sipe_process_presence_wpending(sipe_private, msg);
3896 else if (sipe_strcase_equal(event, "conference"))
3898 sipe_process_conference(sipe_private, msg);
3903 /* The server sends status 'terminated' */
3904 if (subscription_state && strstr(subscription_state, "terminated") ) {
3905 gchar *who = parse_from(sipmsg_find_header(msg, request ? "From" : "To"));
3906 gchar *key = sipe_utils_subscription_key(event, who);
3908 SIPE_DEBUG_INFO("process_incoming_notify: server says that subscription to %s was terminated.", who);
3909 g_free(who);
3911 sipe_subscriptions_remove(sipe_private, key);
3912 g_free(key);
3915 if (!request && event) {
3916 const gchar *expires_header = sipmsg_find_header(msg, "Expires");
3917 int timeout = expires_header ? strtol(expires_header, NULL, 10) : 0;
3918 SIPE_DEBUG_INFO("process_incoming_notify: subscription expires:%d", timeout);
3920 if (timeout) {
3921 /* 2 min ahead of expiration */
3922 timeout = (timeout - 120) > 120 ? (timeout - 120) : timeout;
3924 if (sipe_strcase_equal(event, "presence.wpending") &&
3925 g_slist_find_custom(sip->allow_events, "presence.wpending", (GCompareFunc)g_ascii_strcasecmp))
3927 gchar *action_name = g_strdup_printf("<%s>", "presence.wpending");
3928 sipe_schedule_seconds(sipe_private,
3929 action_name,
3930 NULL,
3931 timeout,
3932 sipe_subscribe_presence_wpending,
3933 NULL);
3934 g_free(action_name);
3936 else if (sipe_strcase_equal(event, "presence") &&
3937 g_slist_find_custom(sip->allow_events, "presence", (GCompareFunc)g_ascii_strcasecmp))
3939 gchar *who = parse_from(sipmsg_find_header(msg, "To"));
3940 gchar *action_name = sipe_utils_presence_key(who);
3942 if (sip->batched_support) {
3943 sipe_process_presence_timeout(sipe_private, msg, who, timeout);
3945 else {
3946 sipe_schedule_seconds(sipe_private,
3947 action_name,
3948 g_strdup(who),
3949 timeout,
3950 sipe_subscribe_presence_single,
3951 g_free);
3952 SIPE_DEBUG_INFO("Resubscription single contact (%s) in %d", who, timeout);
3954 g_free(action_name);
3955 g_free(who);
3960 /* The client responses on received a NOTIFY message */
3961 if (request && !benotify)
3963 sip_transport_response(sipe_private, msg, 200, "OK", NULL);
3968 * Whether user manually changed status or
3969 * it was changed automatically due to user
3970 * became inactive/active again
3972 static gboolean
3973 sipe_is_user_state(struct sipe_core_private *sipe_private)
3975 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
3976 gboolean res;
3977 time_t now = time(NULL);
3979 SIPE_DEBUG_INFO("sipe_is_user_state: sip->idle_switch : %s", asctime(localtime(&(sip->idle_switch))));
3980 SIPE_DEBUG_INFO("sipe_is_user_state: now : %s", asctime(localtime(&now)));
3982 res = ((now - SIPE_IDLE_SET_DELAY * 2) >= sip->idle_switch);
3984 SIPE_DEBUG_INFO("sipe_is_user_state: res = %s", res ? "USER" : "MACHINE");
3985 return res;
3988 static void
3989 send_presence_soap0(struct sipe_core_private *sipe_private,
3990 gboolean do_publish_calendar,
3991 gboolean do_reset_status)
3993 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
3994 struct sipe_calendar* cal = sip->cal;
3995 int availability = 0;
3996 int activity = 0;
3997 gchar *body;
3998 gchar *tmp;
3999 gchar *tmp2 = NULL;
4000 gchar *res_note = NULL;
4001 gchar *res_oof = NULL;
4002 const gchar *note_pub = NULL;
4003 gchar *states = NULL;
4004 gchar *calendar_data = NULL;
4005 gchar *epid = get_epid(sipe_private);
4006 time_t now = time(NULL);
4007 gchar *since_time_str = sipe_utils_time_to_str(now);
4008 const gchar *oof_note = cal ? sipe_ews_get_oof_note(cal) : NULL;
4009 const char *user_input;
4010 gboolean pub_oof = cal && oof_note && (!sip->note || cal->updated > sip->note_since);
4012 if (oof_note && sip->note) {
4013 SIPE_DEBUG_INFO("cal->oof_start : %s", asctime(localtime(&(cal->oof_start))));
4014 SIPE_DEBUG_INFO("sip->note_since : %s", asctime(localtime(&(sip->note_since))));
4017 SIPE_DEBUG_INFO("sip->note : %s", sip->note ? sip->note : "");
4019 if (!sip->initial_state_published ||
4020 do_reset_status)
4022 g_free(sip->status);
4023 sip->status = g_strdup(SIPE_STATUS_ID_AVAILABLE);
4026 sipe_get_act_avail_by_status_2005(sip->status, &activity, &availability);
4028 /* Note */
4029 if (pub_oof) {
4030 note_pub = oof_note;
4031 res_oof = SIPE_SOAP_SET_PRESENCE_OOF_XML;
4032 cal->published = TRUE;
4033 } else if (sip->note) {
4034 if (sip->is_oof_note && !oof_note) { /* stale OOF note, as it's not present in cal already */
4035 g_free(sip->note);
4036 sip->note = NULL;
4037 sip->is_oof_note = FALSE;
4038 sip->note_since = 0;
4039 } else {
4040 note_pub = sip->note;
4041 res_oof = sip->is_oof_note ? SIPE_SOAP_SET_PRESENCE_OOF_XML : "";
4045 if (note_pub)
4047 /* to protocol internal plain text format */
4048 tmp = sipe_backend_markup_strip_html(note_pub);
4049 res_note = g_markup_printf_escaped(SIPE_SOAP_SET_PRESENCE_NOTE_XML, tmp);
4050 g_free(tmp);
4053 /* User State */
4054 if (!do_reset_status) {
4055 if (sipe_is_user_state(sipe_private) && !do_publish_calendar && sip->initial_state_published)
4057 gchar *activity_token = NULL;
4058 int avail_2007 = sipe_get_availability_by_status(sip->status, &activity_token);
4060 states = g_strdup_printf(SIPE_SOAP_SET_PRESENCE_STATES,
4061 avail_2007,
4062 since_time_str,
4063 epid,
4064 activity_token);
4065 g_free(activity_token);
4067 else /* preserve existing publication */
4069 if (sip->user_states) {
4070 states = g_strdup(sip->user_states);
4073 } else {
4074 /* do nothing - then User state will be erased */
4076 sip->initial_state_published = TRUE;
4078 /* CalendarInfo */
4079 if (cal && (!is_empty(cal->legacy_dn) || !is_empty(cal->email)) && cal->fb_start && !is_empty(cal->free_busy))
4081 char *fb_start_str = sipe_utils_time_to_str(cal->fb_start);
4082 char *free_busy_base64 = sipe_cal_get_freebusy_base64(cal->free_busy);
4083 calendar_data = g_strdup_printf(SIPE_SOAP_SET_PRESENCE_CALENDAR,
4084 !is_empty(cal->legacy_dn) ? cal->legacy_dn : cal->email,
4085 fb_start_str,
4086 free_busy_base64);
4087 g_free(fb_start_str);
4088 g_free(free_busy_base64);
4091 user_input = (sipe_is_user_state(sipe_private) ||
4092 sipe_strequal(sip->status, SIPE_STATUS_ID_AVAILABLE)) ?
4093 "active" : "idle";
4095 /* forming resulting XML */
4096 body = g_strdup_printf(SIPE_SOAP_SET_PRESENCE,
4097 sipe_private->username,
4098 availability,
4099 activity,
4100 (tmp = g_ascii_strup(g_get_host_name(), -1)),
4101 res_note ? res_note : "",
4102 res_oof ? res_oof : "",
4103 states ? states : "",
4104 calendar_data ? calendar_data : "",
4105 epid,
4106 since_time_str,
4107 since_time_str,
4108 user_input);
4109 g_free(tmp);
4110 g_free(tmp2);
4111 g_free(res_note);
4112 g_free(states);
4113 g_free(calendar_data);
4115 send_soap_request(sipe_private, body);
4117 g_free(body);
4118 g_free(since_time_str);
4119 g_free(epid);
4122 void
4123 send_presence_soap(struct sipe_core_private *sipe_private,
4124 gboolean do_publish_calendar)
4126 return send_presence_soap0(sipe_private, do_publish_calendar, FALSE);
4130 static gboolean
4131 process_send_presence_category_publish_response(struct sipe_core_private *sipe_private,
4132 struct sipmsg *msg,
4133 struct transaction *trans)
4135 const gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
4137 if (msg->response == 409 && g_str_has_prefix(contenttype, "application/msrtc-fault+xml")) {
4138 sipe_xml *xml;
4139 const sipe_xml *node;
4140 gchar *fault_code;
4141 GHashTable *faults;
4142 int index_our;
4143 gboolean has_device_publication = FALSE;
4145 xml = sipe_xml_parse(msg->body, msg->bodylen);
4147 /* test if version mismatch fault */
4148 fault_code = sipe_xml_data(sipe_xml_child(xml, "Faultcode"));
4149 if (!sipe_strequal(fault_code, "Client.BadCall.WrongDelta")) {
4150 SIPE_DEBUG_INFO("process_send_presence_category_publish_response: unsupported fault code:%s returning.", fault_code);
4151 g_free(fault_code);
4152 sipe_xml_free(xml);
4153 return TRUE;
4155 g_free(fault_code);
4157 /* accumulating information about faulty versions */
4158 faults = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
4159 for (node = sipe_xml_child(xml, "details/operation");
4160 node;
4161 node = sipe_xml_twin(node))
4163 const gchar *index = sipe_xml_attribute(node, "index");
4164 const gchar *curVersion = sipe_xml_attribute(node, "curVersion");
4166 g_hash_table_insert(faults, g_strdup(index), g_strdup(curVersion));
4167 SIPE_DEBUG_INFO("fault added: index:%s curVersion:%s", index, curVersion);
4169 sipe_xml_free(xml);
4171 /* here we are parsing our own request to figure out what publication
4172 * referenced here only by index went wrong
4174 xml = sipe_xml_parse(trans->msg->body, trans->msg->bodylen);
4176 /* publication */
4177 for (node = sipe_xml_child(xml, "publications/publication"),
4178 index_our = 1; /* starts with 1 - our first publication */
4179 node;
4180 node = sipe_xml_twin(node), index_our++)
4182 gchar *idx = g_strdup_printf("%d", index_our);
4183 const gchar *curVersion = g_hash_table_lookup(faults, idx);
4184 const gchar *categoryName = sipe_xml_attribute(node, "categoryName");
4185 g_free(idx);
4187 if (sipe_strequal("device", categoryName)) {
4188 has_device_publication = TRUE;
4191 if (curVersion) { /* fault exist on this index */
4192 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
4193 const gchar *container = sipe_xml_attribute(node, "container");
4194 const gchar *instance = sipe_xml_attribute(node, "instance");
4195 /* key is <category><instance><container> */
4196 gchar *key = g_strdup_printf("<%s><%s><%s>", categoryName, instance, container);
4197 GHashTable *category = g_hash_table_lookup(sip->our_publications, categoryName);
4199 if (category) {
4200 struct sipe_publication *publication =
4201 g_hash_table_lookup(category, key);
4203 SIPE_DEBUG_INFO("key is %s", key);
4205 if (publication) {
4206 SIPE_DEBUG_INFO("Updating %s with version %s. Was %d before.",
4207 key, curVersion, publication->version);
4208 /* updating publication's version to the correct one */
4209 publication->version = atoi(curVersion);
4211 } else {
4212 /* We somehow lost this category from our publications... */
4213 struct sipe_publication *publication = g_new0(struct sipe_publication, 1);
4214 publication->category = g_strdup(categoryName);
4215 publication->instance = atoi(instance);
4216 publication->container = atoi(container);
4217 publication->version = atoi(curVersion);
4218 category = g_hash_table_new_full(g_str_hash, g_str_equal,
4219 g_free, (GDestroyNotify)free_publication);
4220 g_hash_table_insert(category, g_strdup(key), publication);
4221 g_hash_table_insert(sip->our_publications, g_strdup(categoryName), category);
4222 SIPE_DEBUG_INFO("added lost category '%s' key '%s'", categoryName, key);
4224 g_free(key);
4227 sipe_xml_free(xml);
4228 g_hash_table_destroy(faults);
4230 /* rebublishing with right versions */
4231 if (has_device_publication) {
4232 send_publish_category_initial(sipe_private);
4233 } else {
4234 send_presence_status(sipe_private, NULL);
4237 return TRUE;
4241 * Returns 'device' XML part for publication.
4242 * Must be g_free'd after use.
4244 static gchar *
4245 sipe_publish_get_category_device(struct sipe_core_private *sipe_private)
4247 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
4248 gchar *uri;
4249 gchar *doc;
4250 gchar *epid = get_epid(sipe_private);
4251 gchar *uuid = generateUUIDfromEPID(epid);
4252 guint device_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_DEVICE);
4253 /* key is <category><instance><container> */
4254 gchar *key = g_strdup_printf("<%s><%u><%u>", "device", device_instance, 2);
4255 struct sipe_publication *publication =
4256 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "device"), key);
4258 g_free(key);
4259 g_free(epid);
4261 uri = sip_uri_self(sipe_private);
4262 doc = g_strdup_printf(SIPE_PUB_XML_DEVICE,
4263 device_instance,
4264 publication ? publication->version : 0,
4265 uuid,
4266 uri,
4267 "00:00:00+01:00", /* @TODO make timezone real*/
4268 g_get_host_name()
4271 g_free(uri);
4272 g_free(uuid);
4274 return doc;
4278 * A service method - use
4279 * - send_publish_get_category_state_machine and
4280 * - send_publish_get_category_state_user instead.
4281 * Must be g_free'd after use.
4283 static gchar *
4284 sipe_publish_get_category_state(struct sipe_core_private *sipe_private,
4285 gboolean is_user_state)
4287 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
4288 int availability = sipe_get_availability_by_status(sip->status, NULL);
4289 guint instance = is_user_state ? sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_USER) :
4290 sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_MACHINE);
4291 /* key is <category><instance><container> */
4292 gchar *key_2 = g_strdup_printf("<%s><%u><%u>", "state", instance, 2);
4293 gchar *key_3 = g_strdup_printf("<%s><%u><%u>", "state", instance, 3);
4294 struct sipe_publication *publication_2 =
4295 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_2);
4296 struct sipe_publication *publication_3 =
4297 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_3);
4299 g_free(key_2);
4300 g_free(key_3);
4302 if (publication_2 && (publication_2->availability == availability))
4304 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_state: state has NOT changed. Exiting.");
4305 return NULL; /* nothing to update */
4308 return g_strdup_printf( is_user_state ? SIPE_PUB_XML_STATE_USER : SIPE_PUB_XML_STATE_MACHINE,
4309 instance,
4310 publication_2 ? publication_2->version : 0,
4311 availability,
4312 instance,
4313 publication_3 ? publication_3->version : 0,
4314 availability);
4318 * Only Busy and OOF calendar event are published.
4319 * Different instances are used for that.
4321 * Must be g_free'd after use.
4323 static gchar *
4324 sipe_publish_get_category_state_calendar(struct sipe_core_private *sipe_private,
4325 struct sipe_cal_event *event,
4326 const char *uri,
4327 int cal_satus)
4329 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
4330 gchar *start_time_str;
4331 int availability = 0;
4332 gchar *res;
4333 gchar *tmp = NULL;
4334 guint instance = (cal_satus == SIPE_CAL_OOF) ?
4335 sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_CALENDAR_OOF) :
4336 sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_CALENDAR);
4338 /* key is <category><instance><container> */
4339 gchar *key_2 = g_strdup_printf("<%s><%u><%u>", "state", instance, 2);
4340 gchar *key_3 = g_strdup_printf("<%s><%u><%u>", "state", instance, 3);
4341 struct sipe_publication *publication_2 =
4342 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_2);
4343 struct sipe_publication *publication_3 =
4344 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_3);
4346 g_free(key_2);
4347 g_free(key_3);
4349 if (!publication_3 && !event) { /* was nothing, have nothing, exiting */
4350 SIPE_DEBUG_INFO("sipe_publish_get_category_state_calendar: "
4351 "Exiting as no publication and no event for cal_satus:%d", cal_satus);
4352 return NULL;
4355 if (event &&
4356 publication_3 &&
4357 (publication_3->availability == availability) &&
4358 sipe_strequal(publication_3->cal_event_hash, (tmp = sipe_cal_event_hash(event))))
4360 g_free(tmp);
4361 SIPE_DEBUG_INFO("sipe_publish_get_category_state_calendar: "
4362 "cal state has NOT changed for cal_satus:%d. Exiting.", cal_satus);
4363 return NULL; /* nothing to update */
4365 g_free(tmp);
4367 if (event &&
4368 (event->cal_status == SIPE_CAL_BUSY ||
4369 event->cal_status == SIPE_CAL_OOF))
4371 gchar *availability_xml_str = NULL;
4372 gchar *activity_xml_str = NULL;
4373 gchar *escaped_subject = event->subject ? g_markup_escape_text(event->subject, -1) : NULL;
4374 gchar *escaped_location = event->location ? g_markup_escape_text(event->location, -1) : NULL;
4376 if (event->cal_status == SIPE_CAL_BUSY) {
4377 availability_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_AVAIL, 6500);
4380 if (event->cal_status == SIPE_CAL_BUSY && event->is_meeting) {
4381 activity_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_ACTIVITY,
4382 sipe_activity_map[SIPE_ACTIVITY_IN_MEETING].token,
4383 "minAvailability=\"6500\"",
4384 "maxAvailability=\"8999\"");
4385 } else if (event->cal_status == SIPE_CAL_OOF) {
4386 activity_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_ACTIVITY,
4387 sipe_activity_map[SIPE_ACTIVITY_OOF].token,
4388 "minAvailability=\"12000\"",
4389 "");
4391 start_time_str = sipe_utils_time_to_str(event->start_time);
4393 res = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR,
4394 instance,
4395 publication_2 ? publication_2->version : 0,
4396 uri,
4397 start_time_str,
4398 availability_xml_str ? availability_xml_str : "",
4399 activity_xml_str ? activity_xml_str : "",
4400 escaped_subject ? escaped_subject : "",
4401 escaped_location ? escaped_location : "",
4403 instance,
4404 publication_3 ? publication_3->version : 0,
4405 uri,
4406 start_time_str,
4407 availability_xml_str ? availability_xml_str : "",
4408 activity_xml_str ? activity_xml_str : "",
4409 escaped_subject ? escaped_subject : "",
4410 escaped_location ? escaped_location : ""
4412 g_free(escaped_location);
4413 g_free(escaped_subject);
4414 g_free(start_time_str);
4415 g_free(availability_xml_str);
4416 g_free(activity_xml_str);
4419 else /* including !event, SIPE_CAL_FREE, SIPE_CAL_TENTATIVE */
4421 res = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_CLEAR,
4422 instance,
4423 publication_2 ? publication_2->version : 0,
4425 instance,
4426 publication_3 ? publication_3->version : 0
4430 return res;
4434 * Returns 'machineState' XML part for publication.
4435 * Must be g_free'd after use.
4437 static gchar *
4438 sipe_publish_get_category_state_machine(struct sipe_core_private *sipe_private)
4440 return sipe_publish_get_category_state(sipe_private, FALSE);
4444 * Returns 'userState' XML part for publication.
4445 * Must be g_free'd after use.
4447 static gchar *
4448 sipe_publish_get_category_state_user(struct sipe_core_private *sipe_private)
4450 return sipe_publish_get_category_state(sipe_private, TRUE);
4454 * Returns 'note' XML part for publication.
4455 * Must be g_free'd after use.
4457 * Protocol format for Note is plain text.
4459 * @param note a note in Sipe internal HTML format
4460 * @param note_type either personal or OOF
4462 static gchar *
4463 sipe_publish_get_category_note(struct sipe_core_private *sipe_private,
4464 const char *note, /* html */
4465 const char *note_type,
4466 time_t note_start,
4467 time_t note_end)
4469 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
4470 guint instance = sipe_strequal("OOF", note_type) ? sipe_get_pub_instance(sipe_private, SIPE_PUB_NOTE_OOF) : 0;
4471 /* key is <category><instance><container> */
4472 gchar *key_note_200 = g_strdup_printf("<%s><%u><%u>", "note", instance, 200);
4473 gchar *key_note_300 = g_strdup_printf("<%s><%u><%u>", "note", instance, 300);
4474 gchar *key_note_400 = g_strdup_printf("<%s><%u><%u>", "note", instance, 400);
4476 struct sipe_publication *publication_note_200 =
4477 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_200);
4478 struct sipe_publication *publication_note_300 =
4479 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_300);
4480 struct sipe_publication *publication_note_400 =
4481 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_400);
4483 char *tmp = note ? sipe_backend_markup_strip_html(note) : NULL;
4484 char *n1 = tmp ? g_markup_escape_text(tmp, -1) : NULL;
4485 const char *n2 = publication_note_200 ? publication_note_200->note : NULL;
4486 char *res, *tmp1, *tmp2, *tmp3;
4487 char *start_time_attr;
4488 char *end_time_attr;
4490 g_free(tmp);
4491 tmp = NULL;
4492 g_free(key_note_200);
4493 g_free(key_note_300);
4494 g_free(key_note_400);
4496 /* we even need to republish empty note */
4497 if (sipe_strequal(n1, n2))
4499 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_note: note has NOT changed. Exiting.");
4500 g_free(n1);
4501 return NULL; /* nothing to update */
4504 start_time_attr = note_start ? g_strdup_printf(" startTime=\"%s\"", (tmp = sipe_utils_time_to_str(note_start))) : NULL;
4505 g_free(tmp);
4506 tmp = NULL;
4507 end_time_attr = note_end ? g_strdup_printf(" endTime=\"%s\"", (tmp = sipe_utils_time_to_str(note_end))) : NULL;
4508 g_free(tmp);
4510 if (n1) {
4511 tmp1 = g_strdup_printf(SIPE_PUB_XML_NOTE,
4512 instance,
4513 200,
4514 publication_note_200 ? publication_note_200->version : 0,
4515 note_type,
4516 start_time_attr ? start_time_attr : "",
4517 end_time_attr ? end_time_attr : "",
4518 n1);
4520 tmp2 = g_strdup_printf(SIPE_PUB_XML_NOTE,
4521 instance,
4522 300,
4523 publication_note_300 ? publication_note_300->version : 0,
4524 note_type,
4525 start_time_attr ? start_time_attr : "",
4526 end_time_attr ? end_time_attr : "",
4527 n1);
4529 tmp3 = g_strdup_printf(SIPE_PUB_XML_NOTE,
4530 instance,
4531 400,
4532 publication_note_400 ? publication_note_400->version : 0,
4533 note_type,
4534 start_time_attr ? start_time_attr : "",
4535 end_time_attr ? end_time_attr : "",
4536 n1);
4537 } else {
4538 tmp1 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
4539 "note",
4540 instance,
4541 200,
4542 publication_note_200 ? publication_note_200->version : 0,
4543 "static");
4544 tmp2 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
4545 "note",
4546 instance,
4547 300,
4548 publication_note_200 ? publication_note_200->version : 0,
4549 "static");
4550 tmp3 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
4551 "note",
4552 instance,
4553 400,
4554 publication_note_200 ? publication_note_200->version : 0,
4555 "static");
4557 res = g_strconcat(tmp1, tmp2, tmp3, NULL);
4559 g_free(start_time_attr);
4560 g_free(end_time_attr);
4561 g_free(tmp1);
4562 g_free(tmp2);
4563 g_free(tmp3);
4564 g_free(n1);
4566 return res;
4570 * Returns 'calendarData' XML part with WorkingHours for publication.
4571 * Must be g_free'd after use.
4573 static gchar *
4574 sipe_publish_get_category_cal_working_hours(struct sipe_core_private *sipe_private)
4576 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
4577 struct sipe_calendar* cal = sip->cal;
4579 /* key is <category><instance><container> */
4580 gchar *key_cal_1 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 1);
4581 gchar *key_cal_100 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 100);
4582 gchar *key_cal_200 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 200);
4583 gchar *key_cal_300 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 300);
4584 gchar *key_cal_400 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 400);
4585 gchar *key_cal_32000 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 32000);
4587 struct sipe_publication *publication_cal_1 =
4588 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_1);
4589 struct sipe_publication *publication_cal_100 =
4590 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_100);
4591 struct sipe_publication *publication_cal_200 =
4592 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_200);
4593 struct sipe_publication *publication_cal_300 =
4594 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_300);
4595 struct sipe_publication *publication_cal_400 =
4596 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_400);
4597 struct sipe_publication *publication_cal_32000 =
4598 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_32000);
4600 const char *n1 = cal ? cal->working_hours_xml_str : NULL;
4601 const char *n2 = publication_cal_300 ? publication_cal_300->working_hours_xml_str : NULL;
4603 g_free(key_cal_1);
4604 g_free(key_cal_100);
4605 g_free(key_cal_200);
4606 g_free(key_cal_300);
4607 g_free(key_cal_400);
4608 g_free(key_cal_32000);
4610 if (!cal || is_empty(cal->email) || is_empty(cal->working_hours_xml_str)) {
4611 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_cal_working_hours: no data to publish, exiting");
4612 return NULL;
4615 if (sipe_strequal(n1, n2))
4617 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_cal_working_hours: WorkingHours has NOT changed. Exiting.");
4618 return NULL; /* nothing to update */
4621 return g_strdup_printf(SIPE_PUB_XML_WORKING_HOURS,
4622 /* 1 */
4623 publication_cal_1 ? publication_cal_1->version : 0,
4624 cal->email,
4625 cal->working_hours_xml_str,
4626 /* 100 - Public */
4627 publication_cal_100 ? publication_cal_100->version : 0,
4628 /* 200 - Company */
4629 publication_cal_200 ? publication_cal_200->version : 0,
4630 cal->email,
4631 cal->working_hours_xml_str,
4632 /* 300 - Team */
4633 publication_cal_300 ? publication_cal_300->version : 0,
4634 cal->email,
4635 cal->working_hours_xml_str,
4636 /* 400 - Personal */
4637 publication_cal_400 ? publication_cal_400->version : 0,
4638 cal->email,
4639 cal->working_hours_xml_str,
4640 /* 32000 - Blocked */
4641 publication_cal_32000 ? publication_cal_32000->version : 0
4646 * Returns 'calendarData' XML part with FreeBusy for publication.
4647 * Must be g_free'd after use.
4649 static gchar *
4650 sipe_publish_get_category_cal_free_busy(struct sipe_core_private *sipe_private)
4652 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
4653 struct sipe_calendar* cal = sip->cal;
4654 guint cal_data_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_CALENDAR_DATA);
4655 char *fb_start_str;
4656 char *free_busy_base64;
4657 /* const char *st; */
4658 /* const char *fb; */
4659 char *res;
4661 /* key is <category><instance><container> */
4662 gchar *key_cal_1 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 1);
4663 gchar *key_cal_100 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 100);
4664 gchar *key_cal_200 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 200);
4665 gchar *key_cal_300 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 300);
4666 gchar *key_cal_400 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 400);
4667 gchar *key_cal_32000 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 32000);
4669 struct sipe_publication *publication_cal_1 =
4670 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_1);
4671 struct sipe_publication *publication_cal_100 =
4672 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_100);
4673 struct sipe_publication *publication_cal_200 =
4674 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_200);
4675 struct sipe_publication *publication_cal_300 =
4676 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_300);
4677 struct sipe_publication *publication_cal_400 =
4678 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_400);
4679 struct sipe_publication *publication_cal_32000 =
4680 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_32000);
4682 g_free(key_cal_1);
4683 g_free(key_cal_100);
4684 g_free(key_cal_200);
4685 g_free(key_cal_300);
4686 g_free(key_cal_400);
4687 g_free(key_cal_32000);
4689 if (!cal || is_empty(cal->email) || !cal->fb_start || is_empty(cal->free_busy)) {
4690 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_cal_free_busy: no data to publish, exiting");
4691 return NULL;
4694 fb_start_str = sipe_utils_time_to_str(cal->fb_start);
4695 free_busy_base64 = sipe_cal_get_freebusy_base64(cal->free_busy);
4697 /* we will rebuplish the same data to refresh publication time,
4698 * so if data from multiple sources, most recent will be choosen
4700 // st = publication_cal_300 ? publication_cal_300->fb_start_str : NULL;
4701 // fb = publication_cal_300 ? publication_cal_300->free_busy_base64 : NULL;
4703 //if (sipe_strequal(st, fb_start_str) && sipe_strequal(fb, free_busy_base64))
4705 // SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_cal_free_busy: FreeBusy has NOT changed. Exiting.");
4706 // g_free(fb_start_str);
4707 // g_free(free_busy_base64);
4708 // return NULL; /* nothing to update */
4711 res = g_strdup_printf(SIPE_PUB_XML_FREE_BUSY,
4712 /* 1 */
4713 cal_data_instance,
4714 publication_cal_1 ? publication_cal_1->version : 0,
4715 /* 100 - Public */
4716 cal_data_instance,
4717 publication_cal_100 ? publication_cal_100->version : 0,
4718 /* 200 - Company */
4719 cal_data_instance,
4720 publication_cal_200 ? publication_cal_200->version : 0,
4721 cal->email,
4722 fb_start_str,
4723 free_busy_base64,
4724 /* 300 - Team */
4725 cal_data_instance,
4726 publication_cal_300 ? publication_cal_300->version : 0,
4727 cal->email,
4728 fb_start_str,
4729 free_busy_base64,
4730 /* 400 - Personal */
4731 cal_data_instance,
4732 publication_cal_400 ? publication_cal_400->version : 0,
4733 cal->email,
4734 fb_start_str,
4735 free_busy_base64,
4736 /* 32000 - Blocked */
4737 cal_data_instance,
4738 publication_cal_32000 ? publication_cal_32000->version : 0
4741 g_free(fb_start_str);
4742 g_free(free_busy_base64);
4743 return res;
4746 static void send_presence_publish(struct sipe_core_private *sipe_private,
4747 const char *publications)
4749 gchar *uri;
4750 gchar *doc;
4751 gchar *tmp;
4752 gchar *hdr;
4754 uri = sip_uri_self(sipe_private);
4755 doc = g_strdup_printf(SIPE_SEND_PRESENCE,
4756 uri,
4757 publications);
4759 tmp = get_contact(sipe_private);
4760 hdr = g_strdup_printf("Contact: %s\r\n"
4761 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp);
4763 sip_transport_service(sipe_private,
4764 uri,
4765 hdr,
4766 doc,
4767 process_send_presence_category_publish_response);
4769 g_free(tmp);
4770 g_free(hdr);
4771 g_free(uri);
4772 g_free(doc);
4775 static void
4776 send_publish_category_initial(struct sipe_core_private *sipe_private)
4778 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
4779 gchar *pub_device = sipe_publish_get_category_device(sipe_private);
4780 gchar *pub_machine;
4781 gchar *publications;
4783 g_free(sip->status);
4784 sip->status = g_strdup(SIPE_STATUS_ID_AVAILABLE); /* our initial state */
4786 pub_machine = sipe_publish_get_category_state_machine(sipe_private);
4787 publications = g_strdup_printf("%s%s",
4788 pub_device,
4789 pub_machine ? pub_machine : "");
4790 g_free(pub_device);
4791 g_free(pub_machine);
4793 send_presence_publish(sipe_private, publications);
4794 g_free(publications);
4797 static void
4798 send_presence_category_publish(struct sipe_core_private *sipe_private)
4800 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
4801 gchar *pub_state = sipe_is_user_state(sipe_private) ?
4802 sipe_publish_get_category_state_user(sipe_private) :
4803 sipe_publish_get_category_state_machine(sipe_private);
4804 gchar *pub_note = sipe_publish_get_category_note(sipe_private,
4805 sip->note,
4806 sip->is_oof_note ? "OOF" : "personal",
4809 gchar *publications;
4811 if (!pub_state && !pub_note) {
4812 SIPE_DEBUG_INFO_NOFORMAT("send_presence_category_publish: nothing has changed. Exiting.");
4813 return;
4816 publications = g_strdup_printf("%s%s",
4817 pub_state ? pub_state : "",
4818 pub_note ? pub_note : "");
4820 g_free(pub_state);
4821 g_free(pub_note);
4823 send_presence_publish(sipe_private, publications);
4824 g_free(publications);
4828 * Publishes self status
4829 * based on own calendar information.
4831 * For 2007+
4833 void
4834 publish_calendar_status_self(struct sipe_core_private *sipe_private,
4835 SIPE_UNUSED_PARAMETER void *unused)
4837 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
4838 struct sipe_cal_event* event = NULL;
4839 gchar *pub_cal_working_hours = NULL;
4840 gchar *pub_cal_free_busy = NULL;
4841 gchar *pub_calendar = NULL;
4842 gchar *pub_calendar2 = NULL;
4843 gchar *pub_oof_note = NULL;
4844 const gchar *oof_note;
4845 time_t oof_start = 0;
4846 time_t oof_end = 0;
4848 if (!sip->cal) {
4849 SIPE_DEBUG_INFO_NOFORMAT("publish_calendar_status_self() no calendar data.");
4850 return;
4853 SIPE_DEBUG_INFO_NOFORMAT("publish_calendar_status_self() started.");
4854 if (sip->cal->cal_events) {
4855 event = sipe_cal_get_event(sip->cal->cal_events, time(NULL));
4858 if (!event) {
4859 SIPE_DEBUG_INFO_NOFORMAT("publish_calendar_status_self: current event is NULL");
4860 } else {
4861 char *desc = sipe_cal_event_describe(event);
4862 SIPE_DEBUG_INFO("publish_calendar_status_self: current event is:\n%s", desc ? desc : "");
4863 g_free(desc);
4866 /* Logic
4867 if OOF
4868 OOF publish, Busy clean
4869 ilse if Busy
4870 OOF clean, Busy publish
4871 else
4872 OOF clean, Busy clean
4874 if (event && event->cal_status == SIPE_CAL_OOF) {
4875 pub_calendar = sipe_publish_get_category_state_calendar(sipe_private, event, sip->cal->email, SIPE_CAL_OOF);
4876 pub_calendar2 = sipe_publish_get_category_state_calendar(sipe_private, NULL, sip->cal->email, SIPE_CAL_BUSY);
4877 } else if (event && event->cal_status == SIPE_CAL_BUSY) {
4878 pub_calendar = sipe_publish_get_category_state_calendar(sipe_private, NULL, sip->cal->email, SIPE_CAL_OOF);
4879 pub_calendar2 = sipe_publish_get_category_state_calendar(sipe_private, event, sip->cal->email, SIPE_CAL_BUSY);
4880 } else {
4881 pub_calendar = sipe_publish_get_category_state_calendar(sipe_private, NULL, sip->cal->email, SIPE_CAL_OOF);
4882 pub_calendar2 = sipe_publish_get_category_state_calendar(sipe_private, NULL, sip->cal->email, SIPE_CAL_BUSY);
4885 oof_note = sipe_ews_get_oof_note(sip->cal);
4886 if (sipe_strequal("Scheduled", sip->cal->oof_state)) {
4887 oof_start = sip->cal->oof_start;
4888 oof_end = sip->cal->oof_end;
4890 pub_oof_note = sipe_publish_get_category_note(sipe_private, oof_note, "OOF", oof_start, oof_end);
4892 pub_cal_working_hours = sipe_publish_get_category_cal_working_hours(sipe_private);
4893 pub_cal_free_busy = sipe_publish_get_category_cal_free_busy(sipe_private);
4895 if (!pub_cal_working_hours && !pub_cal_free_busy && !pub_calendar && !pub_calendar2 && !pub_oof_note) {
4896 SIPE_DEBUG_INFO_NOFORMAT("publish_calendar_status_self: nothing has changed.");
4897 } else {
4898 gchar *publications = g_strdup_printf("%s%s%s%s%s",
4899 pub_cal_working_hours ? pub_cal_working_hours : "",
4900 pub_cal_free_busy ? pub_cal_free_busy : "",
4901 pub_calendar ? pub_calendar : "",
4902 pub_calendar2 ? pub_calendar2 : "",
4903 pub_oof_note ? pub_oof_note : "");
4905 send_presence_publish(sipe_private, publications);
4906 g_free(publications);
4909 g_free(pub_cal_working_hours);
4910 g_free(pub_cal_free_busy);
4911 g_free(pub_calendar);
4912 g_free(pub_calendar2);
4913 g_free(pub_oof_note);
4915 /* repeat scheduling */
4916 sipe_sched_calendar_status_self_publish(sipe_private, time(NULL));
4919 static void send_presence_status(struct sipe_core_private *sipe_private,
4920 SIPE_UNUSED_PARAMETER void *unused)
4922 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
4923 PurpleStatus * status = purple_account_get_active_status(sip->account);
4925 if (!status) return;
4927 SIPE_DEBUG_INFO("send_presence_status: status: %s (%s)",
4928 purple_status_get_id(status) ? purple_status_get_id(status) : "",
4929 sipe_is_user_state(sipe_private) ? "USER" : "MACHINE");
4931 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
4932 send_presence_category_publish(sipe_private);
4933 } else {
4934 send_presence_soap(sipe_private, FALSE);
4938 static guint sipe_ht_hash_nick(const char *nick)
4940 char *lc = g_utf8_strdown(nick, -1);
4941 guint bucket = g_str_hash(lc);
4942 g_free(lc);
4944 return bucket;
4947 static gboolean sipe_ht_equals_nick(const char *nick1, const char *nick2)
4949 char *nick1_norm = NULL;
4950 char *nick2_norm = NULL;
4951 gboolean equal;
4953 if (nick1 == NULL && nick2 == NULL) return TRUE;
4954 if (nick1 == NULL || nick2 == NULL ||
4955 !g_utf8_validate(nick1, -1, NULL) ||
4956 !g_utf8_validate(nick2, -1, NULL)) return FALSE;
4958 nick1_norm = g_utf8_casefold(nick1, -1);
4959 nick2_norm = g_utf8_casefold(nick2, -1);
4960 equal = g_utf8_collate(nick1_norm, nick2_norm) == 0;
4961 g_free(nick2_norm);
4962 g_free(nick1_norm);
4964 return equal;
4967 /* temporary function */
4968 void sipe_purple_setup(struct sipe_core_public *sipe_public,
4969 PurpleConnection *gc)
4971 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA;
4972 sip->gc = gc;
4973 sip->account = purple_connection_get_account(gc);
4976 struct sipe_core_public *sipe_core_allocate(const gchar *signin_name,
4977 const gchar *login_domain,
4978 const gchar *login_account,
4979 const gchar *password,
4980 const gchar *email,
4981 const gchar *email_url,
4982 const gchar **errmsg)
4984 struct sipe_core_private *sipe_private;
4985 struct sipe_account_data *sip;
4986 gchar **user_domain;
4988 SIPE_DEBUG_INFO("sipe_core_allocate: signin_name '%s'", signin_name);
4990 /* ensure that sign-in name doesn't contain invalid characters */
4991 if (strpbrk(signin_name, "\t\v\r\n") != NULL) {
4992 *errmsg = _("SIP Exchange user name contains invalid characters");
4993 return NULL;
4996 /* ensure that sign-in name format is name@domain */
4997 if (!strchr(signin_name, '@') ||
4998 g_str_has_prefix(signin_name, "@") ||
4999 g_str_has_suffix(signin_name, "@")) {
5000 *errmsg = _("User name should be a valid SIP URI\nExample: user@company.com");
5001 return NULL;
5004 /* ensure that email format is name@domain (if provided) */
5005 if (!is_empty(email) &&
5006 (!strchr(email, '@') ||
5007 g_str_has_prefix(email, "@") ||
5008 g_str_has_suffix(email, "@")))
5010 *errmsg = _("Email address should be valid if provided\nExample: user@company.com");
5011 return NULL;
5014 /* ensure that user name doesn't contain spaces */
5015 user_domain = g_strsplit(signin_name, "@", 2);
5016 SIPE_DEBUG_INFO("sipe_core_allocate: user '%s' domain '%s'", user_domain[0], user_domain[1]);
5017 if (strchr(user_domain[0], ' ') != NULL) {
5018 g_strfreev(user_domain);
5019 *errmsg = _("SIP Exchange user name contains whitespace");
5020 return NULL;
5023 /* ensure that email_url is in proper format if enabled (if provided).
5024 * Example (Exchange): https://server.company.com/EWS/Exchange.asmx
5025 * Example (Domino) : https://[domino_server]/[mail_database_name].nsf
5027 if (!is_empty(email_url)) {
5028 char *tmp = g_ascii_strdown(email_url, -1);
5029 if (!g_str_has_prefix(tmp, "https://"))
5031 g_free(tmp);
5032 g_strfreev(user_domain);
5033 *errmsg = _("Email services URL should be valid if provided\n"
5034 "Example: https://exchange.corp.com/EWS/Exchange.asmx\n"
5035 "Example: https://domino.corp.com/maildatabase.nsf");
5036 return NULL;
5038 g_free(tmp);
5041 sipe_private = g_new0(struct sipe_core_private, 1);
5042 sipe_private->temporary = sip = g_new0(struct sipe_account_data, 1);
5043 sip->subscribed_buddies = FALSE;
5044 sip->initial_state_published = FALSE;
5045 sipe_private->username = g_strdup(signin_name);
5046 sip->email = is_empty(email) ? g_strdup(signin_name) : g_strdup(email);
5047 sip->authdomain = is_empty(login_domain) ? NULL : g_strdup(login_domain);
5048 sip->authuser = is_empty(login_account) ? NULL : g_strdup(login_account);
5049 sip->password = g_strdup(password);
5050 sipe_private->public.sip_name = g_strdup(user_domain[0]);
5051 sipe_private->public.sip_domain = g_strdup(user_domain[1]);
5052 g_strfreev(user_domain);
5054 sipe_private->buddies = g_hash_table_new((GHashFunc)sipe_ht_hash_nick, (GEqualFunc)sipe_ht_equals_nick);
5055 sip->our_publications = g_hash_table_new_full(g_str_hash, g_str_equal,
5056 g_free, (GDestroyNotify)g_hash_table_destroy);
5057 sipe_subscriptions_init(sipe_private);
5058 sip->status = g_strdup(SIPE_STATUS_ID_UNKNOWN);
5060 return((struct sipe_core_public *)sipe_private);
5063 static void
5064 sipe_blist_menu_free_containers(struct sipe_core_private *sipe_private);
5066 void sipe_connection_cleanup(struct sipe_core_private *sipe_private)
5068 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5070 g_free(sipe_private->epid);
5071 sipe_private->epid = NULL;
5073 sip_transport_disconnect(sipe_private);
5075 sipe_schedule_cancel_all(sipe_private);
5077 if (sip->allow_events) {
5078 GSList *entry = sip->allow_events;
5079 while (entry) {
5080 g_free(entry->data);
5081 entry = entry->next;
5084 g_slist_free(sip->allow_events);
5086 if (sip->containers) {
5087 GSList *entry = sip->containers;
5088 while (entry) {
5089 free_container((struct sipe_container *)entry->data);
5090 entry = entry->next;
5093 g_slist_free(sip->containers);
5095 /* libpurple memory leak workaround */
5096 sipe_blist_menu_free_containers(sipe_private);
5098 if (sipe_private->contact)
5099 g_free(sipe_private->contact);
5100 sipe_private->contact = NULL;
5101 if (sip->regcallid)
5102 g_free(sip->regcallid);
5103 sip->regcallid = NULL;
5105 if (sipe_private->focus_factory_uri)
5106 g_free(sipe_private->focus_factory_uri);
5107 sipe_private->focus_factory_uri = NULL;
5109 if (sip->cal) {
5110 sipe_cal_calendar_free(sip->cal);
5112 sip->cal = NULL;
5114 sipe_groupchat_free(sipe_private);
5118 * A callback for g_hash_table_foreach_remove
5120 static gboolean sipe_buddy_remove(SIPE_UNUSED_PARAMETER gpointer key, gpointer buddy,
5121 SIPE_UNUSED_PARAMETER gpointer user_data)
5123 sipe_free_buddy((struct sipe_buddy *) buddy);
5125 /* We must return TRUE as the key/value have already been deleted */
5126 return(TRUE);
5129 void sipe_buddy_free_all(struct sipe_core_private *sipe_private)
5131 g_hash_table_foreach_steal(sipe_private->buddies, sipe_buddy_remove, NULL);
5134 static void sipe_searchresults_im_buddy(PurpleConnection *gc, GList *row,
5135 SIPE_UNUSED_PARAMETER void *user_data)
5137 PurpleAccount *acct = purple_connection_get_account(gc);
5138 char *id = sip_uri_from_name((gchar *)g_list_nth_data(row, 0));
5139 PurpleConversation *conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, id, acct);
5140 if (conv == NULL)
5141 conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, acct, id);
5142 purple_conversation_present(conv);
5143 g_free(id);
5146 static void sipe_searchresults_add_buddy(PurpleConnection *gc, GList *row,
5147 SIPE_UNUSED_PARAMETER void *user_data)
5150 purple_blist_request_add_buddy(purple_connection_get_account(gc),
5151 g_list_nth_data(row, 0), _("Other Contacts"), g_list_nth_data(row, 1));
5154 static gboolean process_search_contact_response(struct sipe_core_private *sipe_private,
5155 struct sipmsg *msg,
5156 SIPE_UNUSED_PARAMETER struct transaction *trans)
5158 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5159 PurpleNotifySearchResults *results;
5160 PurpleNotifySearchColumn *column;
5161 sipe_xml *searchResults;
5162 const sipe_xml *mrow;
5163 int match_count = 0;
5164 gboolean more = FALSE;
5165 gchar *secondary;
5167 SIPE_DEBUG_INFO("process_search_contact_response: body:\n%s", msg->body ? msg->body : "");
5169 searchResults = sipe_xml_parse(msg->body, msg->bodylen);
5170 if (!searchResults) {
5171 SIPE_DEBUG_INFO_NOFORMAT("process_search_contact_response: no parseable searchResults");
5172 return FALSE;
5175 results = purple_notify_searchresults_new();
5177 if (results == NULL) {
5178 SIPE_DEBUG_ERROR_NOFORMAT("purple_parse_searchreply: Unable to display the search results.");
5179 purple_notify_error(sip->gc, NULL, _("Unable to display the search results"), NULL);
5181 sipe_xml_free(searchResults);
5182 return FALSE;
5185 column = purple_notify_searchresults_column_new(_("User name"));
5186 purple_notify_searchresults_column_add(results, column);
5188 column = purple_notify_searchresults_column_new(_("Name"));
5189 purple_notify_searchresults_column_add(results, column);
5191 column = purple_notify_searchresults_column_new(_("Company"));
5192 purple_notify_searchresults_column_add(results, column);
5194 column = purple_notify_searchresults_column_new(_("Country"));
5195 purple_notify_searchresults_column_add(results, column);
5197 column = purple_notify_searchresults_column_new(_("Email"));
5198 purple_notify_searchresults_column_add(results, column);
5200 for (mrow = sipe_xml_child(searchResults, "Body/Array/row"); mrow; mrow = sipe_xml_twin(mrow)) {
5201 GList *row = NULL;
5203 gchar **uri_parts = g_strsplit(sipe_xml_attribute(mrow, "uri"), ":", 2);
5204 row = g_list_append(row, g_strdup(uri_parts[1]));
5205 g_strfreev(uri_parts);
5207 row = g_list_append(row, g_strdup(sipe_xml_attribute(mrow, "displayName")));
5208 row = g_list_append(row, g_strdup(sipe_xml_attribute(mrow, "company")));
5209 row = g_list_append(row, g_strdup(sipe_xml_attribute(mrow, "country")));
5210 row = g_list_append(row, g_strdup(sipe_xml_attribute(mrow, "email")));
5212 purple_notify_searchresults_row_add(results, row);
5213 match_count++;
5216 if ((mrow = sipe_xml_child(searchResults, "Body/directorySearch/moreAvailable")) != NULL) {
5217 char *data = sipe_xml_data(mrow);
5218 more = (g_strcasecmp(data, "true") == 0);
5219 g_free(data);
5222 secondary = g_strdup_printf(
5223 dngettext(PACKAGE_NAME,
5224 "Found %d contact%s:",
5225 "Found %d contacts%s:", match_count),
5226 match_count, more ? _(" (more matched your query)") : "");
5228 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_IM, sipe_searchresults_im_buddy);
5229 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_ADD, sipe_searchresults_add_buddy);
5230 purple_notify_searchresults(sip->gc, NULL, NULL, secondary, results, NULL, NULL);
5232 g_free(secondary);
5233 sipe_xml_free(searchResults);
5234 return TRUE;
5237 void sipe_search_contact_with_cb(PurpleConnection *gc, PurpleRequestFields *fields)
5239 GList *entries = purple_request_field_group_get_fields(purple_request_fields_get_groups(fields)->data);
5240 gchar **attrs = g_new(gchar *, g_list_length(entries) + 1);
5241 unsigned i = 0;
5243 if (!attrs) return;
5245 do {
5246 PurpleRequestField *field = entries->data;
5247 const char *id = purple_request_field_get_id(field);
5248 const char *value = purple_request_field_string_get_value(field);
5250 SIPE_DEBUG_INFO("sipe_search_contact_with_cb: %s = '%s'", id, value ? value : "");
5252 if (value != NULL) attrs[i++] = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, id, value);
5253 } while ((entries = g_list_next(entries)) != NULL);
5254 attrs[i] = NULL;
5256 if (i > 0) {
5257 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
5258 gchar *domain_uri = sip_uri_from_name(sipe_private->public.sip_domain);
5259 gchar *query = g_strjoinv(NULL, attrs);
5260 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 100, query);
5261 SIPE_DEBUG_INFO("sipe_search_contact_with_cb: body:\n%s", body ? body : "");
5262 send_soap_request_with_cb(sipe_private, domain_uri, body,
5263 process_search_contact_response, NULL);
5264 g_free(domain_uri);
5265 g_free(body);
5266 g_free(query);
5269 g_strfreev(attrs);
5272 static void sipe_publish_get_cat_state_user_to_clear(SIPE_UNUSED_PARAMETER const char *name,
5273 gpointer value,
5274 GString* str)
5276 struct sipe_publication *publication = value;
5278 g_string_append_printf( str,
5279 SIPE_PUB_XML_PUBLICATION_CLEAR,
5280 publication->category,
5281 publication->instance,
5282 publication->container,
5283 publication->version,
5284 "static");
5287 void sipe_core_reset_status(struct sipe_core_public *sipe_public)
5289 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
5290 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA;
5291 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) /* 2007+ */
5293 GString* str = g_string_new(NULL);
5294 gchar *publications;
5296 if (!sip->user_state_publications || g_hash_table_size(sip->user_state_publications) == 0) {
5297 SIPE_DEBUG_INFO_NOFORMAT("sipe_reset_status: no userState publications, exiting.");
5298 return;
5301 g_hash_table_foreach(sip->user_state_publications, (GHFunc)sipe_publish_get_cat_state_user_to_clear, str);
5302 publications = g_string_free(str, FALSE);
5304 send_presence_publish(sipe_private, publications);
5305 g_free(publications);
5307 else /* 2005 */
5309 send_presence_soap0(sipe_private, FALSE, TRUE);
5313 /** for Access levels menu */
5314 #define INDENT_FMT " %s"
5316 /** Member is directly placed to access level container.
5317 * For example SIP URI of user is in the container.
5319 #define INDENT_MARKED_FMT "* %s"
5321 /** Member is indirectly belong to access level container.
5322 * For example 'sameEnterprise' is in the container and user
5323 * belongs to that same enterprise.
5325 #define INDENT_MARKED_INHERITED_FMT "= %s"
5327 GSList *sipe_core_buddy_info(struct sipe_core_public *sipe_public,
5328 const gchar *name,
5329 const gchar *status_name,
5330 gboolean is_online)
5332 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
5333 gchar *note = NULL;
5334 gboolean is_oof_note = FALSE;
5335 const gchar *activity = NULL;
5336 gchar *calendar = NULL;
5337 const gchar *meeting_subject = NULL;
5338 const gchar *meeting_location = NULL;
5339 gchar *access_text = NULL;
5340 GSList *info = NULL;
5342 #define SIPE_ADD_BUDDY_INFO_COMMON(l, t) \
5344 struct sipe_buddy_info *sbi = g_malloc(sizeof(struct sipe_buddy_info)); \
5345 sbi->label = (l); \
5346 sbi->text = (t); \
5347 info = g_slist_append(info, sbi); \
5349 #define SIPE_ADD_BUDDY_INFO(l, t) SIPE_ADD_BUDDY_INFO_COMMON((l), g_markup_escape_text((t), -1))
5350 #define SIPE_ADD_BUDDY_INFO_NOESCAPE(l, t) SIPE_ADD_BUDDY_INFO_COMMON((l), (t))
5352 if (sipe_public) { //happens on pidgin exit
5353 struct sipe_buddy *sbuddy = g_hash_table_lookup(sipe_private->buddies, name);
5354 if (sbuddy) {
5355 note = sbuddy->note;
5356 is_oof_note = sbuddy->is_oof_note;
5357 activity = sbuddy->activity;
5358 calendar = sipe_cal_get_description(sbuddy);
5359 meeting_subject = sbuddy->meeting_subject;
5360 meeting_location = sbuddy->meeting_location;
5362 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
5363 gboolean is_group_access = FALSE;
5364 const int container_id = sipe_find_access_level(sipe_private, "user", sipe_get_no_sip_uri(name), &is_group_access);
5365 const char *access_level = sipe_get_access_level_name(container_id);
5366 access_text = is_group_access ?
5367 g_strdup(access_level) :
5368 g_strdup_printf(INDENT_MARKED_FMT, access_level);
5372 //Layout
5373 if (is_online)
5375 const gchar *status_str = activity ? activity : status_name;
5377 SIPE_ADD_BUDDY_INFO(_("Status"), status_str);
5379 if (is_online && !is_empty(calendar))
5381 SIPE_ADD_BUDDY_INFO(_("Calendar"), calendar);
5383 g_free(calendar);
5384 if (!is_empty(meeting_location))
5386 SIPE_DEBUG_INFO("sipe_tooltip_text: %s meeting location: '%s'", name, meeting_location);
5387 SIPE_ADD_BUDDY_INFO(_("Meeting in"), meeting_location);
5389 if (!is_empty(meeting_subject))
5391 SIPE_DEBUG_INFO("sipe_tooltip_text: %s meeting subject: '%s'", name, meeting_subject);
5392 SIPE_ADD_BUDDY_INFO(_("Meeting about"), meeting_subject);
5394 if (note)
5396 SIPE_DEBUG_INFO("sipe_tooltip_text: %s note: '%s'", name, note);
5397 SIPE_ADD_BUDDY_INFO_NOESCAPE(is_oof_note ? _("Out of office note") : _("Note"),
5398 g_strdup_printf("<i>%s</i>", note));
5400 if (access_text) {
5401 SIPE_ADD_BUDDY_INFO(_("Access level"), access_text);
5402 g_free(access_text);
5405 return(info);
5408 static PurpleBuddy *
5409 purple_blist_add_buddy_clone(PurpleGroup * group, PurpleBuddy * buddy)
5411 PurpleBuddy *clone;
5412 const gchar *server_alias, *email;
5413 const PurpleStatus *status = purple_presence_get_active_status(purple_buddy_get_presence(buddy));
5415 clone = purple_buddy_new(buddy->account, buddy->name, buddy->alias);
5417 purple_blist_add_buddy(clone, NULL, group, NULL);
5419 server_alias = purple_buddy_get_server_alias(buddy);
5420 if (server_alias) {
5421 purple_blist_server_alias_buddy(clone, server_alias);
5424 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
5425 if (email) {
5426 purple_blist_node_set_string(&clone->node, EMAIL_PROP, email);
5429 purple_presence_set_status_active(purple_buddy_get_presence(clone), purple_status_get_id(status), TRUE);
5430 //for UI to update;
5431 purple_prpl_got_user_status(clone->account, clone->name, purple_status_get_id(status), NULL);
5432 return clone;
5435 static void
5436 sipe_buddy_menu_copy_to_cb(PurpleBlistNode *node, const char *group_name)
5438 PurpleBuddy *buddy, *b;
5439 PurpleConnection *gc;
5440 PurpleGroup * group = purple_find_group(group_name);
5442 g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node));
5444 buddy = (PurpleBuddy *)node;
5446 SIPE_DEBUG_INFO("sipe_buddy_menu_copy_to_cb: copying %s to %s", buddy->name, group_name);
5447 gc = purple_account_get_connection(buddy->account);
5449 b = purple_find_buddy_in_group(buddy->account, buddy->name, group);
5450 if (!b){
5451 b = purple_blist_add_buddy_clone(group, buddy);
5454 sipe_add_buddy(gc, b, group);
5457 static void
5458 sipe_buddy_menu_chat_new_cb(PurpleBuddy *buddy)
5460 struct sipe_core_private *sipe_private = PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE;
5462 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_new_cb: buddy->name=%s", buddy->name);
5464 /* 2007+ conference */
5465 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007))
5467 sipe_conf_add(sipe_private, buddy->name);
5469 else /* 2005- multiparty chat */
5471 gchar *self = sip_uri_self(sipe_private);
5472 struct sip_session *session;
5474 session = sipe_session_add_chat(sipe_private,
5475 NULL,
5476 TRUE,
5477 self);
5478 session->chat_session->backend = sipe_backend_chat_create(SIPE_CORE_PUBLIC,
5479 session->chat_session,
5480 session->chat_session->title,
5481 self);
5482 g_free(self);
5484 sipe_im_invite(sipe_private, session, buddy->name, NULL, NULL, NULL, FALSE);
5489 * For 2007+ conference only.
5491 static void
5492 sipe_buddy_menu_chat_make_leader_cb(PurpleBuddy *buddy,
5493 struct sipe_chat_session *chat_session)
5495 struct sipe_core_private *sipe_private = PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE;
5496 struct sip_session *session;
5498 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_make_leader_cb: buddy->name=%s", buddy->name);
5499 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_make_leader_cb: chat_title=%s", chat_session->title);
5501 session = sipe_session_find_chat(sipe_private, chat_session);
5503 sipe_conf_modify_user_role(sipe_private, session, buddy->name);
5507 * For 2007+ conference only.
5509 static void
5510 sipe_buddy_menu_chat_remove_cb(PurpleBuddy *buddy,
5511 struct sipe_chat_session *chat_session)
5513 struct sipe_core_private *sipe_private = PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE;
5514 struct sip_session *session;
5516 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_remove_cb: buddy->name=%s", buddy->name);
5517 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_remove_cb: chat_title=%s", chat_session->title);
5519 session = sipe_session_find_chat(sipe_private, chat_session);
5521 sipe_conf_delete_user(sipe_private, session, buddy->name);
5524 static void
5525 sipe_buddy_menu_chat_invite_cb(PurpleBuddy *buddy,
5526 struct sipe_chat_session *chat_session)
5528 struct sipe_core_private *sipe_private = PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE;
5530 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_invite_cb: buddy->name=%s", buddy->name);
5531 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_invite_cb: chat_title=%s", chat_session->title);
5533 sipe_core_chat_invite(SIPE_CORE_PUBLIC, chat_session, buddy->name);
5536 static void
5537 sipe_buddy_menu_make_call_cb(PurpleBuddy *buddy, const char *phone)
5539 struct sipe_core_private *sipe_private = PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE;
5541 SIPE_DEBUG_INFO("sipe_buddy_menu_make_call_cb: buddy->name=%s", buddy->name);
5542 if (phone) {
5543 char *tel_uri = sip_to_tel_uri(phone);
5545 SIPE_DEBUG_INFO("sipe_buddy_menu_make_call_cb: going to call number: %s", tel_uri ? tel_uri : "");
5546 sip_csta_make_call(sipe_private, tel_uri);
5548 g_free(tel_uri);
5552 static void
5553 sipe_buddy_menu_access_level_help_cb(PurpleBuddy *buddy)
5555 /** Translators: replace with URL to localized page
5556 * If it doesn't exist copy the original URL */
5557 purple_notify_uri(buddy->account->gc, _("https://sourceforge.net/apps/mediawiki/sipe/index.php?title=Access_Levels"));
5560 static void
5561 sipe_buddy_menu_send_email_cb(PurpleBuddy *buddy)
5563 const gchar *email;
5564 SIPE_DEBUG_INFO("sipe_buddy_menu_send_email_cb: buddy->name=%s", buddy->name);
5566 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
5567 if (email)
5569 char *command_line = g_strdup_printf(
5570 #ifdef _WIN32
5571 "cmd /c start"
5572 #else
5573 "xdg-email"
5574 #endif
5575 " mailto:%s", email);
5576 SIPE_DEBUG_INFO("sipe_buddy_menu_send_email_cb: going to call email client: %s", command_line);
5578 g_spawn_command_line_async(command_line, NULL);
5579 g_free(command_line);
5581 else
5583 SIPE_DEBUG_INFO("sipe_buddy_menu_send_email_cb: no email address stored for buddy=%s", buddy->name);
5587 static void
5588 sipe_buddy_menu_access_level_cb(PurpleBuddy *buddy,
5589 struct sipe_container *container)
5591 struct sipe_core_private *sipe_private = PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE;
5592 struct sipe_container_member *member;
5594 if (!container || !container->members) return;
5596 member = ((struct sipe_container_member *)container->members->data);
5598 if (!member->type) return;
5600 SIPE_DEBUG_INFO("sipe_buddy_menu_access_level_cb: container->id=%d, member->type=%s, member->value=%s",
5601 container->id, member->type, member->value ? member->value : "");
5603 sipe_change_access_level(sipe_private, container->id, member->type, member->value);
5606 static GList *
5607 sipe_get_access_control_menu(struct sipe_core_private *sipe_private,
5608 const char* uri);
5611 * A menu which appear when right-clicking on buddy in contact list.
5613 GList *
5614 sipe_buddy_menu(PurpleBuddy *buddy)
5616 PurpleBlistNode *g_node;
5617 PurpleGroup *gr_parent;
5618 PurpleMenuAction *act;
5619 GList *menu = NULL;
5620 GList *menu_groups = NULL;
5621 struct sipe_core_private *sipe_private = PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE;
5622 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5623 const char *email;
5624 gchar *self = sip_uri_self(sipe_private);
5626 SIPE_SESSION_FOREACH {
5627 if (!sipe_strcase_equal(self, buddy->name) && session->chat_session)
5629 struct sipe_chat_session *chat_session = session->chat_session;
5630 gboolean is_conf = (chat_session->type == SIPE_CHAT_TYPE_CONFERENCE);
5632 if (sipe_backend_chat_find(chat_session->backend, buddy->name))
5634 gboolean conf_op = sipe_backend_chat_is_operator(chat_session->backend, self);
5636 if (is_conf
5637 && !sipe_backend_chat_is_operator(chat_session->backend, buddy->name) /* Not conf OP */
5638 && conf_op) /* We are a conf OP */
5640 gchar *label = g_strdup_printf(_("Make leader of '%s'"),
5641 chat_session->title);
5642 act = purple_menu_action_new(label,
5643 PURPLE_CALLBACK(sipe_buddy_menu_chat_make_leader_cb),
5644 chat_session, NULL);
5645 g_free(label);
5646 menu = g_list_prepend(menu, act);
5649 if (is_conf
5650 && conf_op) /* We are a conf OP */
5652 gchar *label = g_strdup_printf(_("Remove from '%s'"),
5653 chat_session->title);
5654 act = purple_menu_action_new(label,
5655 PURPLE_CALLBACK(sipe_buddy_menu_chat_remove_cb),
5656 chat_session, NULL);
5657 g_free(label);
5658 menu = g_list_prepend(menu, act);
5661 else
5663 if (!is_conf
5664 || (is_conf && !session->locked))
5666 gchar *label = g_strdup_printf(_("Invite to '%s'"),
5667 chat_session->title);
5668 act = purple_menu_action_new(label,
5669 PURPLE_CALLBACK(sipe_buddy_menu_chat_invite_cb),
5670 chat_session, NULL);
5671 g_free(label);
5672 menu = g_list_prepend(menu, act);
5676 } SIPE_SESSION_FOREACH_END;
5678 act = purple_menu_action_new(_("New chat"),
5679 PURPLE_CALLBACK(sipe_buddy_menu_chat_new_cb),
5680 NULL, NULL);
5681 menu = g_list_prepend(menu, act);
5683 if (sip->csta && !sip->csta->line_status) {
5684 const char *phone;
5685 const char *phone_disp_str;
5686 gchar *tmp = NULL;
5687 /* work phone */
5688 phone = purple_blist_node_get_string(&buddy->node, PHONE_PROP);
5689 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_DISPLAY_PROP);
5690 if (phone) {
5691 gchar *label = g_strdup_printf(_("Work %s"),
5692 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
5693 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
5694 g_free(tmp);
5695 tmp = NULL;
5696 g_free(label);
5697 menu = g_list_prepend(menu, act);
5700 /* mobile phone */
5701 phone = purple_blist_node_get_string(&buddy->node, PHONE_MOBILE_PROP);
5702 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_MOBILE_DISPLAY_PROP);
5703 if (phone) {
5704 gchar *label = g_strdup_printf(_("Mobile %s"),
5705 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
5706 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
5707 g_free(tmp);
5708 tmp = NULL;
5709 g_free(label);
5710 menu = g_list_prepend(menu, act);
5713 /* home phone */
5714 phone = purple_blist_node_get_string(&buddy->node, PHONE_HOME_PROP);
5715 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_HOME_DISPLAY_PROP);
5716 if (phone) {
5717 gchar *label = g_strdup_printf(_("Home %s"),
5718 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
5719 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
5720 g_free(tmp);
5721 tmp = NULL;
5722 g_free(label);
5723 menu = g_list_prepend(menu, act);
5726 /* other phone */
5727 phone = purple_blist_node_get_string(&buddy->node, PHONE_OTHER_PROP);
5728 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_OTHER_DISPLAY_PROP);
5729 if (phone) {
5730 gchar *label = g_strdup_printf(_("Other %s"),
5731 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
5732 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
5733 g_free(tmp);
5734 tmp = NULL;
5735 g_free(label);
5736 menu = g_list_prepend(menu, act);
5739 /* custom1 phone */
5740 phone = purple_blist_node_get_string(&buddy->node, PHONE_CUSTOM1_PROP);
5741 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_CUSTOM1_DISPLAY_PROP);
5742 if (phone) {
5743 gchar *label = g_strdup_printf(_("Custom1 %s"),
5744 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
5745 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
5746 g_free(tmp);
5747 tmp = NULL;
5748 g_free(label);
5749 menu = g_list_prepend(menu, act);
5753 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
5754 if (email) {
5755 act = purple_menu_action_new(_("Send email..."),
5756 PURPLE_CALLBACK(sipe_buddy_menu_send_email_cb),
5757 NULL, NULL);
5758 menu = g_list_prepend(menu, act);
5761 /* Access Level */
5762 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
5763 GList *menu_access_levels = sipe_get_access_control_menu(sipe_private, buddy->name);
5765 act = purple_menu_action_new(_("Access level"),
5766 NULL,
5767 NULL, menu_access_levels);
5768 menu = g_list_prepend(menu, act);
5771 /* Copy to */
5772 gr_parent = purple_buddy_get_group(buddy);
5773 for (g_node = purple_blist_get_root(); g_node; g_node = g_node->next) {
5774 PurpleGroup *group;
5776 if (g_node->type != PURPLE_BLIST_GROUP_NODE)
5777 continue;
5779 group = (PurpleGroup *)g_node;
5780 if (group == gr_parent)
5781 continue;
5783 if (purple_find_buddy_in_group(buddy->account, buddy->name, group))
5784 continue;
5786 act = purple_menu_action_new(purple_group_get_name(group),
5787 PURPLE_CALLBACK(sipe_buddy_menu_copy_to_cb),
5788 group->name, NULL);
5789 menu_groups = g_list_prepend(menu_groups, act);
5791 menu_groups = g_list_reverse(menu_groups);
5793 act = purple_menu_action_new(_("Copy to"),
5794 NULL,
5795 NULL, menu_groups);
5796 menu = g_list_prepend(menu, act);
5798 menu = g_list_reverse(menu);
5800 g_free(self);
5801 return menu;
5804 static void
5805 sipe_ask_access_domain_cb(PurpleConnection *gc, PurpleRequestFields *fields)
5807 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
5808 const char *domain = purple_request_fields_get_string(fields, "access_domain");
5809 int index = purple_request_fields_get_choice(fields, "container_id");
5810 /* move Blocked first */
5811 int i = (index == 4) ? 0 : index + 1;
5812 int container_id = containers[i];
5814 SIPE_DEBUG_INFO("sipe_ask_access_domain_cb: domain=%s, container_id=(%d)%d", domain ? domain : "", index, container_id);
5816 sipe_change_access_level(sipe_private, container_id, "domain", domain);
5819 static void
5820 sipe_ask_access_domain(struct sipe_core_private *sipe_private)
5822 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5823 PurpleAccount *account = sip->account;
5824 PurpleConnection *gc = sip->gc;
5825 PurpleRequestFields *fields;
5826 PurpleRequestFieldGroup *g;
5827 PurpleRequestField *f;
5829 fields = purple_request_fields_new();
5831 g = purple_request_field_group_new(NULL);
5832 f = purple_request_field_string_new("access_domain", _("Domain"), "partner-company.com", FALSE);
5833 purple_request_field_set_required(f, TRUE);
5834 purple_request_field_group_add_field(g, f);
5836 f = purple_request_field_choice_new("container_id", _("Access level"), 0);
5837 purple_request_field_choice_add(f, _("Personal")); /* index 0 */
5838 purple_request_field_choice_add(f, _("Team"));
5839 purple_request_field_choice_add(f, _("Company"));
5840 purple_request_field_choice_add(f, _("Public"));
5841 purple_request_field_choice_add(f, _("Blocked")); /* index 4 */
5842 purple_request_field_choice_set_default_value(f, 3); /* index */
5843 purple_request_field_set_required(f, TRUE);
5844 purple_request_field_group_add_field(g, f);
5846 purple_request_fields_add_group(fields, g);
5848 purple_request_fields(gc, _("Add new domain"),
5849 _("Add new domain"), NULL, fields,
5850 _("Add"), G_CALLBACK(sipe_ask_access_domain_cb),
5851 _("Cancel"), NULL,
5852 account, NULL, NULL, gc);
5855 static void
5856 sipe_buddy_menu_access_level_add_domain_cb(PurpleBuddy *buddy)
5858 sipe_ask_access_domain(PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE);
5862 * Workaround for missing libpurple API to release resources allocated
5863 * during blist_node_menu() callback. See also:
5865 * <http://developer.pidgin.im/ticket/12597>
5867 * We remember all memory blocks in a list and deallocate them when
5869 * - the next time we enter the callback, or
5870 * - the account is disconnected
5872 * That means that after the buddy menu has been closed we have unused
5873 * resources but at least we don't leak them anymore...
5875 static void
5876 sipe_blist_menu_free_containers(struct sipe_core_private *sipe_private)
5878 GSList *entry = sipe_private->blist_menu_containers;
5879 while (entry) {
5880 free_container(entry->data);
5881 entry = entry->next;
5883 g_slist_free(sipe_private->blist_menu_containers);
5884 sipe_private->blist_menu_containers = NULL;
5887 static void
5888 sipe_blist_menu_remember_container(struct sipe_core_private *sipe_private,
5889 struct sipe_container *container)
5891 sipe_private->blist_menu_containers = g_slist_prepend(sipe_private->blist_menu_containers,
5892 container);
5895 static GList *
5896 sipe_get_access_levels_menu(struct sipe_core_private *sipe_private,
5897 const char* member_type,
5898 const char* member_value,
5899 const gboolean extra_menu)
5901 GList *menu_access_levels = NULL;
5902 unsigned int i;
5903 char *menu_name;
5904 PurpleMenuAction *act;
5905 struct sipe_container *container;
5906 struct sipe_container_member *member;
5907 gboolean is_group_access = FALSE;
5908 int container_id = sipe_find_access_level(sipe_private, member_type, member_value, &is_group_access);
5910 for (i = 1; i <= CONTAINERS_LEN; i++) {
5911 /* to put Blocked level last in menu list.
5912 * Blocked should remaim in the first place in the containers[] array.
5914 unsigned int j = (i == CONTAINERS_LEN) ? 0 : i;
5915 const char *acc_level_name = sipe_get_access_level_name(containers[j]);
5917 container = g_new0(struct sipe_container, 1);
5918 member = g_new0(struct sipe_container_member, 1);
5919 container->id = containers[j];
5920 container->members = g_slist_append(container->members, member);
5921 member->type = g_strdup(member_type);
5922 member->value = g_strdup(member_value);
5924 /* libpurple memory leak workaround */
5925 sipe_blist_menu_remember_container(sipe_private, container);
5927 /* current container/access level */
5928 if (((int)containers[j]) == container_id) {
5929 menu_name = is_group_access ?
5930 g_strdup_printf(INDENT_MARKED_INHERITED_FMT, acc_level_name) :
5931 g_strdup_printf(INDENT_MARKED_FMT, acc_level_name);
5932 } else {
5933 menu_name = g_strdup_printf(INDENT_FMT, acc_level_name);
5936 act = purple_menu_action_new(menu_name,
5937 PURPLE_CALLBACK(sipe_buddy_menu_access_level_cb),
5938 container, NULL);
5939 g_free(menu_name);
5940 menu_access_levels = g_list_prepend(menu_access_levels, act);
5943 if (extra_menu && (container_id >= 0)) {
5944 /* separator */
5945 act = purple_menu_action_new(" --------------", NULL, NULL, NULL);
5946 menu_access_levels = g_list_prepend(menu_access_levels, act);
5948 if (!is_group_access) {
5949 container = g_new0(struct sipe_container, 1);
5950 member = g_new0(struct sipe_container_member, 1);
5951 container->id = -1;
5952 container->members = g_slist_append(container->members, member);
5953 member->type = g_strdup(member_type);
5954 member->value = g_strdup(member_value);
5956 /* libpurple memory leak workaround */
5957 sipe_blist_menu_remember_container(sipe_private, container);
5959 /* Translators: remove (clear) previously assigned access level */
5960 menu_name = g_strdup_printf(INDENT_FMT, _("Unspecify"));
5961 act = purple_menu_action_new(menu_name,
5962 PURPLE_CALLBACK(sipe_buddy_menu_access_level_cb),
5963 container, NULL);
5964 g_free(menu_name);
5965 menu_access_levels = g_list_prepend(menu_access_levels, act);
5969 menu_access_levels = g_list_reverse(menu_access_levels);
5970 return menu_access_levels;
5973 static GList *
5974 sipe_get_access_groups_menu(struct sipe_core_private *sipe_private)
5976 GList *menu_access_groups = NULL;
5977 PurpleMenuAction *act;
5978 GSList *access_domains = NULL;
5979 GSList *entry;
5980 char *menu_name;
5981 char *domain;
5983 act = purple_menu_action_new(_("People in my company"),
5984 NULL,
5985 NULL, sipe_get_access_levels_menu(sipe_private, "sameEnterprise", NULL, FALSE));
5986 menu_access_groups = g_list_prepend(menu_access_groups, act);
5988 /* this is original name, don't edit */
5989 act = purple_menu_action_new(_("People in domains connected with my company"),
5990 NULL,
5991 NULL, sipe_get_access_levels_menu(sipe_private, "federated", NULL, FALSE));
5992 menu_access_groups = g_list_prepend(menu_access_groups, act);
5994 act = purple_menu_action_new(_("People in public domains"),
5995 NULL,
5996 NULL, sipe_get_access_levels_menu(sipe_private, "publicCloud", NULL, TRUE));
5997 menu_access_groups = g_list_prepend(menu_access_groups, act);
5999 access_domains = sipe_get_access_domains(sipe_private);
6000 entry = access_domains;
6001 while (entry) {
6002 domain = entry->data;
6004 menu_name = g_strdup_printf(_("People at %s"), domain);
6005 act = purple_menu_action_new(menu_name,
6006 NULL,
6007 NULL, sipe_get_access_levels_menu(sipe_private, "domain", g_strdup(domain), TRUE));
6008 menu_access_groups = g_list_prepend(menu_access_groups, act);
6009 g_free(menu_name);
6011 entry = entry->next;
6014 /* separator */
6015 /* People in domains connected with my company */
6016 act = purple_menu_action_new("-------------------------------------------", NULL, NULL, NULL);
6017 menu_access_groups = g_list_prepend(menu_access_groups, act);
6019 act = purple_menu_action_new(_("Add new domain..."),
6020 PURPLE_CALLBACK(sipe_buddy_menu_access_level_add_domain_cb),
6021 NULL, NULL);
6022 menu_access_groups = g_list_prepend(menu_access_groups, act);
6024 menu_access_groups = g_list_reverse(menu_access_groups);
6026 return menu_access_groups;
6029 static GList *
6030 sipe_get_access_control_menu(struct sipe_core_private *sipe_private,
6031 const char* uri)
6033 GList *menu_access_levels = NULL;
6034 GList *menu_access_groups = NULL;
6035 char *menu_name;
6036 PurpleMenuAction *act;
6038 /* libpurple memory leak workaround */
6039 sipe_blist_menu_free_containers(sipe_private);
6041 menu_access_levels = sipe_get_access_levels_menu(sipe_private, "user", sipe_get_no_sip_uri(uri), TRUE);
6043 menu_access_groups = sipe_get_access_groups_menu(sipe_private);
6045 menu_name = g_strdup_printf(INDENT_FMT, _("Access groups"));
6046 act = purple_menu_action_new(menu_name,
6047 NULL,
6048 NULL, menu_access_groups);
6049 g_free(menu_name);
6050 menu_access_levels = g_list_append(menu_access_levels, act);
6052 menu_name = g_strdup_printf(INDENT_FMT, _("Online help..."));
6053 act = purple_menu_action_new(menu_name,
6054 PURPLE_CALLBACK(sipe_buddy_menu_access_level_help_cb),
6055 NULL, NULL);
6056 g_free(menu_name);
6057 menu_access_levels = g_list_append(menu_access_levels, act);
6059 return menu_access_levels;
6062 static gboolean
6063 process_get_info_response(struct sipe_core_private *sipe_private,
6064 struct sipmsg *msg, struct transaction *trans)
6066 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
6067 char *uri = trans->payload->data;
6069 PurpleNotifyUserInfo *info;
6070 PurpleBuddy *pbuddy = NULL;
6071 struct sipe_buddy *sbuddy;
6072 const char *alias = NULL;
6073 char *device_name = NULL;
6074 char *server_alias = NULL;
6075 char *phone_number = NULL;
6076 char *email = NULL;
6077 const char *site;
6078 char *first_name = NULL;
6079 char *last_name = NULL;
6081 if (!sip) return FALSE;
6083 SIPE_DEBUG_INFO("Fetching %s's user info for %s", uri, sipe_private->username);
6085 pbuddy = purple_find_buddy((PurpleAccount *)sip->account, uri);
6086 alias = purple_buddy_get_local_alias(pbuddy);
6088 //will query buddy UA's capabilities and send answer to log
6089 sipe_options_request(sipe_private, uri);
6091 sbuddy = g_hash_table_lookup(sipe_private->buddies, uri);
6092 if (sbuddy) {
6093 device_name = sbuddy->device_name ? g_strdup(sbuddy->device_name) : NULL;
6096 info = purple_notify_user_info_new();
6098 if (msg->response != 200) {
6099 SIPE_DEBUG_INFO("process_get_info_response: SERVICE response is %d", msg->response);
6100 } else {
6101 sipe_xml *searchResults;
6102 const sipe_xml *mrow;
6104 SIPE_DEBUG_INFO("process_get_info_response: body:\n%s", msg->body ? msg->body : "");
6105 searchResults = sipe_xml_parse(msg->body, msg->bodylen);
6106 if (!searchResults) {
6107 SIPE_DEBUG_INFO_NOFORMAT("process_get_info_response: no parseable searchResults");
6108 } else if ((mrow = sipe_xml_child(searchResults, "Body/Array/row"))) {
6109 const char *value;
6110 server_alias = g_strdup(sipe_xml_attribute(mrow, "displayName"));
6111 email = g_strdup(sipe_xml_attribute(mrow, "email"));
6112 phone_number = g_strdup(sipe_xml_attribute(mrow, "phone"));
6114 /* For 2007 system we will take this from ContactCard -
6115 * it has cleaner tel: URIs at least
6117 if (!SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
6118 char *tel_uri = sip_to_tel_uri(phone_number);
6119 /* trims its parameters, so call first */
6120 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_DISPLAY_NAME, server_alias);
6121 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_EMAIL, email);
6122 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_WORK_PHONE, tel_uri);
6123 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_WORK_PHONE_DISPLAY, phone_number);
6124 g_free(tel_uri);
6127 if (server_alias && strlen(server_alias) > 0) {
6128 purple_notify_user_info_add_pair(info, _("Display name"), server_alias);
6130 if ((value = sipe_xml_attribute(mrow, "title")) && strlen(value) > 0) {
6131 purple_notify_user_info_add_pair(info, _("Job title"), value);
6133 if ((value = sipe_xml_attribute(mrow, "office")) && strlen(value) > 0) {
6134 purple_notify_user_info_add_pair(info, _("Office"), value);
6136 if (phone_number && strlen(phone_number) > 0) {
6137 purple_notify_user_info_add_pair(info, _("Business phone"), phone_number);
6139 if ((value = sipe_xml_attribute(mrow, "company")) && strlen(value) > 0) {
6140 purple_notify_user_info_add_pair(info, _("Company"), value);
6142 if ((value = sipe_xml_attribute(mrow, "city")) && strlen(value) > 0) {
6143 purple_notify_user_info_add_pair(info, _("City"), value);
6145 if ((value = sipe_xml_attribute(mrow, "state")) && strlen(value) > 0) {
6146 purple_notify_user_info_add_pair(info, _("State"), value);
6148 if ((value = sipe_xml_attribute(mrow, "country")) && strlen(value) > 0) {
6149 purple_notify_user_info_add_pair(info, _("Country"), value);
6151 if (email && strlen(email) > 0) {
6152 purple_notify_user_info_add_pair(info, _("Email address"), email);
6156 sipe_xml_free(searchResults);
6159 purple_notify_user_info_add_section_break(info);
6161 if (is_empty(server_alias)) {
6162 g_free(server_alias);
6163 server_alias = g_strdup(purple_buddy_get_server_alias(pbuddy));
6164 if (server_alias) {
6165 purple_notify_user_info_add_pair(info, _("Display name"), server_alias);
6169 /* present alias if it differs from server alias */
6170 if (alias && !sipe_strequal(alias, server_alias))
6172 purple_notify_user_info_add_pair(info, _("Alias"), alias);
6175 if (is_empty(email)) {
6176 g_free(email);
6177 email = g_strdup(purple_blist_node_get_string(&pbuddy->node, EMAIL_PROP));
6178 if (email) {
6179 purple_notify_user_info_add_pair(info, _("Email address"), email);
6183 site = purple_blist_node_get_string(&pbuddy->node, SITE_PROP);
6184 if (site) {
6185 purple_notify_user_info_add_pair(info, _("Site"), site);
6188 sipe_get_first_last_names(sipe_private, uri, &first_name, &last_name);
6189 if (first_name && last_name) {
6190 char *link = g_strconcat("http://www.linkedin.com/pub/dir/", first_name, "/", last_name, NULL);
6192 purple_notify_user_info_add_pair(info, _("Find on LinkedIn"), link);
6193 g_free(link);
6195 g_free(first_name);
6196 g_free(last_name);
6198 if (device_name) {
6199 purple_notify_user_info_add_pair(info, _("Device"), device_name);
6202 /* show a buddy's user info in a nice dialog box */
6203 purple_notify_userinfo(sip->gc, /* connection the buddy info came through */
6204 uri, /* buddy's URI */
6205 info, /* body */
6206 NULL, /* callback called when dialog closed */
6207 NULL); /* userdata for callback */
6209 g_free(phone_number);
6210 g_free(server_alias);
6211 g_free(email);
6212 g_free(device_name);
6214 return TRUE;
6218 * AD search first, LDAP based
6220 void sipe_get_info(PurpleConnection *gc, const char *username)
6222 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
6223 gchar *domain_uri = sip_uri_from_name(sipe_private->public.sip_domain);
6224 char *row = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, "msRTCSIP-PrimaryUserAddress", username);
6225 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 1, row);
6226 struct transaction_payload *payload = g_new0(struct transaction_payload, 1);
6228 payload->destroy = g_free;
6229 payload->data = g_strdup(username);
6231 SIPE_DEBUG_INFO("sipe_get_contact_data: body:\n%s", body ? body : "");
6232 send_soap_request_with_cb(sipe_private, domain_uri, body,
6233 process_get_info_response, payload);
6234 g_free(domain_uri);
6235 g_free(body);
6236 g_free(row);
6240 Local Variables:
6241 mode: c
6242 c-file-style: "bsd"
6243 indent-tabs-mode: t
6244 tab-width: 8
6245 End: