diff options
author | twilson <twilson@f38db490-d61c-443f-a65b-d21fe96a405b> | 2010-05-24 18:21:20 +0000 |
---|---|---|
committer | twilson <twilson@f38db490-d61c-443f-a65b-d21fe96a405b> | 2010-05-24 18:21:20 +0000 |
commit | 7ac4eac5c8be37e424f8ab6d6538bc28f10fd173 (patch) | |
tree | eca5478a1d89cadc9ebfe0a73e6c4d530de11ad9 /res | |
parent | e6aec778a5a582a7ce9770274773d02c92274167 (diff) |
Calendaring support for Exchange Server 2007+ via EWS
This commit adds support for calendaring with Exchange Server 2007+ via
Exchange Web Services. Full write support and for querying attendees. Many
thanks to Jan Kaláb for the feature.
(closes issue #17022)
Reported by: pitel
Patches:
res_calendar_ews.c uploaded by pitel (license 1008)
Tested by: pitel, twilson
Review: https://reviewboard.asterisk.org/r/557/
Review: https://reviewboard.asterisk.org/r/668/
git-svn-id: http://svn.digium.com/svn/asterisk/trunk@265317 f38db490-d61c-443f-a65b-d21fe96a405b
Diffstat (limited to 'res')
-rw-r--r-- | res/res_calendar.c | 9 | ||||
-rw-r--r-- | res/res_calendar_ews.c | 838 |
2 files changed, 847 insertions, 0 deletions
diff --git a/res/res_calendar.c b/res/res_calendar.c index a8c5ee784..2e3c8e4a1 100644 --- a/res/res_calendar.c +++ b/res/res_calendar.c @@ -1260,6 +1260,7 @@ static int calendar_write_exec(struct ast_channel *chan, const char *cmd, char * char *val_dup = NULL; struct ast_calendar *cal = NULL; struct ast_calendar_event *event = NULL; + struct timeval tv = ast_tvnow(); AST_DECLARE_APP_ARGS(fields, AST_APP_ARG(field)[10]; ); @@ -1325,6 +1326,14 @@ static int calendar_write_exec(struct ast_channel *chan, const char *cmd, char * } } + if (!event->start) { + event->start = tv.tv_sec; + } + + if (!event->end) { + event->end = tv.tv_sec; + } + if((ret = cal->tech->write_event(event))) { ast_log(LOG_WARNING, "Writing event to calendar '%s' failed!\n", cal->name); } diff --git a/res/res_calendar_ews.c b/res/res_calendar_ews.c new file mode 100644 index 000000000..bd6a1eecd --- /dev/null +++ b/res/res_calendar_ews.c @@ -0,0 +1,838 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2008 - 2009, Digium, Inc. + * + * Jan Kalab <pitlicek@gmail.com> + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! \file + * \brief Resource for handling MS Exchange Web Service calendars + */ + +/*** MODULEINFO + <depend>neon</depend> +***/ +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include <neon/ne_request.h> +#include <neon/ne_session.h> +#include <neon/ne_uri.h> +#include <neon/ne_socket.h> +#include <neon/ne_auth.h> +#include <neon/ne_xml.h> +#include <neon/ne_xmlreq.h> +#include <neon/ne_utils.h> + +#include "asterisk/module.h" +#include "asterisk/calendar.h" +#include "asterisk/lock.h" +#include "asterisk/config.h" +#include "asterisk/astobj2.h" + +static void *ewscal_load_calendar(void *data); +static void *unref_ewscal(void *obj); +static int ewscal_write_event(struct ast_calendar_event *event); + +static struct ast_calendar_tech ewscal_tech = { + .type = "ews", + .description = "MS Exchange Web Service calendars", + .module = AST_MODULE, + .load_calendar = ewscal_load_calendar, + .unref_calendar = unref_ewscal, + .write_event = ewscal_write_event, +}; + +enum xml_op { + XML_OP_FIND = 100, + XML_OP_GET, + XML_OP_CREATE, +}; + +struct calendar_id { + struct ast_str *id; + AST_LIST_ENTRY(calendar_id) next; +}; + +struct xml_context { + ne_xml_parser *parser; + struct ast_str *cdata; + struct ast_calendar_event *event; + enum xml_op op; + struct ewscal_pvt *pvt; + AST_LIST_HEAD_NOLOCK(ids, calendar_id) ids; +}; + +/* Important states of XML parsing */ +enum { + XML_EVENT_NAME = 10, + XML_EVENT_START, + XML_EVENT_END, + XML_EVENT_BUSY, + XML_EVENT_ORGANIZER, + XML_EVENT_LOCATION, + XML_EVENT_ATTENDEE_LIST, + XML_EVENT_ATTENDEE, + XML_EVENT_MAILBOX, + XML_EVENT_EMAIL_ADDRESS, +}; + +struct ewscal_pvt { + AST_DECLARE_STRING_FIELDS( + AST_STRING_FIELD(url); + AST_STRING_FIELD(user); + AST_STRING_FIELD(secret); + ); + struct ast_calendar *owner; + ne_uri uri; + ne_session *session; + struct ao2_container *events; +}; + +static void ewscal_destructor(void *obj) +{ + struct ewscal_pvt *pvt = obj; + + ast_debug(1, "Destroying pvt for Exchange Web Service calendar %s\n", "pvt->owner->name"); + if (pvt->session) { + ne_session_destroy(pvt->session); + } + ast_string_field_free_memory(pvt); + + ao2_callback(pvt->events, OBJ_UNLINK | OBJ_NODATA | OBJ_MULTIPLE, NULL, NULL); + + ao2_ref(pvt->events, -1); +} + +static void *unref_ewscal(void *obj) +{ + struct ewscal_pvt *pvt = obj; + + ast_debug(5, "EWS: unref_ewscal()\n"); + ao2_ref(pvt, -1); + return NULL; +} + +static int auth_credentials(void *userdata, const char *realm, int attempts, char *username, char *secret) +{ + struct ewscal_pvt *pvt = userdata; + + if (attempts > 1) { + ast_log(LOG_WARNING, "Invalid username or password for Exchange Web Service calendar '%s'\n", pvt->owner->name); + return -1; + } + + ne_strnzcpy(username, pvt->user, NE_ABUFSIZ); + ne_strnzcpy(secret, pvt->secret, NE_ABUFSIZ); + + return 0; +} + +static int ssl_verify(void *userdata, int failures, const ne_ssl_certificate *cert) +{ + struct ewscal_pvt *pvt = userdata; + if (failures & NE_SSL_UNTRUSTED) { + ast_log(LOG_WARNING, "Untrusted SSL certificate for calendar %s!\n", pvt->owner->name); + return 0; + } + return 1; /* NE_SSL_NOTYETVALID, NE_SSL_EXPIRED, NE_SSL_IDMISMATCH */ +} + +static time_t mstime_to_time_t(char *mstime) +{ + struct ast_tm tm; + struct timeval tv; + + if (ast_strptime(mstime, "%FT%TZ", &tm)) { + tv = ast_mktime(&tm, "UTC"); + return tv.tv_sec; + } + return 0; +} + +static int startelm(void *userdata, int parent, const char *nspace, const char *name, const char **atts) +{ + struct xml_context *ctx = userdata; + + ast_debug(3, "EWS: XML: Start: %s\n", name); + if (ctx->op == XML_OP_CREATE) { + return NE_XML_DECLINE; + } + + /* Nodes needed for traversing until CalendarItem is found */ + if (!strcmp(name, "Envelope") || + !strcmp(name, "Body") || + !strcmp(name, "FindItemResponse") || + !strcmp(name, "GetItemResponse") || + !strcmp(name, "CreateItemResponse") || + !strcmp(name, "ResponseMessages") || + !strcmp(name, "FindItemResponseMessage") || !strcmp(name, "GetItemResponseMessage") || + !strcmp(name, "Items") + ) { + return 1; + } else if (!strcmp(name, "RootFolder")) { + /* Get number of events */ + int items; + + ast_debug(3, "EWS: XML: <RootFolder>\n"); + if (sscanf(ne_xml_get_attr(ctx->parser, atts, NULL, "TotalItemsInView"), "%d", &items) != 1) { + /* Couldn't read enything */ + ne_xml_set_error(ctx->parser, "Could't read number of events."); + return NE_XML_ABORT; + } + + ast_debug(3, "EWS: %d calendar items to load\n", items); + if (items < 1) { + /* Stop processing XML if there are no events */ + return NE_XML_DECLINE; + } + return 1; + } else if (!strcmp(name, "CalendarItem")) { + /* Event start */ + ast_debug(3, "EWS: XML: <CalendarItem>\n"); + if (!(ctx->pvt && ctx->pvt->owner)) { + ast_log(LOG_ERROR, "Require a private structure with an owner\n"); + return NE_XML_ABORT; + } + + ctx->event = ast_calendar_event_alloc(ctx->pvt->owner); + if (!ctx->event) { + ast_log(LOG_ERROR, "Could not allocate an event!\n"); + return NE_XML_ABORT; + } + + ctx->cdata = ast_str_create(64); + if (!ctx->cdata) { + ast_log(LOG_ERROR, "Could not allocate CDATA!\n"); + return NE_XML_ABORT; + } + + return 1; + } else if (!strcmp(name, "ItemId")) { + /* Event UID */ + if (ctx->op == XML_OP_FIND) { + struct calendar_id *id; + if (!(id = ast_calloc(1, sizeof(id)))) { + return NE_XML_ABORT; + } + if (!(id->id = ast_str_create(256))) { + ast_free(id); + return NE_XML_ABORT; + } + ast_str_set(&id->id, 0, "%s", ne_xml_get_attr(ctx->parser, atts, NULL, "Id")); + AST_LIST_INSERT_TAIL(&ctx->ids, id, next); + ast_debug(3, "EWS_FIND: XML: UID: %s\n", ast_str_buffer(id->id)); + } else { + ast_debug(3, "EWS_GET: XML: UID: %s\n", ne_xml_get_attr(ctx->parser, atts, NULL, "Id")); + ast_string_field_set(ctx->event, uid, ne_xml_get_attr(ctx->parser, atts, NULL, "Id")); + } + return XML_EVENT_NAME; + } else if (!strcmp(name, "Subject")) { + /* Event name */ + if (!ctx->cdata) { + return NE_XML_ABORT; + } + ast_str_reset(ctx->cdata); + return XML_EVENT_NAME; + } else if (!strcmp(name, "Start")) { + /* Event start time */ + return XML_EVENT_START; + } else if (!strcmp(name, "End")) { + /* Event end time */ + return XML_EVENT_END; + } else if (!strcmp(name, "LegacyFreeBusyStatus")) { + /* Event busy state */ + return XML_EVENT_BUSY; + } else if (!strcmp(name, "Organizer") || + (parent == XML_EVENT_ORGANIZER && (!strcmp(name, "Mailbox") || + !strcmp(name, "Name")))) { + /* Event organizer */ + if (!ctx->cdata) { + return NE_XML_ABORT; + } + ast_str_reset(ctx->cdata); + return XML_EVENT_ORGANIZER; + } else if (!strcmp(name, "Location")) { + /* Event location */ + if (!ctx->cdata) { + return NE_XML_ABORT; + } + ast_str_reset(ctx->cdata); + return XML_EVENT_LOCATION; + } else if (!strcmp(name, "RequiredAttendees") || !strcmp(name, "OptionalAttendees")) { + return XML_EVENT_ATTENDEE_LIST; + } else if (!strcmp(name, "Attendee") && parent == XML_EVENT_ATTENDEE_LIST) { + return XML_EVENT_ATTENDEE; + } else if (!strcmp(name, "Mailbox") && parent == XML_EVENT_ATTENDEE) { + return XML_EVENT_MAILBOX; + } else if (!strcmp(name, "EmailAddress") && parent == XML_EVENT_MAILBOX) { + if (!ctx->cdata) { + return NE_XML_ABORT; + } + ast_str_reset(ctx->cdata); + return XML_EVENT_EMAIL_ADDRESS; + } + + return NE_XML_DECLINE; +} + +static int cdata(void *userdata, int state, const char *cdata, size_t len) +{ + struct xml_context *ctx = userdata; + char data[len + 1]; + + /* !!! DON'T USE AST_STRING_FIELD FUNCTIONS HERE, JUST COLLECT CTX->CDATA !!! */ + if (state < XML_EVENT_NAME || ctx->op == XML_OP_CREATE) { + return 0; + } + + if (!ctx->event) { + ast_log(LOG_ERROR, "Parsing event data, but event object does not exist!\n"); + return 1; + } + + if (!ctx->cdata) { + ast_log(LOG_ERROR, "String for storing CDATA is unitialized!\n"); + return 1; + } + + ast_copy_string(data, cdata, len + 1); + + switch (state) { + case XML_EVENT_START: + ctx->event->start = mstime_to_time_t(data); + break; + case XML_EVENT_END: + ctx->event->end = mstime_to_time_t(data); + break; + case XML_EVENT_BUSY: + if (!strcmp(data, "Busy") || !strcmp(data, "OOF")) { + ast_debug(3, "EWS: XML: Busy: yes\n"); + ctx->event->busy_state = AST_CALENDAR_BS_BUSY; + } + else if (!strcmp(data, "Tentative")) { + ast_debug(3, "EWS: XML: Busy: tentative\n"); + ctx->event->busy_state = AST_CALENDAR_BS_BUSY_TENTATIVE; + } + else { + ast_debug(3, "EWS: XML: Busy: no\n"); + ctx->event->busy_state = AST_CALENDAR_BS_FREE; + } + break; + default: + ast_str_append(&ctx->cdata, 0, "%s", data); + } + + ast_debug(5, "EWS: XML: CDATA: %s\n", ast_str_buffer(ctx->cdata)); + + return 0; +} + +static int endelm(void *userdata, int state, const char *nspace, const char *name) +{ + struct xml_context *ctx = userdata; + + ast_debug(5, "EWS: XML: End: %s\n", name); + if (ctx->op == XML_OP_FIND || ctx->op == XML_OP_CREATE) { + return NE_XML_DECLINE; + } + + if (!strcmp(name, "Subject")) { + /* Event name end*/ + ast_string_field_set(ctx->event, summary, ast_str_buffer(ctx->cdata)); + ast_debug(3, "EWS: XML: Summary: %s\n", ctx->event->summary); + ast_str_reset(ctx->cdata); + } else if (!strcmp(name, "Organizer")) { + /* Event organizer end */ + ast_string_field_set(ctx->event, organizer, ast_str_buffer(ctx->cdata)); + ast_debug(3, "EWS: XML: Organizer: %s\n", ctx->event->organizer); + ast_str_reset(ctx->cdata); + } else if (!strcmp(name, "Location")) { + /* Event location end */ + ast_string_field_set(ctx->event, location, ast_str_buffer(ctx->cdata)); + ast_debug(3, "EWS: XML: Location: %s\n", ctx->event->location); + ast_str_reset(ctx->cdata); + } else if (state == XML_EVENT_EMAIL_ADDRESS) { + struct ast_calendar_attendee *attendee; + + if (!(attendee = ast_calloc(1, sizeof(*attendee)))) { + ctx->event = ast_calendar_unref_event(ctx->event); + return 1; + } + + if (ast_str_strlen(ctx->cdata)) { + attendee->data = ast_strdup(ast_str_buffer(ctx->cdata)); + AST_LIST_INSERT_TAIL(&ctx->event->attendees, attendee, next); + } + ast_debug(3, "EWS: XML: attendee address '%s'\n", ast_str_buffer(ctx->cdata)); + ast_str_reset(ctx->cdata); + } else if (!strcmp(name, "CalendarItem")) { + /* Event end */ + ast_debug(3, "EWS: XML: </CalendarItem>\n"); + ast_free(ctx->cdata); + if (ctx->event) { + ao2_link(ctx->pvt->events, ctx->event); + ctx->event = ast_calendar_unref_event(ctx->event); + } else { + ast_log(LOG_ERROR, "Event data ended in XML, but event object does not exist!\n"); + return 1; + } + } else if (!strcmp(name, "Envelope")) { + /* Events end */ + ast_debug(3, "EWS: XML: All events has been parsed, merging…\n"); + ast_calendar_merge_events(ctx->pvt->owner, ctx->pvt->events); + } + + return 0; +} + +static const char *mstime(time_t t, char *buf, size_t buflen) +{ + struct timeval tv = { + .tv_sec = t, + }; + struct ast_tm tm; + + ast_localtime(&tv, &tm, "utc"); + ast_strftime(buf, buflen, "%FT%TZ", &tm); + + return S_OR(buf, ""); +} + +static const char *msstatus(enum ast_calendar_busy_state state) +{ + switch (state) { + case AST_CALENDAR_BS_BUSY_TENTATIVE: + return "Tentative"; + case AST_CALENDAR_BS_BUSY: + return "Busy"; + case AST_CALENDAR_BS_FREE: + return "Free"; + default: + return ""; + } +} + +static const char *get_soap_action(enum xml_op op) +{ + switch (op) { + case XML_OP_FIND: + return "\"http://schemas.microsoft.com/exchange/services/2006/messages/FindItem\""; + case XML_OP_GET: + return "\"http://schemas.microsoft.com/exchange/services/2006/messages/GetItem\""; + case XML_OP_CREATE: + return "\"http://schemas.microsoft.com/exchange/services/2006/messages/CreateItem\""; + } + + return ""; +} + +static int send_ews_request_and_parse(struct ast_str *request, struct xml_context *ctx) +{ + int ret; + ne_request *req; + ne_xml_parser *parser; + + ast_debug(3, "EWS: HTTP request...\n"); + if (!(ctx && ctx->pvt)) { + ast_log(LOG_ERROR, "There is no private!\n"); + return -1; + } + + if (!ast_str_strlen(request)) { + ast_log(LOG_ERROR, "No request to send!\n"); + return -1; + } + + ast_debug(3, "%s\n", ast_str_buffer(request)); + + /* Prepare HTTP POST request */ + req = ne_request_create(ctx->pvt->session, "POST", ctx->pvt->uri.path); + ne_set_request_flag(req, NE_REQFLAG_IDEMPOTENT, 0); + + /* Set headers--should be application/soap+xml, but MS… :/ */ + ne_add_request_header(req, "Content-Type", "text/xml; charset=utf-8"); + ne_add_request_header(req, "SOAPAction", get_soap_action(ctx->op)); + + /* Set body to SOAP request */ + ne_set_request_body_buffer(req, ast_str_buffer(request), ast_str_strlen(request)); + + /* Prepare XML parser */ + parser = ne_xml_create(); + ctx->parser = parser; + ne_xml_push_handler(parser, startelm, cdata, endelm, ctx); /* Callbacks */ + + /* Dispatch request and parse response as XML */ + ret = ne_xml_dispatch_request(req, parser); + if (ret != NE_OK) { /* Error handling */ + ast_log(LOG_WARNING, "Unable to communicate with Exchange Web Service at '%s': %s\n", ctx->pvt->url, ne_get_error(ctx->pvt->session)); + ne_request_destroy(req); + ast_free(request); + ne_xml_destroy(parser); + return -1; + } + + /* Cleanup */ + ne_request_destroy(req); + ne_xml_destroy(parser); + + return 0; +} + +static int ewscal_write_event(struct ast_calendar_event *event) +{ + struct ast_str *request; + struct ewscal_pvt *pvt = event->owner->tech_pvt; + char start[21], end[21]; + struct xml_context ctx = { + .op = XML_OP_CREATE, + .pvt = pvt, + }; + int ret; + + if (!pvt) { + return -1; + } + + if (!(request = ast_str_create(1024))) { + return -1; + } + + ast_str_set(&request, 0, + "<soap:Envelope xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" " + "xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" " + "xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\" " + "xmlns:t=\"http://schemas.microsoft.com/exchange/services/2006/types\">" + "<soap:Body>" + "<CreateItem xmlns=\"http://schemas.microsoft.com/exchange/services/2006/messages\" " + "xmlns:t=\"http://schemas.microsoft.com/exchange/services/2006/types\" " + "SendMeetingInvitations=\"SendToNone\" >" + "<SavedItemFolderId>" + "<t:DistinguishedFolderId Id=\"calendar\"/>" + "</SavedItemFolderId>" + "<Items>" + "<t:CalendarItem xmlns=\"http://schemas.microsoft.com/exchange/services/2006/types\">" + "<Subject>%s</Subject>" + "<Body BodyType=\"Text\">%s</Body>" + "<ReminderIsSet>false</ReminderIsSet>" + "<Start>%s</Start>" + "<End>%s</End>" + "<IsAllDayEvent>false</IsAllDayEvent>" + "<LegacyFreeBusyStatus>%s</LegacyFreeBusyStatus>" + "<Location>%s</Location>" + "</t:CalendarItem>" + "</Items>" + "</CreateItem>" + "</soap:Body>" + "</soap:Envelope>", + event->summary, + event->description, + mstime(event->start, start, sizeof(start)), + mstime(event->end, end, sizeof(end)), + msstatus(event->busy_state), + event->location + ); + + ret = send_ews_request_and_parse(request, &ctx); + + ast_free(request); + + return ret; +} + +static struct calendar_id *get_ewscal_ids_for(struct ewscal_pvt *pvt) +{ + char start[21], end[21]; + struct ast_tm tm; + struct timeval tv; + struct ast_str *request; + struct xml_context ctx = { + .op = XML_OP_FIND, + .pvt = pvt, + }; + + ast_debug(5, "EWS: get_ewscal_ids_for()\n"); + + if (!pvt) { + ast_log(LOG_ERROR, "There is no private!\n"); + return NULL; + } + + /* Prepare timeframe strings */ + tv = ast_tvnow(); + ast_localtime(&tv, &tm, "UTC"); + ast_strftime(start, sizeof(start), "%FT%TZ", &tm); + tv.tv_sec += 60 * pvt->owner->timeframe; + ast_localtime(&tv, &tm, "UTC"); + ast_strftime(end, sizeof(end), "%FT%TZ", &tm); + + /* Prepare SOAP request */ + if (!(request = ast_str_create(512))) { + return NULL; + } + + ast_str_set(&request, 0, + "<SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\" " + "xmlns:ns1=\"http://schemas.microsoft.com/exchange/services/2006/types\" " + "xmlns:ns2=\"http://schemas.microsoft.com/exchange/services/2006/messages\">" + "<SOAP-ENV:Body>" + "<ns2:FindItem Traversal=\"Shallow\">" + "<ns2:ItemShape>" + "<ns1:BaseShape>IdOnly</ns1:BaseShape>" + "</ns2:ItemShape>" + "<ns2:CalendarView StartDate=\"%s\" EndDate=\"%s\"/>" /* Timeframe */ + "<ns2:ParentFolderIds>" + "<ns1:DistinguishedFolderId Id=\"calendar\"/>" + "</ns2:ParentFolderIds>" + "</ns2:FindItem>" + "</SOAP-ENV:Body>" + "</SOAP-ENV:Envelope>", + start, end /* Timeframe */ + ); + + AST_LIST_HEAD_INIT_NOLOCK(&ctx.ids); + + /* Dispatch request and parse response as XML */ + if (send_ews_request_and_parse(request, &ctx)) { + ast_free(request); + return NULL; + } + + /* Cleanup */ + ast_free(request); + + return AST_LIST_FIRST(&ctx.ids); +} + +static int parse_ewscal_id(struct ewscal_pvt *pvt, const char *id) { + struct ast_str *request; + struct xml_context ctx = { + .pvt = pvt, + .op = XML_OP_GET, + }; + + if (!(request = ast_str_create(512))) { + return -1; + } + + ast_str_set(&request, 0, + "<?xml version=\"1.0\" encoding=\"utf-8\"?>" + "<soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\" " + "xmlns:t=\"http://schemas.microsoft.com/exchange/services/2006/types\">" + "<soap:Body>" + "<GetItem xmlns=\"http://schemas.microsoft.com/exchange/services/2006/messages\">" + "<ItemShape>" + "<t:BaseShape>AllProperties</t:BaseShape>" + "</ItemShape>" + "<ItemIds>" + "<t:ItemId Id=\"%s\"/>" + "</ItemIds>" + "</GetItem>" + "</soap:Body>" + "</soap:Envelope>", id + ); + + if (send_ews_request_and_parse(request, &ctx)) { + ast_free(request); + return -1; + } + + ast_free(request); + + return 0; +} + +static int update_ewscal(struct ewscal_pvt *pvt) +{ + struct calendar_id *id_head; + struct calendar_id *iter; + + if (!(id_head = get_ewscal_ids_for(pvt))) { + return 0; + } + + for (iter = id_head; iter; iter = AST_LIST_NEXT(iter, next)) { + parse_ewscal_id(pvt, ast_str_buffer(iter->id)); + ast_free(iter->id); + ast_free(iter); + } + + return 0; +} + +static void *ewscal_load_calendar(void *void_data) +{ + struct ewscal_pvt *pvt; + const struct ast_config *cfg; + struct ast_variable *v; + struct ast_calendar *cal = void_data; + ast_mutex_t refreshlock; + + ast_debug(5, "EWS: ewscal_load_calendar()\n"); + + if (!(cal && (cfg = ast_calendar_config_acquire()))) { + ast_log(LOG_ERROR, "You must enable calendar support for res_ewscal to load\n"); + return NULL; + } + + if (ao2_trylock(cal)) { + if (cal->unloading) { + ast_log(LOG_WARNING, "Unloading module, load_calendar cancelled.\n"); + } else { + ast_log(LOG_WARNING, "Could not lock calendar, aborting!\n"); + } + ast_calendar_config_release(); + return NULL; + } + + if (!(pvt = ao2_alloc(sizeof(*pvt), ewscal_destructor))) { + ast_log(LOG_ERROR, "Could not allocate ewscal_pvt structure for calendar: %s\n", cal->name); + ast_calendar_config_release(); + return NULL; + } + + pvt->owner = cal; + + if (!(pvt->events = ast_calendar_event_container_alloc())) { + ast_log(LOG_ERROR, "Could not allocate space for fetching events for calendar: %s\n", cal->name); + pvt = unref_ewscal(pvt); + ao2_unlock(cal); + ast_calendar_config_release(); + return NULL; + } + + if (ast_string_field_init(pvt, 32)) { + ast_log(LOG_ERROR, "Couldn't allocate string field space for calendar: %s\n", cal->name); + pvt = unref_ewscal(pvt); + ao2_unlock(cal); + ast_calendar_config_release(); + return NULL; + } + + for (v = ast_variable_browse(cfg, cal->name); v; v = v->next) { + if (!strcasecmp(v->name, "url")) { + ast_string_field_set(pvt, url, v->value); + } else if (!strcasecmp(v->name, "user")) { + ast_string_field_set(pvt, user, v->value); + } else if (!strcasecmp(v->name, "secret")) { + ast_string_field_set(pvt, secret, v->value); + } + } + + ast_calendar_config_release(); + + if (ast_strlen_zero(pvt->url)) { + ast_log(LOG_WARNING, "No URL was specified for Exchange Web Service calendar '%s' - skipping.\n", cal->name); + pvt = unref_ewscal(pvt); + ao2_unlock(cal); + return NULL; + } + + if (ne_uri_parse(pvt->url, &pvt->uri) || pvt->uri.host == NULL || pvt->uri.path == NULL) { + ast_log(LOG_WARNING, "Could not parse url '%s' for Exchange Web Service calendar '%s' - skipping.\n", pvt->url, cal->name); + pvt = unref_ewscal(pvt); + ao2_unlock(cal); + return NULL; + } + + if (pvt->uri.scheme == NULL) { + pvt->uri.scheme = "http"; + } + + if (pvt->uri.port == 0) { + pvt->uri.port = ne_uri_defaultport(pvt->uri.scheme); + } + + ast_debug(3, "ne_uri.scheme = %s\n", pvt->uri.scheme); + ast_debug(3, "ne_uri.host = %s\n", pvt->uri.host); + ast_debug(3, "ne_uri.port = %u\n", pvt->uri.port); + ast_debug(3, "ne_uri.path = %s\n", pvt->uri.path); + ast_debug(3, "user = %s\n", pvt->user); + ast_debug(3, "secret = %s\n", pvt->secret); + + pvt->session = ne_session_create(pvt->uri.scheme, pvt->uri.host, pvt->uri.port); + ne_set_server_auth(pvt->session, auth_credentials, pvt); + ne_set_useragent(pvt->session, "Asterisk"); + + if (!strcasecmp(pvt->uri.scheme, "https")) { + ne_ssl_trust_default_ca(pvt->session); + ne_ssl_set_verify(pvt->session, ssl_verify, pvt); + } + + cal->tech_pvt = pvt; + + ast_mutex_init(&refreshlock); + + /* Load it the first time */ + update_ewscal(pvt); + + ao2_unlock(cal); + + /* The only writing from another thread will be if unload is true */ + for (;;) { + struct timeval tv = ast_tvnow(); + struct timespec ts = {0,}; + + ts.tv_sec = tv.tv_sec + (60 * pvt->owner->refresh); + + ast_mutex_lock(&refreshlock); + while (!pvt->owner->unloading) { + if (ast_cond_timedwait(&pvt->owner->unload, &refreshlock, &ts) == ETIMEDOUT) { + break; + } + } + ast_mutex_unlock(&refreshlock); + + if (pvt->owner->unloading) { + ast_debug(10, "Skipping refresh since we got a shutdown signal\n"); + return NULL; + } + + ast_debug(10, "Refreshing after %d minute timeout\n", pvt->owner->refresh); + + update_ewscal(pvt); + } + + return NULL; +} + +static int load_module(void) +{ + /* Actualy, 0.29.1 is required (because of NTLM authentication), but this + * function does not support matching patch version. */ + if (ne_version_match(0, 29)) { + ast_log(LOG_ERROR, "Exchange Web Service calendar module require neon >= 0.29.1, but %s is installed.\n", ne_version_string()); + return AST_MODULE_LOAD_DECLINE; + } + + if (ast_calendar_register(&ewscal_tech) && (ne_sock_init() == 0)) { + return AST_MODULE_LOAD_DECLINE; + } + + return AST_MODULE_LOAD_SUCCESS; +} + +static int unload_module(void) +{ + ne_sock_exit(); + ast_calendar_unregister(&ewscal_tech); + + return 0; +} + +AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Asterisk MS Exchange Web Service Calendar Integration", + .load = load_module, + .unload = unload_module, +); |