Move slist_insert_unique_sorted to sipe-utils
[siplcs.git] / src / core / sipe.c
blob7ef4917d5028b69a55975822ab760fc55ff2db90
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 * Returns string like "2 4 7 8" - group ids buddy belong to.
337 static gchar *
338 sipe_get_buddy_groups_string (struct sipe_buddy *buddy) {
339 int i = 0;
340 gchar *res;
341 //creating array from GList, converting int to gchar*
342 gchar **ids_arr = g_new(gchar *, g_slist_length(buddy->groups) + 1);
343 GSList *entry = buddy->groups;
345 if (!ids_arr) return NULL;
347 while (entry) {
348 struct sipe_group * group = entry->data;
349 ids_arr[i] = g_strdup_printf("%d", group->id);
350 entry = entry->next;
351 i++;
353 ids_arr[i] = NULL;
354 res = g_strjoinv(" ", ids_arr);
355 g_strfreev(ids_arr);
356 return res;
360 * Sends buddy update to server
362 void
363 sipe_core_group_set_user(struct sipe_core_public *sipe_public, const gchar * who)
365 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA;
366 struct sipe_buddy *buddy = g_hash_table_lookup(SIPE_CORE_PRIVATE->buddies, who);
367 sipe_backend_buddy backend_buddy = sipe_backend_buddy_find(sipe_public, who, NULL);
369 if (buddy && backend_buddy) {
370 gchar *alias = sipe_backend_buddy_get_alias(sipe_public, backend_buddy);
371 gchar *groups = sipe_get_buddy_groups_string(buddy);
372 if (groups) {
373 gchar *body;
374 SIPE_DEBUG_INFO("Saving buddy %s with alias %s and groups %s", who, alias, groups);
376 body = g_markup_printf_escaped(SIPE_SOAP_SET_CONTACT,
377 alias, groups, "true", buddy->name, sip->contacts_delta++
379 send_soap_request(SIPE_CORE_PRIVATE, body);
380 g_free(groups);
381 g_free(body);
383 g_free(alias);
387 static void
388 sipe_sched_calendar_status_update(struct sipe_core_private *sipe_private,
389 time_t calculate_from);
391 static int
392 sipe_get_availability_by_status(const char* sipe_status_id, char** activity_token);
394 static const char*
395 sipe_get_status_by_availability(int avail,
396 char** activity);
398 static void
399 sipe_set_purple_account_status_and_note(const PurpleAccount *account,
400 const char *status_id,
401 const char *message,
402 time_t do_not_publish[]);
404 static void
405 sipe_apply_calendar_status(struct sipe_core_private *sipe_private,
406 struct sipe_buddy *sbuddy,
407 const char *status_id)
409 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
410 time_t cal_avail_since;
411 int cal_status = sipe_cal_get_status(sbuddy, time(NULL), &cal_avail_since);
412 int avail;
413 gchar *self_uri;
415 if (!sbuddy) return;
417 if (cal_status < SIPE_CAL_NO_DATA) {
418 SIPE_DEBUG_INFO("sipe_apply_calendar_status: cal_status : %d for %s", cal_status, sbuddy->name);
419 SIPE_DEBUG_INFO("sipe_apply_calendar_status: cal_avail_since : %s", asctime(localtime(&cal_avail_since)));
422 /* scheduled Cal update call */
423 if (!status_id) {
424 status_id = sbuddy->last_non_cal_status_id;
425 g_free(sbuddy->activity);
426 sbuddy->activity = g_strdup(sbuddy->last_non_cal_activity);
429 if (!status_id) {
430 SIPE_DEBUG_INFO("sipe_apply_calendar_status: status_id is NULL for %s, exiting.",
431 sbuddy->name ? sbuddy->name : "" );
432 return;
435 /* adjust to calendar status */
436 if (cal_status != SIPE_CAL_NO_DATA) {
437 SIPE_DEBUG_INFO("sipe_apply_calendar_status: user_avail_since: %s", asctime(localtime(&sbuddy->user_avail_since)));
439 if (cal_status == SIPE_CAL_BUSY
440 && cal_avail_since > sbuddy->user_avail_since
441 && 6500 >= sipe_get_availability_by_status(status_id, NULL))
443 status_id = SIPE_STATUS_ID_BUSY;
444 g_free(sbuddy->activity);
445 sbuddy->activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_IN_MEETING));
447 avail = sipe_get_availability_by_status(status_id, NULL);
449 SIPE_DEBUG_INFO("sipe_apply_calendar_status: activity_since : %s", asctime(localtime(&sbuddy->activity_since)));
450 if (cal_avail_since > sbuddy->activity_since) {
451 if (cal_status == SIPE_CAL_OOF
452 && avail >= 15000) /* 12000 in 2007 */
454 g_free(sbuddy->activity);
455 sbuddy->activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_OOF));
460 /* then set status_id actually */
461 SIPE_DEBUG_INFO("sipe_apply_calendar_status: to %s for %s", status_id, sbuddy->name ? sbuddy->name : "" );
462 sipe_backend_buddy_set_status(SIPE_CORE_PUBLIC, sbuddy->name, status_id);
464 /* set our account state to the one in roaming (including calendar info) */
465 self_uri = sip_uri_self(sipe_private);
466 if (sip->initial_state_published && sipe_strcase_equal(sbuddy->name, self_uri)) {
467 if (sipe_strequal(status_id, SIPE_STATUS_ID_OFFLINE)) {
468 status_id = g_strdup(SIPE_STATUS_ID_INVISIBLE); /* not not let offline status switch us off */
471 SIPE_DEBUG_INFO("sipe_apply_calendar_status: switch to '%s' for the account", sip->status);
472 sipe_set_purple_account_status_and_note(sip->account, status_id, sip->note, sip->do_not_publish);
474 g_free(self_uri);
477 void
478 sipe_core_buddy_got_status(struct sipe_core_public *sipe_public,
479 const gchar* uri,
480 const gchar *status_id)
482 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
483 struct sipe_buddy *sbuddy = g_hash_table_lookup(sipe_private->buddies, uri);
485 if (!sbuddy) return;
487 /* Check if on 2005 system contact's calendar,
488 * then set/preserve it.
490 if (!SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
491 sipe_apply_calendar_status(sipe_private, sbuddy, status_id);
492 } else {
493 sipe_backend_buddy_set_status(sipe_public, uri, status_id);
497 static void
498 update_calendar_status_cb(SIPE_UNUSED_PARAMETER char *name,
499 struct sipe_buddy *sbuddy,
500 struct sipe_core_private *sipe_private)
502 sipe_apply_calendar_status(sipe_private, sbuddy, NULL);
506 * Updates contact's status
507 * based on their calendar information.
509 * Applicability: 2005 systems
511 static void
512 update_calendar_status(struct sipe_core_private *sipe_private,
513 SIPE_UNUSED_PARAMETER void *unused)
515 SIPE_DEBUG_INFO_NOFORMAT("update_calendar_status() started.");
516 g_hash_table_foreach(sipe_private->buddies, (GHFunc)update_calendar_status_cb, sipe_private);
518 /* repeat scheduling */
519 sipe_sched_calendar_status_update(sipe_private, time(NULL) + 3*60 /* 3 min */);
523 * Schedules process of contacts' status update
524 * based on their calendar information.
525 * Should be scheduled to the beginning of every
526 * 15 min interval, like:
527 * 13:00, 13:15, 13:30, 13:45, etc.
529 * Applicability: 2005 systems
531 static void
532 sipe_sched_calendar_status_update(struct sipe_core_private *sipe_private,
533 time_t calculate_from)
535 int interval = 15*60;
536 /** start of the beginning of closest 15 min interval. */
537 time_t next_start = ((time_t)((int)((int)calculate_from)/interval + 1)*interval);
539 SIPE_DEBUG_INFO("sipe_sched_calendar_status_update: calculate_from time: %s",
540 asctime(localtime(&calculate_from)));
541 SIPE_DEBUG_INFO("sipe_sched_calendar_status_update: next start time : %s",
542 asctime(localtime(&next_start)));
544 sipe_schedule_seconds(sipe_private,
545 "<+2005-cal-status>",
546 NULL,
547 next_start - time(NULL),
548 update_calendar_status,
549 NULL);
553 * Schedules process of self status publish
554 * based on own calendar information.
555 * Should be scheduled to the beginning of every
556 * 15 min interval, like:
557 * 13:00, 13:15, 13:30, 13:45, etc.
559 * Applicability: 2007+ systems
561 static void
562 sipe_sched_calendar_status_self_publish(struct sipe_core_private *sipe_private,
563 time_t calculate_from)
565 int interval = 5*60;
566 /** start of the beginning of closest 5 min interval. */
567 time_t next_start = ((time_t)((int)((int)calculate_from)/interval + 1)*interval);
569 SIPE_DEBUG_INFO("sipe_sched_calendar_status_self_publish: calculate_from time: %s",
570 asctime(localtime(&calculate_from)));
571 SIPE_DEBUG_INFO("sipe_sched_calendar_status_self_publish: next start time : %s",
572 asctime(localtime(&next_start)));
574 sipe_schedule_seconds(sipe_private,
575 "<+2007-cal-status>",
576 NULL,
577 next_start - time(NULL),
578 publish_calendar_status_self,
579 NULL);
582 static void sipe_subscribe_resource_uri(const char *name,
583 SIPE_UNUSED_PARAMETER gpointer value,
584 gchar **resources_uri)
586 gchar *tmp = *resources_uri;
587 *resources_uri = g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp, name);
588 g_free(tmp);
591 static void sipe_subscribe_resource_uri_with_context(const char *name, gpointer value, gchar **resources_uri)
593 struct sipe_buddy *sbuddy = (struct sipe_buddy *)value;
594 gchar *context = sbuddy && sbuddy->just_added ? "><context/></resource>" : "/>";
595 gchar *tmp = *resources_uri;
597 if (sbuddy) sbuddy->just_added = FALSE; /* should be enought to include context one time */
599 *resources_uri = g_strdup_printf("%s<resource uri=\"%s\"%s\n", tmp, name, context);
600 g_free(tmp);
604 * Support for Batch Category SUBSCRIBE [MS-PRES] - msrtc-event-categories+xml OCS 2007
605 * Support for Batch Category SUBSCRIBE [MS-SIP] - adrl+xml LCS 2005
606 * The user sends an initial batched category SUBSCRIBE request against all contacts on his roaming list in only a request
607 * A batch category SUBSCRIBE request MUST have the same To-URI and From-URI.
608 * This header will be send only if adhoclist there is a "Supported: adhoclist" in REGISTER answer else will be send a Single Category SUBSCRIBE
611 static void sipe_subscribe_presence_batched_to(struct sipe_core_private *sipe_private,
612 gchar *resources_uri,
613 gchar *to)
615 gchar *contact = get_contact(sipe_private);
616 gchar *request;
617 gchar *content;
618 gchar *require = "";
619 gchar *accept = "";
620 gchar *autoextend = "";
621 gchar *content_type;
623 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
624 require = ", categoryList";
625 accept = ", application/msrtc-event-categories+xml, application/xpidf+xml, application/pidf+xml";
626 content_type = "application/msrtc-adrl-categorylist+xml";
627 content = g_strdup_printf(
628 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
629 "<action name=\"subscribe\" id=\"63792024\">\n"
630 "<adhocList>\n%s</adhocList>\n"
631 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
632 "<category name=\"calendarData\"/>\n"
633 "<category name=\"contactCard\"/>\n"
634 "<category name=\"note\"/>\n"
635 "<category name=\"state\"/>\n"
636 "</categoryList>\n"
637 "</action>\n"
638 "</batchSub>", sipe_private->username, resources_uri);
639 } else {
640 autoextend = "Supported: com.microsoft.autoextend\r\n";
641 content_type = "application/adrl+xml";
642 content = g_strdup_printf(
643 "<adhoclist xmlns=\"urn:ietf:params:xml:ns:adrl\" uri=\"sip:%s\" name=\"sip:%s\">\n"
644 "<create xmlns=\"\">\n%s</create>\n"
645 "</adhoclist>\n", sipe_private->username, sipe_private->username, resources_uri);
647 g_free(resources_uri);
649 request = g_strdup_printf(
650 "Require: adhoclist%s\r\n"
651 "Supported: eventlist\r\n"
652 "Accept: application/rlmi+xml, multipart/related, text/xml+msrtc.pidf%s\r\n"
653 "Supported: ms-piggyback-first-notify\r\n"
654 "%sSupported: ms-benotify\r\n"
655 "Proxy-Require: ms-benotify\r\n"
656 "Event: presence\r\n"
657 "Content-Type: %s\r\n"
658 "Contact: %s\r\n", require, accept, autoextend, content_type, contact);
659 g_free(contact);
661 sipe_subscribe_presence_buddy(sipe_private, to, request, content);
663 g_free(content);
664 g_free(to);
665 g_free(request);
668 static void sipe_subscribe_presence_batched(struct sipe_core_private *sipe_private,
669 SIPE_UNUSED_PARAMETER void *unused)
671 gchar *to = sip_uri_self(sipe_private);
672 gchar *resources_uri = g_strdup("");
673 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
674 g_hash_table_foreach(sipe_private->buddies, (GHFunc) sipe_subscribe_resource_uri_with_context , &resources_uri);
675 } else {
676 g_hash_table_foreach(sipe_private->buddies, (GHFunc) sipe_subscribe_resource_uri, &resources_uri);
679 sipe_subscribe_presence_batched_to(sipe_private, resources_uri, to);
682 struct presence_batched_routed {
683 gchar *host;
684 GSList *buddies;
687 static void sipe_subscribe_presence_batched_routed_free(void *payload)
689 struct presence_batched_routed *data = payload;
690 GSList *buddies = data->buddies;
691 while (buddies) {
692 g_free(buddies->data);
693 buddies = buddies->next;
695 g_slist_free(data->buddies);
696 g_free(data->host);
697 g_free(payload);
700 static void sipe_subscribe_presence_batched_routed(struct sipe_core_private *sipe_private,
701 void *payload)
703 struct presence_batched_routed *data = payload;
704 GSList *buddies = data->buddies;
705 gchar *resources_uri = g_strdup("");
706 while (buddies) {
707 gchar *tmp = resources_uri;
708 resources_uri = g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp, (char *) buddies->data);
709 g_free(tmp);
710 buddies = buddies->next;
712 sipe_subscribe_presence_batched_to(sipe_private, resources_uri,
713 g_strdup(data->host));
717 * Single Category SUBSCRIBE [MS-PRES] ; To send when the server returns a 200 OK message with state="resubscribe" in response.
718 * The user sends a single SUBSCRIBE request to the subscribed contact.
719 * The To-URI and the URI listed in the resource list MUST be the same for a single category SUBSCRIBE request.
723 static void sipe_subscribe_presence_single(struct sipe_core_private *sipe_private,
724 void *buddy_name)
726 gchar *to = sip_uri((char *)buddy_name);
727 gchar *tmp = get_contact(sipe_private);
728 gchar *request;
729 gchar *content = NULL;
730 gchar *autoextend = "";
731 gchar *content_type = "";
732 struct sipe_buddy *sbuddy = g_hash_table_lookup(sipe_private->buddies, to);
733 gchar *context = sbuddy && sbuddy->just_added ? "><context/></resource>" : "/>";
735 if (sbuddy) sbuddy->just_added = FALSE;
737 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
738 content_type = "Content-Type: application/msrtc-adrl-categorylist+xml\r\n";
739 } else {
740 autoextend = "Supported: com.microsoft.autoextend\r\n";
743 request = g_strdup_printf(
744 "Accept: application/msrtc-event-categories+xml, text/xml+msrtc.pidf, application/xpidf+xml, application/pidf+xml, application/rlmi+xml, multipart/related\r\n"
745 "Supported: ms-piggyback-first-notify\r\n"
746 "%s%sSupported: ms-benotify\r\n"
747 "Proxy-Require: ms-benotify\r\n"
748 "Event: presence\r\n"
749 "Contact: %s\r\n", autoextend, content_type, tmp);
751 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
752 content = g_strdup_printf(
753 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
754 "<action name=\"subscribe\" id=\"63792024\"><adhocList>\n"
755 "<resource uri=\"%s\"%s\n"
756 "</adhocList>\n"
757 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
758 "<category name=\"calendarData\"/>\n"
759 "<category name=\"contactCard\"/>\n"
760 "<category name=\"note\"/>\n"
761 "<category name=\"state\"/>\n"
762 "</categoryList>\n"
763 "</action>\n"
764 "</batchSub>", sipe_private->username, to, context);
767 g_free(tmp);
769 sipe_subscribe_presence_buddy(sipe_private, to, request, content);
771 g_free(content);
772 g_free(to);
773 g_free(request);
776 void sipe_set_status(PurpleAccount *account, PurpleStatus *status)
778 SIPE_DEBUG_INFO("sipe_set_status: status=%s", purple_status_get_id(status));
780 if (!purple_status_is_active(status))
781 return;
783 if (account->gc) {
784 struct sipe_core_private *sipe_private = PURPLE_ACCOUNT_TO_SIPE_CORE_PRIVATE;
785 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
787 if (sip) {
788 gchar *action_name;
789 gchar *tmp;
790 time_t now = time(NULL);
791 const char *status_id = purple_status_get_id(status);
792 const char *note = purple_status_get_attr_string(status, SIPE_STATUS_ATTR_ID_MESSAGE);
793 sipe_activity activity = sipe_get_activity_by_token(status_id);
794 gboolean do_not_publish = ((now - sip->do_not_publish[activity]) <= 2);
796 /* when other point of presence clears note, but we are keeping
797 * state if OOF note.
799 if (do_not_publish && !note && sip->cal && sip->cal->oof_note) {
800 SIPE_DEBUG_INFO_NOFORMAT("sipe_set_status: enabling publication as OOF note keepers.");
801 do_not_publish = FALSE;
804 SIPE_DEBUG_INFO("sipe_set_status: was: sip->do_not_publish[%s]=%d [?] now(time)=%d",
805 status_id, (int)sip->do_not_publish[activity], (int)now);
807 sip->do_not_publish[activity] = 0;
808 SIPE_DEBUG_INFO("sipe_set_status: set: sip->do_not_publish[%s]=%d [0]",
809 status_id, (int)sip->do_not_publish[activity]);
811 if (do_not_publish)
813 SIPE_DEBUG_INFO_NOFORMAT("sipe_set_status: publication was switched off, exiting.");
814 return;
817 g_free(sip->status);
818 sip->status = g_strdup(status_id);
820 /* hack to escape apostrof before comparison */
821 tmp = note ? sipe_utils_str_replace(note, "'", "&apos;") : NULL;
823 /* this will preserve OOF flag as well */
824 if (!sipe_strequal(tmp, sip->note)) {
825 sip->is_oof_note = FALSE;
826 g_free(sip->note);
827 sip->note = g_strdup(note);
828 sip->note_since = time(NULL);
830 g_free(tmp);
832 /* schedule 2 sec to capture idle flag */
833 action_name = g_strdup_printf("<%s>", "+set-status");
834 sipe_schedule_seconds(sipe_private,
835 action_name,
836 NULL,
837 SIPE_IDLE_SET_DELAY,
838 send_presence_status,
839 NULL);
840 g_free(action_name);
845 void
846 sipe_set_idle(PurpleConnection * gc,
847 int interval)
849 SIPE_DEBUG_INFO("sipe_set_idle: interval=%d", interval);
851 if (gc) {
852 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
853 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
855 if (sip) {
856 sip->idle_switch = time(NULL);
857 SIPE_DEBUG_INFO("sipe_set_idle: sip->idle_switch : %s", asctime(localtime(&(sip->idle_switch))));
862 void
863 sipe_group_buddy(PurpleConnection *gc,
864 const char *who,
865 const char *old_group_name,
866 const char *new_group_name)
868 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
869 struct sipe_buddy * buddy = g_hash_table_lookup(sipe_private->buddies, who);
870 struct sipe_group * old_group = NULL;
871 struct sipe_group * new_group;
873 SIPE_DEBUG_INFO("sipe_group_buddy[CB]: who:%s old_group_name:%s new_group_name:%s",
874 who ? who : "", old_group_name ? old_group_name : "", new_group_name ? new_group_name : "");
876 if(!buddy) { // buddy not in roaming list
877 return;
880 if (old_group_name) {
881 old_group = sipe_group_find_by_name(sipe_private, old_group_name);
883 new_group = sipe_group_find_by_name(sipe_private, new_group_name);
885 if (old_group) {
886 buddy->groups = g_slist_remove(buddy->groups, old_group);
887 SIPE_DEBUG_INFO("buddy %s removed from old group %s", who, old_group_name);
890 if (!new_group) {
891 sipe_group_create(sipe_private, new_group_name, who);
892 } else {
893 buddy->groups = slist_insert_unique_sorted(buddy->groups, new_group, (GCompareFunc)sipe_group_compare);
894 sipe_core_group_set_user(SIPE_CORE_PUBLIC, who);
898 void sipe_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
900 SIPE_DEBUG_INFO("sipe_add_buddy[CB]: buddy:%s group:%s", buddy ? buddy->name : "", group ? group->name : "");
902 /* libpurple can call us with undefined buddy or group */
903 if (buddy && group) {
904 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
906 /* Buddy name must be lower case as we use purple_normalize_nocase() to compare */
907 gchar *buddy_name = g_ascii_strdown(buddy->name, -1);
908 purple_blist_rename_buddy(buddy, buddy_name);
909 g_free(buddy_name);
911 /* Prepend sip: if needed */
912 if (!g_str_has_prefix(buddy->name, "sip:")) {
913 gchar *buf = sip_uri_from_name(buddy->name);
914 purple_blist_rename_buddy(buddy, buf);
915 g_free(buf);
918 if (!g_hash_table_lookup(sipe_private->buddies, buddy->name)) {
919 struct sipe_buddy *b = g_new0(struct sipe_buddy, 1);
920 SIPE_DEBUG_INFO("sipe_add_buddy: adding %s", buddy->name);
921 b->name = g_strdup(buddy->name);
922 b->just_added = TRUE;
923 g_hash_table_insert(sipe_private->buddies, b->name, b);
924 /* @TODO should go to callback */
925 sipe_subscribe_presence_single(sipe_private,
926 b->name);
927 } else {
928 SIPE_DEBUG_INFO("sipe_add_buddy: buddy %s already in internal list", buddy->name);
931 sipe_group_buddy(gc, buddy->name, NULL, group->name);
935 static void sipe_free_buddy(struct sipe_buddy *buddy)
937 #ifndef _WIN32
939 * We are calling g_hash_table_foreach_steal(). That means that no
940 * key/value deallocation functions are called. Therefore the glib
941 * hash code does not touch the key (buddy->name) or value (buddy)
942 * of the to-be-deleted hash node at all. It follows that we
944 * - MUST free the memory for the key ourselves and
945 * - ARE allowed to do it in this function
947 * Conclusion: glib must be broken on the Windows platform if sipe
948 * crashes with SIGTRAP when closing. You'll have to live
949 * with the memory leak until this is fixed.
951 g_free(buddy->name);
952 #endif
953 g_free(buddy->activity);
954 g_free(buddy->meeting_subject);
955 g_free(buddy->meeting_location);
956 g_free(buddy->note);
958 g_free(buddy->cal_start_time);
959 g_free(buddy->cal_free_busy_base64);
960 g_free(buddy->cal_free_busy);
961 g_free(buddy->last_non_cal_activity);
963 sipe_cal_free_working_hours(buddy->cal_working_hours);
965 g_free(buddy->device_name);
966 g_slist_free(buddy->groups);
967 g_free(buddy);
971 * Unassociates buddy from group first.
972 * Then see if no groups left, removes buddy completely.
973 * Otherwise updates buddy groups on server.
975 void sipe_remove_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
977 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
978 struct sipe_buddy *b;
979 struct sipe_group *g = NULL;
981 SIPE_DEBUG_INFO("sipe_remove_buddy[CB]: buddy:%s group:%s", buddy ? buddy->name : "", group ? group->name : "");
982 if (!buddy) return;
984 b = g_hash_table_lookup(sipe_private->buddies, buddy->name);
985 if (!b) return;
987 if (group) {
988 g = sipe_group_find_by_name(sipe_private, group->name);
991 if (g) {
992 b->groups = g_slist_remove(b->groups, g);
993 SIPE_DEBUG_INFO("buddy %s removed from group %s", buddy->name, g->name);
996 if (g_slist_length(b->groups) < 1) {
997 gchar *action_name = sipe_utils_presence_key(buddy->name);
998 sipe_schedule_cancel(sipe_private, action_name);
999 g_free(action_name);
1001 g_hash_table_remove(sipe_private->buddies, buddy->name);
1003 if (b->name) {
1004 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
1005 gchar * body = g_strdup_printf(SIPE_SOAP_DEL_CONTACT, b->name, sip->contacts_delta++);
1006 send_soap_request(sipe_private, body);
1007 g_free(body);
1010 sipe_free_buddy(b);
1011 } else {
1012 //updates groups on server
1013 sipe_core_group_set_user(SIPE_CORE_PUBLIC, b->name);
1019 * A callback for g_hash_table_foreach
1021 static void
1022 sipe_buddy_subscribe_cb(char *buddy_name,
1023 SIPE_UNUSED_PARAMETER struct sipe_buddy *buddy,
1024 struct sipe_core_private *sipe_private)
1026 gchar *action_name = sipe_utils_presence_key(buddy_name);
1027 /* g_hash_table_size() can never return 0, otherwise this function wouldn't be called :-) */
1028 guint time_range = (g_hash_table_size(sipe_private->buddies) * 1000) / 25; /* time interval for 25 requests per sec. In msec. */
1029 guint timeout = ((guint) rand()) / (RAND_MAX / time_range) + 1; /* random period within the range but never 0! */
1031 sipe_schedule_mseconds(sipe_private,
1032 action_name,
1033 g_strdup(buddy_name),
1034 timeout,
1035 sipe_subscribe_presence_single,
1036 g_free);
1037 g_free(action_name);
1041 * Removes entries from local buddy list
1042 * that does not correspond ones in the roaming contact list.
1044 static void sipe_cleanup_local_blist(struct sipe_core_private *sipe_private) {
1045 GSList *buddies = sipe_backend_buddy_find_all(SIPE_CORE_PUBLIC, NULL, NULL);
1046 GSList *entry = buddies;
1047 struct sipe_buddy *buddy;
1048 sipe_backend_buddy b;
1049 gchar *bname;
1050 gchar *gname;
1052 SIPE_DEBUG_INFO("sipe_cleanup_local_blist: overall %d backend buddies (including clones)", g_slist_length(buddies));
1053 SIPE_DEBUG_INFO("sipe_cleanup_local_blist: %d sipe buddies (unique)", g_hash_table_size(sipe_private->buddies));
1054 while (entry) {
1055 b = entry->data;
1056 gname = sipe_backend_buddy_get_group_name(SIPE_CORE_PUBLIC, b);
1057 bname = sipe_backend_buddy_get_name(SIPE_CORE_PUBLIC, b);
1058 buddy = g_hash_table_lookup(sipe_private->buddies, bname);
1059 g_free(bname);
1060 if(buddy) {
1061 gboolean in_sipe_groups = FALSE;
1062 GSList *entry2 = buddy->groups;
1063 while (entry2) {
1064 struct sipe_group *group = entry2->data;
1065 if (sipe_strequal(group->name, gname)) {
1066 in_sipe_groups = TRUE;
1067 break;
1069 entry2 = entry2->next;
1071 if(!in_sipe_groups) {
1072 SIPE_DEBUG_INFO("*** REMOVING %s from blist group: %s as not having this group in roaming list", bname, gname);
1073 sipe_backend_buddy_remove(SIPE_CORE_PUBLIC, b);
1075 } else {
1076 SIPE_DEBUG_INFO("*** REMOVING %s from blist group: %s as this buddy not in roaming list", bname, gname);
1077 sipe_backend_buddy_remove(SIPE_CORE_PUBLIC, b);
1079 g_free(gname);
1080 entry = entry->next;
1082 g_slist_free(buddies);
1085 static int
1086 sipe_find_access_level(struct sipe_core_private *sipe_private,
1087 const gchar *type,
1088 const gchar *value,
1089 gboolean *is_group_access);
1091 static void
1092 sipe_refresh_blocked_status_cb(char *buddy_name,
1093 SIPE_UNUSED_PARAMETER struct sipe_buddy *buddy,
1094 struct sipe_core_private *sipe_private)
1096 int container_id = sipe_find_access_level(sipe_private, "user", buddy_name, NULL);
1097 gboolean blocked = (container_id == 32000);
1098 gboolean blocked_in_blist = sipe_backend_buddy_is_blocked(SIPE_CORE_PUBLIC, buddy_name);
1100 /* SIPE_DEBUG_INFO("sipe_refresh_blocked_status_cb: buddy_name=%s, blocked=%s, blocked_in_blist=%s",
1101 buddy_name, blocked ? "T" : "F", blocked_in_blist ? "T" : "F"); */
1103 if (blocked != blocked_in_blist) {
1104 sipe_backend_buddy_set_blocked_status(SIPE_CORE_PUBLIC, buddy_name, blocked);
1108 static void
1109 sipe_refresh_blocked_status(struct sipe_core_private *sipe_private)
1111 g_hash_table_foreach(sipe_private->buddies,
1112 (GHFunc) sipe_refresh_blocked_status_cb,
1113 sipe_private);
1116 static gboolean sipe_process_roaming_contacts(struct sipe_core_private *sipe_private,
1117 struct sipmsg *msg)
1119 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
1120 int len = msg->bodylen;
1122 const gchar *tmp = sipmsg_find_header(msg, "Event");
1123 const sipe_xml *item;
1124 sipe_xml *isc;
1125 const gchar *contacts_delta;
1126 const sipe_xml *group_node;
1127 if (!g_str_has_prefix(tmp, "vnd-microsoft-roaming-contacts")) {
1128 return FALSE;
1131 /* Convert the contact from XML to backend Buddies */
1132 isc = sipe_xml_parse(msg->body, len);
1133 if (!isc) {
1134 return FALSE;
1137 contacts_delta = sipe_xml_attribute(isc, "deltaNum");
1138 if (contacts_delta) {
1139 sip->contacts_delta = (int)g_ascii_strtod(contacts_delta, NULL);
1142 if (sipe_strequal(sipe_xml_name(isc), "contactList")) {
1144 /* Parse groups */
1145 for (group_node = sipe_xml_child(isc, "group"); group_node; group_node = sipe_xml_twin(group_node)) {
1146 struct sipe_group * group = g_new0(struct sipe_group, 1);
1147 const char *name = sipe_xml_attribute(group_node, "name");
1149 if (g_str_has_prefix(name, "~")) {
1150 name = _("Other Contacts");
1152 group->name = g_strdup(name);
1153 group->id = (int)g_ascii_strtod(sipe_xml_attribute(group_node, "id"), NULL);
1155 sipe_group_add(sipe_private, group);
1158 // Make sure we have at least one group
1159 if (g_slist_length(sip->groups) == 0) {
1160 sipe_group_create(sipe_private, _("Other Contacts"), NULL);
1163 /* Parse contacts */
1164 for (item = sipe_xml_child(isc, "contact"); item; item = sipe_xml_twin(item)) {
1165 const gchar *uri = sipe_xml_attribute(item, "uri");
1166 const gchar *name = sipe_xml_attribute(item, "name");
1167 gchar *buddy_name;
1168 struct sipe_buddy *buddy = NULL;
1169 gchar *tmp;
1170 gchar **item_groups;
1171 int i = 0;
1173 /* Buddy name must be lower case as we use purple_normalize_nocase() to compare */
1174 tmp = sip_uri_from_name(uri);
1175 buddy_name = g_ascii_strdown(tmp, -1);
1176 g_free(tmp);
1178 /* assign to group Other Contacts if nothing else received */
1179 tmp = g_strdup(sipe_xml_attribute(item, "groups"));
1180 if(is_empty(tmp)) {
1181 struct sipe_group *group = sipe_group_find_by_name(sipe_private, _("Other Contacts"));
1182 g_free(tmp);
1183 tmp = group ? g_strdup_printf("%d", group->id) : g_strdup("1");
1185 item_groups = g_strsplit(tmp, " ", 0);
1186 g_free(tmp);
1188 while (item_groups[i]) {
1189 struct sipe_group *group = sipe_group_find_by_id(sipe_private, g_ascii_strtod(item_groups[i], NULL));
1191 // If couldn't find the right group for this contact, just put them in the first group we have
1192 if (group == NULL && g_slist_length(sip->groups) > 0) {
1193 group = sip->groups->data;
1196 if (group != NULL) {
1197 gchar *b_alias;
1198 sipe_backend_buddy b = sipe_backend_buddy_find(SIPE_CORE_PUBLIC, buddy_name, group->name);
1199 if (!b){
1200 b = sipe_backend_buddy_add(SIPE_CORE_PUBLIC, buddy_name, uri, group->name);
1201 SIPE_DEBUG_INFO("Created new buddy %s with alias %s", buddy_name, uri);
1204 b_alias = sipe_backend_buddy_get_alias(SIPE_CORE_PUBLIC, b);
1205 if (sipe_strcase_equal(uri, b_alias)) {
1206 if (name != NULL && strlen(name) != 0) {
1207 sipe_backend_buddy_set_alias(SIPE_CORE_PUBLIC, b, name);
1209 SIPE_DEBUG_INFO("Replaced buddy %s alias with %s", buddy_name, name);
1212 g_free(b_alias);
1214 if (!buddy) {
1215 buddy = g_new0(struct sipe_buddy, 1);
1216 buddy->name = sipe_backend_buddy_get_name(SIPE_CORE_PUBLIC, b);
1217 g_hash_table_insert(sipe_private->buddies, buddy->name, buddy);
1219 SIPE_DEBUG_INFO("Added SIPE buddy %s", buddy->name);
1222 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
1224 SIPE_DEBUG_INFO("Added buddy %s to group %s", buddy->name, group->name);
1225 } else {
1226 SIPE_DEBUG_INFO("No group found for contact %s! Unable to add to buddy list",
1227 name);
1230 i++;
1231 } // while, contact groups
1232 g_strfreev(item_groups);
1233 g_free(buddy_name);
1235 } // for, contacts
1237 sipe_cleanup_local_blist(sipe_private);
1239 /* Add self-contact if not there yet. 2005 systems. */
1240 /* This will resemble subscription to roaming_self in 2007 systems */
1241 if (!SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
1242 gchar *self_uri = sip_uri_self(sipe_private);
1243 struct sipe_buddy *buddy = g_hash_table_lookup(sipe_private->buddies, self_uri);
1245 if (!buddy) {
1246 buddy = g_new0(struct sipe_buddy, 1);
1247 buddy->name = g_strdup(self_uri);
1248 g_hash_table_insert(sipe_private->buddies, buddy->name, buddy);
1250 g_free(self_uri);
1253 sipe_xml_free(isc);
1255 /* subscribe to buddies */
1256 if (!sip->subscribed_buddies) { //do it once, then count Expire field to schedule resubscribe.
1257 if (sip->batched_support) {
1258 sipe_subscribe_presence_batched(sipe_private, NULL);
1259 } else {
1260 g_hash_table_foreach(sipe_private->buddies,
1261 (GHFunc)sipe_buddy_subscribe_cb,
1262 sipe_private);
1264 sip->subscribed_buddies = TRUE;
1266 /* for 2005 systems schedule contacts' status update
1267 * based on their calendar information
1269 if (!SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
1270 sipe_sched_calendar_status_update(sipe_private, time(NULL));
1273 return 0;
1277 * Fires on deregistration event initiated by server.
1278 * [MS-SIPREGE] SIP extension.
1281 // 2007 Example
1283 // Content-Type: text/registration-event
1284 // subscription-state: terminated;expires=0
1285 // ms-diagnostics-public: 4141;reason="User disabled"
1287 // deregistered;event=rejected
1289 static void sipe_process_registration_notify(struct sipe_core_private *sipe_private,
1290 struct sipmsg *msg)
1292 const gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
1293 gchar *event = NULL;
1294 gchar *reason = NULL;
1295 gchar *warning;
1297 SIPE_DEBUG_INFO_NOFORMAT("sipe_process_registration_notify: deregistration received.");
1299 if (!g_ascii_strncasecmp(contenttype, "text/registration-event", 23)) {
1300 event = sipmsg_find_part_of_header(msg->body, "event=", NULL, NULL);
1301 //@TODO have proper parameter extraction _by_name_ func, case insesitive.
1302 event = event ? event : sipmsg_find_part_of_header(msg->body, "event=", ";", NULL);
1303 } else {
1304 SIPE_DEBUG_INFO_NOFORMAT("sipe_process_registration_notify: unknown content type, exiting.");
1305 return;
1308 reason = sipmsg_get_ms_diagnostics_reason(msg);
1309 reason = reason ? reason : sipmsg_get_ms_diagnostics_public_reason(msg);
1310 if (!reason) { // for LCS2005
1311 if (event && sipe_strcase_equal(event, "unregistered")) {
1312 //reason = g_strdup(_("User logged out")); // [MS-OCER]
1313 reason = g_strdup(_("you are already signed in at another location"));
1314 } else if (event && sipe_strcase_equal(event, "rejected")) {
1315 reason = g_strdup(_("user disabled")); // [MS-OCER]
1316 } else if (event && sipe_strcase_equal(event, "deactivated")) {
1317 reason = g_strdup(_("user moved")); // [MS-OCER]
1320 g_free(event);
1321 warning = g_strdup_printf(_("You have been rejected by the server: %s"), reason ? reason : _("no reason given"));
1322 g_free(reason);
1324 sipe_backend_connection_error(SIPE_CORE_PUBLIC,
1325 SIPE_CONNECTION_ERROR_INVALID_USERNAME,
1326 warning);
1327 g_free(warning);
1331 static void sipe_process_provisioning_v2(struct sipe_core_private *sipe_private,
1332 struct sipmsg *msg)
1334 sipe_xml *xn_provision_group_list;
1335 const sipe_xml *node;
1337 xn_provision_group_list = sipe_xml_parse(msg->body, msg->bodylen);
1339 /* provisionGroup */
1340 for (node = sipe_xml_child(xn_provision_group_list, "provisionGroup"); node; node = sipe_xml_twin(node)) {
1341 if (sipe_strequal("ServerConfiguration", sipe_xml_attribute(node, "name"))) {
1342 g_free(sipe_private->focus_factory_uri);
1343 sipe_private->focus_factory_uri = sipe_xml_data(sipe_xml_child(node, "focusFactoryUri"));
1344 SIPE_DEBUG_INFO("sipe_process_provisioning_v2: sipe_private->focus_factory_uri=%s",
1345 sipe_private->focus_factory_uri ? sipe_private->focus_factory_uri : "");
1347 #ifdef HAVE_VV
1348 g_free(sipe_private->mras_uri);
1349 sipe_private->mras_uri = g_strstrip(sipe_xml_data(sipe_xml_child(node, "mrasUri")));
1350 SIPE_DEBUG_INFO("sipe_process_provisioning_v2: sipe_private->mras_uri=%s",
1351 sipe_private->mras_uri ? sipe_private->mras_uri : "");
1353 if (sipe_private->mras_uri)
1354 sipe_media_get_av_edge_credentials(sipe_private);
1355 #endif
1356 break;
1359 sipe_xml_free(xn_provision_group_list);
1362 /** for 2005 system */
1363 static void
1364 sipe_process_provisioning(struct sipe_core_private *sipe_private,
1365 struct sipmsg *msg)
1367 sipe_xml *xn_provision;
1368 const sipe_xml *node;
1370 xn_provision = sipe_xml_parse(msg->body, msg->bodylen);
1371 if ((node = sipe_xml_child(xn_provision, "user"))) {
1372 SIPE_DEBUG_INFO("sipe_process_provisioning: uri=%s", sipe_xml_attribute(node, "uri"));
1373 if ((node = sipe_xml_child(node, "line"))) {
1374 const gchar *line_uri = sipe_xml_attribute(node, "uri");
1375 const gchar *server = sipe_xml_attribute(node, "server");
1376 SIPE_DEBUG_INFO("sipe_process_provisioning: line_uri=%s server=%s", line_uri, server);
1377 sip_csta_open(sipe_private, line_uri, server);
1380 sipe_xml_free(xn_provision);
1383 static void sipe_process_roaming_acl(struct sipe_core_private *sipe_private,
1384 struct sipmsg *msg)
1386 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
1387 const gchar *contacts_delta;
1388 sipe_xml *xml;
1390 xml = sipe_xml_parse(msg->body, msg->bodylen);
1391 if (!xml)
1393 return;
1396 contacts_delta = sipe_xml_attribute(xml, "deltaNum");
1397 if (contacts_delta)
1399 sip->acl_delta = (int)g_ascii_strtod(contacts_delta, NULL);
1402 sipe_xml_free(xml);
1405 /** MS-PRES container */
1406 struct sipe_container {
1407 guint id;
1408 guint version;
1409 GSList *members;
1411 /** MS-PRES container member */
1412 struct sipe_container_member {
1413 /** user, domain, sameEnterprise, federated, publicCloud; everyone */
1414 gchar *type;
1415 gchar *value;
1418 static void
1419 free_container_member(struct sipe_container_member *member)
1421 if (!member) return;
1423 g_free(member->type);
1424 g_free(member->value);
1425 g_free(member);
1428 static void
1429 free_container(struct sipe_container *container)
1431 GSList *entry;
1433 if (!container) return;
1435 entry = container->members;
1436 while (entry) {
1437 void *data = entry->data;
1438 entry = g_slist_remove(entry, data);
1439 free_container_member((struct sipe_container_member *)data);
1441 g_free(container);
1444 static void
1445 sipe_send_container_members_prepare(const guint container_id,
1446 const guint container_version,
1447 const gchar *action,
1448 const gchar *type,
1449 const gchar *value,
1450 char **container_xmls)
1452 gchar *value_str = value ? g_strdup_printf(" value=\"%s\"", value) : g_strdup("");
1453 gchar *body;
1455 if (!container_xmls) return;
1457 body = g_strdup_printf(
1458 "<container id=\"%d\" version=\"%d\"><member action=\"%s\" type=\"%s\"%s/></container>",
1459 container_id,
1460 container_version,
1461 action,
1462 type,
1463 value_str);
1464 g_free(value_str);
1466 if ((*container_xmls) == NULL) {
1467 *container_xmls = body;
1468 } else {
1469 char *tmp = *container_xmls;
1471 *container_xmls = g_strconcat(*container_xmls, body, NULL);
1472 g_free(tmp);
1473 g_free(body);
1477 static void
1478 sipe_send_set_container_members(struct sipe_core_private *sipe_private,
1479 char *container_xmls)
1481 gchar *self;
1482 gchar *contact;
1483 gchar *hdr;
1484 gchar *body;
1486 if (!container_xmls) return;
1488 self = sip_uri_self(sipe_private);
1489 body = g_strdup_printf(
1490 "<setContainerMembers xmlns=\"http://schemas.microsoft.com/2006/09/sip/container-management\">"
1491 "%s"
1492 "</setContainerMembers>",
1493 container_xmls);
1495 contact = get_contact(sipe_private);
1496 hdr = g_strdup_printf("Contact: %s\r\n"
1497 "Content-Type: application/msrtc-setcontainermembers+xml\r\n", contact);
1498 g_free(contact);
1500 sip_transport_service(sipe_private,
1501 self,
1502 hdr,
1503 body,
1504 NULL);
1506 g_free(hdr);
1507 g_free(body);
1508 g_free(self);
1512 * Finds locally stored MS-PRES container member
1514 static struct sipe_container_member *
1515 sipe_find_container_member(struct sipe_container *container,
1516 const gchar *type,
1517 const gchar *value)
1519 struct sipe_container_member *member;
1520 GSList *entry;
1522 if (container == NULL || type == NULL) {
1523 return NULL;
1526 entry = container->members;
1527 while (entry) {
1528 member = entry->data;
1529 if (sipe_strcase_equal(member->type, type) &&
1530 sipe_strcase_equal(member->value, value))
1532 return member;
1534 entry = entry->next;
1536 return NULL;
1540 * Finds locally stored MS-PRES container by id
1542 static struct sipe_container *
1543 sipe_find_container(struct sipe_core_private *sipe_private,
1544 guint id)
1546 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
1547 struct sipe_container *container;
1548 GSList *entry;
1550 if (sip == NULL) {
1551 return NULL;
1554 entry = sip->containers;
1555 while (entry) {
1556 container = entry->data;
1557 if (id == container->id) {
1558 return container;
1560 entry = entry->next;
1562 return NULL;
1565 static GSList *
1566 sipe_get_access_domains(struct sipe_core_private *sipe_private)
1568 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
1569 struct sipe_container *container;
1570 struct sipe_container_member *member;
1571 GSList *entry;
1572 GSList *entry2;
1573 GSList *res = NULL;
1575 if (!sip) return NULL;
1577 entry = sip->containers;
1578 while (entry) {
1579 container = entry->data;
1581 entry2 = container->members;
1582 while (entry2) {
1583 member = entry2->data;
1584 if (sipe_strcase_equal(member->type, "domain"))
1586 res = slist_insert_unique_sorted(res, g_strdup(member->value), (GCompareFunc)g_ascii_strcasecmp);
1588 entry2 = entry2->next;
1590 entry = entry->next;
1592 return res;
1596 * Returns pointer to domain part in provided Email URL
1598 * @param email an email URL. Example: first.last@hq.company.com
1599 * @return pointer to domain part of email URL. Coresponding example: hq.company.com
1601 * Doesn't allocate memory
1603 static const char *
1604 sipe_get_domain(const char *email)
1606 char *tmp;
1608 if (!email) return NULL;
1610 tmp = strstr(email, "@");
1612 if (tmp && ((tmp+1) < (email + strlen(email)))) {
1613 return tmp+1;
1614 } else {
1615 return NULL;
1620 /* @TODO: replace with binary search for faster access? */
1621 /** source: http://support.microsoft.com/kb/897567 */
1622 static const char * const public_domains [] = {
1623 "aol.com", "icq.com", "love.com", "mac.com", "br.live.com",
1624 "hotmail.co.il", "hotmail.co.jp", "hotmail.co.th", "hotmail.co.uk",
1625 "hotmail.com", "hotmail.com.ar", "hotmail.com.tr", "hotmail.es",
1626 "hotmail.de", "hotmail.fr", "hotmail.it", "live.at", "live.be",
1627 "live.ca", "live.cl", "live.cn", "live.co.in", "live.co.kr",
1628 "live.co.uk", "live.co.za", "live.com", "live.com.ar", "live.com.au",
1629 "live.com.co", "live.com.mx", "live.com.my", "live.com.pe",
1630 "live.com.ph", "live.com.pk", "live.com.pt", "live.com.sg",
1631 "live.com.ve", "live.de", "live.dk", "live.fr", "live.hk", "live.ie",
1632 "live.in", "live.it", "live.jp", "live.nl", "live.no", "live.ph",
1633 "live.ru", "live.se", "livemail.com.br", "livemail.tw",
1634 "messengeruser.com", "msn.com", "passport.com", "sympatico.ca",
1635 "tw.live.com", "webtv.net", "windowslive.com", "windowslive.es",
1636 "yahoo.com",
1637 NULL};
1639 static gboolean
1640 sipe_is_public_domain(const char *domain)
1642 int i = 0;
1643 while (public_domains[i]) {
1644 if (sipe_strcase_equal(public_domains[i], domain)) {
1645 return TRUE;
1647 i++;
1649 return FALSE;
1653 * Access Levels
1654 * 32000 - Blocked
1655 * 400 - Personal
1656 * 300 - Team
1657 * 200 - Company
1658 * 100 - Public
1660 static const char *
1661 sipe_get_access_level_name(int container_id)
1663 switch(container_id) {
1664 case 32000: return _("Blocked");
1665 case 400: return _("Personal");
1666 case 300: return _("Team");
1667 case 200: return _("Company");
1668 case 100: return _("Public");
1670 return _("Unknown");
1673 static const guint containers[] = {32000, 400, 300, 200, 100};
1674 #define CONTAINERS_LEN (sizeof(containers) / sizeof(guint))
1677 static int
1678 sipe_find_member_access_level(struct sipe_core_private *sipe_private,
1679 const gchar *type,
1680 const gchar *value)
1682 unsigned int i = 0;
1683 const gchar *value_mod = value;
1685 if (!type) return -1;
1687 if (sipe_strequal("user", type)) {
1688 value_mod = sipe_get_no_sip_uri(value);
1691 for (i = 0; i < CONTAINERS_LEN; i++) {
1692 struct sipe_container_member *member;
1693 struct sipe_container *container = sipe_find_container(sipe_private, containers[i]);
1694 if (!container) continue;
1696 member = sipe_find_container_member(container, type, value_mod);
1697 if (member) return containers[i];
1700 return -1;
1703 /** Member type: user, domain, sameEnterprise, federated, publicCloud; everyone */
1704 static int
1705 sipe_find_access_level(struct sipe_core_private *sipe_private,
1706 const gchar *type,
1707 const gchar *value,
1708 gboolean *is_group_access)
1710 int container_id = -1;
1712 if (sipe_strequal("user", type)) {
1713 const char *domain;
1714 const char *no_sip_uri = sipe_get_no_sip_uri(value);
1716 container_id = sipe_find_member_access_level(sipe_private, "user", no_sip_uri);
1717 if (container_id >= 0) {
1718 if (is_group_access) *is_group_access = FALSE;
1719 return container_id;
1722 domain = sipe_get_domain(no_sip_uri);
1723 container_id = sipe_find_member_access_level(sipe_private, "domain", domain);
1724 if (container_id >= 0) {
1725 if (is_group_access) *is_group_access = TRUE;
1726 return container_id;
1729 container_id = sipe_find_member_access_level(sipe_private, "sameEnterprise", NULL);
1730 if ((container_id >= 0) && sipe_strcase_equal(sipe_private->public.sip_domain, domain)) {
1731 if (is_group_access) *is_group_access = TRUE;
1732 return container_id;
1735 container_id = sipe_find_member_access_level(sipe_private, "publicCloud", NULL);
1736 if ((container_id >= 0) && sipe_is_public_domain(domain)) {
1737 if (is_group_access) *is_group_access = TRUE;
1738 return container_id;
1741 container_id = sipe_find_member_access_level(sipe_private, "everyone", NULL);
1742 if ((container_id >= 0)) {
1743 if (is_group_access) *is_group_access = TRUE;
1744 return container_id;
1746 } else {
1747 container_id = sipe_find_member_access_level(sipe_private, type, value);
1748 if (is_group_access) *is_group_access = FALSE;
1751 return container_id;
1755 * @param container_id a new access level. If -1 then current access level
1756 * is just removed (I.e. the member is removed from all containers).
1757 * @param type a type of member. E.g. "user", "sameEnterprise", etc.
1758 * @param value a value for member. E.g. SIP URI for "user" member type.
1760 static void
1761 sipe_change_access_level(struct sipe_core_private *sipe_private,
1762 const int container_id,
1763 const gchar *type,
1764 const gchar *value)
1766 unsigned int i;
1767 int current_container_id = -1;
1768 char *container_xmls = NULL;
1770 /* for each container: find/delete */
1771 for (i = 0; i < CONTAINERS_LEN; i++) {
1772 struct sipe_container_member *member;
1773 struct sipe_container *container = sipe_find_container(sipe_private, containers[i]);
1775 if (!container) continue;
1777 member = sipe_find_container_member(container, type, value);
1778 if (member) {
1779 current_container_id = containers[i];
1780 /* delete/publish current access level */
1781 if (container_id < 0 || container_id != current_container_id) {
1782 sipe_send_container_members_prepare(current_container_id, container->version, "remove", type, value, &container_xmls);
1783 /* remove member from our cache, to be able to recalculate AL below */
1784 container->members = g_slist_remove(container->members, member);
1785 current_container_id = -1;
1790 /* recalculate AL below */
1791 current_container_id = sipe_find_access_level(sipe_private, type, value, NULL);
1793 /* assign/publish new access level */
1794 if (container_id != current_container_id && container_id >= 0) {
1795 struct sipe_container *container = sipe_find_container(sipe_private, container_id);
1796 guint version = container ? container->version : 0;
1798 sipe_send_container_members_prepare(container_id, version, "add", type, value, &container_xmls);
1801 if (container_xmls) {
1802 sipe_send_set_container_members(sipe_private, container_xmls);
1804 g_free(container_xmls);
1807 static void
1808 free_publication(struct sipe_publication *publication)
1810 g_free(publication->category);
1811 g_free(publication->cal_event_hash);
1812 g_free(publication->note);
1814 g_free(publication->working_hours_xml_str);
1815 g_free(publication->fb_start_str);
1816 g_free(publication->free_busy_base64);
1818 g_free(publication);
1821 /* key is <category><instance><container> */
1822 static gboolean
1823 sipe_is_our_publication(struct sipe_core_private *sipe_private,
1824 const gchar *key)
1826 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
1827 GSList *entry;
1829 /* filling keys for our publications if not yet cached */
1830 if (!sip->our_publication_keys) {
1831 guint device_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_DEVICE);
1832 guint machine_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_MACHINE);
1833 guint user_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_USER);
1834 guint calendar_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_CALENDAR);
1835 guint cal_oof_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_CALENDAR_OOF);
1836 guint cal_data_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_CALENDAR_DATA);
1837 guint note_oof_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_NOTE_OOF);
1839 SIPE_DEBUG_INFO_NOFORMAT("* Our Publication Instances *");
1840 SIPE_DEBUG_INFO("\tDevice : %u\t0x%08X", device_instance, device_instance);
1841 SIPE_DEBUG_INFO("\tMachine State : %u\t0x%08X", machine_instance, machine_instance);
1842 SIPE_DEBUG_INFO("\tUser Stare : %u\t0x%08X", user_instance, user_instance);
1843 SIPE_DEBUG_INFO("\tCalendar State : %u\t0x%08X", calendar_instance, calendar_instance);
1844 SIPE_DEBUG_INFO("\tCalendar OOF State : %u\t0x%08X", cal_oof_instance, cal_oof_instance);
1845 SIPE_DEBUG_INFO("\tCalendar FreeBusy : %u\t0x%08X", cal_data_instance, cal_data_instance);
1846 SIPE_DEBUG_INFO("\tOOF Note : %u\t0x%08X", note_oof_instance, note_oof_instance);
1847 SIPE_DEBUG_INFO("\tNote : %u", 0);
1848 SIPE_DEBUG_INFO("\tCalendar WorkingHours: %u", 0);
1850 /* device */
1851 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1852 g_strdup_printf("<%s><%u><%u>", "device", device_instance, 2));
1854 /* state:machineState */
1855 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1856 g_strdup_printf("<%s><%u><%u>", "state", machine_instance, 2));
1857 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1858 g_strdup_printf("<%s><%u><%u>", "state", machine_instance, 3));
1860 /* state:userState */
1861 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1862 g_strdup_printf("<%s><%u><%u>", "state", user_instance, 2));
1863 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1864 g_strdup_printf("<%s><%u><%u>", "state", user_instance, 3));
1866 /* state:calendarState */
1867 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1868 g_strdup_printf("<%s><%u><%u>", "state", calendar_instance, 2));
1869 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1870 g_strdup_printf("<%s><%u><%u>", "state", calendar_instance, 3));
1872 /* state:calendarState OOF */
1873 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1874 g_strdup_printf("<%s><%u><%u>", "state", cal_oof_instance, 2));
1875 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1876 g_strdup_printf("<%s><%u><%u>", "state", cal_oof_instance, 3));
1878 /* note */
1879 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1880 g_strdup_printf("<%s><%u><%u>", "note", 0, 200));
1881 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1882 g_strdup_printf("<%s><%u><%u>", "note", 0, 300));
1883 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1884 g_strdup_printf("<%s><%u><%u>", "note", 0, 400));
1886 /* note OOF */
1887 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1888 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 200));
1889 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1890 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 300));
1891 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1892 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 400));
1894 /* calendarData:WorkingHours */
1895 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1896 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 1));
1897 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1898 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 100));
1899 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1900 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 200));
1901 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1902 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 300));
1903 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1904 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 400));
1905 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1906 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 32000));
1908 /* calendarData:FreeBusy */
1909 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1910 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 1));
1911 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1912 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 100));
1913 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1914 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 200));
1915 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1916 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 300));
1917 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1918 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 400));
1919 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1920 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 32000));
1922 //SIPE_DEBUG_INFO("sipe_is_our_publication: sip->our_publication_keys length=%d",
1923 // sip->our_publication_keys ? (int) g_slist_length(sip->our_publication_keys) : -1);
1926 //SIPE_DEBUG_INFO("sipe_is_our_publication: key=%s", key);
1928 entry = sip->our_publication_keys;
1929 while (entry) {
1930 //SIPE_DEBUG_INFO(" sipe_is_our_publication: entry->data=%s", entry->data);
1931 if (sipe_strequal(entry->data, key)) {
1932 return TRUE;
1934 entry = entry->next;
1936 return FALSE;
1939 /** Property names to store in blist.xml */
1940 #define ALIAS_PROP "alias"
1941 #define EMAIL_PROP "email"
1942 #define PHONE_PROP "phone"
1943 #define PHONE_DISPLAY_PROP "phone-display"
1944 #define PHONE_MOBILE_PROP "phone-mobile"
1945 #define PHONE_MOBILE_DISPLAY_PROP "phone-mobile-display"
1946 #define PHONE_HOME_PROP "phone-home"
1947 #define PHONE_HOME_DISPLAY_PROP "phone-home-display"
1948 #define PHONE_OTHER_PROP "phone-other"
1949 #define PHONE_OTHER_DISPLAY_PROP "phone-other-display"
1950 #define PHONE_CUSTOM1_PROP "phone-custom1"
1951 #define PHONE_CUSTOM1_DISPLAY_PROP "phone-custom1-display"
1952 #define SITE_PROP "site"
1953 #define COMPANY_PROP "company"
1954 #define DEPARTMENT_PROP "department"
1955 #define TITLE_PROP "title"
1956 #define OFFICE_PROP "office"
1957 /** implies work address */
1958 #define ADDRESS_STREET_PROP "address-street"
1959 #define ADDRESS_CITY_PROP "address-city"
1960 #define ADDRESS_STATE_PROP "address-state"
1961 #define ADDRESS_ZIPCODE_PROP "address-zipcode"
1962 #define ADDRESS_COUNTRYCODE_PROP "address-country-code"
1965 * Tries to figure out user first and last name
1966 * based on Display Name and email properties.
1968 * Allocates memory - must be g_free()'d
1970 * Examples to parse:
1971 * First Last
1972 * First Last - Company Name
1973 * Last, First
1974 * Last, First M.
1975 * Last, First (C)(STP) (Company)
1976 * first.last@company.com (preprocessed as "first last")
1977 * first.last.company.com@reuters.net (preprocessed as "first last company com")
1979 * Unusable examples:
1980 * user@company.com (preprocessed as "user")
1981 * first.m.last@company.com (preprocessed as "first m last")
1982 * user.company.com@reuters.net (preprocessed as "user company com")
1984 static void
1985 sipe_get_first_last_names(struct sipe_core_private *sipe_private,
1986 const char *uri,
1987 char **first_name,
1988 char **last_name)
1990 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
1991 sipe_backend_buddy p_buddy;
1992 char *display_name;
1993 gchar *email;
1994 const char *first, *last;
1995 char *tmp;
1996 char **parts;
1997 gboolean has_comma = FALSE;
1999 if (!sip || !uri) return;
2001 p_buddy = sipe_backend_buddy_find(SIPE_CORE_PUBLIC, uri, NULL);
2003 if (!p_buddy) return;
2005 display_name = sipe_backend_buddy_get_alias(SIPE_CORE_PUBLIC, p_buddy);
2006 email = sipe_backend_buddy_get_string(SIPE_CORE_PUBLIC, p_buddy, SIPE_BUDDY_INFO_EMAIL);
2008 if (!display_name && !email) return;
2010 /* if no display name, make "first last anything_else" out of email */
2011 if (email && !display_name) {
2012 display_name = g_strndup(email, strstr(email, "@") - email);
2013 display_name = sipe_utils_str_replace((tmp = display_name), ".", " ");
2014 g_free(tmp);
2017 if (display_name) {
2018 has_comma = (strstr(display_name, ",") != NULL);
2019 display_name = sipe_utils_str_replace((tmp = display_name), ", ", " ");
2020 g_free(tmp);
2021 display_name = sipe_utils_str_replace((tmp = display_name), ",", " ");
2022 g_free(tmp);
2025 parts = g_strsplit(display_name, " ", 0);
2027 if (!parts[0] || !parts[1]) {
2028 g_free(email);
2029 g_free(display_name);
2030 g_strfreev(parts);
2031 return;
2034 if (has_comma) {
2035 last = parts[0];
2036 first = parts[1];
2037 } else {
2038 first = parts[0];
2039 last = parts[1];
2042 if (first_name) {
2043 *first_name = g_strstrip(g_strdup(first));
2046 if (last_name) {
2047 *last_name = g_strstrip(g_strdup(last));
2050 g_free(email);
2051 g_free(display_name);
2052 g_strfreev(parts);
2056 * Update user information
2058 * @param uri buddy SIP URI with 'sip:' prefix whose info we want to change.
2059 * @param property_name
2060 * @param property_value may be modified to strip white space
2062 static void
2063 sipe_update_user_info(struct sipe_core_private *sipe_private,
2064 const char *uri,
2065 sipe_buddy_info_fields propkey,
2066 char *property_value)
2068 GSList *buddies, *entry;
2070 if (property_value)
2071 property_value = g_strstrip(property_value);
2073 entry = buddies = sipe_backend_buddy_find_all(SIPE_CORE_PUBLIC, uri, NULL); /* all buddies in different groups */
2074 while (entry) {
2075 gchar *prop_str;
2076 gchar *server_alias;
2077 gchar *alias;
2078 sipe_backend_buddy p_buddy = entry->data;
2080 /* for Display Name */
2081 if (propkey == SIPE_BUDDY_INFO_DISPLAY_NAME) {
2082 alias = sipe_backend_buddy_get_alias(SIPE_CORE_PUBLIC, p_buddy);
2083 if (property_value && sipe_is_bad_alias(uri, alias)) {
2084 SIPE_DEBUG_INFO("Replacing alias for %s with %s", uri, property_value);
2085 sipe_backend_buddy_set_alias(SIPE_CORE_PUBLIC, p_buddy, property_value);
2087 g_free(alias);
2089 server_alias = sipe_backend_buddy_get_server_alias(SIPE_CORE_PUBLIC, p_buddy);
2090 if (!is_empty(property_value) &&
2091 (!sipe_strequal(property_value, server_alias) || is_empty(server_alias)) )
2093 SIPE_DEBUG_INFO("Replacing service alias for %s with %s", uri, property_value);
2094 sipe_backend_buddy_set_server_alias(SIPE_CORE_PUBLIC, p_buddy, property_value);
2096 g_free(server_alias);
2098 /* for other properties */
2099 else {
2100 if (!is_empty(property_value)) {
2101 prop_str = sipe_backend_buddy_get_string(SIPE_CORE_PUBLIC, p_buddy, propkey);
2102 if (!prop_str || !sipe_strcase_equal(prop_str, property_value)) {
2103 sipe_backend_buddy_set_string(SIPE_CORE_PUBLIC, p_buddy, propkey, property_value);
2105 g_free(prop_str);
2109 entry = entry->next;
2111 g_slist_free(buddies);
2115 * Update user phone
2116 * Suitable for both 2005 and 2007 systems.
2118 * @param uri buddy SIP URI with 'sip:' prefix whose info we want to change.
2119 * @param phone_type
2120 * @param phone may be modified to strip white space
2121 * @param phone_display_string may be modified to strip white space
2123 static void
2124 sipe_update_user_phone(struct sipe_core_private *sipe_private,
2125 const gchar *uri,
2126 const gchar *phone_type,
2127 gchar *phone,
2128 gchar *phone_display_string)
2130 sipe_buddy_info_fields phone_node = SIPE_BUDDY_INFO_WORK_PHONE; /* work phone by default */
2131 sipe_buddy_info_fields phone_display_node = SIPE_BUDDY_INFO_WORK_PHONE_DISPLAY; /* work phone by default */
2133 if(!phone || strlen(phone) == 0) return;
2135 if ((sipe_strequal(phone_type, "mobile") || sipe_strequal(phone_type, "cell"))) {
2136 phone_node = SIPE_BUDDY_INFO_MOBILE_PHONE;
2137 phone_display_node = SIPE_BUDDY_INFO_MOBILE_PHONE_DISPLAY;
2138 } else if (sipe_strequal(phone_type, "home")) {
2139 phone_node = SIPE_BUDDY_INFO_HOME_PHONE;
2140 phone_display_node = SIPE_BUDDY_INFO_HOME_PHONE_DISPLAY;
2141 } else if (sipe_strequal(phone_type, "other")) {
2142 phone_node = SIPE_BUDDY_INFO_OTHER_PHONE;
2143 phone_display_node = SIPE_BUDDY_INFO_OTHER_PHONE_DISPLAY;
2144 } else if (sipe_strequal(phone_type, "custom1")) {
2145 phone_node = SIPE_BUDDY_INFO_CUSTOM1_PHONE;
2146 phone_display_node = SIPE_BUDDY_INFO_CUSTOM1_PHONE_DISPLAY;
2149 sipe_update_user_info(sipe_private, uri, phone_node, phone);
2150 if (phone_display_string) {
2151 sipe_update_user_info(sipe_private, uri, phone_display_node, phone_display_string);
2155 void
2156 sipe_core_update_calendar(struct sipe_core_public *sipe_public)
2158 SIPE_DEBUG_INFO_NOFORMAT("sipe_core_update_calendar: started.");
2160 /* Do in parallel.
2161 * If failed, the branch will be disabled for subsequent calls.
2162 * Can't rely that user turned the functionality on in account settings.
2164 sipe_ews_update_calendar(SIPE_CORE_PRIVATE);
2165 sipe_domino_update_calendar(SIPE_CORE_PRIVATE);
2167 /* schedule repeat */
2168 sipe_schedule_seconds(SIPE_CORE_PRIVATE,
2169 "<+update-calendar>",
2170 NULL,
2171 UPDATE_CALENDAR_INTERVAL,
2172 (sipe_schedule_action)sipe_core_update_calendar,
2173 NULL);
2175 SIPE_DEBUG_INFO_NOFORMAT("sipe_core_update_calendar: finished.");
2179 * This method motivates Purple's Host (e.g. Pidgin) to update its UI
2180 * by using standard Purple's means of signals and saved statuses.
2182 * Thus all UI elements get updated: Status Button with Note, docklet.
2183 * This is ablolutely important as both our status and note can come
2184 * inbound (roaming) or be updated programmatically (e.g. based on our
2185 * calendar data).
2187 static void
2188 sipe_set_purple_account_status_and_note(const PurpleAccount *account,
2189 const char *status_id,
2190 const char *message,
2191 time_t do_not_publish[])
2193 PurpleStatus *status = purple_account_get_active_status(account);
2194 gboolean changed = TRUE;
2196 if (g_str_equal(status_id, purple_status_get_id(status)) &&
2197 sipe_strequal(message, purple_status_get_attr_string(status, SIPE_STATUS_ATTR_ID_MESSAGE)))
2199 changed = FALSE;
2202 if (purple_savedstatus_is_idleaway()) {
2203 changed = FALSE;
2206 if (changed) {
2207 PurpleSavedStatus *saved_status;
2208 const PurpleStatusType *acct_status_type =
2209 purple_status_type_find_with_id(account->status_types, status_id);
2210 PurpleStatusPrimitive primitive = purple_status_type_get_primitive(acct_status_type);
2211 sipe_activity activity = sipe_get_activity_by_token(status_id);
2213 saved_status = purple_savedstatus_find_transient_by_type_and_message(primitive, message);
2214 if (saved_status) {
2215 purple_savedstatus_set_substatus(saved_status, account, acct_status_type, message);
2218 /* If this type+message is unique then create a new transient saved status
2219 * Ref: gtkstatusbox.c
2221 if (!saved_status) {
2222 GList *tmp;
2223 GList *active_accts = purple_accounts_get_all_active();
2225 saved_status = purple_savedstatus_new(NULL, primitive);
2226 purple_savedstatus_set_message(saved_status, message);
2228 for (tmp = active_accts; tmp != NULL; tmp = tmp->next) {
2229 purple_savedstatus_set_substatus(saved_status,
2230 (PurpleAccount *)tmp->data, acct_status_type, message);
2232 g_list_free(active_accts);
2235 do_not_publish[activity] = time(NULL);
2236 SIPE_DEBUG_INFO("sipe_set_purple_account_status_and_note: do_not_publish[%s]=%d [now]",
2237 status_id, (int)do_not_publish[activity]);
2239 /* Set the status for each account */
2240 purple_savedstatus_activate(saved_status);
2244 struct hash_table_delete_payload {
2245 GHashTable *hash_table;
2246 guint container;
2249 static void
2250 sipe_remove_category_container_publications_cb(const char *name,
2251 struct sipe_publication *publication,
2252 struct hash_table_delete_payload *payload)
2254 if (publication->container == payload->container) {
2255 g_hash_table_remove(payload->hash_table, name);
2258 static void
2259 sipe_remove_category_container_publications(GHashTable *our_publications,
2260 const char *category,
2261 guint container)
2263 struct hash_table_delete_payload payload;
2264 payload.hash_table = g_hash_table_lookup(our_publications, category);
2266 if (!payload.hash_table) return;
2268 payload.container = container;
2269 g_hash_table_foreach(payload.hash_table, (GHFunc)sipe_remove_category_container_publications_cb, &payload);
2272 static void
2273 send_publish_category_initial(struct sipe_core_private *sipe_private);
2276 * When we receive some self (BE) NOTIFY with a new subscriber
2277 * we sends a setSubscribers request to him [SIP-PRES] 4.8
2280 static void sipe_process_roaming_self(struct sipe_core_private *sipe_private,
2281 struct sipmsg *msg)
2283 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
2284 gchar *contact;
2285 gchar *to;
2286 sipe_xml *xml;
2287 const sipe_xml *node;
2288 const sipe_xml *node2;
2289 char *display_name = NULL;
2290 char *uri;
2291 GSList *category_names = NULL;
2292 int aggreg_avail = 0;
2293 gboolean do_update_status = FALSE;
2294 gboolean has_note_cleaned = FALSE;
2295 GHashTable *devices;
2297 SIPE_DEBUG_INFO_NOFORMAT("sipe_process_roaming_self");
2299 xml = sipe_xml_parse(msg->body, msg->bodylen);
2300 if (!xml) return;
2302 contact = get_contact(sipe_private);
2303 to = sip_uri_self(sipe_private);
2305 /* categories */
2306 /* set list of categories participating in this XML */
2307 for (node = sipe_xml_child(xml, "categories/category"); node; node = sipe_xml_twin(node)) {
2308 const gchar *name = sipe_xml_attribute(node, "name");
2309 category_names = slist_insert_unique_sorted(category_names, (gchar *)name, (GCompareFunc)strcmp);
2311 SIPE_DEBUG_INFO("sipe_process_roaming_self: category_names length=%d",
2312 category_names ? (int) g_slist_length(category_names) : -1);
2313 /* drop category information */
2314 if (category_names) {
2315 GSList *entry = category_names;
2316 while (entry) {
2317 GHashTable *cat_publications;
2318 const gchar *category = entry->data;
2319 entry = entry->next;
2320 SIPE_DEBUG_INFO("sipe_process_roaming_self: dropping category: %s", category);
2321 cat_publications = g_hash_table_lookup(sip->our_publications, category);
2322 if (cat_publications) {
2323 g_hash_table_remove(sip->our_publications, category);
2324 SIPE_DEBUG_INFO("sipe_process_roaming_self: dropped category: %s", category);
2328 g_slist_free(category_names);
2330 /* filling our categories reflected in roaming data */
2331 devices = g_hash_table_new_full(g_str_hash, g_str_equal,
2332 g_free, NULL);
2333 for (node = sipe_xml_child(xml, "categories/category"); node; node = sipe_xml_twin(node)) {
2334 const char *tmp;
2335 const gchar *name = sipe_xml_attribute(node, "name");
2336 guint container = sipe_xml_int_attribute(node, "container", -1);
2337 guint instance = sipe_xml_int_attribute(node, "instance", -1);
2338 guint version = sipe_xml_int_attribute(node, "version", 0);
2339 time_t publish_time = (tmp = sipe_xml_attribute(node, "publishTime")) ?
2340 sipe_utils_str_to_time(tmp) : 0;
2341 gchar *key;
2342 GHashTable *cat_publications = g_hash_table_lookup(sip->our_publications, name);
2344 /* Ex. clear note: <category name="note"/> */
2345 if (container == (guint)-1) {
2346 g_free(sip->note);
2347 sip->note = NULL;
2348 do_update_status = TRUE;
2349 continue;
2352 /* Ex. clear note: <category name="note" container="200"/> */
2353 if (instance == (guint)-1) {
2354 if (container == 200) {
2355 g_free(sip->note);
2356 sip->note = NULL;
2357 do_update_status = TRUE;
2359 SIPE_DEBUG_INFO("sipe_process_roaming_self: removing publications for: %s/%u", name, container);
2360 sipe_remove_category_container_publications(
2361 sip->our_publications, name, container);
2362 continue;
2365 /* key is <category><instance><container> */
2366 key = g_strdup_printf("<%s><%u><%u>", name, instance, container);
2367 SIPE_DEBUG_INFO("sipe_process_roaming_self: key=%s version=%d", key, version);
2369 /* capture all userState publication for later clean up if required */
2370 if (sipe_strequal(name, "state") && (container == 2 || container == 3)) {
2371 const sipe_xml *xn_state = sipe_xml_child(node, "state");
2373 if (xn_state && sipe_strequal(sipe_xml_attribute(xn_state, "type"), "userState")) {
2374 struct sipe_publication *publication = g_new0(struct sipe_publication, 1);
2375 publication->category = g_strdup(name);
2376 publication->instance = instance;
2377 publication->container = container;
2378 publication->version = version;
2380 if (!sip->user_state_publications) {
2381 sip->user_state_publications = g_hash_table_new_full(
2382 g_str_hash, g_str_equal,
2383 g_free, (GDestroyNotify)free_publication);
2385 g_hash_table_insert(sip->user_state_publications, g_strdup(key), publication);
2386 SIPE_DEBUG_INFO("sipe_process_roaming_self: added to user_state_publications key=%s version=%d",
2387 key, version);
2391 /* count each client instance only once */
2392 if (sipe_strequal(name, "device"))
2393 g_hash_table_replace(devices, g_strdup_printf("%u", instance), NULL);
2395 if (sipe_is_our_publication(sipe_private, key)) {
2396 struct sipe_publication *publication = g_new0(struct sipe_publication, 1);
2398 publication->category = g_strdup(name);
2399 publication->instance = instance;
2400 publication->container = container;
2401 publication->version = version;
2403 /* filling publication->availability */
2404 if (sipe_strequal(name, "state")) {
2405 const sipe_xml *xn_state = sipe_xml_child(node, "state");
2406 const sipe_xml *xn_avail = sipe_xml_child(xn_state, "availability");
2408 if (xn_avail) {
2409 gchar *avail_str = sipe_xml_data(xn_avail);
2410 if (avail_str) {
2411 publication->availability = atoi(avail_str);
2413 g_free(avail_str);
2415 /* for calendarState */
2416 if (xn_state && sipe_strequal(sipe_xml_attribute(xn_state, "type"), "calendarState")) {
2417 const sipe_xml *xn_activity = sipe_xml_child(xn_state, "activity");
2418 struct sipe_cal_event *event = g_new0(struct sipe_cal_event, 1);
2420 event->start_time = sipe_utils_str_to_time(sipe_xml_attribute(xn_state, "startTime"));
2421 if (xn_activity) {
2422 if (sipe_strequal(sipe_xml_attribute(xn_activity, "token"),
2423 sipe_activity_map[SIPE_ACTIVITY_IN_MEETING].token))
2425 event->is_meeting = TRUE;
2428 event->subject = sipe_xml_data(sipe_xml_child(xn_state, "meetingSubject"));
2429 event->location = sipe_xml_data(sipe_xml_child(xn_state, "meetingLocation"));
2431 publication->cal_event_hash = sipe_cal_event_hash(event);
2432 SIPE_DEBUG_INFO("sipe_process_roaming_self: hash=%s",
2433 publication->cal_event_hash);
2434 sipe_cal_event_free(event);
2437 /* filling publication->note */
2438 if (sipe_strequal(name, "note")) {
2439 const sipe_xml *xn_body = sipe_xml_child(node, "note/body");
2441 if (!has_note_cleaned) {
2442 has_note_cleaned = TRUE;
2444 g_free(sip->note);
2445 sip->note = NULL;
2446 sip->note_since = publish_time;
2448 do_update_status = TRUE;
2451 g_free(publication->note);
2452 publication->note = NULL;
2453 if (xn_body) {
2454 char *tmp;
2456 publication->note = g_markup_escape_text((tmp = sipe_xml_data(xn_body)), -1);
2457 g_free(tmp);
2458 if (publish_time >= sip->note_since) {
2459 g_free(sip->note);
2460 sip->note = g_strdup(publication->note);
2461 sip->note_since = publish_time;
2462 sip->is_oof_note = sipe_strequal(sipe_xml_attribute(xn_body, "type"), "OOF");
2464 do_update_status = TRUE;
2469 /* filling publication->fb_start_str, free_busy_base64, working_hours_xml_str */
2470 if (sipe_strequal(name, "calendarData") && (publication->container == 300)) {
2471 const sipe_xml *xn_free_busy = sipe_xml_child(node, "calendarData/freeBusy");
2472 const sipe_xml *xn_working_hours = sipe_xml_child(node, "calendarData/WorkingHours");
2473 if (xn_free_busy) {
2474 publication->fb_start_str = g_strdup(sipe_xml_attribute(xn_free_busy, "startTime"));
2475 publication->free_busy_base64 = sipe_xml_data(xn_free_busy);
2477 if (xn_working_hours) {
2478 publication->working_hours_xml_str = sipe_xml_stringify(xn_working_hours);
2482 if (!cat_publications) {
2483 cat_publications = g_hash_table_new_full(
2484 g_str_hash, g_str_equal,
2485 g_free, (GDestroyNotify)free_publication);
2486 g_hash_table_insert(sip->our_publications, g_strdup(name), cat_publications);
2487 SIPE_DEBUG_INFO("sipe_process_roaming_self: added GHashTable cat=%s", name);
2489 g_hash_table_insert(cat_publications, g_strdup(key), publication);
2490 SIPE_DEBUG_INFO("sipe_process_roaming_self: added key=%s version=%d", key, version);
2492 g_free(key);
2494 /* aggregateState (not an our publication) from 2-nd container */
2495 if (sipe_strequal(name, "state") && container == 2) {
2496 const sipe_xml *xn_state = sipe_xml_child(node, "state");
2498 if (xn_state && sipe_strequal(sipe_xml_attribute(xn_state, "type"), "aggregateState")) {
2499 const sipe_xml *xn_avail = sipe_xml_child(xn_state, "availability");
2501 if (xn_avail) {
2502 gchar *avail_str = sipe_xml_data(xn_avail);
2503 if (avail_str) {
2504 aggreg_avail = atoi(avail_str);
2506 g_free(avail_str);
2509 do_update_status = TRUE;
2513 /* userProperties published by server from AD */
2514 if (!sip->csta && sipe_strequal(name, "userProperties")) {
2515 const sipe_xml *line;
2516 /* line, for Remote Call Control (RCC) */
2517 for (line = sipe_xml_child(node, "userProperties/lines/line"); line; line = sipe_xml_twin(line)) {
2518 const gchar *line_server = sipe_xml_attribute(line, "lineServer");
2519 const gchar *line_type = sipe_xml_attribute(line, "lineType");
2520 gchar *line_uri;
2522 if (!line_server || !(sipe_strequal(line_type, "Rcc") || sipe_strequal(line_type, "Dual"))) continue;
2524 line_uri = sipe_xml_data(line);
2525 if (line_uri) {
2526 SIPE_DEBUG_INFO("sipe_process_roaming_self: line_uri=%s server=%s", line_uri, line_server);
2527 sip_csta_open(sipe_private, line_uri, line_server);
2529 g_free(line_uri);
2531 break;
2535 SIPE_DEBUG_INFO("sipe_process_roaming_self: sip->our_publications size=%d",
2536 sip->our_publications ? (int) g_hash_table_size(sip->our_publications) : -1);
2538 /* active clients for user account */
2539 if (g_hash_table_size(devices) > 1) {
2540 SIPE_CORE_PRIVATE_FLAG_SET(MPOP);
2541 SIPE_DEBUG_INFO("sipe_process_roaming_self: multiple clients detected (%d)",
2542 g_hash_table_size(devices));
2543 } else {
2544 SIPE_CORE_PRIVATE_FLAG_UNSET(MPOP);
2545 SIPE_DEBUG_INFO_NOFORMAT("sipe_process_roaming_self: single client detected");
2547 g_hash_table_destroy(devices);
2549 /* containers */
2550 for (node = sipe_xml_child(xml, "containers/container"); node; node = sipe_xml_twin(node)) {
2551 guint id = sipe_xml_int_attribute(node, "id", 0);
2552 struct sipe_container *container = sipe_find_container(sipe_private, id);
2554 if (container) {
2555 sip->containers = g_slist_remove(sip->containers, container);
2556 SIPE_DEBUG_INFO("sipe_process_roaming_self: removed existing container id=%d v%d", container->id, container->version);
2557 free_container(container);
2559 container = g_new0(struct sipe_container, 1);
2560 container->id = id;
2561 container->version = sipe_xml_int_attribute(node, "version", 0);
2562 sip->containers = g_slist_append(sip->containers, container);
2563 SIPE_DEBUG_INFO("sipe_process_roaming_self: added container id=%d v%d", container->id, container->version);
2565 for (node2 = sipe_xml_child(node, "member"); node2; node2 = sipe_xml_twin(node2)) {
2566 struct sipe_container_member *member = g_new0(struct sipe_container_member, 1);
2567 member->type = g_strdup(sipe_xml_attribute(node2, "type"));
2568 member->value = g_strdup(sipe_xml_attribute(node2, "value"));
2569 container->members = g_slist_append(container->members, member);
2570 SIPE_DEBUG_INFO("sipe_process_roaming_self: added container member type=%s value=%s",
2571 member->type, member->value ? member->value : "");
2575 SIPE_DEBUG_INFO("sipe_process_roaming_self: sip->access_level_set=%s", sip->access_level_set ? "TRUE" : "FALSE");
2576 if (!sip->access_level_set && sipe_xml_child(xml, "containers")) {
2577 char *container_xmls = NULL;
2578 int sameEnterpriseAL = sipe_find_access_level(sipe_private, "sameEnterprise", NULL, NULL);
2579 int federatedAL = sipe_find_access_level(sipe_private, "federated", NULL, NULL);
2581 SIPE_DEBUG_INFO("sipe_process_roaming_self: sameEnterpriseAL=%d", sameEnterpriseAL);
2582 SIPE_DEBUG_INFO("sipe_process_roaming_self: federatedAL=%d", federatedAL);
2583 /* initial set-up to let counterparties see your status */
2584 if (sameEnterpriseAL < 0) {
2585 struct sipe_container *container = sipe_find_container(sipe_private, 200);
2586 guint version = container ? container->version : 0;
2587 sipe_send_container_members_prepare(200, version, "add", "sameEnterprise", NULL, &container_xmls);
2589 if (federatedAL < 0) {
2590 struct sipe_container *container = sipe_find_container(sipe_private, 100);
2591 guint version = container ? container->version : 0;
2592 sipe_send_container_members_prepare(100, version, "add", "federated", NULL, &container_xmls);
2594 sip->access_level_set = TRUE;
2596 if (container_xmls) {
2597 sipe_send_set_container_members(sipe_private, container_xmls);
2599 g_free(container_xmls);
2602 /* Refresh contacts' blocked status */
2603 sipe_refresh_blocked_status(sipe_private);
2605 /* subscribers */
2606 for (node = sipe_xml_child(xml, "subscribers/subscriber"); node; node = sipe_xml_twin(node)) {
2607 const char *user;
2608 const char *acknowledged;
2609 gchar *hdr;
2610 gchar *body;
2612 user = sipe_xml_attribute(node, "user"); /* without 'sip:' prefix */
2613 if (!user) continue;
2614 SIPE_DEBUG_INFO("sipe_process_roaming_self: user %s", user);
2615 display_name = g_strdup(sipe_xml_attribute(node, "displayName"));
2616 uri = sip_uri_from_name(user);
2618 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_DISPLAY_NAME, display_name);
2620 acknowledged= sipe_xml_attribute(node, "acknowledged");
2621 if(sipe_strcase_equal(acknowledged,"false")){
2622 SIPE_DEBUG_INFO("sipe_process_roaming_self: user added you %s", user);
2623 if (!sipe_backend_buddy_find(SIPE_CORE_PUBLIC, uri, NULL)) {
2624 sipe_backend_buddy_request_add(SIPE_CORE_PUBLIC, uri, display_name);
2627 hdr = g_strdup_printf(
2628 "Contact: %s\r\n"
2629 "Content-Type: application/msrtc-presence-setsubscriber+xml\r\n", contact);
2631 body = g_strdup_printf(
2632 "<setSubscribers xmlns=\"http://schemas.microsoft.com/2006/09/sip/presence-subscribers\">"
2633 "<subscriber user=\"%s\" acknowledged=\"true\"/>"
2634 "</setSubscribers>", user);
2636 sip_transport_service(sipe_private,
2638 hdr,
2639 body,
2640 NULL);
2641 g_free(body);
2642 g_free(hdr);
2644 g_free(display_name);
2645 g_free(uri);
2648 g_free(contact);
2649 sipe_xml_free(xml);
2651 /* Publish initial state if not yet.
2652 * Assuming this happens on initial responce to subscription to roaming-self
2653 * so we've already updated our roaming data in full.
2654 * Only for 2007+
2656 if (!sip->initial_state_published) {
2657 send_publish_category_initial(sipe_private);
2658 sipe_groupchat_init(sipe_private);
2659 sip->initial_state_published = TRUE;
2660 /* dalayed run */
2661 sipe_schedule_seconds(sipe_private,
2662 "<+update-calendar>",
2663 NULL,
2664 UPDATE_CALENDAR_DELAY,
2665 (sipe_schedule_action)sipe_core_update_calendar,
2666 NULL);
2667 do_update_status = FALSE;
2668 } else if (aggreg_avail) {
2670 g_free(sip->status);
2671 if (aggreg_avail && aggreg_avail < 18000) { /* not offline */
2672 sip->status = g_strdup(sipe_get_status_by_availability(aggreg_avail, NULL));
2673 } else {
2674 sip->status = g_strdup(SIPE_STATUS_ID_INVISIBLE); /* not not let offline status switch us off */
2678 if (do_update_status) {
2679 SIPE_DEBUG_INFO("sipe_process_roaming_self: switch to '%s' for the account", sip->status);
2680 sipe_set_purple_account_status_and_note(sip->account, sip->status, sip->note, sip->do_not_publish);
2683 g_free(to);
2686 /* IM Session (INVITE and MESSAGE methods) */
2688 static gboolean
2689 process_options_response(SIPE_UNUSED_PARAMETER struct sipe_core_private *sipe_private,
2690 struct sipmsg *msg,
2691 SIPE_UNUSED_PARAMETER struct transaction *trans)
2693 gboolean ret = TRUE;
2695 if (msg->response != 200) {
2696 SIPE_DEBUG_INFO("process_options_response: OPTIONS response is %d", msg->response);
2697 return FALSE;
2700 SIPE_DEBUG_INFO("process_options_response: body:\n%s", msg->body ? msg->body : "");
2702 return ret;
2706 * Asks UA/proxy about its capabilities.
2708 static void sipe_options_request(struct sipe_core_private *sipe_private,
2709 const char *who)
2711 gchar *to = sip_uri(who);
2712 gchar *contact = get_contact(sipe_private);
2713 gchar *request = g_strdup_printf(
2714 "Accept: application/sdp\r\n"
2715 "Contact: %s\r\n", contact);
2716 g_free(contact);
2718 sip_transport_request(sipe_private,
2719 "OPTIONS",
2722 request,
2723 NULL,
2724 NULL,
2725 process_options_response);
2727 g_free(to);
2728 g_free(request);
2731 void
2732 sipe_convo_closed(PurpleConnection * gc, const char *who)
2734 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
2736 SIPE_DEBUG_INFO("conversation with %s closed", who);
2737 sipe_session_close(sipe_private,
2738 sipe_session_find_im(sipe_private, who));
2742 * Returns 2005-style activity and Availability.
2744 * @param status Sipe statis id.
2746 static void
2747 sipe_get_act_avail_by_status_2005(const char *status,
2748 int *activity,
2749 int *availability)
2751 int avail = 300; /* online */
2752 int act = 400; /* Available */
2754 if (sipe_strequal(status, SIPE_STATUS_ID_AWAY)) {
2755 act = 100;
2756 //} else if (sipe_strequal(status, SIPE_STATUS_ID_LUNCH)) {
2757 // act = 150;
2758 } else if (sipe_strequal(status, SIPE_STATUS_ID_BRB)) {
2759 act = 300;
2760 } else if (sipe_strequal(status, SIPE_STATUS_ID_AVAILABLE)) {
2761 act = 400;
2762 //} else if (sipe_strequal(status, SIPE_STATUS_ID_ON_PHONE)) {
2763 // act = 500;
2764 } else if (sipe_strequal(status, SIPE_STATUS_ID_BUSY) ||
2765 sipe_strequal(status, SIPE_STATUS_ID_DND)) {
2766 act = 600;
2767 } else if (sipe_strequal(status, SIPE_STATUS_ID_INVISIBLE) ||
2768 sipe_strequal(status, SIPE_STATUS_ID_OFFLINE)) {
2769 avail = 0; /* offline */
2770 act = 100;
2771 } else {
2772 act = 400; /* Available */
2775 if (activity) *activity = act;
2776 if (availability) *availability = avail;
2780 * [MS-SIP] 2.2.1
2782 * @param activity 2005 aggregated activity. Ex.: 600
2783 * @param availablity 2005 aggregated availablity. Ex.: 300
2785 static const char *
2786 sipe_get_status_by_act_avail_2005(const int activity,
2787 const int availablity,
2788 char **activity_desc)
2790 const char *status_id = NULL;
2791 const char *act = NULL;
2793 if (activity < 150) {
2794 status_id = SIPE_STATUS_ID_AWAY;
2795 } else if (activity < 200) {
2796 //status_id = SIPE_STATUS_ID_LUNCH;
2797 status_id = SIPE_STATUS_ID_AWAY;
2798 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_LUNCH);
2799 } else if (activity < 300) {
2800 //status_id = SIPE_STATUS_ID_IDLE;
2801 status_id = SIPE_STATUS_ID_AWAY;
2802 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_INACTIVE);
2803 } else if (activity < 400) {
2804 status_id = SIPE_STATUS_ID_BRB;
2805 } else if (activity < 500) {
2806 status_id = SIPE_STATUS_ID_AVAILABLE;
2807 } else if (activity < 600) {
2808 //status_id = SIPE_STATUS_ID_ON_PHONE;
2809 status_id = SIPE_STATUS_ID_BUSY;
2810 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_ON_PHONE);
2811 } else if (activity < 700) {
2812 status_id = SIPE_STATUS_ID_BUSY;
2813 } else if (activity < 800) {
2814 status_id = SIPE_STATUS_ID_AWAY;
2815 } else {
2816 status_id = SIPE_STATUS_ID_AVAILABLE;
2819 if (availablity < 100)
2820 status_id = SIPE_STATUS_ID_OFFLINE;
2822 if (activity_desc && act) {
2823 g_free(*activity_desc);
2824 *activity_desc = g_strdup(act);
2827 return status_id;
2831 * [MS-PRES] Table 3: Conversion of legacyInterop elements and attributes to MSRTC elements and attributes.
2833 static const char*
2834 sipe_get_status_by_availability(int avail,
2835 char** activity_desc)
2837 const char *status;
2838 const char *act = NULL;
2840 if (avail < 3000) {
2841 status = SIPE_STATUS_ID_OFFLINE;
2842 } else if (avail < 4500) {
2843 status = SIPE_STATUS_ID_AVAILABLE;
2844 } else if (avail < 6000) {
2845 //status = SIPE_STATUS_ID_IDLE;
2846 status = SIPE_STATUS_ID_AVAILABLE;
2847 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_INACTIVE);
2848 } else if (avail < 7500) {
2849 status = SIPE_STATUS_ID_BUSY;
2850 } else if (avail < 9000) {
2851 //status = SIPE_STATUS_ID_BUSYIDLE;
2852 status = SIPE_STATUS_ID_BUSY;
2853 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_BUSYIDLE);
2854 } else if (avail < 12000) {
2855 status = SIPE_STATUS_ID_DND;
2856 } else if (avail < 15000) {
2857 status = SIPE_STATUS_ID_BRB;
2858 } else if (avail < 18000) {
2859 status = SIPE_STATUS_ID_AWAY;
2860 } else {
2861 status = SIPE_STATUS_ID_OFFLINE;
2864 if (activity_desc && act) {
2865 g_free(*activity_desc);
2866 *activity_desc = g_strdup(act);
2869 return status;
2873 * Returns 2007-style availability value
2875 * @param sipe_status_id (in)
2876 * @param activity_token (out) Must be g_free()'d after use if consumed.
2878 static int
2879 sipe_get_availability_by_status(const char* sipe_status_id, char** activity_token)
2881 int availability;
2882 sipe_activity activity = SIPE_ACTIVITY_UNSET;
2884 if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_AWAY)) {
2885 availability = 15500;
2886 if (!activity_token || !(*activity_token)) {
2887 activity = SIPE_ACTIVITY_AWAY;
2889 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_BRB)) {
2890 availability = 12500;
2891 activity = SIPE_ACTIVITY_BRB;
2892 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_DND)) {
2893 availability = 9500;
2894 activity = SIPE_ACTIVITY_DND;
2895 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_BUSY)) {
2896 availability = 6500;
2897 if (!activity_token || !(*activity_token)) {
2898 activity = SIPE_ACTIVITY_BUSY;
2900 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_AVAILABLE)) {
2901 availability = 3500;
2902 activity = SIPE_ACTIVITY_ONLINE;
2903 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_UNKNOWN)) {
2904 availability = 0;
2905 } else {
2906 // Offline or invisible
2907 availability = 18500;
2908 activity = SIPE_ACTIVITY_OFFLINE;
2911 if (activity_token) {
2912 *activity_token = g_strdup(sipe_activity_map[activity].token);
2914 return availability;
2917 static void process_incoming_notify_rlmi(struct sipe_core_private *sipe_private,
2918 const gchar *data, unsigned len)
2920 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
2921 const char *uri;
2922 sipe_xml *xn_categories;
2923 const sipe_xml *xn_category;
2924 const char *status = NULL;
2925 gboolean do_update_status = FALSE;
2926 gboolean has_note_cleaned = FALSE;
2927 gboolean has_free_busy_cleaned = FALSE;
2929 xn_categories = sipe_xml_parse(data, len);
2930 uri = sipe_xml_attribute(xn_categories, "uri"); /* with 'sip:' prefix */
2932 for (xn_category = sipe_xml_child(xn_categories, "category");
2933 xn_category ;
2934 xn_category = sipe_xml_twin(xn_category) )
2936 const sipe_xml *xn_node;
2937 const char *tmp;
2938 const char *attrVar = sipe_xml_attribute(xn_category, "name");
2939 time_t publish_time = (tmp = sipe_xml_attribute(xn_category, "publishTime")) ?
2940 sipe_utils_str_to_time(tmp) : 0;
2942 /* contactCard */
2943 if (sipe_strequal(attrVar, "contactCard"))
2945 const sipe_xml *card = sipe_xml_child(xn_category, "contactCard");
2947 if (card) {
2948 const sipe_xml *node;
2949 /* identity - Display Name and email */
2950 node = sipe_xml_child(card, "identity");
2951 if (node) {
2952 char* display_name = sipe_xml_data(
2953 sipe_xml_child(node, "name/displayName"));
2954 char* email = sipe_xml_data(
2955 sipe_xml_child(node, "email"));
2957 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_DISPLAY_NAME, display_name);
2958 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_EMAIL, email);
2960 g_free(display_name);
2961 g_free(email);
2963 /* company */
2964 node = sipe_xml_child(card, "company");
2965 if (node) {
2966 char* company = sipe_xml_data(node);
2967 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_COMPANY, company);
2968 g_free(company);
2970 /* department */
2971 node = sipe_xml_child(card, "department");
2972 if (node) {
2973 char* department = sipe_xml_data(node);
2974 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_DEPARTMENT, department);
2975 g_free(department);
2977 /* title */
2978 node = sipe_xml_child(card, "title");
2979 if (node) {
2980 char* title = sipe_xml_data(node);
2981 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_JOB_TITLE, title);
2982 g_free(title);
2984 /* office */
2985 node = sipe_xml_child(card, "office");
2986 if (node) {
2987 char* office = sipe_xml_data(node);
2988 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_OFFICE, office);
2989 g_free(office);
2991 /* site (url) */
2992 node = sipe_xml_child(card, "url");
2993 if (node) {
2994 char* site = sipe_xml_data(node);
2995 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_SITE, site);
2996 g_free(site);
2998 /* phone */
2999 for (node = sipe_xml_child(card, "phone");
3000 node;
3001 node = sipe_xml_twin(node))
3003 const char *phone_type = sipe_xml_attribute(node, "type");
3004 char* phone = sipe_xml_data(sipe_xml_child(node, "uri"));
3005 char* phone_display_string = sipe_xml_data(sipe_xml_child(node, "displayString"));
3007 sipe_update_user_phone(sipe_private, uri, phone_type, phone, phone_display_string);
3009 g_free(phone);
3010 g_free(phone_display_string);
3012 /* address */
3013 for (node = sipe_xml_child(card, "address");
3014 node;
3015 node = sipe_xml_twin(node))
3017 if (sipe_strequal(sipe_xml_attribute(node, "type"), "work")) {
3018 char* street = sipe_xml_data(sipe_xml_child(node, "street"));
3019 char* city = sipe_xml_data(sipe_xml_child(node, "city"));
3020 char* state = sipe_xml_data(sipe_xml_child(node, "state"));
3021 char* zipcode = sipe_xml_data(sipe_xml_child(node, "zipcode"));
3022 char* country_code = sipe_xml_data(sipe_xml_child(node, "countryCode"));
3024 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_STREET, street);
3025 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_CITY, city);
3026 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_STATE, state);
3027 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_ZIPCODE, zipcode);
3028 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_COUNTRY, country_code);
3030 g_free(street);
3031 g_free(city);
3032 g_free(state);
3033 g_free(zipcode);
3034 g_free(country_code);
3036 break;
3041 /* note */
3042 else if (sipe_strequal(attrVar, "note"))
3044 if (uri) {
3045 struct sipe_buddy *sbuddy = g_hash_table_lookup(sipe_private->buddies, uri);
3047 if (!has_note_cleaned) {
3048 has_note_cleaned = TRUE;
3050 g_free(sbuddy->note);
3051 sbuddy->note = NULL;
3052 sbuddy->is_oof_note = FALSE;
3053 sbuddy->note_since = publish_time;
3055 do_update_status = TRUE;
3057 if (sbuddy && (publish_time >= sbuddy->note_since)) {
3058 /* clean up in case no 'note' element is supplied
3059 * which indicate note removal in client
3061 g_free(sbuddy->note);
3062 sbuddy->note = NULL;
3063 sbuddy->is_oof_note = FALSE;
3064 sbuddy->note_since = publish_time;
3066 xn_node = sipe_xml_child(xn_category, "note/body");
3067 if (xn_node) {
3068 char *tmp;
3069 sbuddy->note = g_markup_escape_text((tmp = sipe_xml_data(xn_node)), -1);
3070 g_free(tmp);
3071 sbuddy->is_oof_note = sipe_strequal(sipe_xml_attribute(xn_node, "type"), "OOF");
3072 sbuddy->note_since = publish_time;
3074 SIPE_DEBUG_INFO("process_incoming_notify_rlmi: uri(%s), note(%s)",
3075 uri, sbuddy->note ? sbuddy->note : "");
3077 /* to trigger UI refresh in case no status info is supplied in this update */
3078 do_update_status = TRUE;
3082 /* state */
3083 else if(sipe_strequal(attrVar, "state"))
3085 char *tmp;
3086 int availability;
3087 const sipe_xml *xn_availability;
3088 const sipe_xml *xn_activity;
3089 const sipe_xml *xn_meeting_subject;
3090 const sipe_xml *xn_meeting_location;
3091 struct sipe_buddy *sbuddy = uri ? g_hash_table_lookup(sipe_private->buddies, uri) : NULL;
3093 xn_node = sipe_xml_child(xn_category, "state");
3094 if (!xn_node) continue;
3095 xn_availability = sipe_xml_child(xn_node, "availability");
3096 if (!xn_availability) continue;
3097 xn_activity = sipe_xml_child(xn_node, "activity");
3098 xn_meeting_subject = sipe_xml_child(xn_node, "meetingSubject");
3099 xn_meeting_location = sipe_xml_child(xn_node, "meetingLocation");
3101 tmp = sipe_xml_data(xn_availability);
3102 availability = atoi(tmp);
3103 g_free(tmp);
3105 /* activity, meeting_subject, meeting_location */
3106 if (sbuddy) {
3107 char *tmp = NULL;
3109 /* activity */
3110 g_free(sbuddy->activity);
3111 sbuddy->activity = NULL;
3112 if (xn_activity) {
3113 const char *token = sipe_xml_attribute(xn_activity, "token");
3114 const sipe_xml *xn_custom = sipe_xml_child(xn_activity, "custom");
3116 /* from token */
3117 if (!is_empty(token)) {
3118 sbuddy->activity = g_strdup(sipe_get_activity_desc_by_token(token));
3120 /* from custom element */
3121 if (xn_custom) {
3122 char *custom = sipe_xml_data(xn_custom);
3124 if (!is_empty(custom)) {
3125 sbuddy->activity = custom;
3126 custom = NULL;
3128 g_free(custom);
3131 /* meeting_subject */
3132 g_free(sbuddy->meeting_subject);
3133 sbuddy->meeting_subject = NULL;
3134 if (xn_meeting_subject) {
3135 char *meeting_subject = sipe_xml_data(xn_meeting_subject);
3137 if (!is_empty(meeting_subject)) {
3138 sbuddy->meeting_subject = meeting_subject;
3139 meeting_subject = NULL;
3141 g_free(meeting_subject);
3143 /* meeting_location */
3144 g_free(sbuddy->meeting_location);
3145 sbuddy->meeting_location = NULL;
3146 if (xn_meeting_location) {
3147 char *meeting_location = sipe_xml_data(xn_meeting_location);
3149 if (!is_empty(meeting_location)) {
3150 sbuddy->meeting_location = meeting_location;
3151 meeting_location = NULL;
3153 g_free(meeting_location);
3156 status = sipe_get_status_by_availability(availability, &tmp);
3157 if (sbuddy->activity && tmp) {
3158 char *tmp2 = sbuddy->activity;
3160 sbuddy->activity = g_strdup_printf("%s, %s", sbuddy->activity, tmp);
3161 g_free(tmp);
3162 g_free(tmp2);
3163 } else if (tmp) {
3164 sbuddy->activity = tmp;
3168 do_update_status = TRUE;
3170 /* calendarData */
3171 else if(sipe_strequal(attrVar, "calendarData"))
3173 struct sipe_buddy *sbuddy = uri ? g_hash_table_lookup(sipe_private->buddies, uri) : NULL;
3174 const sipe_xml *xn_free_busy = sipe_xml_child(xn_category, "calendarData/freeBusy");
3175 const sipe_xml *xn_working_hours = sipe_xml_child(xn_category, "calendarData/WorkingHours");
3177 if (sbuddy && xn_free_busy) {
3178 if (!has_free_busy_cleaned) {
3179 has_free_busy_cleaned = TRUE;
3181 g_free(sbuddy->cal_start_time);
3182 sbuddy->cal_start_time = NULL;
3184 g_free(sbuddy->cal_free_busy_base64);
3185 sbuddy->cal_free_busy_base64 = NULL;
3187 g_free(sbuddy->cal_free_busy);
3188 sbuddy->cal_free_busy = NULL;
3190 sbuddy->cal_free_busy_published = publish_time;
3193 if (publish_time >= sbuddy->cal_free_busy_published) {
3194 g_free(sbuddy->cal_start_time);
3195 sbuddy->cal_start_time = g_strdup(sipe_xml_attribute(xn_free_busy, "startTime"));
3197 sbuddy->cal_granularity = sipe_strcase_equal(sipe_xml_attribute(xn_free_busy, "granularity"), "PT15M") ?
3198 15 : 0;
3200 g_free(sbuddy->cal_free_busy_base64);
3201 sbuddy->cal_free_busy_base64 = sipe_xml_data(xn_free_busy);
3203 g_free(sbuddy->cal_free_busy);
3204 sbuddy->cal_free_busy = NULL;
3206 sbuddy->cal_free_busy_published = publish_time;
3208 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);
3212 if (sbuddy && xn_working_hours) {
3213 sipe_cal_parse_working_hours(xn_working_hours, sbuddy);
3218 if (do_update_status) {
3219 if (!status) { /* no status category in this update, using contact's current status */
3220 PurpleBuddy *pbuddy = purple_find_buddy((PurpleAccount *)sip->account, uri);
3221 const PurplePresence *presence = purple_buddy_get_presence(pbuddy);
3222 const PurpleStatus *pstatus = purple_presence_get_active_status(presence);
3223 status = purple_status_get_id(pstatus);
3226 SIPE_DEBUG_INFO("process_incoming_notify_rlmi: %s", status);
3227 sipe_core_buddy_got_status(SIPE_CORE_PUBLIC, uri, status);
3230 sipe_xml_free(xn_categories);
3233 static void sipe_subscribe_poolfqdn_resource_uri(const char *host,
3234 GSList *server,
3235 struct sipe_core_private *sipe_private)
3237 struct presence_batched_routed *payload = g_malloc(sizeof(struct presence_batched_routed));
3238 SIPE_DEBUG_INFO("process_incoming_notify_rlmi_resub: pool(%s)", host);
3239 payload->host = g_strdup(host);
3240 payload->buddies = server;
3241 sipe_subscribe_presence_batched_routed(sipe_private,
3242 payload);
3243 sipe_subscribe_presence_batched_routed_free(payload);
3246 static void process_incoming_notify_rlmi_resub(struct sipe_core_private *sipe_private,
3247 const gchar *data, unsigned len)
3249 sipe_xml *xn_list;
3250 const sipe_xml *xn_resource;
3251 GHashTable *servers = g_hash_table_new_full(g_str_hash, g_str_equal,
3252 g_free, NULL);
3253 GSList *server;
3254 gchar *host;
3256 xn_list = sipe_xml_parse(data, len);
3258 for (xn_resource = sipe_xml_child(xn_list, "resource");
3259 xn_resource;
3260 xn_resource = sipe_xml_twin(xn_resource) )
3262 const char *uri, *state;
3263 const sipe_xml *xn_instance;
3265 xn_instance = sipe_xml_child(xn_resource, "instance");
3266 if (!xn_instance) continue;
3268 uri = sipe_xml_attribute(xn_resource, "uri");
3269 state = sipe_xml_attribute(xn_instance, "state");
3270 SIPE_DEBUG_INFO("process_incoming_notify_rlmi_resub: uri(%s),state(%s)", uri, state);
3272 if (strstr(state, "resubscribe")) {
3273 const char *poolFqdn = sipe_xml_attribute(xn_instance, "poolFqdn");
3275 if (poolFqdn) { //[MS-PRES] Section 3.4.5.1.3 Processing Details
3276 gchar *user = g_strdup(uri);
3277 host = g_strdup(poolFqdn);
3278 server = g_hash_table_lookup(servers, host);
3279 server = g_slist_append(server, user);
3280 g_hash_table_insert(servers, host, server);
3281 } else {
3282 sipe_subscribe_presence_single(sipe_private,
3283 (void *) uri);
3288 /* Send out any deferred poolFqdn subscriptions */
3289 g_hash_table_foreach(servers, (GHFunc) sipe_subscribe_poolfqdn_resource_uri, sipe_private);
3290 g_hash_table_destroy(servers);
3292 sipe_xml_free(xn_list);
3295 static void process_incoming_notify_pidf(struct sipe_core_private *sipe_private,
3296 const gchar *data, unsigned len)
3298 gchar *uri;
3299 gchar *getbasic;
3300 gchar *activity = NULL;
3301 sipe_xml *pidf;
3302 const sipe_xml *basicstatus = NULL, *tuple, *status;
3303 gboolean isonline = FALSE;
3304 const sipe_xml *display_name_node;
3306 pidf = sipe_xml_parse(data, len);
3307 if (!pidf) {
3308 SIPE_DEBUG_INFO("process_incoming_notify_pidf: no parseable pidf:%s", data);
3309 return;
3312 if ((tuple = sipe_xml_child(pidf, "tuple")))
3314 if ((status = sipe_xml_child(tuple, "status"))) {
3315 basicstatus = sipe_xml_child(status, "basic");
3319 if (!basicstatus) {
3320 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_notify_pidf: no basic found");
3321 sipe_xml_free(pidf);
3322 return;
3325 getbasic = sipe_xml_data(basicstatus);
3326 if (!getbasic) {
3327 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_notify_pidf: no basic data found");
3328 sipe_xml_free(pidf);
3329 return;
3332 SIPE_DEBUG_INFO("process_incoming_notify_pidf: basic-status(%s)", getbasic);
3333 if (strstr(getbasic, "open")) {
3334 isonline = TRUE;
3336 g_free(getbasic);
3338 uri = sip_uri(sipe_xml_attribute(pidf, "entity")); /* with 'sip:' prefix */ /* AOL comes without the prefix */
3340 display_name_node = sipe_xml_child(pidf, "display-name");
3341 if (display_name_node) {
3342 char * display_name = sipe_xml_data(display_name_node);
3344 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_DISPLAY_NAME, display_name);
3345 g_free(display_name);
3348 if ((tuple = sipe_xml_child(pidf, "tuple"))) {
3349 if ((status = sipe_xml_child(tuple, "status"))) {
3350 if ((basicstatus = sipe_xml_child(status, "activities"))) {
3351 if ((basicstatus = sipe_xml_child(basicstatus, "activity"))) {
3352 activity = sipe_xml_data(basicstatus);
3353 SIPE_DEBUG_INFO("process_incoming_notify_pidf: activity(%s)", activity);
3359 if (isonline) {
3360 const gchar * status_id = NULL;
3361 if (activity) {
3362 if (sipe_strequal(activity, sipe_activity_map[SIPE_ACTIVITY_BUSY].token)) {
3363 status_id = SIPE_STATUS_ID_BUSY;
3364 } else if (sipe_strequal(activity, sipe_activity_map[SIPE_ACTIVITY_AWAY].token)) {
3365 status_id = SIPE_STATUS_ID_AWAY;
3369 if (!status_id) {
3370 status_id = SIPE_STATUS_ID_AVAILABLE;
3373 SIPE_DEBUG_INFO("process_incoming_notify_pidf: status_id(%s)", status_id);
3374 sipe_core_buddy_got_status(SIPE_CORE_PUBLIC, uri, status_id);
3375 } else {
3376 sipe_core_buddy_got_status(SIPE_CORE_PUBLIC, uri, SIPE_STATUS_ID_OFFLINE);
3379 g_free(activity);
3380 g_free(uri);
3381 sipe_xml_free(pidf);
3384 /** 2005 */
3385 static void
3386 sipe_user_info_has_updated(struct sipe_core_private *sipe_private,
3387 const sipe_xml *xn_userinfo)
3389 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
3390 const sipe_xml *xn_states;
3392 g_free(sip->user_states);
3393 sip->user_states = NULL;
3394 if ((xn_states = sipe_xml_child(xn_userinfo, "states")) != NULL) {
3395 gchar *orig = sip->user_states = sipe_xml_stringify(xn_states);
3397 /* this is a hack-around to remove added newline after inner element,
3398 * state in this case, where it shouldn't be.
3399 * After several use of sipe_xml_stringify, amount of added newlines
3400 * grows significantly.
3402 if (orig) {
3403 gchar c, *stripped = orig;
3404 while ((c = *orig++)) {
3405 if ((c != '\n') /* && (c != '\r') */) {
3406 *stripped++ = c;
3409 *stripped = '\0';
3413 /* Publish initial state if not yet.
3414 * Assuming this happens on initial responce to self subscription
3415 * so we've already updated our UserInfo.
3417 if (!sip->initial_state_published) {
3418 send_presence_soap(sipe_private, FALSE);
3419 /* dalayed run */
3420 sipe_schedule_seconds(sipe_private,
3421 "<+update-calendar>",
3422 NULL,
3423 UPDATE_CALENDAR_DELAY,
3424 (sipe_schedule_action) sipe_core_update_calendar,
3425 NULL);
3429 static void process_incoming_notify_msrtc(struct sipe_core_private *sipe_private,
3430 const gchar *data, unsigned len)
3432 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
3433 char *activity = NULL;
3434 const char *epid;
3435 const char *status_id = NULL;
3436 const char *name;
3437 char *uri;
3438 char *self_uri = sip_uri_self(sipe_private);
3439 int avl;
3440 int act;
3441 const char *device_name = NULL;
3442 const char *cal_start_time = NULL;
3443 const char *cal_granularity = NULL;
3444 char *cal_free_busy_base64 = NULL;
3445 struct sipe_buddy *sbuddy;
3446 const sipe_xml *node;
3447 sipe_xml *xn_presentity;
3448 const sipe_xml *xn_availability;
3449 const sipe_xml *xn_activity;
3450 const sipe_xml *xn_display_name;
3451 const sipe_xml *xn_email;
3452 const sipe_xml *xn_phone_number;
3453 const sipe_xml *xn_userinfo;
3454 const sipe_xml *xn_note;
3455 const sipe_xml *xn_oof;
3456 const sipe_xml *xn_state;
3457 const sipe_xml *xn_contact;
3458 char *note;
3459 int user_avail;
3460 const char *user_avail_nil;
3461 int res_avail;
3462 time_t user_avail_since = 0;
3463 time_t activity_since = 0;
3465 /* fix for Reuters environment on Linux */
3466 if (data && strstr(data, "encoding=\"utf-16\"")) {
3467 char *tmp_data;
3468 tmp_data = replace(data, "encoding=\"utf-16\"", "encoding=\"utf-8\"");
3469 xn_presentity = sipe_xml_parse(tmp_data, strlen(tmp_data));
3470 g_free(tmp_data);
3471 } else {
3472 xn_presentity = sipe_xml_parse(data, len);
3475 xn_availability = sipe_xml_child(xn_presentity, "availability");
3476 xn_activity = sipe_xml_child(xn_presentity, "activity");
3477 xn_display_name = sipe_xml_child(xn_presentity, "displayName");
3478 xn_email = sipe_xml_child(xn_presentity, "email");
3479 xn_phone_number = sipe_xml_child(xn_presentity, "phoneNumber");
3480 xn_userinfo = sipe_xml_child(xn_presentity, "userInfo");
3481 xn_oof = xn_userinfo ? sipe_xml_child(xn_userinfo, "oof") : NULL;
3482 xn_state = xn_userinfo ? sipe_xml_child(xn_userinfo, "states/state"): NULL;
3483 user_avail = xn_state ? sipe_xml_int_attribute(xn_state, "avail", 0) : 0;
3484 user_avail_since = xn_state ? sipe_utils_str_to_time(sipe_xml_attribute(xn_state, "since")) : 0;
3485 user_avail_nil = xn_state ? sipe_xml_attribute(xn_state, "nil") : NULL;
3486 xn_contact = xn_userinfo ? sipe_xml_child(xn_userinfo, "contact") : NULL;
3487 xn_note = xn_userinfo ? sipe_xml_child(xn_userinfo, "note") : NULL;
3488 note = xn_note ? sipe_xml_data(xn_note) : NULL;
3490 if (sipe_strequal(user_avail_nil, "true")) { /* null-ed */
3491 user_avail = 0;
3492 user_avail_since = 0;
3495 name = sipe_xml_attribute(xn_presentity, "uri"); /* without 'sip:' prefix */
3496 uri = sip_uri_from_name(name);
3497 avl = sipe_xml_int_attribute(xn_availability, "aggregate", 0);
3498 epid = sipe_xml_attribute(xn_availability, "epid");
3499 act = sipe_xml_int_attribute(xn_activity, "aggregate", 0);
3501 status_id = sipe_get_status_by_act_avail_2005(act, avl, &activity);
3502 res_avail = sipe_get_availability_by_status(status_id, NULL);
3503 if (user_avail > res_avail) {
3504 res_avail = user_avail;
3505 status_id = sipe_get_status_by_availability(user_avail, NULL);
3508 if (xn_display_name) {
3509 char *display_name = g_strdup(sipe_xml_attribute(xn_display_name, "displayName"));
3510 char *email = xn_email ? g_strdup(sipe_xml_attribute(xn_email, "email")) : NULL;
3511 char *phone_label = xn_phone_number ? g_strdup(sipe_xml_attribute(xn_phone_number, "label")) : NULL;
3512 char *phone_number = xn_phone_number ? g_strdup(sipe_xml_attribute(xn_phone_number, "number")) : NULL;
3513 char *tel_uri = sip_to_tel_uri(phone_number);
3515 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_DISPLAY_NAME, display_name);
3516 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_EMAIL, email);
3517 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_WORK_PHONE, tel_uri);
3518 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_WORK_PHONE_DISPLAY, !is_empty(phone_label) ? phone_label : phone_number);
3520 g_free(tel_uri);
3521 g_free(phone_label);
3522 g_free(phone_number);
3523 g_free(email);
3524 g_free(display_name);
3527 if (xn_contact) {
3528 /* tel */
3529 for (node = sipe_xml_child(xn_contact, "tel"); node; node = sipe_xml_twin(node))
3531 /* Ex.: <tel type="work">tel:+3222220000</tel> */
3532 const char *phone_type = sipe_xml_attribute(node, "type");
3533 char* phone = sipe_xml_data(node);
3535 sipe_update_user_phone(sipe_private, uri, phone_type, phone, NULL);
3537 g_free(phone);
3541 /* devicePresence */
3542 for (node = sipe_xml_child(xn_presentity, "devices/devicePresence"); node; node = sipe_xml_twin(node)) {
3543 const sipe_xml *xn_device_name;
3544 const sipe_xml *xn_calendar_info;
3545 const sipe_xml *xn_state;
3546 char *state;
3548 /* deviceName */
3549 if (sipe_strequal(sipe_xml_attribute(node, "epid"), epid)) {
3550 xn_device_name = sipe_xml_child(node, "deviceName");
3551 device_name = xn_device_name ? sipe_xml_attribute(xn_device_name, "name") : NULL;
3554 /* calendarInfo */
3555 xn_calendar_info = sipe_xml_child(node, "calendarInfo");
3556 if (xn_calendar_info) {
3557 const char *cal_start_time_tmp = sipe_xml_attribute(xn_calendar_info, "startTime");
3559 if (cal_start_time) {
3560 time_t cal_start_time_t = sipe_utils_str_to_time(cal_start_time);
3561 time_t cal_start_time_t_tmp = sipe_utils_str_to_time(cal_start_time_tmp);
3563 if (cal_start_time_t_tmp > cal_start_time_t) {
3564 cal_start_time = cal_start_time_tmp;
3565 cal_granularity = sipe_xml_attribute(xn_calendar_info, "granularity");
3566 g_free(cal_free_busy_base64);
3567 cal_free_busy_base64 = sipe_xml_data(xn_calendar_info);
3569 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);
3571 } else {
3572 cal_start_time = cal_start_time_tmp;
3573 cal_granularity = sipe_xml_attribute(xn_calendar_info, "granularity");
3574 g_free(cal_free_busy_base64);
3575 cal_free_busy_base64 = sipe_xml_data(xn_calendar_info);
3577 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);
3581 /* state */
3582 xn_state = sipe_xml_child(node, "states/state");
3583 if (xn_state) {
3584 int dev_avail = sipe_xml_int_attribute(xn_state, "avail", 0);
3585 time_t dev_avail_since = sipe_utils_str_to_time(sipe_xml_attribute(xn_state, "since"));
3587 state = sipe_xml_data(xn_state);
3588 if (dev_avail_since > user_avail_since &&
3589 dev_avail >= res_avail)
3591 res_avail = dev_avail;
3592 if (!is_empty(state))
3594 if (sipe_strequal(state, sipe_activity_map[SIPE_ACTIVITY_ON_PHONE].token)) {
3595 g_free(activity);
3596 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_ON_PHONE));
3597 } else if (sipe_strequal(state, "presenting")) {
3598 g_free(activity);
3599 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_IN_CONF));
3600 } else {
3601 activity = state;
3602 state = NULL;
3604 activity_since = dev_avail_since;
3606 status_id = sipe_get_status_by_availability(res_avail, &activity);
3608 g_free(state);
3612 /* oof */
3613 if (xn_oof && res_avail >= 15000) { /* 12000 in 2007 */
3614 g_free(activity);
3615 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_OOF));
3616 activity_since = 0;
3619 sbuddy = g_hash_table_lookup(sipe_private->buddies, uri);
3620 if (sbuddy)
3622 g_free(sbuddy->activity);
3623 sbuddy->activity = activity;
3624 activity = NULL;
3626 sbuddy->activity_since = activity_since;
3628 sbuddy->user_avail = user_avail;
3629 sbuddy->user_avail_since = user_avail_since;
3631 g_free(sbuddy->note);
3632 sbuddy->note = NULL;
3633 if (!is_empty(note)) { sbuddy->note = g_markup_escape_text(note, -1); }
3635 sbuddy->is_oof_note = (xn_oof != NULL);
3637 g_free(sbuddy->device_name);
3638 sbuddy->device_name = NULL;
3639 if (!is_empty(device_name)) { sbuddy->device_name = g_strdup(device_name); }
3641 if (!is_empty(cal_free_busy_base64)) {
3642 g_free(sbuddy->cal_start_time);
3643 sbuddy->cal_start_time = g_strdup(cal_start_time);
3645 sbuddy->cal_granularity = sipe_strcase_equal(cal_granularity, "PT15M") ? 15 : 0;
3647 g_free(sbuddy->cal_free_busy_base64);
3648 sbuddy->cal_free_busy_base64 = cal_free_busy_base64;
3649 cal_free_busy_base64 = NULL;
3651 g_free(sbuddy->cal_free_busy);
3652 sbuddy->cal_free_busy = NULL;
3655 sbuddy->last_non_cal_status_id = status_id;
3656 g_free(sbuddy->last_non_cal_activity);
3657 sbuddy->last_non_cal_activity = g_strdup(sbuddy->activity);
3659 if (sipe_strcase_equal(sbuddy->name, self_uri)) {
3660 if (!sipe_strequal(sbuddy->note, sip->note)) /* not same */
3662 sip->is_oof_note = sbuddy->is_oof_note;
3664 g_free(sip->note);
3665 sip->note = g_strdup(sbuddy->note);
3667 sip->note_since = time(NULL);
3670 g_free(sip->status);
3671 sip->status = g_strdup(sbuddy->last_non_cal_status_id);
3674 g_free(cal_free_busy_base64);
3675 g_free(activity);
3677 SIPE_DEBUG_INFO("process_incoming_notify_msrtc: status(%s)", status_id);
3678 sipe_core_buddy_got_status(SIPE_CORE_PUBLIC, uri, status_id);
3680 if (!SIPE_CORE_PRIVATE_FLAG_IS(OCS2007) && sipe_strcase_equal(self_uri, uri)) {
3681 sipe_user_info_has_updated(sipe_private, xn_userinfo);
3684 g_free(note);
3685 sipe_xml_free(xn_presentity);
3686 g_free(uri);
3687 g_free(self_uri);
3690 static void sipe_presence_mime_cb(gpointer user_data, /* sipe_core_private */
3691 const GSList *fields,
3692 const gchar *body,
3693 gsize length)
3695 const gchar *type = sipe_utils_nameval_find(fields, "Content-Type");
3697 if (strstr(type,"application/rlmi+xml")) {
3698 process_incoming_notify_rlmi_resub(user_data, body, length);
3699 } else if (strstr(type, "text/xml+msrtc.pidf")) {
3700 process_incoming_notify_msrtc(user_data, body, length);
3701 } else {
3702 process_incoming_notify_rlmi(user_data, body, length);
3706 static void sipe_process_presence(struct sipe_core_private *sipe_private,
3707 struct sipmsg *msg)
3709 const char *ctype = sipmsg_find_header(msg, "Content-Type");
3711 SIPE_DEBUG_INFO("sipe_process_presence: Content-Type: %s", ctype ? ctype : "");
3713 if (ctype &&
3714 (strstr(ctype, "application/rlmi+xml") ||
3715 strstr(ctype, "application/msrtc-event-categories+xml")))
3717 if (strstr(ctype, "multipart"))
3719 sipe_mime_parts_foreach(ctype, msg->body, sipe_presence_mime_cb, sipe_private);
3721 else if(strstr(ctype, "application/msrtc-event-categories+xml") )
3723 process_incoming_notify_rlmi(sipe_private, msg->body, msg->bodylen);
3725 else if(strstr(ctype, "application/rlmi+xml"))
3727 process_incoming_notify_rlmi_resub(sipe_private, msg->body, msg->bodylen);
3730 else if(ctype && strstr(ctype, "text/xml+msrtc.pidf"))
3732 process_incoming_notify_msrtc(sipe_private, msg->body, msg->bodylen);
3734 else
3736 process_incoming_notify_pidf(sipe_private, msg->body, msg->bodylen);
3740 static void sipe_presence_timeout_mime_cb(gpointer user_data,
3741 SIPE_UNUSED_PARAMETER const GSList *fields,
3742 const gchar *body,
3743 gsize length)
3745 GSList **buddies = user_data;
3746 sipe_xml *xml = sipe_xml_parse(body, length);
3748 if (xml && !sipe_strequal(sipe_xml_name(xml), "list")) {
3749 const gchar *uri = sipe_xml_attribute(xml, "uri");
3750 const sipe_xml *xn_category;
3753 * automaton: presence is never expected to change
3755 * see: http://msdn.microsoft.com/en-us/library/ee354295(office.13).aspx
3757 for (xn_category = sipe_xml_child(xml, "category");
3758 xn_category;
3759 xn_category = sipe_xml_twin(xn_category)) {
3760 if (sipe_strequal(sipe_xml_attribute(xn_category, "name"),
3761 "contactCard")) {
3762 const sipe_xml *node = sipe_xml_child(xn_category, "contactCard/automaton");
3763 if (node) {
3764 char *boolean = sipe_xml_data(node);
3765 if (sipe_strequal(boolean, "true")) {
3766 SIPE_DEBUG_INFO("sipe_process_presence_timeout: %s is an automaton: - not subscribing to presence updates",
3767 uri);
3768 uri = NULL;
3770 g_free(boolean);
3772 break;
3776 if (uri) {
3777 *buddies = g_slist_append(*buddies, sip_uri(uri));
3781 sipe_xml_free(xml);
3784 static void sipe_process_presence_timeout(struct sipe_core_private *sipe_private,
3785 struct sipmsg *msg, gchar *who,
3786 int timeout)
3788 const char *ctype = sipmsg_find_header(msg, "Content-Type");
3789 gchar *action_name = sipe_utils_presence_key(who);
3791 SIPE_DEBUG_INFO("sipe_process_presence_timeout: Content-Type: %s", ctype ? ctype : "");
3793 if (ctype &&
3794 strstr(ctype, "multipart") &&
3795 (strstr(ctype, "application/rlmi+xml") ||
3796 strstr(ctype, "application/msrtc-event-categories+xml"))) {
3797 GSList *buddies = NULL;
3799 sipe_mime_parts_foreach(ctype, msg->body, sipe_presence_timeout_mime_cb, &buddies);
3801 if (buddies) {
3802 struct presence_batched_routed *payload = g_malloc(sizeof(struct presence_batched_routed));
3803 payload->host = g_strdup(who);
3804 payload->buddies = buddies;
3805 sipe_schedule_seconds(sipe_private,
3806 action_name,
3807 payload,
3808 timeout,
3809 sipe_subscribe_presence_batched_routed,
3810 sipe_subscribe_presence_batched_routed_free);
3811 SIPE_DEBUG_INFO("Resubscription multiple contacts with batched support & route(%s) in %d", who, timeout);
3814 } else {
3815 sipe_schedule_seconds(sipe_private,
3816 action_name,
3817 g_strdup(who),
3818 timeout,
3819 sipe_subscribe_presence_single,
3820 g_free);
3821 SIPE_DEBUG_INFO("Resubscription single contact with batched support(%s) in %d", who, timeout);
3823 g_free(action_name);
3827 * Dispatcher for all incoming subscription information
3828 * whether it comes from NOTIFY, BENOTIFY requests or
3829 * piggy-backed to subscription's OK responce.
3831 * @param request whether initiated from BE/NOTIFY request or OK-response message.
3832 * @param benotify whether initiated from NOTIFY or BENOTIFY request.
3834 void process_incoming_notify(struct sipe_core_private *sipe_private,
3835 struct sipmsg *msg,
3836 gboolean request, gboolean benotify)
3838 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
3839 const gchar *content_type = sipmsg_find_header(msg, "Content-Type");
3840 const gchar *event = sipmsg_find_header(msg, "Event");
3841 const gchar *subscription_state = sipmsg_find_header(msg, "subscription-state");
3843 SIPE_DEBUG_INFO("process_incoming_notify: subscription_state: %s", subscription_state ? subscription_state : "");
3845 /* implicit subscriptions */
3846 if (content_type && g_str_has_prefix(content_type, "application/ms-imdn+xml")) {
3847 sipe_process_imdn(sipe_private, msg);
3850 if (event) {
3851 /* for one off subscriptions (send with Expire: 0) */
3852 if (sipe_strcase_equal(event, "vnd-microsoft-provisioning-v2"))
3854 sipe_process_provisioning_v2(sipe_private, msg);
3856 else if (sipe_strcase_equal(event, "vnd-microsoft-provisioning"))
3858 sipe_process_provisioning(sipe_private, msg);
3860 else if (sipe_strcase_equal(event, "presence"))
3862 sipe_process_presence(sipe_private, msg);
3864 else if (sipe_strcase_equal(event, "registration-notify"))
3866 sipe_process_registration_notify(sipe_private, msg);
3869 if (!subscription_state || strstr(subscription_state, "active"))
3871 if (sipe_strcase_equal(event, "vnd-microsoft-roaming-contacts"))
3873 sipe_process_roaming_contacts(sipe_private, msg);
3875 else if (sipe_strcase_equal(event, "vnd-microsoft-roaming-self"))
3877 sipe_process_roaming_self(sipe_private, msg);
3879 else if (sipe_strcase_equal(event, "vnd-microsoft-roaming-ACL"))
3881 sipe_process_roaming_acl(sipe_private, msg);
3883 else if (sipe_strcase_equal(event, "presence.wpending"))
3885 sipe_process_presence_wpending(sipe_private, msg);
3887 else if (sipe_strcase_equal(event, "conference"))
3889 sipe_process_conference(sipe_private, msg);
3894 /* The server sends status 'terminated' */
3895 if (subscription_state && strstr(subscription_state, "terminated") ) {
3896 gchar *who = parse_from(sipmsg_find_header(msg, request ? "From" : "To"));
3897 gchar *key = sipe_utils_subscription_key(event, who);
3899 SIPE_DEBUG_INFO("process_incoming_notify: server says that subscription to %s was terminated.", who);
3900 g_free(who);
3902 sipe_subscriptions_remove(sipe_private, key);
3903 g_free(key);
3906 if (!request && event) {
3907 const gchar *expires_header = sipmsg_find_header(msg, "Expires");
3908 int timeout = expires_header ? strtol(expires_header, NULL, 10) : 0;
3909 SIPE_DEBUG_INFO("process_incoming_notify: subscription expires:%d", timeout);
3911 if (timeout) {
3912 /* 2 min ahead of expiration */
3913 timeout = (timeout - 120) > 120 ? (timeout - 120) : timeout;
3915 if (sipe_strcase_equal(event, "presence.wpending") &&
3916 g_slist_find_custom(sip->allow_events, "presence.wpending", (GCompareFunc)g_ascii_strcasecmp))
3918 gchar *action_name = g_strdup_printf("<%s>", "presence.wpending");
3919 sipe_schedule_seconds(sipe_private,
3920 action_name,
3921 NULL,
3922 timeout,
3923 sipe_subscribe_presence_wpending,
3924 NULL);
3925 g_free(action_name);
3927 else if (sipe_strcase_equal(event, "presence") &&
3928 g_slist_find_custom(sip->allow_events, "presence", (GCompareFunc)g_ascii_strcasecmp))
3930 gchar *who = parse_from(sipmsg_find_header(msg, "To"));
3931 gchar *action_name = sipe_utils_presence_key(who);
3933 if (sip->batched_support) {
3934 sipe_process_presence_timeout(sipe_private, msg, who, timeout);
3936 else {
3937 sipe_schedule_seconds(sipe_private,
3938 action_name,
3939 g_strdup(who),
3940 timeout,
3941 sipe_subscribe_presence_single,
3942 g_free);
3943 SIPE_DEBUG_INFO("Resubscription single contact (%s) in %d", who, timeout);
3945 g_free(action_name);
3946 g_free(who);
3951 /* The client responses on received a NOTIFY message */
3952 if (request && !benotify)
3954 sip_transport_response(sipe_private, msg, 200, "OK", NULL);
3959 * Whether user manually changed status or
3960 * it was changed automatically due to user
3961 * became inactive/active again
3963 static gboolean
3964 sipe_is_user_state(struct sipe_core_private *sipe_private)
3966 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
3967 gboolean res;
3968 time_t now = time(NULL);
3970 SIPE_DEBUG_INFO("sipe_is_user_state: sip->idle_switch : %s", asctime(localtime(&(sip->idle_switch))));
3971 SIPE_DEBUG_INFO("sipe_is_user_state: now : %s", asctime(localtime(&now)));
3973 res = ((now - SIPE_IDLE_SET_DELAY * 2) >= sip->idle_switch);
3975 SIPE_DEBUG_INFO("sipe_is_user_state: res = %s", res ? "USER" : "MACHINE");
3976 return res;
3979 static void
3980 send_presence_soap0(struct sipe_core_private *sipe_private,
3981 gboolean do_publish_calendar,
3982 gboolean do_reset_status)
3984 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
3985 struct sipe_calendar* cal = sip->cal;
3986 int availability = 0;
3987 int activity = 0;
3988 gchar *body;
3989 gchar *tmp;
3990 gchar *tmp2 = NULL;
3991 gchar *res_note = NULL;
3992 gchar *res_oof = NULL;
3993 const gchar *note_pub = NULL;
3994 gchar *states = NULL;
3995 gchar *calendar_data = NULL;
3996 gchar *epid = get_epid(sipe_private);
3997 time_t now = time(NULL);
3998 gchar *since_time_str = sipe_utils_time_to_str(now);
3999 const gchar *oof_note = cal ? sipe_ews_get_oof_note(cal) : NULL;
4000 const char *user_input;
4001 gboolean pub_oof = cal && oof_note && (!sip->note || cal->updated > sip->note_since);
4003 if (oof_note && sip->note) {
4004 SIPE_DEBUG_INFO("cal->oof_start : %s", asctime(localtime(&(cal->oof_start))));
4005 SIPE_DEBUG_INFO("sip->note_since : %s", asctime(localtime(&(sip->note_since))));
4008 SIPE_DEBUG_INFO("sip->note : %s", sip->note ? sip->note : "");
4010 if (!sip->initial_state_published ||
4011 do_reset_status)
4013 g_free(sip->status);
4014 sip->status = g_strdup(SIPE_STATUS_ID_AVAILABLE);
4017 sipe_get_act_avail_by_status_2005(sip->status, &activity, &availability);
4019 /* Note */
4020 if (pub_oof) {
4021 note_pub = oof_note;
4022 res_oof = SIPE_SOAP_SET_PRESENCE_OOF_XML;
4023 cal->published = TRUE;
4024 } else if (sip->note) {
4025 if (sip->is_oof_note && !oof_note) { /* stale OOF note, as it's not present in cal already */
4026 g_free(sip->note);
4027 sip->note = NULL;
4028 sip->is_oof_note = FALSE;
4029 sip->note_since = 0;
4030 } else {
4031 note_pub = sip->note;
4032 res_oof = sip->is_oof_note ? SIPE_SOAP_SET_PRESENCE_OOF_XML : "";
4036 if (note_pub)
4038 /* to protocol internal plain text format */
4039 tmp = sipe_backend_markup_strip_html(note_pub);
4040 res_note = g_markup_printf_escaped(SIPE_SOAP_SET_PRESENCE_NOTE_XML, tmp);
4041 g_free(tmp);
4044 /* User State */
4045 if (!do_reset_status) {
4046 if (sipe_is_user_state(sipe_private) && !do_publish_calendar && sip->initial_state_published)
4048 gchar *activity_token = NULL;
4049 int avail_2007 = sipe_get_availability_by_status(sip->status, &activity_token);
4051 states = g_strdup_printf(SIPE_SOAP_SET_PRESENCE_STATES,
4052 avail_2007,
4053 since_time_str,
4054 epid,
4055 activity_token);
4056 g_free(activity_token);
4058 else /* preserve existing publication */
4060 if (sip->user_states) {
4061 states = g_strdup(sip->user_states);
4064 } else {
4065 /* do nothing - then User state will be erased */
4067 sip->initial_state_published = TRUE;
4069 /* CalendarInfo */
4070 if (cal && (!is_empty(cal->legacy_dn) || !is_empty(cal->email)) && cal->fb_start && !is_empty(cal->free_busy))
4072 char *fb_start_str = sipe_utils_time_to_str(cal->fb_start);
4073 char *free_busy_base64 = sipe_cal_get_freebusy_base64(cal->free_busy);
4074 calendar_data = g_strdup_printf(SIPE_SOAP_SET_PRESENCE_CALENDAR,
4075 !is_empty(cal->legacy_dn) ? cal->legacy_dn : cal->email,
4076 fb_start_str,
4077 free_busy_base64);
4078 g_free(fb_start_str);
4079 g_free(free_busy_base64);
4082 user_input = (sipe_is_user_state(sipe_private) ||
4083 sipe_strequal(sip->status, SIPE_STATUS_ID_AVAILABLE)) ?
4084 "active" : "idle";
4086 /* forming resulting XML */
4087 body = g_strdup_printf(SIPE_SOAP_SET_PRESENCE,
4088 sipe_private->username,
4089 availability,
4090 activity,
4091 (tmp = g_ascii_strup(g_get_host_name(), -1)),
4092 res_note ? res_note : "",
4093 res_oof ? res_oof : "",
4094 states ? states : "",
4095 calendar_data ? calendar_data : "",
4096 epid,
4097 since_time_str,
4098 since_time_str,
4099 user_input);
4100 g_free(tmp);
4101 g_free(tmp2);
4102 g_free(res_note);
4103 g_free(states);
4104 g_free(calendar_data);
4106 send_soap_request(sipe_private, body);
4108 g_free(body);
4109 g_free(since_time_str);
4110 g_free(epid);
4113 void
4114 send_presence_soap(struct sipe_core_private *sipe_private,
4115 gboolean do_publish_calendar)
4117 return send_presence_soap0(sipe_private, do_publish_calendar, FALSE);
4121 static gboolean
4122 process_send_presence_category_publish_response(struct sipe_core_private *sipe_private,
4123 struct sipmsg *msg,
4124 struct transaction *trans)
4126 const gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
4128 if (msg->response == 409 && g_str_has_prefix(contenttype, "application/msrtc-fault+xml")) {
4129 sipe_xml *xml;
4130 const sipe_xml *node;
4131 gchar *fault_code;
4132 GHashTable *faults;
4133 int index_our;
4134 gboolean has_device_publication = FALSE;
4136 xml = sipe_xml_parse(msg->body, msg->bodylen);
4138 /* test if version mismatch fault */
4139 fault_code = sipe_xml_data(sipe_xml_child(xml, "Faultcode"));
4140 if (!sipe_strequal(fault_code, "Client.BadCall.WrongDelta")) {
4141 SIPE_DEBUG_INFO("process_send_presence_category_publish_response: unsupported fault code:%s returning.", fault_code);
4142 g_free(fault_code);
4143 sipe_xml_free(xml);
4144 return TRUE;
4146 g_free(fault_code);
4148 /* accumulating information about faulty versions */
4149 faults = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
4150 for (node = sipe_xml_child(xml, "details/operation");
4151 node;
4152 node = sipe_xml_twin(node))
4154 const gchar *index = sipe_xml_attribute(node, "index");
4155 const gchar *curVersion = sipe_xml_attribute(node, "curVersion");
4157 g_hash_table_insert(faults, g_strdup(index), g_strdup(curVersion));
4158 SIPE_DEBUG_INFO("fault added: index:%s curVersion:%s", index, curVersion);
4160 sipe_xml_free(xml);
4162 /* here we are parsing our own request to figure out what publication
4163 * referenced here only by index went wrong
4165 xml = sipe_xml_parse(trans->msg->body, trans->msg->bodylen);
4167 /* publication */
4168 for (node = sipe_xml_child(xml, "publications/publication"),
4169 index_our = 1; /* starts with 1 - our first publication */
4170 node;
4171 node = sipe_xml_twin(node), index_our++)
4173 gchar *idx = g_strdup_printf("%d", index_our);
4174 const gchar *curVersion = g_hash_table_lookup(faults, idx);
4175 const gchar *categoryName = sipe_xml_attribute(node, "categoryName");
4176 g_free(idx);
4178 if (sipe_strequal("device", categoryName)) {
4179 has_device_publication = TRUE;
4182 if (curVersion) { /* fault exist on this index */
4183 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
4184 const gchar *container = sipe_xml_attribute(node, "container");
4185 const gchar *instance = sipe_xml_attribute(node, "instance");
4186 /* key is <category><instance><container> */
4187 gchar *key = g_strdup_printf("<%s><%s><%s>", categoryName, instance, container);
4188 GHashTable *category = g_hash_table_lookup(sip->our_publications, categoryName);
4190 if (category) {
4191 struct sipe_publication *publication =
4192 g_hash_table_lookup(category, key);
4194 SIPE_DEBUG_INFO("key is %s", key);
4196 if (publication) {
4197 SIPE_DEBUG_INFO("Updating %s with version %s. Was %d before.",
4198 key, curVersion, publication->version);
4199 /* updating publication's version to the correct one */
4200 publication->version = atoi(curVersion);
4202 } else {
4203 /* We somehow lost this category from our publications... */
4204 struct sipe_publication *publication = g_new0(struct sipe_publication, 1);
4205 publication->category = g_strdup(categoryName);
4206 publication->instance = atoi(instance);
4207 publication->container = atoi(container);
4208 publication->version = atoi(curVersion);
4209 category = g_hash_table_new_full(g_str_hash, g_str_equal,
4210 g_free, (GDestroyNotify)free_publication);
4211 g_hash_table_insert(category, g_strdup(key), publication);
4212 g_hash_table_insert(sip->our_publications, g_strdup(categoryName), category);
4213 SIPE_DEBUG_INFO("added lost category '%s' key '%s'", categoryName, key);
4215 g_free(key);
4218 sipe_xml_free(xml);
4219 g_hash_table_destroy(faults);
4221 /* rebublishing with right versions */
4222 if (has_device_publication) {
4223 send_publish_category_initial(sipe_private);
4224 } else {
4225 send_presence_status(sipe_private, NULL);
4228 return TRUE;
4232 * Returns 'device' XML part for publication.
4233 * Must be g_free'd after use.
4235 static gchar *
4236 sipe_publish_get_category_device(struct sipe_core_private *sipe_private)
4238 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
4239 gchar *uri;
4240 gchar *doc;
4241 gchar *epid = get_epid(sipe_private);
4242 gchar *uuid = generateUUIDfromEPID(epid);
4243 guint device_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_DEVICE);
4244 /* key is <category><instance><container> */
4245 gchar *key = g_strdup_printf("<%s><%u><%u>", "device", device_instance, 2);
4246 struct sipe_publication *publication =
4247 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "device"), key);
4249 g_free(key);
4250 g_free(epid);
4252 uri = sip_uri_self(sipe_private);
4253 doc = g_strdup_printf(SIPE_PUB_XML_DEVICE,
4254 device_instance,
4255 publication ? publication->version : 0,
4256 uuid,
4257 uri,
4258 "00:00:00+01:00", /* @TODO make timezone real*/
4259 g_get_host_name()
4262 g_free(uri);
4263 g_free(uuid);
4265 return doc;
4269 * A service method - use
4270 * - send_publish_get_category_state_machine and
4271 * - send_publish_get_category_state_user instead.
4272 * Must be g_free'd after use.
4274 static gchar *
4275 sipe_publish_get_category_state(struct sipe_core_private *sipe_private,
4276 gboolean is_user_state)
4278 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
4279 int availability = sipe_get_availability_by_status(sip->status, NULL);
4280 guint instance = is_user_state ? sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_USER) :
4281 sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_MACHINE);
4282 /* key is <category><instance><container> */
4283 gchar *key_2 = g_strdup_printf("<%s><%u><%u>", "state", instance, 2);
4284 gchar *key_3 = g_strdup_printf("<%s><%u><%u>", "state", instance, 3);
4285 struct sipe_publication *publication_2 =
4286 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_2);
4287 struct sipe_publication *publication_3 =
4288 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_3);
4290 g_free(key_2);
4291 g_free(key_3);
4293 if (publication_2 && (publication_2->availability == availability))
4295 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_state: state has NOT changed. Exiting.");
4296 return NULL; /* nothing to update */
4299 return g_strdup_printf( is_user_state ? SIPE_PUB_XML_STATE_USER : SIPE_PUB_XML_STATE_MACHINE,
4300 instance,
4301 publication_2 ? publication_2->version : 0,
4302 availability,
4303 instance,
4304 publication_3 ? publication_3->version : 0,
4305 availability);
4309 * Only Busy and OOF calendar event are published.
4310 * Different instances are used for that.
4312 * Must be g_free'd after use.
4314 static gchar *
4315 sipe_publish_get_category_state_calendar(struct sipe_core_private *sipe_private,
4316 struct sipe_cal_event *event,
4317 const char *uri,
4318 int cal_satus)
4320 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
4321 gchar *start_time_str;
4322 int availability = 0;
4323 gchar *res;
4324 gchar *tmp = NULL;
4325 guint instance = (cal_satus == SIPE_CAL_OOF) ?
4326 sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_CALENDAR_OOF) :
4327 sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_CALENDAR);
4329 /* key is <category><instance><container> */
4330 gchar *key_2 = g_strdup_printf("<%s><%u><%u>", "state", instance, 2);
4331 gchar *key_3 = g_strdup_printf("<%s><%u><%u>", "state", instance, 3);
4332 struct sipe_publication *publication_2 =
4333 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_2);
4334 struct sipe_publication *publication_3 =
4335 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_3);
4337 g_free(key_2);
4338 g_free(key_3);
4340 if (!publication_3 && !event) { /* was nothing, have nothing, exiting */
4341 SIPE_DEBUG_INFO("sipe_publish_get_category_state_calendar: "
4342 "Exiting as no publication and no event for cal_satus:%d", cal_satus);
4343 return NULL;
4346 if (event &&
4347 publication_3 &&
4348 (publication_3->availability == availability) &&
4349 sipe_strequal(publication_3->cal_event_hash, (tmp = sipe_cal_event_hash(event))))
4351 g_free(tmp);
4352 SIPE_DEBUG_INFO("sipe_publish_get_category_state_calendar: "
4353 "cal state has NOT changed for cal_satus:%d. Exiting.", cal_satus);
4354 return NULL; /* nothing to update */
4356 g_free(tmp);
4358 if (event &&
4359 (event->cal_status == SIPE_CAL_BUSY ||
4360 event->cal_status == SIPE_CAL_OOF))
4362 gchar *availability_xml_str = NULL;
4363 gchar *activity_xml_str = NULL;
4364 gchar *escaped_subject = event->subject ? g_markup_escape_text(event->subject, -1) : NULL;
4365 gchar *escaped_location = event->location ? g_markup_escape_text(event->location, -1) : NULL;
4367 if (event->cal_status == SIPE_CAL_BUSY) {
4368 availability_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_AVAIL, 6500);
4371 if (event->cal_status == SIPE_CAL_BUSY && event->is_meeting) {
4372 activity_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_ACTIVITY,
4373 sipe_activity_map[SIPE_ACTIVITY_IN_MEETING].token,
4374 "minAvailability=\"6500\"",
4375 "maxAvailability=\"8999\"");
4376 } else if (event->cal_status == SIPE_CAL_OOF) {
4377 activity_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_ACTIVITY,
4378 sipe_activity_map[SIPE_ACTIVITY_OOF].token,
4379 "minAvailability=\"12000\"",
4380 "");
4382 start_time_str = sipe_utils_time_to_str(event->start_time);
4384 res = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR,
4385 instance,
4386 publication_2 ? publication_2->version : 0,
4387 uri,
4388 start_time_str,
4389 availability_xml_str ? availability_xml_str : "",
4390 activity_xml_str ? activity_xml_str : "",
4391 escaped_subject ? escaped_subject : "",
4392 escaped_location ? escaped_location : "",
4394 instance,
4395 publication_3 ? publication_3->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 g_free(escaped_location);
4404 g_free(escaped_subject);
4405 g_free(start_time_str);
4406 g_free(availability_xml_str);
4407 g_free(activity_xml_str);
4410 else /* including !event, SIPE_CAL_FREE, SIPE_CAL_TENTATIVE */
4412 res = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_CLEAR,
4413 instance,
4414 publication_2 ? publication_2->version : 0,
4416 instance,
4417 publication_3 ? publication_3->version : 0
4421 return res;
4425 * Returns 'machineState' XML part for publication.
4426 * Must be g_free'd after use.
4428 static gchar *
4429 sipe_publish_get_category_state_machine(struct sipe_core_private *sipe_private)
4431 return sipe_publish_get_category_state(sipe_private, FALSE);
4435 * Returns 'userState' XML part for publication.
4436 * Must be g_free'd after use.
4438 static gchar *
4439 sipe_publish_get_category_state_user(struct sipe_core_private *sipe_private)
4441 return sipe_publish_get_category_state(sipe_private, TRUE);
4445 * Returns 'note' XML part for publication.
4446 * Must be g_free'd after use.
4448 * Protocol format for Note is plain text.
4450 * @param note a note in Sipe internal HTML format
4451 * @param note_type either personal or OOF
4453 static gchar *
4454 sipe_publish_get_category_note(struct sipe_core_private *sipe_private,
4455 const char *note, /* html */
4456 const char *note_type,
4457 time_t note_start,
4458 time_t note_end)
4460 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
4461 guint instance = sipe_strequal("OOF", note_type) ? sipe_get_pub_instance(sipe_private, SIPE_PUB_NOTE_OOF) : 0;
4462 /* key is <category><instance><container> */
4463 gchar *key_note_200 = g_strdup_printf("<%s><%u><%u>", "note", instance, 200);
4464 gchar *key_note_300 = g_strdup_printf("<%s><%u><%u>", "note", instance, 300);
4465 gchar *key_note_400 = g_strdup_printf("<%s><%u><%u>", "note", instance, 400);
4467 struct sipe_publication *publication_note_200 =
4468 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_200);
4469 struct sipe_publication *publication_note_300 =
4470 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_300);
4471 struct sipe_publication *publication_note_400 =
4472 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_400);
4474 char *tmp = note ? sipe_backend_markup_strip_html(note) : NULL;
4475 char *n1 = tmp ? g_markup_escape_text(tmp, -1) : NULL;
4476 const char *n2 = publication_note_200 ? publication_note_200->note : NULL;
4477 char *res, *tmp1, *tmp2, *tmp3;
4478 char *start_time_attr;
4479 char *end_time_attr;
4481 g_free(tmp);
4482 tmp = NULL;
4483 g_free(key_note_200);
4484 g_free(key_note_300);
4485 g_free(key_note_400);
4487 /* we even need to republish empty note */
4488 if (sipe_strequal(n1, n2))
4490 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_note: note has NOT changed. Exiting.");
4491 g_free(n1);
4492 return NULL; /* nothing to update */
4495 start_time_attr = note_start ? g_strdup_printf(" startTime=\"%s\"", (tmp = sipe_utils_time_to_str(note_start))) : NULL;
4496 g_free(tmp);
4497 tmp = NULL;
4498 end_time_attr = note_end ? g_strdup_printf(" endTime=\"%s\"", (tmp = sipe_utils_time_to_str(note_end))) : NULL;
4499 g_free(tmp);
4501 if (n1) {
4502 tmp1 = g_strdup_printf(SIPE_PUB_XML_NOTE,
4503 instance,
4504 200,
4505 publication_note_200 ? publication_note_200->version : 0,
4506 note_type,
4507 start_time_attr ? start_time_attr : "",
4508 end_time_attr ? end_time_attr : "",
4509 n1);
4511 tmp2 = g_strdup_printf(SIPE_PUB_XML_NOTE,
4512 instance,
4513 300,
4514 publication_note_300 ? publication_note_300->version : 0,
4515 note_type,
4516 start_time_attr ? start_time_attr : "",
4517 end_time_attr ? end_time_attr : "",
4518 n1);
4520 tmp3 = g_strdup_printf(SIPE_PUB_XML_NOTE,
4521 instance,
4522 400,
4523 publication_note_400 ? publication_note_400->version : 0,
4524 note_type,
4525 start_time_attr ? start_time_attr : "",
4526 end_time_attr ? end_time_attr : "",
4527 n1);
4528 } else {
4529 tmp1 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
4530 "note",
4531 instance,
4532 200,
4533 publication_note_200 ? publication_note_200->version : 0,
4534 "static");
4535 tmp2 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
4536 "note",
4537 instance,
4538 300,
4539 publication_note_200 ? publication_note_200->version : 0,
4540 "static");
4541 tmp3 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
4542 "note",
4543 instance,
4544 400,
4545 publication_note_200 ? publication_note_200->version : 0,
4546 "static");
4548 res = g_strconcat(tmp1, tmp2, tmp3, NULL);
4550 g_free(start_time_attr);
4551 g_free(end_time_attr);
4552 g_free(tmp1);
4553 g_free(tmp2);
4554 g_free(tmp3);
4555 g_free(n1);
4557 return res;
4561 * Returns 'calendarData' XML part with WorkingHours for publication.
4562 * Must be g_free'd after use.
4564 static gchar *
4565 sipe_publish_get_category_cal_working_hours(struct sipe_core_private *sipe_private)
4567 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
4568 struct sipe_calendar* cal = sip->cal;
4570 /* key is <category><instance><container> */
4571 gchar *key_cal_1 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 1);
4572 gchar *key_cal_100 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 100);
4573 gchar *key_cal_200 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 200);
4574 gchar *key_cal_300 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 300);
4575 gchar *key_cal_400 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 400);
4576 gchar *key_cal_32000 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 32000);
4578 struct sipe_publication *publication_cal_1 =
4579 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_1);
4580 struct sipe_publication *publication_cal_100 =
4581 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_100);
4582 struct sipe_publication *publication_cal_200 =
4583 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_200);
4584 struct sipe_publication *publication_cal_300 =
4585 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_300);
4586 struct sipe_publication *publication_cal_400 =
4587 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_400);
4588 struct sipe_publication *publication_cal_32000 =
4589 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_32000);
4591 const char *n1 = cal ? cal->working_hours_xml_str : NULL;
4592 const char *n2 = publication_cal_300 ? publication_cal_300->working_hours_xml_str : NULL;
4594 g_free(key_cal_1);
4595 g_free(key_cal_100);
4596 g_free(key_cal_200);
4597 g_free(key_cal_300);
4598 g_free(key_cal_400);
4599 g_free(key_cal_32000);
4601 if (!cal || is_empty(cal->email) || is_empty(cal->working_hours_xml_str)) {
4602 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_cal_working_hours: no data to publish, exiting");
4603 return NULL;
4606 if (sipe_strequal(n1, n2))
4608 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_cal_working_hours: WorkingHours has NOT changed. Exiting.");
4609 return NULL; /* nothing to update */
4612 return g_strdup_printf(SIPE_PUB_XML_WORKING_HOURS,
4613 /* 1 */
4614 publication_cal_1 ? publication_cal_1->version : 0,
4615 cal->email,
4616 cal->working_hours_xml_str,
4617 /* 100 - Public */
4618 publication_cal_100 ? publication_cal_100->version : 0,
4619 /* 200 - Company */
4620 publication_cal_200 ? publication_cal_200->version : 0,
4621 cal->email,
4622 cal->working_hours_xml_str,
4623 /* 300 - Team */
4624 publication_cal_300 ? publication_cal_300->version : 0,
4625 cal->email,
4626 cal->working_hours_xml_str,
4627 /* 400 - Personal */
4628 publication_cal_400 ? publication_cal_400->version : 0,
4629 cal->email,
4630 cal->working_hours_xml_str,
4631 /* 32000 - Blocked */
4632 publication_cal_32000 ? publication_cal_32000->version : 0
4637 * Returns 'calendarData' XML part with FreeBusy for publication.
4638 * Must be g_free'd after use.
4640 static gchar *
4641 sipe_publish_get_category_cal_free_busy(struct sipe_core_private *sipe_private)
4643 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
4644 struct sipe_calendar* cal = sip->cal;
4645 guint cal_data_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_CALENDAR_DATA);
4646 char *fb_start_str;
4647 char *free_busy_base64;
4648 /* const char *st; */
4649 /* const char *fb; */
4650 char *res;
4652 /* key is <category><instance><container> */
4653 gchar *key_cal_1 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 1);
4654 gchar *key_cal_100 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 100);
4655 gchar *key_cal_200 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 200);
4656 gchar *key_cal_300 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 300);
4657 gchar *key_cal_400 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 400);
4658 gchar *key_cal_32000 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 32000);
4660 struct sipe_publication *publication_cal_1 =
4661 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_1);
4662 struct sipe_publication *publication_cal_100 =
4663 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_100);
4664 struct sipe_publication *publication_cal_200 =
4665 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_200);
4666 struct sipe_publication *publication_cal_300 =
4667 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_300);
4668 struct sipe_publication *publication_cal_400 =
4669 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_400);
4670 struct sipe_publication *publication_cal_32000 =
4671 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_32000);
4673 g_free(key_cal_1);
4674 g_free(key_cal_100);
4675 g_free(key_cal_200);
4676 g_free(key_cal_300);
4677 g_free(key_cal_400);
4678 g_free(key_cal_32000);
4680 if (!cal || is_empty(cal->email) || !cal->fb_start || is_empty(cal->free_busy)) {
4681 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_cal_free_busy: no data to publish, exiting");
4682 return NULL;
4685 fb_start_str = sipe_utils_time_to_str(cal->fb_start);
4686 free_busy_base64 = sipe_cal_get_freebusy_base64(cal->free_busy);
4688 /* we will rebuplish the same data to refresh publication time,
4689 * so if data from multiple sources, most recent will be choosen
4691 // st = publication_cal_300 ? publication_cal_300->fb_start_str : NULL;
4692 // fb = publication_cal_300 ? publication_cal_300->free_busy_base64 : NULL;
4694 //if (sipe_strequal(st, fb_start_str) && sipe_strequal(fb, free_busy_base64))
4696 // SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_cal_free_busy: FreeBusy has NOT changed. Exiting.");
4697 // g_free(fb_start_str);
4698 // g_free(free_busy_base64);
4699 // return NULL; /* nothing to update */
4702 res = g_strdup_printf(SIPE_PUB_XML_FREE_BUSY,
4703 /* 1 */
4704 cal_data_instance,
4705 publication_cal_1 ? publication_cal_1->version : 0,
4706 /* 100 - Public */
4707 cal_data_instance,
4708 publication_cal_100 ? publication_cal_100->version : 0,
4709 /* 200 - Company */
4710 cal_data_instance,
4711 publication_cal_200 ? publication_cal_200->version : 0,
4712 cal->email,
4713 fb_start_str,
4714 free_busy_base64,
4715 /* 300 - Team */
4716 cal_data_instance,
4717 publication_cal_300 ? publication_cal_300->version : 0,
4718 cal->email,
4719 fb_start_str,
4720 free_busy_base64,
4721 /* 400 - Personal */
4722 cal_data_instance,
4723 publication_cal_400 ? publication_cal_400->version : 0,
4724 cal->email,
4725 fb_start_str,
4726 free_busy_base64,
4727 /* 32000 - Blocked */
4728 cal_data_instance,
4729 publication_cal_32000 ? publication_cal_32000->version : 0
4732 g_free(fb_start_str);
4733 g_free(free_busy_base64);
4734 return res;
4737 static void send_presence_publish(struct sipe_core_private *sipe_private,
4738 const char *publications)
4740 gchar *uri;
4741 gchar *doc;
4742 gchar *tmp;
4743 gchar *hdr;
4745 uri = sip_uri_self(sipe_private);
4746 doc = g_strdup_printf(SIPE_SEND_PRESENCE,
4747 uri,
4748 publications);
4750 tmp = get_contact(sipe_private);
4751 hdr = g_strdup_printf("Contact: %s\r\n"
4752 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp);
4754 sip_transport_service(sipe_private,
4755 uri,
4756 hdr,
4757 doc,
4758 process_send_presence_category_publish_response);
4760 g_free(tmp);
4761 g_free(hdr);
4762 g_free(uri);
4763 g_free(doc);
4766 static void
4767 send_publish_category_initial(struct sipe_core_private *sipe_private)
4769 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
4770 gchar *pub_device = sipe_publish_get_category_device(sipe_private);
4771 gchar *pub_machine;
4772 gchar *publications;
4774 g_free(sip->status);
4775 sip->status = g_strdup(SIPE_STATUS_ID_AVAILABLE); /* our initial state */
4777 pub_machine = sipe_publish_get_category_state_machine(sipe_private);
4778 publications = g_strdup_printf("%s%s",
4779 pub_device,
4780 pub_machine ? pub_machine : "");
4781 g_free(pub_device);
4782 g_free(pub_machine);
4784 send_presence_publish(sipe_private, publications);
4785 g_free(publications);
4788 static void
4789 send_presence_category_publish(struct sipe_core_private *sipe_private)
4791 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
4792 gchar *pub_state = sipe_is_user_state(sipe_private) ?
4793 sipe_publish_get_category_state_user(sipe_private) :
4794 sipe_publish_get_category_state_machine(sipe_private);
4795 gchar *pub_note = sipe_publish_get_category_note(sipe_private,
4796 sip->note,
4797 sip->is_oof_note ? "OOF" : "personal",
4800 gchar *publications;
4802 if (!pub_state && !pub_note) {
4803 SIPE_DEBUG_INFO_NOFORMAT("send_presence_category_publish: nothing has changed. Exiting.");
4804 return;
4807 publications = g_strdup_printf("%s%s",
4808 pub_state ? pub_state : "",
4809 pub_note ? pub_note : "");
4811 g_free(pub_state);
4812 g_free(pub_note);
4814 send_presence_publish(sipe_private, publications);
4815 g_free(publications);
4819 * Publishes self status
4820 * based on own calendar information.
4822 * For 2007+
4824 void
4825 publish_calendar_status_self(struct sipe_core_private *sipe_private,
4826 SIPE_UNUSED_PARAMETER void *unused)
4828 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
4829 struct sipe_cal_event* event = NULL;
4830 gchar *pub_cal_working_hours = NULL;
4831 gchar *pub_cal_free_busy = NULL;
4832 gchar *pub_calendar = NULL;
4833 gchar *pub_calendar2 = NULL;
4834 gchar *pub_oof_note = NULL;
4835 const gchar *oof_note;
4836 time_t oof_start = 0;
4837 time_t oof_end = 0;
4839 if (!sip->cal) {
4840 SIPE_DEBUG_INFO_NOFORMAT("publish_calendar_status_self() no calendar data.");
4841 return;
4844 SIPE_DEBUG_INFO_NOFORMAT("publish_calendar_status_self() started.");
4845 if (sip->cal->cal_events) {
4846 event = sipe_cal_get_event(sip->cal->cal_events, time(NULL));
4849 if (!event) {
4850 SIPE_DEBUG_INFO_NOFORMAT("publish_calendar_status_self: current event is NULL");
4851 } else {
4852 char *desc = sipe_cal_event_describe(event);
4853 SIPE_DEBUG_INFO("publish_calendar_status_self: current event is:\n%s", desc ? desc : "");
4854 g_free(desc);
4857 /* Logic
4858 if OOF
4859 OOF publish, Busy clean
4860 ilse if Busy
4861 OOF clean, Busy publish
4862 else
4863 OOF clean, Busy clean
4865 if (event && event->cal_status == SIPE_CAL_OOF) {
4866 pub_calendar = sipe_publish_get_category_state_calendar(sipe_private, event, sip->cal->email, SIPE_CAL_OOF);
4867 pub_calendar2 = sipe_publish_get_category_state_calendar(sipe_private, NULL, sip->cal->email, SIPE_CAL_BUSY);
4868 } else if (event && event->cal_status == SIPE_CAL_BUSY) {
4869 pub_calendar = sipe_publish_get_category_state_calendar(sipe_private, NULL, sip->cal->email, SIPE_CAL_OOF);
4870 pub_calendar2 = sipe_publish_get_category_state_calendar(sipe_private, event, sip->cal->email, SIPE_CAL_BUSY);
4871 } else {
4872 pub_calendar = sipe_publish_get_category_state_calendar(sipe_private, NULL, sip->cal->email, SIPE_CAL_OOF);
4873 pub_calendar2 = sipe_publish_get_category_state_calendar(sipe_private, NULL, sip->cal->email, SIPE_CAL_BUSY);
4876 oof_note = sipe_ews_get_oof_note(sip->cal);
4877 if (sipe_strequal("Scheduled", sip->cal->oof_state)) {
4878 oof_start = sip->cal->oof_start;
4879 oof_end = sip->cal->oof_end;
4881 pub_oof_note = sipe_publish_get_category_note(sipe_private, oof_note, "OOF", oof_start, oof_end);
4883 pub_cal_working_hours = sipe_publish_get_category_cal_working_hours(sipe_private);
4884 pub_cal_free_busy = sipe_publish_get_category_cal_free_busy(sipe_private);
4886 if (!pub_cal_working_hours && !pub_cal_free_busy && !pub_calendar && !pub_calendar2 && !pub_oof_note) {
4887 SIPE_DEBUG_INFO_NOFORMAT("publish_calendar_status_self: nothing has changed.");
4888 } else {
4889 gchar *publications = g_strdup_printf("%s%s%s%s%s",
4890 pub_cal_working_hours ? pub_cal_working_hours : "",
4891 pub_cal_free_busy ? pub_cal_free_busy : "",
4892 pub_calendar ? pub_calendar : "",
4893 pub_calendar2 ? pub_calendar2 : "",
4894 pub_oof_note ? pub_oof_note : "");
4896 send_presence_publish(sipe_private, publications);
4897 g_free(publications);
4900 g_free(pub_cal_working_hours);
4901 g_free(pub_cal_free_busy);
4902 g_free(pub_calendar);
4903 g_free(pub_calendar2);
4904 g_free(pub_oof_note);
4906 /* repeat scheduling */
4907 sipe_sched_calendar_status_self_publish(sipe_private, time(NULL));
4910 static void send_presence_status(struct sipe_core_private *sipe_private,
4911 SIPE_UNUSED_PARAMETER void *unused)
4913 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
4914 PurpleStatus * status = purple_account_get_active_status(sip->account);
4916 if (!status) return;
4918 SIPE_DEBUG_INFO("send_presence_status: status: %s (%s)",
4919 purple_status_get_id(status) ? purple_status_get_id(status) : "",
4920 sipe_is_user_state(sipe_private) ? "USER" : "MACHINE");
4922 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
4923 send_presence_category_publish(sipe_private);
4924 } else {
4925 send_presence_soap(sipe_private, FALSE);
4929 static guint sipe_ht_hash_nick(const char *nick)
4931 char *lc = g_utf8_strdown(nick, -1);
4932 guint bucket = g_str_hash(lc);
4933 g_free(lc);
4935 return bucket;
4938 static gboolean sipe_ht_equals_nick(const char *nick1, const char *nick2)
4940 char *nick1_norm = NULL;
4941 char *nick2_norm = NULL;
4942 gboolean equal;
4944 if (nick1 == NULL && nick2 == NULL) return TRUE;
4945 if (nick1 == NULL || nick2 == NULL ||
4946 !g_utf8_validate(nick1, -1, NULL) ||
4947 !g_utf8_validate(nick2, -1, NULL)) return FALSE;
4949 nick1_norm = g_utf8_casefold(nick1, -1);
4950 nick2_norm = g_utf8_casefold(nick2, -1);
4951 equal = g_utf8_collate(nick1_norm, nick2_norm) == 0;
4952 g_free(nick2_norm);
4953 g_free(nick1_norm);
4955 return equal;
4958 /* temporary function */
4959 void sipe_purple_setup(struct sipe_core_public *sipe_public,
4960 PurpleConnection *gc)
4962 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA;
4963 sip->gc = gc;
4964 sip->account = purple_connection_get_account(gc);
4967 struct sipe_core_public *sipe_core_allocate(const gchar *signin_name,
4968 const gchar *login_domain,
4969 const gchar *login_account,
4970 const gchar *password,
4971 const gchar *email,
4972 const gchar *email_url,
4973 const gchar **errmsg)
4975 struct sipe_core_private *sipe_private;
4976 struct sipe_account_data *sip;
4977 gchar **user_domain;
4979 SIPE_DEBUG_INFO("sipe_core_allocate: signin_name '%s'", signin_name);
4981 /* ensure that sign-in name doesn't contain invalid characters */
4982 if (strpbrk(signin_name, "\t\v\r\n") != NULL) {
4983 *errmsg = _("SIP Exchange user name contains invalid characters");
4984 return NULL;
4987 /* ensure that sign-in name format is name@domain */
4988 if (!strchr(signin_name, '@') ||
4989 g_str_has_prefix(signin_name, "@") ||
4990 g_str_has_suffix(signin_name, "@")) {
4991 *errmsg = _("User name should be a valid SIP URI\nExample: user@company.com");
4992 return NULL;
4995 /* ensure that email format is name@domain (if provided) */
4996 if (!is_empty(email) &&
4997 (!strchr(email, '@') ||
4998 g_str_has_prefix(email, "@") ||
4999 g_str_has_suffix(email, "@")))
5001 *errmsg = _("Email address should be valid if provided\nExample: user@company.com");
5002 return NULL;
5005 /* ensure that user name doesn't contain spaces */
5006 user_domain = g_strsplit(signin_name, "@", 2);
5007 SIPE_DEBUG_INFO("sipe_core_allocate: user '%s' domain '%s'", user_domain[0], user_domain[1]);
5008 if (strchr(user_domain[0], ' ') != NULL) {
5009 g_strfreev(user_domain);
5010 *errmsg = _("SIP Exchange user name contains whitespace");
5011 return NULL;
5014 /* ensure that email_url is in proper format if enabled (if provided).
5015 * Example (Exchange): https://server.company.com/EWS/Exchange.asmx
5016 * Example (Domino) : https://[domino_server]/[mail_database_name].nsf
5018 if (!is_empty(email_url)) {
5019 char *tmp = g_ascii_strdown(email_url, -1);
5020 if (!g_str_has_prefix(tmp, "https://"))
5022 g_free(tmp);
5023 g_strfreev(user_domain);
5024 *errmsg = _("Email services URL should be valid if provided\n"
5025 "Example: https://exchange.corp.com/EWS/Exchange.asmx\n"
5026 "Example: https://domino.corp.com/maildatabase.nsf");
5027 return NULL;
5029 g_free(tmp);
5032 sipe_private = g_new0(struct sipe_core_private, 1);
5033 sipe_private->temporary = sip = g_new0(struct sipe_account_data, 1);
5034 sip->subscribed_buddies = FALSE;
5035 sip->initial_state_published = FALSE;
5036 sipe_private->username = g_strdup(signin_name);
5037 sip->email = is_empty(email) ? g_strdup(signin_name) : g_strdup(email);
5038 sip->authdomain = is_empty(login_domain) ? NULL : g_strdup(login_domain);
5039 sip->authuser = is_empty(login_account) ? NULL : g_strdup(login_account);
5040 sip->password = g_strdup(password);
5041 sipe_private->public.sip_name = g_strdup(user_domain[0]);
5042 sipe_private->public.sip_domain = g_strdup(user_domain[1]);
5043 g_strfreev(user_domain);
5045 sipe_private->buddies = g_hash_table_new((GHashFunc)sipe_ht_hash_nick, (GEqualFunc)sipe_ht_equals_nick);
5046 sip->our_publications = g_hash_table_new_full(g_str_hash, g_str_equal,
5047 g_free, (GDestroyNotify)g_hash_table_destroy);
5048 sipe_subscriptions_init(sipe_private);
5049 sip->status = g_strdup(SIPE_STATUS_ID_UNKNOWN);
5051 return((struct sipe_core_public *)sipe_private);
5054 static void
5055 sipe_blist_menu_free_containers(struct sipe_core_private *sipe_private);
5057 void sipe_connection_cleanup(struct sipe_core_private *sipe_private)
5059 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5061 g_free(sipe_private->epid);
5062 sipe_private->epid = NULL;
5064 sip_transport_disconnect(sipe_private);
5066 sipe_schedule_cancel_all(sipe_private);
5068 if (sip->allow_events) {
5069 GSList *entry = sip->allow_events;
5070 while (entry) {
5071 g_free(entry->data);
5072 entry = entry->next;
5075 g_slist_free(sip->allow_events);
5077 if (sip->containers) {
5078 GSList *entry = sip->containers;
5079 while (entry) {
5080 free_container((struct sipe_container *)entry->data);
5081 entry = entry->next;
5084 g_slist_free(sip->containers);
5086 /* libpurple memory leak workaround */
5087 sipe_blist_menu_free_containers(sipe_private);
5089 if (sipe_private->contact)
5090 g_free(sipe_private->contact);
5091 sipe_private->contact = NULL;
5092 if (sip->regcallid)
5093 g_free(sip->regcallid);
5094 sip->regcallid = NULL;
5096 if (sipe_private->focus_factory_uri)
5097 g_free(sipe_private->focus_factory_uri);
5098 sipe_private->focus_factory_uri = NULL;
5100 if (sip->cal) {
5101 sipe_cal_calendar_free(sip->cal);
5103 sip->cal = NULL;
5105 sipe_groupchat_free(sipe_private);
5109 * A callback for g_hash_table_foreach_remove
5111 static gboolean sipe_buddy_remove(SIPE_UNUSED_PARAMETER gpointer key, gpointer buddy,
5112 SIPE_UNUSED_PARAMETER gpointer user_data)
5114 sipe_free_buddy((struct sipe_buddy *) buddy);
5116 /* We must return TRUE as the key/value have already been deleted */
5117 return(TRUE);
5120 void sipe_buddy_free_all(struct sipe_core_private *sipe_private)
5122 g_hash_table_foreach_steal(sipe_private->buddies, sipe_buddy_remove, NULL);
5125 static void sipe_searchresults_im_buddy(PurpleConnection *gc, GList *row,
5126 SIPE_UNUSED_PARAMETER void *user_data)
5128 PurpleAccount *acct = purple_connection_get_account(gc);
5129 char *id = sip_uri_from_name((gchar *)g_list_nth_data(row, 0));
5130 PurpleConversation *conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, id, acct);
5131 if (conv == NULL)
5132 conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, acct, id);
5133 purple_conversation_present(conv);
5134 g_free(id);
5137 static void sipe_searchresults_add_buddy(PurpleConnection *gc, GList *row,
5138 SIPE_UNUSED_PARAMETER void *user_data)
5141 purple_blist_request_add_buddy(purple_connection_get_account(gc),
5142 g_list_nth_data(row, 0), _("Other Contacts"), g_list_nth_data(row, 1));
5145 static gboolean process_search_contact_response(struct sipe_core_private *sipe_private,
5146 struct sipmsg *msg,
5147 SIPE_UNUSED_PARAMETER struct transaction *trans)
5149 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5150 PurpleNotifySearchResults *results;
5151 PurpleNotifySearchColumn *column;
5152 sipe_xml *searchResults;
5153 const sipe_xml *mrow;
5154 int match_count = 0;
5155 gboolean more = FALSE;
5156 gchar *secondary;
5158 SIPE_DEBUG_INFO("process_search_contact_response: body:\n%s", msg->body ? msg->body : "");
5160 searchResults = sipe_xml_parse(msg->body, msg->bodylen);
5161 if (!searchResults) {
5162 SIPE_DEBUG_INFO_NOFORMAT("process_search_contact_response: no parseable searchResults");
5163 return FALSE;
5166 results = purple_notify_searchresults_new();
5168 if (results == NULL) {
5169 SIPE_DEBUG_ERROR_NOFORMAT("purple_parse_searchreply: Unable to display the search results.");
5170 purple_notify_error(sip->gc, NULL, _("Unable to display the search results"), NULL);
5172 sipe_xml_free(searchResults);
5173 return FALSE;
5176 column = purple_notify_searchresults_column_new(_("User name"));
5177 purple_notify_searchresults_column_add(results, column);
5179 column = purple_notify_searchresults_column_new(_("Name"));
5180 purple_notify_searchresults_column_add(results, column);
5182 column = purple_notify_searchresults_column_new(_("Company"));
5183 purple_notify_searchresults_column_add(results, column);
5185 column = purple_notify_searchresults_column_new(_("Country"));
5186 purple_notify_searchresults_column_add(results, column);
5188 column = purple_notify_searchresults_column_new(_("Email"));
5189 purple_notify_searchresults_column_add(results, column);
5191 for (mrow = sipe_xml_child(searchResults, "Body/Array/row"); mrow; mrow = sipe_xml_twin(mrow)) {
5192 GList *row = NULL;
5194 gchar **uri_parts = g_strsplit(sipe_xml_attribute(mrow, "uri"), ":", 2);
5195 row = g_list_append(row, g_strdup(uri_parts[1]));
5196 g_strfreev(uri_parts);
5198 row = g_list_append(row, g_strdup(sipe_xml_attribute(mrow, "displayName")));
5199 row = g_list_append(row, g_strdup(sipe_xml_attribute(mrow, "company")));
5200 row = g_list_append(row, g_strdup(sipe_xml_attribute(mrow, "country")));
5201 row = g_list_append(row, g_strdup(sipe_xml_attribute(mrow, "email")));
5203 purple_notify_searchresults_row_add(results, row);
5204 match_count++;
5207 if ((mrow = sipe_xml_child(searchResults, "Body/directorySearch/moreAvailable")) != NULL) {
5208 char *data = sipe_xml_data(mrow);
5209 more = (g_strcasecmp(data, "true") == 0);
5210 g_free(data);
5213 secondary = g_strdup_printf(
5214 dngettext(PACKAGE_NAME,
5215 "Found %d contact%s:",
5216 "Found %d contacts%s:", match_count),
5217 match_count, more ? _(" (more matched your query)") : "");
5219 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_IM, sipe_searchresults_im_buddy);
5220 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_ADD, sipe_searchresults_add_buddy);
5221 purple_notify_searchresults(sip->gc, NULL, NULL, secondary, results, NULL, NULL);
5223 g_free(secondary);
5224 sipe_xml_free(searchResults);
5225 return TRUE;
5228 void sipe_search_contact_with_cb(PurpleConnection *gc, PurpleRequestFields *fields)
5230 GList *entries = purple_request_field_group_get_fields(purple_request_fields_get_groups(fields)->data);
5231 gchar **attrs = g_new(gchar *, g_list_length(entries) + 1);
5232 unsigned i = 0;
5234 if (!attrs) return;
5236 do {
5237 PurpleRequestField *field = entries->data;
5238 const char *id = purple_request_field_get_id(field);
5239 const char *value = purple_request_field_string_get_value(field);
5241 SIPE_DEBUG_INFO("sipe_search_contact_with_cb: %s = '%s'", id, value ? value : "");
5243 if (value != NULL) attrs[i++] = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, id, value);
5244 } while ((entries = g_list_next(entries)) != NULL);
5245 attrs[i] = NULL;
5247 if (i > 0) {
5248 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
5249 gchar *domain_uri = sip_uri_from_name(sipe_private->public.sip_domain);
5250 gchar *query = g_strjoinv(NULL, attrs);
5251 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 100, query);
5252 SIPE_DEBUG_INFO("sipe_search_contact_with_cb: body:\n%s", body ? body : "");
5253 send_soap_request_with_cb(sipe_private, domain_uri, body,
5254 process_search_contact_response, NULL);
5255 g_free(domain_uri);
5256 g_free(body);
5257 g_free(query);
5260 g_strfreev(attrs);
5263 static void sipe_publish_get_cat_state_user_to_clear(SIPE_UNUSED_PARAMETER const char *name,
5264 gpointer value,
5265 GString* str)
5267 struct sipe_publication *publication = value;
5269 g_string_append_printf( str,
5270 SIPE_PUB_XML_PUBLICATION_CLEAR,
5271 publication->category,
5272 publication->instance,
5273 publication->container,
5274 publication->version,
5275 "static");
5278 void sipe_core_reset_status(struct sipe_core_public *sipe_public)
5280 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
5281 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA;
5282 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) /* 2007+ */
5284 GString* str = g_string_new(NULL);
5285 gchar *publications;
5287 if (!sip->user_state_publications || g_hash_table_size(sip->user_state_publications) == 0) {
5288 SIPE_DEBUG_INFO_NOFORMAT("sipe_reset_status: no userState publications, exiting.");
5289 return;
5292 g_hash_table_foreach(sip->user_state_publications, (GHFunc)sipe_publish_get_cat_state_user_to_clear, str);
5293 publications = g_string_free(str, FALSE);
5295 send_presence_publish(sipe_private, publications);
5296 g_free(publications);
5298 else /* 2005 */
5300 send_presence_soap0(sipe_private, FALSE, TRUE);
5304 /** for Access levels menu */
5305 #define INDENT_FMT " %s"
5307 /** Member is directly placed to access level container.
5308 * For example SIP URI of user is in the container.
5310 #define INDENT_MARKED_FMT "* %s"
5312 /** Member is indirectly belong to access level container.
5313 * For example 'sameEnterprise' is in the container and user
5314 * belongs to that same enterprise.
5316 #define INDENT_MARKED_INHERITED_FMT "= %s"
5318 GSList *sipe_core_buddy_info(struct sipe_core_public *sipe_public,
5319 const gchar *name,
5320 const gchar *status_name,
5321 gboolean is_online)
5323 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
5324 gchar *note = NULL;
5325 gboolean is_oof_note = FALSE;
5326 const gchar *activity = NULL;
5327 gchar *calendar = NULL;
5328 const gchar *meeting_subject = NULL;
5329 const gchar *meeting_location = NULL;
5330 gchar *access_text = NULL;
5331 GSList *info = NULL;
5333 #define SIPE_ADD_BUDDY_INFO_COMMON(l, t) \
5335 struct sipe_buddy_info *sbi = g_malloc(sizeof(struct sipe_buddy_info)); \
5336 sbi->label = (l); \
5337 sbi->text = (t); \
5338 info = g_slist_append(info, sbi); \
5340 #define SIPE_ADD_BUDDY_INFO(l, t) SIPE_ADD_BUDDY_INFO_COMMON((l), g_markup_escape_text((t), -1))
5341 #define SIPE_ADD_BUDDY_INFO_NOESCAPE(l, t) SIPE_ADD_BUDDY_INFO_COMMON((l), (t))
5343 if (sipe_public) { //happens on pidgin exit
5344 struct sipe_buddy *sbuddy = g_hash_table_lookup(sipe_private->buddies, name);
5345 if (sbuddy) {
5346 note = sbuddy->note;
5347 is_oof_note = sbuddy->is_oof_note;
5348 activity = sbuddy->activity;
5349 calendar = sipe_cal_get_description(sbuddy);
5350 meeting_subject = sbuddy->meeting_subject;
5351 meeting_location = sbuddy->meeting_location;
5353 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
5354 gboolean is_group_access = FALSE;
5355 const int container_id = sipe_find_access_level(sipe_private, "user", sipe_get_no_sip_uri(name), &is_group_access);
5356 const char *access_level = sipe_get_access_level_name(container_id);
5357 access_text = is_group_access ?
5358 g_strdup(access_level) :
5359 g_strdup_printf(INDENT_MARKED_FMT, access_level);
5363 //Layout
5364 if (is_online)
5366 const gchar *status_str = activity ? activity : status_name;
5368 SIPE_ADD_BUDDY_INFO(_("Status"), status_str);
5370 if (is_online && !is_empty(calendar))
5372 SIPE_ADD_BUDDY_INFO(_("Calendar"), calendar);
5374 g_free(calendar);
5375 if (!is_empty(meeting_location))
5377 SIPE_DEBUG_INFO("sipe_tooltip_text: %s meeting location: '%s'", name, meeting_location);
5378 SIPE_ADD_BUDDY_INFO(_("Meeting in"), meeting_location);
5380 if (!is_empty(meeting_subject))
5382 SIPE_DEBUG_INFO("sipe_tooltip_text: %s meeting subject: '%s'", name, meeting_subject);
5383 SIPE_ADD_BUDDY_INFO(_("Meeting about"), meeting_subject);
5385 if (note)
5387 SIPE_DEBUG_INFO("sipe_tooltip_text: %s note: '%s'", name, note);
5388 SIPE_ADD_BUDDY_INFO_NOESCAPE(is_oof_note ? _("Out of office note") : _("Note"),
5389 g_strdup_printf("<i>%s</i>", note));
5391 if (access_text) {
5392 SIPE_ADD_BUDDY_INFO(_("Access level"), access_text);
5393 g_free(access_text);
5396 return(info);
5399 static PurpleBuddy *
5400 purple_blist_add_buddy_clone(PurpleGroup * group, PurpleBuddy * buddy)
5402 PurpleBuddy *clone;
5403 const gchar *server_alias, *email;
5404 const PurpleStatus *status = purple_presence_get_active_status(purple_buddy_get_presence(buddy));
5406 clone = purple_buddy_new(buddy->account, buddy->name, buddy->alias);
5408 purple_blist_add_buddy(clone, NULL, group, NULL);
5410 server_alias = purple_buddy_get_server_alias(buddy);
5411 if (server_alias) {
5412 purple_blist_server_alias_buddy(clone, server_alias);
5415 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
5416 if (email) {
5417 purple_blist_node_set_string(&clone->node, EMAIL_PROP, email);
5420 purple_presence_set_status_active(purple_buddy_get_presence(clone), purple_status_get_id(status), TRUE);
5421 //for UI to update;
5422 purple_prpl_got_user_status(clone->account, clone->name, purple_status_get_id(status), NULL);
5423 return clone;
5426 static void
5427 sipe_buddy_menu_copy_to_cb(PurpleBlistNode *node, const char *group_name)
5429 PurpleBuddy *buddy, *b;
5430 PurpleConnection *gc;
5431 PurpleGroup * group = purple_find_group(group_name);
5433 g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node));
5435 buddy = (PurpleBuddy *)node;
5437 SIPE_DEBUG_INFO("sipe_buddy_menu_copy_to_cb: copying %s to %s", buddy->name, group_name);
5438 gc = purple_account_get_connection(buddy->account);
5440 b = purple_find_buddy_in_group(buddy->account, buddy->name, group);
5441 if (!b){
5442 b = purple_blist_add_buddy_clone(group, buddy);
5445 sipe_add_buddy(gc, b, group);
5448 static void
5449 sipe_buddy_menu_chat_new_cb(PurpleBuddy *buddy)
5451 struct sipe_core_private *sipe_private = PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE;
5453 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_new_cb: buddy->name=%s", buddy->name);
5455 /* 2007+ conference */
5456 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007))
5458 sipe_conf_add(sipe_private, buddy->name);
5460 else /* 2005- multiparty chat */
5462 gchar *self = sip_uri_self(sipe_private);
5463 struct sip_session *session;
5465 session = sipe_session_add_chat(sipe_private,
5466 NULL,
5467 TRUE,
5468 self);
5469 session->chat_session->backend = sipe_backend_chat_create(SIPE_CORE_PUBLIC,
5470 session->chat_session,
5471 session->chat_session->title,
5472 self);
5473 g_free(self);
5475 sipe_im_invite(sipe_private, session, buddy->name, NULL, NULL, NULL, FALSE);
5480 * For 2007+ conference only.
5482 static void
5483 sipe_buddy_menu_chat_make_leader_cb(PurpleBuddy *buddy,
5484 struct sipe_chat_session *chat_session)
5486 struct sipe_core_private *sipe_private = PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE;
5487 struct sip_session *session;
5489 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_make_leader_cb: buddy->name=%s", buddy->name);
5490 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_make_leader_cb: chat_title=%s", chat_session->title);
5492 session = sipe_session_find_chat(sipe_private, chat_session);
5494 sipe_conf_modify_user_role(sipe_private, session, buddy->name);
5498 * For 2007+ conference only.
5500 static void
5501 sipe_buddy_menu_chat_remove_cb(PurpleBuddy *buddy,
5502 struct sipe_chat_session *chat_session)
5504 struct sipe_core_private *sipe_private = PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE;
5505 struct sip_session *session;
5507 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_remove_cb: buddy->name=%s", buddy->name);
5508 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_remove_cb: chat_title=%s", chat_session->title);
5510 session = sipe_session_find_chat(sipe_private, chat_session);
5512 sipe_conf_delete_user(sipe_private, session, buddy->name);
5515 static void
5516 sipe_buddy_menu_chat_invite_cb(PurpleBuddy *buddy,
5517 struct sipe_chat_session *chat_session)
5519 struct sipe_core_private *sipe_private = PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE;
5521 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_invite_cb: buddy->name=%s", buddy->name);
5522 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_invite_cb: chat_title=%s", chat_session->title);
5524 sipe_core_chat_invite(SIPE_CORE_PUBLIC, chat_session, buddy->name);
5527 static void
5528 sipe_buddy_menu_make_call_cb(PurpleBuddy *buddy, const char *phone)
5530 struct sipe_core_private *sipe_private = PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE;
5532 SIPE_DEBUG_INFO("sipe_buddy_menu_make_call_cb: buddy->name=%s", buddy->name);
5533 if (phone) {
5534 char *tel_uri = sip_to_tel_uri(phone);
5536 SIPE_DEBUG_INFO("sipe_buddy_menu_make_call_cb: going to call number: %s", tel_uri ? tel_uri : "");
5537 sip_csta_make_call(sipe_private, tel_uri);
5539 g_free(tel_uri);
5543 static void
5544 sipe_buddy_menu_access_level_help_cb(PurpleBuddy *buddy)
5546 /** Translators: replace with URL to localized page
5547 * If it doesn't exist copy the original URL */
5548 purple_notify_uri(buddy->account->gc, _("https://sourceforge.net/apps/mediawiki/sipe/index.php?title=Access_Levels"));
5551 static void
5552 sipe_buddy_menu_send_email_cb(PurpleBuddy *buddy)
5554 const gchar *email;
5555 SIPE_DEBUG_INFO("sipe_buddy_menu_send_email_cb: buddy->name=%s", buddy->name);
5557 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
5558 if (email)
5560 char *command_line = g_strdup_printf(
5561 #ifdef _WIN32
5562 "cmd /c start"
5563 #else
5564 "xdg-email"
5565 #endif
5566 " mailto:%s", email);
5567 SIPE_DEBUG_INFO("sipe_buddy_menu_send_email_cb: going to call email client: %s", command_line);
5569 g_spawn_command_line_async(command_line, NULL);
5570 g_free(command_line);
5572 else
5574 SIPE_DEBUG_INFO("sipe_buddy_menu_send_email_cb: no email address stored for buddy=%s", buddy->name);
5578 static void
5579 sipe_buddy_menu_access_level_cb(PurpleBuddy *buddy,
5580 struct sipe_container *container)
5582 struct sipe_core_private *sipe_private = PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE;
5583 struct sipe_container_member *member;
5585 if (!container || !container->members) return;
5587 member = ((struct sipe_container_member *)container->members->data);
5589 if (!member->type) return;
5591 SIPE_DEBUG_INFO("sipe_buddy_menu_access_level_cb: container->id=%d, member->type=%s, member->value=%s",
5592 container->id, member->type, member->value ? member->value : "");
5594 sipe_change_access_level(sipe_private, container->id, member->type, member->value);
5597 static GList *
5598 sipe_get_access_control_menu(struct sipe_core_private *sipe_private,
5599 const char* uri);
5602 * A menu which appear when right-clicking on buddy in contact list.
5604 GList *
5605 sipe_buddy_menu(PurpleBuddy *buddy)
5607 PurpleBlistNode *g_node;
5608 PurpleGroup *gr_parent;
5609 PurpleMenuAction *act;
5610 GList *menu = NULL;
5611 GList *menu_groups = NULL;
5612 struct sipe_core_private *sipe_private = PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE;
5613 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5614 const char *email;
5615 gchar *self = sip_uri_self(sipe_private);
5617 SIPE_SESSION_FOREACH {
5618 if (!sipe_strcase_equal(self, buddy->name) && session->chat_session)
5620 struct sipe_chat_session *chat_session = session->chat_session;
5621 gboolean is_conf = (chat_session->type == SIPE_CHAT_TYPE_CONFERENCE);
5623 if (sipe_backend_chat_find(chat_session->backend, buddy->name))
5625 gboolean conf_op = sipe_backend_chat_is_operator(chat_session->backend, self);
5627 if (is_conf
5628 && !sipe_backend_chat_is_operator(chat_session->backend, buddy->name) /* Not conf OP */
5629 && conf_op) /* We are a conf OP */
5631 gchar *label = g_strdup_printf(_("Make leader of '%s'"),
5632 chat_session->title);
5633 act = purple_menu_action_new(label,
5634 PURPLE_CALLBACK(sipe_buddy_menu_chat_make_leader_cb),
5635 chat_session, NULL);
5636 g_free(label);
5637 menu = g_list_prepend(menu, act);
5640 if (is_conf
5641 && conf_op) /* We are a conf OP */
5643 gchar *label = g_strdup_printf(_("Remove from '%s'"),
5644 chat_session->title);
5645 act = purple_menu_action_new(label,
5646 PURPLE_CALLBACK(sipe_buddy_menu_chat_remove_cb),
5647 chat_session, NULL);
5648 g_free(label);
5649 menu = g_list_prepend(menu, act);
5652 else
5654 if (!is_conf
5655 || (is_conf && !session->locked))
5657 gchar *label = g_strdup_printf(_("Invite to '%s'"),
5658 chat_session->title);
5659 act = purple_menu_action_new(label,
5660 PURPLE_CALLBACK(sipe_buddy_menu_chat_invite_cb),
5661 chat_session, NULL);
5662 g_free(label);
5663 menu = g_list_prepend(menu, act);
5667 } SIPE_SESSION_FOREACH_END;
5669 act = purple_menu_action_new(_("New chat"),
5670 PURPLE_CALLBACK(sipe_buddy_menu_chat_new_cb),
5671 NULL, NULL);
5672 menu = g_list_prepend(menu, act);
5674 if (sip->csta && !sip->csta->line_status) {
5675 const char *phone;
5676 const char *phone_disp_str;
5677 gchar *tmp = NULL;
5678 /* work phone */
5679 phone = purple_blist_node_get_string(&buddy->node, PHONE_PROP);
5680 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_DISPLAY_PROP);
5681 if (phone) {
5682 gchar *label = g_strdup_printf(_("Work %s"),
5683 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
5684 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
5685 g_free(tmp);
5686 tmp = NULL;
5687 g_free(label);
5688 menu = g_list_prepend(menu, act);
5691 /* mobile phone */
5692 phone = purple_blist_node_get_string(&buddy->node, PHONE_MOBILE_PROP);
5693 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_MOBILE_DISPLAY_PROP);
5694 if (phone) {
5695 gchar *label = g_strdup_printf(_("Mobile %s"),
5696 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
5697 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
5698 g_free(tmp);
5699 tmp = NULL;
5700 g_free(label);
5701 menu = g_list_prepend(menu, act);
5704 /* home phone */
5705 phone = purple_blist_node_get_string(&buddy->node, PHONE_HOME_PROP);
5706 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_HOME_DISPLAY_PROP);
5707 if (phone) {
5708 gchar *label = g_strdup_printf(_("Home %s"),
5709 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
5710 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
5711 g_free(tmp);
5712 tmp = NULL;
5713 g_free(label);
5714 menu = g_list_prepend(menu, act);
5717 /* other phone */
5718 phone = purple_blist_node_get_string(&buddy->node, PHONE_OTHER_PROP);
5719 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_OTHER_DISPLAY_PROP);
5720 if (phone) {
5721 gchar *label = g_strdup_printf(_("Other %s"),
5722 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
5723 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
5724 g_free(tmp);
5725 tmp = NULL;
5726 g_free(label);
5727 menu = g_list_prepend(menu, act);
5730 /* custom1 phone */
5731 phone = purple_blist_node_get_string(&buddy->node, PHONE_CUSTOM1_PROP);
5732 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_CUSTOM1_DISPLAY_PROP);
5733 if (phone) {
5734 gchar *label = g_strdup_printf(_("Custom1 %s"),
5735 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
5736 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
5737 g_free(tmp);
5738 tmp = NULL;
5739 g_free(label);
5740 menu = g_list_prepend(menu, act);
5744 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
5745 if (email) {
5746 act = purple_menu_action_new(_("Send email..."),
5747 PURPLE_CALLBACK(sipe_buddy_menu_send_email_cb),
5748 NULL, NULL);
5749 menu = g_list_prepend(menu, act);
5752 /* Access Level */
5753 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
5754 GList *menu_access_levels = sipe_get_access_control_menu(sipe_private, buddy->name);
5756 act = purple_menu_action_new(_("Access level"),
5757 NULL,
5758 NULL, menu_access_levels);
5759 menu = g_list_prepend(menu, act);
5762 /* Copy to */
5763 gr_parent = purple_buddy_get_group(buddy);
5764 for (g_node = purple_blist_get_root(); g_node; g_node = g_node->next) {
5765 PurpleGroup *group;
5767 if (g_node->type != PURPLE_BLIST_GROUP_NODE)
5768 continue;
5770 group = (PurpleGroup *)g_node;
5771 if (group == gr_parent)
5772 continue;
5774 if (purple_find_buddy_in_group(buddy->account, buddy->name, group))
5775 continue;
5777 act = purple_menu_action_new(purple_group_get_name(group),
5778 PURPLE_CALLBACK(sipe_buddy_menu_copy_to_cb),
5779 group->name, NULL);
5780 menu_groups = g_list_prepend(menu_groups, act);
5782 menu_groups = g_list_reverse(menu_groups);
5784 act = purple_menu_action_new(_("Copy to"),
5785 NULL,
5786 NULL, menu_groups);
5787 menu = g_list_prepend(menu, act);
5789 menu = g_list_reverse(menu);
5791 g_free(self);
5792 return menu;
5795 static void
5796 sipe_ask_access_domain_cb(PurpleConnection *gc, PurpleRequestFields *fields)
5798 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
5799 const char *domain = purple_request_fields_get_string(fields, "access_domain");
5800 int index = purple_request_fields_get_choice(fields, "container_id");
5801 /* move Blocked first */
5802 int i = (index == 4) ? 0 : index + 1;
5803 int container_id = containers[i];
5805 SIPE_DEBUG_INFO("sipe_ask_access_domain_cb: domain=%s, container_id=(%d)%d", domain ? domain : "", index, container_id);
5807 sipe_change_access_level(sipe_private, container_id, "domain", domain);
5810 static void
5811 sipe_ask_access_domain(struct sipe_core_private *sipe_private)
5813 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5814 PurpleAccount *account = sip->account;
5815 PurpleConnection *gc = sip->gc;
5816 PurpleRequestFields *fields;
5817 PurpleRequestFieldGroup *g;
5818 PurpleRequestField *f;
5820 fields = purple_request_fields_new();
5822 g = purple_request_field_group_new(NULL);
5823 f = purple_request_field_string_new("access_domain", _("Domain"), "partner-company.com", FALSE);
5824 purple_request_field_set_required(f, TRUE);
5825 purple_request_field_group_add_field(g, f);
5827 f = purple_request_field_choice_new("container_id", _("Access level"), 0);
5828 purple_request_field_choice_add(f, _("Personal")); /* index 0 */
5829 purple_request_field_choice_add(f, _("Team"));
5830 purple_request_field_choice_add(f, _("Company"));
5831 purple_request_field_choice_add(f, _("Public"));
5832 purple_request_field_choice_add(f, _("Blocked")); /* index 4 */
5833 purple_request_field_choice_set_default_value(f, 3); /* index */
5834 purple_request_field_set_required(f, TRUE);
5835 purple_request_field_group_add_field(g, f);
5837 purple_request_fields_add_group(fields, g);
5839 purple_request_fields(gc, _("Add new domain"),
5840 _("Add new domain"), NULL, fields,
5841 _("Add"), G_CALLBACK(sipe_ask_access_domain_cb),
5842 _("Cancel"), NULL,
5843 account, NULL, NULL, gc);
5846 static void
5847 sipe_buddy_menu_access_level_add_domain_cb(PurpleBuddy *buddy)
5849 sipe_ask_access_domain(PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE);
5853 * Workaround for missing libpurple API to release resources allocated
5854 * during blist_node_menu() callback. See also:
5856 * <http://developer.pidgin.im/ticket/12597>
5858 * We remember all memory blocks in a list and deallocate them when
5860 * - the next time we enter the callback, or
5861 * - the account is disconnected
5863 * That means that after the buddy menu has been closed we have unused
5864 * resources but at least we don't leak them anymore...
5866 static void
5867 sipe_blist_menu_free_containers(struct sipe_core_private *sipe_private)
5869 GSList *entry = sipe_private->blist_menu_containers;
5870 while (entry) {
5871 free_container(entry->data);
5872 entry = entry->next;
5874 g_slist_free(sipe_private->blist_menu_containers);
5875 sipe_private->blist_menu_containers = NULL;
5878 static void
5879 sipe_blist_menu_remember_container(struct sipe_core_private *sipe_private,
5880 struct sipe_container *container)
5882 sipe_private->blist_menu_containers = g_slist_prepend(sipe_private->blist_menu_containers,
5883 container);
5886 static GList *
5887 sipe_get_access_levels_menu(struct sipe_core_private *sipe_private,
5888 const char* member_type,
5889 const char* member_value,
5890 const gboolean extra_menu)
5892 GList *menu_access_levels = NULL;
5893 unsigned int i;
5894 char *menu_name;
5895 PurpleMenuAction *act;
5896 struct sipe_container *container;
5897 struct sipe_container_member *member;
5898 gboolean is_group_access = FALSE;
5899 int container_id = sipe_find_access_level(sipe_private, member_type, member_value, &is_group_access);
5901 for (i = 1; i <= CONTAINERS_LEN; i++) {
5902 /* to put Blocked level last in menu list.
5903 * Blocked should remaim in the first place in the containers[] array.
5905 unsigned int j = (i == CONTAINERS_LEN) ? 0 : i;
5906 const char *acc_level_name = sipe_get_access_level_name(containers[j]);
5908 container = g_new0(struct sipe_container, 1);
5909 member = g_new0(struct sipe_container_member, 1);
5910 container->id = containers[j];
5911 container->members = g_slist_append(container->members, member);
5912 member->type = g_strdup(member_type);
5913 member->value = g_strdup(member_value);
5915 /* libpurple memory leak workaround */
5916 sipe_blist_menu_remember_container(sipe_private, container);
5918 /* current container/access level */
5919 if (((int)containers[j]) == container_id) {
5920 menu_name = is_group_access ?
5921 g_strdup_printf(INDENT_MARKED_INHERITED_FMT, acc_level_name) :
5922 g_strdup_printf(INDENT_MARKED_FMT, acc_level_name);
5923 } else {
5924 menu_name = g_strdup_printf(INDENT_FMT, acc_level_name);
5927 act = purple_menu_action_new(menu_name,
5928 PURPLE_CALLBACK(sipe_buddy_menu_access_level_cb),
5929 container, NULL);
5930 g_free(menu_name);
5931 menu_access_levels = g_list_prepend(menu_access_levels, act);
5934 if (extra_menu && (container_id >= 0)) {
5935 /* separator */
5936 act = purple_menu_action_new(" --------------", NULL, NULL, NULL);
5937 menu_access_levels = g_list_prepend(menu_access_levels, act);
5939 if (!is_group_access) {
5940 container = g_new0(struct sipe_container, 1);
5941 member = g_new0(struct sipe_container_member, 1);
5942 container->id = -1;
5943 container->members = g_slist_append(container->members, member);
5944 member->type = g_strdup(member_type);
5945 member->value = g_strdup(member_value);
5947 /* libpurple memory leak workaround */
5948 sipe_blist_menu_remember_container(sipe_private, container);
5950 /* Translators: remove (clear) previously assigned access level */
5951 menu_name = g_strdup_printf(INDENT_FMT, _("Unspecify"));
5952 act = purple_menu_action_new(menu_name,
5953 PURPLE_CALLBACK(sipe_buddy_menu_access_level_cb),
5954 container, NULL);
5955 g_free(menu_name);
5956 menu_access_levels = g_list_prepend(menu_access_levels, act);
5960 menu_access_levels = g_list_reverse(menu_access_levels);
5961 return menu_access_levels;
5964 static GList *
5965 sipe_get_access_groups_menu(struct sipe_core_private *sipe_private)
5967 GList *menu_access_groups = NULL;
5968 PurpleMenuAction *act;
5969 GSList *access_domains = NULL;
5970 GSList *entry;
5971 char *menu_name;
5972 char *domain;
5974 act = purple_menu_action_new(_("People in my company"),
5975 NULL,
5976 NULL, sipe_get_access_levels_menu(sipe_private, "sameEnterprise", NULL, FALSE));
5977 menu_access_groups = g_list_prepend(menu_access_groups, act);
5979 /* this is original name, don't edit */
5980 act = purple_menu_action_new(_("People in domains connected with my company"),
5981 NULL,
5982 NULL, sipe_get_access_levels_menu(sipe_private, "federated", NULL, FALSE));
5983 menu_access_groups = g_list_prepend(menu_access_groups, act);
5985 act = purple_menu_action_new(_("People in public domains"),
5986 NULL,
5987 NULL, sipe_get_access_levels_menu(sipe_private, "publicCloud", NULL, TRUE));
5988 menu_access_groups = g_list_prepend(menu_access_groups, act);
5990 access_domains = sipe_get_access_domains(sipe_private);
5991 entry = access_domains;
5992 while (entry) {
5993 domain = entry->data;
5995 menu_name = g_strdup_printf(_("People at %s"), domain);
5996 act = purple_menu_action_new(menu_name,
5997 NULL,
5998 NULL, sipe_get_access_levels_menu(sipe_private, "domain", g_strdup(domain), TRUE));
5999 menu_access_groups = g_list_prepend(menu_access_groups, act);
6000 g_free(menu_name);
6002 entry = entry->next;
6005 /* separator */
6006 /* People in domains connected with my company */
6007 act = purple_menu_action_new("-------------------------------------------", NULL, NULL, NULL);
6008 menu_access_groups = g_list_prepend(menu_access_groups, act);
6010 act = purple_menu_action_new(_("Add new domain..."),
6011 PURPLE_CALLBACK(sipe_buddy_menu_access_level_add_domain_cb),
6012 NULL, NULL);
6013 menu_access_groups = g_list_prepend(menu_access_groups, act);
6015 menu_access_groups = g_list_reverse(menu_access_groups);
6017 return menu_access_groups;
6020 static GList *
6021 sipe_get_access_control_menu(struct sipe_core_private *sipe_private,
6022 const char* uri)
6024 GList *menu_access_levels = NULL;
6025 GList *menu_access_groups = NULL;
6026 char *menu_name;
6027 PurpleMenuAction *act;
6029 /* libpurple memory leak workaround */
6030 sipe_blist_menu_free_containers(sipe_private);
6032 menu_access_levels = sipe_get_access_levels_menu(sipe_private, "user", sipe_get_no_sip_uri(uri), TRUE);
6034 menu_access_groups = sipe_get_access_groups_menu(sipe_private);
6036 menu_name = g_strdup_printf(INDENT_FMT, _("Access groups"));
6037 act = purple_menu_action_new(menu_name,
6038 NULL,
6039 NULL, menu_access_groups);
6040 g_free(menu_name);
6041 menu_access_levels = g_list_append(menu_access_levels, act);
6043 menu_name = g_strdup_printf(INDENT_FMT, _("Online help..."));
6044 act = purple_menu_action_new(menu_name,
6045 PURPLE_CALLBACK(sipe_buddy_menu_access_level_help_cb),
6046 NULL, NULL);
6047 g_free(menu_name);
6048 menu_access_levels = g_list_append(menu_access_levels, act);
6050 return menu_access_levels;
6053 static gboolean
6054 process_get_info_response(struct sipe_core_private *sipe_private,
6055 struct sipmsg *msg, struct transaction *trans)
6057 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
6058 char *uri = trans->payload->data;
6060 PurpleNotifyUserInfo *info;
6061 PurpleBuddy *pbuddy = NULL;
6062 struct sipe_buddy *sbuddy;
6063 const char *alias = NULL;
6064 char *device_name = NULL;
6065 char *server_alias = NULL;
6066 char *phone_number = NULL;
6067 char *email = NULL;
6068 const char *site;
6069 char *first_name = NULL;
6070 char *last_name = NULL;
6072 if (!sip) return FALSE;
6074 SIPE_DEBUG_INFO("Fetching %s's user info for %s", uri, sipe_private->username);
6076 pbuddy = purple_find_buddy((PurpleAccount *)sip->account, uri);
6077 alias = purple_buddy_get_local_alias(pbuddy);
6079 //will query buddy UA's capabilities and send answer to log
6080 sipe_options_request(sipe_private, uri);
6082 sbuddy = g_hash_table_lookup(sipe_private->buddies, uri);
6083 if (sbuddy) {
6084 device_name = sbuddy->device_name ? g_strdup(sbuddy->device_name) : NULL;
6087 info = purple_notify_user_info_new();
6089 if (msg->response != 200) {
6090 SIPE_DEBUG_INFO("process_get_info_response: SERVICE response is %d", msg->response);
6091 } else {
6092 sipe_xml *searchResults;
6093 const sipe_xml *mrow;
6095 SIPE_DEBUG_INFO("process_get_info_response: body:\n%s", msg->body ? msg->body : "");
6096 searchResults = sipe_xml_parse(msg->body, msg->bodylen);
6097 if (!searchResults) {
6098 SIPE_DEBUG_INFO_NOFORMAT("process_get_info_response: no parseable searchResults");
6099 } else if ((mrow = sipe_xml_child(searchResults, "Body/Array/row"))) {
6100 const char *value;
6101 server_alias = g_strdup(sipe_xml_attribute(mrow, "displayName"));
6102 email = g_strdup(sipe_xml_attribute(mrow, "email"));
6103 phone_number = g_strdup(sipe_xml_attribute(mrow, "phone"));
6105 /* For 2007 system we will take this from ContactCard -
6106 * it has cleaner tel: URIs at least
6108 if (!SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
6109 char *tel_uri = sip_to_tel_uri(phone_number);
6110 /* trims its parameters, so call first */
6111 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_DISPLAY_NAME, server_alias);
6112 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_EMAIL, email);
6113 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_WORK_PHONE, tel_uri);
6114 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_WORK_PHONE_DISPLAY, phone_number);
6115 g_free(tel_uri);
6118 if (server_alias && strlen(server_alias) > 0) {
6119 purple_notify_user_info_add_pair(info, _("Display name"), server_alias);
6121 if ((value = sipe_xml_attribute(mrow, "title")) && strlen(value) > 0) {
6122 purple_notify_user_info_add_pair(info, _("Job title"), value);
6124 if ((value = sipe_xml_attribute(mrow, "office")) && strlen(value) > 0) {
6125 purple_notify_user_info_add_pair(info, _("Office"), value);
6127 if (phone_number && strlen(phone_number) > 0) {
6128 purple_notify_user_info_add_pair(info, _("Business phone"), phone_number);
6130 if ((value = sipe_xml_attribute(mrow, "company")) && strlen(value) > 0) {
6131 purple_notify_user_info_add_pair(info, _("Company"), value);
6133 if ((value = sipe_xml_attribute(mrow, "city")) && strlen(value) > 0) {
6134 purple_notify_user_info_add_pair(info, _("City"), value);
6136 if ((value = sipe_xml_attribute(mrow, "state")) && strlen(value) > 0) {
6137 purple_notify_user_info_add_pair(info, _("State"), value);
6139 if ((value = sipe_xml_attribute(mrow, "country")) && strlen(value) > 0) {
6140 purple_notify_user_info_add_pair(info, _("Country"), value);
6142 if (email && strlen(email) > 0) {
6143 purple_notify_user_info_add_pair(info, _("Email address"), email);
6147 sipe_xml_free(searchResults);
6150 purple_notify_user_info_add_section_break(info);
6152 if (is_empty(server_alias)) {
6153 g_free(server_alias);
6154 server_alias = g_strdup(purple_buddy_get_server_alias(pbuddy));
6155 if (server_alias) {
6156 purple_notify_user_info_add_pair(info, _("Display name"), server_alias);
6160 /* present alias if it differs from server alias */
6161 if (alias && !sipe_strequal(alias, server_alias))
6163 purple_notify_user_info_add_pair(info, _("Alias"), alias);
6166 if (is_empty(email)) {
6167 g_free(email);
6168 email = g_strdup(purple_blist_node_get_string(&pbuddy->node, EMAIL_PROP));
6169 if (email) {
6170 purple_notify_user_info_add_pair(info, _("Email address"), email);
6174 site = purple_blist_node_get_string(&pbuddy->node, SITE_PROP);
6175 if (site) {
6176 purple_notify_user_info_add_pair(info, _("Site"), site);
6179 sipe_get_first_last_names(sipe_private, uri, &first_name, &last_name);
6180 if (first_name && last_name) {
6181 char *link = g_strconcat("http://www.linkedin.com/pub/dir/", first_name, "/", last_name, NULL);
6183 purple_notify_user_info_add_pair(info, _("Find on LinkedIn"), link);
6184 g_free(link);
6186 g_free(first_name);
6187 g_free(last_name);
6189 if (device_name) {
6190 purple_notify_user_info_add_pair(info, _("Device"), device_name);
6193 /* show a buddy's user info in a nice dialog box */
6194 purple_notify_userinfo(sip->gc, /* connection the buddy info came through */
6195 uri, /* buddy's URI */
6196 info, /* body */
6197 NULL, /* callback called when dialog closed */
6198 NULL); /* userdata for callback */
6200 g_free(phone_number);
6201 g_free(server_alias);
6202 g_free(email);
6203 g_free(device_name);
6205 return TRUE;
6209 * AD search first, LDAP based
6211 void sipe_get_info(PurpleConnection *gc, const char *username)
6213 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
6214 gchar *domain_uri = sip_uri_from_name(sipe_private->public.sip_domain);
6215 char *row = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, "msRTCSIP-PrimaryUserAddress", username);
6216 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 1, row);
6217 struct transaction_payload *payload = g_new0(struct transaction_payload, 1);
6219 payload->destroy = g_free;
6220 payload->data = g_strdup(username);
6222 SIPE_DEBUG_INFO("sipe_get_contact_data: body:\n%s", body ? body : "");
6223 send_soap_request_with_cb(sipe_private, domain_uri, body,
6224 process_get_info_response, payload);
6225 g_free(domain_uri);
6226 g_free(body);
6227 g_free(row);
6231 Local Variables:
6232 mode: c
6233 c-file-style: "bsd"
6234 indent-tabs-mode: t
6235 tab-width: 8
6236 End: