static void sched_request(const char *organizer, struct caldav_sched_param *sparam,
			  icalcomponent *oldical, icalcomponent *newical,
			  const char *att_update)
{
    int r;
    icalproperty_method method;
    struct auth_state *authstate;
    icalcomponent *ical, *req, *comp;
    icalproperty *prop;
    icalcomponent_kind kind;
    struct hash_table att_table, comp_table;
    const char *sched_stat = NULL, *recurid;
    struct comp_data *old_data;

    /* Check what kind of action we are dealing with */
    if (!newical) {
	/* Remove */
	ical = oldical;
	method = ICAL_METHOD_CANCEL;
    }
    else {
	/* Create / Modify */
	ical = newical;
	method = ICAL_METHOD_REQUEST;
    }

    if (!att_update) {
	int rights = 0;
	mbentry_t *mbentry = NULL;
	/* Check ACL of auth'd user on userid's Scheduling Outbox */
	const char *outboxname = caldav_mboxname(sparam->userid, SCHED_OUTBOX);

	r = mboxlist_lookup(outboxname, &mbentry, NULL);
	if (r) {
	    syslog(LOG_INFO, "mboxlist_lookup(%s) failed: %s",
		   outboxname, error_message(r));
	}
	else {
	    rights = cyrus_acl_myrights(httpd_authstate, mbentry->acl);
	    mboxlist_entry_free(&mbentry);
	}

	if (!(rights & DACL_INVITE)) {
	    /* DAV:need-privileges */
	    sched_stat = SCHEDSTAT_NOPRIVS;
	    syslog(LOG_DEBUG, "No scheduling send ACL for user %s on Outbox %s",
		   httpd_userid, sparam->userid);

	    goto done;
	}
    }

    /* Create a shell for our iTIP request objects */
    req = icalcomponent_vanew(ICAL_VCALENDAR_COMPONENT,
			      icalproperty_new_version("2.0"),
			      icalproperty_new_prodid(ical_prodid),
			      icalproperty_new_method(method),
			      0);

    /* XXX  Make sure SEQUENCE is incremented */

    /* Copy over any CALSCALE property */
    prop = icalcomponent_get_first_property(ical, ICAL_CALSCALE_PROPERTY);
    if (prop) {
	icalcomponent_add_property(req,
				   icalproperty_new_clone(prop));
    }

    /* Copy over any VTIMEZONE components */
    for (comp = icalcomponent_get_first_component(ical,
						  ICAL_VTIMEZONE_COMPONENT);
	 comp;
	 comp = icalcomponent_get_next_component(ical,
						 ICAL_VTIMEZONE_COMPONENT)) {
	 icalcomponent_add_component(req,
				     icalcomponent_new_clone(comp));
    }

    comp = icalcomponent_get_first_real_component(ical);
    kind = icalcomponent_isa(comp);

    /* Add each component of old object to hash table for comparison */
    construct_hash_table(&comp_table, 10, 1);

    if (!att_update && oldical) {
	comp = icalcomponent_get_first_real_component(oldical);

	/* If the existing object isn't a scheduling object,
	   we don't need to compare components, treat them as new */
	if (icalcomponent_get_first_property(comp, ICAL_ORGANIZER_PROPERTY)) {
	    do {
		old_data = xzmalloc(sizeof(struct comp_data));
		old_data->comp = comp;
		old_data->sequence = icalcomponent_get_sequence(comp);

		prop =
		    icalcomponent_get_first_property(comp,
						     ICAL_RECURRENCEID_PROPERTY);
		if (prop) recurid = icalproperty_get_value_as_string(prop);
		else recurid = "";

		hash_insert(recurid, old_data, &comp_table);

	    } while ((comp = icalcomponent_get_next_component(oldical, kind)));
	}
    }

    /* Create hash table of attendees */
    construct_hash_table(&att_table, 10, 1);

    /* Process each component of new object */
    if (newical) {
	unsigned ncomp = 0;

	comp = icalcomponent_get_first_real_component(newical);
	do {
	    unsigned changed = 1, needs_action = 0;

	    prop = icalcomponent_get_first_property(comp,
						    ICAL_RECURRENCEID_PROPERTY);
	    if (prop) recurid = icalproperty_get_value_as_string(prop);
	    else recurid = "";

	    old_data = hash_del(recurid, &comp_table);

	    if (old_data) {
		/* Per RFC 6638, Section 3.2.8: We need to compare
		   DTSTART, DTEND, DURATION, DUE, RRULE, RDATE, EXDATE */
		if (propcmp(old_data->comp, comp, ICAL_DTSTART_PROPERTY))
		    needs_action = 1;
		else if (propcmp(old_data->comp, comp, ICAL_DTEND_PROPERTY))
		    needs_action = 1;
		else if (propcmp(old_data->comp, comp, ICAL_DURATION_PROPERTY))
		    needs_action = 1;
		else if (propcmp(old_data->comp, comp, ICAL_DUE_PROPERTY))
		    needs_action = 1;
		else if (propcmp(old_data->comp, comp, ICAL_RRULE_PROPERTY))
		    needs_action = 1;
		else if (propcmp(old_data->comp, comp, ICAL_RDATE_PROPERTY))
		    needs_action = 1;
		else if (propcmp(old_data->comp, comp, ICAL_EXDATE_PROPERTY))
		    needs_action = 1;
		else if (kind == ICAL_VPOLL_COMPONENT) {
		}

		if (needs_action &&
		    (old_data->sequence >= icalcomponent_get_sequence(comp))) {
		    /* Make sure SEQUENCE is set properly */
		    icalcomponent_set_sequence(comp,
					       old_data->sequence + 1);
		}

		free(old_data);
	    }

	    if (changed) {
		/* Process all attendees in created/modified components */
		process_attendees(comp, ncomp++, organizer, att_update,
				  &att_table, req, needs_action);
	    }

	} while ((comp = icalcomponent_get_next_component(newical, kind)));
    }

    if (oldical) {
	/* Cancel any components that have been left behind in the old obj */
	struct cancel_rock crock = { organizer, &att_table, req };

	hash_enumerate(&comp_table, sched_cancel, &crock);
    }
    free_hash_table(&comp_table, free);

    icalcomponent_free(req);

    /* Attempt to deliver requests to attendees */
    /* XXX  Do we need to do more checks here? */
    if (sparam->flags & SCHEDTYPE_REMOTE)
	authstate = auth_newstate("anonymous");
    else
	authstate = auth_newstate(sparam->userid);

    hash_enumerate(&att_table, sched_deliver, authstate);
    auth_freestate(authstate);

  done:
    if (newical) {
	unsigned ncomp = 0;
	icalproperty_kind recip_kind;
	const char *(*get_recipient)(const icalproperty *);

	/* Set SCHEDULE-STATUS for each attendee in organizer object */
	comp = icalcomponent_get_first_real_component(newical);
	kind = icalcomponent_isa(comp);

	if (kind == ICAL_VPOLL_COMPONENT) {
	    recip_kind = ICAL_VOTER_PROPERTY;
	    get_recipient = &icalproperty_get_voter;
	}
	else {
	    recip_kind = ICAL_ATTENDEE_PROPERTY;
	    get_recipient = &icalproperty_get_attendee;
	}

	do {
	    for (prop =
		     icalcomponent_get_first_property(comp, recip_kind);
		 prop;
		 prop =
		     icalcomponent_get_next_property(comp, recip_kind)) {
		const char *stat = NULL;
		const char *attendee = get_recipient(prop);

		/* Don't set status if attendee == organizer */
		if (!strcmp(attendee, organizer)) continue;

		if (sched_stat) stat = sched_stat;
		else {
		    struct sched_data *sched_data;

		    sched_data = hash_lookup(attendee, &att_table);
		    if (sched_data && (sched_data->comp_mask & (1 << ncomp)))
			stat = sched_data->status;
		}

		if (stat) {
		    /* Set SCHEDULE-STATUS */
		    icalparameter *param;

		    param = icalparameter_new_schedulestatus(stat);
		    icalproperty_set_parameter(prop, param);
		}
	    }

	    ncomp++;
	} while ((comp = icalcomponent_get_next_component(newical, kind)));
    }

    /* Cleanup */
    if (!sched_stat) free_hash_table(&att_table, free_sched_data);
}