6 * Copyright (C) 2011 SIPE Project <http://sipe.sourceforge.net/>
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24 * OCS2005 specific code
32 #include "sipe-common.h"
33 #include "http-conn.h" /* sipe-cal.h requires this */
35 #include "sipe-backend.h"
36 #include "sipe-buddy.h"
38 #include "sipe-core.h"
39 #include "sipe-core-private.h"
41 #include "sipe-ocs2005.h"
42 #include "sipe-ocs2007.h"
43 #include "sipe-schedule.h"
44 #include "sipe-status.h"
45 #include "sipe-utils.h"
50 * 2005-style Activity and Availability.
54 * @param activity 2005 aggregated activity. Ex.: 600
55 * @param availablity 2005 aggregated availablity. Ex.: 300
57 * The values define the starting point of a range
59 #define SIPE_OCS2005_ACTIVITY_UNKNOWN 0
60 #define SIPE_OCS2005_ACTIVITY_AWAY 100
61 #define SIPE_OCS2005_ACTIVITY_LUNCH 150
62 #define SIPE_OCS2005_ACTIVITY_IDLE 200
63 #define SIPE_OCS2005_ACTIVITY_BRB 300
64 #define SIPE_OCS2005_ACTIVITY_AVAILABLE 400 /* user is active */
65 #define SIPE_OCS2005_ACTIVITY_ON_PHONE 500 /* user is participating in a communcation session */
66 #define SIPE_OCS2005_ACTIVITY_BUSY 600
67 #define SIPE_OCS2005_ACTIVITY_AWAY2 700
68 #define SIPE_OCS2005_ACTIVITY_AVAILABLE2 800
70 #define SIPE_OCS2005_AVAILABILITY_OFFLINE 0
71 #define SIPE_OCS2005_AVAILABILITY_MAYBE 100
72 #define SIPE_OCS2005_AVAILABILITY_ONLINE 300
73 static guint
sipe_ocs2005_activity_from_status(struct sipe_core_private
*sipe_private
)
75 struct sipe_account_data
*sip
= SIPE_ACCOUNT_DATA_PRIVATE
;
76 const gchar
*status
= sip
->status
;
78 if (sipe_strequal(status
, sipe_backend_activity_to_token(SIPE_ACTIVITY_AWAY
))) {
79 return(SIPE_OCS2005_ACTIVITY_AWAY
);
80 /*} else if (sipe_strequal(status, sipe_backend_activity_to_token(SIPE_ACTIVITY_LUNCH))) {
81 return(SIPE_OCS2005_ACTIVITY_LUNCH); */
82 } else if (sipe_strequal(status
, sipe_backend_activity_to_token(SIPE_ACTIVITY_BRB
))) {
83 return(SIPE_OCS2005_ACTIVITY_BRB
);
84 } else if (sipe_strequal(status
, sipe_backend_activity_to_token(SIPE_ACTIVITY_AVAILABLE
))) {
85 return(SIPE_OCS2005_ACTIVITY_AVAILABLE
);
86 /*} else if (sipe_strequal(status, sipe_backend_activity_to_token(SIPE_ACTIVITY_ON_PHONE))) {
87 return(SIPE_OCS2005_ACTIVITY_ON_PHONE); */
88 } else if (sipe_strequal(status
, sipe_backend_activity_to_token(SIPE_ACTIVITY_BUSY
)) ||
89 sipe_strequal(status
, sipe_backend_activity_to_token(SIPE_ACTIVITY_DND
))) {
90 return(SIPE_OCS2005_ACTIVITY_BUSY
);
91 } else if (sipe_strequal(status
, sipe_backend_activity_to_token(SIPE_ACTIVITY_INVISIBLE
)) ||
92 sipe_strequal(status
, sipe_backend_activity_to_token(SIPE_ACTIVITY_OFFLINE
))) {
93 return(SIPE_OCS2005_ACTIVITY_AWAY
);
95 return(SIPE_OCS2005_ACTIVITY_AVAILABLE
);
99 static guint
sipe_ocs2005_availability_from_status(struct sipe_core_private
*sipe_private
)
101 struct sipe_account_data
*sip
= SIPE_ACCOUNT_DATA_PRIVATE
;
102 const gchar
*status
= sip
->status
;
104 if (sipe_strequal(status
, sipe_backend_activity_to_token(SIPE_ACTIVITY_INVISIBLE
)) ||
105 sipe_strequal(status
, sipe_backend_activity_to_token(SIPE_ACTIVITY_OFFLINE
)))
106 return(SIPE_OCS2005_AVAILABILITY_OFFLINE
);
108 return(SIPE_OCS2005_AVAILABILITY_ONLINE
);
111 const gchar
*sipe_ocs2005_status_from_activity_availability(guint activity
,
116 if (availability
< SIPE_OCS2005_AVAILABILITY_MAYBE
) {
117 type
= SIPE_ACTIVITY_OFFLINE
;
118 } else if (activity
< SIPE_OCS2005_ACTIVITY_LUNCH
) {
119 type
= SIPE_ACTIVITY_AWAY
;
120 } else if (activity
< SIPE_OCS2005_ACTIVITY_IDLE
) {
121 //type = SIPE_ACTIVITY_LUNCH;
122 type
= SIPE_ACTIVITY_AWAY
;
123 } else if (activity
< SIPE_OCS2005_ACTIVITY_BRB
) {
124 //type = SIPE_ACTIVITY_IDLE;
125 type
= SIPE_ACTIVITY_AWAY
;
126 } else if (activity
< SIPE_OCS2005_ACTIVITY_AVAILABLE
) {
127 type
= SIPE_ACTIVITY_BRB
;
128 } else if (activity
< SIPE_OCS2005_ACTIVITY_ON_PHONE
) {
129 type
= SIPE_ACTIVITY_AVAILABLE
;
130 } else if (activity
< SIPE_OCS2005_ACTIVITY_BUSY
) {
131 //type = SIPE_ACTIVITY_ON_PHONE;
132 type
= SIPE_ACTIVITY_BUSY
;
133 } else if (activity
< SIPE_OCS2005_ACTIVITY_AWAY2
) {
134 type
= SIPE_ACTIVITY_BUSY
;
135 } else if (activity
< SIPE_OCS2005_ACTIVITY_AVAILABLE2
) {
136 type
= SIPE_ACTIVITY_AWAY
;
138 type
= SIPE_ACTIVITY_AVAILABLE
;
141 return(sipe_backend_activity_to_token(type
));
144 const gchar
*sipe_ocs2005_activity_description(guint activity
)
146 if ((activity
>= SIPE_OCS2005_ACTIVITY_LUNCH
) &&
147 (activity
< SIPE_OCS2005_ACTIVITY_IDLE
)) {
148 return(sipe_core_activity_description(SIPE_ACTIVITY_LUNCH
));
149 } else if ((activity
>= SIPE_OCS2005_ACTIVITY_IDLE
) &&
150 (activity
< SIPE_OCS2005_ACTIVITY_BRB
)) {
151 return(sipe_core_activity_description(SIPE_ACTIVITY_INACTIVE
));
152 } else if ((activity
>= SIPE_OCS2005_ACTIVITY_ON_PHONE
) &&
153 (activity
< SIPE_OCS2005_ACTIVITY_BUSY
)) {
154 return(sipe_core_activity_description(SIPE_ACTIVITY_ON_PHONE
));
160 void sipe_ocs2005_user_info_has_updated(struct sipe_core_private
*sipe_private
,
161 const sipe_xml
*xn_userinfo
)
163 const sipe_xml
*xn_states
;
165 g_free(sipe_private
->ocs2005_user_states
);
166 sipe_private
->ocs2005_user_states
= NULL
;
167 if ((xn_states
= sipe_xml_child(xn_userinfo
, "states")) != NULL
) {
168 gchar
*orig
= sipe_private
->ocs2005_user_states
= sipe_xml_stringify(xn_states
);
170 /* this is a hack-around to remove added newline after inner element,
171 * state in this case, where it shouldn't be.
172 * After several use of sipe_xml_stringify, amount of added newlines
173 * grows significantly.
176 gchar c
, *stripped
= orig
;
177 while ((c
= *orig
++)) {
178 if ((c
!= '\n') /* && (c != '\r') */) {
186 /* Publish initial state if not yet.
187 * Assuming this happens on initial responce to self subscription
188 * so we've already updated our UserInfo.
190 if (!SIPE_CORE_PRIVATE_FLAG_IS(INITIAL_PUBLISH
)) {
191 sipe_ocs2005_presence_publish(sipe_private
, FALSE
);
193 sipe_cal_delayed_calendar_update(sipe_private
);
197 static gboolean
sipe_is_user_available(struct sipe_core_private
*sipe_private
)
199 struct sipe_account_data
*sip
= SIPE_ACCOUNT_DATA_PRIVATE
;
200 return(sipe_strequal(sip
->status
,
201 sipe_backend_activity_to_token(SIPE_ACTIVITY_AVAILABLE
)));
206 * OCS2005 presence XML messages
208 * Calendar publication entry
210 * @param legacy_dn (%s) Ex.: /o=EXCHANGE/ou=BTUK02/cn=Recipients/cn=AHHBTT
211 * @param fb_start_time_str (%s) Ex.: 2009-12-06T17:15:00Z
212 * @param free_busy_base64 (%s) Ex.: AAAAAAAAAAAAAAAAA......
214 #define SIPE_SOAP_SET_PRESENCE_CALENDAR \
215 "<calendarInfo xmlns=\"http://schemas.microsoft.com/2002/09/sip/presence\" mailboxId=\"%s\" startTime=\"%s\" granularity=\"PT15M\">%s</calendarInfo>"
218 * Note publication entry
220 * @param note (%s) Ex.: Working from home
222 #define SIPE_SOAP_SET_PRESENCE_NOTE_XML "<note>%s</note>"
225 * Note's OOF publication entry
227 #define SIPE_SOAP_SET_PRESENCE_OOF_XML "<oof></oof>"
230 * States publication entry for User State
232 * @param avail (%d) Availability 2007-style. Ex.: 9500
233 * @param since_time_str (%s) Ex.: 2010-01-13T10:30:05Z
234 * @param device_id (%s) epid. Ex.: 4c77e6ec72
235 * @param activity_token (%s) Ex.: do-not-disturb
237 #define SIPE_SOAP_SET_PRESENCE_STATES \
239 "<state avail=\"%d\" since=\"%s\" validWith=\"any-device\" deviceId=\"%s\" set=\"manual\" xsi:type=\"userState\">%s</state>"\
243 * Presentity publication entry.
245 * @param uri (%s) SIP URI without 'sip:' prefix. Ex.: fox@atlanta.local
246 * @param aggr_availability (%d) Ex.: 300
247 * @param aggr_activity (%d) Ex.: 600
248 * @param host_name (%s) Uppercased. Ex.: ATLANTA
249 * @param note_xml_str (%s) XML string as SIPE_SOAP_SET_PRESENCE_NOTE_XML
250 * @param oof_xml_str (%s) XML string as SIPE_SOAP_SET_PRESENCE_OOF_XML
251 * @param states_xml_str (%s) XML string as SIPE_SOAP_SET_PRESENCE_STATES
252 * @param calendar_info_xml_str (%s) XML string as SIPE_SOAP_SET_PRESENCE_CALENDAR
253 * @param device_id (%s) epid. Ex.: 4c77e6ec72
254 * @param since_time_str (%s) Ex.: 2010-01-13T10:30:05Z
255 * @param since_time_str (%s) Ex.: 2010-01-13T10:30:05Z
256 * @param user_input (%s) active, idle
258 #define SIPE_SOAP_SET_PRESENCE \
260 " xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\"" \
261 " xmlns:m=\"http://schemas.microsoft.com/winrtc/2002/11/sip\"" \
265 "<m:presentity xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" m:uri=\"sip:%s\">"\
266 "<m:availability m:aggregate=\"%d\"/>"\
267 "<m:activity m:aggregate=\"%d\"/>"\
268 "<deviceName xmlns=\"http://schemas.microsoft.com/2002/09/sip/presence\" name=\"%s\"/>"\
269 "<rtc:devicedata xmlns:rtc=\"http://schemas.microsoft.com/winrtc/2002/11/sip\" namespace=\"rtcService\">"\
270 "<![CDATA[<caps><renders_gif/><renders_isf/></caps>]]></rtc:devicedata>"\
271 "<userInfo xmlns=\"http://schemas.microsoft.com/2002/09/sip/presence\">"\
276 "<device xmlns=\"http://schemas.microsoft.com/2002/09/sip/presence\" deviceId=\"%s\" since=\"%s\" >"\
277 "<userInput since=\"%s\" >%s</userInput>"\
284 static void send_presence_soap(struct sipe_core_private
*sipe_private
,
285 gboolean do_publish_calendar
,
286 gboolean do_reset_status
)
288 struct sipe_account_data
*sip
= SIPE_ACCOUNT_DATA_PRIVATE
;
289 struct sipe_calendar
* cal
= sipe_private
->calendar
;
293 gchar
*res_note
= NULL
;
294 gchar
*res_oof
= NULL
;
295 const gchar
*note_pub
= NULL
;
296 gchar
*states
= NULL
;
297 gchar
*calendar_data
= NULL
;
298 gchar
*epid
= get_epid(sipe_private
);
299 gchar
*from
= sip_uri_self(sipe_private
);
300 time_t now
= time(NULL
);
301 gchar
*since_time_str
= sipe_utils_time_to_str(now
);
302 const gchar
*oof_note
= cal
? sipe_ews_get_oof_note(cal
) : NULL
;
303 const char *user_input
;
304 gboolean pub_oof
= cal
&& oof_note
&& (!sip
->note
|| cal
->updated
> sip
->note_since
);
306 if (oof_note
&& sip
->note
) {
307 SIPE_DEBUG_INFO("cal->oof_start : %s", asctime(localtime(&(cal
->oof_start
))));
308 SIPE_DEBUG_INFO("sip->note_since : %s", asctime(localtime(&(sip
->note_since
))));
311 SIPE_DEBUG_INFO("sip->note : %s", sip
->note
? sip
->note
: "");
313 if (!SIPE_CORE_PRIVATE_FLAG_IS(INITIAL_PUBLISH
) ||
315 sipe_status_set_activity(sipe_private
, SIPE_ACTIVITY_AVAILABLE
);
320 res_oof
= SIPE_SOAP_SET_PRESENCE_OOF_XML
;
321 cal
->published
= TRUE
;
322 } else if (sip
->note
) {
323 if (SIPE_CORE_PRIVATE_FLAG_IS(OOF_NOTE
) &&
324 !oof_note
) { /* stale OOF note, as it's not present in cal already */
327 SIPE_CORE_PRIVATE_FLAG_UNSET(OOF_NOTE
);
330 note_pub
= sip
->note
;
331 res_oof
= SIPE_CORE_PRIVATE_FLAG_IS(OOF_NOTE
) ? SIPE_SOAP_SET_PRESENCE_OOF_XML
: "";
337 /* to protocol internal plain text format */
338 tmp
= sipe_backend_markup_strip_html(note_pub
);
339 res_note
= g_markup_printf_escaped(SIPE_SOAP_SET_PRESENCE_NOTE_XML
, tmp
);
344 if (!do_reset_status
) {
345 if (sipe_status_changed_by_user(sipe_private
) &&
346 !do_publish_calendar
&&
347 SIPE_CORE_PRIVATE_FLAG_IS(INITIAL_PUBLISH
)) {
348 const gchar
*activity_token
;
349 int avail_2007
= sipe_ocs2007_availability_from_status(sip
->status
,
352 states
= g_strdup_printf(SIPE_SOAP_SET_PRESENCE_STATES
,
358 else /* preserve existing publication */
360 if (sipe_private
->ocs2005_user_states
) {
361 states
= g_strdup(sipe_private
->ocs2005_user_states
);
365 /* do nothing - then User state will be erased */
367 SIPE_CORE_PRIVATE_FLAG_SET(INITIAL_PUBLISH
);
370 if (cal
&& (!is_empty(cal
->legacy_dn
) || !is_empty(cal
->email
)) && cal
->fb_start
&& !is_empty(cal
->free_busy
))
372 char *fb_start_str
= sipe_utils_time_to_str(cal
->fb_start
);
373 char *free_busy_base64
= sipe_cal_get_freebusy_base64(cal
->free_busy
);
374 calendar_data
= g_strdup_printf(SIPE_SOAP_SET_PRESENCE_CALENDAR
,
375 !is_empty(cal
->legacy_dn
) ? cal
->legacy_dn
: cal
->email
,
378 g_free(fb_start_str
);
379 g_free(free_busy_base64
);
382 user_input
= (sipe_status_changed_by_user(sipe_private
) ||
383 sipe_is_user_available(sipe_private
)) ?
387 body
= g_strdup_printf(SIPE_SOAP_SET_PRESENCE
,
388 sipe_private
->username
,
389 sipe_ocs2005_availability_from_status(sipe_private
),
390 sipe_ocs2005_activity_from_status(sipe_private
),
391 (tmp
= g_ascii_strup(g_get_host_name(), -1)),
392 res_note
? res_note
: "",
393 res_oof
? res_oof
: "",
394 states
? states
: "",
395 calendar_data
? calendar_data
: "",
404 g_free(calendar_data
);
405 g_free(since_time_str
);
408 sip_soap_raw_request_cb(sipe_private
, from
, body
, NULL
, NULL
);
413 void sipe_ocs2005_presence_publish(struct sipe_core_private
*sipe_private
,
414 gboolean do_publish_calendar
)
416 return send_presence_soap(sipe_private
, do_publish_calendar
, FALSE
);
419 void sipe_ocs2005_reset_status(struct sipe_core_private
*sipe_private
)
421 return send_presence_soap(sipe_private
, FALSE
, TRUE
);
424 void sipe_ocs2005_apply_calendar_status(struct sipe_core_private
*sipe_private
,
425 struct sipe_buddy
*sbuddy
,
426 const char *status_id
)
428 time_t cal_avail_since
;
429 int cal_status
= sipe_cal_get_status(sbuddy
, time(NULL
), &cal_avail_since
);
435 if (cal_status
< SIPE_CAL_NO_DATA
) {
436 SIPE_DEBUG_INFO("sipe_apply_calendar_status: cal_status : %d for %s", cal_status
, sbuddy
->name
);
437 SIPE_DEBUG_INFO("sipe_apply_calendar_status: cal_avail_since : %s", asctime(localtime(&cal_avail_since
)));
440 /* scheduled Cal update call */
442 status_id
= sbuddy
->last_non_cal_status_id
;
443 g_free(sbuddy
->activity
);
444 sbuddy
->activity
= g_strdup(sbuddy
->last_non_cal_activity
);
448 SIPE_DEBUG_INFO("sipe_apply_calendar_status: status_id is NULL for %s, exiting.",
449 sbuddy
->name
? sbuddy
->name
: "" );
453 /* adjust to calendar status */
454 if (cal_status
!= SIPE_CAL_NO_DATA
) {
455 SIPE_DEBUG_INFO("sipe_apply_calendar_status: user_avail_since: %s", asctime(localtime(&sbuddy
->user_avail_since
)));
457 if ((cal_status
== SIPE_CAL_BUSY
) &&
458 (cal_avail_since
> sbuddy
->user_avail_since
) &&
459 sipe_ocs2007_status_is_busy(status_id
)) {
460 status_id
= sipe_backend_activity_to_token(SIPE_ACTIVITY_BUSY
);
461 g_free(sbuddy
->activity
);
462 sbuddy
->activity
= g_strdup(sipe_core_activity_description(SIPE_ACTIVITY_IN_MEETING
));
464 avail
= sipe_ocs2007_availability_from_status(status_id
, NULL
);
466 SIPE_DEBUG_INFO("sipe_apply_calendar_status: activity_since : %s", asctime(localtime(&sbuddy
->activity_since
)));
467 if (cal_avail_since
> sbuddy
->activity_since
) {
468 if ((cal_status
== SIPE_CAL_OOF
) &&
469 sipe_ocs2007_availability_is_away2(avail
)) {
470 g_free(sbuddy
->activity
);
471 sbuddy
->activity
= g_strdup(sipe_core_activity_description(SIPE_ACTIVITY_OOF
));
476 /* then set status_id actually */
477 SIPE_DEBUG_INFO("sipe_apply_calendar_status: to %s for %s", status_id
, sbuddy
->name
? sbuddy
->name
: "" );
478 sipe_backend_buddy_set_status(SIPE_CORE_PUBLIC
, sbuddy
->name
, status_id
);
480 /* set our account state to the one in roaming (including calendar info) */
481 self_uri
= sip_uri_self(sipe_private
);
482 if (SIPE_CORE_PRIVATE_FLAG_IS(INITIAL_PUBLISH
) &&
483 sipe_strcase_equal(sbuddy
->name
, self_uri
)) {
484 if (sipe_strequal(status_id
, sipe_backend_activity_to_token(SIPE_ACTIVITY_OFFLINE
))) {
485 /* do not let offline status switch us off */
486 status_id
= sipe_backend_activity_to_token(SIPE_ACTIVITY_INVISIBLE
);
489 sipe_status_and_note(sipe_private
, status_id
);
494 static void update_calendar_status_cb(SIPE_UNUSED_PARAMETER
char *name
,
495 struct sipe_buddy
*sbuddy
,
496 struct sipe_core_private
*sipe_private
)
498 sipe_ocs2005_apply_calendar_status(sipe_private
, sbuddy
, NULL
);
502 * Updates contact's status
503 * based on their calendar information.
505 static void update_calendar_status(struct sipe_core_private
*sipe_private
,
506 SIPE_UNUSED_PARAMETER
void *unused
)
508 SIPE_DEBUG_INFO_NOFORMAT("update_calendar_status() started.");
509 g_hash_table_foreach(sipe_private
->buddies
,
510 (GHFunc
)update_calendar_status_cb
,
513 /* repeat scheduling */
514 sipe_ocs2005_schedule_status_update(sipe_private
,
515 time(NULL
) + 3 * 60 /* 3 min */);
519 * Schedules process of contacts' status update
520 * based on their calendar information.
521 * Should be scheduled to the beginning of every
522 * 15 min interval, like:
523 * 13:00, 13:15, 13:30, 13:45, etc.
525 void sipe_ocs2005_schedule_status_update(struct sipe_core_private
*sipe_private
,
526 time_t calculate_from
)
528 #define SCHEDULE_INTERVAL 15 * 60 /* 15 min */
530 /* start of the beginning of closest 15 min interval. */
531 time_t next_start
= (calculate_from
/ SCHEDULE_INTERVAL
+ 1) * SCHEDULE_INTERVAL
;
533 SIPE_DEBUG_INFO("sipe_ocs2005_schedule_status_update: calculate_from time: %s",
534 asctime(localtime(&calculate_from
)));
535 SIPE_DEBUG_INFO("sipe_ocs2005_schedule_status_update: next start time : %s",
536 asctime(localtime(&next_start
)));
538 sipe_schedule_seconds(sipe_private
,
539 "<+2005-cal-status>",
541 next_start
- time(NULL
),
542 update_calendar_status
,