Wed Apr 6 11:29:46 2011

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

Generated on Wed Apr 6 11:29:46 2011 for Asterisk - The Open Source Telephony Project by  doxygen 1.4.7