6 * Copyright (C) 2010, 2009 pier11 <pier11@operamail.com>
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
25 For communication with Exchange 2007/2010 Web Server/Web Services:
27 1) Autodiscover (HTTPS POST request). With redirect support. XML content.
28 1.1) DNS SRV record _autodiscover._tcp.<domain> may also be resolved.
29 2) Availability Web service (SOAP = HTTPS POST + XML) call.
30 3) Out of Office (OOF) Web Service (SOAP = HTTPS POST + XML) call.
31 4) Web server authentication required - NTLM and/or Negotiate (Kerberos).
33 Note: ews - EWS stands for Exchange Web Services.
35 It will be able to retrieve our Calendar information (FreeBusy, WorkingHours,
36 Meetings Subject and Location, Is_Meeting) as well as our Out of Office (OOF) note
37 from Exchange Web Services for subsequent publishing.
39 Ref. for more implementation details:
40 http://sourceforge.net/projects/sipe/forums/forum/688535/topic/3403462
42 Similar functionality for Lotus Notes/Domino, iCalendar/CalDAV/Google would
43 be great to implement too.
53 #include "circbuffer.h"
54 #include "connection.h"
65 #include "sipe-utils.h"
66 #include "http-conn.h"
72 * Autodiscover request for Exchange Web Services
73 * @param email (%s) Ex.: alice@cosmo.local
75 #define SIPE_EWS_AUTODISCOVER_REQUEST \
76 "<?xml version=\"1.0\"?>"\
77 "<Autodiscover xmlns=\"http://schemas.microsoft.com/exchange/autodiscover/outlook/requestschema/2006\">"\
79 "<EMailAddress>%s</EMailAddress>"\
80 "<AcceptableResponseSchema>"\
81 "http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a"\
82 "</AcceptableResponseSchema>"\
87 * GetUserOofSettingsRequest SOAP request to Exchange Web Services
88 * to obtain our Out-of-office (OOF) information.
89 * @param email (%s) Ex.: alice@cosmo.local
91 #define SIPE_EWS_USER_OOF_SETTINGS_REQUEST \
92 "<?xml version=\"1.0\" encoding=\"utf-8\"?>"\
93 "<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/\">"\
95 "<GetUserOofSettingsRequest xmlns=\"http://schemas.microsoft.com/exchange/services/2006/messages\">"\
96 "<Mailbox xmlns=\"http://schemas.microsoft.com/exchange/services/2006/types\">"\
97 "<Address>%s</Address>"\
99 "</GetUserOofSettingsRequest>"\
104 * GetUserAvailabilityRequest SOAP request to Exchange Web Services
105 * to obtain our Availability (FreeBusy, WorkingHours, Meetings) information.
106 * @param email (%s) Ex.: alice@cosmo.local
107 * @param start_time (%s) Ex.: 2009-12-06T00:00:00
108 * @param end_time (%s) Ex.: 2009-12-09T23:59:59
110 #define SIPE_EWS_USER_AVAILABILITY_REQUEST \
111 "<?xml version=\"1.0\" encoding=\"utf-8\"?>"\
112 "<soap:Envelope xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\""\
113 " xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\""\
114 " xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\""\
115 " xmlns:t=\"http://schemas.microsoft.com/exchange/services/2006/types\">"\
117 "<GetUserAvailabilityRequest xmlns=\"http://schemas.microsoft.com/exchange/services/2006/messages\""\
118 " xmlns:t=\"http://schemas.microsoft.com/exchange/services/2006/types\">"\
119 "<t:TimeZone xmlns=\"http://schemas.microsoft.com/exchange/services/2006/types\">"\
123 "<Time>00:00:00</Time>"\
124 "<DayOrder>0</DayOrder>"\
126 "<DayOfWeek>Sunday</DayOfWeek>"\
130 "<Time>00:00:00</Time>"\
131 "<DayOrder>0</DayOrder>"\
133 "<DayOfWeek>Sunday</DayOfWeek>"\
136 "<MailboxDataArray>"\
139 "<t:Address>%s</t:Address>"\
141 "<t:AttendeeType>Required</t:AttendeeType>"\
142 "<t:ExcludeConflicts>false</t:ExcludeConflicts>"\
144 "</MailboxDataArray>"\
145 "<t:FreeBusyViewOptions>"\
147 "<t:StartTime>%s</t:StartTime>"\
148 "<t:EndTime>%s</t:EndTime>"\
150 "<t:MergedFreeBusyIntervalInMinutes>15</t:MergedFreeBusyIntervalInMinutes>"\
151 "<t:RequestedView>DetailedMerged</t:RequestedView>"\
152 "</t:FreeBusyViewOptions>"\
153 "</GetUserAvailabilityRequest>"\
157 #define SIPE_EWS_STATE_NONE 0
158 #define SIPE_EWS_STATE_AUTODISCOVER_SUCCESS 1
159 #define SIPE_EWS_STATE_AUTODISCOVER_1_FAILURE -1
160 #define SIPE_EWS_STATE_AUTODISCOVER_2_FAILURE -2
161 #define SIPE_EWS_STATE_AVAILABILITY_SUCCESS 2
162 #define SIPE_EWS_STATE_OOF_SUCCESS 3
166 sipe_ews_cal_events_free(GSList
*cal_events
)
168 GSList
*entry
= cal_events
;
170 if (!cal_events
) return;
173 struct sipe_cal_event
*cal_event
= entry
->data
;
174 sipe_cal_event_free(cal_event
);
178 g_slist_free(cal_events
);
182 sipe_ews_free(struct sipe_ews
* ews
)
185 g_free(ews
->legacy_dn
);
187 g_free(ews
->auth
->domain
);
188 g_free(ews
->auth
->user
);
189 g_free(ews
->auth
->password
);
193 g_free(ews
->oof_url
);
194 g_free(ews
->oab_url
);
195 g_free(ews
->oof_state
);
196 g_free(ews
->oof_note
);
197 g_free(ews
->free_busy
);
198 g_free(ews
->working_hours_xml_str
);
200 sipe_ews_cal_events_free(ews
->cal_events
);
206 sipe_ews_get_oof_note(struct sipe_ews
*ews
)
208 time_t now
= time(NULL
);
210 if (!ews
|| !ews
->oof_state
) return NULL
;
212 if (sipe_strequal(ews
->oof_state
, "Enabled") ||
213 (sipe_strequal(ews
->oof_state
, "Scheduled") && now
>= ews
->oof_start
&& now
<= ews
->oof_end
))
215 return ews
->oof_note
;
224 sipe_ews_run_state_machine(struct sipe_ews
*ews
);
227 sipe_ews_process_avail_response(int return_code
,
232 struct sipe_ews
*ews
= data
;
234 purple_debug_info("sipe", "sipe_ews_process_avail_response: cb started.\n");
236 if(!sipe_strequal(ews
->as_url
, ews
->oof_url
)) { /* whether reuse conn */
237 http_conn_set_close(conn
);
238 ews
->http_conn
= NULL
;
241 if (return_code
== 200 && body
) {
242 const sipe_xml
*node
;
243 const sipe_xml
*resp
;
244 /** ref: [MS-OXWAVLS] */
245 sipe_xml
*xml
= sipe_xml_parse(body
, strlen(body
));
247 Envelope/Body/GetUserAvailabilityResponse/FreeBusyResponseArray/FreeBusyResponse/ResponseMessage@ResponseClass="Success"
248 Envelope/Body/GetUserAvailabilityResponse/FreeBusyResponseArray/FreeBusyResponse/FreeBusyView/MergedFreeBusy
249 Envelope/Body/GetUserAvailabilityResponse/FreeBusyResponseArray/FreeBusyResponse/FreeBusyView/CalendarEventArray/CalendarEvent
250 Envelope/Body/GetUserAvailabilityResponse/FreeBusyResponseArray/FreeBusyResponse/FreeBusyView/WorkingHours
252 resp
= sipe_xml_child(xml
, "Body/GetUserAvailabilityResponse/FreeBusyResponseArray/FreeBusyResponse");
253 if (!resp
) return; /* rather soap:Fault */
254 if (!sipe_strequal(sipe_xml_attribute(sipe_xml_child(resp
, "ResponseMessage"), "ResponseClass"), "Success")) {
255 return; /* Error response */
259 g_free(ews
->free_busy
);
260 ews
->free_busy
= sipe_xml_data(sipe_xml_child(resp
, "FreeBusyView/MergedFreeBusy"));
263 node
= sipe_xml_child(resp
, "FreeBusyView/WorkingHours");
264 g_free(ews
->working_hours_xml_str
);
265 ews
->working_hours_xml_str
= sipe_xml_stringify(node
);
266 purple_debug_info("sipe", "sipe_ews_process_avail_response: ews->working_hours_xml_str:\n%s\n",
267 ews
->working_hours_xml_str
? ews
->working_hours_xml_str
: "");
269 sipe_ews_cal_events_free(ews
->cal_events
);
270 ews
->cal_events
= NULL
;
272 for (node
= sipe_xml_child(resp
, "FreeBusyView/CalendarEventArray/CalendarEvent");
274 node
= sipe_xml_twin(node
))
279 <StartTime>2009-12-07T13:30:00</StartTime>
280 <EndTime>2009-12-07T14:30:00</EndTime>
281 <BusyType>Busy</BusyType>
282 <CalendarEventDetails>
284 <Subject>Lunch</Subject>
285 <Location>Cafe</Location>
286 <IsMeeting>false</IsMeeting>
287 <IsRecurring>true</IsRecurring>
288 <IsException>false</IsException>
289 <IsReminderSet>true</IsReminderSet>
290 <IsPrivate>false</IsPrivate>
291 </CalendarEventDetails>
294 struct sipe_cal_event
*cal_event
= g_new0(struct sipe_cal_event
, 1);
295 ews
->cal_events
= g_slist_append(ews
->cal_events
, cal_event
);
297 tmp
= sipe_xml_data(sipe_xml_child(node
, "StartTime"));
298 cal_event
->start_time
= sipe_utils_str_to_time(tmp
);
301 tmp
= sipe_xml_data(sipe_xml_child(node
, "EndTime"));
302 cal_event
->end_time
= sipe_utils_str_to_time(tmp
);
305 tmp
= sipe_xml_data(sipe_xml_child(node
, "BusyType"));
306 if (sipe_strequal("Free", tmp
)) {
307 cal_event
->cal_status
= SIPE_CAL_FREE
;
308 } else if (sipe_strequal("Tentative", tmp
)) {
309 cal_event
->cal_status
= SIPE_CAL_TENTATIVE
;
310 } else if (sipe_strequal("Busy", tmp
)) {
311 cal_event
->cal_status
= SIPE_CAL_BUSY
;
312 } else if (sipe_strequal("OOF", tmp
)) {
313 cal_event
->cal_status
= SIPE_CAL_OOF
;
315 cal_event
->cal_status
= SIPE_CAL_NO_DATA
;
319 cal_event
->subject
= sipe_xml_data(sipe_xml_child(node
, "CalendarEventDetails/Subject"));
320 cal_event
->location
= sipe_xml_data(sipe_xml_child(node
, "CalendarEventDetails/Location"));
322 tmp
= sipe_xml_data(sipe_xml_child(node
, "CalendarEventDetails/IsMeeting"));
323 cal_event
->is_meeting
= tmp
? sipe_strequal(tmp
, "true") : TRUE
;
329 ews
->state
= SIPE_EWS_STATE_AVAILABILITY_SUCCESS
;
330 sipe_ews_run_state_machine(ews
);
332 } else if (return_code
< 0) {
333 ews
->http_conn
= NULL
;
338 sipe_ews_process_oof_response(int return_code
,
343 struct sipe_ews
*ews
= data
;
345 purple_debug_info("sipe", "sipe_ews_process_oof_response: cb started.\n");
347 http_conn_set_close(conn
);
348 ews
->http_conn
= NULL
;
350 if (return_code
== 200 && body
) {
352 const sipe_xml
*resp
;
353 const sipe_xml
*xn_duration
;
354 /** ref: [MS-OXWOOF] */
355 sipe_xml
*xml
= sipe_xml_parse(body
, strlen(body
));
356 /* Envelope/Body/GetUserOofSettingsResponse/ResponseMessage@ResponseClass="Success"
357 * Envelope/Body/GetUserOofSettingsResponse/OofSettings/OofState=Enabled
358 * Envelope/Body/GetUserOofSettingsResponse/OofSettings/Duration/StartTime
359 * Envelope/Body/GetUserOofSettingsResponse/OofSettings/Duration/EndTime
360 * Envelope/Body/GetUserOofSettingsResponse/OofSettings/InternalReply/Message
362 resp
= sipe_xml_child(xml
, "Body/GetUserOofSettingsResponse");
363 if (!resp
) return; /* rather soap:Fault */
364 if (!sipe_strequal(sipe_xml_attribute(sipe_xml_child(resp
, "ResponseMessage"), "ResponseClass"), "Success")) {
365 return; /* Error response */
368 g_free(ews
->oof_state
);
369 ews
->oof_state
= sipe_xml_data(sipe_xml_child(resp
, "OofSettings/OofState"));
371 old_note
= ews
->oof_note
;
372 ews
->oof_note
= NULL
;
373 if (!sipe_strequal(ews
->oof_state
, "Disabled")) {
374 char *tmp
= sipe_xml_data(
375 sipe_xml_child(resp
, "OofSettings/InternalReply/Message"));
378 /* UTF-8 encoded BOM (0xEF 0xBB 0xBF) as a signature to mark the beginning of a UTF-8 file */
379 if (g_str_has_prefix(tmp
, "\xEF\xBB\xBF")) {
380 html
= g_strdup(tmp
+3);
382 html
= g_strdup(tmp
);
385 tmp
= g_strstrip(purple_markup_strip_html(html
));
387 ews
->oof_note
= g_markup_escape_text(tmp
, -1);
391 if (sipe_strequal(ews
->oof_state
, "Scheduled")
392 && (xn_duration
= sipe_xml_child(resp
, "OofSettings/Duration")))
394 char *tmp
= sipe_xml_data(sipe_xml_child(xn_duration
, "StartTime"));
395 ews
->oof_start
= sipe_utils_str_to_time(tmp
);
398 tmp
= sipe_xml_data(sipe_xml_child(xn_duration
, "EndTime"));
399 ews
->oof_end
= sipe_utils_str_to_time(tmp
);
403 if (!sipe_strequal(old_note
, ews
->oof_note
)) { /* oof note changed */
404 ews
->updated
= time(NULL
);
405 ews
->published
= FALSE
;
411 ews
->state
= SIPE_EWS_STATE_OOF_SUCCESS
;
412 sipe_ews_run_state_machine(ews
);
414 } else if (return_code
< 0) {
415 ews
->http_conn
= NULL
;
420 sipe_ews_process_autodiscover(int return_code
,
425 struct sipe_ews
*ews
= data
;
427 purple_debug_info("sipe", "sipe_ews_process_autodiscover: cb started.\n");
429 http_conn_set_close(conn
);
430 ews
->http_conn
= NULL
;
432 if (return_code
== 200 && body
) {
433 const sipe_xml
*node
;
434 /** ref: [MS-OXDSCLI] */
435 sipe_xml
*xml
= sipe_xml_parse(body
, strlen(body
));
437 /* Autodiscover/Response/User/LegacyDN (trim()) */
438 ews
->legacy_dn
= sipe_xml_data(sipe_xml_child(xml
, "Response/User/LegacyDN"));
439 ews
->legacy_dn
= ews
->legacy_dn
? g_strstrip(ews
->legacy_dn
) : NULL
;
442 for (node
= sipe_xml_child(xml
, "Response/Account/Protocol");
444 node
= sipe_xml_twin(node
))
446 char *type
= sipe_xml_data(sipe_xml_child(node
, "Type"));
447 if (sipe_strequal("EXCH", type
)) {
448 ews
->as_url
= sipe_xml_data(sipe_xml_child(node
, "ASUrl"));
449 ews
->oof_url
= sipe_xml_data(sipe_xml_child(node
, "OOFUrl"));
450 ews
->oab_url
= sipe_xml_data(sipe_xml_child(node
, "OABUrl"));
452 purple_debug_info("sipe", "sipe_ews_process_autodiscover:as_url %s\n",
453 ews
->as_url
? ews
->as_url
: "");
454 purple_debug_info("sipe", "sipe_ews_process_autodiscover:oof_url %s\n",
455 ews
->oof_url
? ews
->oof_url
: "");
456 purple_debug_info("sipe", "sipe_ews_process_autodiscover:oab_url %s\n",
457 ews
->oab_url
? ews
->oab_url
: "");
469 ews
->state
= SIPE_EWS_STATE_AUTODISCOVER_SUCCESS
;
470 sipe_ews_run_state_machine(ews
);
473 if (return_code
< 0) {
474 ews
->http_conn
= NULL
;
476 switch (ews
->auto_disco_method
) {
478 ews
->state
= SIPE_EWS_STATE_AUTODISCOVER_1_FAILURE
; break;
480 ews
->state
= SIPE_EWS_STATE_AUTODISCOVER_2_FAILURE
; break;
482 sipe_ews_run_state_machine(ews
);
487 sipe_ews_do_autodiscover(struct sipe_ews
*ews
,
488 const char* autodiscover_url
)
492 purple_debug_info("sipe", "sipe_ews_do_autodiscover: going autodiscover url=%s\n", autodiscover_url
? autodiscover_url
: "");
494 body
= g_strdup_printf(SIPE_EWS_AUTODISCOVER_REQUEST
, ews
->email
);
495 ews
->http_conn
= http_conn_create(
502 sipe_ews_process_autodiscover
,
508 sipe_ews_do_avail_request(struct sipe_ews
*ews
)
513 time_t now
= time(NULL
);
518 purple_debug_info("sipe", "sipe_ews_do_avail_request: going Availability req.\n");
520 now_tm
= gmtime(&now
);
521 /* start -1 day, 00:00:00 */
525 ews
->fb_start
= sipe_mktime_tz(now_tm
, "UTC");
526 ews
->fb_start
-= 24*60*60;
527 /* end = start + 4 days - 1 sec */
528 end
= ews
->fb_start
+ 4*(24*60*60) - 1;
530 start_str
= sipe_utils_time_to_str(ews
->fb_start
);
531 end_str
= sipe_utils_time_to_str(end
);
533 body
= g_strdup_printf(SIPE_EWS_USER_AVAILABILITY_REQUEST
, ews
->email
, start_str
, end_str
);
534 ews
->http_conn
= http_conn_create(
539 "text/xml; charset=UTF-8",
541 sipe_ews_process_avail_response
,
550 sipe_ews_do_oof_request(struct sipe_ews
*ews
)
554 const char *content_type
= "text/xml; charset=UTF-8";
556 purple_debug_info("sipe", "sipe_ews_do_oof_request: going OOF req.\n");
558 body
= g_strdup_printf(SIPE_EWS_USER_OOF_SETTINGS_REQUEST
, ews
->email
);
559 if (!ews
->http_conn
) {
560 ews
->http_conn
= http_conn_create(ews
->account
,
566 sipe_ews_process_oof_response
,
569 http_conn_post(ews
->http_conn
,
573 sipe_ews_process_oof_response
,
581 sipe_ews_run_state_machine(struct sipe_ews
*ews
)
583 switch (ews
->state
) {
584 case SIPE_EWS_STATE_NONE
:
586 char *maildomain
= strstr(ews
->email
, "@") + 1;
587 char *autodisc_url
= g_strdup_printf("https://Autodiscover.%s/Autodiscover/Autodiscover.xml", maildomain
);
589 ews
->auto_disco_method
= 1;
591 sipe_ews_do_autodiscover(ews
, autodisc_url
);
593 g_free(autodisc_url
);
596 case SIPE_EWS_STATE_AUTODISCOVER_1_FAILURE
:
598 char *maildomain
= strstr(ews
->email
, "@") + 1;
599 char *autodisc_url
= g_strdup_printf("https://%s/Autodiscover/Autodiscover.xml", maildomain
);
601 ews
->auto_disco_method
= 2;
603 sipe_ews_do_autodiscover(ews
, autodisc_url
);
605 g_free(autodisc_url
);
608 case SIPE_EWS_STATE_AUTODISCOVER_2_FAILURE
:
609 ews
->is_disabled
= TRUE
;
611 case SIPE_EWS_STATE_AUTODISCOVER_SUCCESS
:
612 sipe_ews_do_avail_request(ews
);
614 case SIPE_EWS_STATE_AVAILABILITY_SUCCESS
:
615 sipe_ews_do_oof_request(ews
);
617 case SIPE_EWS_STATE_OOF_SUCCESS
:
618 ews
->state
= SIPE_EWS_STATE_AUTODISCOVER_SUCCESS
;
619 ews
->is_updated
= TRUE
;
620 if (ews
->sip
->ocs2007
) {
622 publish_calendar_status_self(ews
->sip
);
625 send_presence_soap(ews
->sip
, TRUE
);
632 sipe_ews_update_calendar(struct sipe_account_data
*sip
)
634 //char *autodisc_srv = g_strdup_printf("_autodiscover._tcp.%s", maildomain);
636 purple_debug_info("sipe", "sipe_ews_update_calendar: started.\n");
641 sip
->ews
= g_new0(struct sipe_ews
, 1);
644 sip
->ews
->account
= sip
->account
;
645 sip
->ews
->email
= g_strdup(sip
->email
);
647 /* user specified a service URL? */
648 value
= purple_account_get_string(sip
->account
, "email_url", NULL
);
649 if (!is_empty(value
)) {
650 sip
->ews
->as_url
= g_strdup(value
);
651 sip
->ews
->oof_url
= g_strdup(value
);
652 sip
->ews
->state
= SIPE_EWS_STATE_AUTODISCOVER_SUCCESS
;
655 sip
->ews
->auth
= g_new0(HttpConnAuth
, 1);
656 sip
->ews
->auth
->use_negotiate
= purple_account_get_bool(sip
->account
, "krb5", FALSE
);
658 /* user specified email login? */
659 value
= purple_account_get_string(sip
->account
, "email_login", NULL
);
660 if (!is_empty(value
)) {
662 /* user specified email login domain? */
663 const char *tmp
= strstr(value
, "\\");
665 sip
->ews
->auth
->domain
= g_strndup(value
, tmp
- value
);
666 sip
->ews
->auth
->user
= g_strdup(tmp
+ 1);
668 sip
->ews
->auth
->user
= g_strdup(value
);
670 sip
->ews
->auth
->password
= g_strdup(purple_account_get_string(sip
->account
, "email_password", NULL
));
673 /* re-use SIPE credentials */
674 sip
->ews
->auth
->domain
= g_strdup(sip
->authdomain
);
675 sip
->ews
->auth
->user
= g_strdup(sip
->authuser
);
676 sip
->ews
->auth
->password
= g_strdup(sip
->password
);
680 if(sip
->ews
->is_disabled
) {
681 purple_debug_info("sipe", "sipe_ews_update_calendar: disabled, exiting.\n");
685 sipe_ews_run_state_machine(sip
->ews
);
687 purple_debug_info("sipe", "sipe_ews_update_calendar: finished.\n");