Mon Oct 8 12:39:04 2012

Asterisk developer's documentation


res_calendar_ews.c

Go to the documentation of this file.
00001 /*
00002  * Asterisk -- An open source telephony toolkit.
00003  *
00004  * Copyright (C) 2008 - 2009, Digium, Inc.
00005  *
00006  * Jan Kalab <pitlicek@gmail.com>
00007  *
00008  * See http://www.asterisk.org for more information about
00009  * the Asterisk project. Please do not directly contact
00010  * any of the maintainers of this project for assistance;
00011  * the project provides a web site, mailing lists and IRC
00012  * channels for your use.
00013  *
00014  * This program is free software, distributed under the terms of
00015  * the GNU General Public License Version 2. See the LICENSE file
00016  * at the top of the source tree.
00017  */
00018 
00019 /*! \file
00020  * \brief Resource for handling MS Exchange Web Service calendars
00021  */
00022 
00023 /*** MODULEINFO
00024    <depend>neon29</depend>
00025    <support_level>core</support_level>
00026 ***/
00027 
00028 #include "asterisk.h"
00029 
00030 ASTERISK_FILE_VERSION(__FILE__, "$Revision: 366740 $")
00031 
00032 #include <ne_request.h>
00033 #include <ne_session.h>
00034 #include <ne_uri.h>
00035 #include <ne_socket.h>
00036 #include <ne_auth.h>
00037 #include <ne_xml.h>
00038 #include <ne_xmlreq.h>
00039 #include <ne_utils.h>
00040 #include <ne_redirect.h>
00041 
00042 #include "asterisk/module.h"
00043 #include "asterisk/calendar.h"
00044 #include "asterisk/lock.h"
00045 #include "asterisk/config.h"
00046 #include "asterisk/astobj2.h"
00047 
00048 static void *ewscal_load_calendar(void *data);
00049 static void *unref_ewscal(void *obj);
00050 static int ewscal_write_event(struct ast_calendar_event *event);
00051 
00052 static struct ast_calendar_tech ewscal_tech = {
00053    .type = "ews",
00054    .description = "MS Exchange Web Service calendars",
00055    .module = AST_MODULE,
00056    .load_calendar = ewscal_load_calendar,
00057    .unref_calendar = unref_ewscal,
00058    .write_event = ewscal_write_event,
00059 };
00060 
00061 enum xml_op {
00062    XML_OP_FIND = 100,
00063    XML_OP_GET,
00064    XML_OP_CREATE,
00065 };
00066 
00067 struct calendar_id {
00068    struct ast_str *id;
00069    AST_LIST_ENTRY(calendar_id) next;
00070 };
00071 
00072 struct xml_context {
00073    ne_xml_parser *parser;
00074    struct ast_str *cdata;
00075    struct ast_calendar_event *event;
00076    enum xml_op op;
00077    struct ewscal_pvt *pvt;
00078    AST_LIST_HEAD_NOLOCK(ids, calendar_id) ids;
00079 };
00080 
00081 /* Important states of XML parsing */
00082 enum {
00083    XML_EVENT_NAME = 10,
00084    XML_EVENT_START,
00085    XML_EVENT_END,
00086    XML_EVENT_BUSY,
00087    XML_EVENT_ORGANIZER,
00088    XML_EVENT_LOCATION,
00089    XML_EVENT_ATTENDEE_LIST,
00090    XML_EVENT_ATTENDEE,
00091    XML_EVENT_MAILBOX,
00092    XML_EVENT_EMAIL_ADDRESS,
00093    XML_EVENT_CATEGORIES,
00094    XML_EVENT_CATEGORY,
00095    XML_EVENT_IMPORTANCE,
00096 };
00097 
00098 struct ewscal_pvt {
00099    AST_DECLARE_STRING_FIELDS(
00100       AST_STRING_FIELD(url);
00101       AST_STRING_FIELD(user);
00102       AST_STRING_FIELD(secret);
00103    );
00104    struct ast_calendar *owner;
00105    ne_uri uri;
00106    ne_session *session;
00107    struct ao2_container *events;
00108    unsigned int items;
00109 };
00110 
00111 static void ewscal_destructor(void *obj)
00112 {
00113    struct ewscal_pvt *pvt = obj;
00114 
00115    ast_debug(1, "Destroying pvt for Exchange Web Service calendar %s\n", "pvt->owner->name");
00116    if (pvt->session) {
00117       ne_session_destroy(pvt->session);
00118    }
00119    ast_string_field_free_memory(pvt);
00120 
00121    ao2_callback(pvt->events, OBJ_UNLINK | OBJ_NODATA | OBJ_MULTIPLE, NULL, NULL);
00122 
00123    ao2_ref(pvt->events, -1);
00124 }
00125 
00126 static void *unref_ewscal(void *obj)
00127 {
00128    struct ewscal_pvt *pvt = obj;
00129 
00130    ast_debug(5, "EWS: unref_ewscal()\n");
00131    ao2_ref(pvt, -1);
00132    return NULL;
00133 }
00134 
00135 static int auth_credentials(void *userdata, const char *realm, int attempts, char *username, char *secret)
00136 {
00137    struct ewscal_pvt *pvt = userdata;
00138 
00139    if (attempts > 1) {
00140       ast_log(LOG_WARNING, "Invalid username or password for Exchange Web Service calendar '%s'\n", pvt->owner->name);
00141       return -1;
00142    }
00143 
00144    ne_strnzcpy(username, pvt->user, NE_ABUFSIZ);
00145    ne_strnzcpy(secret, pvt->secret, NE_ABUFSIZ);
00146 
00147    return 0;
00148 }
00149 
00150 static int ssl_verify(void *userdata, int failures, const ne_ssl_certificate *cert)
00151 {
00152    struct ewscal_pvt *pvt = userdata;
00153    if (failures & NE_SSL_UNTRUSTED) {
00154       ast_log(LOG_WARNING, "Untrusted SSL certificate for calendar %s!\n", pvt->owner->name);
00155       return 0;
00156    }
00157    return 1;   /* NE_SSL_NOTYETVALID, NE_SSL_EXPIRED, NE_SSL_IDMISMATCH */
00158 }
00159 
00160 static time_t mstime_to_time_t(char *mstime)
00161 {
00162    struct ast_tm tm;
00163    struct timeval tv;
00164 
00165    if (ast_strptime(mstime, "%FT%TZ", &tm)) {
00166       tv = ast_mktime(&tm, "UTC");
00167       return tv.tv_sec;
00168    }
00169    return 0;
00170 }
00171 
00172 static int startelm(void *userdata, int parent, const char *nspace, const char *name, const char **atts)
00173 {
00174    struct xml_context *ctx = userdata;
00175 
00176    ast_debug(5, "EWS: XML: Start: %s\n", name);
00177    if (ctx->op == XML_OP_CREATE) {
00178       return NE_XML_DECLINE;
00179    }
00180 
00181    /* Nodes needed for traversing until CalendarItem is found */
00182    if (!strcmp(name, "Envelope") ||
00183       !strcmp(name, "Body") ||
00184       !strcmp(name, "FindItemResponse") ||
00185       !strcmp(name, "GetItemResponse") ||
00186       !strcmp(name, "CreateItemResponse") ||
00187       !strcmp(name, "ResponseMessages") ||
00188       !strcmp(name, "FindItemResponseMessage") || !strcmp(name, "GetItemResponseMessage") ||
00189       !strcmp(name, "Items")
00190    ) {
00191       return 1;
00192    } else if (!strcmp(name, "RootFolder")) {
00193       /* Get number of events */
00194       unsigned int items;
00195 
00196       ast_debug(3, "EWS: XML: <RootFolder>\n");
00197       if (sscanf(ne_xml_get_attr(ctx->parser, atts, NULL, "TotalItemsInView"), "%u", &items) != 1) {
00198          /* Couldn't read enything */
00199          ne_xml_set_error(ctx->parser, "Could't read number of events.");
00200          return NE_XML_ABORT;
00201       }
00202 
00203       ast_debug(3, "EWS: %u calendar items to load\n", items);
00204       ctx->pvt->items = items;
00205       if (items < 1) {
00206          /* Stop processing XML if there are no events */
00207          ast_calendar_merge_events(ctx->pvt->owner, ctx->pvt->events);
00208          return NE_XML_DECLINE;
00209       }
00210       return 1;
00211    } else if (!strcmp(name, "CalendarItem")) {
00212       /* Event start */
00213       ast_debug(3, "EWS: XML: <CalendarItem>\n");
00214       if (!(ctx->pvt && ctx->pvt->owner)) {
00215          ast_log(LOG_ERROR, "Require a private structure with an owner\n");
00216          return NE_XML_ABORT;
00217       }
00218 
00219       ctx->event = ast_calendar_event_alloc(ctx->pvt->owner);
00220       if (!ctx->event) {
00221          ast_log(LOG_ERROR, "Could not allocate an event!\n");
00222          return NE_XML_ABORT;
00223       }
00224 
00225       ctx->cdata = ast_str_create(64);
00226       if (!ctx->cdata) {
00227          ast_log(LOG_ERROR, "Could not allocate CDATA!\n");
00228          return NE_XML_ABORT;
00229       }
00230 
00231       return 1;
00232    } else if (!strcmp(name, "ItemId")) {
00233       /* Event UID */
00234       if (ctx->op == XML_OP_FIND) {
00235          struct calendar_id *id;
00236          if (!(id = ast_calloc(1, sizeof(*id)))) {
00237             return NE_XML_ABORT;
00238          }
00239          if (!(id->id = ast_str_create(256))) {
00240             ast_free(id);
00241             return NE_XML_ABORT;
00242          }
00243          ast_str_set(&id->id, 0, "%s", ne_xml_get_attr(ctx->parser, atts, NULL, "Id"));
00244          AST_LIST_INSERT_TAIL(&ctx->ids, id, next);
00245          ast_debug(3, "EWS_FIND: XML: UID: %s\n", ast_str_buffer(id->id));
00246       } else {
00247          ast_debug(3, "EWS_GET: XML: UID: %s\n", ne_xml_get_attr(ctx->parser, atts, NULL, "Id"));
00248          ast_string_field_set(ctx->event, uid, ne_xml_get_attr(ctx->parser, atts, NULL, "Id"));
00249       }
00250       return XML_EVENT_NAME;
00251    } else if (!strcmp(name, "Subject")) {
00252       /* Event name */
00253       if (!ctx->cdata) {
00254          return NE_XML_ABORT;
00255       }
00256       ast_str_reset(ctx->cdata);
00257       return XML_EVENT_NAME;
00258    } else if (!strcmp(name, "Start")) {
00259       /* Event start time */
00260       return XML_EVENT_START;
00261    } else if (!strcmp(name, "End")) {
00262       /* Event end time */
00263       return XML_EVENT_END;
00264    } else if (!strcmp(name, "LegacyFreeBusyStatus")) {
00265       /* Event busy state */
00266       return XML_EVENT_BUSY;
00267    } else if (!strcmp(name, "Organizer") ||
00268          (parent == XML_EVENT_ORGANIZER && (!strcmp(name, "Mailbox") ||
00269          !strcmp(name, "Name")))) {
00270       /* Event organizer */
00271       if (!ctx->cdata) {
00272          return NE_XML_ABORT;
00273       }
00274       ast_str_reset(ctx->cdata);
00275       return XML_EVENT_ORGANIZER;
00276    } else if (!strcmp(name, "Location")) {
00277       /* Event location */
00278       if (!ctx->cdata) {
00279          return NE_XML_ABORT;
00280       }
00281       ast_str_reset(ctx->cdata);
00282       return XML_EVENT_LOCATION;
00283    } else if (!strcmp(name, "Categories")) {
00284       /* Event categories */
00285       if (!ctx->cdata) {
00286          return NE_XML_ABORT;
00287       }
00288       ast_str_reset(ctx->cdata);
00289       return XML_EVENT_CATEGORIES;
00290    } else if (parent == XML_EVENT_CATEGORIES && !strcmp(name, "String")) {
00291       /* Event category */
00292       return XML_EVENT_CATEGORY;
00293    } else if (!strcmp(name, "Importance")) {
00294       /* Event importance (priority) */
00295       if (!ctx->cdata) {
00296          return NE_XML_ABORT;
00297       }
00298       ast_str_reset(ctx->cdata);
00299       return XML_EVENT_IMPORTANCE;
00300    } else if (!strcmp(name, "RequiredAttendees") || !strcmp(name, "OptionalAttendees")) {
00301       return XML_EVENT_ATTENDEE_LIST;
00302    } else if (!strcmp(name, "Attendee") && parent == XML_EVENT_ATTENDEE_LIST) {
00303       return XML_EVENT_ATTENDEE;
00304    } else if (!strcmp(name, "Mailbox") && parent == XML_EVENT_ATTENDEE) {
00305       return XML_EVENT_MAILBOX;
00306    } else if (!strcmp(name, "EmailAddress") && parent == XML_EVENT_MAILBOX) {
00307       if (!ctx->cdata) {
00308          return NE_XML_ABORT;
00309       }
00310       ast_str_reset(ctx->cdata);
00311       return XML_EVENT_EMAIL_ADDRESS;
00312    }
00313 
00314    return NE_XML_DECLINE;
00315 }
00316 
00317 static int cdata(void *userdata, int state, const char *cdata, size_t len)
00318 {
00319    struct xml_context *ctx = userdata;
00320    char data[len + 1];
00321 
00322    /* !!! DON'T USE AST_STRING_FIELD FUNCTIONS HERE, JUST COLLECT CTX->CDATA !!! */
00323    if (state < XML_EVENT_NAME || ctx->op == XML_OP_CREATE) {
00324       return 0;
00325    }
00326 
00327    if (!ctx->event) {
00328       ast_log(LOG_ERROR, "Parsing event data, but event object does not exist!\n");
00329       return 1;
00330    }
00331 
00332    if (!ctx->cdata) {
00333       ast_log(LOG_ERROR, "String for storing CDATA is unitialized!\n");
00334       return 1;
00335    }
00336 
00337    ast_copy_string(data, cdata, len + 1);
00338 
00339    switch (state) {
00340    case XML_EVENT_START:
00341       ctx->event->start = mstime_to_time_t(data);
00342       break;
00343    case XML_EVENT_END:
00344       ctx->event->end = mstime_to_time_t(data);
00345       break;
00346    case XML_EVENT_BUSY:
00347       if (!strcmp(data, "Busy") || !strcmp(data, "OOF")) {
00348          ast_debug(3, "EWS: XML: Busy: yes\n");
00349          ctx->event->busy_state = AST_CALENDAR_BS_BUSY;
00350       }
00351       else if (!strcmp(data, "Tentative")) {
00352          ast_debug(3, "EWS: XML: Busy: tentative\n");
00353          ctx->event->busy_state = AST_CALENDAR_BS_BUSY_TENTATIVE;
00354       }
00355       else {
00356          ast_debug(3, "EWS: XML: Busy: no\n");
00357          ctx->event->busy_state = AST_CALENDAR_BS_FREE;
00358       }
00359       break;
00360    case XML_EVENT_CATEGORY:
00361       if (ast_str_strlen(ctx->cdata) == 0) {
00362          ast_str_set(&ctx->cdata, 0, "%s", data);
00363       } else {
00364          ast_str_append(&ctx->cdata, 0, ",%s", data);
00365       }
00366       break;
00367    default:
00368       ast_str_append(&ctx->cdata, 0, "%s", data);
00369    }
00370 
00371    ast_debug(5, "EWS: XML: CDATA: %s\n", ast_str_buffer(ctx->cdata));
00372 
00373    return 0;
00374 }
00375 
00376 static int endelm(void *userdata, int state, const char *nspace, const char *name)
00377 {
00378    struct xml_context *ctx = userdata;
00379 
00380    ast_debug(5, "EWS: XML: End:   %s\n", name);
00381    if (ctx->op == XML_OP_FIND || ctx->op == XML_OP_CREATE) {
00382       return NE_XML_DECLINE;
00383    }
00384 
00385    if (!strcmp(name, "Subject")) {
00386       /* Event name end*/
00387       ast_string_field_set(ctx->event, summary, ast_str_buffer(ctx->cdata));
00388       ast_debug(3, "EWS: XML: Summary: %s\n", ctx->event->summary);
00389       ast_str_reset(ctx->cdata);
00390    } else if (!strcmp(name, "Organizer")) {
00391       /* Event organizer end */
00392       ast_string_field_set(ctx->event, organizer, ast_str_buffer(ctx->cdata));
00393       ast_debug(3, "EWS: XML: Organizer: %s\n", ctx->event->organizer);
00394       ast_str_reset(ctx->cdata);
00395    } else if (!strcmp(name, "Location")) {
00396       /* Event location end */
00397       ast_string_field_set(ctx->event, location, ast_str_buffer(ctx->cdata));
00398       ast_debug(3, "EWS: XML: Location: %s\n", ctx->event->location);
00399       ast_str_reset(ctx->cdata);
00400    } else if (!strcmp(name, "Categories")) {
00401       /* Event categories end */
00402       ast_string_field_set(ctx->event, categories, ast_str_buffer(ctx->cdata));
00403       ast_debug(3, "EWS: XML: Categories: %s\n", ctx->event->categories);
00404       ast_str_reset(ctx->cdata);
00405    } else if (!strcmp(name, "Importance")) {
00406       /* Event importance end */
00407       if (!strcmp(ast_str_buffer(ctx->cdata), "Low")) {
00408          ctx->event->priority = 9;
00409       } else if (!strcmp(ast_str_buffer(ctx->cdata), "Normal")) {
00410          ctx->event->priority = 5;
00411       } else if (!strcmp(ast_str_buffer(ctx->cdata), "High")) {
00412          ctx->event->priority = 1;
00413       }
00414       ast_debug(3, "EWS: XML: Importance: %s (%d)\n", ast_str_buffer(ctx->cdata), ctx->event->priority);
00415       ast_str_reset(ctx->cdata);
00416    } else if (state == XML_EVENT_EMAIL_ADDRESS) {
00417       struct ast_calendar_attendee *attendee;
00418 
00419       if (!(attendee = ast_calloc(1, sizeof(*attendee)))) {
00420          ctx->event = ast_calendar_unref_event(ctx->event);
00421          return  1;
00422       }
00423 
00424       if (ast_str_strlen(ctx->cdata)) {
00425          attendee->data = ast_strdup(ast_str_buffer(ctx->cdata));
00426          AST_LIST_INSERT_TAIL(&ctx->event->attendees, attendee, next);
00427       } else {
00428          ast_free(attendee);
00429       }
00430       ast_debug(3, "EWS: XML: attendee address '%s'\n", ast_str_buffer(ctx->cdata));
00431       ast_str_reset(ctx->cdata);
00432    } else if (!strcmp(name, "CalendarItem")) {
00433       /* Event end */
00434       ast_debug(3, "EWS: XML: </CalendarItem>\n");
00435       ast_free(ctx->cdata);
00436       if (ctx->event) {
00437          ao2_link(ctx->pvt->events, ctx->event);
00438          ctx->event = ast_calendar_unref_event(ctx->event);
00439       } else {
00440          ast_log(LOG_ERROR, "Event data ended in XML, but event object does not exist!\n");
00441          return 1;
00442       }
00443    } else if (!strcmp(name, "Envelope")) {
00444       /* Events end */
00445       ast_debug(3, "EWS: XML: %d of %d event(s) has been parsed…\n", ao2_container_count(ctx->pvt->events), ctx->pvt->items);
00446       if (ao2_container_count(ctx->pvt->events) >= ctx->pvt->items) {
00447          ast_debug(3, "EWS: XML: All events has been parsed, merging…\n");
00448          ast_calendar_merge_events(ctx->pvt->owner, ctx->pvt->events);
00449       }
00450    }
00451 
00452    return 0;
00453 }
00454 
00455 static const char *mstime(time_t t, char *buf, size_t buflen)
00456 {
00457    struct timeval tv = {
00458       .tv_sec = t,
00459    };
00460    struct ast_tm tm;
00461 
00462    ast_localtime(&tv, &tm, "utc");
00463    ast_strftime(buf, buflen, "%FT%TZ", &tm);
00464 
00465    return S_OR(buf, "");
00466 }
00467 
00468 static const char *msstatus(enum ast_calendar_busy_state state)
00469 {
00470    switch (state) {
00471    case AST_CALENDAR_BS_BUSY_TENTATIVE:
00472       return "Tentative";
00473    case AST_CALENDAR_BS_BUSY:
00474       return "Busy";
00475    case AST_CALENDAR_BS_FREE:
00476       return "Free";
00477    default:
00478       return "";
00479    }
00480 }
00481 
00482 static const char *get_soap_action(enum xml_op op)
00483 {
00484    switch (op) {
00485    case XML_OP_FIND:
00486       return "\"http://schemas.microsoft.com/exchange/services/2006/messages/FindItem\"";
00487    case XML_OP_GET:
00488       return "\"http://schemas.microsoft.com/exchange/services/2006/messages/GetItem\"";
00489    case XML_OP_CREATE:
00490       return "\"http://schemas.microsoft.com/exchange/services/2006/messages/CreateItem\"";
00491    }
00492 
00493    return "";
00494 }
00495 
00496 static int send_ews_request_and_parse(struct ast_str *request, struct xml_context *ctx)
00497 {
00498    int ret;
00499    ne_request *req;
00500    ne_xml_parser *parser;
00501 
00502    ast_debug(3, "EWS: HTTP request...\n");
00503    if (!(ctx && ctx->pvt)) {
00504       ast_log(LOG_ERROR, "There is no private!\n");
00505       return -1;
00506    }
00507 
00508    if (!ast_str_strlen(request)) {
00509       ast_log(LOG_ERROR, "No request to send!\n");
00510       return -1;
00511    }
00512 
00513    ast_debug(3, "%s\n", ast_str_buffer(request));
00514 
00515    /* Prepare HTTP POST request */
00516    req = ne_request_create(ctx->pvt->session, "POST", ctx->pvt->uri.path);
00517    ne_set_request_flag(req, NE_REQFLAG_IDEMPOTENT, 0);
00518 
00519    /* Set headers--should be application/soap+xml, but MS… :/ */
00520    ne_add_request_header(req, "Content-Type", "text/xml; charset=utf-8");
00521    ne_add_request_header(req, "SOAPAction", get_soap_action(ctx->op));
00522 
00523    /* Set body to SOAP request */
00524    ne_set_request_body_buffer(req, ast_str_buffer(request), ast_str_strlen(request));
00525 
00526    /* Prepare XML parser */
00527    parser = ne_xml_create();
00528    ctx->parser = parser;
00529    ne_xml_push_handler(parser, startelm, cdata, endelm, ctx);  /* Callbacks */
00530 
00531    /* Dispatch request and parse response as XML */
00532    ret = ne_xml_dispatch_request(req, parser);
00533    if (ret != NE_OK) { /* Error handling */
00534       ast_log(LOG_WARNING, "Unable to communicate with Exchange Web Service at '%s': %s\n", ctx->pvt->url, ne_get_error(ctx->pvt->session));
00535       ne_request_destroy(req);
00536       ne_xml_destroy(parser);
00537       return -1;
00538    }
00539 
00540    /* Cleanup */
00541    ne_request_destroy(req);
00542    ne_xml_destroy(parser);
00543 
00544    return 0;
00545 }
00546 
00547 static int ewscal_write_event(struct ast_calendar_event *event)
00548 {
00549    struct ast_str *request;
00550    struct ewscal_pvt *pvt = event->owner->tech_pvt;
00551    char start[21], end[21];
00552    struct xml_context ctx = {
00553       .op = XML_OP_CREATE,
00554       .pvt = pvt,
00555    };
00556    int ret;
00557    char *category, *categories;
00558 
00559    if (!pvt) {
00560       return -1;
00561    }
00562 
00563    if (!(request = ast_str_create(1024))) {
00564       return -1;
00565    }
00566 
00567    ast_str_set(&request, 0,
00568       "<soap:Envelope xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" "
00569          "xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" "
00570          "xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\" "
00571          "xmlns:t=\"http://schemas.microsoft.com/exchange/services/2006/types\">"
00572          "<soap:Body>"
00573          "<CreateItem xmlns=\"http://schemas.microsoft.com/exchange/services/2006/messages\" "
00574             "xmlns:t=\"http://schemas.microsoft.com/exchange/services/2006/types\" "
00575             "SendMeetingInvitations=\"SendToNone\" >"
00576             "<SavedItemFolderId>"
00577                "<t:DistinguishedFolderId Id=\"calendar\"/>"
00578             "</SavedItemFolderId>"
00579             "<Items>"
00580                "<t:CalendarItem xmlns=\"http://schemas.microsoft.com/exchange/services/2006/types\">"
00581                   "<Subject>%s</Subject>"
00582                   "<Body BodyType=\"Text\">%s</Body>"
00583                   "<ReminderIsSet>false</ReminderIsSet>"
00584                   "<Start>%s</Start>"
00585                   "<End>%s</End>"
00586                   "<IsAllDayEvent>false</IsAllDayEvent>"
00587                   "<LegacyFreeBusyStatus>%s</LegacyFreeBusyStatus>"
00588                   "<Location>%s</Location>",
00589       event->summary,
00590       event->description,
00591       mstime(event->start, start, sizeof(start)),
00592       mstime(event->end, end, sizeof(end)),
00593       msstatus(event->busy_state),
00594       event->location
00595    );
00596    /* Event priority */
00597    switch (event->priority) {
00598    case 1:
00599    case 2:
00600    case 3:
00601    case 4:
00602       ast_str_append(&request, 0, "<Importance>High</Importance>");
00603       break;
00604    case 5:
00605       ast_str_append(&request, 0, "<Importance>Normal</Importance>");
00606       break;
00607    case 6:
00608    case 7:
00609    case 8:
00610    case 9:
00611       ast_str_append(&request, 0, "<Importance>Low</Importance>");
00612       break;
00613    }
00614    /* Event categories*/
00615    if (strlen(event->categories) > 0) {
00616       ast_str_append(&request, 0, "<Categories>");
00617       categories = ast_strdupa(event->categories); /* Duplicate string, since strsep() is destructive */
00618       category = strsep(&categories, ",");
00619       while (category != NULL) {
00620          ast_str_append(&request, 0, "<String>%s</String>", category);
00621          category = strsep(&categories, ",");
00622       }
00623       ast_str_append(&request, 0, "</Categories>");
00624    }
00625    /* Finish request */
00626    ast_str_append(&request, 0, "</t:CalendarItem></Items></CreateItem></soap:Body></soap:Envelope>");
00627 
00628    ret = send_ews_request_and_parse(request, &ctx);
00629 
00630    ast_free(request);
00631 
00632    return ret;
00633 }
00634 
00635 static struct calendar_id *get_ewscal_ids_for(struct ewscal_pvt *pvt)
00636 {
00637    char start[21], end[21];
00638    struct ast_tm tm;
00639    struct timeval tv;
00640    struct ast_str *request;
00641    struct xml_context ctx = {
00642       .op = XML_OP_FIND,
00643       .pvt = pvt,
00644    };
00645 
00646    ast_debug(5, "EWS: get_ewscal_ids_for()\n");
00647 
00648    if (!pvt) {
00649       ast_log(LOG_ERROR, "There is no private!\n");
00650       return NULL;
00651    }
00652 
00653    /* Prepare timeframe strings */
00654    tv = ast_tvnow();
00655    ast_localtime(&tv, &tm, "UTC");
00656    ast_strftime(start, sizeof(start), "%FT%TZ", &tm);
00657    tv.tv_sec += 60 * pvt->owner->timeframe;
00658    ast_localtime(&tv, &tm, "UTC");
00659    ast_strftime(end, sizeof(end), "%FT%TZ", &tm);
00660 
00661    /* Prepare SOAP request */
00662    if (!(request = ast_str_create(512))) {
00663       return NULL;
00664    }
00665 
00666    ast_str_set(&request, 0,
00667       "<SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\" "
00668       "xmlns:ns1=\"http://schemas.microsoft.com/exchange/services/2006/types\" "
00669       "xmlns:ns2=\"http://schemas.microsoft.com/exchange/services/2006/messages\">"
00670          "<SOAP-ENV:Body>"
00671             "<ns2:FindItem Traversal=\"Shallow\">"
00672                "<ns2:ItemShape>"
00673                   "<ns1:BaseShape>IdOnly</ns1:BaseShape>"
00674                "</ns2:ItemShape>"
00675                "<ns2:CalendarView StartDate=\"%s\" EndDate=\"%s\"/>" /* Timeframe */
00676                "<ns2:ParentFolderIds>"
00677                   "<ns1:DistinguishedFolderId Id=\"calendar\"/>"
00678                "</ns2:ParentFolderIds>"
00679             "</ns2:FindItem>"
00680          "</SOAP-ENV:Body>"
00681       "</SOAP-ENV:Envelope>",
00682       start, end  /* Timeframe */
00683    );
00684 
00685    AST_LIST_HEAD_INIT_NOLOCK(&ctx.ids);
00686 
00687    /* Dispatch request and parse response as XML */
00688    if (send_ews_request_and_parse(request, &ctx)) {
00689       ast_free(request);
00690       return NULL;
00691    }
00692 
00693    /* Cleanup */
00694    ast_free(request);
00695 
00696    return AST_LIST_FIRST(&ctx.ids);
00697 }
00698 
00699 static int parse_ewscal_id(struct ewscal_pvt *pvt, const char *id) {
00700    struct ast_str *request;
00701    struct xml_context ctx = {
00702       .pvt = pvt,
00703       .op = XML_OP_GET,
00704    };
00705 
00706    if (!(request = ast_str_create(512))) {
00707       return -1;
00708    }
00709 
00710    ast_str_set(&request, 0,
00711       "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
00712       "<soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\" "
00713       "xmlns:t=\"http://schemas.microsoft.com/exchange/services/2006/types\">"
00714       "<soap:Body>"
00715          "<GetItem xmlns=\"http://schemas.microsoft.com/exchange/services/2006/messages\">"
00716             "<ItemShape>"
00717                "<t:BaseShape>AllProperties</t:BaseShape>"
00718             "</ItemShape>"
00719             "<ItemIds>"
00720                "<t:ItemId Id=\"%s\"/>"
00721             "</ItemIds>"
00722          "</GetItem>"
00723       "</soap:Body>"
00724       "</soap:Envelope>", id
00725    );
00726 
00727    if (send_ews_request_and_parse(request, &ctx)) {
00728       ast_free(request);
00729       return -1;
00730    }
00731 
00732    ast_free(request);
00733 
00734    return 0;
00735 }
00736 
00737 static int update_ewscal(struct ewscal_pvt *pvt)
00738 {
00739    struct calendar_id *id_head;
00740    struct calendar_id *iter;
00741 
00742    if (!(id_head = get_ewscal_ids_for(pvt))) {
00743       return 0;
00744    }
00745 
00746    for (iter = id_head; iter; iter = AST_LIST_NEXT(iter, next)) {
00747       parse_ewscal_id(pvt, ast_str_buffer(iter->id));
00748       ast_free(iter->id);
00749       ast_free(iter);
00750    }
00751 
00752    return 0;
00753 }
00754 
00755 static void *ewscal_load_calendar(void *void_data)
00756 {
00757    struct ewscal_pvt *pvt;
00758    const struct ast_config *cfg;
00759    struct ast_variable *v;
00760    struct ast_calendar *cal = void_data;
00761    ast_mutex_t refreshlock;
00762 
00763    ast_debug(5, "EWS: ewscal_load_calendar()\n");
00764 
00765    if (!(cal && (cfg = ast_calendar_config_acquire()))) {
00766       ast_log(LOG_ERROR, "You must enable calendar support for res_ewscal to load\n");
00767       return NULL;
00768    }
00769 
00770    if (ao2_trylock(cal)) {
00771       if (cal->unloading) {
00772          ast_log(LOG_WARNING, "Unloading module, load_calendar cancelled.\n");
00773       } else {
00774          ast_log(LOG_WARNING, "Could not lock calendar, aborting!\n");
00775       }
00776       ast_calendar_config_release();
00777       return NULL;
00778    }
00779 
00780    if (!(pvt = ao2_alloc(sizeof(*pvt), ewscal_destructor))) {
00781       ast_log(LOG_ERROR, "Could not allocate ewscal_pvt structure for calendar: %s\n", cal->name);
00782       ast_calendar_config_release();
00783       return NULL;
00784    }
00785 
00786    pvt->owner = cal;
00787 
00788    if (!(pvt->events = ast_calendar_event_container_alloc())) {
00789       ast_log(LOG_ERROR, "Could not allocate space for fetching events for calendar: %s\n", cal->name);
00790       pvt = unref_ewscal(pvt);
00791       ao2_unlock(cal);
00792       ast_calendar_config_release();
00793       return NULL;
00794    }
00795 
00796    if (ast_string_field_init(pvt, 32)) {
00797       ast_log(LOG_ERROR, "Couldn't allocate string field space for calendar: %s\n", cal->name);
00798       pvt = unref_ewscal(pvt);
00799       ao2_unlock(cal);
00800       ast_calendar_config_release();
00801       return NULL;
00802    }
00803 
00804    for (v = ast_variable_browse(cfg, cal->name); v; v = v->next) {
00805       if (!strcasecmp(v->name, "url")) {
00806          ast_string_field_set(pvt, url, v->value);
00807       } else if (!strcasecmp(v->name, "user")) {
00808          ast_string_field_set(pvt, user, v->value);
00809       } else if (!strcasecmp(v->name, "secret")) {
00810          ast_string_field_set(pvt, secret, v->value);
00811       }
00812    }
00813 
00814    ast_calendar_config_release();
00815 
00816    if (ast_strlen_zero(pvt->url)) {
00817       ast_log(LOG_WARNING, "No URL was specified for Exchange Web Service calendar '%s' - skipping.\n", cal->name);
00818       pvt = unref_ewscal(pvt);
00819       ao2_unlock(cal);
00820       return NULL;
00821    }
00822 
00823    if (ne_uri_parse(pvt->url, &pvt->uri) || pvt->uri.host == NULL || pvt->uri.path == NULL) {
00824       ast_log(LOG_WARNING, "Could not parse url '%s' for Exchange Web Service calendar '%s' - skipping.\n", pvt->url, cal->name);
00825       pvt = unref_ewscal(pvt);
00826       ao2_unlock(cal);
00827       return NULL;
00828    }
00829 
00830    if (pvt->uri.scheme == NULL) {
00831       pvt->uri.scheme = "http";
00832    }
00833 
00834    if (pvt->uri.port == 0) {
00835       pvt->uri.port = ne_uri_defaultport(pvt->uri.scheme);
00836    }
00837 
00838    ast_debug(3, "ne_uri.scheme   = %s\n", pvt->uri.scheme);
00839    ast_debug(3, "ne_uri.host  = %s\n", pvt->uri.host);
00840    ast_debug(3, "ne_uri.port  = %u\n", pvt->uri.port);
00841    ast_debug(3, "ne_uri.path  = %s\n", pvt->uri.path);
00842    ast_debug(3, "user      = %s\n", pvt->user);
00843    ast_debug(3, "secret    = %s\n", pvt->secret);
00844 
00845    pvt->session = ne_session_create(pvt->uri.scheme, pvt->uri.host, pvt->uri.port);
00846    ne_redirect_register(pvt->session);
00847    ne_set_server_auth(pvt->session, auth_credentials, pvt);
00848    ne_set_useragent(pvt->session, "Asterisk");
00849 
00850    if (!strcasecmp(pvt->uri.scheme, "https")) {
00851       ne_ssl_trust_default_ca(pvt->session);
00852       ne_ssl_set_verify(pvt->session, ssl_verify, pvt);
00853    }
00854 
00855    cal->tech_pvt = pvt;
00856 
00857    ast_mutex_init(&refreshlock);
00858 
00859    /* Load it the first time */
00860    update_ewscal(pvt);
00861 
00862    ao2_unlock(cal);
00863 
00864    /* The only writing from another thread will be if unload is true */
00865    for (;;) {
00866       struct timeval tv = ast_tvnow();
00867       struct timespec ts = {0,};
00868 
00869       ts.tv_sec = tv.tv_sec + (60 * pvt->owner->refresh);
00870 
00871       ast_mutex_lock(&refreshlock);
00872       while (!pvt->owner->unloading) {
00873          if (ast_cond_timedwait(&pvt->owner->unload, &refreshlock, &ts) == ETIMEDOUT) {
00874             break;
00875          }
00876       }
00877       ast_mutex_unlock(&refreshlock);
00878 
00879       if (pvt->owner->unloading) {
00880          ast_debug(10, "Skipping refresh since we got a shutdown signal\n");
00881          return NULL;
00882       }
00883 
00884       ast_debug(10, "Refreshing after %d minute timeout\n", pvt->owner->refresh);
00885 
00886       update_ewscal(pvt);
00887    }
00888 
00889    return NULL;
00890 }
00891 
00892 static int load_module(void)
00893 {
00894    /* Actualy, 0.29.1 is required (because of NTLM authentication), but this
00895     * function does not support matching patch version. */
00896    if (ne_version_match(0, 29)) {
00897       ast_log(LOG_ERROR, "Exchange Web Service calendar module require neon >= 0.29.1, but %s is installed.\n", ne_version_string());
00898       return AST_MODULE_LOAD_DECLINE;
00899    }
00900 
00901    if (ast_calendar_register(&ewscal_tech) && (ne_sock_init() == 0)) {
00902       return AST_MODULE_LOAD_DECLINE;
00903    }
00904 
00905    return AST_MODULE_LOAD_SUCCESS;
00906 }
00907 
00908 static int unload_module(void)
00909 {
00910    ne_sock_exit();
00911    ast_calendar_unregister(&ewscal_tech);
00912 
00913    return 0;
00914 }
00915 
00916 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "Asterisk MS Exchange Web Service Calendar Integration",
00917    .load = load_module,
00918    .unload = unload_module,
00919    .load_pri = AST_MODPRI_DEVSTATE_PLUGIN,
00920 );

Generated on Mon Oct 8 12:39:04 2012 for Asterisk - The Open Source Telephony Project by  doxygen 1.4.7