Allow Asterisk to compile under GCC 4.10
[asterisk/asterisk.git] / res / res_calendar_ews.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2008 - 2009, Digium, Inc.
5  *
6  * Jan Kalab <pitlicek@gmail.com>
7  *
8  * See http://www.asterisk.org for more information about
9  * the Asterisk project. Please do not directly contact
10  * any of the maintainers of this project for assistance;
11  * the project provides a web site, mailing lists and IRC
12  * channels for your use.
13  *
14  * This program is free software, distributed under the terms of
15  * the GNU General Public License Version 2. See the LICENSE file
16  * at the top of the source tree.
17  */
18
19 /*! \file
20  * \brief Resource for handling MS Exchange Web Service calendars
21  */
22
23 /*** MODULEINFO
24         <depend>neon29</depend>
25         <support_level>core</support_level>
26 ***/
27
28 #include "asterisk.h"
29
30 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
31
32 #include <ne_request.h>
33 #include <ne_session.h>
34 #include <ne_uri.h>
35 #include <ne_socket.h>
36 #include <ne_auth.h>
37 #include <ne_xml.h>
38 #include <ne_xmlreq.h>
39 #include <ne_utils.h>
40 #include <ne_redirect.h>
41
42 #include "asterisk/module.h"
43 #include "asterisk/channel.h"
44 #include "asterisk/calendar.h"
45 #include "asterisk/lock.h"
46 #include "asterisk/config.h"
47 #include "asterisk/astobj2.h"
48
49 static void *ewscal_load_calendar(void *data);
50 static void *unref_ewscal(void *obj);
51 static int ewscal_write_event(struct ast_calendar_event *event);
52
53 static struct ast_calendar_tech ewscal_tech = {
54         .type = "ews",
55         .description = "MS Exchange Web Service calendars",
56         .module = AST_MODULE,
57         .load_calendar = ewscal_load_calendar,
58         .unref_calendar = unref_ewscal,
59         .write_event = ewscal_write_event,
60 };
61
62 enum xml_op {
63         XML_OP_FIND = 100,
64         XML_OP_GET,
65         XML_OP_CREATE,
66 };
67
68 struct calendar_id {
69         struct ast_str *id;
70         AST_LIST_ENTRY(calendar_id) next;
71 };
72
73 struct xml_context {
74         ne_xml_parser *parser;
75         struct ast_str *cdata;
76         struct ast_calendar_event *event;
77         enum xml_op op;
78         struct ewscal_pvt *pvt;
79         AST_LIST_HEAD_NOLOCK(ids, calendar_id) ids;
80 };
81
82 /* Important states of XML parsing */
83 enum {
84         XML_EVENT_CALENDAR_ITEM = 9,
85         XML_EVENT_NAME = 10,
86         XML_EVENT_DESCRIPTION,
87         XML_EVENT_START,
88         XML_EVENT_END,
89         XML_EVENT_BUSY,
90         XML_EVENT_ORGANIZER,
91         XML_EVENT_LOCATION,
92         XML_EVENT_ATTENDEE_LIST,
93         XML_EVENT_ATTENDEE,
94         XML_EVENT_MAILBOX,
95         XML_EVENT_EMAIL_ADDRESS,
96         XML_EVENT_CATEGORIES,
97         XML_EVENT_CATEGORY,
98         XML_EVENT_IMPORTANCE,
99 };
100
101 struct ewscal_pvt {
102         AST_DECLARE_STRING_FIELDS(
103                 AST_STRING_FIELD(url);
104                 AST_STRING_FIELD(user);
105                 AST_STRING_FIELD(secret);
106         );
107         struct ast_calendar *owner;
108         ne_uri uri;
109         ne_session *session;
110         struct ao2_container *events;
111         unsigned int items;
112 };
113
114 static void ewscal_destructor(void *obj)
115 {
116         struct ewscal_pvt *pvt = obj;
117
118         ast_debug(1, "Destroying pvt for Exchange Web Service calendar %s\n", "pvt->owner->name");
119         if (pvt->session) {
120                 ne_session_destroy(pvt->session);
121         }
122         ast_string_field_free_memory(pvt);
123
124         ao2_callback(pvt->events, OBJ_UNLINK | OBJ_NODATA | OBJ_MULTIPLE, NULL, NULL);
125
126         ao2_ref(pvt->events, -1);
127 }
128
129 static void *unref_ewscal(void *obj)
130 {
131         struct ewscal_pvt *pvt = obj;
132
133         ast_debug(5, "EWS: unref_ewscal()\n");
134         ao2_ref(pvt, -1);
135         return NULL;
136 }
137
138 static int auth_credentials(void *userdata, const char *realm, int attempts, char *username, char *secret)
139 {
140         struct ewscal_pvt *pvt = userdata;
141
142         if (attempts > 1) {
143                 ast_log(LOG_WARNING, "Invalid username or password for Exchange Web Service calendar '%s'\n", pvt->owner->name);
144                 return -1;
145         }
146
147         ne_strnzcpy(username, pvt->user, NE_ABUFSIZ);
148         ne_strnzcpy(secret, pvt->secret, NE_ABUFSIZ);
149
150         return 0;
151 }
152
153 static int ssl_verify(void *userdata, int failures, const ne_ssl_certificate *cert)
154 {
155         struct ewscal_pvt *pvt = userdata;
156         if (failures & NE_SSL_UNTRUSTED) {
157                 ast_log(LOG_WARNING, "Untrusted SSL certificate for calendar %s!\n", pvt->owner->name);
158                 return 0;
159         }
160         return 1;       /* NE_SSL_NOTYETVALID, NE_SSL_EXPIRED, NE_SSL_IDMISMATCH */
161 }
162
163 static time_t mstime_to_time_t(char *mstime)
164 {
165         struct ast_tm tm;
166         struct timeval tv;
167
168         if (ast_strptime(mstime, "%FT%TZ", &tm)) {
169                 tv = ast_mktime(&tm, "UTC");
170                 return tv.tv_sec;
171         }
172         return 0;
173 }
174
175 static int startelm(void *userdata, int parent, const char *nspace, const char *name, const char **atts)
176 {
177         struct xml_context *ctx = userdata;
178
179         ast_debug(5, "EWS: XML: Start: %s\n", name);
180         if (ctx->op == XML_OP_CREATE) {
181                 return NE_XML_DECLINE;
182         }
183
184         /* Nodes needed for traversing until CalendarItem is found */
185         if (!strcmp(name, "Envelope") ||
186                 (!strcmp(name, "Body") && parent != XML_EVENT_CALENDAR_ITEM) ||
187                 !strcmp(name, "FindItemResponse") ||
188                 !strcmp(name, "GetItemResponse") ||
189                 !strcmp(name, "CreateItemResponse") ||
190                 !strcmp(name, "ResponseMessages") ||
191                 !strcmp(name, "FindItemResponseMessage") || !strcmp(name, "GetItemResponseMessage") ||
192                 !strcmp(name, "Items")
193         ) {
194                 return 1;
195         } else if (!strcmp(name, "RootFolder")) {
196                 /* Get number of events */
197                 unsigned int items;
198
199                 ast_debug(3, "EWS: XML: <RootFolder>\n");
200                 if (sscanf(ne_xml_get_attr(ctx->parser, atts, NULL, "TotalItemsInView"), "%u", &items) != 1) {
201                         /* Couldn't read enything */
202                         ne_xml_set_error(ctx->parser, "Could't read number of events.");
203                         return NE_XML_ABORT;
204                 }
205
206                 ast_debug(3, "EWS: %u calendar items to load\n", items);
207                 ctx->pvt->items = items;
208                 if (items < 1) {
209                         /* Stop processing XML if there are no events */
210                         ast_calendar_merge_events(ctx->pvt->owner, ctx->pvt->events);
211                         return NE_XML_DECLINE;
212                 }
213                 return 1;
214         } else if (!strcmp(name, "CalendarItem")) {
215                 /* Event start */
216                 ast_debug(3, "EWS: XML: <CalendarItem>\n");
217                 if (!(ctx->pvt && ctx->pvt->owner)) {
218                         ast_log(LOG_ERROR, "Require a private structure with an owner\n");
219                         return NE_XML_ABORT;
220                 }
221
222                 ctx->event = ast_calendar_event_alloc(ctx->pvt->owner);
223                 if (!ctx->event) {
224                         ast_log(LOG_ERROR, "Could not allocate an event!\n");
225                         return NE_XML_ABORT;
226                 }
227
228                 ctx->cdata = ast_str_create(64);
229                 if (!ctx->cdata) {
230                         ast_log(LOG_ERROR, "Could not allocate CDATA!\n");
231                         return NE_XML_ABORT;
232                 }
233
234                 return XML_EVENT_CALENDAR_ITEM;
235         } else if (!strcmp(name, "ItemId")) {
236                 /* Event UID */
237                 if (ctx->op == XML_OP_FIND) {
238                         struct calendar_id *id;
239                         if (!(id = ast_calloc(1, sizeof(*id)))) {
240                                 return NE_XML_ABORT;
241                         }
242                         if (!(id->id = ast_str_create(256))) {
243                                 ast_free(id);
244                                 return NE_XML_ABORT;
245                         }
246                         ast_str_set(&id->id, 0, "%s", ne_xml_get_attr(ctx->parser, atts, NULL, "Id"));
247                         AST_LIST_INSERT_TAIL(&ctx->ids, id, next);
248                         ast_debug(3, "EWS_FIND: XML: UID: %s\n", ast_str_buffer(id->id));
249                 } else {
250                         ast_debug(3, "EWS_GET: XML: UID: %s\n", ne_xml_get_attr(ctx->parser, atts, NULL, "Id"));
251                         ast_string_field_set(ctx->event, uid, ne_xml_get_attr(ctx->parser, atts, NULL, "Id"));
252                 }
253                 return XML_EVENT_NAME;
254         } else if (!strcmp(name, "Subject")) {
255                 /* Event name */
256                 if (!ctx->cdata) {
257                         return NE_XML_ABORT;
258                 }
259                 ast_str_reset(ctx->cdata);
260                 return XML_EVENT_NAME;
261         } else if (!strcmp(name, "Body") && parent == XML_EVENT_CALENDAR_ITEM) {
262                 /* Event body/description */
263                 if (!ctx->cdata) {
264                         return NE_XML_ABORT;
265                 }
266                 ast_str_reset(ctx->cdata);
267                 return XML_EVENT_DESCRIPTION;
268         } else if (!strcmp(name, "Start")) {
269                 /* Event start time */
270                 return XML_EVENT_START;
271         } else if (!strcmp(name, "End")) {
272                 /* Event end time */
273                 return XML_EVENT_END;
274         } else if (!strcmp(name, "LegacyFreeBusyStatus")) {
275                 /* Event busy state */
276                 return XML_EVENT_BUSY;
277         } else if (!strcmp(name, "Organizer") ||
278                         (parent == XML_EVENT_ORGANIZER && (!strcmp(name, "Mailbox") ||
279                         !strcmp(name, "Name")))) {
280                 /* Event organizer */
281                 if (!ctx->cdata) {
282                         return NE_XML_ABORT;
283                 }
284                 ast_str_reset(ctx->cdata);
285                 return XML_EVENT_ORGANIZER;
286         } else if (!strcmp(name, "Location")) {
287                 /* Event location */
288                 if (!ctx->cdata) {
289                         return NE_XML_ABORT;
290                 }
291                 ast_str_reset(ctx->cdata);
292                 return XML_EVENT_LOCATION;
293         } else if (!strcmp(name, "Categories")) {
294                 /* Event categories */
295                 if (!ctx->cdata) {
296                         return NE_XML_ABORT;
297                 }
298                 ast_str_reset(ctx->cdata);
299                 return XML_EVENT_CATEGORIES;
300         } else if (parent == XML_EVENT_CATEGORIES && !strcmp(name, "String")) {
301                 /* Event category */
302                 return XML_EVENT_CATEGORY;
303         } else if (!strcmp(name, "Importance")) {
304                 /* Event importance (priority) */
305                 if (!ctx->cdata) {
306                         return NE_XML_ABORT;
307                 }
308                 ast_str_reset(ctx->cdata);
309                 return XML_EVENT_IMPORTANCE;
310         } else if (!strcmp(name, "RequiredAttendees") || !strcmp(name, "OptionalAttendees")) {
311                 return XML_EVENT_ATTENDEE_LIST;
312         } else if (!strcmp(name, "Attendee") && parent == XML_EVENT_ATTENDEE_LIST) {
313                 return XML_EVENT_ATTENDEE;
314         } else if (!strcmp(name, "Mailbox") && parent == XML_EVENT_ATTENDEE) {
315                 return XML_EVENT_MAILBOX;
316         } else if (!strcmp(name, "EmailAddress") && parent == XML_EVENT_MAILBOX) {
317                 if (!ctx->cdata) {
318                         return NE_XML_ABORT;
319                 }
320                 ast_str_reset(ctx->cdata);
321                 return XML_EVENT_EMAIL_ADDRESS;
322         }
323
324         return NE_XML_DECLINE;
325 }
326
327 static int cdata(void *userdata, int state, const char *cdata, size_t len)
328 {
329         struct xml_context *ctx = userdata;
330         char data[len + 1];
331
332         /* !!! DON'T USE AST_STRING_FIELD FUNCTIONS HERE, JUST COLLECT CTX->CDATA !!! */
333         if (state < XML_EVENT_NAME || ctx->op == XML_OP_CREATE) {
334                 return 0;
335         }
336
337         if (!ctx->event) {
338                 ast_log(LOG_ERROR, "Parsing event data, but event object does not exist!\n");
339                 return 1;
340         }
341
342         if (!ctx->cdata) {
343                 ast_log(LOG_ERROR, "String for storing CDATA is unitialized!\n");
344                 return 1;
345         }
346
347         ast_copy_string(data, cdata, len + 1);
348
349         switch (state) {
350         case XML_EVENT_START:
351                 ctx->event->start = mstime_to_time_t(data);
352                 break;
353         case XML_EVENT_END:
354                 ctx->event->end = mstime_to_time_t(data);
355                 break;
356         case XML_EVENT_BUSY:
357                 if (!strcmp(data, "Busy") || !strcmp(data, "OOF")) {
358                         ast_debug(3, "EWS: XML: Busy: yes\n");
359                         ctx->event->busy_state = AST_CALENDAR_BS_BUSY;
360                 }
361                 else if (!strcmp(data, "Tentative")) {
362                         ast_debug(3, "EWS: XML: Busy: tentative\n");
363                         ctx->event->busy_state = AST_CALENDAR_BS_BUSY_TENTATIVE;
364                 }
365                 else {
366                         ast_debug(3, "EWS: XML: Busy: no\n");
367                         ctx->event->busy_state = AST_CALENDAR_BS_FREE;
368                 }
369                 break;
370         case XML_EVENT_CATEGORY:
371                 if (ast_str_strlen(ctx->cdata) == 0) {
372                         ast_str_set(&ctx->cdata, 0, "%s", data);
373                 } else {
374                         ast_str_append(&ctx->cdata, 0, ",%s", data);
375                 }
376                 break;
377         default:
378                 ast_str_append(&ctx->cdata, 0, "%s", data);
379         }
380
381         ast_debug(5, "EWS: XML: CDATA: %s\n", ast_str_buffer(ctx->cdata));
382
383         return 0;
384 }
385
386 static int endelm(void *userdata, int state, const char *nspace, const char *name)
387 {
388         struct xml_context *ctx = userdata;
389
390         ast_debug(5, "EWS: XML: End:   %s\n", name);
391         if (ctx->op == XML_OP_FIND || ctx->op == XML_OP_CREATE) {
392                 return NE_XML_DECLINE;
393         }
394
395         if (!strcmp(name, "Subject")) {
396                 /* Event name end*/
397                 ast_string_field_set(ctx->event, summary, ast_str_buffer(ctx->cdata));
398                 ast_debug(3, "EWS: XML: Summary: %s\n", ctx->event->summary);
399                 ast_str_reset(ctx->cdata);
400         } else if (!strcmp(name, "Body") && state == XML_EVENT_DESCRIPTION) {
401                 /* Event body/description end */
402                 ast_string_field_set(ctx->event, description, ast_str_buffer(ctx->cdata));
403                 ast_debug(3, "EWS: XML: Description: %s\n", ctx->event->description);
404                 ast_str_reset(ctx->cdata);
405         } else if (!strcmp(name, "Organizer")) {
406                 /* Event organizer end */
407                 ast_string_field_set(ctx->event, organizer, ast_str_buffer(ctx->cdata));
408                 ast_debug(3, "EWS: XML: Organizer: %s\n", ctx->event->organizer);
409                 ast_str_reset(ctx->cdata);
410         } else if (!strcmp(name, "Location")) {
411                 /* Event location end */
412                 ast_string_field_set(ctx->event, location, ast_str_buffer(ctx->cdata));
413                 ast_debug(3, "EWS: XML: Location: %s\n", ctx->event->location);
414                 ast_str_reset(ctx->cdata);
415         } else if (!strcmp(name, "Categories")) {
416                 /* Event categories end */
417                 ast_string_field_set(ctx->event, categories, ast_str_buffer(ctx->cdata));
418                 ast_debug(3, "EWS: XML: Categories: %s\n", ctx->event->categories);
419                 ast_str_reset(ctx->cdata);
420         } else if (!strcmp(name, "Importance")) {
421                 /* Event importance end */
422                 if (!strcmp(ast_str_buffer(ctx->cdata), "Low")) {
423                         ctx->event->priority = 9;
424                 } else if (!strcmp(ast_str_buffer(ctx->cdata), "Normal")) {
425                         ctx->event->priority = 5;
426                 } else if (!strcmp(ast_str_buffer(ctx->cdata), "High")) {
427                         ctx->event->priority = 1;
428                 }
429                 ast_debug(3, "EWS: XML: Importance: %s (%d)\n", ast_str_buffer(ctx->cdata), ctx->event->priority);
430                 ast_str_reset(ctx->cdata);
431         } else if (state == XML_EVENT_EMAIL_ADDRESS) {
432                 struct ast_calendar_attendee *attendee;
433
434                 if (!(attendee = ast_calloc(1, sizeof(*attendee)))) {
435                         ctx->event = ast_calendar_unref_event(ctx->event);
436                         return  1;
437                 }
438
439                 if (ast_str_strlen(ctx->cdata)) {
440                         attendee->data = ast_strdup(ast_str_buffer(ctx->cdata));
441                         AST_LIST_INSERT_TAIL(&ctx->event->attendees, attendee, next);
442                 } else {
443                         ast_free(attendee);
444                 }
445                 ast_debug(3, "EWS: XML: attendee address '%s'\n", ast_str_buffer(ctx->cdata));
446                 ast_str_reset(ctx->cdata);
447         } else if (!strcmp(name, "CalendarItem")) {
448                 /* Event end */
449                 ast_debug(3, "EWS: XML: </CalendarItem>\n");
450                 ast_free(ctx->cdata);
451                 if (ctx->event) {
452                         ao2_link(ctx->pvt->events, ctx->event);
453                         ctx->event = ast_calendar_unref_event(ctx->event);
454                 } else {
455                         ast_log(LOG_ERROR, "Event data ended in XML, but event object does not exist!\n");
456                         return 1;
457                 }
458         } else if (!strcmp(name, "Envelope")) {
459                 /* Events end */
460                 ast_debug(3, "EWS: XML: %d of %u event(s) has been parsed…\n", ao2_container_count(ctx->pvt->events), ctx->pvt->items);
461                 if (ao2_container_count(ctx->pvt->events) >= ctx->pvt->items) {
462                         ast_debug(3, "EWS: XML: All events has been parsed, merging…\n");
463                         ast_calendar_merge_events(ctx->pvt->owner, ctx->pvt->events);
464                 }
465         }
466
467         return 0;
468 }
469
470 static const char *mstime(time_t t, char *buf, size_t buflen)
471 {
472         struct timeval tv = {
473                 .tv_sec = t,
474         };
475         struct ast_tm tm;
476
477         ast_localtime(&tv, &tm, "utc");
478         ast_strftime(buf, buflen, "%FT%TZ", &tm);
479
480         return S_OR(buf, "");
481 }
482
483 static const char *msstatus(enum ast_calendar_busy_state state)
484 {
485         switch (state) {
486         case AST_CALENDAR_BS_BUSY_TENTATIVE:
487                 return "Tentative";
488         case AST_CALENDAR_BS_BUSY:
489                 return "Busy";
490         case AST_CALENDAR_BS_FREE:
491                 return "Free";
492         default:
493                 return "";
494         }
495 }
496
497 static const char *get_soap_action(enum xml_op op)
498 {
499         switch (op) {
500         case XML_OP_FIND:
501                 return "\"http://schemas.microsoft.com/exchange/services/2006/messages/FindItem\"";
502         case XML_OP_GET:
503                 return "\"http://schemas.microsoft.com/exchange/services/2006/messages/GetItem\"";
504         case XML_OP_CREATE:
505                 return "\"http://schemas.microsoft.com/exchange/services/2006/messages/CreateItem\"";
506         }
507
508         return "";
509 }
510
511 static int send_ews_request_and_parse(struct ast_str *request, struct xml_context *ctx)
512 {
513         int ret;
514         ne_request *req;
515         ne_xml_parser *parser;
516
517         ast_debug(3, "EWS: HTTP request...\n");
518         if (!(ctx && ctx->pvt)) {
519                 ast_log(LOG_ERROR, "There is no private!\n");
520                 return -1;
521         }
522
523         if (!ast_str_strlen(request)) {
524                 ast_log(LOG_ERROR, "No request to send!\n");
525                 return -1;
526         }
527
528         ast_debug(3, "%s\n", ast_str_buffer(request));
529
530         /* Prepare HTTP POST request */
531         req = ne_request_create(ctx->pvt->session, "POST", ctx->pvt->uri.path);
532         ne_set_request_flag(req, NE_REQFLAG_IDEMPOTENT, 0);
533
534         /* Set headers--should be application/soap+xml, but MS… :/ */
535         ne_add_request_header(req, "Content-Type", "text/xml; charset=utf-8");
536         ne_add_request_header(req, "SOAPAction", get_soap_action(ctx->op));
537
538         /* Set body to SOAP request */
539         ne_set_request_body_buffer(req, ast_str_buffer(request), ast_str_strlen(request));
540
541         /* Prepare XML parser */
542         parser = ne_xml_create();
543         ctx->parser = parser;
544         ne_xml_push_handler(parser, startelm, cdata, endelm, ctx);      /* Callbacks */
545
546         /* Dispatch request and parse response as XML */
547         ret = ne_xml_dispatch_request(req, parser);
548         if (ret != NE_OK) { /* Error handling */
549                 ast_log(LOG_WARNING, "Unable to communicate with Exchange Web Service at '%s': %s\n", ctx->pvt->url, ne_get_error(ctx->pvt->session));
550                 ne_request_destroy(req);
551                 ne_xml_destroy(parser);
552                 return -1;
553         }
554
555         /* Cleanup */
556         ne_request_destroy(req);
557         ne_xml_destroy(parser);
558
559         return 0;
560 }
561
562 static int ewscal_write_event(struct ast_calendar_event *event)
563 {
564         struct ast_str *request;
565         struct ewscal_pvt *pvt = event->owner->tech_pvt;
566         char start[21], end[21];
567         struct xml_context ctx = {
568                 .op = XML_OP_CREATE,
569                 .pvt = pvt,
570         };
571         int ret;
572         char *category, *categories;
573
574         if (!pvt) {
575                 return -1;
576         }
577
578         if (!(request = ast_str_create(1024))) {
579                 return -1;
580         }
581
582         ast_str_set(&request, 0,
583                 "<soap:Envelope xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" "
584                         "xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" "
585                         "xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\" "
586                         "xmlns:t=\"http://schemas.microsoft.com/exchange/services/2006/types\">"
587                         "<soap:Body>"
588                         "<CreateItem xmlns=\"http://schemas.microsoft.com/exchange/services/2006/messages\" "
589                                 "xmlns:t=\"http://schemas.microsoft.com/exchange/services/2006/types\" "
590                                 "SendMeetingInvitations=\"SendToNone\" >"
591                                 "<SavedItemFolderId>"
592                                         "<t:DistinguishedFolderId Id=\"calendar\"/>"
593                                 "</SavedItemFolderId>"
594                                 "<Items>"
595                                         "<t:CalendarItem xmlns=\"http://schemas.microsoft.com/exchange/services/2006/types\">"
596                                                 "<Subject>%s</Subject>"
597                                                 "<Body BodyType=\"Text\">%s</Body>"
598                                                 "<ReminderIsSet>false</ReminderIsSet>"
599                                                 "<Start>%s</Start>"
600                                                 "<End>%s</End>"
601                                                 "<IsAllDayEvent>false</IsAllDayEvent>"
602                                                 "<LegacyFreeBusyStatus>%s</LegacyFreeBusyStatus>"
603                                                 "<Location>%s</Location>",
604                 event->summary,
605                 event->description,
606                 mstime(event->start, start, sizeof(start)),
607                 mstime(event->end, end, sizeof(end)),
608                 msstatus(event->busy_state),
609                 event->location
610         );
611         /* Event priority */
612         switch (event->priority) {
613         case 1:
614         case 2:
615         case 3:
616         case 4:
617                 ast_str_append(&request, 0, "<Importance>High</Importance>");
618                 break;
619         case 5:
620                 ast_str_append(&request, 0, "<Importance>Normal</Importance>");
621                 break;
622         case 6:
623         case 7:
624         case 8:
625         case 9:
626                 ast_str_append(&request, 0, "<Importance>Low</Importance>");
627                 break;
628         }
629         /* Event categories*/
630         if (strlen(event->categories) > 0) {
631                 ast_str_append(&request, 0, "<Categories>");
632                 categories = ast_strdupa(event->categories);    /* Duplicate string, since strsep() is destructive */
633                 category = strsep(&categories, ",");
634                 while (category != NULL) {
635                         ast_str_append(&request, 0, "<String>%s</String>", category);
636                         category = strsep(&categories, ",");
637                 }
638                 ast_str_append(&request, 0, "</Categories>");
639         }
640         /* Finish request */
641         ast_str_append(&request, 0, "</t:CalendarItem></Items></CreateItem></soap:Body></soap:Envelope>");
642
643         ret = send_ews_request_and_parse(request, &ctx);
644
645         ast_free(request);
646
647         return ret;
648 }
649
650 static struct calendar_id *get_ewscal_ids_for(struct ewscal_pvt *pvt)
651 {
652         char start[21], end[21];
653         struct ast_tm tm;
654         struct timeval tv;
655         struct ast_str *request;
656         struct xml_context ctx = {
657                 .op = XML_OP_FIND,
658                 .pvt = pvt,
659         };
660
661         ast_debug(5, "EWS: get_ewscal_ids_for()\n");
662
663         if (!pvt) {
664                 ast_log(LOG_ERROR, "There is no private!\n");
665                 return NULL;
666         }
667
668         /* Prepare timeframe strings */
669         tv = ast_tvnow();
670         ast_localtime(&tv, &tm, "UTC");
671         ast_strftime(start, sizeof(start), "%FT%TZ", &tm);
672         tv.tv_sec += 60 * pvt->owner->timeframe;
673         ast_localtime(&tv, &tm, "UTC");
674         ast_strftime(end, sizeof(end), "%FT%TZ", &tm);
675
676         /* Prepare SOAP request */
677         if (!(request = ast_str_create(512))) {
678                 return NULL;
679         }
680
681         ast_str_set(&request, 0,
682                 "<SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\" "
683                 "xmlns:ns1=\"http://schemas.microsoft.com/exchange/services/2006/types\" "
684                 "xmlns:ns2=\"http://schemas.microsoft.com/exchange/services/2006/messages\">"
685                         "<SOAP-ENV:Body>"
686                                 "<ns2:FindItem Traversal=\"Shallow\">"
687                                         "<ns2:ItemShape>"
688                                                 "<ns1:BaseShape>IdOnly</ns1:BaseShape>"
689                                         "</ns2:ItemShape>"
690                                         "<ns2:CalendarView StartDate=\"%s\" EndDate=\"%s\"/>"   /* Timeframe */
691                                         "<ns2:ParentFolderIds>"
692                                                 "<ns1:DistinguishedFolderId Id=\"calendar\"/>"
693                                         "</ns2:ParentFolderIds>"
694                                 "</ns2:FindItem>"
695                         "</SOAP-ENV:Body>"
696                 "</SOAP-ENV:Envelope>",
697                 start, end      /* Timeframe */
698         );
699
700         AST_LIST_HEAD_INIT_NOLOCK(&ctx.ids);
701
702         /* Dispatch request and parse response as XML */
703         if (send_ews_request_and_parse(request, &ctx)) {
704                 ast_free(request);
705                 return NULL;
706         }
707
708         /* Cleanup */
709         ast_free(request);
710
711         return AST_LIST_FIRST(&ctx.ids);
712 }
713
714 static int parse_ewscal_id(struct ewscal_pvt *pvt, const char *id) {
715         struct ast_str *request;
716         struct xml_context ctx = {
717                 .pvt = pvt,
718                 .op = XML_OP_GET,
719         };
720
721         if (!(request = ast_str_create(512))) {
722                 return -1;
723         }
724
725         ast_str_set(&request, 0,
726                 "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
727                 "<soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\" "
728                 "xmlns:t=\"http://schemas.microsoft.com/exchange/services/2006/types\">"
729                 "<soap:Body>"
730                         "<GetItem xmlns=\"http://schemas.microsoft.com/exchange/services/2006/messages\">"
731                                 "<ItemShape>"
732                                         "<t:BaseShape>AllProperties</t:BaseShape>"
733                                 "</ItemShape>"
734                                 "<ItemIds>"
735                                         "<t:ItemId Id=\"%s\"/>"
736                                 "</ItemIds>"
737                         "</GetItem>"
738                 "</soap:Body>"
739                 "</soap:Envelope>", id
740         );
741
742         if (send_ews_request_and_parse(request, &ctx)) {
743                 ast_free(request);
744                 return -1;
745         }
746
747         ast_free(request);
748
749         return 0;
750 }
751
752 static int update_ewscal(struct ewscal_pvt *pvt)
753 {
754         struct calendar_id *id_head;
755         struct calendar_id *iter;
756
757         if (!(id_head = get_ewscal_ids_for(pvt))) {
758                 return 0;
759         }
760
761         for (iter = id_head; iter; iter = AST_LIST_NEXT(iter, next)) {
762                 parse_ewscal_id(pvt, ast_str_buffer(iter->id));
763                 ast_free(iter->id);
764                 ast_free(iter);
765         }
766
767         return 0;
768 }
769
770 static void *ewscal_load_calendar(void *void_data)
771 {
772         struct ewscal_pvt *pvt;
773         const struct ast_config *cfg;
774         struct ast_variable *v;
775         struct ast_calendar *cal = void_data;
776         ast_mutex_t refreshlock;
777
778         ast_debug(5, "EWS: ewscal_load_calendar()\n");
779
780         if (!(cal && (cfg = ast_calendar_config_acquire()))) {
781                 ast_log(LOG_ERROR, "You must enable calendar support for res_ewscal to load\n");
782                 return NULL;
783         }
784
785         if (ao2_trylock(cal)) {
786                 if (cal->unloading) {
787                         ast_log(LOG_WARNING, "Unloading module, load_calendar cancelled.\n");
788                 } else {
789                         ast_log(LOG_WARNING, "Could not lock calendar, aborting!\n");
790                 }
791                 ast_calendar_config_release();
792                 return NULL;
793         }
794
795         if (!(pvt = ao2_alloc(sizeof(*pvt), ewscal_destructor))) {
796                 ast_log(LOG_ERROR, "Could not allocate ewscal_pvt structure for calendar: %s\n", cal->name);
797                 ast_calendar_config_release();
798                 return NULL;
799         }
800
801         pvt->owner = cal;
802
803         if (!(pvt->events = ast_calendar_event_container_alloc())) {
804                 ast_log(LOG_ERROR, "Could not allocate space for fetching events for calendar: %s\n", cal->name);
805                 pvt = unref_ewscal(pvt);
806                 ao2_unlock(cal);
807                 ast_calendar_config_release();
808                 return NULL;
809         }
810
811         if (ast_string_field_init(pvt, 32)) {
812                 ast_log(LOG_ERROR, "Couldn't allocate string field space for calendar: %s\n", cal->name);
813                 pvt = unref_ewscal(pvt);
814                 ao2_unlock(cal);
815                 ast_calendar_config_release();
816                 return NULL;
817         }
818
819         for (v = ast_variable_browse(cfg, cal->name); v; v = v->next) {
820                 if (!strcasecmp(v->name, "url")) {
821                         ast_string_field_set(pvt, url, v->value);
822                 } else if (!strcasecmp(v->name, "user")) {
823                         ast_string_field_set(pvt, user, v->value);
824                 } else if (!strcasecmp(v->name, "secret")) {
825                         ast_string_field_set(pvt, secret, v->value);
826                 }
827         }
828
829         ast_calendar_config_release();
830
831         if (ast_strlen_zero(pvt->url)) {
832                 ast_log(LOG_WARNING, "No URL was specified for Exchange Web Service calendar '%s' - skipping.\n", cal->name);
833                 pvt = unref_ewscal(pvt);
834                 ao2_unlock(cal);
835                 return NULL;
836         }
837
838         if (ne_uri_parse(pvt->url, &pvt->uri) || pvt->uri.host == NULL || pvt->uri.path == NULL) {
839                 ast_log(LOG_WARNING, "Could not parse url '%s' for Exchange Web Service calendar '%s' - skipping.\n", pvt->url, cal->name);
840                 pvt = unref_ewscal(pvt);
841                 ao2_unlock(cal);
842                 return NULL;
843         }
844
845         if (pvt->uri.scheme == NULL) {
846                 pvt->uri.scheme = "http";
847         }
848
849         if (pvt->uri.port == 0) {
850                 pvt->uri.port = ne_uri_defaultport(pvt->uri.scheme);
851         }
852
853         ast_debug(3, "ne_uri.scheme     = %s\n", pvt->uri.scheme);
854         ast_debug(3, "ne_uri.host       = %s\n", pvt->uri.host);
855         ast_debug(3, "ne_uri.port       = %u\n", pvt->uri.port);
856         ast_debug(3, "ne_uri.path       = %s\n", pvt->uri.path);
857         ast_debug(3, "user              = %s\n", pvt->user);
858         ast_debug(3, "secret            = %s\n", pvt->secret);
859
860         pvt->session = ne_session_create(pvt->uri.scheme, pvt->uri.host, pvt->uri.port);
861         ne_redirect_register(pvt->session);
862         ne_set_server_auth(pvt->session, auth_credentials, pvt);
863         ne_set_useragent(pvt->session, "Asterisk");
864
865         if (!strcasecmp(pvt->uri.scheme, "https")) {
866                 ne_ssl_trust_default_ca(pvt->session);
867                 ne_ssl_set_verify(pvt->session, ssl_verify, pvt);
868         }
869
870         cal->tech_pvt = pvt;
871
872         ast_mutex_init(&refreshlock);
873
874         /* Load it the first time */
875         update_ewscal(pvt);
876
877         ao2_unlock(cal);
878
879         /* The only writing from another thread will be if unload is true */
880         for (;;) {
881                 struct timeval tv = ast_tvnow();
882                 struct timespec ts = {0,};
883
884                 ts.tv_sec = tv.tv_sec + (60 * pvt->owner->refresh);
885
886                 ast_mutex_lock(&refreshlock);
887                 while (!pvt->owner->unloading) {
888                         if (ast_cond_timedwait(&pvt->owner->unload, &refreshlock, &ts) == ETIMEDOUT) {
889                                 break;
890                         }
891                 }
892                 ast_mutex_unlock(&refreshlock);
893
894                 if (pvt->owner->unloading) {
895                         ast_debug(10, "Skipping refresh since we got a shutdown signal\n");
896                         return NULL;
897                 }
898
899                 ast_debug(10, "Refreshing after %d minute timeout\n", pvt->owner->refresh);
900
901                 update_ewscal(pvt);
902         }
903
904         return NULL;
905 }
906
907 static int load_module(void)
908 {
909         /* Actualy, 0.29.1 is required (because of NTLM authentication), but this
910          * function does not support matching patch version. */
911         if (ne_version_match(0, 29)) {
912                 ast_log(LOG_ERROR, "Exchange Web Service calendar module require neon >= 0.29.1, but %s is installed.\n", ne_version_string());
913                 return AST_MODULE_LOAD_DECLINE;
914         }
915
916         if (ast_calendar_register(&ewscal_tech) && (ne_sock_init() == 0)) {
917                 return AST_MODULE_LOAD_DECLINE;
918         }
919
920         return AST_MODULE_LOAD_SUCCESS;
921 }
922
923 static int unload_module(void)
924 {
925         ne_sock_exit();
926         ast_calendar_unregister(&ewscal_tech);
927
928         return 0;
929 }
930
931 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "Asterisk MS Exchange Web Service Calendar Integration",
932         .load = load_module,
933         .unload = unload_module,
934         .load_pri = AST_MODPRI_DEVSTATE_PLUGIN,
935 );