Wed Aug 7 17:15:44 2019

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

Generated on 7 Aug 2019 for Asterisk - The Open Source Telephony Project by  doxygen 1.6.1