Asterisk - The Open Source Telephony Project  18.5.0
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 <[email protected]>
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 
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 {
104  AST_STRING_FIELD(secret);
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  }
121  ne_uri_free(&pvt->uri);
123 
125 
126  ao2_ref(pvt->events, -1);
127 }
128 
129 static void *unref_ewscal(void *obj)
130 {
131  struct ewscal_pvt *pvt = obj;
132 
133  ast_debug(5, "EWS: unref_ewscal()\n");
134  ao2_ref(pvt, -1);
135  return NULL;
136 }
137 
138 static int auth_credentials(void *userdata, const char *realm, int attempts, char *username, char *secret)
139 {
140  struct ewscal_pvt *pvt = userdata;
141 
142  if (attempts > 1) {
143  ast_log(LOG_WARNING, "Invalid username or password for Exchange Web Service calendar '%s'\n", pvt->owner->name);
144  return -1;
145  }
146 
147  ne_strnzcpy(username, pvt->user, NE_ABUFSIZ);
148  ne_strnzcpy(secret, pvt->secret, NE_ABUFSIZ);
149 
150  return 0;
151 }
152 
153 static int ssl_verify(void *userdata, int failures, const ne_ssl_certificate *cert)
154 {
155  struct ewscal_pvt *pvt = userdata;
156  if (failures & NE_SSL_UNTRUSTED) {
157  ast_log(LOG_WARNING, "Untrusted SSL certificate for calendar %s!\n", pvt->owner->name);
158  return 0;
159  }
160  return 1; /* NE_SSL_NOTYETVALID, NE_SSL_EXPIRED, NE_SSL_IDMISMATCH */
161 }
162 
163 static time_t mstime_to_time_t(char *mstime)
164 {
165  struct ast_tm tm;
166  struct timeval tv;
167 
168  if (ast_strptime(mstime, "%FT%TZ", &tm)) {
169  tv = ast_mktime(&tm, "UTC");
170  return tv.tv_sec;
171  }
172  return 0;
173 }
174 
175 static int startelm(void *userdata, int parent, const char *nspace, const char *name, const char **atts)
176 {
177  struct xml_context *ctx = userdata;
178 
179  ast_debug(5, "EWS: XML: Start: %s\n", name);
180  if (ctx->op == XML_OP_CREATE) {
181  return NE_XML_DECLINE;
182  }
183 
184  /* Nodes needed for traversing until CalendarItem is found */
185  if (!strcmp(name, "Envelope") ||
186  (!strcmp(name, "Body") && parent != XML_EVENT_CALENDAR_ITEM) ||
187  !strcmp(name, "FindItemResponse") ||
188  !strcmp(name, "GetItemResponse") ||
189  !strcmp(name, "CreateItemResponse") ||
190  !strcmp(name, "ResponseMessages") ||
191  !strcmp(name, "FindItemResponseMessage") || !strcmp(name, "GetItemResponseMessage") ||
192  !strcmp(name, "Items")
193  ) {
194  return 1;
195  } else if (!strcmp(name, "RootFolder")) {
196  /* Get number of events */
197  unsigned int items;
198 
199  ast_debug(3, "EWS: XML: <RootFolder>\n");
200  if (sscanf(ne_xml_get_attr(ctx->parser, atts, NULL, "TotalItemsInView"), "%u", &items) != 1) {
201  /* Couldn't read enything */
202  ne_xml_set_error(ctx->parser, "Could't read number of events.");
203  return NE_XML_ABORT;
204  }
205 
206  ast_debug(3, "EWS: %u calendar items to load\n", items);
207  ctx->pvt->items = items;
208  if (items < 1) {
209  /* Stop processing XML if there are no events */
211  return NE_XML_DECLINE;
212  }
213  return 1;
214  } else if (!strcmp(name, "CalendarItem")) {
215  /* Event start */
216  ast_debug(3, "EWS: XML: <CalendarItem>\n");
217  if (!(ctx->pvt && ctx->pvt->owner)) {
218  ast_log(LOG_ERROR, "Require a private structure with an owner\n");
219  return NE_XML_ABORT;
220  }
221 
222  ctx->event = ast_calendar_event_alloc(ctx->pvt->owner);
223  if (!ctx->event) {
224  ast_log(LOG_ERROR, "Could not allocate an event!\n");
225  return NE_XML_ABORT;
226  }
227 
228  ctx->cdata = ast_str_create(64);
229  if (!ctx->cdata) {
230  ast_log(LOG_ERROR, "Could not allocate CDATA!\n");
231  return NE_XML_ABORT;
232  }
233 
235  } else if (!strcmp(name, "ItemId")) {
236  /* Event UID */
237  if (ctx->op == XML_OP_FIND) {
238  struct calendar_id *id;
239  if (!(id = ast_calloc(1, sizeof(*id)))) {
240  return NE_XML_ABORT;
241  }
242  if (!(id->id = ast_str_create(256))) {
243  ast_free(id);
244  return NE_XML_ABORT;
245  }
246  ast_str_set(&id->id, 0, "%s", ne_xml_get_attr(ctx->parser, atts, NULL, "Id"));
247  AST_LIST_INSERT_TAIL(&ctx->ids, id, next);
248  ast_debug(3, "EWS_FIND: XML: UID: %s\n", ast_str_buffer(id->id));
249  } else {
250  ast_debug(3, "EWS_GET: XML: UID: %s\n", ne_xml_get_attr(ctx->parser, atts, NULL, "Id"));
251  ast_string_field_set(ctx->event, uid, ne_xml_get_attr(ctx->parser, atts, NULL, "Id"));
252  }
253  return XML_EVENT_NAME;
254  } else if (!strcmp(name, "Subject")) {
255  /* Event name */
256  if (!ctx->cdata) {
257  return NE_XML_ABORT;
258  }
259  ast_str_reset(ctx->cdata);
260  return XML_EVENT_NAME;
261  } else if (!strcmp(name, "Body") && parent == XML_EVENT_CALENDAR_ITEM) {
262  /* Event body/description */
263  if (!ctx->cdata) {
264  return NE_XML_ABORT;
265  }
266  ast_str_reset(ctx->cdata);
267  return XML_EVENT_DESCRIPTION;
268  } else if (!strcmp(name, "Start")) {
269  /* Event start time */
270  return XML_EVENT_START;
271  } else if (!strcmp(name, "End")) {
272  /* Event end time */
273  return XML_EVENT_END;
274  } else if (!strcmp(name, "LegacyFreeBusyStatus")) {
275  /* Event busy state */
276  return XML_EVENT_BUSY;
277  } else if (!strcmp(name, "Organizer") ||
278  (parent == XML_EVENT_ORGANIZER && (!strcmp(name, "Mailbox") ||
279  !strcmp(name, "Name")))) {
280  /* Event organizer */
281  if (!ctx->cdata) {
282  return NE_XML_ABORT;
283  }
284  ast_str_reset(ctx->cdata);
285  return XML_EVENT_ORGANIZER;
286  } else if (!strcmp(name, "Location")) {
287  /* Event location */
288  if (!ctx->cdata) {
289  return NE_XML_ABORT;
290  }
291  ast_str_reset(ctx->cdata);
292  return XML_EVENT_LOCATION;
293  } else if (!strcmp(name, "Categories")) {
294  /* Event categories */
295  if (!ctx->cdata) {
296  return NE_XML_ABORT;
297  }
298  ast_str_reset(ctx->cdata);
299  return XML_EVENT_CATEGORIES;
300  } else if (parent == XML_EVENT_CATEGORIES && !strcmp(name, "String")) {
301  /* Event category */
302  return XML_EVENT_CATEGORY;
303  } else if (!strcmp(name, "Importance")) {
304  /* Event importance (priority) */
305  if (!ctx->cdata) {
306  return NE_XML_ABORT;
307  }
308  ast_str_reset(ctx->cdata);
309  return XML_EVENT_IMPORTANCE;
310  } else if (!strcmp(name, "RequiredAttendees") || !strcmp(name, "OptionalAttendees")) {
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 
327 static int cdata(void *userdata, int state, const char *cdata, size_t len)
328 {
329  struct xml_context *ctx = userdata;
330  char data[len + 1];
331 
332  /* !!! DON'T USE AST_STRING_FIELD FUNCTIONS HERE, JUST COLLECT CTX->CDATA !!! */
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;
370  case XML_EVENT_CATEGORY:
371  if (ast_str_strlen(ctx->cdata) == 0) {
372  ast_str_set(&ctx->cdata, 0, "%s", data);
373  } else {
374  ast_str_append(&ctx->cdata, 0, ",%s", data);
375  }
376  break;
377  default:
378  ast_str_append(&ctx->cdata, 0, "%s", data);
379  }
380 
381  ast_debug(5, "EWS: XML: CDATA: %s\n", ast_str_buffer(ctx->cdata));
382 
383  return 0;
384 }
385 
386 static int endelm(void *userdata, int state, const char *nspace, const char *name)
387 {
388  struct xml_context *ctx = userdata;
389 
390  ast_debug(5, "EWS: XML: End: %s\n", name);
391  if (ctx->op == XML_OP_FIND || ctx->op == XML_OP_CREATE) {
392  return NE_XML_DECLINE;
393  }
394 
395  if (!strcmp(name, "Subject")) {
396  /* Event name end*/
397  ast_string_field_set(ctx->event, summary, ast_str_buffer(ctx->cdata));
398  ast_debug(3, "EWS: XML: Summary: %s\n", ctx->event->summary);
399  ast_str_reset(ctx->cdata);
400  } else if (!strcmp(name, "Body") && state == XML_EVENT_DESCRIPTION) {
401  /* Event body/description end */
402  ast_string_field_set(ctx->event, description, ast_str_buffer(ctx->cdata));
403  ast_debug(3, "EWS: XML: Description: %s\n", ctx->event->description);
404  ast_str_reset(ctx->cdata);
405  } else if (!strcmp(name, "Organizer")) {
406  /* Event organizer end */
407  ast_string_field_set(ctx->event, organizer, ast_str_buffer(ctx->cdata));
408  ast_debug(3, "EWS: XML: Organizer: %s\n", ctx->event->organizer);
409  ast_str_reset(ctx->cdata);
410  } else if (!strcmp(name, "Location")) {
411  /* Event location end */
412  ast_string_field_set(ctx->event, location, ast_str_buffer(ctx->cdata));
413  ast_debug(3, "EWS: XML: Location: %s\n", ctx->event->location);
414  ast_str_reset(ctx->cdata);
415  } else if (!strcmp(name, "Categories")) {
416  /* Event categories end */
418  ast_debug(3, "EWS: XML: Categories: %s\n", ctx->event->categories);
419  ast_str_reset(ctx->cdata);
420  } else if (!strcmp(name, "Importance")) {
421  /* Event importance end */
422  if (!strcmp(ast_str_buffer(ctx->cdata), "Low")) {
423  ctx->event->priority = 9;
424  } else if (!strcmp(ast_str_buffer(ctx->cdata), "Normal")) {
425  ctx->event->priority = 5;
426  } else if (!strcmp(ast_str_buffer(ctx->cdata), "High")) {
427  ctx->event->priority = 1;
428  }
429  ast_debug(3, "EWS: XML: Importance: %s (%d)\n", ast_str_buffer(ctx->cdata), ctx->event->priority);
430  ast_str_reset(ctx->cdata);
431  } else if (state == XML_EVENT_EMAIL_ADDRESS) {
432  struct ast_calendar_attendee *attendee;
433 
434  if (!(attendee = ast_calloc(1, sizeof(*attendee)))) {
435  ctx->event = ast_calendar_unref_event(ctx->event);
436  return 1;
437  }
438 
439  if (ast_str_strlen(ctx->cdata)) {
440  attendee->data = ast_strdup(ast_str_buffer(ctx->cdata));
441  AST_LIST_INSERT_TAIL(&ctx->event->attendees, attendee, next);
442  } else {
443  ast_free(attendee);
444  }
445  ast_debug(3, "EWS: XML: attendee address '%s'\n", ast_str_buffer(ctx->cdata));
446  ast_str_reset(ctx->cdata);
447  } else if (!strcmp(name, "CalendarItem")) {
448  /* Event end */
449  ast_debug(3, "EWS: XML: </CalendarItem>\n");
450  ast_free(ctx->cdata);
451  if (ctx->event) {
452  ao2_link(ctx->pvt->events, ctx->event);
453  ctx->event = ast_calendar_unref_event(ctx->event);
454  } else {
455  ast_log(LOG_ERROR, "Event data ended in XML, but event object does not exist!\n");
456  return 1;
457  }
458  } else if (!strcmp(name, "Envelope")) {
459  /* Events end */
460  ast_debug(3, "EWS: XML: %d of %u event(s) has been parsed…\n", ao2_container_count(ctx->pvt->events), ctx->pvt->items);
461  if (ao2_container_count(ctx->pvt->events) >= ctx->pvt->items) {
462  ast_debug(3, "EWS: XML: All events has been parsed, merging…\n");
464  }
465  }
466 
467  return 0;
468 }
469 
470 static const char *mstime(time_t t, char *buf, size_t buflen)
471 {
472  struct timeval tv = {
473  .tv_sec = t,
474  };
475  struct ast_tm tm;
476 
477  ast_localtime(&tv, &tm, "utc");
478  ast_strftime(buf, buflen, "%FT%TZ", &tm);
479 
480  return S_OR(buf, "");
481 }
482 
483 static const char *msstatus(enum ast_calendar_busy_state state)
484 {
485  switch (state) {
487  return "Tentative";
489  return "Busy";
491  return "Free";
492  default:
493  return "";
494  }
495 }
496 
497 static const char *get_soap_action(enum xml_op op)
498 {
499  switch (op) {
500  case XML_OP_FIND:
501  return "\"http://schemas.microsoft.com/exchange/services/2006/messages/FindItem\"";
502  case XML_OP_GET:
503  return "\"http://schemas.microsoft.com/exchange/services/2006/messages/GetItem\"";
504  case XML_OP_CREATE:
505  return "\"http://schemas.microsoft.com/exchange/services/2006/messages/CreateItem\"";
506  }
507 
508  return "";
509 }
510 
511 static int send_ews_request_and_parse(struct ast_str *request, struct xml_context *ctx)
512 {
513  int ret;
514  ne_request *req;
515  ne_xml_parser *parser;
516 
517  ast_debug(3, "EWS: HTTP request...\n");
518  if (!(ctx && ctx->pvt)) {
519  ast_log(LOG_ERROR, "There is no private!\n");
520  return -1;
521  }
522 
523  if (!ast_str_strlen(request)) {
524  ast_log(LOG_ERROR, "No request to send!\n");
525  return -1;
526  }
527 
528  ast_debug(3, "%s\n", ast_str_buffer(request));
529 
530  /* Prepare HTTP POST request */
531  req = ne_request_create(ctx->pvt->session, "POST", ctx->pvt->uri.path);
532  ne_set_request_flag(req, NE_REQFLAG_IDEMPOTENT, 0);
533 
534  /* Set headers--should be application/soap+xml, but MS… :/ */
535  ne_add_request_header(req, "Content-Type", "text/xml; charset=utf-8");
536  ne_add_request_header(req, "SOAPAction", get_soap_action(ctx->op));
537 
538  /* Set body to SOAP request */
539  ne_set_request_body_buffer(req, ast_str_buffer(request), ast_str_strlen(request));
540 
541  /* Prepare XML parser */
542  parser = ne_xml_create();
543  ctx->parser = parser;
544  ne_xml_push_handler(parser, startelm, cdata, endelm, ctx); /* Callbacks */
545 
546  /* Dispatch request and parse response as XML */
547  ret = ne_xml_dispatch_request(req, parser);
548  if (ret != NE_OK) { /* Error handling */
549  ast_log(LOG_WARNING, "Unable to communicate with Exchange Web Service at '%s': %s\n", ctx->pvt->url, ne_get_error(ctx->pvt->session));
550  ne_request_destroy(req);
551  ne_xml_destroy(parser);
552  return -1;
553  }
554 
555  /* Cleanup */
556  ne_request_destroy(req);
557  ne_xml_destroy(parser);
558 
559  return 0;
560 }
561 
563 {
564  struct ast_str *request;
565  struct ewscal_pvt *pvt = event->owner->tech_pvt;
566  char start[21], end[21];
567  struct xml_context ctx = {
568  .op = XML_OP_CREATE,
569  .pvt = pvt,
570  };
571  int ret;
572  char *category, *categories;
573 
574  if (!pvt) {
575  return -1;
576  }
577 
578  if (!(request = ast_str_create(1024))) {
579  return -1;
580  }
581 
582  ast_str_set(&request, 0,
583  "<soap:Envelope xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" "
584  "xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" "
585  "xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\" "
586  "xmlns:t=\"http://schemas.microsoft.com/exchange/services/2006/types\">"
587  "<soap:Body>"
588  "<CreateItem xmlns=\"http://schemas.microsoft.com/exchange/services/2006/messages\" "
589  "xmlns:t=\"http://schemas.microsoft.com/exchange/services/2006/types\" "
590  "SendMeetingInvitations=\"SendToNone\" >"
591  "<SavedItemFolderId>"
592  "<t:DistinguishedFolderId Id=\"calendar\"/>"
593  "</SavedItemFolderId>"
594  "<Items>"
595  "<t:CalendarItem xmlns=\"http://schemas.microsoft.com/exchange/services/2006/types\">"
596  "<Subject>%s</Subject>"
597  "<Body BodyType=\"Text\">%s</Body>"
598  "<ReminderIsSet>false</ReminderIsSet>"
599  "<Start>%s</Start>"
600  "<End>%s</End>"
601  "<IsAllDayEvent>false</IsAllDayEvent>"
602  "<LegacyFreeBusyStatus>%s</LegacyFreeBusyStatus>"
603  "<Location>%s</Location>",
604  event->summary,
605  event->description,
606  mstime(event->start, start, sizeof(start)),
607  mstime(event->end, end, sizeof(end)),
608  msstatus(event->busy_state),
609  event->location
610  );
611  /* Event priority */
612  switch (event->priority) {
613  case 1:
614  case 2:
615  case 3:
616  case 4:
617  ast_str_append(&request, 0, "<Importance>High</Importance>");
618  break;
619  case 5:
620  ast_str_append(&request, 0, "<Importance>Normal</Importance>");
621  break;
622  case 6:
623  case 7:
624  case 8:
625  case 9:
626  ast_str_append(&request, 0, "<Importance>Low</Importance>");
627  break;
628  }
629  /* Event categories*/
630  if (strlen(event->categories) > 0) {
631  ast_str_append(&request, 0, "<Categories>");
632  categories = ast_strdupa(event->categories); /* Duplicate string, since strsep() is destructive */
633  category = strsep(&categories, ",");
634  while (category != NULL) {
635  ast_str_append(&request, 0, "<String>%s</String>", category);
636  category = strsep(&categories, ",");
637  }
638  ast_str_append(&request, 0, "</Categories>");
639  }
640  /* Finish request */
641  ast_str_append(&request, 0, "</t:CalendarItem></Items></CreateItem></soap:Body></soap:Envelope>");
642 
643  ret = send_ews_request_and_parse(request, &ctx);
644 
645  ast_free(request);
646 
647  return ret;
648 }
649 
650 static struct calendar_id *get_ewscal_ids_for(struct ewscal_pvt *pvt)
651 {
652  char start[21], end[21];
653  struct ast_tm tm;
654  struct timeval tv;
655  struct ast_str *request;
656  struct xml_context ctx = {
657  .op = XML_OP_FIND,
658  .pvt = pvt,
659  };
660 
661  ast_debug(5, "EWS: get_ewscal_ids_for()\n");
662 
663  if (!pvt) {
664  ast_log(LOG_ERROR, "There is no private!\n");
665  return NULL;
666  }
667 
668  /* Prepare timeframe strings */
669  tv = ast_tvnow();
670  ast_localtime(&tv, &tm, "UTC");
671  ast_strftime(start, sizeof(start), "%FT%TZ", &tm);
672  tv.tv_sec += 60 * pvt->owner->timeframe;
673  ast_localtime(&tv, &tm, "UTC");
674  ast_strftime(end, sizeof(end), "%FT%TZ", &tm);
675 
676  /* Prepare SOAP request */
677  if (!(request = ast_str_create(512))) {
678  return NULL;
679  }
680 
681  ast_str_set(&request, 0,
682  "<SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\" "
683  "xmlns:ns1=\"http://schemas.microsoft.com/exchange/services/2006/types\" "
684  "xmlns:ns2=\"http://schemas.microsoft.com/exchange/services/2006/messages\">"
685  "<SOAP-ENV:Body>"
686  "<ns2:FindItem Traversal=\"Shallow\">"
687  "<ns2:ItemShape>"
688  "<ns1:BaseShape>IdOnly</ns1:BaseShape>"
689  "</ns2:ItemShape>"
690  "<ns2:CalendarView StartDate=\"%s\" EndDate=\"%s\"/>" /* Timeframe */
691  "<ns2:ParentFolderIds>"
692  "<ns1:DistinguishedFolderId Id=\"calendar\"/>"
693  "</ns2:ParentFolderIds>"
694  "</ns2:FindItem>"
695  "</SOAP-ENV:Body>"
696  "</SOAP-ENV:Envelope>",
697  start, end /* Timeframe */
698  );
699 
701 
702  /* Dispatch request and parse response as XML */
703  if (send_ews_request_and_parse(request, &ctx)) {
704  ast_free(request);
705  return NULL;
706  }
707 
708  /* Cleanup */
709  ast_free(request);
710 
711  return AST_LIST_FIRST(&ctx.ids);
712 }
713 
714 static int parse_ewscal_id(struct ewscal_pvt *pvt, const char *id) {
715  struct ast_str *request;
716  struct xml_context ctx = {
717  .pvt = pvt,
718  .op = XML_OP_GET,
719  };
720 
721  if (!(request = ast_str_create(512))) {
722  return -1;
723  }
724 
725  ast_str_set(&request, 0,
726  "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
727  "<soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\" "
728  "xmlns:t=\"http://schemas.microsoft.com/exchange/services/2006/types\">"
729  "<soap:Body>"
730  "<GetItem xmlns=\"http://schemas.microsoft.com/exchange/services/2006/messages\">"
731  "<ItemShape>"
732  "<t:BaseShape>AllProperties</t:BaseShape>"
733  "</ItemShape>"
734  "<ItemIds>"
735  "<t:ItemId Id=\"%s\"/>"
736  "</ItemIds>"
737  "</GetItem>"
738  "</soap:Body>"
739  "</soap:Envelope>", id
740  );
741 
742  if (send_ews_request_and_parse(request, &ctx)) {
743  ast_free(request);
744  return -1;
745  }
746 
747  ast_free(request);
748 
749  return 0;
750 }
751 
752 static int update_ewscal(struct ewscal_pvt *pvt)
753 {
754  struct calendar_id *id_head;
755  struct calendar_id *iter;
756 
757  if (!(id_head = get_ewscal_ids_for(pvt))) {
758  return 0;
759  }
760 
761  for (iter = id_head; iter; iter = AST_LIST_NEXT(iter, next)) {
762  parse_ewscal_id(pvt, ast_str_buffer(iter->id));
763  ast_free(iter->id);
764  ast_free(iter);
765  }
766 
767  return 0;
768 }
769 
770 static void *ewscal_load_calendar(void *void_data)
771 {
772  struct ewscal_pvt *pvt;
773  const struct ast_config *cfg;
774  struct ast_variable *v;
775  struct ast_calendar *cal = void_data;
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 
803  if (!(pvt->events = ast_calendar_event_container_alloc())) {
804  ast_log(LOG_ERROR, "Could not allocate space for fetching events for calendar: %s\n", cal->name);
805  pvt = unref_ewscal(pvt);
806  ao2_unlock(cal);
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")) {
821  ast_string_field_set(pvt, url, v->value);
822  } else if (!strcasecmp(v->name, "user")) {
823  ast_string_field_set(pvt, user, v->value);
824  } else if (!strcasecmp(v->name, "secret")) {
825  ast_string_field_set(pvt, secret, v->value);
826  }
827  }
828 
830 
831  if (ast_strlen_zero(pvt->url)) {
832  ast_log(LOG_WARNING, "No URL was specified for Exchange Web Service calendar '%s' - skipping.\n", cal->name);
833  pvt = unref_ewscal(pvt);
834  ao2_unlock(cal);
835  return NULL;
836  }
837 
838  if (ne_uri_parse(pvt->url, &pvt->uri) || pvt->uri.host == NULL || pvt->uri.path == NULL) {
839  ast_log(LOG_WARNING, "Could not parse url '%s' for Exchange Web Service calendar '%s' - skipping.\n", pvt->url, cal->name);
840  pvt = unref_ewscal(pvt);
841  ao2_unlock(cal);
842  return NULL;
843  }
844 
845  if (pvt->uri.scheme == NULL) {
846  pvt->uri.scheme = "http";
847  }
848 
849  if (pvt->uri.port == 0) {
850  pvt->uri.port = ne_uri_defaultport(pvt->uri.scheme);
851  }
852 
853  ast_debug(3, "ne_uri.scheme = %s\n", pvt->uri.scheme);
854  ast_debug(3, "ne_uri.host = %s\n", pvt->uri.host);
855  ast_debug(3, "ne_uri.port = %u\n", pvt->uri.port);
856  ast_debug(3, "ne_uri.path = %s\n", pvt->uri.path);
857  ast_debug(3, "user = %s\n", pvt->user);
858  ast_debug(3, "secret = %s\n", pvt->secret);
859 
860  pvt->session = ne_session_create(pvt->uri.scheme, pvt->uri.host, pvt->uri.port);
861  ne_redirect_register(pvt->session);
862  ne_set_server_auth(pvt->session, auth_credentials, pvt);
863  ne_set_useragent(pvt->session, "Asterisk");
864 
865  if (!strcasecmp(pvt->uri.scheme, "https")) {
866  ne_ssl_trust_default_ca(pvt->session);
867  ne_ssl_set_verify(pvt->session, ssl_verify, pvt);
868  }
869 
870  cal->tech_pvt = pvt;
871 
872  ast_mutex_init(&refreshlock);
873 
874  /* Load it the first time */
875  update_ewscal(pvt);
876 
877  ao2_unlock(cal);
878 
879  /* The only writing from another thread will be if unload is true */
880  for (;;) {
881  struct timeval tv = ast_tvnow();
882  struct timespec ts = {0,};
883 
884  ts.tv_sec = tv.tv_sec + (60 * pvt->owner->refresh);
885 
886  ast_mutex_lock(&refreshlock);
887  while (!pvt->owner->unloading) {
888  if (ast_cond_timedwait(&pvt->owner->unload, &refreshlock, &ts) == ETIMEDOUT) {
889  break;
890  }
891  }
892  ast_mutex_unlock(&refreshlock);
893 
894  if (pvt->owner->unloading) {
895  ast_debug(10, "Skipping refresh since we got a shutdown signal\n");
896  return NULL;
897  }
898 
899  ast_debug(10, "Refreshing after %d minute timeout\n", pvt->owner->refresh);
900 
901  update_ewscal(pvt);
902  }
903 
904  return NULL;
905 }
906 
907 static int load_module(void)
908 {
909  /* Actualy, 0.29.1 is required (because of NTLM authentication), but this
910  * function does not support matching patch version.
911  *
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 
932 static int unload_module(void)
933 {
934  ne_sock_exit();
935  ast_calendar_unregister(&ewscal_tech);
936 
937  return 0;
938 }
939 
940 AST_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",
946 );
struct ast_variable * next
static int unload_module(void)
struct xml_context::ids ids
ast_cond_t unload
Definition: calendar.h:135
static int ssl_verify(void *userdata, int failures, const ne_ssl_certificate *cert)
unsigned int unloading
Definition: calendar.h:136
Asterisk locking-related definitions:
int ast_calendar_register(struct ast_calendar_tech *tech)
Register a new calendar technology.
Definition: res_calendar.c:549
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 AST_LIST_FIRST(head)
Returns the first entry contained in a list.
Definition: linkedlists.h:420
int ao2_container_count(struct ao2_container *c)
Returns the number of elements in a container.
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)
struct ast_variable * ast_variable_browse(const struct ast_config *config, const char *category_name)
Definition: extconf.c:1216
static int ewscal_write_event(struct ast_calendar_event *event)
char buf[BUFSIZE]
Definition: eagi_proxy.c:66
xml_op
#define LOG_WARNING
Definition: logger.h:274
char * ast_str_buffer(const struct ast_str *buf)
Returns the string buffer within the ast_str buf.
Definition: strings.h:714
#define ao2_callback(c, flags, cb_fn, arg)
Definition: astobj2.h:1716
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
Structure for variables, used for configurations and for channel variables.
#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
Definition: astman.c:222
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:1091
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:150
#define AST_DECLARE_STRING_FIELDS(field_list)
Declare the fields needed in a structure.
Definition: stringfields.h:337
#define ast_mutex_lock(a)
Definition: lock.h:187
#define ao2_unlock(a)
Definition: astobj2.h:730
static void ewscal_destructor(void *obj)
#define ast_strdup(str)
A wrapper for strdup()
Definition: astmm.h:243
int timeframe
Definition: calendar.h:133
#define NULL
Definition: resample.c:96
char * end
Definition: eagi_proxy.c:73
const ast_string_field description
Definition: calendar.h:101
const ast_string_field organizer
Definition: calendar.h:101
#define ast_strlen_zero(foo)
Definition: strings.h:52
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:1065
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.
Configuration File Parser.
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:452
#define ast_log
Definition: astobj2.c:42
const ast_string_field location
Definition: calendar.h:101
General Asterisk PBX channel definitions.
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:353
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:689
#define AST_STRING_FIELD(name)
Declare a string field.
Definition: stringfields.h:299
ne_xml_parser * parser
#define ao2_ref(o, delta)
Definition: astobj2.h:464
static struct calendar_id * get_ewscal_ids_for(struct ewscal_pvt *pvt)
A general API for managing calendar events with Asterisk.
#define ast_strdupa(s)
duplicate a string in memory from the stack
Definition: astmm.h:300
AST_LIST_HEAD_NOLOCK(contactliststruct, contact)
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:2550
struct calendar_id * next
const ast_string_field secret
unsigned int items
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:667
#define LOG_ERROR
Definition: logger.h:285
#define AST_LIST_INSERT_TAIL(head, elm, field)
Appends a list entry to the tail of a list.
Definition: linkedlists.h:730
#define ao2_trylock(a)
Definition: astobj2.h:740
The descriptor of a dynamic string XXX storage will be optimized later if needed We use the ts field ...
Definition: strings.h:584
struct association categories[]
static ast_mutex_t refreshlock
Definition: res_calendar.c:225
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
#define ao2_alloc(data_size, destructor_fn)
Definition: astobj2.h:411
#define AST_LIST_ENTRY(type)
Declare a forward link structure inside a list entry.
Definition: linkedlists.h:409
static const char name[]
Definition: cdr_mysql.c:74
#define ast_free(a)
Definition: astmm.h:182
#define ast_calloc(num, len)
A wrapper for calloc()
Definition: astmm.h:204
const struct ast_config * ast_calendar_config_acquire(void)
Grab and lock pointer to the calendar config (read only)
Definition: res_calendar.c:258
static void * ewscal_load_calendar(void *data)
static const char * mstime(time_t t, char *buf, size_t buflen)
Module has failed to load, may be in an inconsistent state.
Definition: module.h:78
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
static int load_module(void)
static int request(void *obj)
Definition: chan_pjsip.c:2559
struct ast_calendar_event * ast_calendar_unref_event(struct ast_calendar_event *event)
Unreference an ast_calendar_event.
Definition: res_calendar.c:321
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)
AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS|AST_MODFLAG_LOAD_ORDER, "HTTP Phone Provisioning",.support_level=AST_MODULE_SUPPORT_EXTENDED,.load=load_module,.unload=unload_module,.reload=reload,.load_pri=AST_MODPRI_CHANNEL_DEPEND,.requires="http",)
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:653
void ast_calendar_unregister(struct ast_calendar_tech *tech)
Unregister a new calendar technology.
Definition: res_calendar.c:587
size_t ast_str_strlen(const struct ast_str *buf)
Returns the current length of the string stored within buf.
Definition: strings.h:688
#define AST_LIST_HEAD_INIT_NOLOCK(head)
Initializes a list head structure.
Definition: linkedlists.h:680
char * strsep(char **str, const char *delims)
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:2357
#define AST_MODULE
void ast_copy_string(char *dst, const char *src, size_t size)
Size-limited null-terminating string copy.
Definition: strings.h:401
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:79
#define ast_mutex_init(pmutex)
Definition: lock.h:184
Generic container type.
Asterisk calendar structure.
Definition: calendar.h:117
void ast_calendar_config_release(void)
Release the calendar config.
Definition: res_calendar.c:270
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:46
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:368
#define ast_cond_timedwait(cond, mutex, time)
Definition: lock.h:204
enum xml_op op
ne_session * session
Structure for mutex and tracking information.
Definition: lock.h:135
static void * unref_ewscal(void *obj)
const ast_string_field url
#define ast_str_create(init_len)
Create a malloc&#39;ed dynamic length string.
Definition: strings.h:620
#define ast_mutex_unlock(a)
Definition: lock.h:188
struct ast_calendar_event * event
#define ast_string_field_set(x, field, data)
Set a field to a simple string value.
Definition: stringfields.h:514
#define ao2_link(container, obj)
Definition: astobj2.h:1549