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