00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027 #include "asterisk.h"
00028
00029 ASTERISK_FILE_VERSION(__FILE__, "$Revision: 300214 $")
00030
00031 #include <libical/ical.h>
00032 #include <ne_session.h>
00033 #include <ne_uri.h>
00034 #include <ne_request.h>
00035 #include <ne_auth.h>
00036 #include <ne_redirect.h>
00037
00038 #include "asterisk/module.h"
00039 #include "asterisk/calendar.h"
00040 #include "asterisk/lock.h"
00041 #include "asterisk/config.h"
00042 #include "asterisk/astobj2.h"
00043
00044 static void *ical_load_calendar(void *data);
00045 static void *unref_icalendar(void *obj);
00046
00047 static struct ast_calendar_tech ical_tech = {
00048 .type = "ical",
00049 .module = AST_MODULE,
00050 .description = "iCalendar .ics calendars",
00051 .load_calendar = ical_load_calendar,
00052 .unref_calendar = unref_icalendar,
00053 };
00054
00055 struct icalendar_pvt {
00056 AST_DECLARE_STRING_FIELDS(
00057 AST_STRING_FIELD(url);
00058 AST_STRING_FIELD(user);
00059 AST_STRING_FIELD(secret);
00060 );
00061 struct ast_calendar *owner;
00062 ne_uri uri;
00063 ne_session *session;
00064 icalcomponent *data;
00065 struct ao2_container *events;
00066 };
00067
00068 static void icalendar_destructor(void *obj)
00069 {
00070 struct icalendar_pvt *pvt = obj;
00071
00072 ast_debug(1, "Destroying pvt for iCalendar %s\n", pvt->owner->name);
00073 if (pvt->session) {
00074 ne_session_destroy(pvt->session);
00075 }
00076 if (pvt->data) {
00077 icalcomponent_free(pvt->data);
00078 }
00079 ast_string_field_free_memory(pvt);
00080
00081 ao2_callback(pvt->events, OBJ_UNLINK | OBJ_NODATA | OBJ_MULTIPLE, NULL, NULL);
00082
00083 ao2_ref(pvt->events, -1);
00084 }
00085
00086 static void *unref_icalendar(void *obj)
00087 {
00088 struct icalendar_pvt *pvt = obj;
00089
00090 ao2_ref(pvt, -1);
00091 return NULL;
00092 }
00093
00094 static int fetch_response_reader(void *data, const char *block, size_t len)
00095 {
00096 struct ast_str **response = data;
00097 unsigned char *tmp;
00098
00099 if (!(tmp = ast_malloc(len + 1))) {
00100 return -1;
00101 }
00102 memcpy(tmp, block, len);
00103 tmp[len] = '\0';
00104 ast_str_append(response, 0, "%s", tmp);
00105 ast_free(tmp);
00106
00107 return 0;
00108 }
00109
00110 static int auth_credentials(void *userdata, const char *realm, int attempts, char *username, char *secret)
00111 {
00112 struct icalendar_pvt *pvt = userdata;
00113
00114 if (attempts > 1) {
00115 ast_log(LOG_WARNING, "Invalid username or password for iCalendar '%s'\n", pvt->owner->name);
00116 return -1;
00117 }
00118
00119 ne_strnzcpy(username, pvt->user, NE_ABUFSIZ);
00120 ne_strnzcpy(secret, pvt->secret, NE_ABUFSIZ);
00121
00122 return 0;
00123 }
00124
00125 static icalcomponent *fetch_icalendar(struct icalendar_pvt *pvt)
00126 {
00127 int ret;
00128 struct ast_str *response;
00129 ne_request *req;
00130 icalcomponent *comp = NULL;
00131
00132 if (!pvt) {
00133 ast_log(LOG_ERROR, "There is no private!\n");
00134 }
00135
00136 if (!(response = ast_str_create(512))) {
00137 ast_log(LOG_ERROR, "Could not allocate memory for response.\n");
00138 return NULL;
00139 }
00140
00141 req = ne_request_create(pvt->session, "GET", pvt->uri.path);
00142 ne_add_response_body_reader(req, ne_accept_2xx, fetch_response_reader, &response);
00143
00144 ret = ne_request_dispatch(req);
00145 ne_request_destroy(req);
00146 if (ret != NE_OK || !ast_str_strlen(response)) {
00147 ast_log(LOG_WARNING, "Unable to retrieve iCalendar '%s' from '%s': %s\n", pvt->owner->name, pvt->url, ne_get_error(pvt->session));
00148 ast_free(response);
00149 return NULL;
00150 }
00151
00152 if (!ast_strlen_zero(ast_str_buffer(response))) {
00153 comp = icalparser_parse_string(ast_str_buffer(response));
00154 }
00155 ast_free(response);
00156
00157 return comp;
00158 }
00159
00160 static time_t icalfloat_to_timet(icaltimetype time)
00161 {
00162 struct ast_tm tm = {0,};
00163 struct timeval tv;
00164
00165 tm.tm_mday = time.day;
00166 tm.tm_mon = time.month - 1;
00167 tm.tm_year = time.year - 1900;
00168 tm.tm_hour = time.hour;
00169 tm.tm_min = time.minute;
00170 tm.tm_sec = time.second;
00171 tm.tm_isdst = -1;
00172 tv = ast_mktime(&tm, NULL);
00173
00174 return tv.tv_sec;
00175 }
00176
00177
00178
00179
00180
00181
00182
00183
00184
00185
00186 static void icalendar_add_event(icalcomponent *comp, struct icaltime_span *span, void *data)
00187 {
00188 struct icalendar_pvt *pvt = data;
00189 struct ast_calendar_event *event;
00190 icaltimezone *utc = icaltimezone_get_utc_timezone();
00191 icaltimetype start, end, tmp;
00192 icalcomponent *valarm;
00193 icalproperty *prop;
00194 struct icaltriggertype trigger;
00195
00196 if (!(pvt && pvt->owner)) {
00197 ast_log(LOG_ERROR, "Require a private structure with an ownenr\n");
00198 return;
00199 }
00200
00201 if (!(event = ast_calendar_event_alloc(pvt->owner))) {
00202 ast_log(LOG_ERROR, "Could not allocate an event!\n");
00203 return;
00204 }
00205
00206 start = icalcomponent_get_dtstart(comp);
00207 end = icalcomponent_get_dtend(comp);
00208
00209 event->start = icaltime_get_tzid(start) ? span->start : icalfloat_to_timet(start);
00210 event->end = icaltime_get_tzid(end) ? span->end : icalfloat_to_timet(end);
00211 event->busy_state = span->is_busy ? AST_CALENDAR_BS_BUSY : AST_CALENDAR_BS_FREE;
00212
00213 if ((prop = icalcomponent_get_first_property(comp, ICAL_SUMMARY_PROPERTY))) {
00214 ast_string_field_set(event, summary, icalproperty_get_value_as_string(prop));
00215 }
00216
00217 if ((prop = icalcomponent_get_first_property(comp, ICAL_DESCRIPTION_PROPERTY))) {
00218 ast_string_field_set(event, description, icalproperty_get_value_as_string(prop));
00219 }
00220
00221 if ((prop = icalcomponent_get_first_property(comp, ICAL_ORGANIZER_PROPERTY))) {
00222 ast_string_field_set(event, organizer, icalproperty_get_value_as_string(prop));
00223 }
00224
00225 if ((prop = icalcomponent_get_first_property(comp, ICAL_LOCATION_PROPERTY))) {
00226 ast_string_field_set(event, location, icalproperty_get_value_as_string(prop));
00227 }
00228
00229 if ((prop = icalcomponent_get_first_property(comp, ICAL_CATEGORIES_PROPERTY))) {
00230 ast_string_field_set(event, categories, icalproperty_get_value_as_string(prop));
00231 }
00232
00233 if ((prop = icalcomponent_get_first_property(comp, ICAL_PRIORITY_PROPERTY))) {
00234 event->priority = icalvalue_get_integer(icalproperty_get_value(prop));
00235 }
00236
00237 if ((prop = icalcomponent_get_first_property(comp, ICAL_UID_PROPERTY))) {
00238 ast_string_field_set(event, uid, icalproperty_get_value_as_string(prop));
00239 } else {
00240 ast_log(LOG_WARNING, "No UID found, but one is required. Generating, but updates may not be acurate\n");
00241 if (!ast_strlen_zero(event->summary)) {
00242 ast_string_field_set(event, uid, event->summary);
00243 } else {
00244 char tmp[100];
00245 snprintf(tmp, sizeof(tmp), "%lu", event->start);
00246 ast_string_field_set(event, uid, tmp);
00247 }
00248 }
00249
00250
00251 for (prop = icalcomponent_get_first_property(comp, ICAL_ATTENDEE_PROPERTY);
00252 prop; prop = icalcomponent_get_next_property(comp, ICAL_ATTENDEE_PROPERTY)) {
00253 struct ast_calendar_attendee *attendee;
00254 const char *data;
00255
00256 if (!(attendee = ast_calloc(1, sizeof(*attendee)))) {
00257 event = ast_calendar_unref_event(event);
00258 return;
00259 }
00260 data = icalproperty_get_attendee(prop);
00261 if (!ast_strlen_zero(data)) {
00262 attendee->data = ast_strdup(data);;
00263 AST_LIST_INSERT_TAIL(&event->attendees, attendee, next);
00264 }
00265 }
00266
00267
00268
00269
00270
00271 if (!(valarm = icalcomponent_get_first_component(comp, ICAL_VALARM_COMPONENT))) {
00272 ao2_link(pvt->events, event);
00273 event = ast_calendar_unref_event(event);
00274 return;
00275 }
00276
00277 if (!(prop = icalcomponent_get_first_property(valarm, ICAL_TRIGGER_PROPERTY))) {
00278 ast_log(LOG_WARNING, "VALARM has no TRIGGER, skipping!\n");
00279 ao2_link(pvt->events, event);
00280 event = ast_calendar_unref_event(event);
00281 return;
00282 }
00283
00284 trigger = icalproperty_get_trigger(prop);
00285
00286 if (icaltriggertype_is_null_trigger(trigger)) {
00287 ast_log(LOG_WARNING, "Bad TRIGGER for VALARM, skipping!\n");
00288 ao2_link(pvt->events, event);
00289 event = ast_calendar_unref_event(event);
00290 return;
00291 }
00292
00293 if (!icaltime_is_null_time(trigger.time)) {
00294 tmp = icaltime_convert_to_zone(trigger.time, utc);
00295 event->alarm = icaltime_as_timet_with_zone(tmp, utc);
00296 } else {
00297
00298
00299 tmp = icaltime_add(start, trigger.duration);
00300 event->alarm = icaltime_as_timet_with_zone(tmp, utc);
00301 }
00302
00303 ao2_link(pvt->events, event);
00304 event = ast_calendar_unref_event(event);
00305
00306 return;
00307 }
00308
00309 static void icalendar_update_events(struct icalendar_pvt *pvt)
00310 {
00311 struct icaltimetype start_time, end_time;
00312 icalcomponent *iter;
00313
00314 if (!pvt) {
00315 ast_log(LOG_ERROR, "iCalendar is NULL\n");
00316 return;
00317 }
00318
00319 if (!pvt->owner) {
00320 ast_log(LOG_ERROR, "iCalendar is an orphan!\n");
00321 return;
00322 }
00323
00324 if (!pvt->data) {
00325 ast_log(LOG_ERROR, "The iCalendar has not been parsed!\n");
00326 return;
00327 }
00328
00329 start_time = icaltime_current_time_with_zone(icaltimezone_get_utc_timezone());
00330 end_time = icaltime_current_time_with_zone(icaltimezone_get_utc_timezone());
00331 end_time.second += pvt->owner->timeframe * 60;
00332 icaltime_normalize(end_time);
00333
00334 for (iter = icalcomponent_get_first_component(pvt->data, ICAL_VEVENT_COMPONENT);
00335 iter;
00336 iter = icalcomponent_get_next_component(pvt->data, ICAL_VEVENT_COMPONENT))
00337 {
00338 icalcomponent_foreach_recurrence(iter, start_time, end_time, icalendar_add_event, pvt);
00339 }
00340
00341 ast_calendar_merge_events(pvt->owner, pvt->events);
00342 }
00343
00344 static void *ical_load_calendar(void *void_data)
00345 {
00346 struct icalendar_pvt *pvt;
00347 const struct ast_config *cfg;
00348 struct ast_variable *v;
00349 struct ast_calendar *cal = void_data;
00350 ast_mutex_t refreshlock;
00351
00352 if (!(cal && (cfg = ast_calendar_config_acquire()))) {
00353 ast_log(LOG_ERROR, "You must enable calendar support for res_icalendar to load\n");
00354 return NULL;
00355 }
00356 if (ao2_trylock(cal)) {
00357 if (cal->unloading) {
00358 ast_log(LOG_WARNING, "Unloading module, load_calendar cancelled.\n");
00359 } else {
00360 ast_log(LOG_WARNING, "Could not lock calendar, aborting!\n");
00361 }
00362 ast_calendar_config_release();
00363 return NULL;
00364 }
00365
00366 if (!(pvt = ao2_alloc(sizeof(*pvt), icalendar_destructor))) {
00367 ast_log(LOG_ERROR, "Could not allocate icalendar_pvt structure for calendar: %s\n", cal->name);
00368 ast_calendar_config_release();
00369 return NULL;
00370 }
00371
00372 pvt->owner = cal;
00373
00374 if (!(pvt->events = ast_calendar_event_container_alloc())) {
00375 ast_log(LOG_ERROR, "Could not allocate space for fetching events for calendar: %s\n", cal->name);
00376 pvt = unref_icalendar(pvt);
00377 ao2_unlock(cal);
00378 ast_calendar_config_release();
00379 return NULL;
00380 }
00381
00382 if (ast_string_field_init(pvt, 32)) {
00383 ast_log(LOG_ERROR, "Couldn't allocate string field space for calendar: %s\n", cal->name);
00384 pvt = unref_icalendar(pvt);
00385 ao2_unlock(cal);
00386 ast_calendar_config_release();
00387 return NULL;
00388 }
00389
00390 for (v = ast_variable_browse(cfg, cal->name); v; v = v->next) {
00391 if (!strcasecmp(v->name, "url")) {
00392 ast_string_field_set(pvt, url, v->value);
00393 } else if (!strcasecmp(v->name, "user")) {
00394 ast_string_field_set(pvt, user, v->value);
00395 } else if (!strcasecmp(v->name, "secret")) {
00396 ast_string_field_set(pvt, secret, v->value);
00397 }
00398 }
00399
00400 ast_calendar_config_release();
00401
00402 if (ast_strlen_zero(pvt->url)) {
00403 ast_log(LOG_WARNING, "No URL was specified for iCalendar '%s' - skipping.\n", cal->name);
00404 pvt = unref_icalendar(pvt);
00405 ao2_unlock(cal);
00406 return NULL;
00407 }
00408
00409 if (ne_uri_parse(pvt->url, &pvt->uri) || pvt->uri.host == NULL || pvt->uri.path == NULL) {
00410 ast_log(LOG_WARNING, "Could not parse url '%s' for iCalendar '%s' - skipping.\n", pvt->url, cal->name);
00411 pvt = unref_icalendar(pvt);
00412 ao2_unlock(cal);
00413 return NULL;
00414 }
00415
00416 if (pvt->uri.scheme == NULL) {
00417 pvt->uri.scheme = "http";
00418 }
00419
00420 if (pvt->uri.port == 0) {
00421 pvt->uri.port = ne_uri_defaultport(pvt->uri.scheme);
00422 }
00423
00424 pvt->session = ne_session_create(pvt->uri.scheme, pvt->uri.host, pvt->uri.port);
00425 ne_redirect_register(pvt->session);
00426 ne_set_server_auth(pvt->session, auth_credentials, pvt);
00427 if (!strcasecmp(pvt->uri.scheme, "https")) {
00428 ne_ssl_trust_default_ca(pvt->session);
00429 }
00430
00431 cal->tech_pvt = pvt;
00432
00433 ast_mutex_init(&refreshlock);
00434
00435
00436 if (!(pvt->data = fetch_icalendar(pvt))) {
00437 ast_log(LOG_WARNING, "Unable to parse iCalendar '%s'\n", cal->name);
00438 }
00439
00440 icalendar_update_events(pvt);
00441
00442 ao2_unlock(cal);
00443
00444
00445 for(;;) {
00446 struct timeval tv = ast_tvnow();
00447 struct timespec ts = {0,};
00448
00449 ts.tv_sec = tv.tv_sec + (60 * pvt->owner->refresh);
00450
00451 ast_mutex_lock(&refreshlock);
00452 while (!pvt->owner->unloading) {
00453 if (ast_cond_timedwait(&pvt->owner->unload, &refreshlock, &ts) == ETIMEDOUT) {
00454 break;
00455 }
00456 }
00457 ast_mutex_unlock(&refreshlock);
00458
00459 if (pvt->owner->unloading) {
00460 ast_debug(10, "Skipping refresh since we got a shutdown signal\n");
00461 return NULL;
00462 }
00463
00464 ast_debug(10, "Refreshing after %d minute timeout\n", pvt->owner->refresh);
00465
00466 if (!(pvt->data = fetch_icalendar(pvt))) {
00467 ast_log(LOG_WARNING, "Unable to parse iCalendar '%s'\n", pvt->owner->name);
00468 continue;
00469 }
00470
00471 icalendar_update_events(pvt);
00472 }
00473
00474 return NULL;
00475 }
00476
00477 static int load_module(void)
00478 {
00479 ne_sock_init();
00480 if (ast_calendar_register(&ical_tech)) {
00481 ne_sock_exit();
00482 return AST_MODULE_LOAD_DECLINE;
00483 }
00484
00485 return AST_MODULE_LOAD_SUCCESS;
00486 }
00487
00488 static int unload_module(void)
00489 {
00490 ast_calendar_unregister(&ical_tech);
00491 ne_sock_exit();
00492 return 0;
00493 }
00494
00495 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "Asterisk iCalendar .ics file integration",
00496 .load = load_module,
00497 .unload = unload_module,
00498 .load_pri = AST_MODPRI_DEVSTATE_PLUGIN,
00499 );