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.
51 /* for xmlnode_get_descendant */
52 #include "sipe-utils.h"
59 * Autodiscover request for Exchange Web Services
60 * @param email (%s) Ex.: alice@cosmo.local
62 #define SIPE_EWS_AUTODISCOVER_REQUEST \
63 "<?xml version=\"1.0\"?>"\
64 "<Autodiscover xmlns=\"http://schemas.microsoft.com/exchange/autodiscover/outlook/requestschema/2006\">"\
66 "<EMailAddress>%s</EMailAddress>"\
67 "<AcceptableResponseSchema>"\
68 "http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a"\
69 "</AcceptableResponseSchema>"\
74 * GetUserOofSettingsRequest SOAP request to Exchange Web Services
75 * to obtain our Out-of-office (OOF) information.
76 * @param email (%s) Ex.: alice@cosmo.local
78 #define SIPE_EWS_USER_OOF_SETTINGS_REQUEST \
79 "<?xml version=\"1.0\" encoding=\"utf-8\"?>"\
80 "<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/\">"\
82 "<GetUserOofSettingsRequest xmlns=\"http://schemas.microsoft.com/exchange/services/2006/messages\">"\
83 "<Mailbox xmlns=\"http://schemas.microsoft.com/exchange/services/2006/types\">"\
84 "<Address>%s</Address>"\
86 "</GetUserOofSettingsRequest>"\
91 * GetUserAvailabilityRequest SOAP request to Exchange Web Services
92 * to obtain our Availability (FreeBusy, WorkingHours, Meetings) information.
93 * @param email (%s) Ex.: alice@cosmo.local
94 * @param start_time (%s) Ex.: 2009-12-06T00:00:00
95 * @param end_time (%s) Ex.: 2009-12-09T23:59:59
97 #define SIPE_EWS_USER_AVAILABILITY_REQUEST \
98 "<?xml version=\"1.0\" encoding=\"utf-8\"?>"\
99 "<soap:Envelope xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\""\
100 " xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\""\
101 " xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\""\
102 " xmlns:t=\"http://schemas.microsoft.com/exchange/services/2006/types\">"\
104 "<GetUserAvailabilityRequest xmlns=\"http://schemas.microsoft.com/exchange/services/2006/messages\""\
105 " xmlns:t=\"http://schemas.microsoft.com/exchange/services/2006/types\">"\
106 "<t:TimeZone xmlns=\"http://schemas.microsoft.com/exchange/services/2006/types\">"\
110 "<Time>00:00:00</Time>"\
111 "<DayOrder>0</DayOrder>"\
113 "<DayOfWeek>Sunday</DayOfWeek>"\
117 "<Time>00:00:00</Time>"\
118 "<DayOrder>0</DayOrder>"\
120 "<DayOfWeek>Sunday</DayOfWeek>"\
123 "<MailboxDataArray>"\
126 "<t:Address>%s</t:Address>"\
128 "<t:AttendeeType>Required</t:AttendeeType>"\
129 "<t:ExcludeConflicts>false</t:ExcludeConflicts>"\
131 "</MailboxDataArray>"\
132 "<t:FreeBusyViewOptions>"\
134 "<t:StartTime>%s</t:StartTime>"\
135 "<t:EndTime>%s</t:EndTime>"\
137 "<t:MergedFreeBusyIntervalInMinutes>15</t:MergedFreeBusyIntervalInMinutes>"\
138 "<t:RequestedView>DetailedMerged</t:RequestedView>"\
139 "</t:FreeBusyViewOptions>"\
140 "</GetUserAvailabilityRequest>"\
144 #define SIPE_EWS_STATE_NONE 0
145 #define SIPE_EWS_STATE_AUTODISCOVER_SUCCESS 1
146 #define SIPE_EWS_STATE_AUTODISCOVER_1_FAILURE -1
147 #define SIPE_EWS_STATE_AUTODISCOVER_2_FAILURE -2
148 #define SIPE_EWS_STATE_AVAILABILITY_SUCCESS 2
149 #define SIPE_EWS_STATE_OOF_SUCCESS 3
153 sipe_ews_cal_events_free(GSList
*cal_events
)
155 GSList
*entry
= cal_events
;
157 if (!cal_events
) return;
160 struct sipe_cal_event
*cal_event
= entry
->data
;
161 sipe_cal_event_free(cal_event
);
165 g_slist_free(cal_events
);
169 sipe_ews_free(struct sipe_ews
* ews
)
172 g_free(ews
->legacy_dn
);
174 g_free(ews
->auth
->domain
);
175 g_free(ews
->auth
->user
);
176 g_free(ews
->auth
->password
);
180 g_free(ews
->oof_url
);
181 g_free(ews
->oab_url
);
182 g_free(ews
->oof_state
);
183 g_free(ews
->oof_note
);
184 g_free(ews
->free_busy
);
185 g_free(ews
->working_hours_xml_str
);
187 sipe_ews_cal_events_free(ews
->cal_events
);
193 sipe_ews_get_oof_note(struct sipe_ews
*ews
)
195 time_t now
= time(NULL
);
197 if (!ews
) return NULL
;
199 if (!strcmp(ews
->oof_state
, "Enabled") ||
200 (!strcmp(ews
->oof_state
, "Scheduled") && now
>= ews
->oof_start
&& now
<= ews
->oof_end
))
202 return ews
->oof_note
;
211 sipe_ews_run_state_machine(struct sipe_ews
*ews
);
214 sipe_ews_process_avail_response(int return_code
,
218 struct sipe_ews
*ews
= data
;
220 purple_debug_info("sipe", "sipe_ews_process_avail_response: cb started.\n");
222 if(ews
->oof_url
&& strcmp(ews
->as_url
, ews
->oof_url
)) { /* whether reuse conn */
223 http_conn_set_close(ews
->http_conn
);
224 ews
->http_conn
= NULL
;
227 if (return_code
== 200 && body
) {
230 /** ref: [MS-OXWAVLS] */
231 xmlnode
*xml
= xmlnode_from_str(body
, strlen(body
));
233 Envelope/Body/GetUserAvailabilityResponse/FreeBusyResponseArray/FreeBusyResponse/ResponseMessage@ResponseClass="Success"
234 Envelope/Body/GetUserAvailabilityResponse/FreeBusyResponseArray/FreeBusyResponse/FreeBusyView/MergedFreeBusy
235 Envelope/Body/GetUserAvailabilityResponse/FreeBusyResponseArray/FreeBusyResponse/FreeBusyView/CalendarEventArray/CalendarEvent
236 Envelope/Body/GetUserAvailabilityResponse/FreeBusyResponseArray/FreeBusyResponse/FreeBusyView/WorkingHours
238 resp
= xmlnode_get_descendant(xml
, "Body", "GetUserAvailabilityResponse", "FreeBusyResponseArray", "FreeBusyResponse", NULL
);
239 if (!resp
) return; /* rather soap:Fault */
240 if (strcmp(xmlnode_get_attrib(xmlnode_get_child(resp
, "ResponseMessage"), "ResponseClass"), "Success")) {
241 return; /* Error response */
245 g_free(ews
->free_busy
);
246 ews
->free_busy
= xmlnode_get_data(xmlnode_get_descendant(resp
, "FreeBusyView", "MergedFreeBusy", NULL
));
249 node
= xmlnode_get_descendant(resp
, "FreeBusyView", "WorkingHours", NULL
);
250 g_free(ews
->working_hours_xml_str
);
251 ews
->working_hours_xml_str
= xmlnode_to_str(node
, NULL
);
252 purple_debug_info("sipe", "sipe_ews_process_avail_response: ews->working_hours_xml_str:\n%s\n",
253 ews
->working_hours_xml_str
? ews
->working_hours_xml_str
: "");
255 sipe_ews_cal_events_free(ews
->cal_events
);
256 ews
->cal_events
= NULL
;
258 for (node
= xmlnode_get_descendant(resp
, "FreeBusyView", "CalendarEventArray", "CalendarEvent", NULL
);
260 node
= xmlnode_get_next_twin(node
))
265 <StartTime>2009-12-07T13:30:00</StartTime>
266 <EndTime>2009-12-07T14:30:00</EndTime>
267 <BusyType>Busy</BusyType>
268 <CalendarEventDetails>
270 <Subject>Lunch</Subject>
271 <Location>Cafe</Location>
272 <IsMeeting>false</IsMeeting>
273 <IsRecurring>true</IsRecurring>
274 <IsException>false</IsException>
275 <IsReminderSet>true</IsReminderSet>
276 <IsPrivate>false</IsPrivate>
277 </CalendarEventDetails>
280 struct sipe_cal_event
*cal_event
= g_new0(struct sipe_cal_event
, 1);
281 ews
->cal_events
= g_slist_append(ews
->cal_events
, cal_event
);
283 tmp
= xmlnode_get_data(xmlnode_get_child(node
, "StartTime"));
284 cal_event
->start_time
= purple_str_to_time(tmp
, FALSE
, NULL
, NULL
, NULL
);
287 tmp
= xmlnode_get_data(xmlnode_get_child(node
, "EndTime"));
288 cal_event
->end_time
= purple_str_to_time(tmp
, FALSE
, NULL
, NULL
, NULL
);
291 tmp
= xmlnode_get_data(xmlnode_get_child(node
, "BusyType"));
292 if (!strcmp("Free", tmp
)) {
293 cal_event
->cal_status
= SIPE_CAL_FREE
;
294 } else if (!strcmp("Tentative", tmp
)) {
295 cal_event
->cal_status
= SIPE_CAL_TENTATIVE
;
296 } else if (!strcmp("Busy", tmp
)) {
297 cal_event
->cal_status
= SIPE_CAL_BUSY
;
298 } else if (!strcmp("OOF", tmp
)) {
299 cal_event
->cal_status
= SIPE_CAL_OOF
;
301 cal_event
->cal_status
= SIPE_CAL_NO_DATA
;
305 cal_event
->subject
= xmlnode_get_data(xmlnode_get_descendant(node
, "CalendarEventDetails", "Subject", NULL
));
306 cal_event
->location
= xmlnode_get_data(xmlnode_get_descendant(node
, "CalendarEventDetails", "Location", NULL
));
308 tmp
= xmlnode_get_data(xmlnode_get_descendant(node
, "CalendarEventDetails", "IsMeeting", NULL
));
309 cal_event
->is_meeting
= tmp
? !strcmp(tmp
, "true") : TRUE
;
315 ews
->state
= SIPE_EWS_STATE_AVAILABILITY_SUCCESS
;
316 sipe_ews_run_state_machine(ews
);
318 } else if (return_code
< 0) {
319 ews
->http_conn
= NULL
;
324 sipe_ews_process_oof_response(int return_code
,
328 struct sipe_ews
*ews
= data
;
330 purple_debug_info("sipe", "sipe_ews_process_oof_response: cb started.\n");
332 http_conn_set_close(ews
->http_conn
);
333 ews
->http_conn
= NULL
;
335 if (return_code
== 200 && body
) {
338 xmlnode
*xn_duration
;
339 /** ref: [MS-OXWOOF] */
340 xmlnode
*xml
= xmlnode_from_str(body
, strlen(body
));
341 /* Envelope/Body/GetUserOofSettingsResponse/ResponseMessage@ResponseClass="Success"
342 * Envelope/Body/GetUserOofSettingsResponse/OofSettings/OofState=Enabled
343 * Envelope/Body/GetUserOofSettingsResponse/OofSettings/Duration/StartTime
344 * Envelope/Body/GetUserOofSettingsResponse/OofSettings/Duration/EndTime
345 * Envelope/Body/GetUserOofSettingsResponse/OofSettings/InternalReply/Message
347 resp
= xmlnode_get_descendant(xml
, "Body", "GetUserOofSettingsResponse", NULL
);
348 if (!resp
) return; /* rather soap:Fault */
349 if (strcmp(xmlnode_get_attrib(xmlnode_get_child(resp
, "ResponseMessage"), "ResponseClass"), "Success")) {
350 return; /* Error response */
353 g_free(ews
->oof_state
);
354 ews
->oof_state
= xmlnode_get_data(xmlnode_get_descendant(resp
, "OofSettings", "OofState", NULL
));
356 old_note
= ews
->oof_note
;
357 ews
->oof_note
= NULL
;
358 if (strcmp(ews
->oof_state
, "Disabled")) {
359 char *tmp
= xmlnode_get_data(
360 xmlnode_get_descendant(resp
, "OofSettings", "InternalReply", "Message", NULL
));
362 /* UTF-8 encoded BOM (0xEF 0xBB 0xBF) as a signature to mark the beginning of a UTF-8 file */
363 if (tmp
&& !strncmp(tmp
, "\xEF\xBB\xBF", 3)) {
364 html
= purple_unescape_html(tmp
+3);
366 html
= purple_unescape_html(tmp
);
369 ews
->oof_note
= g_strstrip(purple_markup_strip_html(html
));
373 if (!strcmp(ews
->oof_state
, "Scheduled")
374 && (xn_duration
= xmlnode_get_descendant(resp
, "OofSettings", "Duration", NULL
)))
376 char *tmp
= xmlnode_get_data(xmlnode_get_child(xn_duration
, "StartTime"));
377 ews
->oof_start
= purple_str_to_time(tmp
, FALSE
, NULL
, NULL
, NULL
);
380 tmp
= xmlnode_get_data(xmlnode_get_child(xn_duration
, "EndTime"));
381 ews
->oof_end
= purple_str_to_time(tmp
, FALSE
, NULL
, NULL
, NULL
);
384 if (!(old_note
&& ews
->oof_note
&& !strcmp(old_note
, ews
->oof_note
))) { /* oof note changed */
385 ews
->oof_start
= time(NULL
);
386 ews
->oof_end
= (time_t)-1;
393 ews
->state
= SIPE_EWS_STATE_OOF_SUCCESS
;
394 sipe_ews_run_state_machine(ews
);
396 } else if (return_code
< 0) {
397 ews
->http_conn
= NULL
;
402 sipe_ews_process_autodiscover(int return_code
,
406 struct sipe_ews
*ews
= data
;
408 purple_debug_info("sipe", "sipe_ews_process_autodiscover: cb started.\n");
410 http_conn_set_close(ews
->http_conn
);
411 ews
->http_conn
= NULL
;
413 if (return_code
== 200 && body
) {
415 /** ref: [MS-OXDSCLI] */
416 xmlnode
*xml
= xmlnode_from_str(body
, strlen(body
));
418 /* Autodiscover/Response/User/LegacyDN (trim()) */
419 ews
->legacy_dn
= xmlnode_get_data(xmlnode_get_descendant(xml
, "Response", "User", "LegacyDN", NULL
));
420 ews
->legacy_dn
= ews
->legacy_dn
? g_strstrip(ews
->legacy_dn
) : NULL
;
423 for (node
= xmlnode_get_descendant(xml
, "Response", "Account", "Protocol", NULL
);
425 node
= xmlnode_get_next_twin(node
))
427 char *type
= xmlnode_get_data(xmlnode_get_child(node
, "Type"));
428 if (!strcmp("EXCH", type
)) {
429 ews
->as_url
= xmlnode_get_data(xmlnode_get_child(node
, "ASUrl"));
430 ews
->oof_url
= xmlnode_get_data(xmlnode_get_child(node
, "OOFUrl"));
431 ews
->oab_url
= xmlnode_get_data(xmlnode_get_child(node
, "OABUrl"));
433 purple_debug_info("sipe", "sipe_ews_process_autodiscover:as_url %s\n",
434 ews
->as_url
? ews
->as_url
: "");
435 purple_debug_info("sipe", "sipe_ews_process_autodiscover:oof_url %s\n",
436 ews
->oof_url
? ews
->oof_url
: "");
437 purple_debug_info("sipe", "sipe_ews_process_autodiscover:oab_url %s\n",
438 ews
->oab_url
? ews
->oab_url
: "");
450 ews
->state
= SIPE_EWS_STATE_AUTODISCOVER_SUCCESS
;
451 sipe_ews_run_state_machine(ews
);
454 if (return_code
< 0) {
455 ews
->http_conn
= NULL
;
457 switch (ews
->auto_disco_method
) {
459 ews
->state
= SIPE_EWS_STATE_AUTODISCOVER_1_FAILURE
; break;
461 ews
->state
= SIPE_EWS_STATE_AUTODISCOVER_2_FAILURE
; break;
463 sipe_ews_run_state_machine(ews
);
468 sipe_ews_do_autodiscover(struct sipe_ews
*ews
,
469 const char* autodiscover_url
)
473 purple_debug_info("sipe", "sipe_ews_do_autodiscover: going autodiscover url=%s\n", autodiscover_url
? autodiscover_url
: "");
475 body
= g_strdup_printf(SIPE_EWS_AUTODISCOVER_REQUEST
, ews
->email
);
476 ews
->http_conn
= http_conn_create(
483 (HttpConnCallback
)sipe_ews_process_autodiscover
,
489 sipe_ews_do_avail_request(struct sipe_ews
*ews
)
494 time_t now
= time(NULL
);
499 purple_debug_info("sipe", "sipe_ews_do_avail_request: going Availability req.\n");
501 now_tm
= gmtime(&now
);
502 /* start -1 day, 00:00:00 */
506 ews
->fb_start
= sipe_mktime_tz(now_tm
, "UTC");
507 ews
->fb_start
-= 24*60*60;
508 /* end = start + 4 days - 1 sec */
509 end
= ews
->fb_start
+ 4*(24*60*60) - 1;
511 start_str
= g_strdup(purple_utf8_strftime(SIPE_XML_DATE_PATTERN
, gmtime(&ews
->fb_start
)));
512 end_str
= g_strdup(purple_utf8_strftime(SIPE_XML_DATE_PATTERN
, gmtime(&end
)));
514 body
= g_strdup_printf(SIPE_EWS_USER_AVAILABILITY_REQUEST
, ews
->email
, start_str
, end_str
);
515 ews
->http_conn
= http_conn_create(
520 "text/xml; charset=UTF-8",
522 (HttpConnCallback
)sipe_ews_process_avail_response
,
531 sipe_ews_do_oof_request(struct sipe_ews
*ews
)
535 const char *content_type
= "text/xml; charset=UTF-8";
537 purple_debug_info("sipe", "sipe_ews_do_oof_request: going OOF req.\n");
539 body
= g_strdup_printf(SIPE_EWS_USER_OOF_SETTINGS_REQUEST
, ews
->email
);
540 if (!ews
->http_conn
) {
541 ews
->http_conn
= http_conn_create(ews
->account
,
547 (HttpConnCallback
)sipe_ews_process_oof_response
,
550 http_conn_post(ews
->http_conn
,
554 (HttpConnCallback
)sipe_ews_process_oof_response
,
562 sipe_ews_run_state_machine(struct sipe_ews
*ews
)
564 switch (ews
->state
) {
565 case SIPE_EWS_STATE_NONE
:
567 char *maildomain
= strstr(ews
->email
, "@") + 1;
568 char *autodisc_url
= g_strdup_printf("https://Autodiscover.%s/Autodiscover/Autodiscover.xml", maildomain
);
570 ews
->auto_disco_method
= 1;
572 sipe_ews_do_autodiscover(ews
, autodisc_url
);
574 g_free(autodisc_url
);
577 case SIPE_EWS_STATE_AUTODISCOVER_1_FAILURE
:
579 char *maildomain
= strstr(ews
->email
, "@") + 1;
580 char *autodisc_url
= g_strdup_printf("https://%s/Autodiscover/Autodiscover.xml", maildomain
);
582 ews
->auto_disco_method
= 2;
584 sipe_ews_do_autodiscover(ews
, autodisc_url
);
586 g_free(autodisc_url
);
589 case SIPE_EWS_STATE_AUTODISCOVER_2_FAILURE
:
590 ews
->is_disabled
= TRUE
;
592 case SIPE_EWS_STATE_AUTODISCOVER_SUCCESS
:
593 sipe_ews_do_avail_request(ews
);
595 case SIPE_EWS_STATE_AVAILABILITY_SUCCESS
:
596 sipe_ews_do_oof_request(ews
);
598 case SIPE_EWS_STATE_OOF_SUCCESS
:
599 ews
->state
= SIPE_EWS_STATE_AUTODISCOVER_SUCCESS
;
600 ews
->is_updated
= TRUE
;
601 if (ews
->sip
->ocs2007
) {
603 publish_calendar_status_self(ews
->sip
);
606 send_presence_soap(ews
->sip
, TRUE
);
613 sipe_ews_update_calendar(struct sipe_account_data
*sip
)
615 //char *autodisc_srv = g_strdup_printf("_autodiscover._tcp.%s", maildomain);
617 purple_debug_info("sipe", "sipe_ews_update_calendar: started.\n");
620 const char *email_url
;
621 const char *email_login
;
622 const char *email_password
;
623 char *email_auth_user
= NULL
;
624 char *email_auth_domain
= NULL
;
626 sip
->ews
= g_new0(struct sipe_ews
, 1);
629 sip
->ews
->account
= sip
->account
;
630 email_url
= purple_account_get_string(sip
->account
, "email_url", NULL
);
631 email_login
= purple_account_get_string(sip
->account
, "email_login", NULL
);
632 email_password
= purple_account_get_string(sip
->account
, "email_password", NULL
);
635 sip
->ews
->as_url
= g_strdup(email_url
);
636 sip
->ews
->oof_url
= g_strdup(email_url
);
637 sip
->ews
->state
= SIPE_EWS_STATE_AUTODISCOVER_SUCCESS
;
640 if (email_login
&& (tmp
= strstr(email_login
, "\\"))) {
641 email_auth_user
= g_strdup(tmp
+ 1);
642 email_auth_domain
= g_strndup(email_login
, tmp
- email_login
);
644 email_auth_user
= g_strdup(email_login
);
647 sip
->ews
->email
= g_strdup(sip
->email
);
649 sip
->ews
->auth
= g_new0(HttpConnAuth
, 1);
650 sip
->ews
->auth
->domain
= !is_empty(email_login
) ? email_auth_domain
: g_strdup(sip
->authdomain
);
651 sip
->ews
->auth
->user
= !is_empty(email_login
) ? email_auth_user
: g_strdup(sip
->authuser
);
652 sip
->ews
->auth
->password
= !is_empty(email_login
) ? g_strdup(email_password
) : g_strdup(sip
->password
);
653 sip
->ews
->auth
->use_negotiate
= purple_account_get_bool(sip
->account
, "krb5", FALSE
);
656 if(sip
->ews
->is_disabled
) {
657 purple_debug_info("sipe", "sipe_ews_update_calendar: disabled, exiting.\n");
661 sipe_ews_run_state_machine(sip
->ews
);
663 purple_debug_info("sipe", "sipe_ews_update_calendar: finished.\n");