Asterisk - The Open Source Telephony Project GIT-master-d856a3e
res_calendar_ews.c
Go to the documentation of this file.
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>res_calendar</depend>
25 <depend>neon29</depend>
26 <support_level>extended</support_level>
27***/
28
29#include "asterisk.h"
30
31#include <ne_request.h>
32#include <ne_session.h>
33#include <ne_uri.h>
34#include <ne_socket.h>
35#include <ne_auth.h>
36#include <ne_xml.h>
37#include <ne_xmlreq.h>
38#include <ne_utils.h>
39#include <ne_redirect.h>
40
41#include "asterisk/module.h"
42#include "asterisk/channel.h"
43#include "asterisk/calendar.h"
44#include "asterisk/lock.h"
45#include "asterisk/config.h"
46#include "asterisk/astobj2.h"
47
48static void *ewscal_load_calendar(void *data);
49static void *unref_ewscal(void *obj);
51
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
61enum xml_op {
65};
66
68 struct ast_str *id;
70};
71
73 ne_xml_parser *parser;
74 struct ast_str *cdata;
76 enum xml_op op;
77 struct ewscal_pvt *pvt;
79};
80
81/* Important states of XML parsing */
82enum {
98};
99
105 );
107 ne_uri uri;
108 ne_session *session;
110 unsigned int items;
111};
112
113static void ewscal_destructor(void *obj)
114{
115 struct ewscal_pvt *pvt = obj;
116
117 ast_debug(1, "Destroying pvt for Exchange Web Service calendar %s\n", "pvt->owner->name");
118 if (pvt->session) {
119 ne_session_destroy(pvt->session);
120 }
121 ne_uri_free(&pvt->uri);
123
125
126 ao2_ref(pvt->events, -1);
127}
128
129static 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
138static 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
153static 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
163static 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
175static 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 */
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
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
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);
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);
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);
310 } else if (!strcmp(name, "RequiredAttendees") || !strcmp(name, "OptionalAttendees")) {
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);
322 }
323
324 return NE_XML_DECLINE;
325}
326
327static 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 !!! */
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");
360 }
361 else if (!strcmp(data, "Tentative")) {
362 ast_debug(3, "EWS: XML: Busy: tentative\n");
364 }
365 else {
366 ast_debug(3, "EWS: XML: Busy: no\n");
368 }
369 break;
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
386static 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 */
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)))) {
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);
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");
464 }
465 }
466
467 return 0;
468}
469
470static 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
483static const char *msstatus(enum ast_calendar_busy_state state)
484{
485 switch (state) {
487 return "Tentative";
489 return "Busy";
491 return "Free";
492 default:
493 return "";
494 }
495}
496
497static 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
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
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
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
644
646
647 return ret;
648}
649
650static 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
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
701
702 /* Dispatch request and parse response as XML */
705 return NULL;
706 }
707
708 /* Cleanup */
710
711 return AST_LIST_FIRST(&ctx.ids);
712}
713
714static 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
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
744 return -1;
745 }
746
748
749 return 0;
750}
751
752static 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
770static 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;
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 }
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);
798 return NULL;
799 }
800
801 pvt->owner = cal;
802
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);
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);
816 return NULL;
817 }
818
819 for (v = ast_variable_browse(cfg, cal->name); v; v = v->next) {
820 if (!strcasecmp(v->name, "url")) {
822 } else if (!strcasecmp(v->name, "user")) {
824 } else if (!strcasecmp(v->name, "secret")) {
825 ast_string_field_set(pvt, secret, v->value);
826 }
827 }
828
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
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
887 while (!pvt->owner->unloading) {
888 if (ast_cond_timedwait(&pvt->owner->unload, &refreshlock, &ts) == ETIMEDOUT) {
889 break;
890 }
891 }
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
907static 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 *
912 * The ne_version_match function returns non-zero if the library
913 * version is not of major version major, or the minor version
914 * is less than minor. For neon versions 0.x, every minor
915 * version is assumed to be incompatible with every other minor
916 * version.
917 *
918 * I.e. for version 1.2..1.9 we would do ne_version_match(1, 2)
919 * but for version 0.29 and 0.30 we need two checks. */
920 if (ne_version_match(0, 29) && ne_version_match(0, 30)) {
921 ast_log(LOG_ERROR, "Exchange Web Service calendar module require neon >= 0.29.1, but %s is installed.\n", ne_version_string());
923 }
924
925 if (ast_calendar_register(&ewscal_tech) && (ne_sock_init() == 0)) {
927 }
928
930}
931
932static int unload_module(void)
933{
934 ne_sock_exit();
936
937 return 0;
938}
939
940AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "Asterisk MS Exchange Web Service Calendar Integration",
941 .support_level = AST_MODULE_SUPPORT_EXTENDED,
942 .load = load_module,
943 .unload = unload_module,
944 .load_pri = AST_MODPRI_DEVSTATE_PLUGIN,
945 .requires = "res_calendar",
#define AST_MODULE
enum queue_result id
Definition: app_queue.c:1638
Asterisk main include file. File version handling, generic pbx functions.
#define ast_free(a)
Definition: astmm.h:180
#define ast_strdup(str)
A wrapper for strdup()
Definition: astmm.h:241
#define ast_strdupa(s)
duplicate a string in memory from the stack
Definition: astmm.h:298
#define ast_calloc(num, len)
A wrapper for calloc()
Definition: astmm.h:202
#define ast_log
Definition: astobj2.c:42
#define ao2_link(container, obj)
Add an object to a container.
Definition: astobj2.h:1532
#define ao2_callback(c, flags, cb_fn, arg)
ao2_callback() is a generic function that applies cb_fn() to all objects in a container,...
Definition: astobj2.h:1693
int ao2_container_count(struct ao2_container *c)
Returns the number of elements in a container.
#define ao2_unlock(a)
Definition: astobj2.h:729
#define ao2_ref(o, delta)
Reference/unreference an object and return the old refcount.
Definition: astobj2.h:459
#define ao2_trylock(a)
Definition: astobj2.h:739
@ OBJ_NODATA
Definition: astobj2.h:1044
@ OBJ_MULTIPLE
Definition: astobj2.h:1049
@ OBJ_UNLINK
Definition: astobj2.h:1039
#define ao2_alloc(data_size, destructor_fn)
Definition: astobj2.h:409
A general API for managing calendar events with Asterisk.
void ast_calendar_unregister(struct ast_calendar_tech *tech)
Unregister a new calendar technology.
Definition: res_calendar.c:589
void ast_calendar_merge_events(struct ast_calendar *cal, struct ao2_container *new_events)
Add an event to the list of events for a calendar.
void ast_calendar_config_release(void)
Release the calendar config.
Definition: res_calendar.c:272
ast_calendar_busy_state
Definition: calendar.h:83
@ AST_CALENDAR_BS_FREE
Definition: calendar.h:84
@ AST_CALENDAR_BS_BUSY_TENTATIVE
Definition: calendar.h:85
@ AST_CALENDAR_BS_BUSY
Definition: calendar.h:86
struct ao2_container * ast_calendar_event_container_alloc(void)
Allocate an astobj2 container for ast_calendar_event objects.
Definition: res_calendar.c:691
int ast_calendar_register(struct ast_calendar_tech *tech)
Register a new calendar technology.
Definition: res_calendar.c:551
struct ast_calendar_event * ast_calendar_event_alloc(struct ast_calendar *cal)
Allocate an astobj2 ast_calendar_event object.
Definition: res_calendar.c:669
struct ast_calendar_event * ast_calendar_unref_event(struct ast_calendar_event *event)
Unreference an ast_calendar_event.
Definition: res_calendar.c:323
const struct ast_config * ast_calendar_config_acquire(void)
Grab and lock pointer to the calendar config (read only)
Definition: res_calendar.c:260
static int request(void *obj)
Definition: chan_pjsip.c:2605
General Asterisk PBX channel definitions.
char * end
Definition: eagi_proxy.c:73
char buf[BUFSIZE]
Definition: eagi_proxy.c:66
static const char name[]
Definition: format_mp3.c:68
static int len(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t buflen)
char * strsep(char **str, const char *delims)
Configuration File Parser.
struct ast_variable * ast_variable_browse(const struct ast_config *config, const char *category_name)
Definition: extconf.c:1215
#define ast_debug(level,...)
Log a DEBUG message.
#define LOG_ERROR
#define LOG_WARNING
#define AST_LIST_HEAD_INIT_NOLOCK(head)
Initializes a list head structure.
Definition: linkedlists.h:681
#define AST_LIST_HEAD_NOLOCK(name, type)
Defines a structure to be used to hold a list of specified type (with no lock).
Definition: linkedlists.h:225
#define AST_LIST_INSERT_TAIL(head, elm, field)
Appends a list entry to the tail of a list.
Definition: linkedlists.h:731
#define AST_LIST_ENTRY(type)
Declare a forward link structure inside a list entry.
Definition: linkedlists.h:410
#define AST_LIST_FIRST(head)
Returns the first entry contained in a list.
Definition: linkedlists.h:421
#define AST_LIST_NEXT(elm, field)
Returns the next entry in the list after the given entry.
Definition: linkedlists.h:439
struct ast_tm * ast_localtime(const struct timeval *timep, struct ast_tm *p_tm, const char *zone)
Timezone-independent version of localtime_r(3).
Definition: localtime.c:1739
int ast_strftime(char *buf, size_t len, const char *format, const struct ast_tm *tm)
Special version of strftime(3) that handles fractions of a second. Takes the same arguments as strfti...
Definition: localtime.c:2524
struct timeval ast_mktime(struct ast_tm *const tmp, const char *zone)
Timezone-independent version of mktime(3).
Definition: localtime.c:2357
char * ast_strptime(const char *s, const char *format, struct ast_tm *tm)
Special version of strptime(3) which places the answer in the common structure ast_tm....
Definition: localtime.c:2550
Asterisk locking-related definitions:
#define ast_cond_timedwait(cond, mutex, time)
Definition: lock.h:206
#define ast_mutex_init(pmutex)
Definition: lock.h:186
#define ast_mutex_unlock(a)
Definition: lock.h:190
#define ast_mutex_lock(a)
Definition: lock.h:189
Asterisk module definitions.
@ AST_MODFLAG_LOAD_ORDER
Definition: module.h:331
#define AST_MODULE_INFO(keystr, flags_to_set, desc, fields...)
Definition: module.h:557
@ AST_MODPRI_DEVSTATE_PLUGIN
Definition: module.h:344
@ AST_MODULE_SUPPORT_EXTENDED
Definition: module.h:122
#define ASTERISK_GPL_KEY
The text the key() function should return.
Definition: module.h:46
@ AST_MODULE_LOAD_SUCCESS
Definition: module.h:70
@ AST_MODULE_LOAD_DECLINE
Module has failed to load, may be in an inconsistent state.
Definition: module.h:78
static ast_mutex_t refreshlock
Definition: res_calendar.c:227
static int cdata(void *userdata, int state, const char *cdata, size_t len)
static int startelm(void *userdata, int parent, const char *nspace, const char *name, const char **atts)
static void * unref_ewscal(void *obj)
static time_t mstime_to_time_t(char *mstime)
xml_op
@ XML_OP_GET
@ XML_OP_FIND
@ XML_OP_CREATE
static int update_ewscal(struct ewscal_pvt *pvt)
static const char * get_soap_action(enum xml_op op)
static void * ewscal_load_calendar(void *data)
static int send_ews_request_and_parse(struct ast_str *request, struct xml_context *ctx)
static int ssl_verify(void *userdata, int failures, const ne_ssl_certificate *cert)
static struct ast_calendar_tech ewscal_tech
static const char * mstime(time_t t, char *buf, size_t buflen)
static int auth_credentials(void *userdata, const char *realm, int attempts, char *username, char *secret)
static void ewscal_destructor(void *obj)
static int load_module(void)
static const char * msstatus(enum ast_calendar_busy_state state)
static int parse_ewscal_id(struct ewscal_pvt *pvt, const char *id)
static int unload_module(void)
static int ewscal_write_event(struct ast_calendar_event *event)
static struct calendar_id * get_ewscal_ids_for(struct ewscal_pvt *pvt)
static int endelm(void *userdata, int state, const char *nspace, const char *name)
@ XML_EVENT_EMAIL_ADDRESS
@ XML_EVENT_IMPORTANCE
@ XML_EVENT_BUSY
@ XML_EVENT_ATTENDEE_LIST
@ XML_EVENT_DESCRIPTION
@ XML_EVENT_END
@ XML_EVENT_CALENDAR_ITEM
@ XML_EVENT_CATEGORY
@ XML_EVENT_START
@ XML_EVENT_LOCATION
@ XML_EVENT_CATEGORIES
@ XML_EVENT_MAILBOX
@ XML_EVENT_NAME
@ XML_EVENT_ORGANIZER
@ XML_EVENT_ATTENDEE
static char url[512]
#define NULL
Definition: resample.c:96
#define AST_DECLARE_STRING_FIELDS(field_list)
Declare the fields needed in a structure.
Definition: stringfields.h:341
#define AST_STRING_FIELD(name)
Declare a string field.
Definition: stringfields.h:303
#define ast_string_field_set(x, field, data)
Set a field to a simple string value.
Definition: stringfields.h:521
#define ast_string_field_init(x, size)
Initialize a field pool and fields.
Definition: stringfields.h:359
#define ast_string_field_free_memory(x)
free all memory - to be called before destroying the object
Definition: stringfields.h:374
int ast_str_append(struct ast_str **buf, ssize_t max_len, const char *fmt,...)
Append to a thread local dynamic string.
Definition: strings.h:1139
char * ast_str_buffer(const struct ast_str *buf)
Returns the string buffer within the ast_str buf.
Definition: strings.h:761
#define S_OR(a, b)
returns the equivalent of logic or for strings: first one if not empty, otherwise second one.
Definition: strings.h:80
static force_inline int attribute_pure ast_strlen_zero(const char *s)
Definition: strings.h:65
void ast_str_reset(struct ast_str *buf)
Reset the content of a dynamic string. Useful before a series of ast_str_append.
Definition: strings.h:693
#define ast_str_create(init_len)
Create a malloc'ed dynamic length string.
Definition: strings.h:659
int ast_str_set(struct ast_str **buf, ssize_t max_len, const char *fmt,...)
Set a dynamic string using variable arguments.
Definition: strings.h:1113
size_t ast_str_strlen(const struct ast_str *buf)
Returns the current length of the string stored within buf.
Definition: strings.h:730
void ast_copy_string(char *dst, const char *src, size_t size)
Size-limited null-terminating string copy.
Definition: strings.h:425
Generic container type.
struct ast_calendar_attendee * next
Definition: calendar.h:91
Calendar events.
Definition: calendar.h:95
enum ast_calendar_busy_state busy_state
Definition: calendar.h:109
const ast_string_field location
Definition: calendar.h:103
const ast_string_field description
Definition: calendar.h:103
const ast_string_field categories
Definition: calendar.h:103
const ast_string_field organizer
Definition: calendar.h:103
struct ast_calendar_event::attendees attendees
const ast_string_field summary
Definition: calendar.h:103
Individual calendaring technology data.
Definition: calendar.h:71
const char * type
Definition: calendar.h:72
Asterisk calendar structure.
Definition: calendar.h:119
void * tech_pvt
Definition: calendar.h:121
int timeframe
Definition: calendar.h:135
ast_cond_t unload
Definition: calendar.h:137
unsigned int unloading
Definition: calendar.h:138
const ast_string_field name
Definition: calendar.h:129
Structure for mutex and tracking information.
Definition: lock.h:135
Support for dynamic strings.
Definition: strings.h:623
Structure for variables, used for configurations and for channel variables.
struct ast_variable * next
struct ast_str * id
struct calendar_id * next
Definition: astman.c:222
const ast_string_field url
struct ast_calendar * owner
const ast_string_field user
struct ao2_container * events
ne_session * session
const ast_string_field secret
unsigned int items
structure to hold users read from users.conf
struct ewscal_pvt * pvt
struct ast_calendar_event * event
ne_xml_parser * parser
enum xml_op op
struct xml_context::ids ids
struct ast_str * cdata
struct association categories[]
struct timeval ast_tvnow(void)
Returns current timeval. Meant to replace calls to gettimeofday().
Definition: time.h:159