[svn-commits] twilson: branch group/calendaring_ews r258514 - in /team/group/calendaring_ew...

SVN commits to the Digium repositories svn-commits at lists.digium.com
Thu Apr 22 00:08:06 CDT 2010


Author: twilson
Date: Thu Apr 22 00:08:05 2010
New Revision: 258514

URL: http://svnview.digium.com/svn/asterisk?view=rev&rev=258514
Log:
Initial import of Jan Kaláb's Exchange Web Servcies code

I just moved variable declarations to the beginning of code blocksand removed
trailing whitespace.

Added:
    team/group/calendaring_ews/res/res_calendar_ews.c   (with props)
Modified:
    team/group/calendaring_ews/   (props changed)

Propchange: team/group/calendaring_ews/
------------------------------------------------------------------------------
    automerge = *

Propchange: team/group/calendaring_ews/
------------------------------------------------------------------------------
    automerge-email = twilson at digium.com

Added: team/group/calendaring_ews/res/res_calendar_ews.c
URL: http://svnview.digium.com/svn/asterisk/team/group/calendaring_ews/res/res_calendar_ews.c?view=auto&rev=258514
==============================================================================
--- team/group/calendaring_ews/res/res_calendar_ews.c (added)
+++ team/group/calendaring_ews/res/res_calendar_ews.c Thu Apr 22 00:08:05 2010
@@ -1,0 +1,601 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2008 - 2009, Digium, Inc.
+ *
+ * Jan Kalab <pitlicek at 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 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,
+};
+
+struct xml_context {
+	ne_xml_parser *parser;
+	struct ast_str *cdata;
+	struct ast_calendar_event *event;
+	struct ewscal_pvt *pvt;
+};
+
+/* 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
+};
+
+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(1, "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;
+
+	ast_debug(1, "MSTime: %s\n", mstime);
+	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)
+{
+	/* ast_debug(1, "EWS: XML: Start: %s\n", name); */
+	struct xml_context *ctx = userdata;
+
+	/* Nodes needed for traversing until CalendarItem is found */
+	if (	!strcmp(name, "Envelope")			||
+		!strcmp(name, "Body")				||
+		!strcmp(name, "FindItemResponse")		||
+		!strcmp(name, "ResponseMessages")		||
+		!strcmp(name, "FindItemResponseMessage")	||
+		!strcmp(name, "Items")
+	) {
+		return 1;
+	}
+
+	/* Get number of events */
+	else if (!strcmp(name, "RootFolder")) {
+		int items;
+
+		ast_debug(1, "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(1, "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;
+	}
+
+	/* Event start */
+	else if (!strcmp(name, "CalendarItem")) {
+		ast_debug(1, "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;
+	}
+
+	/* Event UID */
+	else if (!strcmp(name, "ItemId")) {
+		ast_debug(1, "EWS: 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;
+	}
+
+	/* Event name */
+	else if (!strcmp(name, "Subject")) {
+		if (!ctx->cdata) {
+			return NE_XML_ABORT;
+		}
+		ast_str_reset(ctx->cdata);
+		return XML_EVENT_NAME;
+	}
+
+	/* Event start time */
+	else if (!strcmp(name, "Start")) {
+		return XML_EVENT_START;
+	}
+
+	/* Event end time */
+	else if (!strcmp(name, "End")) {
+		return XML_EVENT_END;
+	}
+
+	/* Event busy state */
+	else if (!strcmp(name, "LegacyFreeBusyStatus")) {
+		return XML_EVENT_BUSY;
+	}
+
+	/* Event organizer */
+	else if (!strcmp(name, "Organizer") || (parent == XML_EVENT_ORGANIZER && (!strcmp(name, "Mailbox") || !strcmp(name, "Name")))) {
+		if (!ctx->cdata) {
+			return NE_XML_ABORT;
+		}
+		ast_str_reset(ctx->cdata);
+		return XML_EVENT_ORGANIZER;
+	}
+
+	/* Event location */
+	else if (!strcmp(name, "Location")) {
+		if (!ctx->cdata) {
+			return NE_XML_ABORT;
+		}
+		ast_str_reset(ctx->cdata);
+		return XML_EVENT_LOCATION;
+	}
+
+	return NE_XML_DECLINE;
+}
+
+static int cdata(void *userdata, int state, const char *cdata, size_t len)
+{
+	/* !!! DON'T USE AST_STRING_FIELD FUNCTIONS HERE, JUST COLLECT CTX->CDATA !!! */
+	if (state >= XML_EVENT_NAME) {	/* We've got some interesting CDATA */
+		struct xml_context *ctx = userdata;
+		char data[len + 1];
+
+		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(1, "EWS: XML: Busy: yes\n");
+				ctx->event->busy_state = AST_CALENDAR_BS_BUSY;
+			}
+			else if (!strcmp(data, "Tentative")) {
+				ast_debug(1, "EWS: XML: Busy: tentative\n");
+				ctx->event->busy_state = AST_CALENDAR_BS_BUSY_TENTATIVE;
+			}
+			else {
+				ast_debug(1, "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(1, "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)
+{
+	/* ast_debug(1, "EWS: XML: End:   %s\n", name); */
+	struct xml_context *ctx = userdata;
+
+	/* Event name end*/
+	if (!strcmp(name, "Subject")) {
+		ast_string_field_set(ctx->event, summary, ast_str_buffer(ctx->cdata));
+		ast_debug(1, "EWS: XML: Summary: %s\n", ctx->event->summary);
+		ast_str_reset(ctx->cdata);
+	}
+
+	/* Event organizer end */
+	else if (!strcmp(name, "Organizer")) {
+		ast_string_field_set(ctx->event, organizer, ast_str_buffer(ctx->cdata));
+		ast_debug(1, "EWS: XML: Organizer: %s\n", ctx->event->organizer);
+		ast_str_reset(ctx->cdata);
+	}
+
+	/* Event location end */
+	else if (!strcmp(name, "Location")) {
+		ast_string_field_set(ctx->event, location, ast_str_buffer(ctx->cdata));
+		ast_debug(1, "EWS: XML: Location: %s\n", ctx->event->location);
+		ast_str_reset(ctx->cdata);
+	}
+
+	/* Event end */
+	else if (!strcmp(name, "CalendarItem")) {
+		ast_debug(1, "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;
+		}
+	}
+
+	/* Events end */
+	else if (!strcmp(name, "RootFolder")) {
+		ast_debug(1, "EWS: XML: All events has been parsed, merging…\n");
+		ast_calendar_merge_events(ctx->pvt->owner, ctx->pvt->events);
+	}
+	return 0;
+}
+
+static int update_ewscal(struct ewscal_pvt *pvt)
+{
+	char start[21], end[21];
+	struct ast_tm tm;
+	struct timeval tv;
+	struct ast_str *request;
+	struct xml_context ctx;
+	int ret;
+	ne_request *req;
+	ne_xml_parser *parser;
+
+	ast_debug(1, "EWS: update_ewscal()\n");
+
+	/*
+	 * This will perform HTTP request
+	 */
+	ast_debug(1, "EWS: HTTP request...\n");
+	if (!pvt) {
+		ast_log(LOG_ERROR, "There is no private!\n");
+		return -1;
+	}
+
+	/* 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 -1;
+	}
+	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>AllProperties</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_debug(1, "%s", ast_str_buffer(request));
+
+	/* Prepare HTTP POST request */
+	req = ne_request_create(pvt->session, "POST", pvt->uri.path);
+	ne_set_request_flag(req, NE_REQFLAG_IDEMPOTENT, 0);
+
+	/* Set headers */
+	ne_add_request_header(req, "Content-Type", "text/xml; charset=utf-8");	/* should be application/soap+xml, but MS… :/ */
+	ne_add_request_header(req, "SOAPAction", "\"http://schemas.microsoft.com/exchange/services/2006/messages/FindItem\"");
+
+	/* 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;
+	ctx.event = NULL;
+	ctx.pvt = pvt;
+	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", pvt->url, ne_get_error(pvt->session));
+		ne_request_destroy(req);
+		ast_free(request);
+		ne_xml_destroy(parser);
+		return -1;
+	}
+
+	/* Cleanup */
+	ast_free(request);
+	ne_request_destroy(req);
+	ne_xml_destroy(parser);
+
+	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(1, "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(1, "ne_uri.scheme	= %s\n", pvt->uri.scheme);
+	ast_debug(1, "ne_uri.host	= %s\n", pvt->uri.host);
+	ast_debug(1, "ne_uri.port	= %u\n", pvt->uri.port);
+	ast_debug(1, "ne_uri.path	= %s\n", pvt->uri.path);
+	ast_debug(1, "user		= %s\n", pvt->user);
+	ast_debug(1, "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)
+{
+	ast_debug(1, "EWS: load_module():");
+	if (ne_version_match(0, 29)) {	/* Actualy, 0.29.1 is required (because of NTLM authentication), but this function does not support matching patch version. */
+		ast_debug(1, " AST_MODULE_LOAD_DECLINE\n");
+		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)) {
+		ast_debug(1, " AST_MODULE_LOAD_DECLINE\n");
+		return AST_MODULE_LOAD_DECLINE;
+	}
+	ast_debug(1, " AST_MODULE_LOAD_SUCCESS\n");
+	return AST_MODULE_LOAD_SUCCESS;
+}
+
+static int unload_module(void)
+{
+	ast_debug(1, "EWS: unload_module()\n");
+	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,
+);

Propchange: team/group/calendaring_ews/res/res_calendar_ews.c
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: team/group/calendaring_ews/res/res_calendar_ews.c
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision

Propchange: team/group/calendaring_ews/res/res_calendar_ews.c
------------------------------------------------------------------------------
    svn:mime-type = text/plain




More information about the svn-commits mailing list