6 * Copyright (C) 2010 SIPE Project <http://sipe.sourceforge.net/>
7 * Copyright (C) 2010, 2009 pier11 <pier11@operamail.com>
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
20 * You should have received a copy of the GNU General Public License
21 * along with this program; if not, write to the Free Software
22 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
26 For communication with Exchange 2007/2010 Web Server/Web Services:
28 1) Autodiscover (HTTPS POST request). With redirect support. XML content.
29 1.1) DNS SRV record _autodiscover._tcp.<domain> may also be resolved.
30 2) Availability Web service (SOAP = HTTPS POST + XML) call.
31 3) Out of Office (OOF) Web Service (SOAP = HTTPS POST + XML) call.
32 4) Web server authentication required - NTLM and/or Negotiate (Kerberos).
34 Note: ews - EWS stands for Exchange Web Services.
36 It will be able to retrieve our Calendar information (FreeBusy, WorkingHours,
37 Meetings Subject and Location, Is_Meeting) as well as our Out of Office (OOF) note
38 from Exchange Web Services for subsequent publishing.
40 Ref. for more implementation details:
41 http://sourceforge.net/projects/sipe/forums/forum/688535/topic/3403462
43 Similar functionality for Lotus Notes/Domino, iCalendar/CalDAV/Google would
44 be great to implement too.
52 #include "http-conn.h"
53 #include "sipe-backend.h"
54 #include "sipe-common.h"
56 #include "sipe-core.h"
57 #include "sipe-core-private.h"
59 #include "sipe-utils.h"
64 * Autodiscover request for Exchange Web Services
65 * @param email (%s) Ex.: alice@cosmo.local
67 #define SIPE_EWS_AUTODISCOVER_REQUEST \
68 "<?xml version=\"1.0\"?>"\
69 "<Autodiscover xmlns=\"http://schemas.microsoft.com/exchange/autodiscover/outlook/requestschema/2006\">"\
71 "<EMailAddress>%s</EMailAddress>"\
72 "<AcceptableResponseSchema>"\
73 "http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a"\
74 "</AcceptableResponseSchema>"\
79 * GetUserOofSettingsRequest SOAP request to Exchange Web Services
80 * to obtain our Out-of-office (OOF) information.
81 * @param email (%s) Ex.: alice@cosmo.local
83 #define SIPE_EWS_USER_OOF_SETTINGS_REQUEST \
84 "<?xml version=\"1.0\" encoding=\"utf-8\"?>"\
85 "<soap:Envelope xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">"\
87 "<GetUserOofSettingsRequest xmlns=\"http://schemas.microsoft.com/exchange/services/2006/messages\">"\
88 "<Mailbox xmlns=\"http://schemas.microsoft.com/exchange/services/2006/types\">"\
89 "<Address>%s</Address>"\
91 "</GetUserOofSettingsRequest>"\
96 * GetUserAvailabilityRequest SOAP request to Exchange Web Services
97 * to obtain our Availability (FreeBusy, WorkingHours, Meetings) information.
98 * @param email (%s) Ex.: alice@cosmo.local
99 * @param start_time (%s) Ex.: 2009-12-06T00:00:00
100 * @param end_time (%s) Ex.: 2009-12-09T23:59:59
102 #define SIPE_EWS_USER_AVAILABILITY_REQUEST \
103 "<?xml version=\"1.0\" encoding=\"utf-8\"?>"\
104 "<soap:Envelope xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\""\
105 " xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\""\
106 " xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\""\
107 " xmlns:t=\"http://schemas.microsoft.com/exchange/services/2006/types\">"\
109 "<GetUserAvailabilityRequest xmlns=\"http://schemas.microsoft.com/exchange/services/2006/messages\""\
110 " xmlns:t=\"http://schemas.microsoft.com/exchange/services/2006/types\">"\
111 "<t:TimeZone xmlns=\"http://schemas.microsoft.com/exchange/services/2006/types\">"\
115 "<Time>00:00:00</Time>"\
116 "<DayOrder>0</DayOrder>"\
118 "<DayOfWeek>Sunday</DayOfWeek>"\
122 "<Time>00:00:00</Time>"\
123 "<DayOrder>0</DayOrder>"\
125 "<DayOfWeek>Sunday</DayOfWeek>"\
128 "<MailboxDataArray>"\
131 "<t:Address>%s</t:Address>"\
133 "<t:AttendeeType>Required</t:AttendeeType>"\
134 "<t:ExcludeConflicts>false</t:ExcludeConflicts>"\
136 "</MailboxDataArray>"\
137 "<t:FreeBusyViewOptions>"\
139 "<t:StartTime>%s</t:StartTime>"\
140 "<t:EndTime>%s</t:EndTime>"\
142 "<t:MergedFreeBusyIntervalInMinutes>15</t:MergedFreeBusyIntervalInMinutes>"\
143 "<t:RequestedView>DetailedMerged</t:RequestedView>"\
144 "</t:FreeBusyViewOptions>"\
145 "</GetUserAvailabilityRequest>"\
149 #define SIPE_EWS_STATE_NONE 0
150 #define SIPE_EWS_STATE_AUTODISCOVER_SUCCESS 1
151 #define SIPE_EWS_STATE_AUTODISCOVER_1_FAILURE -1
152 #define SIPE_EWS_STATE_AUTODISCOVER_2_FAILURE -2
153 #define SIPE_EWS_STATE_AVAILABILITY_SUCCESS 3
154 #define SIPE_EWS_STATE_AVAILABILITY_FAILURE -3
155 #define SIPE_EWS_STATE_OOF_SUCCESS 4
156 #define SIPE_EWS_STATE_OOF_FAILURE -4
159 sipe_ews_get_oof_note(struct sipe_calendar
*cal
)
161 time_t now
= time(NULL
);
163 if (!cal
|| !cal
->oof_state
) return NULL
;
165 if (sipe_strequal(cal
->oof_state
, "Enabled") ||
166 (sipe_strequal(cal
->oof_state
, "Scheduled") && now
>= cal
->oof_start
&& now
<= cal
->oof_end
))
168 return cal
->oof_note
;
177 sipe_ews_run_state_machine(struct sipe_calendar
*cal
);
180 sipe_ews_process_avail_response(int return_code
,
182 SIPE_UNUSED_PARAMETER
const char *content_type
,
186 struct sipe_calendar
*cal
= data
;
188 SIPE_DEBUG_INFO_NOFORMAT("sipe_ews_process_avail_response: cb started.");
190 if(!sipe_strequal(cal
->as_url
, cal
->oof_url
)) { /* whether reuse conn */
191 http_conn_set_close(conn
);
192 cal
->http_conn
= NULL
;
195 if (return_code
== 200 && body
) {
196 const sipe_xml
*node
;
197 const sipe_xml
*resp
;
198 /** ref: [MS-OXWAVLS] */
199 sipe_xml
*xml
= sipe_xml_parse(body
, strlen(body
));
201 Envelope/Body/GetUserAvailabilityResponse/FreeBusyResponseArray/FreeBusyResponse/ResponseMessage@ResponseClass="Success"
202 Envelope/Body/GetUserAvailabilityResponse/FreeBusyResponseArray/FreeBusyResponse/FreeBusyView/MergedFreeBusy
203 Envelope/Body/GetUserAvailabilityResponse/FreeBusyResponseArray/FreeBusyResponse/FreeBusyView/CalendarEventArray/CalendarEvent
204 Envelope/Body/GetUserAvailabilityResponse/FreeBusyResponseArray/FreeBusyResponse/FreeBusyView/WorkingHours
206 resp
= sipe_xml_child(xml
, "Body/GetUserAvailabilityResponse/FreeBusyResponseArray/FreeBusyResponse");
207 if (!resp
) return; /* rather soap:Fault */
208 if (!sipe_strequal(sipe_xml_attribute(sipe_xml_child(resp
, "ResponseMessage"), "ResponseClass"), "Success")) {
209 return; /* Error response */
213 g_free(cal
->free_busy
);
214 cal
->free_busy
= sipe_xml_data(sipe_xml_child(resp
, "FreeBusyView/MergedFreeBusy"));
217 node
= sipe_xml_child(resp
, "FreeBusyView/WorkingHours");
218 g_free(cal
->working_hours_xml_str
);
219 cal
->working_hours_xml_str
= sipe_xml_stringify(node
);
220 SIPE_DEBUG_INFO("sipe_ews_process_avail_response: cal->working_hours_xml_str:\n%s",
221 cal
->working_hours_xml_str
? cal
->working_hours_xml_str
: "");
223 sipe_cal_events_free(cal
->cal_events
);
224 cal
->cal_events
= NULL
;
226 for (node
= sipe_xml_child(resp
, "FreeBusyView/CalendarEventArray/CalendarEvent");
228 node
= sipe_xml_twin(node
))
233 <StartTime>2009-12-07T13:30:00</StartTime>
234 <EndTime>2009-12-07T14:30:00</EndTime>
235 <BusyType>Busy</BusyType>
236 <CalendarEventDetails>
238 <Subject>Lunch</Subject>
239 <Location>Cafe</Location>
240 <IsMeeting>false</IsMeeting>
241 <IsRecurring>true</IsRecurring>
242 <IsException>false</IsException>
243 <IsReminderSet>true</IsReminderSet>
244 <IsPrivate>false</IsPrivate>
245 </CalendarEventDetails>
248 struct sipe_cal_event
*cal_event
= g_new0(struct sipe_cal_event
, 1);
249 cal
->cal_events
= g_slist_append(cal
->cal_events
, cal_event
);
251 tmp
= sipe_xml_data(sipe_xml_child(node
, "StartTime"));
252 cal_event
->start_time
= sipe_utils_str_to_time(tmp
);
255 tmp
= sipe_xml_data(sipe_xml_child(node
, "EndTime"));
256 cal_event
->end_time
= sipe_utils_str_to_time(tmp
);
259 tmp
= sipe_xml_data(sipe_xml_child(node
, "BusyType"));
260 if (sipe_strequal("Free", tmp
)) {
261 cal_event
->cal_status
= SIPE_CAL_FREE
;
262 } else if (sipe_strequal("Tentative", tmp
)) {
263 cal_event
->cal_status
= SIPE_CAL_TENTATIVE
;
264 } else if (sipe_strequal("Busy", tmp
)) {
265 cal_event
->cal_status
= SIPE_CAL_BUSY
;
266 } else if (sipe_strequal("OOF", tmp
)) {
267 cal_event
->cal_status
= SIPE_CAL_OOF
;
269 cal_event
->cal_status
= SIPE_CAL_NO_DATA
;
273 cal_event
->subject
= sipe_xml_data(sipe_xml_child(node
, "CalendarEventDetails/Subject"));
274 cal_event
->location
= sipe_xml_data(sipe_xml_child(node
, "CalendarEventDetails/Location"));
276 tmp
= sipe_xml_data(sipe_xml_child(node
, "CalendarEventDetails/IsMeeting"));
277 cal_event
->is_meeting
= tmp
? sipe_strequal(tmp
, "true") : TRUE
;
283 cal
->state
= SIPE_EWS_STATE_AVAILABILITY_SUCCESS
;
284 sipe_ews_run_state_machine(cal
);
287 if (return_code
< 0) {
288 cal
->http_conn
= NULL
;
290 cal
->state
= SIPE_EWS_STATE_AVAILABILITY_FAILURE
;
291 sipe_ews_run_state_machine(cal
);
296 sipe_ews_process_oof_response(int return_code
,
298 SIPE_UNUSED_PARAMETER
const char *content_type
,
302 struct sipe_calendar
*cal
= data
;
304 SIPE_DEBUG_INFO_NOFORMAT("sipe_ews_process_oof_response: cb started.");
306 http_conn_set_close(conn
);
307 cal
->http_conn
= NULL
;
309 if (return_code
== 200 && body
) {
311 const sipe_xml
*resp
;
312 const sipe_xml
*xn_duration
;
313 /** ref: [MS-OXWOOF] */
314 sipe_xml
*xml
= sipe_xml_parse(body
, strlen(body
));
315 /* Envelope/Body/GetUserOofSettingsResponse/ResponseMessage@ResponseClass="Success"
316 * Envelope/Body/GetUserOofSettingsResponse/OofSettings/OofState=Enabled
317 * Envelope/Body/GetUserOofSettingsResponse/OofSettings/Duration/StartTime
318 * Envelope/Body/GetUserOofSettingsResponse/OofSettings/Duration/EndTime
319 * Envelope/Body/GetUserOofSettingsResponse/OofSettings/InternalReply/Message
321 resp
= sipe_xml_child(xml
, "Body/GetUserOofSettingsResponse");
322 if (!resp
) return; /* rather soap:Fault */
323 if (!sipe_strequal(sipe_xml_attribute(sipe_xml_child(resp
, "ResponseMessage"), "ResponseClass"), "Success")) {
324 return; /* Error response */
327 g_free(cal
->oof_state
);
328 cal
->oof_state
= sipe_xml_data(sipe_xml_child(resp
, "OofSettings/OofState"));
330 old_note
= cal
->oof_note
;
331 cal
->oof_note
= NULL
;
332 if (!sipe_strequal(cal
->oof_state
, "Disabled")) {
333 char *tmp
= sipe_xml_data(
334 sipe_xml_child(resp
, "OofSettings/InternalReply/Message"));
337 /* UTF-8 encoded BOM (0xEF 0xBB 0xBF) as a signature to mark the beginning of a UTF-8 file */
338 if (g_str_has_prefix(tmp
, "\xEF\xBB\xBF")) {
339 html
= g_strdup(tmp
+3);
341 html
= g_strdup(tmp
);
344 tmp
= g_strstrip(sipe_backend_markup_strip_html(html
));
346 cal
->oof_note
= g_markup_escape_text(tmp
, -1);
350 if (sipe_strequal(cal
->oof_state
, "Scheduled")
351 && (xn_duration
= sipe_xml_child(resp
, "OofSettings/Duration")))
353 char *tmp
= sipe_xml_data(sipe_xml_child(xn_duration
, "StartTime"));
354 cal
->oof_start
= sipe_utils_str_to_time(tmp
);
357 tmp
= sipe_xml_data(sipe_xml_child(xn_duration
, "EndTime"));
358 cal
->oof_end
= sipe_utils_str_to_time(tmp
);
362 if (!sipe_strequal(old_note
, cal
->oof_note
)) { /* oof note changed */
363 cal
->updated
= time(NULL
);
364 cal
->published
= FALSE
;
370 cal
->state
= SIPE_EWS_STATE_OOF_SUCCESS
;
371 sipe_ews_run_state_machine(cal
);
374 if (return_code
< 0) {
375 cal
->http_conn
= NULL
;
377 cal
->state
= SIPE_EWS_STATE_OOF_FAILURE
;
378 sipe_ews_run_state_machine(cal
);
383 sipe_ews_process_autodiscover(int return_code
,
385 SIPE_UNUSED_PARAMETER
const char *content_type
,
389 struct sipe_calendar
*cal
= data
;
391 SIPE_DEBUG_INFO_NOFORMAT("sipe_ews_process_autodiscover: cb started.");
393 http_conn_set_close(conn
);
394 cal
->http_conn
= NULL
;
396 if (return_code
== 200 && body
) {
397 const sipe_xml
*node
;
398 /** ref: [MS-OXDSCLI] */
399 sipe_xml
*xml
= sipe_xml_parse(body
, strlen(body
));
401 /* Autodiscover/Response/User/LegacyDN (trim()) */
402 cal
->legacy_dn
= sipe_xml_data(sipe_xml_child(xml
, "Response/User/LegacyDN"));
403 cal
->legacy_dn
= cal
->legacy_dn
? g_strstrip(cal
->legacy_dn
) : NULL
;
406 for (node
= sipe_xml_child(xml
, "Response/Account/Protocol");
408 node
= sipe_xml_twin(node
))
410 char *type
= sipe_xml_data(sipe_xml_child(node
, "Type"));
411 if (sipe_strequal("EXCH", type
)) {
412 cal
->as_url
= sipe_xml_data(sipe_xml_child(node
, "ASUrl"));
413 cal
->oof_url
= sipe_xml_data(sipe_xml_child(node
, "OOFUrl"));
414 cal
->oab_url
= sipe_xml_data(sipe_xml_child(node
, "OABUrl"));
416 SIPE_DEBUG_INFO("sipe_ews_process_autodiscover:as_url %s",
417 cal
->as_url
? cal
->as_url
: "");
418 SIPE_DEBUG_INFO("sipe_ews_process_autodiscover:oof_url %s",
419 cal
->oof_url
? cal
->oof_url
: "");
420 SIPE_DEBUG_INFO("sipe_ews_process_autodiscover:oab_url %s",
421 cal
->oab_url
? cal
->oab_url
: "");
433 cal
->state
= SIPE_EWS_STATE_AUTODISCOVER_SUCCESS
;
434 sipe_ews_run_state_machine(cal
);
437 if (return_code
< 0) {
438 cal
->http_conn
= NULL
;
440 switch (cal
->auto_disco_method
) {
442 cal
->state
= SIPE_EWS_STATE_AUTODISCOVER_1_FAILURE
; break;
444 cal
->state
= SIPE_EWS_STATE_AUTODISCOVER_2_FAILURE
; break;
446 sipe_ews_run_state_machine(cal
);
451 sipe_ews_do_autodiscover(struct sipe_calendar
*cal
,
452 const char* autodiscover_url
)
456 SIPE_DEBUG_INFO("sipe_ews_do_autodiscover: going autodiscover url=%s", autodiscover_url
? autodiscover_url
: "");
458 body
= g_strdup_printf(SIPE_EWS_AUTODISCOVER_REQUEST
, cal
->email
);
459 cal
->http_conn
= http_conn_create(
460 (struct sipe_core_public
*) cal
->sipe_private
,
461 NULL
, /* HttpSession */
464 HTTP_CONN_ALLOW_REDIRECT
,
469 sipe_ews_process_autodiscover
,
475 sipe_ews_do_avail_request(struct sipe_calendar
*cal
)
480 time_t now
= time(NULL
);
485 SIPE_DEBUG_INFO_NOFORMAT("sipe_ews_do_avail_request: going Availability req.");
487 now_tm
= gmtime(&now
);
488 /* start -1 day, 00:00:00 */
492 cal
->fb_start
= sipe_mktime_tz(now_tm
, "UTC");
493 cal
->fb_start
-= 24*60*60;
494 /* end = start + 4 days - 1 sec */
495 end
= cal
->fb_start
+ SIPE_FREE_BUSY_PERIOD_SEC
- 1;
497 start_str
= sipe_utils_time_to_str(cal
->fb_start
);
498 end_str
= sipe_utils_time_to_str(end
);
500 body
= g_strdup_printf(SIPE_EWS_USER_AVAILABILITY_REQUEST
, cal
->email
, start_str
, end_str
);
501 cal
->http_conn
= http_conn_create(
502 (struct sipe_core_public
*) cal
->sipe_private
,
503 NULL
, /* HttpSession */
506 HTTP_CONN_ALLOW_REDIRECT
,
509 "text/xml; charset=UTF-8",
511 sipe_ews_process_avail_response
,
520 sipe_ews_do_oof_request(struct sipe_calendar
*cal
)
524 const char *content_type
= "text/xml; charset=UTF-8";
526 SIPE_DEBUG_INFO_NOFORMAT("sipe_ews_do_oof_request: going OOF req.");
528 body
= g_strdup_printf(SIPE_EWS_USER_OOF_SETTINGS_REQUEST
, cal
->email
);
529 if (!cal
->http_conn
|| http_conn_is_closed(cal
->http_conn
)) {
530 cal
->http_conn
= http_conn_create((struct sipe_core_public
*)cal
->sipe_private
,
531 NULL
, /* HttpSession */
534 HTTP_CONN_ALLOW_REDIRECT
,
539 sipe_ews_process_oof_response
,
542 http_conn_send(cal
->http_conn
,
547 sipe_ews_process_oof_response
,
555 sipe_ews_run_state_machine(struct sipe_calendar
*cal
)
557 switch (cal
->state
) {
558 case SIPE_EWS_STATE_NONE
:
560 char *maildomain
= strstr(cal
->email
, "@") + 1;
561 char *autodisc_url
= g_strdup_printf("https://Autodiscover.%s/Autodiscover/Autodiscover.xml", maildomain
);
563 cal
->auto_disco_method
= 1;
565 sipe_ews_do_autodiscover(cal
, autodisc_url
);
567 g_free(autodisc_url
);
570 case SIPE_EWS_STATE_AUTODISCOVER_1_FAILURE
:
572 char *maildomain
= strstr(cal
->email
, "@") + 1;
573 char *autodisc_url
= g_strdup_printf("https://%s/Autodiscover/Autodiscover.xml", maildomain
);
575 cal
->auto_disco_method
= 2;
577 sipe_ews_do_autodiscover(cal
, autodisc_url
);
579 g_free(autodisc_url
);
582 case SIPE_EWS_STATE_AUTODISCOVER_2_FAILURE
:
583 case SIPE_EWS_STATE_AVAILABILITY_FAILURE
:
584 case SIPE_EWS_STATE_OOF_FAILURE
:
585 cal
->is_ews_disabled
= TRUE
;
587 case SIPE_EWS_STATE_AUTODISCOVER_SUCCESS
:
588 sipe_ews_do_avail_request(cal
);
590 case SIPE_EWS_STATE_AVAILABILITY_SUCCESS
:
591 sipe_ews_do_oof_request(cal
);
593 case SIPE_EWS_STATE_OOF_SUCCESS
:
595 struct sipe_core_private
*sipe_private
= cal
->sipe_private
;
597 cal
->state
= SIPE_EWS_STATE_AUTODISCOVER_SUCCESS
;
598 cal
->is_updated
= TRUE
;
599 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007
)) {
601 publish_calendar_status_self(sipe_private
,
605 send_presence_soap(sipe_private
, TRUE
);
613 sipe_ews_update_calendar(struct sipe_core_private
*sipe_private
)
615 struct sipe_account_data
*sip
= SIPE_ACCOUNT_DATA_PRIVATE
;
616 //char *autodisc_srv = g_strdup_printf("_autodiscover._tcp.%s", maildomain);
619 SIPE_DEBUG_INFO_NOFORMAT("sipe_ews_update_calendar: started.");
621 if (sipe_cal_calendar_init(sipe_private
, &has_url
)) {
623 sip
->cal
->state
= SIPE_EWS_STATE_AUTODISCOVER_SUCCESS
;
627 if (sip
->cal
->is_ews_disabled
) {
628 SIPE_DEBUG_INFO_NOFORMAT("sipe_ews_update_calendar: disabled, exiting.");
632 sipe_ews_run_state_machine(sip
->cal
);
634 SIPE_DEBUG_INFO_NOFORMAT("sipe_ews_update_calendar: finished.");