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