Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F117880856
http_caldav_sched.c
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Authored By
Unknown
Size
81 KB
Referenced Files
None
Subscribers
None
http_caldav_sched.c
View Options
/* http_caldav_sched.c -- Routines for dealing with CALDAV scheduling in httpd
*
* Copyright (c) 1994-2015 Carnegie Mellon University. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The name "Carnegie Mellon University" must not be used to
* endorse or promote products derived from this software without
* prior written permission. For permission or any legal
* details, please contact
* Carnegie Mellon University
* Center for Technology Transfer and Enterprise Creation
* 4615 Forbes Avenue
* Suite 302
* Pittsburgh, PA 15213
* (412) 268-7393, fax: (412) 268-7395
* innovation@andrew.cmu.edu
*
* 4. Redistributions of any form whatsoever must retain the following
* acknowledgment:
* "This product includes software developed by Computing Services
* at Carnegie Mellon University (http://www.cmu.edu/computing/)."
*
* CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
* THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
* FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
* AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
*/
#include
<config.h>
#include
<syslog.h>
#include
<jansson.h>
#include
<libical/ical.h>
#include
<sys/types.h>
#include
<sys/wait.h>
#include
"httpd.h"
#include
"http_caldav_sched.h"
#include
"http_dav.h"
#include
"http_proxy.h"
#include
"notify.h"
#include
"md5.h"
#include
"smtpclient.h"
#include
"strhash.h"
#include
"times.h"
#include
"xmalloc.h"
#include
"xstrlcat.h"
#include
"xstrlcpy.h"
/* generated headers are not necessarily in current directory */
#include
"imap/http_err.h"
#include
"imap/imap_err.h"
int
caladdress_lookup
(
const
char
*
addr
,
struct
sched_param
*
param
,
const
char
*
myuserid
)
{
const
char
*
userid
=
addr
,
*
p
;
int
islocal
=
1
,
found
=
1
;
size_t
len
;
char
*
testuser
=
NULL
;
if
(
myuserid
)
{
if
(
strchr
(
myuserid
,
'@'
)
||
!
httpd_extradomain
)
testuser
=
xstrdup
(
myuserid
);
else
testuser
=
strconcat
(
myuserid
,
"@"
,
httpd_extradomain
,
(
char
*
)
NULL
);
}
memset
(
param
,
0
,
sizeof
(
struct
sched_param
));
if
(
!
addr
)
return
HTTP_NOT_FOUND
;
if
(
!
strncasecmp
(
userid
,
"mailto:"
,
7
))
userid
+=
7
;
if
(
testuser
&&
!
strcasecmp
(
userid
,
testuser
))
{
param
->
isyou
=
1
;
param
->
userid
=
testuser
;
return
0
;
// myself is always local
}
free
(
testuser
);
len
=
strlen
(
userid
);
/* XXX Do LDAP/DB/socket lookup to see if user is local */
/* XXX Hack until real lookup stuff is written */
if
((
p
=
strchr
(
userid
,
'@'
))
&&
*++
p
)
{
struct
strlist
*
domains
=
cua_domains
;
for
(;
domains
&&
strcmp
(
p
,
domains
->
s
);
domains
=
domains
->
next
);
if
(
!
domains
)
islocal
=
0
;
else
if
(
!
config_virtdomains
)
len
=
p
-
userid
-
1
;
}
if
(
islocal
)
{
/* User is in a local domain */
int
r
;
const
char
*
mboxname
=
NULL
;
mbentry_t
*
mbentry
=
NULL
;
mbname_t
*
mbname
=
NULL
;
if
(
!
found
)
return
HTTP_NOT_FOUND
;
else
param
->
userid
=
xstrndup
(
userid
,
len
);
/* freed by sched_param_free */
/* Lookup user's cal-home-set to see if its on this server */
mbname
=
mbname_from_userid
(
param
->
userid
);
mbname_push_boxes
(
mbname
,
config_getstring
(
IMAPOPT_CALENDARPREFIX
));
mboxname
=
mbname_intname
(
mbname
);
r
=
http_mlookup
(
mboxname
,
&
mbentry
,
NULL
);
mbname_free
(
&
mbname
);
if
(
!
r
)
{
param
->
server
=
xstrdupnull
(
mbentry
->
server
);
/* freed by sched_param_free */
mboxlist_entry_free
(
&
mbentry
);
if
(
param
->
server
)
param
->
flags
|=
SCHEDTYPE_ISCHEDULE
;
return
0
;
}
/* Fall through and try remote */
}
/* User is outside of our domain(s) -
Do remote scheduling (default = iMIP) */
if
(
param
->
userid
)
free
(
param
->
userid
);
param
->
userid
=
xstrdupnull
(
userid
);
/* freed by sched_param_free */
param
->
flags
|=
SCHEDTYPE_REMOTE
;
#ifdef WITH_DKIM
/* Do iSchedule DNS SRV lookup */
/* XXX If success, set server, port,
and flags |= SCHEDTYPE_ISCHEDULE [ | SCHEDTYPE_SSL ] */
#ifdef IOPTEST
/* CalConnect ioptest */
if
(
!
strcmp
(
p
,
"example.com"
))
{
param
->
server
=
xstrdup
(
"ischedule.example.com"
);
param
->
port
=
8008
;
param
->
flags
|=
SCHEDTYPE_ISCHEDULE
;
}
else
if
(
!
strcmp
(
p
,
"mysite.edu"
))
{
param
->
server
=
xstrdup
(
"ischedule.mysite.edu"
);
param
->
port
=
8080
;
param
->
flags
|=
SCHEDTYPE_ISCHEDULE
;
}
else
if
(
!
strcmp
(
p
,
"bedework.org"
))
{
param
->
server
=
xstrdrup
(
"www.bedework.org"
);
param
->
port
=
80
;
param
->
flags
|=
SCHEDTYPE_ISCHEDULE
;
}
#endif
/* IOPTEST */
#endif
/* WITH_DKIM */
return
0
;
}
/* Send an iMIP request for attendees in 'ical' */
static
int
imip_send
(
icalcomponent
*
ical
,
const
char
*
recipient
,
unsigned
is_update
)
{
const
char
*
notifier
=
config_getstring
(
IMAPOPT_IMIPNOTIFIER
);
/* nothing to send */
if
(
!
notifier
)
return
-1
;
const
char
*
ical_str
=
icalcomponent_as_ical_string
(
ical
);
json_t
*
val
=
json_pack
(
"{s:s s:s s:b}"
,
"recipient"
,
recipient
,
"ical"
,
ical_str
,
"is_update"
,
is_update
);
char
*
serial
=
json_dumps
(
val
,
JSON_COMPACT
);
notify
(
notifier
,
"IMIP"
,
NULL
,
httpd_userid
,
NULL
,
0
,
NULL
,
serial
,
NULL
);
free
(
serial
);
json_decref
(
val
);
return
0
;
}
/* Add a <response> XML element for 'recipient' to 'root' */
xmlNodePtr
xml_add_schedresponse
(
xmlNodePtr
root
,
xmlNsPtr
dav_ns
,
xmlChar
*
recipient
,
xmlChar
*
status
)
{
xmlNodePtr
resp
,
recip
;
resp
=
xmlNewChild
(
root
,
NULL
,
BAD_CAST
"response"
,
NULL
);
recip
=
xmlNewChild
(
resp
,
NULL
,
BAD_CAST
"recipient"
,
NULL
);
if
(
dav_ns
)
xml_add_href
(
recip
,
dav_ns
,
(
const
char
*
)
recipient
);
else
xmlNodeAddContent
(
recip
,
recipient
);
if
(
status
)
xmlNewChild
(
resp
,
NULL
,
BAD_CAST
"request-status"
,
status
);
return
resp
;
}
struct
remote_rock
{
struct
transaction_t
*
txn
;
icalcomponent
*
ical
;
xmlNodePtr
root
;
xmlNsPtr
*
ns
;
};
/* Send an iTIP busytime request to remote attendees via iMIP or iSchedule */
static
void
busytime_query_remote
(
const
char
*
server
__attribute__
((
unused
)),
void
*
data
,
void
*
rock
)
{
struct
sched_param
*
remote
=
(
struct
sched_param
*
)
data
;
struct
remote_rock
*
rrock
=
(
struct
remote_rock
*
)
rock
;
icalcomponent
*
comp
;
struct
proplist
*
list
;
xmlNodePtr
resp
;
const
char
*
status
=
NULL
;
int
r
;
comp
=
icalcomponent_get_first_real_component
(
rrock
->
ical
);
/* Add the attendees to the iTIP request */
for
(
list
=
remote
->
props
;
list
;
list
=
list
->
next
)
{
icalcomponent_add_property
(
comp
,
list
->
prop
);
}
if
(
remote
->
flags
==
SCHEDTYPE_REMOTE
)
{
/* Use iMIP -
don't bother sending, its not very useful and not well supported */
status
=
REQSTAT_TEMPFAIL
;
}
else
{
/* Use iSchedule */
xmlNodePtr
xml
;
r
=
isched_send
(
remote
,
NULL
,
rrock
->
ical
,
&
xml
);
if
(
r
)
status
=
REQSTAT_TEMPFAIL
;
else
if
(
xmlStrcmp
(
xml
->
name
,
BAD_CAST
"schedule-response"
))
{
if
(
r
)
status
=
REQSTAT_TEMPFAIL
;
}
else
{
xmlNodePtr
cur
;
/* Process each response element */
for
(
cur
=
xml
->
children
;
cur
;
cur
=
cur
->
next
)
{
xmlNodePtr
node
;
xmlChar
*
recip
=
NULL
,
*
status
=
NULL
,
*
content
=
NULL
;
if
(
cur
->
type
!=
XML_ELEMENT_NODE
)
continue
;
for
(
node
=
cur
->
children
;
node
;
node
=
node
->
next
)
{
if
(
node
->
type
!=
XML_ELEMENT_NODE
)
continue
;
if
(
!
xmlStrcmp
(
node
->
name
,
BAD_CAST
"recipient"
))
recip
=
xmlNodeGetContent
(
node
);
else
if
(
!
xmlStrcmp
(
node
->
name
,
BAD_CAST
"request-status"
))
status
=
xmlNodeGetContent
(
node
);
else
if
(
!
xmlStrcmp
(
node
->
name
,
BAD_CAST
"calendar-data"
))
content
=
xmlNodeGetContent
(
node
);
}
resp
=
xml_add_schedresponse
(
rrock
->
root
,
!
(
rrock
->
txn
->
req_tgt
.
allow
&
ALLOW_ISCHEDULE
)
?
rrock
->
ns
[
NS_DAV
]
:
NULL
,
recip
,
status
);
xmlFree
(
status
);
xmlFree
(
recip
);
if
(
content
)
{
xmlNodePtr
cdata
=
xmlNewTextChild
(
resp
,
NULL
,
BAD_CAST
"calendar-data"
,
NULL
);
xmlAddChild
(
cdata
,
xmlNewCDataBlock
(
rrock
->
root
->
doc
,
content
,
xmlStrlen
(
content
)));
xmlFree
(
content
);
/* iCal data in resp SHOULD NOT be transformed */
rrock
->
txn
->
flags
.
cc
|=
CC_NOTRANSFORM
;
}
}
xmlFreeDoc
(
xml
->
doc
);
}
}
/* Report request-status (if necesary)
* Remove the attendees from the iTIP request and hash bucket
*/
for
(
list
=
remote
->
props
;
list
;
list
=
list
->
next
)
{
if
(
status
)
{
const
char
*
attendee
=
icalproperty_get_attendee
(
list
->
prop
);
xml_add_schedresponse
(
rrock
->
root
,
!
(
rrock
->
txn
->
req_tgt
.
allow
&
ALLOW_ISCHEDULE
)
?
rrock
->
ns
[
NS_DAV
]
:
NULL
,
BAD_CAST
attendee
,
BAD_CAST
status
);
}
icalcomponent_remove_property
(
comp
,
list
->
prop
);
icalproperty_free
(
list
->
prop
);
}
if
(
remote
->
server
)
free
(
remote
->
server
);
}
static
void
free_sched_param_props
(
void
*
data
)
{
struct
sched_param
*
sched_param
=
(
struct
sched_param
*
)
data
;
if
(
sched_param
)
{
struct
proplist
*
prop
,
*
next
;
for
(
prop
=
sched_param
->
props
;
prop
;
prop
=
next
)
{
next
=
prop
->
next
;
free
(
prop
);
}
free
(
sched_param
);
}
}
/* Perform a Busy Time query based on given VFREEBUSY component */
/* NOTE: This function is destructive of 'ical' */
int
sched_busytime_query
(
struct
transaction_t
*
txn
,
struct
mime_type_t
*
mime
,
icalcomponent
*
ical
)
{
int
ret
=
0
;
static
const
char
*
calendarprefix
=
NULL
;
icalcomponent
*
comp
;
char
mailboxname
[
MAX_MAILBOX_BUFFER
];
icalproperty
*
prop
=
NULL
,
*
next
;
const
char
*
uid
=
NULL
,
*
organizer
=
NULL
;
struct
sched_param
sparam
;
struct
auth_state
*
org_authstate
=
NULL
;
xmlNodePtr
root
=
NULL
;
xmlNsPtr
ns
[
NUM_NAMESPACE
];
struct
propfind_ctx
fctx
;
struct
calquery_filter
calfilter
;
struct
hash_table
remote_table
;
struct
sched_param
*
remote
=
NULL
;
if
(
!
calendarprefix
)
{
calendarprefix
=
config_getstring
(
IMAPOPT_CALENDARPREFIX
);
}
comp
=
icalcomponent_get_first_real_component
(
ical
);
uid
=
icalcomponent_get_uid
(
comp
);
prop
=
icalcomponent_get_first_property
(
comp
,
ICAL_ORGANIZER_PROPERTY
);
organizer
=
icalproperty_get_organizer
(
prop
);
/* XXX Do we need to do more checks here? */
if
(
caladdress_lookup
(
organizer
,
&
sparam
,
httpd_userid
)
||
(
sparam
.
flags
&
SCHEDTYPE_REMOTE
))
org_authstate
=
auth_newstate
(
"anonymous"
);
else
org_authstate
=
auth_newstate
(
sparam
.
userid
);
/* Start construction of our schedule-response */
if
(
!
(
root
=
init_xml_response
(
"schedule-response"
,
(
txn
->
req_tgt
.
allow
&
ALLOW_ISCHEDULE
)
?
NS_ISCHED
:
NS_CALDAV
,
NULL
,
ns
)))
{
ret
=
HTTP_SERVER_ERROR
;
txn
->
error
.
desc
=
"Unable to create XML response
\r\n
"
;
goto
done
;
}
/* Need DAV for hrefs */
ensure_ns
(
ns
,
NS_DAV
,
root
,
XML_NS_DAV
,
"D"
);
/* Populate our filter and propfind context for local attendees */
memset
(
&
calfilter
,
0
,
sizeof
(
struct
calquery_filter
));
calfilter
.
comp
=
CAL_COMP_VEVENT
|
CAL_COMP_VFREEBUSY
|
CAL_COMP_VAVAILABILITY
;
calfilter
.
start
=
icalcomponent_get_dtstart
(
comp
);
calfilter
.
end
=
icalcomponent_get_dtend
(
comp
);
calfilter
.
flags
=
BUSYTIME_QUERY
|
CHECK_CAL_TRANSP
|
CHECK_USER_AVAIL
;
memset
(
&
fctx
,
0
,
sizeof
(
struct
propfind_ctx
));
fctx
.
req_tgt
=
&
txn
->
req_tgt
;
fctx
.
depth
=
2
;
fctx
.
userid
=
httpd_userid
;
fctx
.
userisadmin
=
httpd_userisadmin
;
fctx
.
authstate
=
org_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
;
/* Create hash table for any remote attendee servers */
construct_hash_table
(
&
remote_table
,
10
,
1
);
/* Process each attendee */
for
(
prop
=
icalcomponent_get_first_property
(
comp
,
ICAL_ATTENDEE_PROPERTY
);
prop
;
prop
=
next
)
{
const
char
*
attendee
;
int
r
;
next
=
icalcomponent_get_next_property
(
comp
,
ICAL_ATTENDEE_PROPERTY
);
/* Remove each attendee so we can add in only those
that reside on a given remote server later */
icalcomponent_remove_property
(
comp
,
prop
);
/* Is attendee remote or local? */
attendee
=
icalproperty_get_attendee
(
prop
);
r
=
caladdress_lookup
(
attendee
,
&
sparam
,
httpd_userid
);
/* Don't allow scheduling of remote users via an iSchedule request */
if
((
sparam
.
flags
&
SCHEDTYPE_REMOTE
)
&&
(
txn
->
req_tgt
.
allow
&
ALLOW_ISCHEDULE
))
{
r
=
HTTP_FORBIDDEN
;
}
if
(
r
)
{
xml_add_schedresponse
(
root
,
!
(
txn
->
req_tgt
.
allow
&
ALLOW_ISCHEDULE
)
?
ns
[
NS_DAV
]
:
NULL
,
BAD_CAST
attendee
,
BAD_CAST
REQSTAT_NOUSER
);
icalproperty_free
(
prop
);
}
else
if
(
sparam
.
flags
)
{
/* Remote attendee */
struct
proplist
*
newprop
;
const
char
*
key
;
if
(
sparam
.
flags
==
SCHEDTYPE_REMOTE
)
{
/* iMIP - collect attendees under empty key (no server) */
key
=
""
;
}
else
{
/* iSchedule - collect attendees by server */
key
=
sparam
.
server
;
}
remote
=
hash_lookup
(
key
,
&
remote_table
);
if
(
!
remote
)
{
/* New remote - add it to the hash table */
remote
=
xzmalloc
(
sizeof
(
struct
sched_param
));
if
(
sparam
.
server
)
remote
->
server
=
xstrdup
(
sparam
.
server
);
remote
->
port
=
sparam
.
port
;
remote
->
flags
=
sparam
.
flags
;
hash_insert
(
key
,
remote
,
&
remote_table
);
}
newprop
=
xmalloc
(
sizeof
(
struct
proplist
));
newprop
->
prop
=
prop
;
newprop
->
next
=
remote
->
props
;
remote
->
props
=
newprop
;
}
else
{
/* Local attendee on this server */
xmlNodePtr
resp
;
const
char
*
userid
=
sparam
.
userid
;
icalcomponent
*
busy
=
NULL
;
resp
=
xml_add_schedresponse
(
root
,
!
(
txn
->
req_tgt
.
allow
&
ALLOW_ISCHEDULE
)
?
ns
[
NS_DAV
]
:
NULL
,
BAD_CAST
attendee
,
NULL
);
/* XXX - BROKEN WITH DOMAIN SPLIT, POS */
/* Check ACL of ORGANIZER on attendee's Scheduling Inbox */
snprintf
(
mailboxname
,
sizeof
(
mailboxname
),
"user.%s.%s.Inbox"
,
userid
,
calendarprefix
);
r
=
mboxlist_lookup
(
mailboxname
,
NULL
,
NULL
);
if
(
r
)
{
syslog
(
LOG_INFO
,
"mboxlist_lookup(%s) failed: %s"
,
mailboxname
,
error_message
(
r
));
xmlNewChild
(
resp
,
NULL
,
BAD_CAST
"request-status"
,
BAD_CAST
REQSTAT_REJECTED
);
}
else
{
/* Start query at attendee's calendar-home-set */
snprintf
(
mailboxname
,
sizeof
(
mailboxname
),
"user.%s.%s"
,
userid
,
calendarprefix
);
fctx
.
davdb
=
NULL
;
fctx
.
req_tgt
->
collection
=
NULL
;
calfilter
.
freebusy
.
len
=
0
;
busy
=
busytime_query_local
(
txn
,
&
fctx
,
mailboxname
,
ICAL_METHOD_REPLY
,
uid
,
organizer
,
attendee
);
}
if
(
busy
)
{
xmlNodePtr
cdata
;
struct
buf
*
fb_str
=
mime
->
from_object
(
busy
);
icalcomponent_free
(
busy
);
xmlNewChild
(
resp
,
NULL
,
BAD_CAST
"request-status"
,
BAD_CAST
REQSTAT_SUCCESS
);
cdata
=
xmlNewTextChild
(
resp
,
NULL
,
BAD_CAST
"calendar-data"
,
NULL
);
/* Trim any charset from content-type */
buf_reset
(
&
txn
->
buf
);
buf_printf
(
&
txn
->
buf
,
"%.*s"
,
(
int
)
strcspn
(
mime
->
content_type
,
";"
),
mime
->
content_type
);
xmlNewProp
(
cdata
,
BAD_CAST
"content-type"
,
BAD_CAST
buf_cstring
(
&
txn
->
buf
));
if
(
mime
->
version
)
xmlNewProp
(
cdata
,
BAD_CAST
"version"
,
BAD_CAST
mime
->
version
);
xmlAddChild
(
cdata
,
xmlNewCDataBlock
(
root
->
doc
,
BAD_CAST
buf_base
(
fb_str
),
buf_len
(
fb_str
)));
buf_destroy
(
fb_str
);
/* iCalendar data in response should not be transformed */
txn
->
flags
.
cc
|=
CC_NOTRANSFORM
;
}
else
{
xmlNewChild
(
resp
,
NULL
,
BAD_CAST
"request-status"
,
BAD_CAST
REQSTAT_NOUSER
);
}
icalproperty_free
(
prop
);
}
}
buf_reset
(
&
txn
->
buf
);
if
(
remote
)
{
struct
remote_rock
rrock
=
{
txn
,
ical
,
root
,
ns
};
hash_enumerate
(
&
remote_table
,
busytime_query_remote
,
&
rrock
);
}
free_hash_table
(
&
remote_table
,
free_sched_param_props
);
/* Output the XML response */
if
(
!
ret
)
xml_response
(
HTTP_OK
,
txn
,
root
->
doc
);
done
:
if
(
org_authstate
)
auth_freestate
(
org_authstate
);
if
(
calfilter
.
freebusy
.
fb
)
free
(
calfilter
.
freebusy
.
fb
);
if
(
root
)
xmlFreeDoc
(
root
->
doc
);
return
ret
;
}
static
void
free_sched_data
(
void
*
data
)
{
struct
sched_data
*
sched_data
=
(
struct
sched_data
*
)
data
;
if
(
sched_data
)
{
if
(
sched_data
->
itip
)
icalcomponent_free
(
sched_data
->
itip
);
free
(
sched_data
);
}
}
#define SCHEDSTAT_PENDING "1.0"
#define SCHEDSTAT_SENT "1.1"
#define SCHEDSTAT_DELIVERED "1.2"
#define SCHEDSTAT_SUCCESS "2.0"
#define SCHEDSTAT_PARAM "2.3"
#define SCHEDSTAT_NOUSER "3.7"
#define SCHEDSTAT_NOPRIVS "3.8"
#define SCHEDSTAT_TEMPFAIL "5.1"
#define SCHEDSTAT_PERMFAIL "5.2"
#define SCHEDSTAT_REJECTED "5.3"
/* Deliver scheduling object to a remote recipient */
static
void
sched_deliver_remote
(
const
char
*
recipient
,
struct
sched_param
*
sparam
,
struct
sched_data
*
sched_data
)
{
int
r
;
if
(
sparam
->
flags
&
SCHEDTYPE_ISCHEDULE
)
{
/* Use iSchedule */
xmlNodePtr
xml
;
r
=
isched_send
(
sparam
,
recipient
,
sched_data
->
itip
,
&
xml
);
if
(
r
)
{
sched_data
->
status
=
sched_data
->
ischedule
?
REQSTAT_TEMPFAIL
:
SCHEDSTAT_TEMPFAIL
;
}
else
if
(
xmlStrcmp
(
xml
->
name
,
BAD_CAST
"schedule-response"
))
{
sched_data
->
status
=
sched_data
->
ischedule
?
REQSTAT_TEMPFAIL
:
SCHEDSTAT_TEMPFAIL
;
}
else
{
xmlNodePtr
cur
;
/* Process each response element */
for
(
cur
=
xml
->
children
;
cur
;
cur
=
cur
->
next
)
{
xmlNodePtr
node
;
xmlChar
*
recip
=
NULL
,
*
status
=
NULL
;
static
char
statbuf
[
1024
];
if
(
cur
->
type
!=
XML_ELEMENT_NODE
)
continue
;
for
(
node
=
cur
->
children
;
node
;
node
=
node
->
next
)
{
if
(
node
->
type
!=
XML_ELEMENT_NODE
)
continue
;
if
(
!
xmlStrcmp
(
node
->
name
,
BAD_CAST
"recipient"
))
recip
=
xmlNodeGetContent
(
node
);
else
if
(
!
xmlStrcmp
(
node
->
name
,
BAD_CAST
"request-status"
))
status
=
xmlNodeGetContent
(
node
);
}
if
(
!
strncmp
((
const
char
*
)
status
,
"2.0"
,
3
))
{
sched_data
->
status
=
sched_data
->
ischedule
?
REQSTAT_DELIVERED
:
SCHEDSTAT_DELIVERED
;
}
else
{
if
(
sched_data
->
ischedule
)
strlcpy
(
statbuf
,
(
const
char
*
)
status
,
sizeof
(
statbuf
));
else
strlcpy
(
statbuf
,
(
const
char
*
)
status
,
4
);
sched_data
->
status
=
statbuf
;
}
xmlFree
(
status
);
xmlFree
(
recip
);
}
}
}
else
{
if
(
!
strncasecmp
(
recipient
,
"mailto:"
,
7
))
r
=
imip_send
(
sched_data
->
itip
,
recipient
+
7
,
sched_data
->
is_update
);
else
r
=
1
;
/* code doesn't matter */
if
(
!
r
)
{
sched_data
->
status
=
sched_data
->
ischedule
?
REQSTAT_SENT
:
SCHEDSTAT_SENT
;
}
else
{
sched_data
->
status
=
sched_data
->
ischedule
?
REQSTAT_TEMPFAIL
:
SCHEDSTAT_TEMPFAIL
;
}
}
}
#ifdef HAVE_VPOLL
/*
* deliver_merge_reply() helper function
*
* Merge VOTER responses into VPOLL subcomponents
*/
static
void
deliver_merge_vpoll_reply
(
icalcomponent
*
ical
,
icalcomponent
*
reply
)
{
icalcomponent
*
new_ballot
,
*
vvoter
;
icalproperty
*
voterp
;
const
char
*
voter
;
/* Get VOTER from reply */
new_ballot
=
icalcomponent_get_first_component
(
reply
,
ICAL_VVOTER_COMPONENT
);
voterp
=
icalcomponent_get_first_property
(
new_ballot
,
ICAL_VOTER_PROPERTY
);
voter
=
icalproperty_get_voter
(
voterp
);
/* Locate VOTER in existing VPOLL */
for
(
vvoter
=
icalcomponent_get_first_component
(
ical
,
ICAL_VVOTER_COMPONENT
);
vvoter
;
vvoter
=
icalcomponent_get_next_component
(
ical
,
ICAL_VVOTER_COMPONENT
))
{
voterp
=
icalcomponent_get_first_property
(
vvoter
,
ICAL_VOTER_PROPERTY
);
if
(
!
strcmp
(
voter
,
icalproperty_get_voter
(
voterp
)))
{
icalcomponent_remove_component
(
ical
,
vvoter
);
icalcomponent_free
(
vvoter
);
break
;
}
}
/* XXX Actually need to compare POLL-ITEM-IDs */
icalcomponent_add_component
(
ical
,
icalcomponent_new_clone
(
new_ballot
));
}
/* sched_reply() helper function
*
* Add voter responses to VPOLL reply and remove candidate components
*
*/
static
void
sched_vpoll_reply
(
icalcomponent
*
poll
)
{
icalcomponent
*
item
,
*
next
;
for
(
item
=
icalcomponent_get_first_component
(
poll
,
ICAL_ANY_COMPONENT
);
item
;
item
=
next
)
{
next
=
icalcomponent_get_next_component
(
poll
,
ICAL_ANY_COMPONENT
);
switch
(
icalcomponent_isa
(
item
))
{
case
ICAL_VVOTER_COMPONENT
:
/* Our ballot, leave it */
/* XXX Need to compare against previous votes */
break
;
default
:
/* Candidate component, remove it */
icalcomponent_remove_component
(
poll
,
item
);
icalcomponent_free
(
item
);
break
;
}
}
}
static
int
deliver_merge_pollstatus
(
icalcomponent
*
ical
,
icalcomponent
*
request
)
{
int
deliver_inbox
=
0
;
icalcomponent
*
oldpoll
,
*
newpoll
,
*
vvoter
,
*
next
;
/* Remove each VVOTER from old object */
oldpoll
=
icalcomponent_get_first_component
(
ical
,
ICAL_VPOLL_COMPONENT
);
for
(
vvoter
=
icalcomponent_get_first_component
(
oldpoll
,
ICAL_VVOTER_COMPONENT
);
vvoter
;
vvoter
=
next
)
{
next
=
icalcomponent_get_next_component
(
oldpoll
,
ICAL_VVOTER_COMPONENT
);
icalcomponent_remove_component
(
oldpoll
,
vvoter
);
icalcomponent_free
(
vvoter
);
}
/* Add each VVOTER in the iTIP request to old object */
newpoll
=
icalcomponent_get_first_component
(
request
,
ICAL_VPOLL_COMPONENT
);
for
(
vvoter
=
icalcomponent_get_first_component
(
newpoll
,
ICAL_VVOTER_COMPONENT
);
vvoter
;
vvoter
=
icalcomponent_get_next_component
(
newpoll
,
ICAL_VVOTER_COMPONENT
))
{
icalcomponent_add_component
(
oldpoll
,
icalcomponent_new_clone
(
vvoter
));
}
return
deliver_inbox
;
}
static
void
sched_pollstatus
(
const
char
*
organizer
,
struct
sched_param
*
sparam
,
icalcomponent
*
ical
,
const
char
*
voter
)
{
struct
auth_state
*
authstate
;
struct
sched_data
sched_data
;
icalcomponent
*
itip
,
*
comp
;
icalproperty
*
prop
;
/* XXX Do we need to do more checks here? */
if
(
sparam
->
flags
&
SCHEDTYPE_REMOTE
)
authstate
=
auth_newstate
(
"anonymous"
);
else
authstate
=
auth_newstate
(
sparam
->
userid
);
memset
(
&
sched_data
,
0
,
sizeof
(
struct
sched_data
));
sched_data
.
force_send
=
ICAL_SCHEDULEFORCESEND_NONE
;
/* Create a shell for our iTIP request objects */
itip
=
icalcomponent_vanew
(
ICAL_VCALENDAR_COMPONENT
,
icalproperty_new_version
(
"2.0"
),
icalproperty_new_prodid
(
ical_prodid
),
icalproperty_new_method
(
ICAL_METHOD_POLLSTATUS
),
0
);
/* Copy over any CALSCALE property */
prop
=
icalcomponent_get_first_property
(
ical
,
ICAL_CALSCALE_PROPERTY
);
if
(
prop
)
icalcomponent_add_property
(
itip
,
icalproperty_new_clone
(
prop
));
/* Process each VPOLL in resource */
for
(
comp
=
icalcomponent_get_first_component
(
ical
,
ICAL_VPOLL_COMPONENT
);
comp
;
comp
=
icalcomponent_get_next_component
(
ical
,
ICAL_VPOLL_COMPONENT
))
{
icalcomponent
*
stat
,
*
poll
,
*
sub
,
*
next
;
struct
strlist
*
voters
=
NULL
;
/* Make a working copy of the iTIP */
stat
=
icalcomponent_new_clone
(
itip
);
/* Make a working copy of the VPOLL and add to pollstatus */
poll
=
icalcomponent_new_clone
(
comp
);
icalcomponent_add_component
(
stat
,
poll
);
/* Process each sub-component of VPOLL */
for
(
sub
=
icalcomponent_get_first_component
(
poll
,
ICAL_ANY_COMPONENT
);
sub
;
sub
=
next
)
{
next
=
icalcomponent_get_next_component
(
poll
,
ICAL_ANY_COMPONENT
);
switch
(
icalcomponent_isa
(
sub
))
{
case
ICAL_VVOTER_COMPONENT
:
{
/* Make list of VOTERs (stripping SCHEDULE-STATUS) */
const
char
*
this_voter
;
prop
=
icalcomponent_get_first_property
(
sub
,
ICAL_VOTER_PROPERTY
);
this_voter
=
icalproperty_get_voter
(
prop
);
/* Don't update organizer or voter that triggered POLLSTATUS */
if
(
strcmp
(
this_voter
,
organizer
)
&&
strcmp
(
this_voter
,
voter
))
appendstrlist
(
&
voters
,
(
char
*
)
this_voter
);
icalproperty_remove_parameter_by_name
(
prop
,
"SCHEDULE-STATUS"
);
break
;
}
default
:
/* Remove candidate components */
icalcomponent_remove_component
(
poll
,
sub
);
icalcomponent_free
(
sub
);
break
;
}
}
/* Attempt to deliver to each voter in the list - removing as we go */
while
(
voters
)
{
struct
strlist
*
next
=
voters
->
next
;
sched_data
.
itip
=
stat
;
sched_deliver
(
voters
->
s
,
&
sched_data
,
authstate
);
free
(
voters
->
s
);
free
(
voters
);
voters
=
next
;
}
icalcomponent_free
(
stat
);
}
icalcomponent_free
(
itip
);
auth_freestate
(
authstate
);
}
#else
/* HAVE_VPOLL */
static
void
deliver_merge_vpoll_reply
(
icalcomponent
*
ical
__attribute__
((
unused
)),
icalcomponent
*
reply
__attribute__
((
unused
)))
{
return
;
}
static
void
sched_vpoll_reply
(
icalcomponent
*
poll
__attribute__
((
unused
)))
{
return
;
}
static
int
deliver_merge_pollstatus
(
icalcomponent
*
ical
__attribute__
((
unused
)),
icalcomponent
*
request
__attribute__
((
unused
)))
{
return
0
;
}
static
void
sched_pollstatus
(
const
char
*
organizer
__attribute__
((
unused
)),
struct
sched_param
*
sparam
__attribute__
((
unused
)),
icalcomponent
*
ical
__attribute__
((
unused
)),
const
char
*
voter
__attribute__
((
unused
)))
{
return
;
}
#endif
/* HAVE_VPOLL */
static
const
char
*
deliver_merge_reply
(
icalcomponent
*
ical
,
icalcomponent
*
reply
)
{
struct
hash_table
comp_table
;
icalcomponent
*
comp
,
*
itip
;
icalcomponent_kind
kind
;
icalproperty
*
prop
,
*
att
;
icalparameter
*
param
;
icalparameter_partstat
partstat
=
ICAL_PARTSTAT_NONE
;
icalparameter_rsvp
rsvp
=
ICAL_RSVP_NONE
;
const
char
*
recurid
,
*
attendee
=
NULL
,
*
req_stat
=
SCHEDSTAT_SUCCESS
;
/* Add each component of old object to hash table for comparison */
construct_hash_table
(
&
comp_table
,
10
,
1
);
comp
=
icalcomponent_get_first_real_component
(
ical
);
kind
=
icalcomponent_isa
(
comp
);
do
{
prop
=
icalcomponent_get_first_property
(
comp
,
ICAL_RECURRENCEID_PROPERTY
);
if
(
prop
)
recurid
=
icalproperty_get_value_as_string
(
prop
);
else
recurid
=
""
;
hash_insert
(
recurid
,
comp
,
&
comp_table
);
}
while
((
comp
=
icalcomponent_get_next_component
(
ical
,
kind
)));
/* Process each component in the iTIP reply */
for
(
itip
=
icalcomponent_get_first_component
(
reply
,
kind
);
itip
;
itip
=
icalcomponent_get_next_component
(
reply
,
kind
))
{
/* Lookup this comp in the hash table */
prop
=
icalcomponent_get_first_property
(
itip
,
ICAL_RECURRENCEID_PROPERTY
);
if
(
prop
)
recurid
=
icalproperty_get_value_as_string
(
prop
);
else
recurid
=
""
;
comp
=
hash_lookup
(
recurid
,
&
comp_table
);
if
(
!
comp
)
{
/* New recurrence overridden by attendee.
Create a new recurrence from master component. */
comp
=
icalcomponent_new_clone
(
hash_lookup
(
""
,
&
comp_table
));
/* Add RECURRENCE-ID */
icalcomponent_add_property
(
comp
,
icalproperty_new_clone
(
prop
));
/* Remove RRULE */
prop
=
icalcomponent_get_first_property
(
comp
,
ICAL_RRULE_PROPERTY
);
if
(
prop
)
{
icalcomponent_remove_property
(
comp
,
prop
);
icalproperty_free
(
prop
);
}
/* Replace DTSTART, DTEND, SEQUENCE */
prop
=
icalcomponent_get_first_property
(
comp
,
ICAL_DTSTART_PROPERTY
);
if
(
prop
)
{
icalcomponent_remove_property
(
comp
,
prop
);
icalproperty_free
(
prop
);
}
prop
=
icalcomponent_get_first_property
(
itip
,
ICAL_DTSTART_PROPERTY
);
if
(
prop
)
icalcomponent_add_property
(
comp
,
icalproperty_new_clone
(
prop
));
prop
=
icalcomponent_get_first_property
(
comp
,
ICAL_DTEND_PROPERTY
);
if
(
prop
)
{
icalcomponent_remove_property
(
comp
,
prop
);
icalproperty_free
(
prop
);
}
prop
=
icalcomponent_get_first_property
(
itip
,
ICAL_DTEND_PROPERTY
);
if
(
prop
)
icalcomponent_add_property
(
comp
,
icalproperty_new_clone
(
prop
));
prop
=
icalcomponent_get_first_property
(
comp
,
ICAL_SEQUENCE_PROPERTY
);
if
(
prop
)
{
icalcomponent_remove_property
(
comp
,
prop
);
icalproperty_free
(
prop
);
}
prop
=
icalcomponent_get_first_property
(
itip
,
ICAL_SEQUENCE_PROPERTY
);
if
(
prop
)
icalcomponent_add_property
(
comp
,
icalproperty_new_clone
(
prop
));
icalcomponent_add_component
(
ical
,
comp
);
}
/* Get the sending attendee */
att
=
icalcomponent_get_first_invitee
(
itip
);
attendee
=
icalproperty_get_invitee
(
att
);
param
=
icalproperty_get_first_parameter
(
att
,
ICAL_PARTSTAT_PARAMETER
);
if
(
param
)
partstat
=
icalparameter_get_partstat
(
param
);
param
=
icalproperty_get_first_parameter
(
att
,
ICAL_RSVP_PARAMETER
);
if
(
param
)
rsvp
=
icalparameter_get_rsvp
(
param
);
prop
=
icalcomponent_get_first_property
(
itip
,
ICAL_REQUESTSTATUS_PROPERTY
);
if
(
prop
)
{
struct
icalreqstattype
rq
=
icalproperty_get_requeststatus
(
prop
);
req_stat
=
icalenum_reqstat_code
(
rq
.
code
);
}
/* Find matching attendee in existing object */
for
(
prop
=
icalcomponent_get_first_invitee
(
comp
);
prop
&&
strcmp
(
attendee
,
icalproperty_get_invitee
(
prop
));
prop
=
icalcomponent_get_next_invitee
(
comp
));
if
(
!
prop
)
{
/* Attendee added themselves to this recurrence */
assert
(
icalproperty_isa
(
prop
)
!=
ICAL_VOTER_PROPERTY
);
prop
=
icalproperty_new_clone
(
att
);
icalcomponent_add_property
(
comp
,
prop
);
}
/* Set PARTSTAT */
if
(
partstat
!=
ICAL_PARTSTAT_NONE
)
{
param
=
icalparameter_new_partstat
(
partstat
);
icalproperty_set_parameter
(
prop
,
param
);
}
/* Set RSVP */
icalproperty_remove_parameter_by_kind
(
prop
,
ICAL_RSVP_PARAMETER
);
if
(
rsvp
!=
ICAL_RSVP_NONE
)
{
param
=
icalparameter_new_rsvp
(
rsvp
);
icalproperty_add_parameter
(
prop
,
param
);
}
/* Set SCHEDULE-STATUS */
param
=
icalparameter_new_schedulestatus
(
req_stat
);
icalproperty_set_parameter
(
prop
,
param
);
/* Handle VPOLL reply */
if
(
kind
==
ICAL_VPOLL_COMPONENT
)
deliver_merge_vpoll_reply
(
comp
,
itip
);
}
free_hash_table
(
&
comp_table
,
NULL
);
return
attendee
;
}
static
int
deliver_merge_request
(
const
char
*
attendee
,
icalcomponent
*
ical
,
icalcomponent
*
request
)
{
int
deliver_inbox
=
0
;
struct
hash_table
comp_table
;
icalcomponent
*
comp
,
*
itip
;
icalcomponent_kind
kind
=
ICAL_NO_COMPONENT
;
icalproperty
*
prop
;
icalparameter
*
param
;
const
char
*
tzid
,
*
recurid
;
/* Add each VTIMEZONE of old object to hash table for comparison */
construct_hash_table
(
&
comp_table
,
10
,
1
);
for
(
comp
=
icalcomponent_get_first_component
(
ical
,
ICAL_VTIMEZONE_COMPONENT
);
comp
;
comp
=
icalcomponent_get_next_component
(
ical
,
ICAL_VTIMEZONE_COMPONENT
))
{
prop
=
icalcomponent_get_first_property
(
comp
,
ICAL_TZID_PROPERTY
);
tzid
=
icalproperty_get_tzid
(
prop
);
hash_insert
(
tzid
,
comp
,
&
comp_table
);
}
/* Process each VTIMEZONE in the iTIP request */
for
(
itip
=
icalcomponent_get_first_component
(
request
,
ICAL_VTIMEZONE_COMPONENT
);
itip
;
itip
=
icalcomponent_get_next_component
(
request
,
ICAL_VTIMEZONE_COMPONENT
))
{
/* Lookup this TZID in the hash table */
prop
=
icalcomponent_get_first_property
(
itip
,
ICAL_TZID_PROPERTY
);
tzid
=
icalproperty_get_tzid
(
prop
);
comp
=
hash_lookup
(
tzid
,
&
comp_table
);
if
(
comp
)
{
/* Remove component from old object */
icalcomponent_remove_component
(
ical
,
comp
);
icalcomponent_free
(
comp
);
}
/* Add new/modified component from iTIP request */
icalcomponent_add_component
(
ical
,
icalcomponent_new_clone
(
itip
));
}
free_hash_table
(
&
comp_table
,
NULL
);
/* Add each component of old object to hash table for comparison */
construct_hash_table
(
&
comp_table
,
10
,
1
);
comp
=
icalcomponent_get_first_real_component
(
ical
);
if
(
comp
)
kind
=
icalcomponent_isa
(
comp
);
for
(;
comp
;
comp
=
icalcomponent_get_next_component
(
ical
,
kind
))
{
prop
=
icalcomponent_get_first_property
(
comp
,
ICAL_RECURRENCEID_PROPERTY
);
if
(
prop
)
recurid
=
icalproperty_get_value_as_string
(
prop
);
else
recurid
=
""
;
hash_insert
(
recurid
,
comp
,
&
comp_table
);
}
/* Process each component in the iTIP request */
itip
=
icalcomponent_get_first_real_component
(
request
);
if
(
kind
==
ICAL_NO_COMPONENT
)
kind
=
icalcomponent_isa
(
itip
);
for
(;
itip
;
itip
=
icalcomponent_get_next_component
(
request
,
kind
))
{
icalcomponent
*
new_comp
=
icalcomponent_new_clone
(
itip
);
/* Lookup this comp in the hash table */
prop
=
icalcomponent_get_first_property
(
itip
,
ICAL_RECURRENCEID_PROPERTY
);
if
(
prop
)
recurid
=
icalproperty_get_value_as_string
(
prop
);
else
recurid
=
""
;
comp
=
hash_lookup
(
recurid
,
&
comp_table
);
if
(
comp
)
{
int
old_seq
,
new_seq
;
/* Check if this is something more than an update */
/* XXX Probably need to check PARTSTAT=NEEDS-ACTION
and RSVP=TRUE as well */
old_seq
=
icalcomponent_get_sequence
(
comp
);
new_seq
=
icalcomponent_get_sequence
(
itip
);
if
(
new_seq
>
old_seq
)
deliver_inbox
=
1
;
/* Copy over any COMPLETED, PERCENT-COMPLETE,
or TRANSP properties */
prop
=
icalcomponent_get_first_property
(
comp
,
ICAL_COMPLETED_PROPERTY
);
if
(
prop
)
{
icalcomponent_add_property
(
new_comp
,
icalproperty_new_clone
(
prop
));
}
prop
=
icalcomponent_get_first_property
(
comp
,
ICAL_PERCENTCOMPLETE_PROPERTY
);
if
(
prop
)
{
icalcomponent_add_property
(
new_comp
,
icalproperty_new_clone
(
prop
));
}
prop
=
icalcomponent_get_first_property
(
comp
,
ICAL_TRANSP_PROPERTY
);
if
(
prop
)
{
icalcomponent_add_property
(
new_comp
,
icalproperty_new_clone
(
prop
));
}
/* Copy over any ORGANIZER;SCHEDULE-STATUS */
/* XXX Do we only do this iff PARTSTAT!=NEEDS-ACTION */
prop
=
icalcomponent_get_first_property
(
comp
,
ICAL_ORGANIZER_PROPERTY
);
param
=
icalproperty_get_schedulestatus_parameter
(
prop
);
if
(
param
)
{
param
=
icalparameter_new_clone
(
param
);
prop
=
icalcomponent_get_first_property
(
new_comp
,
ICAL_ORGANIZER_PROPERTY
);
icalproperty_add_parameter
(
prop
,
param
);
}
/* Remove component from old object */
icalcomponent_remove_component
(
ical
,
comp
);
icalcomponent_free
(
comp
);
}
else
{
/* New component */
deliver_inbox
=
1
;
}
if
(
config_allowsched
==
IMAP_ENUM_CALDAV_ALLOWSCHEDULING_APPLE
&&
kind
==
ICAL_VEVENT_COMPONENT
)
{
/* Make VEVENT component transparent if recipient ATTENDEE
PARTSTAT=NEEDS-ACTION (for compatibility with CalendarServer) */
for
(
prop
=
icalcomponent_get_first_property
(
new_comp
,
ICAL_ATTENDEE_PROPERTY
);
prop
&&
strcmp
(
icalproperty_get_attendee
(
prop
),
attendee
);
prop
=
icalcomponent_get_next_property
(
new_comp
,
ICAL_ATTENDEE_PROPERTY
));
param
=
icalproperty_get_first_parameter
(
prop
,
ICAL_PARTSTAT_PARAMETER
);
if
(
param
&&
icalparameter_get_partstat
(
param
)
==
ICAL_PARTSTAT_NEEDSACTION
)
{
prop
=
icalcomponent_get_first_property
(
new_comp
,
ICAL_TRANSP_PROPERTY
);
if
(
prop
)
icalproperty_set_transp
(
prop
,
ICAL_TRANSP_TRANSPARENT
);
else
{
prop
=
icalproperty_new_transp
(
ICAL_TRANSP_TRANSPARENT
);
icalcomponent_add_property
(
new_comp
,
prop
);
}
}
}
/* Add new/modified component from iTIP request */
icalcomponent_add_component
(
ical
,
new_comp
);
}
free_hash_table
(
&
comp_table
,
NULL
);
return
deliver_inbox
;
}
/* Deliver scheduling object to local recipient */
static
void
sched_deliver_local
(
const
char
*
recipient
,
struct
sched_param
*
sparam
,
struct
sched_data
*
sched_data
,
struct
auth_state
*
authstate
)
{
int
r
=
0
,
rights
,
reqd_privs
,
deliver_inbox
=
1
;
const
char
*
userid
=
sparam
->
userid
,
*
attendee
=
NULL
;
static
struct
buf
resource
=
BUF_INITIALIZER
;
static
unsigned
sched_count
=
0
;
const
char
*
mailboxname
=
NULL
;
mbentry_t
*
mbentry
=
NULL
;
struct
mailbox
*
mailbox
=
NULL
,
*
inbox
=
NULL
;
struct
caldav_db
*
caldavdb
=
NULL
;
struct
caldav_data
*
cdata
;
icalcomponent
*
ical
=
NULL
;
icalproperty_method
method
;
icalcomponent_kind
kind
;
icalcomponent
*
comp
;
icalproperty
*
prop
;
struct
transaction_t
txn
;
/* Start with an empty (clean) transaction */
memset
(
&
txn
,
0
,
sizeof
(
struct
transaction_t
));
/* Check ACL of sender on recipient's Scheduling Inbox */
mailboxname
=
caldav_mboxname
(
userid
,
SCHED_INBOX
);
r
=
mboxlist_lookup
(
mailboxname
,
&
mbentry
,
NULL
);
if
(
r
)
{
syslog
(
LOG_INFO
,
"mboxlist_lookup(%s) failed: %s"
,
mailboxname
,
error_message
(
r
));
sched_data
->
status
=
sched_data
->
ischedule
?
REQSTAT_REJECTED
:
SCHEDSTAT_REJECTED
;
goto
done
;
}
rights
=
httpd_myrights
(
authstate
,
mbentry
->
acl
);
mboxlist_entry_free
(
&
mbentry
);
reqd_privs
=
sched_data
->
is_reply
?
DACL_REPLY
:
DACL_INVITE
;
if
(
!
(
rights
&
reqd_privs
))
{
sched_data
->
status
=
sched_data
->
ischedule
?
REQSTAT_NOPRIVS
:
SCHEDSTAT_NOPRIVS
;
syslog
(
LOG_DEBUG
,
"No scheduling receive ACL for user %s on Inbox %s"
,
httpd_userid
,
userid
);
goto
done
;
}
/* Open recipient's Inbox for writing */
if
((
r
=
mailbox_open_iwl
(
mailboxname
,
&
inbox
)))
{
syslog
(
LOG_ERR
,
"mailbox_open_iwl(%s) failed: %s"
,
mailboxname
,
error_message
(
r
));
sched_data
->
status
=
sched_data
->
ischedule
?
REQSTAT_TEMPFAIL
:
SCHEDSTAT_TEMPFAIL
;
goto
done
;
}
/* Get METHOD of the iTIP message */
method
=
icalcomponent_get_method
(
sched_data
->
itip
);
/* Search for iCal UID in recipient's calendars */
caldavdb
=
caldav_open_userid
(
userid
);
if
(
!
caldavdb
)
{
sched_data
->
status
=
sched_data
->
ischedule
?
REQSTAT_TEMPFAIL
:
SCHEDSTAT_TEMPFAIL
;
goto
done
;
}
caldav_lookup_uid
(
caldavdb
,
icalcomponent_get_uid
(
sched_data
->
itip
),
&
cdata
);
if
(
cdata
->
dav
.
mailbox
)
{
mailboxname
=
cdata
->
dav
.
mailbox
;
buf_setcstr
(
&
resource
,
cdata
->
dav
.
resource
);
}
else
if
(
sched_data
->
is_reply
)
{
/* Can't find object belonging to organizer - ignore reply */
sched_data
->
status
=
sched_data
->
ischedule
?
REQSTAT_PERMFAIL
:
SCHEDSTAT_PERMFAIL
;
goto
done
;
}
else
if
(
method
==
ICAL_METHOD_CANCEL
||
method
==
ICAL_METHOD_POLLSTATUS
)
{
/* Can't find object belonging to attendee - we're done */
sched_data
->
status
=
sched_data
->
ischedule
?
REQSTAT_SUCCESS
:
SCHEDSTAT_DELIVERED
;
goto
done
;
}
else
{
/* Can't find object belonging to attendee - use default calendar */
mailboxname
=
caldav_mboxname
(
userid
,
SCHED_DEFAULT
);
buf_reset
(
&
resource
);
/* XXX - sanitize the uid? */
buf_printf
(
&
resource
,
"%s.ics"
,
icalcomponent_get_uid
(
sched_data
->
itip
));
/* Create new attendee object */
ical
=
icalcomponent_vanew
(
ICAL_VCALENDAR_COMPONENT
,
0
);
/* Copy over VERSION property */
prop
=
icalcomponent_get_first_property
(
sched_data
->
itip
,
ICAL_VERSION_PROPERTY
);
icalcomponent_add_property
(
ical
,
icalproperty_new_clone
(
prop
));
/* Copy over PRODID property */
prop
=
icalcomponent_get_first_property
(
sched_data
->
itip
,
ICAL_PRODID_PROPERTY
);
icalcomponent_add_property
(
ical
,
icalproperty_new_clone
(
prop
));
/* Copy over any CALSCALE property */
prop
=
icalcomponent_get_first_property
(
sched_data
->
itip
,
ICAL_CALSCALE_PROPERTY
);
if
(
prop
)
{
icalcomponent_add_property
(
ical
,
icalproperty_new_clone
(
prop
));
}
}
/* Open recipient's calendar for writing */
r
=
mailbox_open_iwl
(
mailboxname
,
&
mailbox
);
if
(
r
)
{
syslog
(
LOG_ERR
,
"mailbox_open_iwl(%s) failed: %s"
,
mailboxname
,
error_message
(
r
));
sched_data
->
status
=
sched_data
->
ischedule
?
REQSTAT_TEMPFAIL
:
SCHEDSTAT_TEMPFAIL
;
goto
done
;
}
if
(
cdata
->
dav
.
imap_uid
)
{
struct
index_record
record
;
/* Load message containing the resource and parse iCal data */
r
=
mailbox_find_index_record
(
mailbox
,
cdata
->
dav
.
imap_uid
,
&
record
);
ical
=
record_to_ical
(
mailbox
,
&
record
);
for
(
comp
=
icalcomponent_get_first_component
(
sched_data
->
itip
,
ICAL_ANY_COMPONENT
);
comp
;
comp
=
icalcomponent_get_next_component
(
sched_data
->
itip
,
ICAL_ANY_COMPONENT
))
{
/* Don't allow component type to be changed */
int
reject
=
0
;
kind
=
icalcomponent_isa
(
comp
);
switch
(
kind
)
{
case
ICAL_VEVENT_COMPONENT
:
if
(
cdata
->
comp_type
!=
CAL_COMP_VEVENT
)
reject
=
1
;
break
;
case
ICAL_VTODO_COMPONENT
:
if
(
cdata
->
comp_type
!=
CAL_COMP_VTODO
)
reject
=
1
;
break
;
case
ICAL_VJOURNAL_COMPONENT
:
if
(
cdata
->
comp_type
!=
CAL_COMP_VJOURNAL
)
reject
=
1
;
break
;
case
ICAL_VFREEBUSY_COMPONENT
:
if
(
cdata
->
comp_type
!=
CAL_COMP_VFREEBUSY
)
reject
=
1
;
break
;
case
ICAL_VAVAILABILITY_COMPONENT
:
if
(
cdata
->
comp_type
!=
CAL_COMP_VAVAILABILITY
)
reject
=
1
;
break
;
#ifdef HAVE_VPOLL
case
ICAL_VPOLL_COMPONENT
:
if
(
cdata
->
comp_type
!=
CAL_COMP_VPOLL
)
reject
=
1
;
break
;
#endif
default
:
break
;
}
/* Don't allow ORGANIZER to be changed */
if
(
!
reject
&&
cdata
->
organizer
)
{
prop
=
icalcomponent_get_first_property
(
comp
,
ICAL_ORGANIZER_PROPERTY
);
if
(
prop
)
{
const
char
*
organizer
=
organizer
=
icalproperty_get_organizer
(
prop
);
if
(
!
strncasecmp
(
organizer
,
"mailto:"
,
7
))
organizer
+=
7
;
if
(
strcmp
(
cdata
->
organizer
,
organizer
))
reject
=
1
;
}
}
if
(
reject
)
{
sched_data
->
status
=
sched_data
->
ischedule
?
REQSTAT_REJECTED
:
SCHEDSTAT_REJECTED
;
goto
done
;
}
}
}
switch
(
method
)
{
case
ICAL_METHOD_CANCEL
:
/* Get component type */
comp
=
icalcomponent_get_first_real_component
(
ical
);
kind
=
icalcomponent_isa
(
comp
);
/* Set STATUS:CANCELLED on all components */
do
{
icalcomponent_set_status
(
comp
,
ICAL_STATUS_CANCELLED
);
icalcomponent_set_sequence
(
comp
,
icalcomponent_get_sequence
(
comp
)
+
1
);
}
while
((
comp
=
icalcomponent_get_next_component
(
ical
,
kind
)));
break
;
case
ICAL_METHOD_REPLY
:
attendee
=
deliver_merge_reply
(
ical
,
sched_data
->
itip
);
break
;
case
ICAL_METHOD_REQUEST
:
deliver_inbox
=
deliver_merge_request
(
recipient
,
ical
,
sched_data
->
itip
);
break
;
case
ICAL_METHOD_POLLSTATUS
:
deliver_inbox
=
deliver_merge_pollstatus
(
ical
,
sched_data
->
itip
);
break
;
default
:
/* Unknown METHOD -- ignore it */
syslog
(
LOG_ERR
,
"Unknown iTIP method: %s"
,
icalenum_method_to_string
(
method
));
sched_data
->
is_reply
=
0
;
goto
inbox
;
}
/* Create header cache */
txn
.
req_hdrs
=
spool_new_hdrcache
();
if
(
!
txn
.
req_hdrs
)
r
=
HTTP_SERVER_ERROR
;
/* Store the (updated) object in the recipients's calendar */
if
(
!
r
)
r
=
caldav_store_resource
(
&
txn
,
ical
,
mailbox
,
buf_cstring
(
&
resource
),
caldavdb
,
NEW_STAG
);
if
(
r
==
HTTP_CREATED
||
r
==
HTTP_NO_CONTENT
)
{
sched_data
->
status
=
sched_data
->
ischedule
?
REQSTAT_SUCCESS
:
SCHEDSTAT_DELIVERED
;
}
else
{
syslog
(
LOG_ERR
,
"caldav_store_resource(%s) failed: %s (%s)"
,
mailbox
->
name
,
error_message
(
r
),
txn
.
error
.
resource
);
sched_data
->
status
=
sched_data
->
ischedule
?
REQSTAT_TEMPFAIL
:
SCHEDSTAT_TEMPFAIL
;
goto
done
;
}
inbox
:
if
(
deliver_inbox
)
{
/* Create a name for the new iTIP message resource */
buf_reset
(
&
resource
);
buf_printf
(
&
resource
,
"%x-%d-%ld-%u.ics"
,
strhash
(
icalcomponent_get_uid
(
sched_data
->
itip
)),
getpid
(),
time
(
0
),
sched_count
++
);
/* Store the message in the recipient's Inbox */
r
=
caldav_store_resource
(
&
txn
,
sched_data
->
itip
,
inbox
,
buf_cstring
(
&
resource
),
caldavdb
,
0
);
/* XXX What do we do if storing to Inbox fails? */
}
/* XXX Should this be a config option? - it might have perf implications */
if
(
sched_data
->
is_reply
)
{
/* Send updates to attendees - skipping sender of reply */
comp
=
icalcomponent_get_first_real_component
(
ical
);
if
(
icalcomponent_isa
(
comp
)
==
ICAL_VPOLL_COMPONENT
)
sched_pollstatus
(
recipient
,
sparam
,
ical
,
attendee
);
else
sched_request
(
userid
,
recipient
,
sparam
,
NULL
,
ical
,
attendee
);
}
done
:
if
(
ical
)
icalcomponent_free
(
ical
);
mailbox_close
(
&
inbox
);
mailbox_close
(
&
mailbox
);
if
(
caldavdb
)
caldav_close
(
caldavdb
);
spool_free_hdrcache
(
txn
.
req_hdrs
);
buf_free
(
&
txn
.
buf
);
}
/* Deliver scheduling object to recipient's Inbox */
void
sched_deliver
(
const
char
*
recipient
,
void
*
data
,
void
*
rock
)
{
struct
sched_data
*
sched_data
=
(
struct
sched_data
*
)
data
;
struct
auth_state
*
authstate
=
(
struct
auth_state
*
)
rock
;
struct
sched_param
sparam
;
int
islegal
;
/* Check SCHEDULE-FORCE-SEND value */
switch
(
sched_data
->
force_send
)
{
case
ICAL_SCHEDULEFORCESEND_NONE
:
islegal
=
1
;
break
;
case
ICAL_SCHEDULEFORCESEND_REPLY
:
islegal
=
sched_data
->
is_reply
;
break
;
case
ICAL_SCHEDULEFORCESEND_REQUEST
:
islegal
=
!
sched_data
->
is_reply
;
break
;
default
:
islegal
=
0
;
break
;
}
if
(
!
islegal
)
{
sched_data
->
status
=
SCHEDSTAT_PARAM
;
return
;
}
if
(
caladdress_lookup
(
recipient
,
&
sparam
,
httpd_userid
))
{
sched_data
->
status
=
sched_data
->
ischedule
?
REQSTAT_NOUSER
:
SCHEDSTAT_NOUSER
;
/* Unknown user */
return
;
}
/* don't schedule to yourself */
if
(
sparam
.
isyou
)
return
;
if
(
sparam
.
flags
)
{
/* Remote recipient */
sched_deliver_remote
(
recipient
,
&
sparam
,
sched_data
);
}
else
{
/* Local recipient */
sched_deliver_local
(
recipient
,
&
sparam
,
sched_data
,
authstate
);
}
}
struct
comp_data
{
icalcomponent
*
comp
;
icalparameter_partstat
partstat
;
int
sequence
;
};
static
void
free_comp_data
(
void
*
data
)
{
struct
comp_data
*
comp_data
=
(
struct
comp_data
*
)
data
;
if
(
comp_data
)
{
if
(
comp_data
->
comp
)
icalcomponent_free
(
comp_data
->
comp
);
free
(
comp_data
);
}
}
/*
* sched_request/reply() helper function
*
* Update DTSTAMP, remove VALARMs,
* optionally remove scheduling params from ORGANIZER
*/
static
void
clean_component
(
icalcomponent
*
comp
,
int
clean_org
)
{
icalcomponent
*
alarm
,
*
next
;
icalproperty
*
prop
;
/* Replace DTSTAMP on component */
prop
=
icalcomponent_get_first_property
(
comp
,
ICAL_DTSTAMP_PROPERTY
);
icalcomponent_remove_property
(
comp
,
prop
);
icalproperty_free
(
prop
);
prop
=
icalproperty_new_dtstamp
(
icaltime_current_time_with_zone
(
utc_zone
));
icalcomponent_add_property
(
comp
,
prop
);
/* Remove any VALARM components */
for
(
alarm
=
icalcomponent_get_first_component
(
comp
,
ICAL_VALARM_COMPONENT
);
alarm
;
alarm
=
next
)
{
next
=
icalcomponent_get_next_component
(
comp
,
ICAL_VALARM_COMPONENT
);
icalcomponent_remove_component
(
comp
,
alarm
);
icalcomponent_free
(
alarm
);
}
if
(
clean_org
)
{
/* Grab the organizer */
prop
=
icalcomponent_get_first_property
(
comp
,
ICAL_ORGANIZER_PROPERTY
);
/* Remove CalDAV Scheduling parameters from organizer */
icalproperty_remove_parameter_by_name
(
prop
,
"SCHEDULE-AGENT"
);
icalproperty_remove_parameter_by_name
(
prop
,
"SCHEDULE-FORCE-SEND"
);
}
}
/*
* sched_request() helper function
*
* Add EXDATE to master component if attendee is excluded from recurrence
*/
struct
exclude_rock
{
unsigned
ncomp
;
icalcomponent
*
comp
;
};
static
void
sched_exclude
(
const
char
*
attendee
__attribute__
((
unused
)),
void
*
data
,
void
*
rock
)
{
struct
sched_data
*
sched_data
=
(
struct
sched_data
*
)
data
;
struct
exclude_rock
*
erock
=
(
struct
exclude_rock
*
)
rock
;
if
(
!
(
sched_data
->
comp_mask
&
(
1
<<
erock
->
ncomp
)))
{
icalproperty
*
recurid
,
*
exdate
;
struct
icaltimetype
exdt
;
icalparameter
*
param
;
/* Fetch the RECURRENCE-ID and use it to create a new EXDATE */
recurid
=
icalcomponent_get_first_property
(
erock
->
comp
,
ICAL_RECURRENCEID_PROPERTY
);
exdt
=
icalproperty_get_recurrenceid
(
recurid
);
exdate
=
icalproperty_new_exdate
(
exdt
);
/* Copy any parameters from RECURRENCE-ID to EXDATE */
param
=
icalproperty_get_first_parameter
(
recurid
,
ICAL_TZID_PARAMETER
);
if
(
param
)
{
icalproperty_add_parameter
(
exdate
,
icalparameter_new_clone
(
param
));
}
param
=
icalproperty_get_first_parameter
(
recurid
,
ICAL_VALUE_PARAMETER
);
if
(
param
)
{
icalproperty_add_parameter
(
exdate
,
icalparameter_new_clone
(
param
));
}
/* XXX Need to handle RANGE parameter */
/* Add the EXDATE to the master component for this attendee */
icalcomponent_add_property
(
sched_data
->
master
,
exdate
);
}
}
/*
* sched_request() helper function
*
* Process all attendees in the given component and add a
* properly modified component to the attendee's iTIP request if necessary
*/
static
void
process_attendees
(
icalcomponent
*
comp
,
unsigned
ncomp
,
const
char
*
organizer
,
const
char
*
att_update
__attribute__
((
unused
)),
// XXX: att_update logic
struct
hash_table
*
att_table
,
icalcomponent
*
itip
,
unsigned
needs_action
,
unsigned
is_changed
,
icalcomponent
*
oldcomp
)
{
icalcomponent
*
copy
;
icalproperty
*
prop
;
icalparameter
*
param
;
int
dummy
=
1
;
struct
hash_table
oldatt_table
;
construct_hash_table
(
&
oldatt_table
,
10
,
1
);
if
(
oldcomp
)
{
for
(
prop
=
icalcomponent_get_first_invitee
(
oldcomp
);
prop
;
prop
=
icalcomponent_get_next_invitee
(
oldcomp
))
{
const
char
*
attendee
=
icalproperty_get_invitee
(
prop
);
/* Don't modify attendee == organizer */
if
(
!
strcasecmp
(
attendee
,
organizer
))
continue
;
/* seen this one before, so we send them updates */
char
*
lat
=
xstrdup
(
attendee
);
lcase
(
lat
);
hash_insert
(
lat
,
&
dummy
,
&
oldatt_table
);
free
(
lat
);
}
}
/* Strip SCHEDULE-STATUS from each attendee
and optionally set PARTSTAT=NEEDS-ACTION */
for
(
prop
=
icalcomponent_get_first_invitee
(
comp
);
prop
;
prop
=
icalcomponent_get_next_invitee
(
comp
))
{
const
char
*
attendee
=
icalproperty_get_invitee
(
prop
);
/* Don't modify attendee == organizer */
if
(
!
strcasecmp
(
attendee
,
organizer
))
continue
;
icalproperty_remove_parameter_by_name
(
prop
,
"SCHEDULE-STATUS"
);
if
(
needs_action
)
{
/* Set PARTSTAT */
param
=
icalparameter_new_partstat
(
ICAL_PARTSTAT_NEEDSACTION
);
icalproperty_set_parameter
(
prop
,
param
);
}
}
/* Clone a working copy of the component */
copy
=
icalcomponent_new_clone
(
comp
);
clean_component
(
copy
,
0
);
/* Process each attendee */
for
(
prop
=
icalcomponent_get_first_invitee
(
copy
);
prop
;
prop
=
icalcomponent_get_next_invitee
(
copy
))
{
unsigned
do_sched
=
1
;
icalparameter_scheduleforcesend
force_send
=
ICAL_SCHEDULEFORCESEND_NONE
;
const
char
*
attendee
=
icalproperty_get_invitee
(
prop
);
char
*
lat
=
xstrdup
(
attendee
);
lcase
(
lat
);
int
is_new
=
!
hash_lookup
(
lat
,
&
oldatt_table
);
free
(
lat
);
/* Don't schedule attendee == organizer */
if
(
!
strcasecmp
(
attendee
,
organizer
))
continue
;
/* don't send an update if there's no change */
if
(
!
is_new
&&
!
is_changed
)
continue
;
/* Check CalDAV Scheduling parameters */
param
=
icalproperty_get_scheduleagent_parameter
(
prop
);
if
(
param
)
{
icalparameter_scheduleagent
agent
=
icalparameter_get_scheduleagent
(
param
);
if
(
agent
!=
ICAL_SCHEDULEAGENT_SERVER
)
do_sched
=
0
;
icalproperty_remove_parameter_by_ref
(
prop
,
param
);
}
param
=
icalproperty_get_scheduleforcesend_parameter
(
prop
);
if
(
param
)
{
force_send
=
icalparameter_get_scheduleforcesend
(
param
);
icalproperty_remove_parameter_by_ref
(
prop
,
param
);
}
/* Create/update iTIP request for this attendee */
if
(
do_sched
)
{
struct
sched_data
*
sched_data
;
icalcomponent
*
new_comp
;
sched_data
=
hash_lookup
(
attendee
,
att_table
);
if
(
!
sched_data
)
{
/* New attendee - add it to the hash table */
sched_data
=
xzmalloc
(
sizeof
(
struct
sched_data
));
sched_data
->
itip
=
icalcomponent_new_clone
(
itip
);
sched_data
->
force_send
=
force_send
;
sched_data
->
is_update
=
!
is_new
;
hash_insert
(
attendee
,
sched_data
,
att_table
);
}
new_comp
=
icalcomponent_new_clone
(
copy
);
icalcomponent_add_component
(
sched_data
->
itip
,
new_comp
);
sched_data
->
comp_mask
|=
(
1
<<
ncomp
);
/* XXX We assume that the master component is always first */
if
(
!
ncomp
)
sched_data
->
master
=
new_comp
;
}
}
free_hash_table
(
&
oldatt_table
,
NULL
);
/* XXX We assume that the master component is always first */
if
(
ncomp
)
{
/* Handle attendees that are excluded from this recurrence */
struct
exclude_rock
erock
=
{
ncomp
,
copy
};
hash_enumerate
(
att_table
,
sched_exclude
,
&
erock
);
}
icalcomponent_free
(
copy
);
}
/*
* sched_request() helper function
*
* Organizer removed this component, mark it as cancelled for all attendees
*/
struct
cancel_rock
{
const
char
*
organizer
;
struct
hash_table
*
att_table
;
icalcomponent
*
itip
;
};
static
void
sched_cancel
(
const
char
*
recurid
__attribute__
((
unused
)),
void
*
data
,
void
*
rock
)
{
struct
comp_data
*
old_data
=
(
struct
comp_data
*
)
data
;
struct
cancel_rock
*
crock
=
(
struct
cancel_rock
*
)
rock
;
/* Deleting the object -- set STATUS to CANCELLED for component */
icalcomponent_set_status
(
old_data
->
comp
,
ICAL_STATUS_CANCELLED
);
icalcomponent_set_sequence
(
old_data
->
comp
,
old_data
->
sequence
+
1
);
process_attendees
(
old_data
->
comp
,
0
,
crock
->
organizer
,
NULL
,
crock
->
att_table
,
crock
->
itip
,
0
,
0
,
NULL
);
}
/*
* Compare the properties of the given kind in two components.
* Returns 0 if equal, 1 otherwise.
*
* If the property exists in neither comp, then they are equal.
* If the property exists in only 1 comp, then they are not equal.
* if the property is RDATE or EXDATE, create an MD5 hash of all
* property strings for each component and compare the hashes.
* Otherwise compare the two property strings.
*/
static
unsigned
propcmp
(
icalcomponent
*
oldical
,
icalcomponent
*
newical
,
icalproperty_kind
kind
)
{
icalproperty
*
oldprop
=
icalcomponent_get_first_property
(
oldical
,
kind
);
icalproperty
*
newprop
=
icalcomponent_get_first_property
(
newical
,
kind
);
if
(
!
oldprop
)
return
(
newprop
!=
NULL
);
else
if
(
!
newprop
)
return
1
;
else
if
((
kind
==
ICAL_RDATE_PROPERTY
)
||
(
kind
==
ICAL_EXDATE_PROPERTY
))
{
MD5_CTX
ctx
;
const
char
*
str
;
unsigned
char
old_md5
[
MD5_DIGEST_LENGTH
],
new_md5
[
MD5_DIGEST_LENGTH
];
MD5Init
(
&
ctx
);
do
{
str
=
icalproperty_get_value_as_string
(
oldprop
);
MD5Update
(
&
ctx
,
str
,
strlen
(
str
));
}
while
((
oldprop
=
icalcomponent_get_next_property
(
oldical
,
kind
)));
MD5Final
(
old_md5
,
&
ctx
);
MD5Init
(
&
ctx
);
do
{
str
=
icalproperty_get_value_as_string
(
newprop
);
MD5Update
(
&
ctx
,
str
,
strlen
(
str
));
}
while
((
newprop
=
icalcomponent_get_next_property
(
newical
,
kind
)));
MD5Final
(
new_md5
,
&
ctx
);
return
(
memcmp
(
old_md5
,
new_md5
,
MD5_DIGEST_LENGTH
)
!=
0
);
}
else
{
return
(
strcmp
(
icalproperty_get_value_as_string
(
oldprop
),
icalproperty_get_value_as_string
(
newprop
))
!=
0
);
}
}
/* Create and deliver an organizer scheduling request */
void
sched_request
(
const
char
*
userid
,
const
char
*
organizer
,
struct
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 */
char
*
outboxname
=
caldav_mboxname
(
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
=
httpd_myrights
(
httpd_authstate
,
mbentry
->
acl
);
}
free
(
outboxname
);
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
,
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
is_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
)
{
is_changed
=
0
;
/* 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
);
}
if
(
needs_action
)
is_changed
=
1
;
else
if
(
propcmp
(
old_data
->
comp
,
comp
,
ICAL_SUMMARY_PROPERTY
))
is_changed
=
1
;
else
if
(
propcmp
(
old_data
->
comp
,
comp
,
ICAL_LOCATION_PROPERTY
))
is_changed
=
1
;
else
if
(
propcmp
(
old_data
->
comp
,
comp
,
ICAL_DESCRIPTION_PROPERTY
))
is_changed
=
1
;
}
/* Process all attendees in created/modified components */
process_attendees
(
comp
,
ncomp
++
,
organizer
,
att_update
,
&
att_table
,
req
,
needs_action
,
is_changed
,
old_data
?
old_data
->
comp
:
NULL
);
free
(
old_data
);
}
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
(
userid
);
hash_enumerate
(
&
att_table
,
sched_deliver
,
authstate
);
auth_freestate
(
authstate
);
done
:
if
(
newical
)
{
unsigned
ncomp
=
0
;
/* Set SCHEDULE-STATUS for each attendee in organizer object */
comp
=
icalcomponent_get_first_real_component
(
newical
);
kind
=
icalcomponent_isa
(
comp
);
do
{
for
(
prop
=
icalcomponent_get_first_invitee
(
comp
);
prop
;
prop
=
icalcomponent_get_next_invitee
(
comp
))
{
const
char
*
stat
=
NULL
;
const
char
*
attendee
=
icalproperty_get_invitee
(
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
);
}
/*
* sched_reply() helper function
*
* Remove all attendees from 'comp' other than the one corresponding to 'userid'
*
* Returns the new trimmed component (must be freed by caller)
* Optionally returns the 'attendee' property, his/her 'propstat',
* and the 'recurid' of the component
*/
static
icalcomponent
*
trim_attendees
(
icalcomponent
*
comp
,
const
char
*
userid
,
icalproperty
**
attendee
,
icalparameter_partstat
*
partstat
,
const
char
**
recurid
)
{
icalcomponent
*
copy
;
icalproperty
*
prop
,
*
nextprop
,
*
myattendee
=
NULL
;
if
(
partstat
)
*
partstat
=
ICAL_PARTSTAT_NONE
;
/* Clone a working copy of the component */
copy
=
icalcomponent_new_clone
(
comp
);
/* Locate userid in the attendee list (stripping others) */
for
(
prop
=
icalcomponent_get_first_invitee
(
copy
);
prop
;
prop
=
nextprop
)
{
const
char
*
att
=
icalproperty_get_invitee
(
prop
);
struct
sched_param
sparam
;
nextprop
=
icalcomponent_get_next_invitee
(
copy
);
if
(
!
myattendee
&&
!
caladdress_lookup
(
att
,
&
sparam
,
httpd_userid
)
&&
!
(
sparam
.
flags
&
SCHEDTYPE_REMOTE
)
&&
!
strcmpsafe
(
sparam
.
userid
,
userid
))
{
/* Found it */
myattendee
=
prop
;
if
(
partstat
)
{
/* Get the PARTSTAT */
icalparameter
*
param
=
icalproperty_get_first_parameter
(
myattendee
,
ICAL_PARTSTAT_PARAMETER
);
if
(
param
)
*
partstat
=
icalparameter_get_partstat
(
param
);
}
}
else
{
/* Some other attendee, remove it */
icalcomponent_remove_invitee
(
copy
,
prop
);
}
sched_param_free
(
&
sparam
);
}
if
(
attendee
)
*
attendee
=
myattendee
;
if
(
recurid
)
{
prop
=
icalcomponent_get_first_property
(
copy
,
ICAL_RECURRENCEID_PROPERTY
);
if
(
prop
)
*
recurid
=
icalproperty_get_value_as_string
(
prop
);
else
*
recurid
=
""
;
}
return
copy
;
}
/*
* sched_reply() helper function
*
* Attendee removed this component, mark it as declined for the organizer.
*/
static
void
sched_decline
(
const
char
*
recurid
__attribute__
((
unused
)),
void
*
data
,
void
*
rock
)
{
struct
comp_data
*
old_data
=
(
struct
comp_data
*
)
data
;
icalcomponent
*
itip
=
(
icalcomponent
*
)
rock
;
icalproperty
*
myattendee
;
icalparameter
*
param
;
/* Don't send a decline for cancelled components */
if
(
icalcomponent_get_status
(
old_data
->
comp
)
==
ICAL_STATUS_CANCELLED
)
return
;
myattendee
=
icalcomponent_get_first_property
(
old_data
->
comp
,
ICAL_ATTENDEE_PROPERTY
);
param
=
icalparameter_new_partstat
(
ICAL_PARTSTAT_DECLINED
);
icalproperty_set_parameter
(
myattendee
,
param
);
clean_component
(
old_data
->
comp
,
1
);
icalcomponent_add_component
(
itip
,
old_data
->
comp
);
}
/* Create and deliver an attendee scheduling reply */
void
sched_reply
(
const
char
*
userid
,
icalcomponent
*
oldical
,
icalcomponent
*
newical
)
{
int
r
,
rights
=
0
;
mbentry_t
*
mbentry
=
NULL
;
char
*
outboxname
;
icalcomponent
*
ical
;
struct
sched_data
*
sched_data
;
struct
auth_state
*
authstate
;
icalcomponent
*
comp
;
icalproperty
*
prop
;
icalparameter
*
param
;
icalcomponent_kind
kind
;
icalparameter_scheduleforcesend
force_send
=
ICAL_SCHEDULEFORCESEND_NONE
;
const
char
*
organizer
,
*
recurid
;
struct
hash_table
comp_table
;
struct
comp_data
*
old_data
;
/* Check what kind of action we are dealing with */
if
(
!
newical
)
{
/* Remove */
ical
=
oldical
;
}
else
{
/* Create / Modify */
ical
=
newical
;
}
/* Check CalDAV Scheduling parameters on the organizer */
comp
=
icalcomponent_get_first_real_component
(
ical
);
kind
=
icalcomponent_isa
(
comp
);
prop
=
icalcomponent_get_first_property
(
comp
,
ICAL_ORGANIZER_PROPERTY
);
organizer
=
icalproperty_get_organizer
(
prop
);
param
=
icalproperty_get_scheduleagent_parameter
(
prop
);
if
(
param
&&
icalparameter_get_scheduleagent
(
param
)
!=
ICAL_SCHEDULEAGENT_SERVER
)
{
/* We are not supposed to send replies to the organizer */
return
;
}
param
=
icalproperty_get_scheduleforcesend_parameter
(
prop
);
if
(
param
)
force_send
=
icalparameter_get_scheduleforcesend
(
param
);
sched_data
=
xzmalloc
(
sizeof
(
struct
sched_data
));
sched_data
->
is_reply
=
1
;
sched_data
->
force_send
=
force_send
;
/* Check ACL of auth'd user on userid's Scheduling Outbox */
outboxname
=
caldav_mboxname
(
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
=
httpd_myrights
(
httpd_authstate
,
mbentry
->
acl
);
}
free
(
outboxname
);
mboxlist_entry_free
(
&
mbentry
);
if
(
!
(
rights
&
DACL_REPLY
))
{
/* DAV:need-privileges */
if
(
newical
)
sched_data
->
status
=
SCHEDSTAT_NOPRIVS
;
syslog
(
LOG_DEBUG
,
"No scheduling send ACL for user %s on Outbox %s"
,
httpd_userid
,
userid
);
goto
done
;
}
/* Create our reply iCal object */
sched_data
->
itip
=
icalcomponent_vanew
(
ICAL_VCALENDAR_COMPONENT
,
icalproperty_new_version
(
"2.0"
),
icalproperty_new_prodid
(
ical_prodid
),
icalproperty_new_method
(
ICAL_METHOD_REPLY
),
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
(
sched_data
->
itip
,
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
(
sched_data
->
itip
,
icalcomponent_new_clone
(
comp
));
}
/* Add each component of old object to hash table for comparison */
construct_hash_table
(
&
comp_table
,
10
,
1
);
if
(
oldical
)
{
comp
=
icalcomponent_get_first_real_component
(
oldical
);
do
{
old_data
=
xzmalloc
(
sizeof
(
struct
comp_data
));
old_data
->
comp
=
trim_attendees
(
comp
,
userid
,
NULL
,
&
old_data
->
partstat
,
&
recurid
);
hash_insert
(
recurid
,
old_data
,
&
comp_table
);
}
while
((
comp
=
icalcomponent_get_next_component
(
oldical
,
kind
)));
}
/* Process each component of new object */
if
(
newical
)
{
unsigned
ncomp
=
0
;
comp
=
icalcomponent_get_first_real_component
(
newical
);
do
{
icalcomponent
*
copy
;
icalproperty
*
myattendee
;
icalparameter_partstat
partstat
;
int
changed
=
1
;
copy
=
trim_attendees
(
comp
,
userid
,
&
myattendee
,
&
partstat
,
&
recurid
);
if
(
myattendee
)
{
/* Found our userid */
old_data
=
hash_del
(
recurid
,
&
comp_table
);
if
(
old_data
)
{
if
(
kind
==
ICAL_VPOLL_COMPONENT
)
{
/* VPOLL replies always override existing votes */
sched_vpoll_reply
(
copy
);
}
else
{
/* XXX Need to check EXDATE */
/* Compare PARTSTAT in the two components */
if
(
old_data
->
partstat
==
partstat
)
{
changed
=
0
;
}
}
free_comp_data
(
old_data
);
}
}
else
{
/* Our user isn't in this component */
/* XXX Can this actually happen? */
changed
=
0
;
}
if
(
changed
)
{
clean_component
(
copy
,
1
);
icalcomponent_add_component
(
sched_data
->
itip
,
copy
);
sched_data
->
comp_mask
|=
(
1
<<
ncomp
);
}
else
icalcomponent_free
(
copy
);
ncomp
++
;
}
while
((
comp
=
icalcomponent_get_next_component
(
newical
,
kind
)));
}
/* Decline any components that have been left behind in the old obj */
hash_enumerate
(
&
comp_table
,
sched_decline
,
sched_data
->
itip
);
free_hash_table
(
&
comp_table
,
free_comp_data
);
done
:
if
(
sched_data
->
itip
&&
icalcomponent_get_first_real_component
(
sched_data
->
itip
))
{
/* We built a reply object */
if
(
!
sched_data
->
status
)
{
/* Attempt to deliver reply to organizer */
authstate
=
auth_newstate
(
userid
);
sched_deliver
(
organizer
,
sched_data
,
authstate
);
auth_freestate
(
authstate
);
}
if
(
newical
)
{
unsigned
ncomp
=
0
;
/* Set SCHEDULE-STATUS for organizer in attendee object */
comp
=
icalcomponent_get_first_real_component
(
newical
);
do
{
if
(
sched_data
->
comp_mask
&
(
1
<<
ncomp
))
{
prop
=
icalcomponent_get_first_property
(
comp
,
ICAL_ORGANIZER_PROPERTY
);
param
=
icalparameter_new_schedulestatus
(
sched_data
->
status
);
icalproperty_add_parameter
(
prop
,
param
);
}
ncomp
++
;
}
while
((
comp
=
icalcomponent_get_next_component
(
newical
,
kind
)));
}
}
/* Cleanup */
free_sched_data
(
sched_data
);
}
void
sched_param_free
(
struct
sched_param
*
sparam
)
{
if
(
sparam
->
userid
)
free
(
sparam
->
userid
);
if
(
sparam
->
server
)
free
(
sparam
->
server
);
if
(
sparam
->
props
)
{
free_sched_param_props
(
sparam
->
props
);
}
memset
(
sparam
,
0
,
sizeof
(
struct
sched_param
));
}
File Metadata
Details
Attached
Mime Type
text/x-c
Expires
Sun, Apr 5, 11:32 PM (1 w, 5 d ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
18831582
Default Alt Text
http_caldav_sched.c (81 KB)
Attached To
Mode
R111 cyrus-imapd
Attached
Detach File
Event Timeline