filetransfer: use 'deallocate' callback in sipe_file_transfer
[siplcs.git] / src / core / sipe-ews.c
blob5d00e05bca98deed829141d437c75987a0a78d62
1 /**
2 * @file sipe-ews.c
4 * pidgin-sipe
6 * Copyright (C) 2010-2015 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
25 /**
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.
47 #include <string.h>
48 #include <time.h>
50 #include <glib.h>
52 #include "sipe-backend.h"
53 #include "sipe-common.h"
54 #include "sipe-cal.h"
55 #include "sipe-core.h"
56 #include "sipe-core-private.h"
57 #include "sipe-ews.h"
58 #include "sipe-ews-autodiscover.h"
59 #include "sipe-http.h"
60 #include "sipe-utils.h"
61 #include "sipe-xml.h"
63 /**
64 * GetUserOofSettingsRequest SOAP request to Exchange Web Services
65 * to obtain our Out-of-office (OOF) information.
66 * @param email (%s) Ex.: alice@cosmo.local
68 #define SIPE_EWS_USER_OOF_SETTINGS_REQUEST \
69 "<?xml version=\"1.0\" encoding=\"utf-8\"?>"\
70 "<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/\">"\
71 "<soap:Body>"\
72 "<GetUserOofSettingsRequest xmlns=\"http://schemas.microsoft.com/exchange/services/2006/messages\">"\
73 "<Mailbox xmlns=\"http://schemas.microsoft.com/exchange/services/2006/types\">"\
74 "<Address>%s</Address>"\
75 "</Mailbox>"\
76 "</GetUserOofSettingsRequest>"\
77 "</soap:Body>"\
78 "</soap:Envelope>"
80 /**
81 * GetUserAvailabilityRequest SOAP request to Exchange Web Services
82 * to obtain our Availability (FreeBusy, WorkingHours, Meetings) information.
83 * @param email (%s) Ex.: alice@cosmo.local
84 * @param start_time (%s) Ex.: 2009-12-06T00:00:00
85 * @param end_time (%s) Ex.: 2009-12-09T23:59:59
87 #define SIPE_EWS_USER_AVAILABILITY_REQUEST \
88 "<?xml version=\"1.0\" encoding=\"utf-8\"?>"\
89 "<soap:Envelope xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\""\
90 " xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\""\
91 " xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\""\
92 " xmlns:t=\"http://schemas.microsoft.com/exchange/services/2006/types\">"\
93 "<soap:Body>"\
94 "<GetUserAvailabilityRequest xmlns=\"http://schemas.microsoft.com/exchange/services/2006/messages\""\
95 " xmlns:t=\"http://schemas.microsoft.com/exchange/services/2006/types\">"\
96 "<t:TimeZone xmlns=\"http://schemas.microsoft.com/exchange/services/2006/types\">"\
97 "<Bias>0</Bias>"\
98 "<StandardTime>"\
99 "<Bias>0</Bias>"\
100 "<Time>00:00:00</Time>"\
101 "<DayOrder>0</DayOrder>"\
102 "<Month>0</Month>"\
103 "<DayOfWeek>Sunday</DayOfWeek>"\
104 "</StandardTime>"\
105 "<DaylightTime>"\
106 "<Bias>0</Bias>"\
107 "<Time>00:00:00</Time>"\
108 "<DayOrder>0</DayOrder>"\
109 "<Month>0</Month>"\
110 "<DayOfWeek>Sunday</DayOfWeek>"\
111 "</DaylightTime>"\
112 "</t:TimeZone>"\
113 "<MailboxDataArray>"\
114 "<t:MailboxData>"\
115 "<t:Email>"\
116 "<t:Address>%s</t:Address>"\
117 "</t:Email>"\
118 "<t:AttendeeType>Required</t:AttendeeType>"\
119 "<t:ExcludeConflicts>false</t:ExcludeConflicts>"\
120 "</t:MailboxData>"\
121 "</MailboxDataArray>"\
122 "<t:FreeBusyViewOptions>"\
123 "<t:TimeWindow>"\
124 "<t:StartTime>%s</t:StartTime>"\
125 "<t:EndTime>%s</t:EndTime>"\
126 "</t:TimeWindow>"\
127 "<t:MergedFreeBusyIntervalInMinutes>15</t:MergedFreeBusyIntervalInMinutes>"\
128 "<t:RequestedView>DetailedMerged</t:RequestedView>"\
129 "</t:FreeBusyViewOptions>"\
130 "</GetUserAvailabilityRequest>"\
131 "</soap:Body>"\
132 "</soap:Envelope>"
134 #define SIPE_EWS_STATE_IDLE 0
135 #define SIPE_EWS_STATE_AUTODISCOVER_TRIGGERED 1
136 #define SIPE_EWS_STATE_AVAILABILITY_SUCCESS 2
137 #define SIPE_EWS_STATE_AVAILABILITY_FAILURE -2
138 #define SIPE_EWS_STATE_OOF_SUCCESS 3
139 #define SIPE_EWS_STATE_OOF_FAILURE -3
141 char *
142 sipe_ews_get_oof_note(struct sipe_calendar *cal)
144 time_t now = time(NULL);
146 if (!cal || !cal->oof_state) return NULL;
148 if (sipe_strequal(cal->oof_state, "Enabled") ||
149 (sipe_strequal(cal->oof_state, "Scheduled") && now >= cal->oof_start && now <= cal->oof_end))
151 return cal->oof_note;
153 else
155 return NULL;
159 static void
160 sipe_ews_run_state_machine(struct sipe_calendar *cal);
162 static void sipe_ews_process_avail_response(SIPE_UNUSED_PARAMETER struct sipe_core_private *sipe_private,
163 guint status,
164 SIPE_UNUSED_PARAMETER GSList *headers,
165 const gchar *body,
166 gpointer data)
168 struct sipe_calendar *cal = data;
170 SIPE_DEBUG_INFO_NOFORMAT("sipe_ews_process_avail_response: cb started.");
172 cal->request = NULL;
174 if ((status == SIPE_HTTP_STATUS_OK) && body) {
175 const sipe_xml *node;
176 const sipe_xml *resp;
177 /** ref: [MS-OXWAVLS] */
178 sipe_xml *xml = sipe_xml_parse(body, strlen(body));
180 Envelope/Body/GetUserAvailabilityResponse/FreeBusyResponseArray/FreeBusyResponse/ResponseMessage@ResponseClass="Success"
181 Envelope/Body/GetUserAvailabilityResponse/FreeBusyResponseArray/FreeBusyResponse/FreeBusyView/MergedFreeBusy
182 Envelope/Body/GetUserAvailabilityResponse/FreeBusyResponseArray/FreeBusyResponse/FreeBusyView/CalendarEventArray/CalendarEvent
183 Envelope/Body/GetUserAvailabilityResponse/FreeBusyResponseArray/FreeBusyResponse/FreeBusyView/WorkingHours
185 resp = sipe_xml_child(xml, "Body/GetUserAvailabilityResponse/FreeBusyResponseArray/FreeBusyResponse");
186 if (!resp) return; /* rather soap:Fault */
187 if (!sipe_strequal(sipe_xml_attribute(sipe_xml_child(resp, "ResponseMessage"), "ResponseClass"), "Success")) {
188 return; /* Error response */
191 /* MergedFreeBusy */
192 g_free(cal->free_busy);
193 cal->free_busy = sipe_xml_data(sipe_xml_child(resp, "FreeBusyView/MergedFreeBusy"));
195 /* WorkingHours */
196 node = sipe_xml_child(resp, "FreeBusyView/WorkingHours");
197 g_free(cal->working_hours_xml_str);
198 cal->working_hours_xml_str = sipe_xml_stringify(node);
199 SIPE_DEBUG_INFO("sipe_ews_process_avail_response: cal->working_hours_xml_str:\n%s",
200 cal->working_hours_xml_str ? cal->working_hours_xml_str : "");
202 sipe_cal_events_free(cal->cal_events);
203 cal->cal_events = NULL;
204 /* CalendarEvents */
205 for (node = sipe_xml_child(resp, "FreeBusyView/CalendarEventArray/CalendarEvent");
206 node;
207 node = sipe_xml_twin(node))
209 char *tmp;
211 <CalendarEvent>
212 <StartTime>2009-12-07T13:30:00</StartTime>
213 <EndTime>2009-12-07T14:30:00</EndTime>
214 <BusyType>Busy</BusyType>
215 <CalendarEventDetails>
216 <ID>0000000...</ID>
217 <Subject>Lunch</Subject>
218 <Location>Cafe</Location>
219 <IsMeeting>false</IsMeeting>
220 <IsRecurring>true</IsRecurring>
221 <IsException>false</IsException>
222 <IsReminderSet>true</IsReminderSet>
223 <IsPrivate>false</IsPrivate>
224 </CalendarEventDetails>
225 </CalendarEvent>
227 struct sipe_cal_event *cal_event = g_new0(struct sipe_cal_event, 1);
228 cal->cal_events = g_slist_append(cal->cal_events, cal_event);
230 tmp = sipe_xml_data(sipe_xml_child(node, "StartTime"));
231 cal_event->start_time = sipe_utils_str_to_time(tmp);
232 g_free(tmp);
234 tmp = sipe_xml_data(sipe_xml_child(node, "EndTime"));
235 cal_event->end_time = sipe_utils_str_to_time(tmp);
236 g_free(tmp);
238 tmp = sipe_xml_data(sipe_xml_child(node, "BusyType"));
239 if (sipe_strequal("Free", tmp)) {
240 cal_event->cal_status = SIPE_CAL_FREE;
241 } else if (sipe_strequal("Tentative", tmp)) {
242 cal_event->cal_status = SIPE_CAL_TENTATIVE;
243 } else if (sipe_strequal("Busy", tmp)) {
244 cal_event->cal_status = SIPE_CAL_BUSY;
245 } else if (sipe_strequal("OOF", tmp)) {
246 cal_event->cal_status = SIPE_CAL_OOF;
247 } else {
248 cal_event->cal_status = SIPE_CAL_NO_DATA;
250 g_free(tmp);
252 cal_event->subject = sipe_xml_data(sipe_xml_child(node, "CalendarEventDetails/Subject"));
253 cal_event->location = sipe_xml_data(sipe_xml_child(node, "CalendarEventDetails/Location"));
255 tmp = sipe_xml_data(sipe_xml_child(node, "CalendarEventDetails/IsMeeting"));
256 cal_event->is_meeting = tmp ? sipe_strequal(tmp, "true") : TRUE;
257 g_free(tmp);
260 sipe_xml_free(xml);
262 cal->state = SIPE_EWS_STATE_AVAILABILITY_SUCCESS;
263 sipe_ews_run_state_machine(cal);
265 } else {
266 cal->state = SIPE_EWS_STATE_AVAILABILITY_FAILURE;
267 sipe_ews_run_state_machine(cal);
271 static void sipe_ews_process_oof_response(SIPE_UNUSED_PARAMETER struct sipe_core_private *sipe_private,
272 guint status,
273 SIPE_UNUSED_PARAMETER GSList *headers,
274 const gchar *body,
275 gpointer data)
277 struct sipe_calendar *cal = data;
279 SIPE_DEBUG_INFO_NOFORMAT("sipe_ews_process_oof_response: cb started.");
281 cal->request = NULL;
283 if ((status == SIPE_HTTP_STATUS_OK) && body) {
284 char *old_note;
285 const sipe_xml *resp;
286 const sipe_xml *xn_duration;
287 /** ref: [MS-OXWOOF] */
288 sipe_xml *xml = sipe_xml_parse(body, strlen(body));
289 /* Envelope/Body/GetUserOofSettingsResponse/ResponseMessage@ResponseClass="Success"
290 * Envelope/Body/GetUserOofSettingsResponse/OofSettings/OofState=Enabled
291 * Envelope/Body/GetUserOofSettingsResponse/OofSettings/Duration/StartTime
292 * Envelope/Body/GetUserOofSettingsResponse/OofSettings/Duration/EndTime
293 * Envelope/Body/GetUserOofSettingsResponse/OofSettings/InternalReply/Message
295 resp = sipe_xml_child(xml, "Body/GetUserOofSettingsResponse");
296 if (!resp) return; /* rather soap:Fault */
297 if (!sipe_strequal(sipe_xml_attribute(sipe_xml_child(resp, "ResponseMessage"), "ResponseClass"), "Success")) {
298 return; /* Error response */
301 g_free(cal->oof_state);
302 cal->oof_state = sipe_xml_data(sipe_xml_child(resp, "OofSettings/OofState"));
304 old_note = cal->oof_note;
305 cal->oof_note = NULL;
306 if (!sipe_strequal(cal->oof_state, "Disabled")) {
307 char *tmp = sipe_xml_data(
308 sipe_xml_child(resp, "OofSettings/InternalReply/Message"));
309 char *html;
311 /* UTF-8 encoded BOM (0xEF 0xBB 0xBF) as a signature to mark the beginning of a UTF-8 file */
312 if (g_str_has_prefix(tmp, "\xEF\xBB\xBF")) {
313 html = g_strdup(tmp+3);
314 } else {
315 html = g_strdup(tmp);
317 g_free(tmp);
318 tmp = g_strstrip(sipe_backend_markup_strip_html(html));
319 g_free(html);
320 cal->oof_note = g_markup_escape_text(tmp, -1);
321 g_free(tmp);
324 if (sipe_strequal(cal->oof_state, "Scheduled")
325 && (xn_duration = sipe_xml_child(resp, "OofSettings/Duration")))
327 char *tmp = sipe_xml_data(sipe_xml_child(xn_duration, "StartTime"));
328 cal->oof_start = sipe_utils_str_to_time(tmp);
329 g_free(tmp);
331 tmp = sipe_xml_data(sipe_xml_child(xn_duration, "EndTime"));
332 cal->oof_end = sipe_utils_str_to_time(tmp);
333 g_free(tmp);
336 if (!sipe_strequal(old_note, cal->oof_note)) { /* oof note changed */
337 cal->updated = time(NULL);
338 cal->published = FALSE;
340 g_free(old_note);
342 sipe_xml_free(xml);
344 cal->state = SIPE_EWS_STATE_OOF_SUCCESS;
345 sipe_ews_run_state_machine(cal);
347 } else {
348 cal->state = SIPE_EWS_STATE_OOF_FAILURE;
349 sipe_ews_run_state_machine(cal);
353 static void sipe_ews_send_http_request(struct sipe_calendar *cal)
355 if (cal->request) {
356 sipe_core_email_authentication(cal->sipe_private,
357 cal->request);
358 sipe_http_request_allow_redirect(cal->request);
359 sipe_http_request_ready(cal->request);
363 static void sipe_ews_do_avail_request(struct sipe_calendar *cal)
365 if (cal->as_url) {
366 char *body;
367 time_t end;
368 time_t now = time(NULL);
369 char *start_str;
370 char *end_str;
371 struct tm *now_tm;
373 SIPE_DEBUG_INFO_NOFORMAT("sipe_ews_do_avail_request: going Availability req.");
375 now_tm = gmtime(&now);
376 /* start -1 day, 00:00:00 */
377 now_tm->tm_sec = 0;
378 now_tm->tm_min = 0;
379 now_tm->tm_hour = 0;
380 cal->fb_start = sipe_mktime_tz(now_tm, "UTC");
381 cal->fb_start -= 24*60*60;
382 /* end = start + 4 days - 1 sec */
383 end = cal->fb_start + SIPE_FREE_BUSY_PERIOD_SEC - 1;
385 start_str = sipe_utils_time_to_str(cal->fb_start);
386 end_str = sipe_utils_time_to_str(end);
388 body = g_strdup_printf(SIPE_EWS_USER_AVAILABILITY_REQUEST, cal->email, start_str, end_str);
389 cal->request = sipe_http_request_post(cal->sipe_private,
390 cal->as_url,
391 NULL,
392 body,
393 "text/xml; charset=UTF-8",
394 sipe_ews_process_avail_response,
395 cal);
396 g_free(body);
397 g_free(start_str);
398 g_free(end_str);
400 sipe_ews_send_http_request(cal);
404 static void sipe_ews_do_oof_request(struct sipe_calendar *cal)
406 if (cal->oof_url) {
407 char *body;
409 SIPE_DEBUG_INFO_NOFORMAT("sipe_ews_do_oof_request: going OOF req.");
411 body = g_strdup_printf(SIPE_EWS_USER_OOF_SETTINGS_REQUEST, cal->email);
412 cal->request = sipe_http_request_post(cal->sipe_private,
413 cal->as_url,
414 NULL,
415 body,
416 "text/xml; charset=UTF-8",
417 sipe_ews_process_oof_response,
418 cal);
419 g_free(body);
421 sipe_ews_send_http_request(cal);
425 static void
426 sipe_ews_run_state_machine(struct sipe_calendar *cal)
428 switch (cal->state) {
429 case SIPE_EWS_STATE_AVAILABILITY_FAILURE:
430 case SIPE_EWS_STATE_OOF_FAILURE:
431 cal->is_ews_disabled = TRUE;
432 break;
433 case SIPE_EWS_STATE_IDLE:
434 sipe_ews_do_avail_request(cal);
435 break;
436 case SIPE_EWS_STATE_AUTODISCOVER_TRIGGERED:
437 /* do nothing */
438 break;
439 case SIPE_EWS_STATE_AVAILABILITY_SUCCESS:
440 sipe_ews_do_oof_request(cal);
441 break;
442 case SIPE_EWS_STATE_OOF_SUCCESS:
444 struct sipe_core_private *sipe_private = cal->sipe_private;
446 cal->state = SIPE_EWS_STATE_IDLE;
447 cal->is_updated = TRUE;
448 sipe_cal_presence_publish(sipe_private, TRUE);
450 break;
454 static void sipe_calendar_ews_autodiscover_cb(SIPE_UNUSED_PARAMETER struct sipe_core_private *sipe_private,
455 const struct sipe_ews_autodiscover_data *ews_data,
456 gpointer callback_data)
458 struct sipe_calendar *cal = callback_data;
460 if (ews_data) {
461 cal->as_url = g_strdup(ews_data->as_url);
462 cal->legacy_dn = g_strdup(ews_data->legacy_dn);
463 cal->oab_url = g_strdup(ews_data->oab_url);
464 cal->oof_url = g_strdup(ews_data->oof_url);
465 cal->state = SIPE_EWS_STATE_IDLE;
466 sipe_ews_run_state_machine(cal);
467 } else {
468 SIPE_DEBUG_INFO_NOFORMAT("sipe_calendar_ews_autodiscover_cb: EWS disabled");
469 cal->is_ews_disabled = TRUE;
473 void sipe_ews_update_calendar(struct sipe_core_private *sipe_private)
475 //char *autodisc_srv = g_strdup_printf("_autodiscover._tcp.%s", maildomain);
476 struct sipe_calendar *cal;
478 SIPE_DEBUG_INFO_NOFORMAT("sipe_ews_update_calendar: started.");
480 sipe_cal_calendar_init(sipe_private);
481 cal = sipe_private->calendar;
483 if (cal->is_ews_disabled) {
484 SIPE_DEBUG_INFO_NOFORMAT("sipe_ews_update_calendar: disabled, exiting.");
485 } else if (!cal->as_url && (cal->state != SIPE_EWS_STATE_AUTODISCOVER_TRIGGERED)) {
486 cal->state = SIPE_EWS_STATE_AUTODISCOVER_TRIGGERED;
487 sipe_ews_autodiscover_start(sipe_private,
488 sipe_calendar_ews_autodiscover_cb,
489 cal);
490 } else {
491 sipe_ews_run_state_machine(cal);
492 SIPE_DEBUG_INFO_NOFORMAT("sipe_ews_update_calendar: finished.");
497 Local Variables:
498 mode: c
499 c-file-style: "bsd"
500 indent-tabs-mode: t
501 tab-width: 8
502 End: