31 #include <ne_request.h>
32 #include <ne_session.h>
34 #include <ne_socket.h>
37 #include <ne_xmlreq.h>
39 #include <ne_redirect.h>
48 static void *ewscal_load_calendar(
void *data);
49 static void *unref_ewscal(
void *obj);
54 .description =
"MS Exchange Web Service calendars",
56 .load_calendar = ewscal_load_calendar,
57 .unref_calendar = unref_ewscal,
58 .write_event = ewscal_write_event,
73 ne_xml_parser *parser;
83 XML_EVENT_CALENDAR_ITEM = 9,
85 XML_EVENT_DESCRIPTION,
91 XML_EVENT_ATTENDEE_LIST,
94 XML_EVENT_EMAIL_ADDRESS,
113 static void ewscal_destructor(
void *obj)
117 ast_debug(1,
"Destroying pvt for Exchange Web Service calendar %s\n",
"pvt->owner->name");
119 ne_session_destroy(pvt->session);
121 ne_uri_free(&pvt->uri);
129 static void *unref_ewscal(
void *obj)
138 static int auth_credentials(
void *userdata,
const char *realm,
int attempts,
char *username,
char *secret)
143 ast_log(LOG_WARNING,
"Invalid username or password for Exchange Web Service calendar '%s'\n", pvt->owner->
name);
147 ne_strnzcpy(username, pvt->user, NE_ABUFSIZ);
148 ne_strnzcpy(secret, pvt->secret, NE_ABUFSIZ);
153 static int ssl_verify(
void *userdata,
int failures,
const ne_ssl_certificate *cert)
156 if (failures & NE_SSL_UNTRUSTED) {
157 ast_log(LOG_WARNING,
"Untrusted SSL certificate for calendar %s!\n", pvt->owner->
name);
163 static time_t mstime_to_time_t(
char *mstime)
175 static int startelm(
void *userdata,
int parent,
const char *nspace,
const char *name,
const char **atts)
179 ast_debug(5,
"EWS: XML: Start: %s\n", name);
180 if (ctx->op == XML_OP_CREATE) {
181 return NE_XML_DECLINE;
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")
195 }
else if (!strcmp(name,
"RootFolder")) {
199 ast_debug(3,
"EWS: XML: <RootFolder>\n");
200 if (sscanf(ne_xml_get_attr(ctx->parser, atts, NULL,
"TotalItemsInView"),
"%u", &items) != 1) {
202 ne_xml_set_error(ctx->parser,
"Could't read number of events.");
206 ast_debug(3,
"EWS: %u calendar items to load\n", items);
207 ctx->pvt->items = items;
211 return NE_XML_DECLINE;
214 }
else if (!strcmp(name,
"CalendarItem")) {
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");
224 ast_log(LOG_ERROR,
"Could not allocate an event!\n");
230 ast_log(LOG_ERROR,
"Could not allocate CDATA!\n");
234 return XML_EVENT_CALENDAR_ITEM;
235 }
else if (!strcmp(name,
"ItemId")) {
237 if (ctx->op == XML_OP_FIND) {
246 ast_str_set(&id->id, 0,
"%s", ne_xml_get_attr(ctx->parser, atts, NULL,
"Id"));
250 ast_debug(3,
"EWS_GET: XML: UID: %s\n", ne_xml_get_attr(ctx->parser, atts, NULL,
"Id"));
253 return XML_EVENT_NAME;
254 }
else if (!strcmp(name,
"Subject")) {
260 return XML_EVENT_NAME;
261 }
else if (!strcmp(name,
"Body") && parent == XML_EVENT_CALENDAR_ITEM) {
267 return XML_EVENT_DESCRIPTION;
268 }
else if (!strcmp(name,
"Start")) {
270 return XML_EVENT_START;
271 }
else if (!strcmp(name,
"End")) {
273 return XML_EVENT_END;
274 }
else if (!strcmp(name,
"LegacyFreeBusyStatus")) {
276 return XML_EVENT_BUSY;
277 }
else if (!strcmp(name,
"Organizer") ||
278 (parent == XML_EVENT_ORGANIZER && (!strcmp(name,
"Mailbox") ||
279 !strcmp(name,
"Name")))) {
285 return XML_EVENT_ORGANIZER;
286 }
else if (!strcmp(name,
"Location")) {
292 return XML_EVENT_LOCATION;
293 }
else if (!strcmp(name,
"Categories")) {
299 return XML_EVENT_CATEGORIES;
300 }
else if (parent == XML_EVENT_CATEGORIES && !strcmp(name,
"String")) {
302 return XML_EVENT_CATEGORY;
303 }
else if (!strcmp(name,
"Importance")) {
309 return XML_EVENT_IMPORTANCE;
310 }
else if (!strcmp(name,
"RequiredAttendees") || !strcmp(name,
"OptionalAttendees")) {
311 return XML_EVENT_ATTENDEE_LIST;
312 }
else if (!strcmp(name,
"Attendee") && parent == XML_EVENT_ATTENDEE_LIST) {
313 return XML_EVENT_ATTENDEE;
314 }
else if (!strcmp(name,
"Mailbox") && parent == XML_EVENT_ATTENDEE) {
315 return XML_EVENT_MAILBOX;
316 }
else if (!strcmp(name,
"EmailAddress") && parent == XML_EVENT_MAILBOX) {
321 return XML_EVENT_EMAIL_ADDRESS;
324 return NE_XML_DECLINE;
327 static int cdata(
void *userdata,
int state,
const char *cdata,
size_t len)
338 ast_log(LOG_ERROR,
"Parsing event data, but event object does not exist!\n");
343 ast_log(LOG_ERROR,
"String for storing CDATA is unitialized!\n");
350 case XML_EVENT_START:
351 ctx->event->
start = mstime_to_time_t(data);
354 ctx->event->
end = mstime_to_time_t(data);
357 if (!strcmp(data,
"Busy") || !strcmp(data,
"OOF")) {
359 ctx->event->
busy_state = AST_CALENDAR_BS_BUSY;
361 else if (!strcmp(data,
"Tentative")) {
362 ast_debug(3,
"EWS: XML: Busy: tentative\n");
363 ctx->event->
busy_state = AST_CALENDAR_BS_BUSY_TENTATIVE;
367 ctx->event->
busy_state = AST_CALENDAR_BS_FREE;
370 case XML_EVENT_CATEGORY:
386 static int endelm(
void *userdata,
int state,
const char *nspace,
const char *name)
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;
395 if (!strcmp(name,
"Subject")) {
398 ast_debug(3,
"EWS: XML: Summary: %s\n", ctx->event->summary);
400 }
else if (!strcmp(name,
"Body") && state == XML_EVENT_DESCRIPTION) {
403 ast_debug(3,
"EWS: XML: Description: %s\n", ctx->event->description);
405 }
else if (!strcmp(name,
"Organizer")) {
408 ast_debug(3,
"EWS: XML: Organizer: %s\n", ctx->event->organizer);
410 }
else if (!strcmp(name,
"Location")) {
413 ast_debug(3,
"EWS: XML: Location: %s\n", ctx->event->location);
415 }
else if (!strcmp(name,
"Categories")) {
418 ast_debug(3,
"EWS: XML: Categories: %s\n", ctx->event->categories);
420 }
else if (!strcmp(name,
"Importance")) {
431 }
else if (state == XML_EVENT_EMAIL_ADDRESS) {
434 if (!(attendee =
ast_calloc(1,
sizeof(*attendee)))) {
447 }
else if (!strcmp(name,
"CalendarItem")) {
449 ast_debug(3,
"EWS: XML: </CalendarItem>\n");
450 ast_free(ctx->cdata);
452 ao2_link(ctx->pvt->events, ctx->event);
455 ast_log(LOG_ERROR,
"Event data ended in XML, but event object does not exist!\n");
458 }
else if (!strcmp(name,
"Envelope")) {
462 ast_debug(3,
"EWS: XML: All events has been parsed, merging…\n");
470 static const char *mstime(time_t t,
char *buf,
size_t buflen)
472 struct timeval tv = {
480 return S_OR(buf,
"");
483 static const char *msstatus(
enum ast_calendar_busy_state state)
486 case AST_CALENDAR_BS_BUSY_TENTATIVE:
488 case AST_CALENDAR_BS_BUSY:
490 case AST_CALENDAR_BS_FREE:
497 static const char *get_soap_action(
enum xml_op op)
501 return "\"http://schemas.microsoft.com/exchange/services/2006/messages/FindItem\"";
503 return "\"http://schemas.microsoft.com/exchange/services/2006/messages/GetItem\"";
505 return "\"http://schemas.microsoft.com/exchange/services/2006/messages/CreateItem\"";
511 static int send_ews_request_and_parse(
struct ast_str *request,
struct xml_context *ctx)
515 ne_xml_parser *parser;
518 if (!(ctx && ctx->pvt)) {
519 ast_log(LOG_ERROR,
"There is no private!\n");
524 ast_log(LOG_ERROR,
"No request to send!\n");
531 req = ne_request_create(ctx->pvt->session,
"POST", ctx->pvt->uri.path);
532 ne_set_request_flag(req, NE_REQFLAG_IDEMPOTENT, 0);
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));
542 parser = ne_xml_create();
543 ctx->parser = parser;
544 ne_xml_push_handler(parser, startelm, cdata, endelm, ctx);
547 ret = ne_xml_dispatch_request(req, parser);
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);
556 ne_request_destroy(req);
557 ne_xml_destroy(parser);
565 struct ewscal_pvt *pvt =
event->owner->tech_pvt;
566 char start[21], end[21];
572 char *category, *categories;
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\">"
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>"
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>"
601 "<IsAllDayEvent>false</IsAllDayEvent>"
602 "<LegacyFreeBusyStatus>%s</LegacyFreeBusyStatus>"
603 "<Location>%s</Location>",
606 mstime(event->
start, start,
sizeof(start)),
607 mstime(event->
end, end,
sizeof(end)),
630 if (strlen(event->categories) > 0) {
633 category = strsep(&categories,
",");
634 while (category != NULL) {
636 category = strsep(&categories,
",");
641 ast_str_append(&request, 0,
"</t:CalendarItem></Items></CreateItem></soap:Body></soap:Envelope>");
643 ret = send_ews_request_and_parse(request, &ctx);
652 char start[21], end[21];
661 ast_debug(5,
"EWS: get_ewscal_ids_for()\n");
664 ast_log(LOG_ERROR,
"There is no private!\n");
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\">"
686 "<ns2:FindItem Traversal=\"Shallow\">"
688 "<ns1:BaseShape>IdOnly</ns1:BaseShape>"
690 "<ns2:CalendarView StartDate=\"%s\" EndDate=\"%s\"/>"
691 "<ns2:ParentFolderIds>"
692 "<ns1:DistinguishedFolderId Id=\"calendar\"/>"
693 "</ns2:ParentFolderIds>"
696 "</SOAP-ENV:Envelope>",
703 if (send_ews_request_and_parse(request, &ctx)) {
714 static int parse_ewscal_id(
struct ewscal_pvt *pvt,
const char *
id) {
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\">"
730 "<GetItem xmlns=\"http://schemas.microsoft.com/exchange/services/2006/messages\">"
732 "<t:BaseShape>AllProperties</t:BaseShape>"
735 "<t:ItemId Id=\"%s\"/>"
739 "</soap:Envelope>",
id
742 if (send_ews_request_and_parse(request, &ctx)) {
752 static int update_ewscal(
struct ewscal_pvt *pvt)
757 if (!(id_head = get_ewscal_ids_for(pvt))) {
761 for (iter = id_head; iter; iter =
AST_LIST_NEXT(iter, next)) {
770 static void *ewscal_load_calendar(
void *void_data)
778 ast_debug(5,
"EWS: ewscal_load_calendar()\n");
781 ast_log(LOG_ERROR,
"You must enable calendar support for res_ewscal to load\n");
785 if (ao2_trylock(cal)) {
786 if (cal->unloading) {
787 ast_log(LOG_WARNING,
"Unloading module, load_calendar cancelled.\n");
789 ast_log(LOG_WARNING,
"Could not lock calendar, aborting!\n");
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);
804 ast_log(LOG_ERROR,
"Could not allocate space for fetching events for calendar: %s\n", cal->
name);
805 pvt = unref_ewscal(pvt);
812 ast_log(LOG_ERROR,
"Couldn't allocate string field space for calendar: %s\n", cal->
name);
813 pvt = unref_ewscal(pvt);
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")) {
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);
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);
845 if (pvt->uri.scheme == NULL) {
846 pvt->uri.scheme =
"http";
849 if (pvt->uri.port == 0) {
850 pvt->uri.port = ne_uri_defaultport(pvt->uri.scheme);
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);
858 ast_debug(3,
"secret = %s\n", pvt->secret);
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");
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);
872 ast_mutex_init(&refreshlock);
882 struct timespec ts = {0,};
884 ts.tv_sec = tv.tv_sec + (60 * pvt->owner->
refresh);
886 ast_mutex_lock(&refreshlock);
887 while (!pvt->owner->unloading) {
888 if (ast_cond_timedwait(&pvt->owner->unload, &refreshlock, &ts) == ETIMEDOUT) {
892 ast_mutex_unlock(&refreshlock);
894 if (pvt->owner->unloading) {
895 ast_debug(10,
"Skipping refresh since we got a shutdown signal\n");
907 static int load_module(
void)
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());
932 static int unload_module(
void)
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,
943 .unload = unload_module,
945 .requires =
"res_calendar",
struct ast_variable * next
Asterisk locking-related definitions:
int ast_calendar_register(struct ast_calendar_tech *tech)
Register a new calendar technology.
Asterisk main include file. File version handling, generic pbx functions.
#define AST_LIST_FIRST(head)
Returns the first entry contained in a list.
int ao2_container_count(struct ao2_container *c)
Returns the number of elements in a container.
char * ast_str_buffer(const struct ast_str *buf)
Returns the string buffer within the ast_str buf.
#define ao2_callback(c, flags, cb_fn, arg)
ao2_callback() is a generic function that applies cb_fn() to all objects in a container, as described below.
struct ast_tm * ast_localtime(const struct timeval *timep, struct ast_tm *p_tm, const char *zone)
Timezone-independent version of localtime_r(3).
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.
int ast_str_append(struct ast_str **buf, ssize_t max_len, const char *fmt,...)
Append to a thread local dynamic string.
enum ast_calendar_busy_state busy_state
struct timeval ast_tvnow(void)
Returns current timeval. Meant to replace calls to gettimeofday().
#define AST_DECLARE_STRING_FIELDS(field_list)
Declare the fields needed in a structure.
#define ast_strdup(str)
A wrapper for strdup()
int ast_str_set(struct ast_str **buf, ssize_t max_len, const char *fmt,...)
Set a dynamic string using variable arguments.
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.
General Asterisk PBX channel definitions.
#define ast_string_field_init(x, size)
Initialize a field pool and fields.
#define ast_strdupa(s)
duplicate a string in memory from the stack
#define ao2_ref(o, delta)
Reference/unreference an object and return the old refcount.
struct ao2_container * ast_calendar_event_container_alloc(void)
Allocate an astobj2 container for ast_calendar_event objects.
#define AST_STRING_FIELD(name)
Declare a string field.
A general API for managing calendar events with Asterisk.
static struct stasis_rest_handlers events
REST handler for /api-docs/events.json.
#define ast_debug(level,...)
Log a DEBUG message.
const ast_string_field name
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.
#define AST_LIST_HEAD_NOLOCK(name, type)
Defines a structure to be used to hold a list of specified type (with no lock).
struct ast_calendar_event * ast_calendar_event_alloc(struct ast_calendar *cal)
Allocate an astobj2 ast_calendar_event object.
#define AST_LIST_INSERT_TAIL(head, elm, field)
Appends a list entry to the tail of a list.
Support for dynamic strings.
#define AST_LIST_ENTRY(type)
Declare a forward link structure inside a list entry.
#define ast_calloc(num, len)
A wrapper for calloc()
const struct ast_config * ast_calendar_config_acquire(void)
Grab and lock pointer to the calendar config (read only)
Module has failed to load, may be in an inconsistent state.
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...
struct ast_calendar_event * ast_calendar_unref_event(struct ast_calendar_event *event)
Unreference an ast_calendar_event.
structure to hold users read from users.conf
void ast_str_reset(struct ast_str *buf)
Reset the content of a dynamic string. Useful before a series of ast_str_append.
void ast_calendar_unregister(struct ast_calendar_tech *tech)
Unregister a new calendar technology.
size_t ast_str_strlen(const struct ast_str *buf)
Returns the current length of the string stored within buf.
#define AST_LIST_HEAD_INIT_NOLOCK(head)
Initializes a list head structure.
struct timeval ast_mktime(struct ast_tm *const tmp, const char *zone)
Timezone-independent version of mktime(3).
void ast_copy_string(char *dst, const char *src, size_t size)
Size-limited null-terminating string copy.
Individual calendaring technology data.
#define S_OR(a, b)
returns the equivalent of logic or for strings: first one if not empty, otherwise second one...
Asterisk calendar structure.
void ast_calendar_config_release(void)
Release the calendar config.
#define ASTERISK_GPL_KEY
The text the key() function should return.
Asterisk module definitions.
#define ast_string_field_free_memory(x)
free all memory - to be called before destroying the object
Structure for mutex and tracking information.
#define ast_str_create(init_len)
Create a malloc'ed dynamic length string.
#define ast_string_field_set(x, field, data)
Set a field to a simple string value.
#define ao2_link(container, obj)
Add an object to a container.