static int action_freebusy(struct transaction_t *txn, int rights)
{
    int ret = 0;
    struct tm *tm;
    struct strlist *param;
    struct mime_type_t *mime = NULL;
    struct propfind_ctx fctx;
    struct calquery_filter calfilter;
    time_t start;
    struct icaldurationtype period = icaldurationtype_null_duration();
    icaltimezone *utc = icaltimezone_get_utc_timezone();
    icalcomponent *cal;

    /* Check rights */
    if (!(rights & DACL_READFB)) {
	/* DAV:need-privileges */
	txn->error.precond = DAV_NEED_PRIVS;
	txn->error.resource = txn->req_tgt.path;
	txn->error.rights = DACL_READFB;
	return HTTP_NO_PRIVS;
    }

    /* Check/find 'format' */
    param = hash_lookup("format", &txn->req_qparams);
    if (param) {
	if (param->next  /* once only */) return HTTP_BAD_REQUEST;

	for (mime = caldav_mime_types; mime->content_type; mime++) {
	    if (is_mediatype(param->s, mime->content_type)) break;
	}
    }
    else mime = caldav_mime_types;

    if (!mime || !mime->content_type) return HTTP_NOT_ACCEPTABLE;

    memset(&calfilter, 0, sizeof(struct calquery_filter));
    calfilter.comp =
	CAL_COMP_VEVENT | CAL_COMP_VFREEBUSY | CAL_COMP_VAVAILABILITY;
    calfilter.flags = BUSYTIME_QUERY | CHECK_CAL_TRANSP | CHECK_USER_AVAIL;

    /* Check for 'start' */
    param = hash_lookup("start", &txn->req_qparams);
    if (param) {
	if (param->next  /* once only */) return HTTP_BAD_REQUEST;

	calfilter.start = icaltime_from_rfc3339_string(param->s);
	if (icaltime_is_null_time(calfilter.start)) return HTTP_BAD_REQUEST;

	/* Default to end of given day */	
	start = icaltime_as_timet_with_zone(calfilter.start, utc);
	tm = localtime(&start);

	period.seconds = 60 - tm->tm_sec;
	period.minutes = 59 - tm->tm_min;
	period.hours   = 23 - tm->tm_hour;
    }
    else {
	/* Default to start of current day */
	start = time(0);
	tm = localtime(&start);
	tm->tm_hour = tm->tm_min = tm->tm_sec = 0;
	calfilter.start = icaltime_from_timet_with_zone(mktime(tm), 0, utc);

	/* Default to 42 day period */
	period.days = 42;
    }

    /* Check for 'period' */
    param = hash_lookup("period", &txn->req_qparams);
    if (param) {
	if (param->next  /* once only */ ||
	    hash_lookup("end", &txn->req_qparams)  /* can't use with 'end' */)
	    return HTTP_BAD_REQUEST;

	period = icaldurationtype_from_string(param->s);
	if (icaldurationtype_is_bad_duration(period)) return HTTP_BAD_REQUEST;
    }

    /* Check for 'end' */
    param = hash_lookup("end", &txn->req_qparams);
    if (param) {
	if (param->next  /* once only */) return HTTP_BAD_REQUEST;

	calfilter.end = icaltime_from_rfc3339_string(param->s);
	if (icaltime_is_null_time(calfilter.end)) return HTTP_BAD_REQUEST;
    }
    else {
	/* Set end based on period */
	calfilter.end = icaltime_add(calfilter.start, period);
    }


    memset(&fctx, 0, sizeof(struct propfind_ctx));
    fctx.req_tgt = &txn->req_tgt;
    fctx.depth = 2;
    fctx.userid = proxy_userid;
    fctx.userisadmin = httpd_userisadmin;
    fctx.authstate = httpd_authstate;
    fctx.reqd_privs = 0;  /* handled by CALDAV:schedule-deliver on Inbox */
    fctx.filter = apply_calfilter;
    fctx.filter_crit = &calfilter;
    fctx.err = &txn->error;
    fctx.ret = &ret;
    fctx.fetcheddata = 0;

    cal = busytime_query_local(txn, &fctx, txn->req_tgt.mboxname,
			       0, NULL, NULL, NULL);

    if (calfilter.freebusy.fb) free(calfilter.freebusy.fb);

    if (cal) {
	const char *proto, *host;
	icalcomponent *fb;
	icalproperty *url;
	char *cal_str;

	/* Construct URL */
	buf_reset(&txn->buf);
	http_proto_host(txn->req_hdrs, &proto, &host);
	buf_printf(&txn->buf, "%s://%s%s/user/%.*s/?%s",
		   proto, host, namespace_calendar.prefix,
		   (int) txn->req_tgt.userlen, txn->req_tgt.user,
		   URI_QUERY(txn->req_uri));

	/* Set URL property */
	fb = icalcomponent_get_first_component(cal, ICAL_VFREEBUSY_COMPONENT);
	url = icalproperty_new_url(buf_cstring(&txn->buf));
	icalcomponent_add_property(fb, url);

	/* Set filename of resource */
	buf_reset(&txn->buf);
	buf_printf(&txn->buf, "%.*s.%s",
		   (int) txn->req_tgt.userlen, txn->req_tgt.user,
		   mime->file_ext2);
	txn->resp_body.fname = buf_cstring(&txn->buf);

	txn->resp_body.type = mime->content_type;

	/* iCalendar data in response should not be transformed */
	txn->flags.cc |= CC_NOTRANSFORM;

	/* Output the iCalendar object */
	cal_str = mime->to_string(cal);
	icalcomponent_free(cal);

	write_body(HTTP_OK, txn, cal_str, strlen(cal_str));
	free(cal_str);
    }
    else ret = HTTP_NOT_FOUND;

    return ret;
}