Wed Jan 8 2020 09:49:49

Asterisk developer's documentation


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