/** * Copyright (C) 2022 Graham Leggett * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ /* * redwax_libical - libical routines for generating calendars * */ #include #include #include "config.h" #include "redwax-tool.h" #include "redwax_util.h" #if HAVE_LIBICAL_ICAL_H #include /** * Keep track of calendar. */ typedef struct redwax_libical_vcalendar_t { const char *path; const char *file; apr_file_t *out; icalcomponent *cal; } redwax_libical_vcalendar_t; module libical_module; static apr_status_t redwax_libical_initialise(redwax_tool_t *r) { return OK; } static void redwax_libical_make_vcalendar(redwax_libical_vcalendar_t *vcal) { vcal->cal = icalcomponent_vanew(ICAL_VCALENDAR_COMPONENT, icalproperty_new_version("2.0"), icalproperty_new_prodid("-//Redwax Project//redwax-tool//EN"), NULL); // vcal->cal = icalcomponent_new_vcalendar(); } static apr_status_t redwax_libical_open_vcalendar(redwax_tool_t *r, const char *path, redwax_libical_vcalendar_t **pvcal) { apr_finfo_t finfo; apr_file_t *out; redwax_libical_vcalendar_t *vcal = apr_pcalloc(r->pool, sizeof(redwax_libical_vcalendar_t)); apr_status_t status; if (!strcmp(path, "-")) { out = r->out; redwax_libical_make_vcalendar(vcal); } else if (APR_SUCCESS == apr_stat(&finfo, path, APR_FINFO_TYPE, r->pool) && finfo.filetype == APR_DIR) { out = NULL; } else { status = apr_file_open(&out, path, APR_FOPEN_WRITE | APR_FOPEN_CREATE | APR_FOPEN_TRUNCATE, APR_FPROT_OS_DEFAULT, r->pool); if (APR_SUCCESS != status) { redwax_print_error(r, "Could not open '%s': %pm\n", path, &status); return status; } redwax_libical_make_vcalendar(vcal); } vcal->path = path; vcal->out = out; *pvcal = vcal; return APR_SUCCESS; } static apr_status_t redwax_libical_load_vcomponent(redwax_tool_t *r, redwax_libical_vcalendar_t *vcal, icalcomponent_kind kind, const char *uid, icalcomponent **comp) { if (vcal->out) { *comp = icalcomponent_new(kind); icalcomponent_add_component(vcal->cal, *comp); icalcomponent_set_uid(*comp, uid); } else { char *calname, *calpath, *buffer; apr_file_t *in; apr_off_t end = 0, start = 0; apr_size_t bytes_read; apr_status_t status; calname = apr_pstrcat(r->pool, uid, ".ics", NULL); if (APR_SUCCESS != (status = apr_filepath_merge(&calpath, vcal->path, calname, APR_FILEPATH_NATIVE, r->pool))) { redwax_print_error(r, "Could not merge '%s' and '%s': %pm\n", vcal->path, calname, &status); return status; } else { vcal->file = calpath; } status = apr_file_open(&in, vcal->file, APR_FOPEN_READ, APR_FPROT_OS_DEFAULT, r->pool); if (APR_ENOENT == status) { redwax_libical_make_vcalendar(vcal); *comp = icalcomponent_new(kind); icalcomponent_set_uid(*comp, uid); icalcomponent_add_component(vcal->cal, *comp); return APR_SUCCESS; } else if (APR_SUCCESS != status) { redwax_print_error(r, "Could not read '%s': %pm\n", vcal->file, &status); return status; } /* how long is the key? */ status = apr_file_seek(in, APR_END, &end); if (APR_SUCCESS != status) { redwax_print_error(r, "Could not seek '%s': %pm\n", vcal->file, &status); return status; } /* back to the beginning */ status = apr_file_seek(in, APR_SET, &start); if (APR_SUCCESS != status) { redwax_print_error(r, "Could not seek '%s': %pm\n", vcal->file, &status); return status; } buffer = apr_palloc(r->pool, end + 1); buffer[end] = 0; status = apr_file_read_full(in, buffer, end, &bytes_read); if (APR_SUCCESS != status) { redwax_print_error(r, "Could not get '%s': %pm\n", vcal->file, &status); return status; } vcal->cal = icalparser_parse_string(buffer); if(icalerrno != ICAL_NO_ERROR) { redwax_print_error(r, "Could not parse '%s': %s\n", vcal->file, icalerror_perror()); return APR_EINVAL; } apr_file_close(in); *comp = icalcomponent_get_first_component(vcal->cal, kind); while (*comp) { const char *u = icalcomponent_get_uid(*comp); if (u && !strcmp(uid, u)) { return APR_SUCCESS; } *comp = icalcomponent_get_next_component(*comp, kind); } *comp = icalcomponent_new(kind); icalcomponent_set_uid(*comp, uid); icalcomponent_add_component(vcal->cal, *comp); } return APR_SUCCESS; } static apr_status_t redwax_libical_save_vcomponent(redwax_tool_t *r, redwax_libical_vcalendar_t *vcal) { if (!vcal->out && vcal->cal) { apr_file_t *out; apr_status_t status; status = apr_file_open(&out, vcal->file, APR_FOPEN_WRITE | APR_FOPEN_CREATE | APR_FOPEN_TRUNCATE, APR_FPROT_OS_DEFAULT, r->pool); if (APR_SUCCESS != status) { redwax_print_error(r, "Could not open '%s': %pm\n", vcal->file, &status); return status; } status = apr_file_puts(icalcomponent_as_ical_string(vcal->cal), out); if (APR_SUCCESS != status) { redwax_print_error(r, "Could not write '%s': %pm\n", vcal->file, &status); return status; } icalcomponent_free(vcal->cal); vcal->cal = NULL; apr_file_close(out); vcal->file = NULL; } return APR_SUCCESS; } static apr_status_t redwax_libical_close_vcalendar(redwax_tool_t *r, redwax_libical_vcalendar_t *vcal) { if (vcal->out && vcal->cal) { apr_status_t status; status = apr_file_puts(icalcomponent_as_ical_string(vcal->cal), vcal->out); icalcomponent_free(vcal->cal); return status; } return APR_SUCCESS; } static apr_status_t redwax_libical_add_event(redwax_tool_t *r, redwax_libical_vcalendar_t *vcal, const redwax_certificate_t *cert) { icalcomponent *event, *alarm; apr_status_t status; if (cert->x509) { const char *uid; if (cert->x509->skid_der) { uid = redwax_pencode_base16_binary(r->pool, cert->x509->skid_der, cert->x509->skid_len, REDWAX_ENCODE_NONE, NULL); } else { /* skip */ return APR_SUCCESS; } status = redwax_libical_load_vcomponent(r, vcal, ICAL_VEVENT_COMPONENT, uid, &event); if (APR_SUCCESS != status) { return status; } if (cert->common.subject) { icalcomponent_set_summary(event, apr_psprintf(r->pool, "%s", cert->common.subject)); } if (cert->x509->before) { icalcomponent_set_dtstamp(event, icaltime_from_timet_with_zone(*cert->x509->before, 0, icaltimezone_get_utc_timezone())); } if (cert->x509->after) { struct icaltimetype after = icaltime_from_timet_with_zone(*cert->x509->after, 0, icaltimezone_get_utc_timezone()); after.is_date = 1; icalcomponent_set_dtstart(event, after); icaltime_adjust(&after, 1, 0, 0, 0); icalcomponent_set_dtend(event, after); } if (cert->x509->compact && cert->x509->pem) { icalcomponent_set_description(event, apr_pstrcat(r->pool, cert->x509->compact, cert->x509->pem, NULL)); } else if (cert->x509->compact) { icalcomponent_set_description(event, cert->x509->compact); } else if (cert->x509->pem) { icalcomponent_set_description(event, cert->x509->pem); } alarm = icalcomponent_get_first_component(event, ICAL_VALARM_COMPONENT); if (!alarm && r->calendar_alarm) { apr_uuid_t uuid; char ubuf[APR_UUID_FORMATTED_LENGTH + 1]; struct icaltriggertype trigger; trigger = icaltriggertype_from_string(r->calendar_alarm); alarm = icalcomponent_new_valarm(); apr_uuid_get(&uuid); apr_uuid_format(ubuf, &uuid); icalcomponent_set_uid(alarm, ubuf); icalcomponent_add_property(alarm, icalproperty_new_action(ICAL_ACTION_DISPLAY)); icalcomponent_set_description(alarm, "Certificate renewal"); icalcomponent_add_property(alarm, icalproperty_new_trigger(trigger)); icalcomponent_add_component(event, alarm); } if (!icalcomponent_get_first_property(event, ICAL_TRANSP_PROPERTY)) { icalcomponent_add_property(event, icalproperty_new_transp(ICAL_TRANSP_TRANSPARENT)); } } return redwax_libical_save_vcomponent(r, vcal); } static apr_status_t redwax_libical_add_todo(redwax_tool_t *r, redwax_libical_vcalendar_t *vcal, const redwax_certificate_t *cert) { icalcomponent *todo, *alarm; apr_status_t status; if (cert->x509) { const char *uid; if (cert->x509->skid_der) { uid = redwax_pencode_base16_binary(r->pool, cert->x509->skid_der, cert->x509->skid_len, REDWAX_ENCODE_NONE, NULL); } else { /* skip */ return APR_SUCCESS; } status = redwax_libical_load_vcomponent(r, vcal, ICAL_VTODO_COMPONENT, uid, &todo); if (APR_SUCCESS != status) { return status; } if (cert->common.subject) { icalcomponent_set_summary(todo, apr_psprintf(r->pool, "%s", cert->common.subject)); } if (cert->x509->before) { icalcomponent_set_dtstamp(todo, icaltime_from_timet_with_zone(*cert->x509->before, 0, icaltimezone_get_utc_timezone())); icalcomponent_set_dtstart(todo, icaltime_from_timet_with_zone(*cert->x509->before, 0, icaltimezone_get_utc_timezone())); } if (cert->x509->after) { icalcomponent_set_due(todo, icaltime_from_timet_with_zone(*cert->x509->after, 0, icaltimezone_get_utc_timezone())); } if (cert->x509->compact && cert->x509->pem) { icalcomponent_set_description(todo, apr_pstrcat(r->pool, cert->x509->compact, cert->x509->pem, NULL)); } else if (cert->x509->compact) { icalcomponent_set_description(todo, cert->x509->compact); } else if (cert->x509->pem) { icalcomponent_set_description(todo, cert->x509->pem); } alarm = icalcomponent_get_first_component(todo, ICAL_VALARM_COMPONENT); if (!alarm && r->calendar_alarm) { apr_uuid_t uuid; char ubuf[APR_UUID_FORMATTED_LENGTH + 1]; struct icaltriggertype trigger; trigger = icaltriggertype_from_string(r->calendar_alarm); alarm = icalcomponent_new_valarm(); apr_uuid_get(&uuid); apr_uuid_format(ubuf, &uuid); icalcomponent_set_uid(alarm, ubuf); icalcomponent_add_property(alarm, icalproperty_new_action(ICAL_ACTION_DISPLAY)); icalcomponent_set_description(alarm, "Certificate renewal"); icalcomponent_add_property(alarm, icalproperty_new_trigger(trigger)); icalcomponent_add_component(todo, alarm); } } return redwax_libical_save_vcomponent(r, vcal); } static apr_status_t redwax_libical_process_calendar_out(redwax_tool_t *r, const char *file) { redwax_libical_vcalendar_t *vcal; int i; apr_status_t status; if (r->cert_out) { for (i = 0; i < r->certs_out->nelts; i++) { const redwax_certificate_t *cert = &APR_ARRAY_IDX(r->certs_out, i, const redwax_certificate_t); redwax_print_error(r, "calendar-out: certificate: %s\n", cert->common.subject); } } if (r->chain_out) { for (i = 0; i < r->intermediates_out->nelts; i++) { const redwax_certificate_t *cert = &APR_ARRAY_IDX(r->intermediates_out, i, const redwax_certificate_t); redwax_print_error(r, "calendar-out: intermediate: %s\n", cert->common.subject); } } if (r->trust_out) { for (i = 0; i < r->trusted_out->nelts; i++) { const redwax_certificate_t *cert = &APR_ARRAY_IDX(r->trusted_out, i, const redwax_certificate_t); redwax_print_error(r, "calendar-out: trusted: %s\n", cert->common.subject); } } status = redwax_libical_open_vcalendar(r, file, &vcal); if (APR_SUCCESS != status) { return status; } if (r->cert_out) { for (i = 0; i < r->certs_out->nelts; i++) { const redwax_certificate_t *cert = &APR_ARRAY_IDX(r->certs_out, i, const redwax_certificate_t); status = redwax_libical_add_event(r, vcal, cert); if (APR_SUCCESS != status) { return status; } } } if (r->chain_out) { for (i = 0; i < r->intermediates_out->nelts; i++) { const redwax_certificate_t *cert = &APR_ARRAY_IDX(r->intermediates_out, i, const redwax_certificate_t); status = redwax_libical_add_event(r, vcal, cert); if (APR_SUCCESS != status) { return status; } } } if (r->trust_out) { for (i = 0; i < r->trusted_out->nelts; i++) { const redwax_certificate_t *cert = &APR_ARRAY_IDX(r->trusted_out, i, const redwax_certificate_t); status = redwax_libical_add_event(r, vcal, cert); if (APR_SUCCESS != status) { return status; } } } return redwax_libical_close_vcalendar(r, vcal); } static apr_status_t redwax_libical_process_reminder_out(redwax_tool_t *r, const char *file) { redwax_libical_vcalendar_t *vcal; int i; apr_status_t status; if (r->cert_out) { for (i = 0; i < r->certs_out->nelts; i++) { const redwax_certificate_t *cert = &APR_ARRAY_IDX(r->certs_out, i, const redwax_certificate_t); redwax_print_error(r, "reminder-out: certificate: %s\n", cert->common.subject); } } if (r->chain_out) { for (i = 0; i < r->intermediates_out->nelts; i++) { const redwax_certificate_t *cert = &APR_ARRAY_IDX(r->intermediates_out, i, const redwax_certificate_t); redwax_print_error(r, "reminder-out: intermediate: %s\n", cert->common.subject); } } if (r->trust_out) { for (i = 0; i < r->trusted_out->nelts; i++) { const redwax_certificate_t *cert = &APR_ARRAY_IDX(r->trusted_out, i, const redwax_certificate_t); redwax_print_error(r, "reminder-out: trusted: %s\n", cert->common.subject); } } status = redwax_libical_open_vcalendar(r, file, &vcal); if (APR_SUCCESS != status) { return status; } if (r->cert_out) { for (i = 0; i < r->certs_out->nelts; i++) { const redwax_certificate_t *cert = &APR_ARRAY_IDX(r->certs_out, i, const redwax_certificate_t); status = redwax_libical_add_todo(r, vcal, cert); if (APR_SUCCESS != status) { return status; } } } if (r->chain_out) { for (i = 0; i < r->intermediates_out->nelts; i++) { const redwax_certificate_t *cert = &APR_ARRAY_IDX(r->intermediates_out, i, const redwax_certificate_t); status = redwax_libical_add_todo(r, vcal, cert); if (APR_SUCCESS != status) { return status; } } } if (r->trust_out) { for (i = 0; i < r->trusted_out->nelts; i++) { const redwax_certificate_t *cert = &APR_ARRAY_IDX(r->trusted_out, i, const redwax_certificate_t); status = redwax_libical_add_todo(r, vcal, cert); if (APR_SUCCESS != status) { return status; } } } return redwax_libical_close_vcalendar(r, vcal); } static apr_status_t redwax_libical_set_calendar_alarm(redwax_tool_t *r, const char *alarm) { struct icaltriggertype trigger; trigger = icaltriggertype_from_string(alarm); if (icaltriggertype_is_null_trigger(trigger) || icaltriggertype_is_bad_trigger(trigger)) { redwax_print_error(r, "calendar-alarm: trigger is bad: %s\n", alarm); return APR_EINVAL; } else { r->calendar_alarm = alarm; } return APR_SUCCESS; } void redwax_add_default_libical_hooks() { rt_hook_initialise(redwax_libical_initialise, NULL, NULL, APR_HOOK_MIDDLE); rt_hook_process_calendar_out(redwax_libical_process_calendar_out, NULL, NULL, APR_HOOK_MIDDLE); rt_hook_process_reminder_out(redwax_libical_process_reminder_out, NULL, NULL, APR_HOOK_MIDDLE); rt_hook_set_calendar_alarm(redwax_libical_set_calendar_alarm, NULL, NULL, APR_HOOK_MIDDLE); } #else void redwax_add_default_libical_hooks() { } #endif REDWAX_DECLARE_MODULE(libical) = { STANDARD_MODULE_STUFF, redwax_add_default_libical_hooks /* register hooks */ };