Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F117885905
http_dav.c
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Authored By
Unknown
Size
183 KB
Referenced Files
None
Subscribers
None
http_dav.c
View Options
/* http_dav.c -- Routines for dealing with DAV properties in httpd
*
* Copyright (c) 1994-2011 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.
*
*/
/*
* TODO:
*
* - CALDAV:supported-calendar-component-set should be a bitmask in
* cyrus.index header Mailbox Options field
*
* - CALDAV:schedule-calendar-transp should be a flag in
* cyrus.index header (Mailbox Options)
*
* - DAV:creationdate sould be added to cyrus.header since it only
* gets set at creation time
*
* - Should add a last_metadata_update field to cyrus.index header
* for use in PROPFIND, PROPPATCH, and possibly REPORT.
* This would get updated any time a mailbox annotation, mailbox
* acl, or quota root limit is changed
*
* - Should we use cyrus.index header Format field to indicate
* CalDAV mailbox?
*
*/
#include
"http_dav.h"
#include
"annotate.h"
#include
"acl.h"
#include
"append.h"
#include
"caldav_db.h"
#include
"global.h"
#include
"http_err.h"
#include
"http_proxy.h"
#include
"imap_err.h"
#include
"index.h"
#include
"proxy.h"
#include
"times.h"
#include
"syslog.h"
#include
"strhash.h"
#include
"tok.h"
#include
"xmalloc.h"
#include
"xstrlcat.h"
#include
"xstrlcpy.h"
#include
<libxml/uri.h>
#define SYNC_TOKEN_URL_SCHEME "data:,"
static
const
struct
dav_namespace_t
{
const
char
*
href
;
const
char
*
prefix
;
}
known_namespaces
[]
=
{
{
XML_NS_DAV
,
"D"
},
{
XML_NS_CALDAV
,
"C"
},
{
XML_NS_CARDDAV
,
"C"
},
{
XML_NS_ISCHED
,
NULL
},
{
XML_NS_CS
,
"CS"
},
{
XML_NS_CYRUS
,
"CY"
},
{
XML_NS_USERFLAG
,
"UF"
},
{
XML_NS_SYSFLAG
,
"SF"
},
};
/* PROPFIND modes */
enum
{
PROPFIND_NONE
=
0
,
/* only used with REPORT */
PROPFIND_ALL
,
PROPFIND_NAME
,
PROPFIND_PROP
,
PROPFIND_EXPAND
/* only used with expand-prop REPORT */
};
static
int
principal_parse_path
(
const
char
*
path
,
struct
request_target_t
*
tgt
,
const
char
**
errstr
);
static
int
propfind_displayname
(
const
xmlChar
*
name
,
xmlNsPtr
ns
,
struct
propfind_ctx
*
fctx
,
xmlNodePtr
resp
,
struct
propstat
propstat
[],
void
*
rock
);
static
int
propfind_restype
(
const
xmlChar
*
name
,
xmlNsPtr
ns
,
struct
propfind_ctx
*
fctx
,
xmlNodePtr
resp
,
struct
propstat
propstat
[],
void
*
rock
);
static
int
propfind_alturiset
(
const
xmlChar
*
name
,
xmlNsPtr
ns
,
struct
propfind_ctx
*
fctx
,
xmlNodePtr
resp
,
struct
propstat
propstat
[],
void
*
rock
);
static
int
propfind_principalurl
(
const
xmlChar
*
name
,
xmlNsPtr
ns
,
struct
propfind_ctx
*
fctx
,
xmlNodePtr
resp
,
struct
propstat
propstat
[],
void
*
rock
);
static
int
report_prin_prop_search
(
struct
transaction_t
*
txn
,
struct
meth_params
*
rparams
,
xmlNodePtr
inroot
,
struct
propfind_ctx
*
fctx
);
static
int
report_prin_search_prop_set
(
struct
transaction_t
*
txn
,
struct
meth_params
*
rparams
,
xmlNodePtr
inroot
,
struct
propfind_ctx
*
fctx
);
static
int
allprop_cb
(
const
char
*
mailbox
__attribute__
((
unused
)),
uint32_t
uid
__attribute__
((
unused
)),
const
char
*
entry
,
const
char
*
userid
,
const
struct
buf
*
attrib
,
void
*
rock
);
/* Array of supported REPORTs */
static
const
struct
report_type_t
principal_reports
[]
=
{
/* WebDAV Versioning (RFC 3253) REPORTs */
{
"expand-property"
,
NS_DAV
,
"multistatus"
,
&
report_expand_prop
,
DACL_READ
,
0
},
/* WebDAV ACL (RFC 3744) REPORTs */
{
"principal-property-search"
,
NS_DAV
,
"multistatus"
,
&
report_prin_prop_search
,
0
,
REPORT_ALLOW_PROPS
|
REPORT_DEPTH_ZERO
},
{
"principal-search-property-set"
,
NS_DAV
,
"principal-search-property-set"
,
&
report_prin_search_prop_set
,
0
,
REPORT_DEPTH_ZERO
},
{
NULL
,
0
,
NULL
,
NULL
,
0
,
0
}
};
/* Array of known "live" properties */
static
const
struct
prop_entry
principal_props
[]
=
{
/* WebDAV (RFC 4918) properties */
{
"creationdate"
,
NS_DAV
,
PROP_ALLPROP
,
NULL
,
NULL
,
NULL
},
{
"displayname"
,
NS_DAV
,
PROP_ALLPROP
|
PROP_COLLECTION
,
propfind_displayname
,
NULL
,
NULL
},
{
"getcontentlanguage"
,
NS_DAV
,
PROP_ALLPROP
,
NULL
,
NULL
,
NULL
},
{
"getcontentlength"
,
NS_DAV
,
PROP_ALLPROP
|
PROP_COLLECTION
,
propfind_getlength
,
NULL
,
NULL
},
{
"getcontenttype"
,
NS_DAV
,
PROP_ALLPROP
,
NULL
,
NULL
,
NULL
},
{
"getetag"
,
NS_DAV
,
PROP_ALLPROP
,
NULL
,
NULL
,
NULL
},
{
"getlastmodified"
,
NS_DAV
,
PROP_ALLPROP
,
NULL
,
NULL
,
NULL
},
{
"lockdiscovery"
,
NS_DAV
,
PROP_ALLPROP
|
PROP_COLLECTION
,
propfind_lockdisc
,
NULL
,
NULL
},
{
"resourcetype"
,
NS_DAV
,
PROP_ALLPROP
|
PROP_COLLECTION
,
propfind_restype
,
NULL
,
NULL
},
{
"supportedlock"
,
NS_DAV
,
PROP_ALLPROP
|
PROP_COLLECTION
,
propfind_suplock
,
NULL
,
NULL
},
/* WebDAV Versioning (RFC 3253) properties */
{
"supported-report-set"
,
NS_DAV
,
PROP_COLLECTION
,
propfind_reportset
,
NULL
,
(
void
*
)
principal_reports
},
/* WebDAV ACL (RFC 3744) properties */
{
"alternate-URI-set"
,
NS_DAV
,
PROP_COLLECTION
,
propfind_alturiset
,
NULL
,
NULL
},
{
"principal-URL"
,
NS_DAV
,
PROP_COLLECTION
|
PROP_EXPAND
,
propfind_principalurl
,
NULL
,
NULL
},
{
"group-member-set"
,
NS_DAV
,
0
,
NULL
,
NULL
,
NULL
},
{
"group-membership"
,
NS_DAV
,
0
,
NULL
,
NULL
,
NULL
},
{
"principal-collection-set"
,
NS_DAV
,
PROP_COLLECTION
,
propfind_princolset
,
NULL
,
NULL
},
/* WebDAV Current Principal (RFC 5397) properties */
{
"current-user-principal"
,
NS_DAV
,
PROP_COLLECTION
|
PROP_EXPAND
,
propfind_curprin
,
NULL
,
NULL
},
/* CalDAV (RFC 4791) properties */
{
"calendar-home-set"
,
NS_CALDAV
,
PROP_COLLECTION
|
PROP_EXPAND
,
propfind_calhome
,
NULL
,
NULL
},
/* CalDAV Scheduling (RFC 6638) properties */
{
"schedule-inbox-URL"
,
NS_CALDAV
,
PROP_COLLECTION
|
PROP_EXPAND
,
propfind_schedinbox
,
NULL
,
NULL
},
{
"schedule-outbox-URL"
,
NS_CALDAV
,
PROP_COLLECTION
|
PROP_EXPAND
,
propfind_schedoutbox
,
NULL
,
NULL
},
{
"calendar-user-address-set"
,
NS_CALDAV
,
PROP_COLLECTION
,
propfind_caluseraddr
,
NULL
,
NULL
},
{
"calendar-user-type"
,
NS_CALDAV
,
PROP_COLLECTION
,
propfind_calusertype
,
NULL
,
NULL
},
/* CardDAV (RFC 6352) properties */
{
"addressbook-home-set"
,
NS_CARDDAV
,
PROP_COLLECTION
|
PROP_EXPAND
,
propfind_abookhome
,
NULL
,
NULL
},
/* Apple Calendar Server properties */
{
"getctag"
,
NS_CS
,
PROP_ALLPROP
,
NULL
,
NULL
,
NULL
},
{
NULL
,
0
,
0
,
NULL
,
NULL
,
NULL
}
};
static
struct
meth_params
princ_params
=
{
.
parse_path
=
&
principal_parse_path
,
.
get
=
NULL
,
.
lprops
=
principal_props
,
.
reports
=
principal_reports
};
/* Namespace for WebDAV principals */
struct
namespace_t
namespace_principal
=
{
URL_NS_PRINCIPAL
,
0
,
"/dav/principals"
,
NULL
,
1
/* auth */
,
/*mbtype */
0
,
ALLOW_READ
|
ALLOW_DAV
,
NULL
,
NULL
,
NULL
,
NULL
,
{
{
NULL
,
NULL
},
/* ACL */
{
NULL
,
NULL
},
/* COPY */
{
NULL
,
NULL
},
/* DELETE */
{
&
meth_get_dav
,
&
princ_params
},
/* GET */
{
&
meth_get_dav
,
&
princ_params
},
/* HEAD */
{
NULL
,
NULL
},
/* LOCK */
{
NULL
,
NULL
},
/* MKCALENDAR */
{
NULL
,
NULL
},
/* MKCOL */
{
NULL
,
NULL
},
/* MOVE */
{
&
meth_options
,
NULL
},
/* OPTIONS */
{
NULL
,
NULL
},
/* POST */
{
&
meth_propfind
,
&
princ_params
},
/* PROPFIND */
{
NULL
,
NULL
},
/* PROPPATCH */
{
NULL
,
NULL
},
/* PUT */
{
&
meth_report
,
&
princ_params
},
/* REPORT */
{
&
meth_trace
,
NULL
},
/* TRACE */
{
NULL
,
NULL
}
/* UNLOCK */
}
};
/* Linked-list of properties for fetching */
struct
propfind_entry_list
{
xmlChar
*
name
;
/* Property name (needs to be freed) */
xmlNsPtr
ns
;
/* Property namespace */
unsigned
char
flags
;
/* Flags for how/where prop apply */
int
(
*
get
)(
const
xmlChar
*
name
,
/* Callback to fetch property */
xmlNsPtr
ns
,
struct
propfind_ctx
*
fctx
,
xmlNodePtr
resp
,
struct
propstat
propstat
[],
void
*
rock
);
void
*
rock
;
/* Add'l data to pass to callback */
struct
propfind_entry_list
*
next
;
};
/* Bitmask of privilege flags */
enum
{
PRIV_IMPLICIT
=
(
1
<<
0
),
PRIV_INBOX
=
(
1
<<
1
),
PRIV_OUTBOX
=
(
1
<<
2
)
};
/* Array of precondition/postcondition errors */
static
const
struct
precond_t
{
const
char
*
name
;
/* Property name */
unsigned
ns
;
/* Index into known namespace array */
}
preconds
[]
=
{
/* Placeholder for zero (no) precondition code */
{
NULL
,
0
},
/* WebDAV (RFC 4918) preconditons */
{
"cannot-modify-protected-property"
,
NS_DAV
},
{
"lock-token-matches-request-uri"
,
NS_DAV
},
{
"lock-token-submitted"
,
NS_DAV
},
{
"no-conflicting-lock"
,
NS_DAV
},
/* WebDAV Versioning (RFC 3253) preconditions */
{
"supported-report"
,
NS_DAV
},
{
"resource-must-be-null"
,
NS_DAV
},
/* WebDAV ACL (RFC 3744) preconditions */
{
"need-privileges"
,
NS_DAV
},
{
"no-invert"
,
NS_DAV
},
{
"no-abstract"
,
NS_DAV
},
{
"not-supported-privilege"
,
NS_DAV
},
{
"recognized-principal"
,
NS_DAV
},
{
"allowed-principal"
,
NS_DAV
},
{
"grant-only"
,
NS_DAV
},
/* WebDAV Quota (RFC 4331) preconditions */
{
"quota-not-exceeded"
,
NS_DAV
},
{
"sufficient-disk-space"
,
NS_DAV
},
/* WebDAV Extended MKCOL (RFC 5689) preconditions */
{
"valid-resourcetype"
,
NS_DAV
},
/* WebDAV Sync (RFC 6578) preconditions */
{
"valid-sync-token"
,
NS_DAV
},
{
"number-of-matches-within-limits"
,
NS_DAV
},
/* CalDAV (RFC 4791) preconditions */
{
"supported-calendar-data"
,
NS_CALDAV
},
{
"valid-calendar-data"
,
NS_CALDAV
},
{
"valid-calendar-object-resource"
,
NS_CALDAV
},
{
"supported-calendar-component"
,
NS_CALDAV
},
{
"calendar-collection-location-ok"
,
NS_CALDAV
},
{
"no-uid-conflict"
,
NS_CALDAV
},
{
"supported-filter"
,
NS_CALDAV
},
{
"valid-filter"
,
NS_CALDAV
},
/* RSCALE (draft-ietf-calext-rscale) preconditions */
{
"supported-rscale"
,
NS_CALDAV
},
/* TZ by Ref (draft-ietf-tzdist-caldav-timezone-ref) preconditions */
{
"valid-timezone"
,
NS_CALDAV
},
/* Managed Attachments (draft-daboo-caldav-attachments) preconditions */
{
"valid-managed-id"
,
NS_CALDAV
},
/* CalDAV Scheduling (RFC 6638) preconditions */
{
"valid-scheduling-message"
,
NS_CALDAV
},
{
"valid-organizer"
,
NS_CALDAV
},
{
"unique-scheduling-object-resource"
,
NS_CALDAV
},
{
"same-organizer-in-all-components"
,
NS_CALDAV
},
{
"allowed-organizer-scheduling-object-change"
,
NS_CALDAV
},
{
"allowed-attendee-scheduling-object-change"
,
NS_CALDAV
},
/* iSchedule (draft-desruisseaux-ischedule) preconditions */
{
"version-not-supported"
,
NS_ISCHED
},
{
"invalid-calendar-data-type"
,
NS_ISCHED
},
{
"invalid-calendar-data"
,
NS_ISCHED
},
{
"invalid-scheduling-message"
,
NS_ISCHED
},
{
"originator-missing"
,
NS_ISCHED
},
{
"too-many-originators"
,
NS_ISCHED
},
{
"originator-invalid"
,
NS_ISCHED
},
{
"originator-denied"
,
NS_ISCHED
},
{
"recipient-missing"
,
NS_ISCHED
},
{
"recipient-mismatch"
,
NS_ISCHED
},
{
"verification-failed"
,
NS_ISCHED
},
/* CardDAV (RFC 6352) preconditions */
{
"supported-address-data"
,
NS_CARDDAV
},
{
"valid-address-data"
,
NS_CARDDAV
},
{
"no-uid-conflict"
,
NS_CARDDAV
},
{
"addressbook-collection-location-ok"
,
NS_CARDDAV
},
{
"supported-filter"
,
NS_CARDDAV
}
};
/* Parse request-target path in DAV principals namespace */
static
int
principal_parse_path
(
const
char
*
path
,
struct
request_target_t
*
tgt
,
const
char
**
errstr
)
{
char
*
p
;
size_t
len
;
/* Make a working copy of target path */
strlcpy
(
tgt
->
path
,
path
,
sizeof
(
tgt
->
path
));
tgt
->
tail
=
tgt
->
path
+
strlen
(
tgt
->
path
);
p
=
tgt
->
path
;
/* Sanity check namespace */
len
=
strlen
(
namespace_principal
.
prefix
);
if
(
strlen
(
p
)
<
len
||
strncmp
(
namespace_principal
.
prefix
,
p
,
len
)
||
(
path
[
len
]
&&
path
[
len
]
!=
'/'
))
{
*
errstr
=
"Namespace mismatch request target path"
;
return
HTTP_FORBIDDEN
;
}
tgt
->
prefix
=
namespace_principal
.
prefix
;
/* Skip namespace */
p
+=
len
;
if
(
!*
p
||
!*++
p
)
return
0
;
/* Check if we're in user space */
len
=
strcspn
(
p
,
"/"
);
if
(
!
strncmp
(
p
,
"user"
,
len
))
{
p
+=
len
;
if
(
!*
p
||
!*++
p
)
return
0
;
/* Get user id */
len
=
strcspn
(
p
,
"/"
);
tgt
->
userid
=
xstrndup
(
p
,
len
);
p
+=
len
;
if
(
!*
p
||
!*++
p
)
return
0
;
}
else
return
HTTP_NOT_FOUND
;
/* need to specify a userid */
if
(
*
p
)
{
// *errstr = "Too many segments in request target path";
return
HTTP_NOT_FOUND
;
}
return
0
;
}
/* Evaluate If header. Note that we can't short-circuit any of the tests
because we need to check for a lock-token anywhere in the header */
static
int
eval_if
(
const
char
*
hdr
,
const
char
*
etag
,
const
char
*
lock_token
,
unsigned
*
locked
)
{
unsigned
ret
=
0
;
tok_t
tok_l
;
char
*
list
;
/* Process each list, ORing the results */
tok_init
(
&
tok_l
,
hdr
,
")"
,
TOK_TRIMLEFT
|
TOK_TRIMRIGHT
);
while
((
list
=
tok_next
(
&
tok_l
)))
{
unsigned
ret_l
=
1
;
tok_t
tok_c
;
char
*
cond
;
/* XXX Need to handle Resource-Tag for Tagged-list (COPY/MOVE dest) */
/* Process each condition, ANDing the results */
tok_initm
(
&
tok_c
,
list
+
1
,
"]>"
,
TOK_TRIMLEFT
|
TOK_TRIMRIGHT
);
while
((
cond
=
tok_next
(
&
tok_c
)))
{
unsigned
r
,
not
=
0
;
if
(
!
strncmp
(
cond
,
"Not"
,
3
))
{
not
=
1
;
cond
+=
3
;
while
(
*
cond
==
' '
)
cond
++
;
}
if
(
*
cond
==
'['
)
{
/* ETag */
r
=
!
etagcmp
(
cond
+
1
,
etag
);
}
else
{
/* State Token */
if
(
!
lock_token
)
r
=
0
;
else
{
r
=
!
strcmp
(
cond
+
1
,
lock_token
);
if
(
r
)
{
/* Correct lock-token has been provided */
*
locked
=
0
;
}
}
}
ret_l
&=
(
not
?
!
r
:
r
);
}
tok_fini
(
&
tok_c
);
ret
|=
ret_l
;
}
tok_fini
(
&
tok_l
);
return
(
ret
||
locked
);
}
/* Check headers for any preconditions */
int
dav_check_precond
(
struct
transaction_t
*
txn
,
const
void
*
data
,
const
char
*
etag
,
time_t
lastmod
)
{
const
struct
dav_data
*
ddata
=
(
const
struct
dav_data
*
)
data
;
hdrcache_t
hdrcache
=
txn
->
req_hdrs
;
const
char
**
hdr
;
const
char
*
lock_token
=
NULL
;
unsigned
locked
=
0
;
/* Check for a write-lock on the source */
if
(
ddata
&&
ddata
->
lock_expire
>
time
(
NULL
))
{
lock_token
=
ddata
->
lock_token
;
switch
(
txn
->
meth
)
{
case
METH_DELETE
:
case
METH_LOCK
:
case
METH_MOVE
:
case
METH_POST
:
case
METH_PUT
:
/* State-changing method: Only the lock owner can execute
and MUST provide the correct lock-token in an If header */
if
(
strcmp
(
ddata
->
lock_ownerid
,
httpd_userid
))
return
HTTP_LOCKED
;
locked
=
1
;
break
;
case
METH_UNLOCK
:
/* State-changing method: Authorized in meth_unlock() */
break
;
case
METH_ACL
:
case
METH_MKCALENDAR
:
case
METH_MKCOL
:
case
METH_PROPPATCH
:
/* State-changing method: Locks on collections unsupported */
break
;
default
:
/* Non-state-changing method: Always allowed */
break
;
}
}
/* Per RFC 4918, If is similar to If-Match, but with lock-token submission.
Per RFC 7232, LOCK errors supercede preconditions */
if
((
hdr
=
spool_getheader
(
hdrcache
,
"If"
)))
{
/* State tokens (sync-token, lock-token) and Etags */
if
(
!
eval_if
(
hdr
[
0
],
etag
,
lock_token
,
&
locked
))
return
HTTP_PRECOND_FAILED
;
}
if
(
locked
)
{
/* Correct lock-token was not provided in If header */
return
HTTP_LOCKED
;
}
/* Do normal HTTP checks */
return
check_precond
(
txn
,
etag
,
lastmod
);
}
unsigned
get_preferences
(
struct
transaction_t
*
txn
)
{
unsigned
mask
=
0
,
prefs
=
0
;
const
char
**
hdr
;
/* Create a mask for preferences honored by method */
switch
(
txn
->
meth
)
{
case
METH_COPY
:
case
METH_MOVE
:
case
METH_POST
:
case
METH_PUT
:
mask
=
PREFER_REP
;
break
;
case
METH_MKCALENDAR
:
case
METH_MKCOL
:
case
METH_PROPPATCH
:
mask
=
PREFER_MIN
;
break
;
case
METH_PROPFIND
:
case
METH_REPORT
:
mask
=
(
PREFER_MIN
|
PREFER_NOROOT
);
break
;
}
if
(
!
mask
)
return
0
;
else
{
txn
->
flags
.
vary
|=
VARY_PREFER
;
if
(
mask
&
PREFER_MIN
)
txn
->
flags
.
vary
|=
VARY_BRIEF
;
}
/* Check for Prefer header(s) */
if
((
hdr
=
spool_getheader
(
txn
->
req_hdrs
,
"Prefer"
)))
{
int
i
;
for
(
i
=
0
;
hdr
[
i
];
i
++
)
{
tok_t
tok
;
char
*
token
;
tok_init
(
&
tok
,
hdr
[
i
],
",
\r\n
"
,
TOK_TRIMLEFT
|
TOK_TRIMRIGHT
);
while
((
token
=
tok_next
(
&
tok
)))
{
if
((
mask
&
PREFER_MIN
)
&&
!
strcmp
(
token
,
"return=minimal"
))
prefs
|=
PREFER_MIN
;
else
if
((
mask
&
PREFER_REP
)
&&
!
strcmp
(
token
,
"return=representation"
))
prefs
|=
PREFER_REP
;
else
if
((
mask
&
PREFER_NOROOT
)
&&
!
strcmp
(
token
,
"depth-noroot"
))
prefs
|=
PREFER_NOROOT
;
}
tok_fini
(
&
tok
);
}
txn
->
resp_body
.
prefs
=
prefs
;
}
/* Check for Brief header */
if
((
mask
&
PREFER_MIN
)
&&
(
hdr
=
spool_getheader
(
txn
->
req_hdrs
,
"Brief"
))
&&
!
strcasecmp
(
hdr
[
0
],
"t"
))
{
prefs
|=
PREFER_MIN
;
}
return
prefs
;
}
/* Check requested MIME type */
struct
mime_type_t
*
get_accept_type
(
const
char
**
hdr
,
struct
mime_type_t
*
types
)
{
struct
mime_type_t
*
ret
=
NULL
;
struct
accept
*
e
,
*
enc
=
parse_accept
(
hdr
);
for
(
e
=
enc
;
e
&&
e
->
token
;
e
++
)
{
if
(
!
ret
&&
e
->
qual
>
0.0
)
{
struct
mime_type_t
*
m
;
for
(
m
=
types
;
!
ret
&&
m
->
content_type
;
m
++
)
{
if
(
is_mediatype
(
e
->
token
,
m
->
content_type
))
ret
=
m
;
}
}
free
(
e
->
token
);
}
if
(
enc
)
free
(
enc
);
return
ret
;
}
static
int
add_privs
(
int
rights
,
unsigned
flags
,
xmlNodePtr
parent
,
xmlNodePtr
root
,
xmlNsPtr
*
ns
);
/* Ensure that we have a given namespace. If it doesn't exist in what we
* parsed in the request, create it and attach to 'node'.
*/
int
ensure_ns
(
xmlNsPtr
*
respNs
,
int
ns
,
xmlNodePtr
node
,
const
char
*
url
,
const
char
*
prefix
)
{
if
(
!
respNs
[
ns
])
{
xmlNsPtr
nsDef
;
char
myprefix
[
20
];
/* Search for existing namespace using our prefix */
for
(
nsDef
=
node
->
nsDef
;
nsDef
;
nsDef
=
nsDef
->
next
)
{
if
((
!
nsDef
->
prefix
&&
!
prefix
)
||
(
nsDef
->
prefix
&&
prefix
&&
!
strcmp
((
const
char
*
)
nsDef
->
prefix
,
prefix
)))
break
;
}
if
(
nsDef
)
{
/* Prefix is already used - generate a new one */
snprintf
(
myprefix
,
sizeof
(
myprefix
),
"X%X"
,
strhash
(
url
)
&
0xffff
);
prefix
=
myprefix
;
}
respNs
[
ns
]
=
xmlNewNs
(
node
,
BAD_CAST
url
,
BAD_CAST
prefix
);
}
/* XXX check for errors */
return
0
;
}
/* Add namespaces declared in the request to our root node and Ns array */
static
int
xml_add_ns
(
xmlNodePtr
req
,
xmlNsPtr
*
respNs
,
xmlNodePtr
root
)
{
for
(;
req
;
req
=
req
->
next
)
{
if
(
req
->
type
==
XML_ELEMENT_NODE
)
{
xmlNsPtr
nsDef
;
for
(
nsDef
=
req
->
nsDef
;
nsDef
;
nsDef
=
nsDef
->
next
)
{
if
(
!
xmlStrcmp
(
nsDef
->
href
,
BAD_CAST
XML_NS_DAV
))
ensure_ns
(
respNs
,
NS_DAV
,
root
,
(
const
char
*
)
nsDef
->
href
,
(
const
char
*
)
nsDef
->
prefix
);
else
if
(
!
xmlStrcmp
(
nsDef
->
href
,
BAD_CAST
XML_NS_CALDAV
))
ensure_ns
(
respNs
,
NS_CALDAV
,
root
,
(
const
char
*
)
nsDef
->
href
,
(
const
char
*
)
nsDef
->
prefix
);
else
if
(
!
xmlStrcmp
(
nsDef
->
href
,
BAD_CAST
XML_NS_CARDDAV
))
ensure_ns
(
respNs
,
NS_CARDDAV
,
root
,
(
const
char
*
)
nsDef
->
href
,
(
const
char
*
)
nsDef
->
prefix
);
else
if
(
!
xmlStrcmp
(
nsDef
->
href
,
BAD_CAST
XML_NS_CS
))
ensure_ns
(
respNs
,
NS_CS
,
root
,
(
const
char
*
)
nsDef
->
href
,
(
const
char
*
)
nsDef
->
prefix
);
else
if
(
!
xmlStrcmp
(
nsDef
->
href
,
BAD_CAST
XML_NS_CYRUS
))
ensure_ns
(
respNs
,
NS_CYRUS
,
root
,
(
const
char
*
)
nsDef
->
href
,
(
const
char
*
)
nsDef
->
prefix
);
else
xmlNewNs
(
root
,
nsDef
->
href
,
nsDef
->
prefix
);
}
}
xml_add_ns
(
req
->
children
,
respNs
,
root
);
}
/* XXX check for errors */
return
0
;
}
/* Initialize an XML tree for a property response */
xmlNodePtr
init_xml_response
(
const
char
*
resp
,
int
ns
,
xmlNodePtr
req
,
xmlNsPtr
*
respNs
)
{
/* Start construction of our XML response tree */
xmlDocPtr
doc
=
xmlNewDoc
(
BAD_CAST
"1.0"
);
xmlNodePtr
root
=
NULL
;
if
(
!
doc
)
return
NULL
;
if
(
!
(
root
=
xmlNewNode
(
NULL
,
BAD_CAST
resp
)))
return
NULL
;
xmlDocSetRootElement
(
doc
,
root
);
/* Add namespaces from request to our response,
* creating array of known namespaces that we can reference later.
*/
memset
(
respNs
,
0
,
NUM_NAMESPACE
*
sizeof
(
xmlNsPtr
));
xml_add_ns
(
req
,
respNs
,
root
);
/* Set namespace of root node */
ensure_ns
(
respNs
,
ns
,
root
,
known_namespaces
[
ns
].
href
,
known_namespaces
[
ns
].
prefix
);
xmlSetNs
(
root
,
respNs
[
ns
]);
return
root
;
}
xmlNodePtr
xml_add_href
(
xmlNodePtr
parent
,
xmlNsPtr
ns
,
const
char
*
href
)
{
xmlChar
*
uri
=
xmlURIEscapeStr
(
BAD_CAST
href
,
BAD_CAST
":/?="
);
xmlNodePtr
node
=
xmlNewChild
(
parent
,
ns
,
BAD_CAST
"href"
,
uri
);
free
(
uri
);
return
node
;
}
xmlNodePtr
xml_add_error
(
xmlNodePtr
root
,
struct
error_t
*
err
,
xmlNsPtr
*
avail_ns
)
{
xmlNsPtr
ns
[
NUM_NAMESPACE
];
xmlNodePtr
error
,
node
;
const
struct
precond_t
*
precond
=
&
preconds
[
err
->
precond
];
unsigned
err_ns
=
NS_DAV
;
const
char
*
resp_desc
=
"responsedescription"
;
if
(
precond
->
ns
==
NS_ISCHED
)
{
err_ns
=
NS_ISCHED
;
resp_desc
=
"response-description"
;
}
if
(
!
root
)
{
error
=
root
=
init_xml_response
(
"error"
,
err_ns
,
NULL
,
ns
);
avail_ns
=
ns
;
}
else
error
=
xmlNewChild
(
root
,
NULL
,
BAD_CAST
"error"
,
NULL
);
ensure_ns
(
avail_ns
,
precond
->
ns
,
root
,
known_namespaces
[
precond
->
ns
].
href
,
known_namespaces
[
precond
->
ns
].
prefix
);
node
=
xmlNewChild
(
error
,
avail_ns
[
precond
->
ns
],
BAD_CAST
precond
->
name
,
NULL
);
switch
(
err
->
precond
)
{
case
DAV_NEED_PRIVS
:
if
(
err
->
resource
&&
err
->
rights
)
{
unsigned
flags
=
0
;
size_t
rlen
=
strlen
(
err
->
resource
);
const
char
*
p
=
err
->
resource
+
rlen
;
node
=
xmlNewChild
(
node
,
NULL
,
BAD_CAST
"resource"
,
NULL
);
xml_add_href
(
node
,
NULL
,
err
->
resource
);
if
(
rlen
>
6
&&
!
strcmp
(
p
-6
,
SCHED_INBOX
))
flags
=
PRIV_INBOX
;
else
if
(
rlen
>
7
&&
!
strcmp
(
p
-7
,
SCHED_OUTBOX
))
flags
=
PRIV_OUTBOX
;
add_privs
(
err
->
rights
,
flags
,
node
,
root
,
avail_ns
);
}
break
;
default
:
if
(
err
->
resource
)
xml_add_href
(
node
,
avail_ns
[
NS_DAV
],
err
->
resource
);
break
;
}
if
(
err
->
desc
)
{
xmlNewTextChild
(
error
,
NULL
,
BAD_CAST
resp_desc
,
BAD_CAST
err
->
desc
);
}
return
root
;
}
void
xml_add_lockdisc
(
xmlNodePtr
node
,
const
char
*
root
,
struct
dav_data
*
data
)
{
time_t
now
=
time
(
NULL
);
if
(
data
->
lock_expire
>
now
)
{
xmlNodePtr
active
,
node1
;
char
tbuf
[
30
];
/* "Second-" + long int + NUL */
active
=
xmlNewChild
(
node
,
NULL
,
BAD_CAST
"activelock"
,
NULL
);
node1
=
xmlNewChild
(
active
,
NULL
,
BAD_CAST
"lockscope"
,
NULL
);
xmlNewChild
(
node1
,
NULL
,
BAD_CAST
"exclusive"
,
NULL
);
node1
=
xmlNewChild
(
active
,
NULL
,
BAD_CAST
"locktype"
,
NULL
);
xmlNewChild
(
node1
,
NULL
,
BAD_CAST
"write"
,
NULL
);
xmlNewChild
(
active
,
NULL
,
BAD_CAST
"depth"
,
BAD_CAST
"0"
);
if
(
data
->
lock_owner
)
{
/* Last char of token signals href (1) or text (0) */
if
(
data
->
lock_token
[
strlen
(
data
->
lock_token
)
-1
]
==
'1'
)
{
node1
=
xmlNewChild
(
active
,
NULL
,
BAD_CAST
"owner"
,
NULL
);
xml_add_href
(
node1
,
NULL
,
data
->
lock_owner
);
}
else
{
xmlNewTextChild
(
active
,
NULL
,
BAD_CAST
"owner"
,
BAD_CAST
data
->
lock_owner
);
}
}
snprintf
(
tbuf
,
sizeof
(
tbuf
),
"Second-%lu"
,
data
->
lock_expire
-
now
);
xmlNewChild
(
active
,
NULL
,
BAD_CAST
"timeout"
,
BAD_CAST
tbuf
);
node1
=
xmlNewChild
(
active
,
NULL
,
BAD_CAST
"locktoken"
,
NULL
);
xml_add_href
(
node1
,
NULL
,
data
->
lock_token
);
node1
=
xmlNewChild
(
active
,
NULL
,
BAD_CAST
"lockroot"
,
NULL
);
xml_add_href
(
node1
,
NULL
,
root
);
}
}
/* Add a property 'name', of namespace 'ns', with content 'content',
* and status code/string 'status' to propstat element 'stat'.
* 'stat' will be created as necessary.
*/
xmlNodePtr
xml_add_prop
(
long
status
,
xmlNsPtr
davns
,
struct
propstat
*
propstat
,
const
xmlChar
*
name
,
xmlNsPtr
ns
,
xmlChar
*
content
,
unsigned
precond
)
{
xmlNodePtr
newprop
=
NULL
;
if
(
!
propstat
->
root
)
{
propstat
->
root
=
xmlNewNode
(
davns
,
BAD_CAST
"propstat"
);
xmlNewChild
(
propstat
->
root
,
NULL
,
BAD_CAST
"prop"
,
NULL
);
}
if
(
name
)
newprop
=
xmlNewTextChild
(
propstat
->
root
->
children
,
ns
,
name
,
content
);
propstat
->
status
=
status
;
propstat
->
precond
=
precond
;
return
newprop
;
}
struct
allprop_rock
{
struct
propfind_ctx
*
fctx
;
struct
propstat
*
propstat
;
};
/* Add a response tree to 'root' for the specified href and
either error code or property list */
int
xml_add_response
(
struct
propfind_ctx
*
fctx
,
long
code
,
unsigned
precond
)
{
xmlNodePtr
resp
;
resp
=
xmlNewChild
(
fctx
->
root
,
fctx
->
ns
[
NS_DAV
],
BAD_CAST
"response"
,
NULL
);
if
(
!
resp
)
{
fctx
->
err
->
desc
=
"Unable to add response XML element"
;
*
fctx
->
ret
=
HTTP_SERVER_ERROR
;
return
HTTP_SERVER_ERROR
;
}
xml_add_href
(
resp
,
NULL
,
fctx
->
req_tgt
->
path
);
if
(
code
)
{
xmlNewChild
(
resp
,
NULL
,
BAD_CAST
"status"
,
BAD_CAST
http_statusline
(
code
));
if
(
precond
)
{
xmlNodePtr
error
=
xmlNewChild
(
resp
,
NULL
,
BAD_CAST
"error"
,
NULL
);
xmlNewChild
(
error
,
NULL
,
BAD_CAST
preconds
[
precond
].
name
,
NULL
);
}
}
else
{
struct
propstat
propstat
[
NUM_PROPSTAT
],
*
stat
;
struct
propfind_entry_list
*
e
;
int
i
;
memset
(
propstat
,
0
,
NUM_PROPSTAT
*
sizeof
(
struct
propstat
));
/* Process each property in the linked list */
for
(
e
=
fctx
->
elist
;
e
;
e
=
e
->
next
)
{
int
r
=
HTTP_NOT_FOUND
;
if
(
e
->
get
)
{
r
=
0
;
/* Pre-screen request based on prop flags */
if
(
fctx
->
req_tgt
->
resource
)
{
if
(
!
(
e
->
flags
&
PROP_RESOURCE
))
r
=
HTTP_NOT_FOUND
;
}
else
if
(
!
(
e
->
flags
&
PROP_COLLECTION
))
r
=
HTTP_NOT_FOUND
;
if
(
!
r
)
{
if
(
fctx
->
mode
==
PROPFIND_NAME
)
{
xml_add_prop
(
HTTP_OK
,
fctx
->
ns
[
NS_DAV
],
&
propstat
[
PROPSTAT_OK
],
e
->
name
,
e
->
ns
,
NULL
,
0
);
}
else
{
r
=
e
->
get
(
e
->
name
,
e
->
ns
,
fctx
,
resp
,
propstat
,
e
->
rock
);
}
}
}
switch
(
r
)
{
case
0
:
case
HTTP_OK
:
/* Nothing to do - property handled in callback */
break
;
case
HTTP_UNAUTHORIZED
:
xml_add_prop
(
HTTP_UNAUTHORIZED
,
fctx
->
ns
[
NS_DAV
],
&
propstat
[
PROPSTAT_UNAUTH
],
e
->
name
,
e
->
ns
,
NULL
,
0
);
break
;
case
HTTP_FORBIDDEN
:
xml_add_prop
(
HTTP_FORBIDDEN
,
fctx
->
ns
[
NS_DAV
],
&
propstat
[
PROPSTAT_FORBID
],
e
->
name
,
e
->
ns
,
NULL
,
0
);
break
;
case
HTTP_NOT_FOUND
:
if
(
!
(
fctx
->
prefer
&
PREFER_MIN
))
{
xml_add_prop
(
HTTP_NOT_FOUND
,
fctx
->
ns
[
NS_DAV
],
&
propstat
[
PROPSTAT_NOTFOUND
],
e
->
name
,
e
->
ns
,
NULL
,
0
);
}
break
;
default
:
xml_add_prop
(
r
,
fctx
->
ns
[
NS_DAV
],
&
propstat
[
PROPSTAT_ERROR
],
e
->
name
,
e
->
ns
,
NULL
,
0
);
break
;
}
}
/* Process dead properties for allprop/propname */
if
(
fctx
->
mailbox
&&
!
fctx
->
req_tgt
->
resource
&&
(
fctx
->
mode
==
PROPFIND_ALL
||
fctx
->
mode
==
PROPFIND_NAME
))
{
struct
allprop_rock
arock
=
{
fctx
,
propstat
};
annotatemore_findall
(
fctx
->
mailbox
->
name
,
0
,
"*"
,
allprop_cb
,
&
arock
);
}
/* Check if we have any propstat elements */
for
(
i
=
0
;
i
<
NUM_PROPSTAT
&&
!
propstat
[
i
].
root
;
i
++
);
if
(
i
==
NUM_PROPSTAT
)
{
/* Add an empty propstat 200 */
xml_add_prop
(
HTTP_OK
,
fctx
->
ns
[
NS_DAV
],
&
propstat
[
PROPSTAT_OK
],
NULL
,
NULL
,
NULL
,
0
);
}
/* Add status and optional error to the propstat elements
and then add them to response element */
for
(
i
=
0
;
i
<
NUM_PROPSTAT
;
i
++
)
{
stat
=
&
propstat
[
i
];
if
(
stat
->
root
)
{
xmlNewChild
(
stat
->
root
,
NULL
,
BAD_CAST
"status"
,
BAD_CAST
http_statusline
(
stat
->
status
));
if
(
stat
->
precond
)
{
struct
error_t
error
=
{
NULL
,
stat
->
precond
,
NULL
,
0
};
xml_add_error
(
stat
->
root
,
&
error
,
fctx
->
ns
);
}
xmlAddChild
(
resp
,
stat
->
root
);
}
}
}
fctx
->
record
=
NULL
;
return
0
;
}
/* Helper function to prescreen/fetch resource data */
int
propfind_getdata
(
const
xmlChar
*
name
,
xmlNsPtr
ns
,
struct
propfind_ctx
*
fctx
,
struct
propstat
propstat
[],
xmlNodePtr
prop
,
struct
mime_type_t
*
mime_types
,
int
precond
,
const
char
*
data
,
unsigned
long
datalen
)
{
int
ret
=
0
;
xmlChar
*
type
,
*
ver
=
NULL
;
struct
mime_type_t
*
mime
;
type
=
xmlGetProp
(
prop
,
BAD_CAST
"content-type"
);
if
(
type
)
ver
=
xmlGetProp
(
prop
,
BAD_CAST
"version"
);
/* Check/find requested MIME type */
for
(
mime
=
mime_types
;
type
&&
mime
->
content_type
;
mime
++
)
{
if
(
is_mediatype
((
const
char
*
)
type
,
mime
->
content_type
))
{
if
(
ver
&&
(
!
mime
->
version
||
xmlStrcmp
(
ver
,
BAD_CAST
mime
->
version
)))
{
continue
;
}
break
;
}
}
if
(
!
propstat
)
{
/* Prescreen "property" request */
if
(
!
mime
->
content_type
)
{
fctx
->
err
->
precond
=
precond
;
ret
=
*
fctx
->
ret
=
HTTP_FORBIDDEN
;
}
}
else
{
/* Add "property" */
char
*
freeme
=
NULL
;
prop
=
xml_add_prop
(
HTTP_OK
,
fctx
->
ns
[
NS_DAV
],
&
propstat
[
PROPSTAT_OK
],
name
,
ns
,
NULL
,
0
);
if
(
mime
!=
mime_types
)
{
/* Not the storage format - convert into requested MIME type */
void
*
obj
=
mime_types
->
from_string
(
data
);
data
=
freeme
=
mime
->
to_string
(
obj
);
datalen
=
strlen
(
data
);
mime_types
->
free
(
obj
);
}
if
(
type
)
{
xmlSetProp
(
prop
,
BAD_CAST
"content-type"
,
type
);
if
(
ver
)
xmlSetProp
(
prop
,
BAD_CAST
"version"
,
ver
);
}
xmlAddChild
(
prop
,
xmlNewCDataBlock
(
fctx
->
root
->
doc
,
BAD_CAST
data
,
datalen
));
fctx
->
fetcheddata
=
1
;
if
(
freeme
)
free
(
freeme
);
}
if
(
type
)
xmlFree
(
type
);
if
(
ver
)
xmlFree
(
ver
);
return
ret
;
}
/* Callback to fetch DAV:creationdate */
int
propfind_creationdate
(
const
xmlChar
*
name
,
xmlNsPtr
ns
,
struct
propfind_ctx
*
fctx
,
xmlNodePtr
resp
__attribute__
((
unused
)),
struct
propstat
propstat
[],
void
*
rock
__attribute__
((
unused
)))
{
time_t
t
=
0
;
char
datestr
[
21
];
if
(
fctx
->
data
)
{
struct
dav_data
*
ddata
=
(
struct
dav_data
*
)
fctx
->
data
;
t
=
ddata
->
creationdate
;
}
else
if
(
fctx
->
mailbox
)
{
struct
stat
sbuf
;
fstat
(
fctx
->
mailbox
->
header_fd
,
&
sbuf
);
t
=
sbuf
.
st_ctime
;
}
if
(
!
t
)
return
HTTP_NOT_FOUND
;
rfc3339date_gen
(
datestr
,
sizeof
(
datestr
),
t
);
xml_add_prop
(
HTTP_OK
,
fctx
->
ns
[
NS_DAV
],
&
propstat
[
PROPSTAT_OK
],
name
,
ns
,
BAD_CAST
datestr
,
0
);
return
0
;
}
/* Callback to fetch DAV:displayname */
static
int
propfind_displayname
(
const
xmlChar
*
name
,
xmlNsPtr
ns
,
struct
propfind_ctx
*
fctx
,
xmlNodePtr
resp
__attribute__
((
unused
)),
struct
propstat
propstat
[],
void
*
rock
__attribute__
((
unused
)))
{
/* XXX Do LDAP/SQL lookup here */
buf_reset
(
&
fctx
->
buf
);
if
(
fctx
->
req_tgt
->
userid
)
buf_printf
(
&
fctx
->
buf
,
"%s"
,
fctx
->
req_tgt
->
userid
);
xml_add_prop
(
HTTP_OK
,
fctx
->
ns
[
NS_DAV
],
&
propstat
[
PROPSTAT_OK
],
name
,
ns
,
BAD_CAST
buf_cstring
(
&
fctx
->
buf
),
0
);
return
0
;
}
/* Callback to fetch DAV:getcontentlength */
int
propfind_getlength
(
const
xmlChar
*
name
,
xmlNsPtr
ns
,
struct
propfind_ctx
*
fctx
,
xmlNodePtr
resp
__attribute__
((
unused
)),
struct
propstat
propstat
[],
void
*
rock
__attribute__
((
unused
)))
{
buf_reset
(
&
fctx
->
buf
);
if
(
fctx
->
record
)
{
buf_printf
(
&
fctx
->
buf
,
"%u"
,
fctx
->
record
->
size
-
fctx
->
record
->
header_size
);
}
xml_add_prop
(
HTTP_OK
,
fctx
->
ns
[
NS_DAV
],
&
propstat
[
PROPSTAT_OK
],
name
,
ns
,
BAD_CAST
buf_cstring
(
&
fctx
->
buf
),
0
);
return
0
;
}
/* Callback to fetch DAV:getetag */
int
propfind_getetag
(
const
xmlChar
*
name
,
xmlNsPtr
ns
,
struct
propfind_ctx
*
fctx
,
xmlNodePtr
resp
__attribute__
((
unused
)),
struct
propstat
propstat
[],
void
*
rock
__attribute__
((
unused
)))
{
if
(
fctx
->
req_tgt
->
resource
&&
!
fctx
->
record
)
return
HTTP_NOT_FOUND
;
if
(
!
fctx
->
mailbox
)
return
HTTP_NOT_FOUND
;
buf_reset
(
&
fctx
->
buf
);
if
(
fctx
->
record
)
{
/* add DQUOTEs */
buf_printf
(
&
fctx
->
buf
,
"
\"
%s
\"
"
,
message_guid_encode
(
&
fctx
->
record
->
guid
));
}
else
{
buf_printf
(
&
fctx
->
buf
,
"
\"
%u-%u-%u
\"
"
,
fctx
->
mailbox
->
i
.
uidvalidity
,
fctx
->
mailbox
->
i
.
last_uid
,
fctx
->
mailbox
->
i
.
exists
);
}
xml_add_prop
(
HTTP_OK
,
fctx
->
ns
[
NS_DAV
],
&
propstat
[
PROPSTAT_OK
],
name
,
ns
,
BAD_CAST
buf_cstring
(
&
fctx
->
buf
),
0
);
return
0
;
}
/* Callback to fetch DAV:getlastmodified */
int
propfind_getlastmod
(
const
xmlChar
*
name
,
xmlNsPtr
ns
,
struct
propfind_ctx
*
fctx
,
xmlNodePtr
resp
__attribute__
((
unused
)),
struct
propstat
propstat
[],
void
*
rock
__attribute__
((
unused
)))
{
if
(
!
fctx
->
mailbox
||
(
fctx
->
req_tgt
->
resource
&&
!
fctx
->
record
))
return
HTTP_NOT_FOUND
;
buf_ensure
(
&
fctx
->
buf
,
30
);
httpdate_gen
(
fctx
->
buf
.
s
,
fctx
->
buf
.
alloc
,
fctx
->
record
?
fctx
->
record
->
internaldate
:
fctx
->
mailbox
->
index_mtime
);
xml_add_prop
(
HTTP_OK
,
fctx
->
ns
[
NS_DAV
],
&
propstat
[
PROPSTAT_OK
],
name
,
ns
,
BAD_CAST
fctx
->
buf
.
s
,
0
);
return
0
;
}
/* Callback to fetch DAV:lockdiscovery */
int
propfind_lockdisc
(
const
xmlChar
*
name
,
xmlNsPtr
ns
,
struct
propfind_ctx
*
fctx
,
xmlNodePtr
resp
__attribute__
((
unused
)),
struct
propstat
propstat
[],
void
*
rock
__attribute__
((
unused
)))
{
xmlNodePtr
node
=
xml_add_prop
(
HTTP_OK
,
fctx
->
ns
[
NS_DAV
],
&
propstat
[
PROPSTAT_OK
],
name
,
ns
,
NULL
,
0
);
if
(
fctx
->
mailbox
&&
fctx
->
record
)
{
struct
dav_data
*
ddata
=
(
struct
dav_data
*
)
fctx
->
data
;
xml_add_lockdisc
(
node
,
fctx
->
req_tgt
->
path
,
ddata
);
}
return
0
;
}
/* Callback to fetch DAV:resourcetype */
static
int
propfind_restype
(
const
xmlChar
*
name
,
xmlNsPtr
ns
,
struct
propfind_ctx
*
fctx
,
xmlNodePtr
resp
__attribute__
((
unused
)),
struct
propstat
propstat
[],
void
*
rock
__attribute__
((
unused
)))
{
xmlNodePtr
node
=
xml_add_prop
(
HTTP_OK
,
fctx
->
ns
[
NS_DAV
],
&
propstat
[
PROPSTAT_OK
],
name
,
ns
,
NULL
,
0
);
if
(
fctx
->
req_tgt
->
userid
)
xmlNewChild
(
node
,
NULL
,
BAD_CAST
"principal"
,
NULL
);
return
0
;
}
/* Callback to "write" resourcetype property */
int
proppatch_restype
(
xmlNodePtr
prop
,
unsigned
set
,
struct
proppatch_ctx
*
pctx
,
struct
propstat
propstat
[],
void
*
rock
)
{
const
char
*
coltype
=
(
const
char
*
)
rock
;
unsigned
precond
=
0
;
if
(
set
&&
(
pctx
->
meth
!=
METH_PROPPATCH
))
{
/* "Writeable" for MKCOL/MKCALENDAR only */
xmlNodePtr
cur
;
for
(
cur
=
prop
->
children
;
cur
;
cur
=
cur
->
next
)
{
if
(
cur
->
type
!=
XML_ELEMENT_NODE
)
continue
;
/* Make sure we have valid resourcetypes for the collection */
if
(
xmlStrcmp
(
cur
->
name
,
BAD_CAST
"collection"
)
&&
(
!
coltype
||
xmlStrcmp
(
cur
->
name
,
BAD_CAST
coltype
)))
break
;
}
if
(
!
cur
)
{
/* All resourcetypes are valid */
xml_add_prop
(
HTTP_OK
,
pctx
->
ns
[
NS_DAV
],
&
propstat
[
PROPSTAT_OK
],
prop
->
name
,
prop
->
ns
,
NULL
,
0
);
return
0
;
}
/* Invalid resourcetype */
precond
=
DAV_VALID_RESTYPE
;
}
else
{
/* Protected property */
precond
=
DAV_PROT_PROP
;
}
xml_add_prop
(
HTTP_FORBIDDEN
,
pctx
->
ns
[
NS_DAV
],
&
propstat
[
PROPSTAT_FORBID
],
prop
->
name
,
prop
->
ns
,
NULL
,
precond
);
*
pctx
->
ret
=
HTTP_FORBIDDEN
;
return
0
;
}
/* Callback to fetch DAV:supportedlock */
int
propfind_suplock
(
const
xmlChar
*
name
,
xmlNsPtr
ns
,
struct
propfind_ctx
*
fctx
,
xmlNodePtr
resp
__attribute__
((
unused
)),
struct
propstat
propstat
[],
void
*
rock
__attribute__
((
unused
)))
{
xmlNodePtr
node
=
xml_add_prop
(
HTTP_OK
,
fctx
->
ns
[
NS_DAV
],
&
propstat
[
PROPSTAT_OK
],
name
,
ns
,
NULL
,
0
);
if
(
fctx
->
mailbox
&&
fctx
->
record
)
{
xmlNodePtr
entry
=
xmlNewChild
(
node
,
NULL
,
BAD_CAST
"lockentry"
,
NULL
);
xmlNodePtr
scope
=
xmlNewChild
(
entry
,
NULL
,
BAD_CAST
"lockscope"
,
NULL
);
xmlNodePtr
type
=
xmlNewChild
(
entry
,
NULL
,
BAD_CAST
"locktype"
,
NULL
);
xmlNewChild
(
scope
,
NULL
,
BAD_CAST
"exclusive"
,
NULL
);
xmlNewChild
(
type
,
NULL
,
BAD_CAST
"write"
,
NULL
);
}
return
0
;
}
/* Callback to fetch DAV:supported-report-set */
int
propfind_reportset
(
const
xmlChar
*
name
,
xmlNsPtr
ns
,
struct
propfind_ctx
*
fctx
,
xmlNodePtr
resp
__attribute__
((
unused
)),
struct
propstat
propstat
[],
void
*
rock
__attribute__
((
unused
)))
{
xmlNodePtr
top
,
node
;
const
struct
report_type_t
*
report
;
top
=
xml_add_prop
(
HTTP_OK
,
fctx
->
ns
[
NS_DAV
],
&
propstat
[
PROPSTAT_OK
],
name
,
ns
,
NULL
,
0
);
for
(
report
=
(
const
struct
report_type_t
*
)
rock
;
report
&&
report
->
name
;
report
++
)
{
node
=
xmlNewChild
(
top
,
NULL
,
BAD_CAST
"supported-report"
,
NULL
);
node
=
xmlNewChild
(
node
,
NULL
,
BAD_CAST
"report"
,
NULL
);
ensure_ns
(
fctx
->
ns
,
report
->
ns
,
resp
->
parent
,
known_namespaces
[
report
->
ns
].
href
,
known_namespaces
[
report
->
ns
].
prefix
);
xmlNewChild
(
node
,
fctx
->
ns
[
report
->
ns
],
BAD_CAST
report
->
name
,
NULL
);
}
return
0
;
}
/* Callback to fetch DAV:alternate-URI-set */
static
int
propfind_alturiset
(
const
xmlChar
*
name
,
xmlNsPtr
ns
,
struct
propfind_ctx
*
fctx
,
xmlNodePtr
resp
__attribute__
((
unused
)),
struct
propstat
propstat
[],
void
*
rock
__attribute__
((
unused
)))
{
xml_add_prop
(
HTTP_OK
,
fctx
->
ns
[
NS_DAV
],
&
propstat
[
PROPSTAT_OK
],
name
,
ns
,
NULL
,
0
);
return
0
;
}
/* Callback to fetch DAV:principal-URL */
static
int
propfind_principalurl
(
const
xmlChar
*
name
,
xmlNsPtr
ns
,
struct
propfind_ctx
*
fctx
,
xmlNodePtr
resp
__attribute__
((
unused
)),
struct
propstat
propstat
[],
void
*
rock
__attribute__
((
unused
)))
{
xmlNodePtr
node
=
xml_add_prop
(
HTTP_OK
,
fctx
->
ns
[
NS_DAV
],
&
propstat
[
PROPSTAT_OK
],
name
,
ns
,
NULL
,
0
);
buf_reset
(
&
fctx
->
buf
);
if
(
fctx
->
req_tgt
->
userid
)
{
xmlNodePtr
expand
=
(
xmlNodePtr
)
rock
;
buf_printf
(
&
fctx
->
buf
,
"%s/user/%s/"
,
namespace_principal
.
prefix
,
fctx
->
req_tgt
->
userid
);
if
(
expand
)
{
/* Return properties for this URL */
expand_property
(
expand
,
fctx
,
buf_cstring
(
&
fctx
->
buf
),
&
principal_parse_path
,
principal_props
,
node
,
0
);
return
0
;
}
}
/* Return just the URL */
xml_add_href
(
node
,
NULL
,
buf_cstring
(
&
fctx
->
buf
));
return
0
;
}
/* Callback to fetch DAV:owner */
int
propfind_owner
(
const
xmlChar
*
name
,
xmlNsPtr
ns
,
struct
propfind_ctx
*
fctx
,
xmlNodePtr
resp
__attribute__
((
unused
)),
struct
propstat
propstat
[],
void
*
rock
__attribute__
((
unused
)))
{
xmlNodePtr
node
=
xml_add_prop
(
HTTP_OK
,
fctx
->
ns
[
NS_DAV
],
&
propstat
[
PROPSTAT_OK
],
name
,
ns
,
NULL
,
0
);
if
(
fctx
->
req_tgt
->
userid
)
{
xmlNodePtr
expand
=
(
xmlNodePtr
)
rock
;
buf_reset
(
&
fctx
->
buf
);
buf_printf
(
&
fctx
->
buf
,
"%s/user/%s/"
,
namespace_principal
.
prefix
,
fctx
->
req_tgt
->
userid
);
if
(
expand
)
{
/* Return properties for this URL */
expand_property
(
expand
,
fctx
,
buf_cstring
(
&
fctx
->
buf
),
&
principal_parse_path
,
principal_props
,
node
,
0
);
}
else
{
/* Return just the URL */
xml_add_href
(
node
,
NULL
,
buf_cstring
(
&
fctx
->
buf
));
}
}
return
0
;
}
/* Add possibly 'abstract' supported-privilege 'priv_name', of namespace 'ns',
* with description 'desc_str' to node 'root'. For now, we alssume all
* descriptions are English.
*/
static
xmlNodePtr
add_suppriv
(
xmlNodePtr
root
,
const
char
*
priv_name
,
xmlNsPtr
ns
,
int
abstract
,
const
char
*
desc_str
)
{
xmlNodePtr
supp
,
priv
,
desc
;
supp
=
xmlNewChild
(
root
,
NULL
,
BAD_CAST
"supported-privilege"
,
NULL
);
priv
=
xmlNewChild
(
supp
,
NULL
,
BAD_CAST
"privilege"
,
NULL
);
xmlNewChild
(
priv
,
ns
,
BAD_CAST
priv_name
,
NULL
);
if
(
abstract
)
xmlNewChild
(
supp
,
NULL
,
BAD_CAST
"abstract"
,
NULL
);
desc
=
xmlNewChild
(
supp
,
NULL
,
BAD_CAST
"description"
,
BAD_CAST
desc_str
);
xmlNodeSetLang
(
desc
,
BAD_CAST
"en"
);
return
supp
;
}
/* Callback to fetch DAV:supported-privilege-set */
int
propfind_supprivset
(
const
xmlChar
*
name
,
xmlNsPtr
ns
,
struct
propfind_ctx
*
fctx
,
xmlNodePtr
resp
,
struct
propstat
propstat
[],
void
*
rock
__attribute__
((
unused
)))
{
xmlNodePtr
set
,
all
,
agg
,
write
;
unsigned
tgt_flags
=
0
;
set
=
xml_add_prop
(
HTTP_OK
,
fctx
->
ns
[
NS_DAV
],
&
propstat
[
PROPSTAT_OK
],
name
,
ns
,
NULL
,
0
);
all
=
add_suppriv
(
set
,
"all"
,
NULL
,
0
,
"Any operation"
);
agg
=
add_suppriv
(
all
,
"read"
,
NULL
,
0
,
"Read any object"
);
add_suppriv
(
agg
,
"read-current-user-privilege-set"
,
NULL
,
1
,
"Read current user privilege set"
);
if
(
fctx
->
req_tgt
->
namespace
==
URL_NS_CALENDAR
)
{
if
(
fctx
->
req_tgt
->
collection
)
{
ensure_ns
(
fctx
->
ns
,
NS_CALDAV
,
resp
->
parent
,
XML_NS_CALDAV
,
"C"
);
if
(
!
strcmp
(
fctx
->
req_tgt
->
collection
,
SCHED_INBOX
))
tgt_flags
=
TGT_SCHED_INBOX
;
else
if
(
!
strcmp
(
fctx
->
req_tgt
->
collection
,
SCHED_OUTBOX
))
tgt_flags
=
TGT_SCHED_OUTBOX
;
else
{
add_suppriv
(
agg
,
"read-free-busy"
,
fctx
->
ns
[
NS_CALDAV
],
0
,
"Read free/busy time"
);
}
}
}
write
=
add_suppriv
(
all
,
"write"
,
NULL
,
0
,
"Write any object"
);
add_suppriv
(
write
,
"write-content"
,
NULL
,
0
,
"Write resource content"
);
add_suppriv
(
write
,
"write-properties"
,
NULL
,
0
,
"Write properties"
);
agg
=
add_suppriv
(
write
,
"bind"
,
NULL
,
0
,
"Add new member to collection"
);
ensure_ns
(
fctx
->
ns
,
NS_CYRUS
,
resp
->
parent
,
XML_NS_CYRUS
,
"CY"
);
add_suppriv
(
agg
,
"make-collection"
,
fctx
->
ns
[
NS_CYRUS
],
0
,
"Make new collection"
);
add_suppriv
(
agg
,
"add-resource"
,
fctx
->
ns
[
NS_CYRUS
],
0
,
"Add new resource"
);
agg
=
add_suppriv
(
write
,
"unbind"
,
NULL
,
0
,
"Remove member from collection"
);
add_suppriv
(
agg
,
"remove-collection"
,
fctx
->
ns
[
NS_CYRUS
],
0
,
"Remove collection"
);
add_suppriv
(
agg
,
"remove-resource"
,
fctx
->
ns
[
NS_CYRUS
],
0
,
"Remove resource"
);
agg
=
add_suppriv
(
all
,
"admin"
,
fctx
->
ns
[
NS_CYRUS
],
0
,
"Perform administrative operations"
);
add_suppriv
(
agg
,
"read-acl"
,
NULL
,
1
,
"Read ACL"
);
add_suppriv
(
agg
,
"write-acl"
,
NULL
,
1
,
"Write ACL"
);
add_suppriv
(
agg
,
"unlock"
,
NULL
,
1
,
"Unlock resource"
);
if
(
tgt_flags
==
TGT_SCHED_INBOX
)
{
agg
=
add_suppriv
(
all
,
"schedule-deliver"
,
fctx
->
ns
[
NS_CALDAV
],
0
,
"Deliver scheduling messages"
);
add_suppriv
(
agg
,
"schedule-deliver-invite"
,
fctx
->
ns
[
NS_CALDAV
],
0
,
"Deliver scheduling messages from Organizers"
);
add_suppriv
(
agg
,
"schedule-deliver-reply"
,
fctx
->
ns
[
NS_CALDAV
],
0
,
"Deliver scheduling messages from Attendees"
);
add_suppriv
(
agg
,
"schedule-query-freebusy"
,
fctx
->
ns
[
NS_CALDAV
],
0
,
"Accept free/busy requests"
);
}
else
if
(
tgt_flags
==
TGT_SCHED_OUTBOX
)
{
agg
=
add_suppriv
(
all
,
"schedule-send"
,
fctx
->
ns
[
NS_CALDAV
],
0
,
"Send scheduling messages"
);
add_suppriv
(
agg
,
"schedule-send-invite"
,
fctx
->
ns
[
NS_CALDAV
],
0
,
"Send scheduling messages by Organizers"
);
add_suppriv
(
agg
,
"schedule-send-reply"
,
fctx
->
ns
[
NS_CALDAV
],
0
,
"Send scheduling messages by Attendees"
);
add_suppriv
(
agg
,
"schedule-send-freebusy"
,
fctx
->
ns
[
NS_CALDAV
],
0
,
"Submit free/busy requests"
);
}
return
0
;
}
static
int
add_privs
(
int
rights
,
unsigned
flags
,
xmlNodePtr
parent
,
xmlNodePtr
root
,
xmlNsPtr
*
ns
)
{
xmlNodePtr
priv
;
if
((
rights
&
DACL_ALL
)
==
DACL_ALL
&&
/* DAV:all on CALDAV:schedule-in/outbox MUST include CALDAV:schedule */
(
!
(
flags
&
(
PRIV_INBOX
|
PRIV_OUTBOX
))
||
(
rights
&
DACL_SCHED
)
==
DACL_SCHED
))
{
priv
=
xmlNewChild
(
parent
,
NULL
,
BAD_CAST
"privilege"
,
NULL
);
xmlNewChild
(
priv
,
NULL
,
BAD_CAST
"all"
,
NULL
);
}
if
((
rights
&
DACL_READ
)
==
DACL_READ
)
{
priv
=
xmlNewChild
(
parent
,
NULL
,
BAD_CAST
"privilege"
,
NULL
);
xmlNewChild
(
priv
,
NULL
,
BAD_CAST
"read"
,
NULL
);
if
(
flags
&
PRIV_IMPLICIT
)
rights
|=
DACL_READFB
;
}
if
((
rights
&
DACL_READFB
)
&&
/* CALDAV:read-free-busy does not apply to CALDAV:schedule-in/outbox */
!
(
flags
&
(
PRIV_INBOX
|
PRIV_OUTBOX
)))
{
priv
=
xmlNewChild
(
parent
,
NULL
,
BAD_CAST
"privilege"
,
NULL
);
ensure_ns
(
ns
,
NS_CALDAV
,
root
,
XML_NS_CALDAV
,
"C"
);
xmlNewChild
(
priv
,
ns
[
NS_CALDAV
],
BAD_CAST
"read-free-busy"
,
NULL
);
}
if
((
rights
&
DACL_WRITE
)
==
DACL_WRITE
)
{
priv
=
xmlNewChild
(
parent
,
NULL
,
BAD_CAST
"privilege"
,
NULL
);
xmlNewChild
(
priv
,
NULL
,
BAD_CAST
"write"
,
NULL
);
}
if
(
rights
&
DACL_WRITECONT
)
{
priv
=
xmlNewChild
(
parent
,
NULL
,
BAD_CAST
"privilege"
,
NULL
);
xmlNewChild
(
priv
,
NULL
,
BAD_CAST
"write-content"
,
NULL
);
}
if
(
rights
&
DACL_WRITEPROPS
)
{
priv
=
xmlNewChild
(
parent
,
NULL
,
BAD_CAST
"privilege"
,
NULL
);
xmlNewChild
(
priv
,
NULL
,
BAD_CAST
"write-properties"
,
NULL
);
}
if
(
rights
&
(
DACL_BIND
|
DACL_UNBIND
|
DACL_ADMIN
))
{
ensure_ns
(
ns
,
NS_CYRUS
,
root
,
XML_NS_CYRUS
,
"CY"
);
}
if
((
rights
&
DACL_BIND
)
==
DACL_BIND
)
{
priv
=
xmlNewChild
(
parent
,
NULL
,
BAD_CAST
"privilege"
,
NULL
);
xmlNewChild
(
priv
,
NULL
,
BAD_CAST
"bind"
,
NULL
);
}
if
(
rights
&
DACL_MKCOL
)
{
priv
=
xmlNewChild
(
parent
,
NULL
,
BAD_CAST
"privilege"
,
NULL
);
xmlNewChild
(
priv
,
ns
[
NS_CYRUS
],
BAD_CAST
"make-collection"
,
NULL
);
}
if
(
rights
&
DACL_ADDRSRC
)
{
priv
=
xmlNewChild
(
parent
,
NULL
,
BAD_CAST
"privilege"
,
NULL
);
xmlNewChild
(
priv
,
ns
[
NS_CYRUS
],
BAD_CAST
"add-resource"
,
NULL
);
}
if
((
rights
&
DACL_UNBIND
)
==
DACL_UNBIND
)
{
priv
=
xmlNewChild
(
parent
,
NULL
,
BAD_CAST
"privilege"
,
NULL
);
xmlNewChild
(
priv
,
NULL
,
BAD_CAST
"unbind"
,
NULL
);
}
if
(
rights
&
DACL_RMCOL
)
{
priv
=
xmlNewChild
(
parent
,
NULL
,
BAD_CAST
"privilege"
,
NULL
);
xmlNewChild
(
priv
,
ns
[
NS_CYRUS
],
BAD_CAST
"remove-collection"
,
NULL
);
}
if
((
rights
&
DACL_RMRSRC
)
==
DACL_RMRSRC
)
{
priv
=
xmlNewChild
(
parent
,
NULL
,
BAD_CAST
"privilege"
,
NULL
);
xmlNewChild
(
priv
,
ns
[
NS_CYRUS
],
BAD_CAST
"remove-resource"
,
NULL
);
}
if
(
rights
&
DACL_ADMIN
)
{
priv
=
xmlNewChild
(
parent
,
NULL
,
BAD_CAST
"privilege"
,
NULL
);
xmlNewChild
(
priv
,
ns
[
NS_CYRUS
],
BAD_CAST
"admin"
,
NULL
);
}
if
(
rights
&
DACL_SCHED
)
{
ensure_ns
(
ns
,
NS_CALDAV
,
root
,
XML_NS_CALDAV
,
"C"
);
}
if
((
rights
&
DACL_SCHED
)
==
DACL_SCHED
)
{
priv
=
xmlNewChild
(
parent
,
NULL
,
BAD_CAST
"privilege"
,
NULL
);
if
(
flags
&
PRIV_INBOX
)
xmlNewChild
(
priv
,
ns
[
NS_CALDAV
],
BAD_CAST
"schedule-deliver"
,
NULL
);
else
if
(
flags
&
PRIV_OUTBOX
)
xmlNewChild
(
priv
,
ns
[
NS_CALDAV
],
BAD_CAST
"schedule-send"
,
NULL
);
}
if
(
rights
&
DACL_INVITE
)
{
priv
=
xmlNewChild
(
parent
,
NULL
,
BAD_CAST
"privilege"
,
NULL
);
if
(
flags
&
PRIV_INBOX
)
xmlNewChild
(
priv
,
ns
[
NS_CALDAV
],
BAD_CAST
"schedule-deliver-invite"
,
NULL
);
else
if
(
flags
&
PRIV_OUTBOX
)
xmlNewChild
(
priv
,
ns
[
NS_CALDAV
],
BAD_CAST
"schedule-send-invite"
,
NULL
);
}
if
(
rights
&
DACL_REPLY
)
{
priv
=
xmlNewChild
(
parent
,
NULL
,
BAD_CAST
"privilege"
,
NULL
);
if
(
flags
&
PRIV_INBOX
)
xmlNewChild
(
priv
,
ns
[
NS_CALDAV
],
BAD_CAST
"schedule-deliver-reply"
,
NULL
);
else
if
(
flags
&
PRIV_OUTBOX
)
xmlNewChild
(
priv
,
ns
[
NS_CALDAV
],
BAD_CAST
"schedule-send-reply"
,
NULL
);
}
if
(
rights
&
DACL_SCHEDFB
)
{
priv
=
xmlNewChild
(
parent
,
NULL
,
BAD_CAST
"privilege"
,
NULL
);
if
(
flags
&
PRIV_INBOX
)
xmlNewChild
(
priv
,
ns
[
NS_CALDAV
],
BAD_CAST
"schedule-query-freebusy"
,
NULL
);
else
if
(
flags
&
PRIV_OUTBOX
)
xmlNewChild
(
priv
,
ns
[
NS_CALDAV
],
BAD_CAST
"schedule-send-freebusy"
,
NULL
);
}
return
0
;
}
/* Callback to fetch DAV:current-user-privilege-set */
int
propfind_curprivset
(
const
xmlChar
*
name
,
xmlNsPtr
ns
,
struct
propfind_ctx
*
fctx
,
xmlNodePtr
resp
,
struct
propstat
propstat
[],
void
*
rock
__attribute__
((
unused
)))
{
int
rights
;
unsigned
flags
=
0
;
xmlNodePtr
set
;
if
(
!
fctx
->
mailbox
)
return
HTTP_NOT_FOUND
;
rights
=
httpd_myrights
(
fctx
->
authstate
,
fctx
->
mailbox
->
acl
);
if
((
rights
&
DACL_READ
)
!=
DACL_READ
)
{
return
HTTP_UNAUTHORIZED
;
}
/* Add in implicit rights */
if
(
fctx
->
userisadmin
)
{
rights
|=
DACL_ADMIN
;
}
else
if
(
mboxname_userownsmailbox
(
httpd_userid
,
fctx
->
mailbox
->
name
))
{
rights
|=
config_implicitrights
;
/* we always allow admin by the owner in DAV */
rights
|=
DACL_ADMIN
;
}
/* Build the rest of the XML response */
set
=
xml_add_prop
(
HTTP_OK
,
fctx
->
ns
[
NS_DAV
],
&
propstat
[
PROPSTAT_OK
],
name
,
ns
,
NULL
,
0
);
if
(
!
fctx
->
req_tgt
->
resource
)
{
if
(
fctx
->
req_tgt
->
namespace
==
URL_NS_CALENDAR
)
{
flags
=
PRIV_IMPLICIT
;
if
(
fctx
->
req_tgt
->
collection
)
{
if
(
!
strcmp
(
fctx
->
req_tgt
->
collection
,
SCHED_INBOX
))
flags
=
PRIV_INBOX
;
else
if
(
!
strcmp
(
fctx
->
req_tgt
->
collection
,
SCHED_OUTBOX
))
flags
=
PRIV_OUTBOX
;
}
}
add_privs
(
rights
,
flags
,
set
,
resp
->
parent
,
fctx
->
ns
);
}
return
0
;
}
/* Callback to fetch DAV:acl */
int
propfind_acl
(
const
xmlChar
*
name
,
xmlNsPtr
ns
,
struct
propfind_ctx
*
fctx
,
xmlNodePtr
resp
,
struct
propstat
propstat
[],
void
*
rock
__attribute__
((
unused
)))
{
xmlNodePtr
acl
;
char
*
aclstr
,
*
userid
;
unsigned
flags
=
0
;
if
(
!
fctx
->
mailbox
)
return
HTTP_NOT_FOUND
;
/* owner has implicit admin rights */
if
(
!
mboxname_userownsmailbox
(
httpd_userid
,
fctx
->
mailbox
->
name
))
{
int
rights
=
httpd_myrights
(
fctx
->
authstate
,
fctx
->
mailbox
->
acl
);
if
(
!
(
rights
&
DACL_ADMIN
))
return
HTTP_UNAUTHORIZED
;
}
if
(
fctx
->
req_tgt
->
namespace
==
URL_NS_CALENDAR
)
{
flags
=
PRIV_IMPLICIT
;
if
(
fctx
->
req_tgt
->
collection
)
{
if
(
!
strcmp
(
fctx
->
req_tgt
->
collection
,
SCHED_INBOX
))
flags
=
PRIV_INBOX
;
else
if
(
!
strcmp
(
fctx
->
req_tgt
->
collection
,
SCHED_OUTBOX
))
flags
=
PRIV_OUTBOX
;
}
}
/* Start the acl XML response */
acl
=
xml_add_prop
(
HTTP_OK
,
fctx
->
ns
[
NS_DAV
],
&
propstat
[
PROPSTAT_OK
],
name
,
ns
,
NULL
,
0
);
/* Parse the ACL string (userid/rights pairs) */
userid
=
aclstr
=
xstrdup
(
fctx
->
mailbox
->
acl
);
while
(
userid
)
{
int
rights
;
char
*
rightstr
,
*
nextid
;
xmlNodePtr
ace
,
node
;
rightstr
=
strchr
(
userid
,
'\t'
);
if
(
!
rightstr
)
break
;
*
rightstr
++
=
'\0'
;
nextid
=
strchr
(
rightstr
,
'\t'
);
if
(
!
nextid
)
break
;
*
nextid
++
=
'\0'
;
/* Check for negative rights */
/* XXX Does this correspond to DAV:deny? */
if
(
*
userid
==
'-'
)
{
userid
=
nextid
;
continue
;
}
rights
=
cyrus_acl_strtomask
(
rightstr
);
/* Add ace XML element for this userid/right pair */
ace
=
xmlNewChild
(
acl
,
NULL
,
BAD_CAST
"ace"
,
NULL
);
node
=
xmlNewChild
(
ace
,
NULL
,
BAD_CAST
"principal"
,
NULL
);
if
(
!
strcmp
(
userid
,
fctx
->
userid
))
xmlNewChild
(
node
,
NULL
,
BAD_CAST
"self"
,
NULL
);
else
if
(
mboxname_userownsmailbox
(
userid
,
fctx
->
mailbox
->
name
))
{
xmlNewChild
(
node
,
NULL
,
BAD_CAST
"owner"
,
NULL
);
/* we always allow admin by the owner in DAV */
rights
|=
DACL_ADMIN
;
}
else
if
(
!
strcmp
(
userid
,
"anyone"
))
xmlNewChild
(
node
,
NULL
,
BAD_CAST
"authenticated"
,
NULL
);
/* XXX - well, it's better than a user called 'anonymous'
* Is there any IMAP equivalent to "unauthenticated"?
* Is there any DAV equivalent to "anonymous"?
*/
else
if
(
!
strcmp
(
userid
,
"anonymous"
))
xmlNewChild
(
node
,
NULL
,
BAD_CAST
"unauthenticated"
,
NULL
);
else
if
(
!
strncmp
(
userid
,
"group:"
,
6
))
{
buf_reset
(
&
fctx
->
buf
);
buf_printf
(
&
fctx
->
buf
,
"%s/group/%s/"
,
namespace_principal
.
prefix
,
userid
+
6
);
xml_add_href
(
node
,
NULL
,
buf_cstring
(
&
fctx
->
buf
));
}
else
{
buf_reset
(
&
fctx
->
buf
);
buf_printf
(
&
fctx
->
buf
,
"%s/user/%s/"
,
namespace_principal
.
prefix
,
userid
);
xml_add_href
(
node
,
NULL
,
buf_cstring
(
&
fctx
->
buf
));
}
node
=
xmlNewChild
(
ace
,
NULL
,
BAD_CAST
"grant"
,
NULL
);
add_privs
(
rights
,
flags
,
node
,
resp
->
parent
,
fctx
->
ns
);
/* system userids are protected */
if
(
is_system_user
(
userid
))
xmlNewChild
(
ace
,
NULL
,
BAD_CAST
"protected"
,
NULL
);
if
(
fctx
->
req_tgt
->
resource
)
{
node
=
xmlNewChild
(
ace
,
NULL
,
BAD_CAST
"inherited"
,
NULL
);
buf_reset
(
&
fctx
->
buf
);
buf_printf
(
&
fctx
->
buf
,
"%.*s"
,
(
int
)(
fctx
->
req_tgt
->
resource
-
fctx
->
req_tgt
->
path
),
fctx
->
req_tgt
->
path
);
xml_add_href
(
node
,
NULL
,
buf_cstring
(
&
fctx
->
buf
));
}
userid
=
nextid
;
}
if
(
aclstr
)
free
(
aclstr
);
return
0
;
}
/* Callback to fetch DAV:acl-restrictions */
int
propfind_aclrestrict
(
const
xmlChar
*
name
,
xmlNsPtr
ns
,
struct
propfind_ctx
*
fctx
,
xmlNodePtr
resp
__attribute__
((
unused
)),
struct
propstat
propstat
[],
void
*
rock
__attribute__
((
unused
)))
{
xmlNodePtr
node
=
xml_add_prop
(
HTTP_OK
,
fctx
->
ns
[
NS_DAV
],
&
propstat
[
PROPSTAT_OK
],
name
,
ns
,
NULL
,
0
);
xmlNewChild
(
node
,
NULL
,
BAD_CAST
"no-invert"
,
NULL
);
return
0
;
}
/* Callback to fetch DAV:principal-collection-set */
EXPORTED
int
propfind_princolset
(
const
xmlChar
*
name
,
xmlNsPtr
ns
,
struct
propfind_ctx
*
fctx
,
xmlNodePtr
resp
__attribute__
((
unused
)),
struct
propstat
propstat
[],
void
*
rock
__attribute__
((
unused
)))
{
xmlNodePtr
node
=
xml_add_prop
(
HTTP_OK
,
fctx
->
ns
[
NS_DAV
],
&
propstat
[
PROPSTAT_OK
],
name
,
ns
,
NULL
,
0
);
buf_reset
(
&
fctx
->
buf
);
buf_printf
(
&
fctx
->
buf
,
"%s/"
,
namespace_principal
.
prefix
);
xmlNewChild
(
node
,
NULL
,
BAD_CAST
"href"
,
BAD_CAST
buf_cstring
(
&
fctx
->
buf
));
return
0
;
}
/* Callback to fetch DAV:quota-available-bytes and DAV:quota-used-bytes */
EXPORTED
int
propfind_quota
(
const
xmlChar
*
name
,
xmlNsPtr
ns
,
struct
propfind_ctx
*
fctx
,
xmlNodePtr
resp
__attribute__
((
unused
)),
struct
propstat
propstat
[],
void
*
rock
__attribute__
((
unused
)))
{
static
char
prevroot
[
MAX_MAILBOX_BUFFER
];
char
foundroot
[
MAX_MAILBOX_BUFFER
],
*
qr
=
NULL
;
if
(
fctx
->
mailbox
)
{
/* Use the quotaroot as specified in mailbox header */
qr
=
fctx
->
mailbox
->
quotaroot
;
}
else
{
/* Find the quotaroot governing this hierarchy */
if
(
quota_findroot
(
foundroot
,
sizeof
(
foundroot
),
fctx
->
req_tgt
->
mboxname
))
{
qr
=
foundroot
;
}
}
if
(
!
qr
)
return
HTTP_NOT_FOUND
;
if
(
!
fctx
->
quota
.
root
||
strcmp
(
fctx
->
quota
.
root
,
qr
))
{
/* Different quotaroot - read it */
syslog
(
LOG_DEBUG
,
"reading quota for '%s'"
,
qr
);
fctx
->
quota
.
root
=
strcpy
(
prevroot
,
qr
);
quota_read
(
&
fctx
->
quota
,
NULL
,
0
);
}
buf_reset
(
&
fctx
->
buf
);
if
(
!
xmlStrcmp
(
name
,
BAD_CAST
"quota-available-bytes"
))
{
/* Calculate limit in bytes and subtract usage */
quota_t
limit
=
fctx
->
quota
.
limits
[
QUOTA_STORAGE
]
*
quota_units
[
QUOTA_STORAGE
];
buf_printf
(
&
fctx
->
buf
,
QUOTA_T_FMT
,
limit
-
fctx
->
quota
.
useds
[
QUOTA_STORAGE
]);
}
else
if
(
fctx
->
record
)
{
/* Bytes used by resource */
buf_printf
(
&
fctx
->
buf
,
"%u"
,
fctx
->
record
->
size
);
}
else
if
(
fctx
->
mailbox
)
{
/* Bytes used by calendar collection */
buf_printf
(
&
fctx
->
buf
,
QUOTA_T_FMT
,
fctx
->
mailbox
->
i
.
quota_mailbox_used
);
}
else
{
/* Bytes used by entire hierarchy */
buf_printf
(
&
fctx
->
buf
,
QUOTA_T_FMT
,
fctx
->
quota
.
useds
[
QUOTA_STORAGE
]);
}
xml_add_prop
(
HTTP_OK
,
fctx
->
ns
[
NS_DAV
],
&
propstat
[
PROPSTAT_OK
],
name
,
ns
,
BAD_CAST
buf_cstring
(
&
fctx
->
buf
),
0
);
return
0
;
}
/* Callback to fetch DAV:current-user-principal */
EXPORTED
int
propfind_curprin
(
const
xmlChar
*
name
,
xmlNsPtr
ns
,
struct
propfind_ctx
*
fctx
,
xmlNodePtr
resp
__attribute__
((
unused
)),
struct
propstat
propstat
[],
void
*
rock
)
{
xmlNodePtr
node
=
xml_add_prop
(
HTTP_OK
,
fctx
->
ns
[
NS_DAV
],
&
propstat
[
PROPSTAT_OK
],
name
,
ns
,
NULL
,
0
);
if
(
httpd_userid
)
{
xmlNodePtr
expand
=
(
xmlNodePtr
)
rock
;
buf_reset
(
&
fctx
->
buf
);
if
(
strchr
(
httpd_userid
,
'@'
))
{
buf_printf
(
&
fctx
->
buf
,
"%s/user/%s/"
,
namespace_principal
.
prefix
,
httpd_userid
);
}
else
if
(
httpd_extradomain
)
{
buf_printf
(
&
fctx
->
buf
,
"%s/user/%s@%s/"
,
namespace_principal
.
prefix
,
httpd_userid
,
httpd_extradomain
);
}
else
{
buf_printf
(
&
fctx
->
buf
,
"%s/user/%s@%s/"
,
namespace_principal
.
prefix
,
httpd_userid
,
config_defdomain
);
}
if
(
expand
)
{
/* Return properties for this URL */
expand_property
(
expand
,
fctx
,
buf_cstring
(
&
fctx
->
buf
),
&
principal_parse_path
,
principal_props
,
node
,
0
);
}
else
{
/* Return just the URL */
xml_add_href
(
node
,
NULL
,
buf_cstring
(
&
fctx
->
buf
));
}
}
else
{
xmlNewChild
(
node
,
NULL
,
BAD_CAST
"unauthenticated"
,
NULL
);
}
return
0
;
}
/* Callback to fetch DAV:add-member */
int
propfind_addmember
(
const
xmlChar
*
name
,
xmlNsPtr
ns
,
struct
propfind_ctx
*
fctx
,
xmlNodePtr
resp
__attribute__
((
unused
)),
struct
propstat
propstat
[],
void
*
rock
__attribute__
((
unused
)))
{
xmlNodePtr
node
;
int
len
;
if
(
!
fctx
->
req_tgt
->
collection
||
!
strcmp
(
fctx
->
req_tgt
->
collection
,
SCHED_INBOX
)
||
!
strcmp
(
fctx
->
req_tgt
->
collection
,
SCHED_OUTBOX
))
{
/* Only allowed on non-scheduling collections */
return
HTTP_NOT_FOUND
;
}
node
=
xml_add_prop
(
HTTP_OK
,
fctx
->
ns
[
NS_DAV
],
&
propstat
[
PROPSTAT_OK
],
name
,
ns
,
NULL
,
0
);
len
=
fctx
->
req_tgt
->
resource
?
(
size_t
)
(
fctx
->
req_tgt
->
resource
-
fctx
->
req_tgt
->
path
)
:
strlen
(
fctx
->
req_tgt
->
path
);
buf_reset
(
&
fctx
->
buf
);
buf_printf
(
&
fctx
->
buf
,
"%.*s?action=add-member"
,
len
,
fctx
->
req_tgt
->
path
);
xml_add_href
(
node
,
NULL
,
buf_cstring
(
&
fctx
->
buf
));
return
0
;
}
static
int
get_synctoken
(
struct
mailbox
*
mailbox
,
struct
buf
*
buf
)
{
buf_reset
(
buf
);
buf_printf
(
buf
,
SYNC_TOKEN_URL_SCHEME
"%u-"
MODSEQ_FMT
,
mailbox
->
i
.
uidvalidity
,
mailbox
->
i
.
highestmodseq
);
return
0
;
}
/* Callback to fetch DAV:sync-token and CS:getctag */
int
propfind_sync_token
(
const
xmlChar
*
name
,
xmlNsPtr
ns
,
struct
propfind_ctx
*
fctx
,
xmlNodePtr
resp
__attribute__
((
unused
)),
struct
propstat
propstat
[],
void
*
rock
__attribute__
((
unused
)))
{
if
(
!
fctx
->
req_tgt
->
collection
||
/* until we support sync on cal-home */
!
fctx
->
mailbox
||
fctx
->
record
)
return
HTTP_NOT_FOUND
;
/* not defined on the top-level collection either (aka #calendars) */
if
(
!
fctx
->
req_tgt
->
collection
)
return
HTTP_NOT_FOUND
;
get_synctoken
(
fctx
->
mailbox
,
&
fctx
->
buf
);
xml_add_prop
(
HTTP_OK
,
fctx
->
ns
[
NS_DAV
],
&
propstat
[
PROPSTAT_OK
],
name
,
ns
,
BAD_CAST
buf_cstring
(
&
fctx
->
buf
),
0
);
return
0
;
}
/* Callback to fetch properties from resource header */
int
propfind_fromhdr
(
const
xmlChar
*
name
,
xmlNsPtr
ns
,
struct
propfind_ctx
*
fctx
,
xmlNodePtr
resp
__attribute__
((
unused
)),
struct
propstat
propstat
[],
void
*
rock
)
{
const
char
*
hdrname
=
(
const
char
*
)
rock
;
int
r
=
HTTP_NOT_FOUND
;
if
(
fctx
->
record
&&
(
mailbox_cached_header
(
hdrname
)
!=
BIT32_MAX
)
&&
!
mailbox_cacherecord
(
fctx
->
mailbox
,
fctx
->
record
))
{
unsigned
size
;
struct
protstream
*
stream
;
hdrcache_t
hdrs
=
NULL
;
const
char
**
hdr
;
size
=
cacheitem_size
(
fctx
->
record
,
CACHE_HEADERS
);
stream
=
prot_readmap
(
cacheitem_base
(
fctx
->
record
,
CACHE_HEADERS
),
size
);
hdrs
=
spool_new_hdrcache
();
spool_fill_hdrcache
(
stream
,
NULL
,
hdrs
,
NULL
);
prot_free
(
stream
);
if
((
hdr
=
spool_getheader
(
hdrs
,
(
const
char
*
)
hdrname
)))
{
xml_add_prop
(
HTTP_OK
,
fctx
->
ns
[
NS_DAV
],
&
propstat
[
PROPSTAT_OK
],
name
,
ns
,
BAD_CAST
hdr
[
0
],
0
);
r
=
0
;
}
spool_free_hdrcache
(
hdrs
);
}
return
r
;
}
static
struct
flaggedresources
{
const
char
*
name
;
int
flag
;
}
fres
[]
=
{
{
"answered"
,
FLAG_ANSWERED
},
{
"flagged"
,
FLAG_FLAGGED
},
{
"seen"
,
FLAG_SEEN
},
{
NULL
,
0
}
/* last is always NULL */
};
/* Callback to write a property to annotation DB */
static
int
proppatch_toresource
(
xmlNodePtr
prop
,
unsigned
set
,
struct
proppatch_ctx
*
pctx
,
struct
propstat
propstat
[],
void
*
rock
__attribute__
((
unused
)))
{
xmlChar
*
freeme
=
NULL
;
annotate_state_t
*
astate
=
NULL
;
struct
buf
value
=
BUF_INITIALIZER
;
int
r
=
1
;
/* default to error */
/* flags only store "exists" */
if
(
!
strcmp
((
const
char
*
)
prop
->
ns
->
href
,
XML_NS_SYSFLAG
))
{
struct
flaggedresources
*
frp
;
int
isset
;
for
(
frp
=
fres
;
frp
->
name
;
frp
++
)
{
if
(
strcasecmp
((
const
char
*
)
prop
->
name
,
frp
->
name
))
continue
;
r
=
0
;
/* ok to do nothing */
isset
=
pctx
->
record
->
system_flags
&
frp
->
flag
;
if
(
set
)
{
if
(
isset
)
goto
done
;
pctx
->
record
->
system_flags
|=
frp
->
flag
;
}
else
{
if
(
!
isset
)
goto
done
;
pctx
->
record
->
system_flags
&=
~
frp
->
flag
;
}
r
=
mailbox_rewrite_index_record
(
pctx
->
mailbox
,
pctx
->
record
);
goto
done
;
}
goto
done
;
}
if
(
!
strcmp
((
const
char
*
)
prop
->
ns
->
href
,
XML_NS_USERFLAG
))
{
int
userflag
=
0
;
int
isset
;
r
=
mailbox_user_flag
(
pctx
->
mailbox
,
(
const
char
*
)
prop
->
name
,
&
userflag
,
1
);
if
(
r
)
goto
done
;
isset
=
pctx
->
record
->
user_flags
[
userflag
/
32
]
&
(
1
<<
userflag
%
31
);
if
(
set
)
{
if
(
isset
)
goto
done
;
pctx
->
record
->
user_flags
[
userflag
/
32
]
|=
(
1
<<
userflag
%
31
);
}
else
{
if
(
!
isset
)
goto
done
;
pctx
->
record
->
user_flags
[
userflag
/
32
]
&=
~
(
1
<<
userflag
%
31
);
}
r
=
mailbox_rewrite_index_record
(
pctx
->
mailbox
,
pctx
->
record
);
goto
done
;
}
/* otherwise it's a database annotation */
buf_reset
(
&
pctx
->
buf
);
buf_printf
(
&
pctx
->
buf
,
ANNOT_NS
"<%s>%s"
,
(
const
char
*
)
prop
->
ns
->
href
,
prop
->
name
);
if
(
set
)
{
freeme
=
xmlNodeGetContent
(
prop
);
buf_init_ro_cstr
(
&
value
,
(
const
char
*
)
freeme
);
}
r
=
mailbox_get_annotate_state
(
pctx
->
mailbox
,
pctx
->
record
->
uid
,
&
astate
);
if
(
!
r
)
r
=
annotate_state_write
(
astate
,
buf_cstring
(
&
pctx
->
buf
),
""
,
&
value
);
/* we need to rewrite the record to update the modseq because the layering
* of annotations and mailboxes is broken */
if
(
!
r
)
r
=
mailbox_rewrite_index_record
(
pctx
->
mailbox
,
pctx
->
record
);
done
:
if
(
!
r
)
{
xml_add_prop
(
HTTP_OK
,
pctx
->
ns
[
NS_DAV
],
&
propstat
[
PROPSTAT_OK
],
prop
->
name
,
prop
->
ns
,
NULL
,
0
);
}
else
{
xml_add_prop
(
HTTP_SERVER_ERROR
,
pctx
->
ns
[
NS_DAV
],
&
propstat
[
PROPSTAT_ERROR
],
prop
->
name
,
prop
->
ns
,
NULL
,
0
);
}
if
(
freeme
)
xmlFree
(
freeme
);
return
0
;
}
/* Callback to read a property from annotation DB */
static
int
propfind_fromresource
(
const
xmlChar
*
name
,
xmlNsPtr
ns
,
struct
propfind_ctx
*
fctx
,
xmlNodePtr
resp
__attribute__
((
unused
)),
struct
propstat
propstat
[],
void
*
rock
__attribute__
((
unused
)))
{
struct
buf
attrib
=
BUF_INITIALIZER
;
xmlNodePtr
node
;
int
r
=
0
;
/* default no error */
if
(
!
strcmp
((
const
char
*
)
ns
->
href
,
XML_NS_SYSFLAG
))
{
struct
flaggedresources
*
frp
;
int
isset
;
for
(
frp
=
fres
;
frp
->
name
;
frp
++
)
{
if
(
strcasecmp
((
const
char
*
)
name
,
frp
->
name
))
continue
;
isset
=
fctx
->
record
->
system_flags
&
frp
->
flag
;
if
(
isset
)
buf_setcstr
(
&
attrib
,
"1"
);
goto
done
;
}
goto
done
;
}
if
(
!
strcmp
((
const
char
*
)
ns
->
href
,
XML_NS_USERFLAG
))
{
int
userflag
=
0
;
int
isset
;
r
=
mailbox_user_flag
(
fctx
->
mailbox
,
(
const
char
*
)
name
,
&
userflag
,
0
);
if
(
r
)
goto
done
;
isset
=
fctx
->
record
->
user_flags
[
userflag
/
32
]
&
(
1
<<
userflag
%
31
);
if
(
isset
)
buf_setcstr
(
&
attrib
,
"1"
);
goto
done
;
}
/* otherwise it's a DB annotation */
buf_reset
(
&
fctx
->
buf
);
buf_printf
(
&
fctx
->
buf
,
ANNOT_NS
"<%s>%s"
,
(
const
char
*
)
ns
->
href
,
name
);
r
=
annotatemore_msg_lookup
(
fctx
->
mailbox
->
name
,
fctx
->
record
->
uid
,
buf_cstring
(
&
fctx
->
buf
),
NULL
,
&
attrib
);
done
:
if
(
r
)
return
HTTP_SERVER_ERROR
;
if
(
!
buf_len
(
&
attrib
))
return
HTTP_NOT_FOUND
;
node
=
xml_add_prop
(
HTTP_OK
,
fctx
->
ns
[
NS_DAV
],
&
propstat
[
PROPSTAT_OK
],
name
,
ns
,
NULL
,
0
);
xmlAddChild
(
node
,
xmlNewCDataBlock
(
fctx
->
root
->
doc
,
BAD_CAST
buf_cstring
(
&
attrib
),
buf_len
(
&
attrib
)));
return
0
;
}
/* Callback to read a property from annotation DB */
int
propfind_fromdb
(
const
xmlChar
*
name
,
xmlNsPtr
ns
,
struct
propfind_ctx
*
fctx
,
xmlNodePtr
resp
__attribute__
((
unused
)),
struct
propstat
propstat
[],
void
*
rock
__attribute__
((
unused
)))
{
struct
buf
attrib
=
BUF_INITIALIZER
;
xmlNodePtr
node
;
int
r
=
0
;
if
(
fctx
->
req_tgt
->
resource
)
return
propfind_fromresource
(
name
,
ns
,
fctx
,
NULL
,
propstat
,
NULL
);
buf_reset
(
&
fctx
->
buf
);
buf_printf
(
&
fctx
->
buf
,
ANNOT_NS
"<%s>%s"
,
(
const
char
*
)
ns
->
href
,
name
);
if
(
fctx
->
mailbox
&&
!
fctx
->
record
&&
!
(
r
=
annotatemore_lookupmask
(
fctx
->
mailbox
->
name
,
buf_cstring
(
&
fctx
->
buf
),
httpd_userid
,
&
attrib
)))
{
if
(
!
buf_len
(
&
attrib
)
&&
!
xmlStrcmp
(
name
,
BAD_CAST
"displayname"
))
{
/* Special case empty displayname -- use last segment of path */
buf_setcstr
(
&
attrib
,
strrchr
(
fctx
->
mailbox
->
name
,
'.'
)
+
1
);
}
}
if
(
r
)
return
HTTP_SERVER_ERROR
;
if
(
!
buf_len
(
&
attrib
))
return
HTTP_NOT_FOUND
;
node
=
xml_add_prop
(
HTTP_OK
,
fctx
->
ns
[
NS_DAV
],
&
propstat
[
PROPSTAT_OK
],
name
,
ns
,
NULL
,
0
);
xmlAddChild
(
node
,
xmlNewCDataBlock
(
fctx
->
root
->
doc
,
BAD_CAST
buf_cstring
(
&
attrib
),
buf_len
(
&
attrib
)));
buf_free
(
&
attrib
);
return
0
;
}
/* Callback to write a property to annotation DB */
int
proppatch_todb
(
xmlNodePtr
prop
,
unsigned
set
,
struct
proppatch_ctx
*
pctx
,
struct
propstat
propstat
[],
void
*
rock
)
{
xmlChar
*
freeme
=
NULL
;
annotate_state_t
*
astate
=
NULL
;
struct
buf
value
=
BUF_INITIALIZER
;
int
r
;
if
(
pctx
->
req_tgt
->
resource
)
return
proppatch_toresource
(
prop
,
set
,
pctx
,
propstat
,
NULL
);
buf_reset
(
&
pctx
->
buf
);
buf_printf
(
&
pctx
->
buf
,
ANNOT_NS
"<%s>%s"
,
(
const
char
*
)
prop
->
ns
->
href
,
prop
->
name
);
if
(
set
)
{
if
(
rock
)
{
buf_init_ro_cstr
(
&
value
,
(
const
char
*
)
rock
);
}
else
{
freeme
=
xmlNodeGetContent
(
prop
);
buf_init_ro_cstr
(
&
value
,
(
const
char
*
)
freeme
);
}
}
r
=
mailbox_get_annotate_state
(
pctx
->
mailbox
,
0
,
&
astate
);
if
(
!
r
)
r
=
annotate_state_writemask
(
astate
,
buf_cstring
(
&
pctx
->
buf
),
httpd_userid
,
&
value
);
if
(
!
r
)
{
xml_add_prop
(
HTTP_OK
,
pctx
->
ns
[
NS_DAV
],
&
propstat
[
PROPSTAT_OK
],
prop
->
name
,
prop
->
ns
,
NULL
,
0
);
}
else
{
xml_add_prop
(
HTTP_SERVER_ERROR
,
pctx
->
ns
[
NS_DAV
],
&
propstat
[
PROPSTAT_ERROR
],
prop
->
name
,
prop
->
ns
,
NULL
,
0
);
}
if
(
freeme
)
xmlFree
(
freeme
);
return
0
;
}
/* annotemore_findall callback for adding dead properties (allprop/propname) */
static
int
allprop_cb
(
const
char
*
mailbox
__attribute__
((
unused
)),
uint32_t
uid
__attribute__
((
unused
)),
const
char
*
entry
,
const
char
*
userid
,
const
struct
buf
*
attrib
,
void
*
rock
)
{
struct
allprop_rock
*
arock
=
(
struct
allprop_rock
*
)
rock
;
const
struct
prop_entry
*
pentry
;
char
*
href
,
*
name
;
xmlNsPtr
ns
;
xmlNodePtr
node
;
/* Make sure its a shared entry or the user's private one */
if
(
*
userid
&&
strcmp
(
userid
,
arock
->
fctx
->
userid
))
return
0
;
/* Split entry into namespace href and name ( <href>name ) */
buf_setcstr
(
&
arock
->
fctx
->
buf
,
entry
+
strlen
(
ANNOT_NS
)
+
1
);
href
=
(
char
*
)
buf_cstring
(
&
arock
->
fctx
->
buf
);
if
((
name
=
strchr
(
href
,
'>'
)))
*
name
++
=
'\0'
;
else
if
((
name
=
strchr
(
href
,
':'
)))
*
name
++
=
'\0'
;
/* Look for a match against live properties */
for
(
pentry
=
arock
->
fctx
->
lprops
;
pentry
->
name
&&
(
strcmp
(
name
,
pentry
->
name
)
||
strcmp
(
href
,
known_namespaces
[
pentry
->
ns
].
href
));
pentry
++
);
if
(
pentry
->
name
&&
(
arock
->
fctx
->
mode
==
PROPFIND_ALL
/* Skip all live properties */
||
(
pentry
->
flags
&
PROP_ALLPROP
)))
/* Skip those already included */
return
0
;
/* Look for an instance of this namespace in our response */
ns
=
hash_lookup
(
href
,
arock
->
fctx
->
ns_table
);
if
(
!
ns
)
{
char
prefix
[
5
];
snprintf
(
prefix
,
sizeof
(
prefix
),
"X%u"
,
arock
->
fctx
->
prefix_count
++
);
ns
=
xmlNewNs
(
arock
->
fctx
->
root
,
BAD_CAST
href
,
BAD_CAST
prefix
);
hash_insert
(
href
,
ns
,
arock
->
fctx
->
ns_table
);
}
/* Add the dead property to the response */
node
=
xml_add_prop
(
HTTP_OK
,
arock
->
fctx
->
ns
[
NS_DAV
],
&
arock
->
propstat
[
PROPSTAT_OK
],
BAD_CAST
name
,
ns
,
NULL
,
0
);
if
(
arock
->
fctx
->
mode
==
PROPFIND_ALL
)
{
xmlAddChild
(
node
,
xmlNewCDataBlock
(
arock
->
fctx
->
root
->
doc
,
BAD_CAST
attrib
->
s
,
attrib
->
len
));
}
return
0
;
}
static
int
prescreen_prop
(
const
struct
prop_entry
*
entry
,
xmlNodePtr
prop
,
struct
propfind_ctx
*
fctx
)
{
unsigned
allowed
=
1
;
if
(
fctx
->
req_tgt
->
resource
&&
!
(
entry
->
flags
&
PROP_RESOURCE
))
allowed
=
0
;
else
if
(
entry
->
flags
&
PROP_PRESCREEN
)
{
void
*
rock
=
(
entry
->
flags
&
PROP_NEEDPROP
)
?
prop
:
entry
->
rock
;
allowed
=
!
entry
->
get
(
prop
->
name
,
NULL
,
fctx
,
NULL
,
NULL
,
rock
);
}
return
allowed
;
}
/* Parse the requested properties and create a linked list of fetch callbacks.
* The list gets reused for each href if Depth > 0
*/
static
int
preload_proplist
(
xmlNodePtr
proplist
,
struct
propfind_ctx
*
fctx
)
{
int
ret
=
0
;
xmlNodePtr
prop
;
const
struct
prop_entry
*
entry
;
struct
propfind_entry_list
*
tail
=
NULL
;
switch
(
fctx
->
mode
)
{
case
PROPFIND_ALL
:
case
PROPFIND_NAME
:
/* Add live properties for allprop/propname */
for
(
entry
=
fctx
->
lprops
;
entry
->
name
;
entry
++
)
{
if
(
entry
->
flags
&
PROP_ALLPROP
)
{
/* Pre-screen request based on prop flags */
int
allowed
=
prescreen_prop
(
entry
,
NULL
,
fctx
);
if
(
allowed
||
fctx
->
mode
==
PROP_ALLPROP
)
{
struct
propfind_entry_list
*
nentry
=
xzmalloc
(
sizeof
(
struct
propfind_entry_list
));
ensure_ns
(
fctx
->
ns
,
entry
->
ns
,
fctx
->
root
,
known_namespaces
[
entry
->
ns
].
href
,
known_namespaces
[
entry
->
ns
].
prefix
);
nentry
->
name
=
xmlStrdup
(
BAD_CAST
entry
->
name
);
nentry
->
ns
=
fctx
->
ns
[
entry
->
ns
];
if
(
allowed
)
{
nentry
->
flags
=
entry
->
flags
;
nentry
->
get
=
entry
->
get
;
nentry
->
rock
=
entry
->
rock
;
}
/* Append new entry to linked list */
if
(
tail
)
{
tail
->
next
=
nentry
;
tail
=
nentry
;
}
else
tail
=
fctx
->
elist
=
nentry
;
}
}
}
/* Fall through and build hash table of namespaces */
case
PROPFIND_EXPAND
:
/* Add all namespaces attached to the response to our hash table */
if
(
!
fctx
->
ns_table
->
size
)
{
xmlNsPtr
nsDef
;
construct_hash_table
(
fctx
->
ns_table
,
10
,
1
);
for
(
nsDef
=
fctx
->
root
->
nsDef
;
nsDef
;
nsDef
=
nsDef
->
next
)
{
hash_insert
((
const
char
*
)
nsDef
->
href
,
nsDef
,
fctx
->
ns_table
);
}
}
}
/* Iterate through requested properties */
for
(
prop
=
proplist
;
!*
fctx
->
ret
&&
prop
;
prop
=
prop
->
next
)
{
if
(
prop
->
type
==
XML_ELEMENT_NODE
)
{
struct
propfind_entry_list
*
nentry
;
xmlChar
*
name
;
xmlNsPtr
ns
=
prop
->
ns
;
if
(
fctx
->
mode
==
PROPFIND_EXPAND
)
{
/* Get name/namespace from <property> */
xmlChar
*
namespace
;
name
=
xmlGetProp
(
prop
,
BAD_CAST
"name"
);
namespace
=
xmlGetProp
(
prop
,
BAD_CAST
"namespace"
);
if
(
namespace
)
{
const
char
*
ns_href
=
(
const
char
*
)
namespace
;
unsigned
i
;
/* Look for this namespace in our known array */
for
(
i
=
0
;
i
<
NUM_NAMESPACE
;
i
++
)
{
if
(
!
strcmp
(
ns_href
,
known_namespaces
[
i
].
href
))
{
ensure_ns
(
fctx
->
ns
,
i
,
fctx
->
root
,
known_namespaces
[
i
].
href
,
known_namespaces
[
i
].
prefix
);
ns
=
fctx
->
ns
[
i
];
break
;
}
}
if
(
i
>=
NUM_NAMESPACE
)
{
/* Look for this namespace in hash table */
ns
=
hash_lookup
(
ns_href
,
fctx
->
ns_table
);
if
(
!
ns
)
{
char
prefix
[
6
];
snprintf
(
prefix
,
sizeof
(
prefix
),
"X%X"
,
strhash
(
ns_href
)
&
0xffff
);
ns
=
xmlNewNs
(
fctx
->
root
,
BAD_CAST
ns_href
,
BAD_CAST
prefix
);
hash_insert
(
ns_href
,
ns
,
fctx
->
ns_table
);
}
}
xmlFree
(
namespace
);
}
}
else
{
/* node IS the property */
name
=
xmlStrdup
(
prop
->
name
);
}
/* Look for a match against our known properties */
for
(
entry
=
fctx
->
lprops
;
entry
->
name
&&
(
strcmp
((
const
char
*
)
prop
->
name
,
entry
->
name
)
||
strcmp
((
const
char
*
)
prop
->
ns
->
href
,
known_namespaces
[
entry
->
ns
].
href
));
entry
++
);
/* Skip properties already included by allprop */
if
(
fctx
->
mode
==
PROPFIND_ALL
&&
(
entry
->
flags
&
PROP_ALLPROP
))
continue
;
nentry
=
xzmalloc
(
sizeof
(
struct
propfind_entry_list
));
nentry
->
name
=
name
;
nentry
->
ns
=
ns
;
if
(
entry
->
name
)
{
/* Found a match - Pre-screen request based on prop flags */
if
(
prescreen_prop
(
entry
,
prop
,
fctx
))
{
nentry
->
flags
=
entry
->
flags
;
nentry
->
get
=
entry
->
get
;
if
((
entry
->
flags
&
PROP_NEEDPROP
)
||
((
entry
->
flags
&
PROP_EXPAND
)
&&
fctx
->
mode
==
PROPFIND_EXPAND
))
nentry
->
rock
=
prop
;
else
nentry
->
rock
=
entry
->
rock
;
}
ret
=
*
fctx
->
ret
;
}
else
{
/* No match, treat as a dead property. Need to look for both collections
* resources */
nentry
->
flags
=
PROP_COLLECTION
|
PROP_RESOURCE
;
nentry
->
get
=
propfind_fromdb
;
}
/* Append new entry to linked list */
if
(
tail
)
{
tail
->
next
=
nentry
;
tail
=
nentry
;
}
else
tail
=
fctx
->
elist
=
nentry
;
}
}
return
ret
;
}
/* Execute the given property patch instructions */
static
int
do_proppatch
(
struct
proppatch_ctx
*
pctx
,
xmlNodePtr
instr
)
{
struct
propstat
propstat
[
NUM_PROPSTAT
];
int
i
;
memset
(
propstat
,
0
,
NUM_PROPSTAT
*
sizeof
(
struct
propstat
));
/* Iterate through propertyupdate children */
for
(;
instr
;
instr
=
instr
->
next
)
{
if
(
instr
->
type
==
XML_ELEMENT_NODE
)
{
xmlNodePtr
prop
;
unsigned
set
=
0
;
if
(
!
xmlStrcmp
(
instr
->
name
,
BAD_CAST
"set"
))
set
=
1
;
else
if
((
pctx
->
meth
==
METH_PROPPATCH
)
&&
!
xmlStrcmp
(
instr
->
name
,
BAD_CAST
"remove"
))
set
=
0
;
else
{
syslog
(
LOG_INFO
,
"Unknown PROPPATCH instruction"
);
pctx
->
err
->
desc
=
"Unknown PROPPATCH instruction"
;
return
HTTP_BAD_REQUEST
;
}
/* Find child element */
for
(
prop
=
instr
->
children
;
prop
&&
prop
->
type
!=
XML_ELEMENT_NODE
;
prop
=
prop
->
next
);
if
(
!
prop
||
xmlStrcmp
(
prop
->
name
,
BAD_CAST
"prop"
))
{
pctx
->
err
->
desc
=
"Missing prop element"
;
return
HTTP_BAD_REQUEST
;
}
/* Iterate through requested properties */
for
(
prop
=
prop
->
children
;
prop
;
prop
=
prop
->
next
)
{
if
(
prop
->
type
==
XML_ELEMENT_NODE
)
{
const
struct
prop_entry
*
entry
;
/* Look for a match against our known properties */
for
(
entry
=
pctx
->
lprops
;
entry
->
name
&&
(
strcmp
((
const
char
*
)
prop
->
name
,
entry
->
name
)
||
strcmp
((
const
char
*
)
prop
->
ns
->
href
,
known_namespaces
[
entry
->
ns
].
href
));
entry
++
);
if
(
entry
->
name
)
{
if
(
!
entry
->
put
)
{
/* Protected property */
xml_add_prop
(
HTTP_FORBIDDEN
,
pctx
->
ns
[
NS_DAV
],
&
propstat
[
PROPSTAT_FORBID
],
prop
->
name
,
prop
->
ns
,
NULL
,
DAV_PROT_PROP
);
*
pctx
->
ret
=
HTTP_FORBIDDEN
;
}
else
{
/* Write "live" property */
entry
->
put
(
prop
,
set
,
pctx
,
propstat
,
entry
->
rock
);
}
}
else
{
/* Write "dead" property */
proppatch_todb
(
prop
,
set
,
pctx
,
propstat
,
NULL
);
}
}
}
}
}
/* One or more of the properties failed */
if
(
*
pctx
->
ret
&&
propstat
[
PROPSTAT_OK
].
root
)
{
/* 200 status must become 424 */
propstat
[
PROPSTAT_FAILEDDEP
].
root
=
propstat
[
PROPSTAT_OK
].
root
;
propstat
[
PROPSTAT_FAILEDDEP
].
status
=
HTTP_FAILED_DEP
;
propstat
[
PROPSTAT_OK
].
root
=
NULL
;
}
/* Add status and optional error to the propstat elements
and then add them to the response element */
for
(
i
=
0
;
i
<
NUM_PROPSTAT
;
i
++
)
{
struct
propstat
*
stat
=
&
propstat
[
i
];
if
(
stat
->
root
)
{
xmlNewChild
(
stat
->
root
,
NULL
,
BAD_CAST
"status"
,
BAD_CAST
http_statusline
(
stat
->
status
));
if
(
stat
->
precond
)
{
struct
error_t
error
=
{
NULL
,
stat
->
precond
,
NULL
,
0
};
xml_add_error
(
stat
->
root
,
&
error
,
pctx
->
ns
);
}
xmlAddChild
(
pctx
->
root
,
stat
->
root
);
}
}
return
0
;
}
/* Parse an XML body into a tree */
int
parse_xml_body
(
struct
transaction_t
*
txn
,
xmlNodePtr
*
root
)
{
const
char
**
hdr
;
xmlParserCtxtPtr
ctxt
;
xmlDocPtr
doc
=
NULL
;
int
r
=
0
;
*
root
=
NULL
;
/* Read body */
txn
->
req_body
.
flags
|=
BODY_DECODE
;
r
=
http_read_body
(
httpd_in
,
httpd_out
,
txn
->
req_hdrs
,
&
txn
->
req_body
,
&
txn
->
error
.
desc
);
if
(
r
)
{
txn
->
flags
.
conn
=
CONN_CLOSE
;
return
r
;
}
if
(
!
buf_len
(
&
txn
->
req_body
.
payload
))
return
0
;
/* Check Content-Type */
if
(
!
(
hdr
=
spool_getheader
(
txn
->
req_hdrs
,
"Content-Type"
))
||
(
!
is_mediatype
(
"text/xml"
,
hdr
[
0
])
&&
!
is_mediatype
(
"application/xml"
,
hdr
[
0
])))
{
txn
->
error
.
desc
=
"This method requires an XML body
\r\n
"
;
return
HTTP_BAD_MEDIATYPE
;
}
/* Parse the XML request */
ctxt
=
xmlNewParserCtxt
();
if
(
ctxt
)
{
doc
=
xmlCtxtReadMemory
(
ctxt
,
buf_cstring
(
&
txn
->
req_body
.
payload
),
buf_len
(
&
txn
->
req_body
.
payload
),
NULL
,
NULL
,
XML_PARSE_NOWARNING
);
xmlFreeParserCtxt
(
ctxt
);
}
if
(
!
doc
)
{
txn
->
error
.
desc
=
"Unable to parse XML body
\r\n
"
;
return
HTTP_BAD_REQUEST
;
}
/* Get the root element of the XML request */
if
(
!
(
*
root
=
xmlDocGetRootElement
(
doc
)))
{
txn
->
error
.
desc
=
"Missing root element in request
\r\n
"
;
return
HTTP_BAD_REQUEST
;
}
return
0
;
}
static
void
set_system_acls
(
struct
buf
*
buf
,
mbentry_t
*
mbentry
)
{
char
*
userid
,
*
aclstr
;
/* Parse the ACL string (userid/rights pairs) */
userid
=
aclstr
=
xstrdup
(
mbentry
->
acl
);
while
(
userid
)
{
char
*
rightstr
,
*
nextid
;
rightstr
=
strchr
(
userid
,
'\t'
);
if
(
!
rightstr
)
break
;
*
rightstr
++
=
'\0'
;
nextid
=
strchr
(
rightstr
,
'\t'
);
if
(
!
nextid
)
break
;
*
nextid
++
=
'\0'
;
/* we only want system users */
if
(
is_system_user
(
userid
))
buf_printf
(
buf
,
"%s
\t
%s
\t
"
,
userid
,
rightstr
);
userid
=
nextid
;
}
free
(
aclstr
);
}
/* Perform an ACL request
*
* preconditions:
* DAV:no-ace-conflict
* DAV:no-protected-ace-conflict
* DAV:no-inherited-ace-conflict
* DAV:limited-number-of-aces
* DAV:deny-before-grant
* DAV:grant-only
* DAV:no-invert
* DAV:no-abstract
* DAV:not-supported-privilege
* DAV:missing-required-principal
* DAV:recognized-principal
* DAV:allowed-principal
*/
int
meth_acl
(
struct
transaction_t
*
txn
,
void
*
params
)
{
struct
meth_params
*
aparams
=
(
struct
meth_params
*
)
params
;
int
ret
=
0
,
r
,
rights
,
overwrite
=
OVERWRITE_YES
;
xmlDocPtr
indoc
=
NULL
;
xmlNodePtr
root
,
ace
;
struct
mailbox
*
mailbox
=
NULL
;
mbentry_t
*
mbentry
=
NULL
;
struct
buf
acl
=
BUF_INITIALIZER
;
const
char
**
hdr
;
/* Response should not be cached */
txn
->
flags
.
cc
|=
CC_NOCACHE
;
/* Parse the path */
if
((
r
=
aparams
->
parse_path
(
txn
->
req_uri
->
path
,
&
txn
->
req_tgt
,
&
txn
->
error
.
desc
)))
return
r
;
/* Make sure method is allowed (only allowed on collections) */
if
(
!
(
txn
->
req_tgt
.
allow
&
ALLOW_WRITECOL
))
{
txn
->
error
.
desc
=
"ACLs can only be set on collections
\r\n
"
;
syslog
(
LOG_DEBUG
,
"Tried to set ACL on non-collection"
);
return
HTTP_NOT_ALLOWED
;
}
/* Locate the mailbox */
r
=
http_mlookup
(
txn
->
req_tgt
.
mboxname
,
&
mbentry
,
NULL
);
if
(
r
)
{
syslog
(
LOG_ERR
,
"mlookup(%s) failed: %s"
,
txn
->
req_tgt
.
mboxname
,
error_message
(
r
));
txn
->
error
.
desc
=
error_message
(
r
);
switch
(
r
)
{
case
IMAP_PERMISSION_DENIED
:
return
HTTP_FORBIDDEN
;
case
IMAP_MAILBOX_NONEXISTENT
:
return
HTTP_NOT_FOUND
;
default
:
return
HTTP_SERVER_ERROR
;
}
}
if
(
!
mboxname_userownsmailbox
(
httpd_userid
,
mbentry
->
name
))
{
/* Check ACL for current user */
rights
=
httpd_myrights
(
httpd_authstate
,
mbentry
->
acl
);
if
(
!
(
rights
&
DACL_ADMIN
))
{
/* DAV:need-privileges */
txn
->
error
.
precond
=
DAV_NEED_PRIVS
;
txn
->
error
.
resource
=
txn
->
req_tgt
.
path
;
txn
->
error
.
rights
=
DACL_ADMIN
;
mboxlist_entry_free
(
&
mbentry
);
return
HTTP_NO_PRIVS
;
}
}
if
(
mbentry
->
server
)
{
/* Remote mailbox */
struct
backend
*
be
;
be
=
proxy_findserver
(
mbentry
->
server
,
&
http_protocol
,
proxy_userid
,
&
backend_cached
,
NULL
,
NULL
,
httpd_in
);
mboxlist_entry_free
(
&
mbentry
);
if
(
!
be
)
return
HTTP_UNAVAILABLE
;
return
http_pipe_req_resp
(
be
,
txn
);
}
/* Local Mailbox */
/* Check for "IMAP-mode" */
if
((
hdr
=
spool_getheader
(
txn
->
req_hdrs
,
"Overwrite"
))
&&
!
strcmp
(
hdr
[
0
],
"F"
))
{
overwrite
=
OVERWRITE_NO
;
}
if
(
overwrite
)
{
/* Open mailbox for writing */
r
=
mailbox_open_iwl
(
txn
->
req_tgt
.
mboxname
,
&
mailbox
);
if
(
r
)
{
syslog
(
LOG_ERR
,
"http_mailbox_open(%s) failed: %s"
,
txn
->
req_tgt
.
mboxname
,
error_message
(
r
));
txn
->
error
.
desc
=
error_message
(
r
);
ret
=
HTTP_SERVER_ERROR
;
goto
done
;
}
}
/* Parse the ACL body */
ret
=
parse_xml_body
(
txn
,
&
root
);
if
(
!
ret
&&
!
root
)
{
txn
->
error
.
desc
=
"Missing request body
\r\n
"
;
ret
=
HTTP_BAD_REQUEST
;
}
if
(
ret
)
goto
done
;
indoc
=
root
->
doc
;
/* Make sure its an DAV:acl element */
if
(
xmlStrcmp
(
root
->
name
,
BAD_CAST
"acl"
))
{
txn
->
error
.
desc
=
"Missing acl element in ACL request
\r\n
"
;
ret
=
HTTP_BAD_REQUEST
;
goto
done
;
}
set_system_acls
(
&
acl
,
mbentry
);
/* Parse the DAV:ace elements */
for
(
ace
=
root
->
children
;
ace
;
ace
=
ace
->
next
)
{
if
(
ace
->
type
==
XML_ELEMENT_NODE
)
{
xmlNodePtr
child
=
NULL
,
prin
=
NULL
,
privs
=
NULL
;
const
char
*
userid
=
NULL
;
int
rights
=
0
;
char
rightstr
[
100
];
struct
request_target_t
tgt
;
for
(
child
=
ace
->
children
;
child
;
child
=
child
->
next
)
{
if
(
child
->
type
==
XML_ELEMENT_NODE
)
{
if
(
!
xmlStrcmp
(
child
->
name
,
BAD_CAST
"principal"
))
{
if
(
prin
)
{
txn
->
error
.
desc
=
"Multiple principals in ACE
\r\n
"
;
ret
=
HTTP_BAD_REQUEST
;
goto
done
;
}
for
(
prin
=
child
->
children
;
prin
&&
prin
->
type
!=
XML_ELEMENT_NODE
;
prin
=
prin
->
next
);
}
else
if
(
!
xmlStrcmp
(
child
->
name
,
BAD_CAST
"grant"
))
{
if
(
privs
)
{
txn
->
error
.
desc
=
"Multiple grant|deny in ACE
\r\n
"
;
ret
=
HTTP_BAD_REQUEST
;
goto
done
;
}
for
(
privs
=
child
->
children
;
privs
&&
privs
->
type
!=
XML_ELEMENT_NODE
;
privs
=
privs
->
next
);
}
else
if
(
!
xmlStrcmp
(
child
->
name
,
BAD_CAST
"deny"
))
{
/* DAV:grant-only */
txn
->
error
.
precond
=
DAV_GRANT_ONLY
;
ret
=
HTTP_FORBIDDEN
;
goto
done
;
}
else
if
(
!
xmlStrcmp
(
child
->
name
,
BAD_CAST
"invert"
))
{
/* DAV:no-invert */
txn
->
error
.
precond
=
DAV_NO_INVERT
;
ret
=
HTTP_FORBIDDEN
;
goto
done
;
}
else
{
txn
->
error
.
desc
=
"Unknown element in ACE
\r\n
"
;
ret
=
HTTP_BAD_REQUEST
;
goto
done
;
}
}
}
if
(
!
xmlStrcmp
(
prin
->
name
,
BAD_CAST
"self"
))
{
userid
=
proxy_userid
;
}
else
if
(
!
xmlStrcmp
(
prin
->
name
,
BAD_CAST
"owner"
))
{
userid
=
mboxname_to_userid
(
txn
->
req_tgt
.
mboxname
);
}
else
if
(
!
xmlStrcmp
(
prin
->
name
,
BAD_CAST
"authenticated"
))
{
userid
=
"anyone"
;
}
else
if
(
!
xmlStrcmp
(
prin
->
name
,
BAD_CAST
"unauthenticated"
))
{
userid
=
"anonymous"
;
}
else
if
(
!
xmlStrcmp
(
prin
->
name
,
BAD_CAST
"href"
))
{
xmlChar
*
href
=
xmlNodeGetContent
(
prin
);
xmlURIPtr
uri
;
const
char
*
errstr
=
NULL
;
size_t
plen
=
strlen
(
namespace_principal
.
prefix
);
uri
=
parse_uri
(
METH_UNKNOWN
,
(
const
char
*
)
href
,
1
,
&
errstr
);
if
(
uri
&&
!
strncmp
(
namespace_principal
.
prefix
,
uri
->
path
,
plen
)
&&
uri
->
path
[
plen
]
==
'/'
)
{
memset
(
&
tgt
,
0
,
sizeof
(
struct
request_target_t
));
tgt
.
namespace
=
URL_NS_PRINCIPAL
;
/* XXX: there is no doubt that this leaks memory */
r
=
principal_parse_path
(
uri
->
path
,
&
tgt
,
&
errstr
);
if
(
!
r
&&
tgt
.
userid
)
userid
=
tgt
.
userid
;
}
if
(
uri
)
xmlFreeURI
(
uri
);
xmlFree
(
href
);
}
if
(
!
userid
)
{
/* DAV:recognized-principal */
txn
->
error
.
precond
=
DAV_RECOG_PRINC
;
ret
=
HTTP_FORBIDDEN
;
goto
done
;
}
if
(
is_system_user
(
userid
))
{
/* DAV:allowed-principal */
txn
->
error
.
precond
=
DAV_ALLOW_PRINC
;
ret
=
HTTP_FORBIDDEN
;
goto
done
;
}
for
(;
privs
;
privs
=
privs
->
next
)
{
if
(
privs
->
type
==
XML_ELEMENT_NODE
)
{
xmlNodePtr
priv
=
privs
->
children
;
for
(;
priv
->
type
!=
XML_ELEMENT_NODE
;
priv
=
priv
->
next
);
if
(
aparams
->
acl_ext
&&
aparams
->
acl_ext
(
txn
,
priv
,
&
rights
))
{
/* Extension (CalDAV) privileges */
if
(
txn
->
error
.
precond
)
{
ret
=
HTTP_FORBIDDEN
;
goto
done
;
}
}
else
if
(
!
xmlStrcmp
(
priv
->
ns
->
href
,
BAD_CAST
XML_NS_DAV
))
{
/* WebDAV privileges */
if
(
!
xmlStrcmp
(
priv
->
name
,
BAD_CAST
"all"
))
{
rights
|=
DACL_ALL
;
}
else
if
(
!
xmlStrcmp
(
priv
->
name
,
BAD_CAST
"read"
))
rights
|=
DACL_READ
;
else
if
(
!
xmlStrcmp
(
priv
->
name
,
BAD_CAST
"write"
))
rights
|=
DACL_WRITE
;
else
if
(
!
xmlStrcmp
(
priv
->
name
,
BAD_CAST
"write-content"
))
rights
|=
DACL_WRITECONT
;
else
if
(
!
xmlStrcmp
(
priv
->
name
,
BAD_CAST
"write-properties"
))
rights
|=
DACL_WRITEPROPS
;
else
if
(
!
xmlStrcmp
(
priv
->
name
,
BAD_CAST
"bind"
))
rights
|=
DACL_BIND
;
else
if
(
!
xmlStrcmp
(
priv
->
name
,
BAD_CAST
"unbind"
))
rights
|=
DACL_UNBIND
;
else
if
(
!
xmlStrcmp
(
priv
->
name
,
BAD_CAST
"read-current-user-privilege-set"
)
||
!
xmlStrcmp
(
priv
->
name
,
BAD_CAST
"read-acl"
)
||
!
xmlStrcmp
(
priv
->
name
,
BAD_CAST
"write-acl"
)
||
!
xmlStrcmp
(
priv
->
name
,
BAD_CAST
"unlock"
))
{
/* DAV:no-abstract */
txn
->
error
.
precond
=
DAV_NO_ABSTRACT
;
ret
=
HTTP_FORBIDDEN
;
goto
done
;
}
else
{
/* DAV:not-supported-privilege */
txn
->
error
.
precond
=
DAV_SUPP_PRIV
;
ret
=
HTTP_FORBIDDEN
;
goto
done
;
}
}
else
if
(
!
xmlStrcmp
(
priv
->
ns
->
href
,
BAD_CAST
XML_NS_CALDAV
))
{
if
(
!
xmlStrcmp
(
priv
->
name
,
BAD_CAST
"read-free-busy"
))
rights
|=
DACL_READFB
;
else
{
/* DAV:not-supported-privilege */
txn
->
error
.
precond
=
DAV_SUPP_PRIV
;
ret
=
HTTP_FORBIDDEN
;
goto
done
;
}
}
else
if
(
!
xmlStrcmp
(
priv
->
ns
->
href
,
BAD_CAST
XML_NS_CYRUS
))
{
/* Cyrus-specific privileges */
if
(
!
xmlStrcmp
(
priv
->
name
,
BAD_CAST
"make-collection"
))
rights
|=
DACL_MKCOL
;
else
if
(
!
xmlStrcmp
(
priv
->
name
,
BAD_CAST
"remove-collection"
))
rights
|=
DACL_RMCOL
;
else
if
(
!
xmlStrcmp
(
priv
->
name
,
BAD_CAST
"add-resource"
))
rights
|=
DACL_ADDRSRC
;
else
if
(
!
xmlStrcmp
(
priv
->
name
,
BAD_CAST
"remove-resource"
))
rights
|=
DACL_RMRSRC
;
else
if
(
!
xmlStrcmp
(
priv
->
name
,
BAD_CAST
"admin"
))
rights
|=
DACL_ADMIN
;
else
{
/* DAV:not-supported-privilege */
txn
->
error
.
precond
=
DAV_SUPP_PRIV
;
ret
=
HTTP_FORBIDDEN
;
goto
done
;
}
}
else
{
/* DAV:not-supported-privilege */
txn
->
error
.
precond
=
DAV_SUPP_PRIV
;
ret
=
HTTP_FORBIDDEN
;
goto
done
;
}
}
}
/* gotta have something to do! */
if
(
rights
)
{
cyrus_acl_masktostr
(
rights
,
rightstr
);
buf_printf
(
&
acl
,
"%s
\t
%s
\t
"
,
userid
,
rightstr
);
}
}
}
r
=
mboxlist_sync_setacls
(
mbentry
->
name
,
buf_cstring
(
&
acl
));
if
(
r
)
{
syslog
(
LOG_ERR
,
"mboxlist_setacl(%s) failed: %s"
,
mbentry
->
name
,
error_message
(
r
));
txn
->
error
.
desc
=
error_message
(
r
);
ret
=
HTTP_SERVER_ERROR
;
goto
done
;
}
response_header
(
HTTP_OK
,
txn
);
done
:
buf_free
(
&
acl
);
if
(
indoc
)
xmlFreeDoc
(
indoc
);
mboxlist_entry_free
(
&
mbentry
);
return
ret
;
}
/* Perform a COPY/MOVE request
*
* preconditions:
* *DAV:need-privileges
*/
int
meth_copy
(
struct
transaction_t
*
txn
,
void
*
params
)
{
struct
meth_params
*
cparams
=
(
struct
meth_params
*
)
params
;
int
ret
=
HTTP_CREATED
,
r
,
precond
,
rights
;
const
char
**
hdr
;
xmlURIPtr
dest_uri
;
static
struct
request_target_t
dest_tgt
;
/* Parsed destination URL */
struct
backend
*
src_be
=
NULL
,
*
dest_be
=
NULL
;
struct
mailbox
*
src_mbox
=
NULL
,
*
dest_mbox
=
NULL
;
mbentry_t
*
mbentry
=
NULL
;
struct
dav_data
*
ddata
;
struct
index_record
src_rec
;
const
char
*
etag
=
NULL
;
time_t
lastmod
=
0
;
unsigned
flags
=
0
;
void
*
src_davdb
=
NULL
,
*
dest_davdb
=
NULL
,
*
obj
=
NULL
;
struct
buf
msg_buf
=
BUF_INITIALIZER
;
/* Response should not be cached */
txn
->
flags
.
cc
|=
CC_NOCACHE
;
/* Parse the source path */
if
((
r
=
cparams
->
parse_path
(
txn
->
req_uri
->
path
,
&
txn
->
req_tgt
,
&
txn
->
error
.
desc
)))
return
r
;
/* Make sure method is allowed (not allowed on collections yet) */
if
(
!
(
txn
->
req_tgt
.
allow
&
ALLOW_WRITE
))
return
HTTP_NOT_ALLOWED
;
/* Check for mandatory Destination header */
if
(
!
(
hdr
=
spool_getheader
(
txn
->
req_hdrs
,
"Destination"
)))
{
txn
->
error
.
desc
=
"Missing Destination header
\r\n
"
;
return
HTTP_BAD_REQUEST
;
}
/* Parse destination URI */
if
(
!
(
dest_uri
=
parse_uri
(
METH_UNKNOWN
,
hdr
[
0
],
1
,
&
txn
->
error
.
desc
)))
{
txn
->
error
.
desc
=
"Illegal Destination target URI"
;
return
HTTP_BAD_REQUEST
;
}
/* Make sure source and dest resources are NOT the same */
if
(
!
strcmp
(
txn
->
req_uri
->
path
,
dest_uri
->
path
))
{
txn
->
error
.
desc
=
"Source and destination resources are the same
\r\n
"
;
r
=
HTTP_FORBIDDEN
;
}
/* Parse the destination path */
if
(
!
r
)
{
r
=
cparams
->
parse_path
(
dest_uri
->
path
,
&
dest_tgt
,
&
txn
->
error
.
desc
);
}
xmlFreeURI
(
dest_uri
);
if
(
r
)
return
HTTP_FORBIDDEN
;
/* We don't yet handle COPY/MOVE on collections */
if
(
!
dest_tgt
.
resource
)
return
HTTP_NOT_ALLOWED
;
/* Locate the source mailbox */
if
((
r
=
http_mlookup
(
txn
->
req_tgt
.
mboxname
,
&
mbentry
,
NULL
)))
{
syslog
(
LOG_ERR
,
"mlookup(%s) failed: %s"
,
txn
->
req_tgt
.
mboxname
,
error_message
(
r
));
txn
->
error
.
desc
=
error_message
(
r
);
switch
(
r
)
{
case
IMAP_PERMISSION_DENIED
:
return
HTTP_FORBIDDEN
;
case
IMAP_MAILBOX_NONEXISTENT
:
return
HTTP_NOT_FOUND
;
default
:
return
HTTP_SERVER_ERROR
;
}
}
/* Check ACL for current user on source mailbox */
rights
=
httpd_myrights
(
httpd_authstate
,
mbentry
->
acl
);
if
(((
rights
&
DACL_READ
)
!=
DACL_READ
)
||
((
txn
->
meth
==
METH_MOVE
)
&&
!
(
rights
&
DACL_RMRSRC
)))
{
/* DAV:need-privileges */
txn
->
error
.
precond
=
DAV_NEED_PRIVS
;
txn
->
error
.
resource
=
txn
->
req_tgt
.
path
;
txn
->
error
.
rights
=
(
rights
&
DACL_READ
)
!=
DACL_READ
?
DACL_READ
:
DACL_RMRSRC
;
mboxlist_entry_free
(
&
mbentry
);
return
HTTP_NO_PRIVS
;
}
if
(
mbentry
->
server
)
{
/* Remote source mailbox */
src_be
=
proxy_findserver
(
mbentry
->
server
,
&
http_protocol
,
proxy_userid
,
&
backend_cached
,
NULL
,
NULL
,
httpd_in
);
mboxlist_entry_free
(
&
mbentry
);
if
(
!
src_be
)
return
HTTP_UNAVAILABLE
;
}
mboxlist_entry_free
(
&
mbentry
);
/* Locate the destination mailbox */
r
=
http_mlookup
(
dest_tgt
.
mboxname
,
&
mbentry
,
NULL
);
if
(
r
)
{
syslog
(
LOG_ERR
,
"mlookup(%s) failed: %s"
,
dest_tgt
.
mboxname
,
error_message
(
r
));
txn
->
error
.
desc
=
error_message
(
r
);
switch
(
r
)
{
case
IMAP_PERMISSION_DENIED
:
return
HTTP_FORBIDDEN
;
case
IMAP_MAILBOX_NONEXISTENT
:
return
HTTP_NOT_FOUND
;
default
:
return
HTTP_SERVER_ERROR
;
}
}
/* Check ACL for current user on destination */
rights
=
httpd_myrights
(
httpd_authstate
,
mbentry
->
acl
);
if
(
!
(
rights
&
DACL_ADDRSRC
)
||
!
(
rights
&
DACL_WRITECONT
))
{
/* DAV:need-privileges */
txn
->
error
.
precond
=
DAV_NEED_PRIVS
;
txn
->
error
.
resource
=
dest_tgt
.
path
;
txn
->
error
.
rights
=
!
(
rights
&
DACL_ADDRSRC
)
?
DACL_ADDRSRC
:
DACL_WRITECONT
;
mboxlist_entry_free
(
&
mbentry
);
return
HTTP_NO_PRIVS
;
}
if
(
mbentry
->
server
)
{
/* Remote destination mailbox */
dest_be
=
proxy_findserver
(
mbentry
->
server
,
&
http_protocol
,
proxy_userid
,
&
backend_cached
,
NULL
,
NULL
,
httpd_in
);
mboxlist_entry_free
(
&
mbentry
);
if
(
!
dest_be
)
return
HTTP_UNAVAILABLE
;
}
mboxlist_entry_free
(
&
mbentry
);
if
(
src_be
)
{
/* Remote source mailbox */
/* XXX Currently only supports standard Murder */
if
(
!
dest_be
)
return
HTTP_NOT_ALLOWED
;
/* Replace cached Destination header with just the absolute path */
hdr
=
spool_getheader
(
txn
->
req_hdrs
,
"Destination"
);
strcpy
((
char
*
)
hdr
[
0
],
dest_tgt
.
path
);
if
(
src_be
==
dest_be
)
{
/* Simply send the COPY to the backend */
return
http_pipe_req_resp
(
src_be
,
txn
);
}
/* This is the harder case: GET from source and PUT on destination */
return
http_proxy_copy
(
src_be
,
dest_be
,
txn
);
}
/* Local Mailbox */
if
(
txn
->
meth
==
METH_MOVE
)
{
/* Open source mailbox for writiing */
r
=
mailbox_open_iwl
(
txn
->
req_tgt
.
mboxname
,
&
src_mbox
);
}
else
{
/* Open source mailbox for reading */
r
=
mailbox_open_irl
(
txn
->
req_tgt
.
mboxname
,
&
src_mbox
);
}
/* Open the DAV DB corresponding to the dest mailbox */
dest_davdb
=
cparams
->
davdb
.
open_db
(
dest_mbox
);
/* Find message UID for the dest resource, if exists */
cparams
->
davdb
.
lookup_resource
(
dest_davdb
,
dest_tgt
.
mboxname
,
dest_tgt
.
resource
,
0
,
(
void
**
)
&
ddata
,
0
);
/* XXX Check errors */
/* Finished our initial read of dest mailbox */
mailbox_unlock_index
(
dest_mbox
,
NULL
);
/* Check any preconditions on destination */
if
((
hdr
=
spool_getheader
(
txn
->
req_hdrs
,
"Overwrite"
))
&&
!
strcmp
(
hdr
[
0
],
"F"
))
{
if
(
ddata
->
rowid
)
{
/* Don't overwrite the destination resource */
ret
=
HTTP_PRECOND_FAILED
;
goto
done
;
}
overwrite
=
OVERWRITE_NO
;
}
if
(
r
)
{
syslog
(
LOG_ERR
,
"mailbox_open_%s(%s) failed: %s"
,
txn
->
meth
==
METH_MOVE
?
"iwl"
:
"irl"
,
txn
->
req_tgt
.
mboxname
,
error_message
(
r
));
txn
->
error
.
desc
=
error_message
(
r
);
ret
=
HTTP_SERVER_ERROR
;
goto
done
;
}
/* Open the DAV DB corresponding to the src mailbox */
src_davdb
=
cparams
->
davdb
.
open_db
(
src_mbox
);
/* Find message UID for the source resource */
cparams
->
davdb
.
lookup_resource
(
src_davdb
,
txn
->
req_tgt
.
mboxname
,
txn
->
req_tgt
.
resource
,
0
,
(
void
**
)
&
ddata
,
0
);
if
(
!
ddata
->
rowid
)
{
ret
=
HTTP_NOT_FOUND
;
goto
done
;
}
if
(
ddata
->
imap_uid
)
{
/* Mapped URL - Fetch index record for the resource */
r
=
mailbox_find_index_record
(
src_mbox
,
ddata
->
imap_uid
,
&
src_rec
,
NULL
);
if
(
r
)
{
txn
->
error
.
desc
=
error_message
(
r
);
ret
=
HTTP_SERVER_ERROR
;
goto
done
;
}
etag
=
message_guid_encode
(
&
src_rec
.
guid
);
lastmod
=
src_rec
.
internaldate
;
}
else
{
/* Unmapped URL (empty resource) */
etag
=
NULL_ETAG
;
lastmod
=
ddata
->
creationdate
;
}
/* Check any preconditions on source */
precond
=
cparams
->
check_precond
(
txn
,
(
void
**
)
ddata
,
etag
,
lastmod
);
switch
(
precond
)
{
case
HTTP_OK
:
break
;
case
HTTP_LOCKED
:
txn
->
error
.
precond
=
DAV_NEED_LOCK_TOKEN
;
txn
->
error
.
resource
=
txn
->
req_tgt
.
path
;
default
:
/* We failed a precondition - don't perform the request */
ret
=
precond
;
goto
done
;
}
/* Load message containing the resource and parse data */
mailbox_map_record
(
src_mbox
,
&
src_rec
,
&
msg_buf
);
obj
=
cparams
->
mime_types
[
0
].
from_string
(
buf_base
(
&
msg_buf
)
+
src_rec
.
header_size
);
buf_free
(
&
msg_buf
);
if
(
txn
->
meth
!=
METH_MOVE
)
{
/* Done with source mailbox */
mailbox_unlock_index
(
src_mbox
,
NULL
);
}
/* Open dest mailbox for writing */
r
=
mailbox_open_iwl
(
dest_tgt
.
mboxname
,
&
dest_mbox
);
if
(
r
)
{
syslog
(
LOG_ERR
,
"mailbox_open_iwl(%s) failed: %s"
,
dest_tgt
.
mboxname
,
error_message
(
r
));
txn
->
error
.
desc
=
error_message
(
r
);
ret
=
HTTP_SERVER_ERROR
;
goto
done
;
}
/* Open the DAV DB corresponding to the dest mailbox */
dest_davdb
=
cparams
->
davdb
.
open_db
(
dest_mbox
);
/* Find message UID for the dest resource, if exists */
cparams
->
davdb
.
lookup_resource
(
dest_davdb
,
dest_tgt
.
mboxname
,
dest_tgt
.
resource
,
0
,
(
void
**
)
&
ddata
);
/* XXX Check errors */
/* Check any preconditions on destination */
if
((
hdr
=
spool_getheader
(
txn
->
req_hdrs
,
"Overwrite"
))
&&
!
strcmp
(
hdr
[
0
],
"F"
))
{
if
(
ddata
->
rowid
)
{
/* Don't overwrite the destination resource */
ret
=
HTTP_PRECOND_FAILED
;
goto
done
;
}
}
if
(
get_preferences
(
txn
)
&
PREFER_REP
)
flags
|=
PREFER_REP
;
if
((
txn
->
meth
==
METH_MOVE
)
&&
(
dest_mbox
==
src_mbox
))
flags
|=
NO_DUP_CHECK
;
/* Store the resource at destination */
ret
=
cparams
->
copy
(
txn
,
obj
,
dest_mbox
,
dest_tgt
.
resource
,
dest_davdb
,
flags
);
/* we're done, no need to keep this */
mailbox_unlock_index
(
dest_mbox
,
NULL
);
/* For MOVE, we need to delete the source resource */
if
((
txn
->
meth
==
METH_MOVE
)
&&
(
ret
==
HTTP_CREATED
||
ret
==
HTTP_NO_CONTENT
))
{
/* Find message UID for the source resource */
cparams
->
davdb
.
lookup_resource
(
src_davdb
,
txn
->
req_tgt
.
mboxname
,
txn
->
req_tgt
.
resource
,
0
,
(
void
**
)
&
ddata
,
0
);
/* XXX Check errors */
/* Fetch index record for the source resource */
if
(
ddata
->
imap_uid
&&
!
mailbox_find_index_record
(
src_mbox
,
ddata
->
imap_uid
,
&
src_rec
,
NULL
))
{
/* Expunge the source message */
src_rec
.
system_flags
|=
FLAG_EXPUNGED
;
if
((
r
=
mailbox_rewrite_index_record
(
src_mbox
,
&
src_rec
)))
{
syslog
(
LOG_ERR
,
"expunging src record (%s) failed: %s"
,
txn
->
req_tgt
.
mboxname
,
error_message
(
r
));
txn
->
error
.
desc
=
error_message
(
r
);
ret
=
HTTP_SERVER_ERROR
;
goto
done
;
}
}
}
done
:
if
(
ret
==
HTTP_CREATED
)
{
/* Tell client where to find the new resource */
txn
->
location
=
dest_tgt
.
path
;
}
else
{
/* Don't confuse client by providing ETag of Destination resource */
txn
->
resp_body
.
etag
=
NULL
;
}
if
(
obj
)
cparams
->
mime_types
[
0
].
free
(
obj
);
if
(
dest_davdb
)
cparams
->
davdb
.
close_db
(
dest_davdb
);
mailbox_close
(
&
dest_mbox
);
if
(
src_davdb
)
cparams
->
davdb
.
close_db
(
src_davdb
);
mailbox_close
(
&
src_mbox
);
return
ret
;
}
/* this is when a user tries to delete a mailbox that they don't own, and don't
* have permission to delete */
static
int
remove_user_acl
(
const
char
*
userid
,
const
char
*
mboxname
)
{
return
mboxlist_setacl
(
&
httpd_namespace
,
mboxname
,
userid
,
/*rights*/
NULL
,
/*isadmin*/
1
,
httpd_userid
,
httpd_authstate
);
}
/* Perform a DELETE request */
int
meth_delete
(
struct
transaction_t
*
txn
,
void
*
params
)
{
struct
meth_params
*
dparams
=
(
struct
meth_params
*
)
params
;
int
ret
=
HTTP_NO_CONTENT
,
r
=
0
,
precond
,
rights
,
needrights
;
struct
buf
synctoken
=
BUF_INITIALIZER
;
struct
mboxevent
*
mboxevent
=
NULL
;
struct
mailbox
*
mailbox
=
NULL
;
mbentry_t
*
mbentry
=
NULL
;
struct
dav_data
*
ddata
;
struct
index_record
record
;
const
char
*
etag
=
NULL
;
time_t
lastmod
=
0
;
void
*
davdb
=
NULL
;
/* Response should not be cached */
txn
->
flags
.
cc
|=
CC_NOCACHE
;
/* Parse the path */
r
=
dparams
->
parse_path
(
txn
->
req_uri
->
path
,
&
txn
->
req_tgt
,
&
txn
->
error
.
desc
);
if
(
r
)
return
r
;
/* Make sure method is allowed */
if
(
!
(
txn
->
req_tgt
.
allow
&
ALLOW_DELETE
))
return
HTTP_NOT_ALLOWED
;
/* Locate the mailbox */
r
=
http_mlookup
(
txn
->
req_tgt
.
mboxname
,
&
mbentry
,
NULL
);
if
(
r
)
{
syslog
(
LOG_ERR
,
"mlookup(%s) failed: %s"
,
txn
->
req_tgt
.
mboxname
,
error_message
(
r
));
txn
->
error
.
desc
=
error_message
(
r
);
switch
(
r
)
{
case
IMAP_PERMISSION_DENIED
:
return
HTTP_FORBIDDEN
;
case
IMAP_MAILBOX_NONEXISTENT
:
return
HTTP_NOT_FOUND
;
default
:
return
HTTP_SERVER_ERROR
;
}
}
/* Check ACL for current user */
rights
=
httpd_myrights
(
httpd_authstate
,
mbentry
->
acl
);
needrights
=
txn
->
req_tgt
.
resource
?
DACL_RMRSRC
:
DACL_RMCOL
;
if
(
!
(
rights
&
needrights
))
{
mboxlist_entry_free
(
&
mbentry
);
/* special case delete mailbox where not the owner */
if
(
!
txn
->
req_tgt
.
resource
&&
!
mboxname_userownsmailbox
(
httpd_userid
,
txn
->
req_tgt
.
mboxname
))
if
(
!
remove_user_acl
(
httpd_userid
,
txn
->
req_tgt
.
mboxname
))
return
HTTP_OK
;
/* DAV:need-privileges */
txn
->
error
.
precond
=
DAV_NEED_PRIVS
;
txn
->
error
.
resource
=
txn
->
req_tgt
.
path
;
return
HTTP_NO_PRIVS
;
}
if
(
mbentry
->
server
)
{
/* Remote mailbox */
struct
backend
*
be
;
be
=
proxy_findserver
(
mbentry
->
server
,
&
http_protocol
,
proxy_userid
,
&
backend_cached
,
NULL
,
NULL
,
httpd_in
);
mboxlist_entry_free
(
&
mbentry
);
if
(
!
be
)
return
HTTP_UNAVAILABLE
;
return
http_pipe_req_resp
(
be
,
txn
);
}
mboxlist_entry_free
(
&
mbentry
);
/* Local Mailbox */
if
(
!
txn
->
req_tgt
.
resource
)
{
/* DELETE collection */
/* Open mailbox for reading */
r
=
mailbox_open_irl
(
txn
->
req_tgt
.
mboxname
,
&
mailbox
);
if
(
r
)
{
syslog
(
LOG_ERR
,
"http_mailbox_open(%s) failed: %s"
,
txn
->
req_tgt
.
mboxname
,
error_message
(
r
));
txn
->
error
.
desc
=
error_message
(
r
);
return
HTTP_SERVER_ERROR
;
}
/* Open the DAV DB corresponding to the mailbox */
davdb
=
dparams
->
davdb
.
open_db
(
mailbox
);
/* Do any special processing */
if
(
dparams
->
delete
)
dparams
->
delete
(
txn
,
mailbox
,
NULL
,
NULL
);
mailbox_close
(
&
mailbox
);
mboxevent
=
mboxevent_new
(
EVENT_MAILBOX_DELETE
);
if
(
mboxlist_delayed_delete_isenabled
())
{
r
=
mboxlist_delayed_deletemailbox
(
txn
->
req_tgt
.
mboxname
,
httpd_userisadmin
||
httpd_userisproxyadmin
,
httpd_userid
,
httpd_authstate
,
mboxevent
,
/*checkack*/
1
,
/*force*/
0
);
}
else
{
r
=
mboxlist_deletemailbox
(
txn
->
req_tgt
.
mboxname
,
httpd_userisadmin
||
httpd_userisproxyadmin
,
httpd_userid
,
httpd_authstate
,
mboxevent
,
/*checkack*/
1
,
/*localonly*/
0
,
/*force*/
0
);
}
if
(
r
==
IMAP_PERMISSION_DENIED
)
ret
=
HTTP_FORBIDDEN
;
else
if
(
r
==
IMAP_MAILBOX_NONEXISTENT
)
ret
=
HTTP_NOT_FOUND
;
else
if
(
r
)
ret
=
HTTP_SERVER_ERROR
;
goto
done
;
}
/* DELETE resource */
/* Open mailbox for writing */
r
=
mailbox_open_iwl
(
txn
->
req_tgt
.
mboxname
,
&
mailbox
);
if
(
r
)
{
syslog
(
LOG_ERR
,
"http_mailbox_open(%s) failed: %s"
,
txn
->
req_tgt
.
mboxname
,
error_message
(
r
));
txn
->
error
.
desc
=
error_message
(
r
);
ret
=
HTTP_SERVER_ERROR
;
goto
done
;
}
/* Open the DAV DB corresponding to the mailbox */
davdb
=
dparams
->
davdb
.
open_db
(
mailbox
);
/* Find message UID for the resource, if exists */
dparams
->
davdb
.
lookup_resource
(
davdb
,
txn
->
req_tgt
.
mboxname
,
txn
->
req_tgt
.
resource
,
0
,
(
void
**
)
&
ddata
,
0
);
if
(
!
ddata
->
rowid
)
{
ret
=
HTTP_NOT_FOUND
;
goto
done
;
}
memset
(
&
record
,
0
,
sizeof
(
struct
index_record
));
if
(
ddata
->
imap_uid
)
{
/* Mapped URL - Fetch index record for the resource */
r
=
mailbox_find_index_record
(
mailbox
,
ddata
->
imap_uid
,
&
record
,
NULL
);
if
(
r
)
{
txn
->
error
.
desc
=
error_message
(
r
);
ret
=
HTTP_SERVER_ERROR
;
goto
done
;
}
etag
=
message_guid_encode
(
&
record
.
guid
);
lastmod
=
record
.
internaldate
;
}
else
{
/* Unmapped URL (empty resource) */
etag
=
NULL_ETAG
;
lastmod
=
ddata
->
creationdate
;
}
/* Check any preconditions */
precond
=
dparams
->
check_precond
(
txn
,
(
void
*
)
ddata
,
etag
,
lastmod
);
switch
(
precond
)
{
case
HTTP_OK
:
break
;
case
HTTP_LOCKED
:
txn
->
error
.
precond
=
DAV_NEED_LOCK_TOKEN
;
txn
->
error
.
resource
=
txn
->
req_tgt
.
path
;
default
:
/* We failed a precondition - don't perform the request */
ret
=
precond
;
goto
done
;
}
if
(
record
.
uid
)
{
/* Expunge the resource */
record
.
system_flags
|=
FLAG_EXPUNGED
;
mboxevent
=
mboxevent_new
(
EVENT_MESSAGE_EXPUNGE
);
r
=
mailbox_rewrite_index_record
(
mailbox
,
&
record
);
if
(
r
)
{
syslog
(
LOG_ERR
,
"expunging record (%s) failed: %s"
,
txn
->
req_tgt
.
mboxname
,
error_message
(
r
));
txn
->
error
.
desc
=
error_message
(
r
);
ret
=
HTTP_SERVER_ERROR
;
goto
done
;
}
mboxevent_extract_record
(
mboxevent
,
mailbox
,
&
record
);
mboxevent_extract_mailbox
(
mboxevent
,
mailbox
);
mboxevent_set_numunseen
(
mboxevent
,
mailbox
,
-1
);
mboxevent_set_access
(
mboxevent
,
NULL
,
NULL
,
httpd_userid
,
txn
->
req_tgt
.
mboxname
,
0
);
}
/* Do any special processing */
if
(
dparams
->
delete
)
dparams
->
delete
(
txn
,
mailbox
,
&
record
,
ddata
);
get_synctoken
(
mailbox
,
&
synctoken
);
txn
->
resp_body
.
synctoken
=
buf_cstring
(
&
synctoken
);
write_body
(
ret
,
txn
,
NULL
,
0
);
buf_free
(
&
synctoken
);
done
:
if
(
davdb
)
dparams
->
davdb
.
close_db
(
davdb
);
mailbox_close
(
&
mailbox
);
if
(
!
r
)
mboxevent_notify
(
mboxevent
);
mboxevent_free
(
&
mboxevent
);
return
ret
;
}
/* Perform a GET/HEAD request on a DAV resource */
int
meth_get_dav
(
struct
transaction_t
*
txn
,
void
*
params
)
{
struct
meth_params
*
gparams
=
(
struct
meth_params
*
)
params
;
const
char
**
hdr
;
struct
mime_type_t
*
mime
=
NULL
;
int
ret
=
0
,
r
,
precond
,
rights
;
const
char
*
data
=
NULL
;
unsigned
long
datalen
=
0
,
offset
=
0
;
struct
buf
msg_buf
=
BUF_INITIALIZER
;
struct
buf
synctoken
=
BUF_INITIALIZER
;
struct
resp_body_t
*
resp_body
=
&
txn
->
resp_body
;
struct
mailbox
*
mailbox
=
NULL
;
mbentry_t
*
mbentry
=
NULL
;
struct
dav_data
*
ddata
;
struct
index_record
record
;
const
char
*
etag
=
NULL
;
time_t
lastmod
=
0
;
void
*
davdb
=
NULL
;
char
*
freeme
=
NULL
;
/* Parse the path */
ret
=
gparams
->
parse_path
(
txn
->
req_uri
->
path
,
&
txn
->
req_tgt
,
&
txn
->
error
.
desc
);
if
(
ret
)
return
ret
;
if
(
!
txn
->
req_tgt
.
resource
)
{
/* Do any collection processing */
if
(
gparams
->
get
)
return
gparams
->
get
(
txn
,
NULL
,
NULL
,
NULL
);
/* We don't handle GET on a collection */
return
HTTP_NO_CONTENT
;
}
if
(
gparams
->
mime_types
)
{
/* Check requested MIME type:
1st entry in gparams->mime_types array MUST be default MIME type */
if
((
hdr
=
spool_getheader
(
txn
->
req_hdrs
,
"Accept"
)))
mime
=
get_accept_type
(
hdr
,
gparams
->
mime_types
);
else
mime
=
gparams
->
mime_types
;
if
(
!
mime
)
return
HTTP_NOT_ACCEPTABLE
;
}
/* Locate the mailbox */
r
=
http_mlookup
(
txn
->
req_tgt
.
mboxname
,
&
mbentry
,
NULL
);
if
(
r
)
{
syslog
(
LOG_ERR
,
"mlookup(%s) failed: %s"
,
txn
->
req_tgt
.
mboxname
,
error_message
(
r
));
txn
->
error
.
desc
=
error_message
(
r
);
switch
(
r
)
{
case
IMAP_PERMISSION_DENIED
:
return
HTTP_FORBIDDEN
;
case
IMAP_MAILBOX_NONEXISTENT
:
return
HTTP_NOT_FOUND
;
default
:
return
HTTP_SERVER_ERROR
;
}
}
/* Check ACL for current user */
rights
=
httpd_myrights
(
httpd_authstate
,
mbentry
->
acl
);
if
(
!
(
rights
&
DACL_READ
))
{
/* DAV:need-privileges */
txn
->
error
.
precond
=
DAV_NEED_PRIVS
;
txn
->
error
.
resource
=
txn
->
req_tgt
.
path
;
txn
->
error
.
rights
=
DACL_READ
;
mboxlist_entry_free
(
&
mbentry
);
return
HTTP_NO_PRIVS
;
}
if
(
mbentry
->
server
)
{
/* Remote mailbox */
struct
backend
*
be
;
be
=
proxy_findserver
(
mbentry
->
server
,
&
http_protocol
,
proxy_userid
,
&
backend_cached
,
NULL
,
NULL
,
httpd_in
);
mboxlist_entry_free
(
&
mbentry
);
if
(
!
be
)
return
HTTP_UNAVAILABLE
;
return
http_pipe_req_resp
(
be
,
txn
);
}
mboxlist_entry_free
(
&
mbentry
);
/* Local Mailbox */
/* Open mailbox for reading */
r
=
mailbox_open_irl
(
txn
->
req_tgt
.
mboxname
,
&
mailbox
);
if
(
r
)
{
syslog
(
LOG_ERR
,
"http_mailbox_open(%s) failed: %s"
,
txn
->
req_tgt
.
mboxname
,
error_message
(
r
));
goto
done
;
}
/* Open the DAV DB corresponding to the mailbox */
davdb
=
gparams
->
davdb
.
open_db
(
mailbox
);
/* Find message UID for the resource */
gparams
->
davdb
.
lookup_resource
(
davdb
,
txn
->
req_tgt
.
mboxname
,
txn
->
req_tgt
.
resource
,
0
,
(
void
**
)
&
ddata
,
0
);
if
(
!
ddata
->
rowid
)
{
ret
=
HTTP_NOT_FOUND
;
goto
done
;
}
memset
(
&
record
,
0
,
sizeof
(
struct
index_record
));
if
(
ddata
->
imap_uid
)
{
/* Mapped URL - Fetch index record for the resource */
r
=
mailbox_find_index_record
(
mailbox
,
ddata
->
imap_uid
,
&
record
,
NULL
);
if
(
r
)
{
txn
->
error
.
desc
=
error_message
(
r
);
ret
=
HTTP_SERVER_ERROR
;
goto
done
;
}
txn
->
flags
.
ranges
=
1
;
etag
=
message_guid_encode
(
&
record
.
guid
);
lastmod
=
record
.
internaldate
;
}
else
{
/* Unmapped URL (empty resource) */
txn
->
flags
.
ranges
=
0
;
etag
=
NULL_ETAG
;
lastmod
=
ddata
->
creationdate
;
}
/* Check any preconditions, including range request */
precond
=
gparams
->
check_precond
(
txn
,
(
void
*
)
ddata
,
etag
,
lastmod
);
switch
(
precond
)
{
case
HTTP_OK
:
case
HTTP_PARTIAL
:
case
HTTP_NOT_MODIFIED
:
/* Fill in ETag, Last-Modified, Expires, and Cache-Control */
resp_body
->
etag
=
etag
;
resp_body
->
lastmod
=
lastmod
;
resp_body
->
maxage
=
3600
;
/* 1 hr */
txn
->
flags
.
cc
|=
CC_MAXAGE
|
CC_REVALIDATE
;
/* don't use stale data */
if
(
precond
!=
HTTP_NOT_MODIFIED
)
break
;
default
:
/* We failed a precondition - don't perform the request */
ret
=
precond
;
goto
done
;
}
/* Do any special processing */
if
(
gparams
->
get
)
{
ret
=
gparams
->
get
(
txn
,
mailbox
,
&
record
,
ddata
);
if
(
ret
)
goto
done
;
}
if
(
record
.
uid
)
{
if
(
mime
)
{
txn
->
flags
.
vary
|=
VARY_ACCEPT
;
resp_body
->
type
=
mime
->
content_type
;
}
/* Resource length doesn't include RFC 5322 header */
offset
=
record
.
header_size
;
datalen
=
record
.
size
-
offset
;
if
(
txn
->
meth
==
METH_GET
)
{
/* Load message containing the resource */
r
=
mailbox_map_record
(
mailbox
,
&
record
,
&
msg_buf
);
if
(
r
)
goto
done
;
/* iCalendar data in response should not be transformed */
txn
->
flags
.
cc
|=
CC_NOTRANSFORM
;
data
=
buf_base
(
&
msg_buf
)
+
offset
;
if
(
mime
!=
gparams
->
mime_types
)
{
/* Not the storage format - convert into requested MIME type */
void
*
obj
=
gparams
->
mime_types
[
0
].
from_string
(
data
);
data
=
freeme
=
mime
->
to_string
(
obj
);
datalen
=
strlen
(
data
);
gparams
->
mime_types
[
0
].
free
(
obj
);
}
}
}
r
=
get_synctoken
(
mailbox
,
&
synctoken
);
if
(
r
)
goto
done
;
resp_body
->
synctoken
=
buf_cstring
(
&
synctoken
);
write_body
(
precond
,
txn
,
data
,
datalen
);
buf_free
(
&
msg_buf
);
done
:
buf_free
(
&
synctoken
);
if
(
davdb
)
gparams
->
davdb
.
close_db
(
davdb
);
if
(
r
)
{
txn
->
error
.
desc
=
error_message
(
r
);
ret
=
HTTP_SERVER_ERROR
;
}
free
(
freeme
);
mailbox_close
(
&
mailbox
);
return
ret
;
}
/* Perform a LOCK request
*
* preconditions:
* DAV:need-privileges
* DAV:no-conflicting-lock
* DAV:lock-token-submitted
*/
int
meth_lock
(
struct
transaction_t
*
txn
,
void
*
params
)
{
struct
meth_params
*
lparams
=
(
struct
meth_params
*
)
params
;
int
ret
=
HTTP_OK
,
r
,
precond
,
rights
;
struct
mailbox
*
mailbox
=
NULL
;
mbentry_t
*
mbentry
=
NULL
;
struct
dav_data
*
ddata
;
struct
index_record
oldrecord
;
const
char
*
etag
;
time_t
lastmod
;
xmlDocPtr
indoc
=
NULL
,
outdoc
=
NULL
;
xmlNodePtr
root
=
NULL
;
xmlNsPtr
ns
[
NUM_NAMESPACE
];
xmlChar
*
owner
=
NULL
;
time_t
now
=
time
(
NULL
);
void
*
davdb
=
NULL
;
/* XXX We ignore Depth and Timeout header fields */
/* Response should not be cached */
txn
->
flags
.
cc
|=
CC_NOCACHE
;
/* Parse the path */
if
((
r
=
lparams
->
parse_path
(
txn
->
req_uri
->
path
,
&
txn
->
req_tgt
,
&
txn
->
error
.
desc
)))
return
r
;
/* Make sure method is allowed (only allowed on resources) */
if
(
!
(
txn
->
req_tgt
.
allow
&
ALLOW_WRITE
))
return
HTTP_NOT_ALLOWED
;
/* Locate the mailbox */
r
=
http_mlookup
(
txn
->
req_tgt
.
mboxname
,
&
mbentry
,
NULL
);
if
(
r
)
{
syslog
(
LOG_ERR
,
"mlookup(%s) failed: %s"
,
txn
->
req_tgt
.
mboxname
,
error_message
(
r
));
txn
->
error
.
desc
=
error_message
(
r
);
switch
(
r
)
{
case
IMAP_PERMISSION_DENIED
:
return
HTTP_FORBIDDEN
;
case
IMAP_MAILBOX_NONEXISTENT
:
return
HTTP_NOT_FOUND
;
default
:
return
HTTP_SERVER_ERROR
;
}
}
/* Check ACL for current user */
rights
=
httpd_myrights
(
httpd_authstate
,
mbentry
->
acl
);
if
(
!
(
rights
&
DACL_WRITECONT
)
||
!
(
rights
&
DACL_ADDRSRC
))
{
/* DAV:need-privileges */
txn
->
error
.
precond
=
DAV_NEED_PRIVS
;
txn
->
error
.
resource
=
txn
->
req_tgt
.
path
;
txn
->
error
.
rights
=
!
(
rights
&
DACL_WRITECONT
)
?
DACL_WRITECONT
:
DACL_ADDRSRC
;
mboxlist_entry_free
(
&
mbentry
);
return
HTTP_NO_PRIVS
;
}
if
(
mbentry
->
server
)
{
/* Remote mailbox */
struct
backend
*
be
;
be
=
proxy_findserver
(
mbentry
->
server
,
&
http_protocol
,
proxy_userid
,
&
backend_cached
,
NULL
,
NULL
,
httpd_in
);
mboxlist_entry_free
(
&
mbentry
);
if
(
!
be
)
return
HTTP_UNAVAILABLE
;
return
http_pipe_req_resp
(
be
,
txn
);
}
mboxlist_entry_free
(
&
mbentry
);
/* Local Mailbox */
/* Open mailbox for reading */
r
=
mailbox_open_irl
(
txn
->
req_tgt
.
mboxname
,
&
mailbox
);
if
(
r
)
{
syslog
(
LOG_ERR
,
"http_mailbox_open(%s) failed: %s"
,
txn
->
req_tgt
.
mboxname
,
error_message
(
r
));
txn
->
error
.
desc
=
error_message
(
r
);
ret
=
HTTP_SERVER_ERROR
;
goto
done
;
}
/* Open the DAV DB corresponding to the mailbox */
davdb
=
lparams
->
davdb
.
open_db
(
mailbox
);
/* Find message UID for the resource, if exists */
lparams
->
davdb
.
lookup_resource
(
davdb
,
txn
->
req_tgt
.
mboxname
,
txn
->
req_tgt
.
resource
,
1
,
(
void
*
)
&
ddata
,
0
);
if
(
ddata
->
imap_uid
)
{
/* Locking existing resource */
/* Fetch index record for the resource */
r
=
mailbox_find_index_record
(
mailbox
,
ddata
->
imap_uid
,
&
oldrecord
,
NULL
);
if
(
r
)
{
txn
->
error
.
desc
=
error_message
(
r
);
ret
=
HTTP_SERVER_ERROR
;
goto
done
;
}
etag
=
message_guid_encode
(
&
oldrecord
.
guid
);
lastmod
=
oldrecord
.
internaldate
;
}
else
if
(
ddata
->
rowid
)
{
/* Unmapped URL (empty resource) */
etag
=
NULL_ETAG
;
lastmod
=
ddata
->
creationdate
;
}
else
{
/* New resource */
etag
=
NULL
;
lastmod
=
0
;
ddata
->
creationdate
=
now
;
ddata
->
mailbox
=
mailbox
->
name
;
ddata
->
resource
=
txn
->
req_tgt
.
resource
;
}
/* Check any preconditions */
precond
=
lparams
->
check_precond
(
txn
,
ddata
,
etag
,
lastmod
);
switch
(
precond
)
{
case
HTTP_OK
:
break
;
case
HTTP_LOCKED
:
if
(
strcmp
(
ddata
->
lock_ownerid
,
httpd_userid
))
txn
->
error
.
precond
=
DAV_LOCKED
;
else
txn
->
error
.
precond
=
DAV_NEED_LOCK_TOKEN
;
txn
->
error
.
resource
=
txn
->
req_tgt
.
path
;
default
:
/* We failed a precondition - don't perform the request */
ret
=
precond
;
goto
done
;
}
if
(
ddata
->
lock_expire
<=
now
)
{
/* Create new lock */
xmlNodePtr
node
,
sub
;
unsigned
owner_is_href
=
0
;
/* Parse the required body */
ret
=
parse_xml_body
(
txn
,
&
root
);
if
(
!
ret
&&
!
root
)
{
txn
->
error
.
desc
=
"Missing request body"
;
ret
=
HTTP_BAD_REQUEST
;
}
if
(
ret
)
goto
done
;
/* Check for correct root element */
indoc
=
root
->
doc
;
if
(
xmlStrcmp
(
root
->
name
,
BAD_CAST
"lockinfo"
))
{
txn
->
error
.
desc
=
"Incorrect root element in XML request
\r\n
"
;
ret
=
HTTP_BAD_MEDIATYPE
;
goto
done
;
}
/* Parse elements of lockinfo */
for
(
node
=
root
->
children
;
node
;
node
=
node
->
next
)
{
if
(
node
->
type
!=
XML_ELEMENT_NODE
)
continue
;
if
(
!
xmlStrcmp
(
node
->
name
,
BAD_CAST
"lockscope"
))
{
/* Find child element of lockscope */
for
(
sub
=
node
->
children
;
sub
&&
sub
->
type
!=
XML_ELEMENT_NODE
;
sub
=
sub
->
next
);
/* Make sure its an exclusive element */
if
(
!
sub
||
xmlStrcmp
(
sub
->
name
,
BAD_CAST
"exclusive"
))
{
txn
->
error
.
desc
=
"Only exclusive locks are supported"
;
ret
=
HTTP_BAD_REQUEST
;
goto
done
;
}
}
else
if
(
!
xmlStrcmp
(
node
->
name
,
BAD_CAST
"locktype"
))
{
/* Find child element of locktype */
for
(
sub
=
node
->
children
;
sub
&&
sub
->
type
!=
XML_ELEMENT_NODE
;
sub
=
sub
->
next
);
/* Make sure its a write element */
if
(
!
sub
||
xmlStrcmp
(
sub
->
name
,
BAD_CAST
"write"
))
{
txn
->
error
.
desc
=
"Only write locks are supported"
;
ret
=
HTTP_BAD_REQUEST
;
goto
done
;
}
}
else
if
(
!
xmlStrcmp
(
node
->
name
,
BAD_CAST
"owner"
))
{
/* Find child element of owner */
for
(
sub
=
node
->
children
;
sub
&&
sub
->
type
!=
XML_ELEMENT_NODE
;
sub
=
sub
->
next
);
if
(
!
sub
)
{
owner
=
xmlNodeGetContent
(
node
);
}
/* Make sure its a href element */
else
if
(
xmlStrcmp
(
sub
->
name
,
BAD_CAST
"href"
))
{
ret
=
HTTP_BAD_REQUEST
;
goto
done
;
}
else
{
owner_is_href
=
1
;
owner
=
xmlNodeGetContent
(
sub
);
}
}
}
ddata
->
lock_ownerid
=
httpd_userid
;
if
(
owner
)
ddata
->
lock_owner
=
(
const
char
*
)
owner
;
/* Construct lock-token */
assert
(
!
buf_len
(
&
txn
->
buf
));
buf_printf
(
&
txn
->
buf
,
XML_NS_CYRUS
"lock/%s-%x-%u"
,
mailbox
->
uniqueid
,
strhash
(
txn
->
req_tgt
.
resource
),
owner_is_href
);
ddata
->
lock_token
=
buf_cstring
(
&
txn
->
buf
);
}
/* Update lock expiration */
ddata
->
lock_expire
=
now
+
300
;
/* 5 min */
/* Start construction of our prop response */
if
(
!
(
root
=
init_xml_response
(
"prop"
,
NS_DAV
,
root
,
ns
)))
{
ret
=
HTTP_SERVER_ERROR
;
txn
->
error
.
desc
=
"Unable to create XML response
\r\n
"
;
goto
done
;
}
outdoc
=
root
->
doc
;
root
=
xmlNewChild
(
root
,
NULL
,
BAD_CAST
"lockdiscovery"
,
NULL
);
xml_add_lockdisc
(
root
,
txn
->
req_tgt
.
path
,
(
struct
dav_data
*
)
ddata
);
lparams
->
davdb
.
write_resourceLOCKONLY
(
davdb
,
ddata
,
1
);
txn
->
resp_body
.
lock
=
ddata
->
lock_token
;
if
(
!
ddata
->
rowid
)
{
ret
=
HTTP_CREATED
;
/* Tell client about the new resource */
txn
->
resp_body
.
etag
=
NULL_ETAG
;
/* Tell client where to find the new resource */
txn
->
location
=
txn
->
req_tgt
.
path
;
}
else
ret
=
HTTP_OK
;
xml_response
(
ret
,
txn
,
outdoc
);
ret
=
0
;
done
:
if
(
davdb
)
lparams
->
davdb
.
close_db
(
davdb
);
mailbox_close
(
&
mailbox
);
if
(
outdoc
)
xmlFreeDoc
(
outdoc
);
if
(
indoc
)
xmlFreeDoc
(
indoc
);
if
(
owner
)
xmlFree
(
owner
);
return
ret
;
}
/* Perform a MKCOL/MKCALENDAR request */
/*
* preconditions:
* DAV:resource-must-be-null
* DAV:need-privileges
* DAV:valid-resourcetype
* CALDAV:calendar-collection-location-ok
* CALDAV:valid-calendar-data (CALDAV:calendar-timezone)
*/
int
meth_mkcol
(
struct
transaction_t
*
txn
,
void
*
params
)
{
struct
meth_params
*
mparams
=
(
struct
meth_params
*
)
params
;
int
ret
=
0
,
r
=
0
;
xmlDocPtr
indoc
=
NULL
,
outdoc
=
NULL
;
xmlNodePtr
root
=
NULL
,
instr
=
NULL
;
xmlNsPtr
ns
[
NUM_NAMESPACE
];
char
*
partition
=
NULL
;
struct
proppatch_ctx
pctx
;
struct
mailbox
*
mailbox
=
NULL
;
memset
(
&
pctx
,
0
,
sizeof
(
struct
proppatch_ctx
));
/* Response should not be cached */
txn
->
flags
.
cc
|=
CC_NOCACHE
;
/* Parse the path */
if
((
r
=
mparams
->
parse_path
(
txn
->
req_uri
->
path
,
&
txn
->
req_tgt
,
&
txn
->
error
.
desc
)))
{
txn
->
error
.
precond
=
CALDAV_LOCATION_OK
;
return
HTTP_FORBIDDEN
;
}
/* Make sure method is allowed (only allowed on home-set) */
if
(
!
(
txn
->
req_tgt
.
allow
&
ALLOW_WRITECOL
))
{
txn
->
error
.
precond
=
CALDAV_LOCATION_OK
;
return
HTTP_FORBIDDEN
;
}
/* Check if we are allowed to create the mailbox */
r
=
mboxlist_createmailboxcheck
(
txn
->
req_tgt
.
mboxname
,
0
,
NULL
,
httpd_userisadmin
||
httpd_userisproxyadmin
,
httpd_userid
,
httpd_authstate
,
NULL
,
&
partition
,
0
);
switch
(
r
)
{
case
0
:
break
;
case
IMAP_MAILBOX_EXISTS
:
txn
->
error
.
precond
=
DAV_RSRC_EXISTS
;
ret
=
HTTP_FORBIDDEN
;
goto
done
;
case
IMAP_PERMISSION_DENIED
:
ret
=
HTTP_NO_PRIVS
;
goto
done
;
default
:
ret
=
HTTP_SERVER_ERROR
;
txn
->
error
.
desc
=
error_message
(
r
);
goto
done
;
}
if
(
!
config_partitiondir
(
partition
))
{
/* Invalid partition, assume its a server (remote mailbox) */
char
*
server
=
partition
,
*
p
;
struct
backend
*
be
;
/* Trim remote partition */
p
=
strchr
(
server
,
'!'
);
if
(
p
)
*
p
++
=
'\0'
;
be
=
proxy_findserver
(
server
,
&
http_protocol
,
proxy_userid
,
&
backend_cached
,
NULL
,
NULL
,
httpd_in
);
if
(
!
be
)
ret
=
HTTP_UNAVAILABLE
;
else
ret
=
http_pipe_req_resp
(
be
,
txn
);
goto
done
;
}
/* Local Mailbox */
/* Parse the MKCOL/MKCALENDAR body, if exists */
ret
=
parse_xml_body
(
txn
,
&
root
);
if
(
ret
)
goto
done
;
if
(
root
)
{
/* Check for correct root element */
indoc
=
root
->
doc
;
if
(
txn
->
meth
==
METH_MKCOL
)
r
=
xmlStrcmp
(
root
->
name
,
BAD_CAST
"mkcol"
);
else
r
=
xmlStrcmp
(
root
->
name
,
BAD_CAST
mparams
->
mkcol
.
xml_req
);
if
(
r
)
{
txn
->
error
.
desc
=
"Incorrect root element in XML request
\r\n
"
;
ret
=
HTTP_BAD_MEDIATYPE
;
goto
done
;
}
instr
=
root
->
children
;
}
/* Create the mailbox */
r
=
mboxlist_createmailbox
(
txn
->
req_tgt
.
mboxname
,
mparams
->
mkcol
.
mbtype
,
partition
,
httpd_userisadmin
||
httpd_userisproxyadmin
,
httpd_userid
,
httpd_authstate
,
/*localonly*/
0
,
/*forceuser*/
0
,
/*dbonly*/
0
,
/*notify*/
0
,
&
mailbox
);
if
(
instr
&&
!
r
)
{
/* Start construction of our mkcol/mkcalendar response */
if
(
txn
->
meth
==
METH_MKCOL
)
root
=
init_xml_response
(
"mkcol-response"
,
NS_DAV
,
root
,
ns
);
else
root
=
init_xml_response
(
mparams
->
mkcol
.
xml_resp
,
mparams
->
mkcol
.
xml_ns
,
root
,
ns
);
if
(
!
root
)
{
ret
=
HTTP_SERVER_ERROR
;
txn
->
error
.
desc
=
"Unable to create XML response
\r\n
"
;
goto
done
;
}
outdoc
=
root
->
doc
;
/* Populate our proppatch context */
pctx
.
req_tgt
=
&
txn
->
req_tgt
;
pctx
.
meth
=
txn
->
meth
;
pctx
.
mailbox
=
mailbox
;
pctx
.
lprops
=
mparams
->
lprops
;
pctx
.
root
=
root
;
pctx
.
ns
=
ns
;
pctx
.
tid
=
NULL
;
pctx
.
err
=
&
txn
->
error
;
pctx
.
ret
=
&
r
;
/* Execute the property patch instructions */
if
(
!
r
)
ret
=
do_proppatch
(
&
pctx
,
instr
);
if
(
ret
||
r
)
{
/* Something failed. Abort the txn and change the OK status */
if
(
!
ret
)
{
/* Error response MUST be a multistatus */
xmlNodeSetName
(
root
,
BAD_CAST
"multistatus"
);
xmlSetNs
(
root
,
ns
[
NS_DAV
]);
/* Output the XML response */
xml_response
(
HTTP_MULTI_STATUS
,
txn
,
outdoc
);
ret
=
0
;
}
goto
done
;
}
}
if
(
!
r
)
ret
=
HTTP_CREATED
;
else
if
(
r
==
IMAP_PERMISSION_DENIED
)
ret
=
HTTP_NO_PRIVS
;
else
if
(
r
==
IMAP_MAILBOX_EXISTS
)
{
txn
->
error
.
precond
=
DAV_RSRC_EXISTS
;
ret
=
HTTP_FORBIDDEN
;
}
else
if
(
r
)
{
txn
->
error
.
desc
=
error_message
(
r
);
ret
=
HTTP_SERVER_ERROR
;
}
done
:
buf_free
(
&
pctx
.
buf
);
mailbox_close
(
&
mailbox
);
if
(
partition
)
free
(
partition
);
if
(
outdoc
)
xmlFreeDoc
(
outdoc
);
if
(
indoc
)
xmlFreeDoc
(
indoc
);
return
ret
;
}
/* dav_foreach() callback to find props on a resource */
int
propfind_by_resource
(
void
*
rock
,
void
*
data
)
{
struct
propfind_ctx
*
fctx
=
(
struct
propfind_ctx
*
)
rock
;
struct
dav_data
*
ddata
=
(
struct
dav_data
*
)
data
;
struct
index_record
record
;
char
*
p
;
size_t
len
;
int
r
,
ret
=
0
;
/* Append resource name to URL path */
if
(
!
fctx
->
req_tgt
->
resource
)
{
len
=
strlen
(
fctx
->
req_tgt
->
path
);
p
=
fctx
->
req_tgt
->
path
+
len
;
}
else
{
p
=
fctx
->
req_tgt
->
resource
;
len
=
p
-
fctx
->
req_tgt
->
path
;
}
if
(
p
[
-1
]
!=
'/'
)
{
*
p
++
=
'/'
;
len
++
;
}
strlcpy
(
p
,
ddata
->
resource
,
MAX_MAILBOX_PATH
-
len
);
fctx
->
req_tgt
->
resource
=
p
;
fctx
->
req_tgt
->
reslen
=
strlen
(
p
);
fctx
->
data
=
data
;
if
(
ddata
->
imap_uid
&&
!
fctx
->
record
)
{
/* Fetch index record for the resource */
r
=
mailbox_find_index_record
(
fctx
->
mailbox
,
ddata
->
imap_uid
,
&
record
,
NULL
);
/* XXX Check errors */
fctx
->
record
=
r
?
NULL
:
&
record
;
}
if
(
!
ddata
->
imap_uid
||
!
fctx
->
record
)
{
/* Add response for missing target */
ret
=
xml_add_response
(
fctx
,
HTTP_NOT_FOUND
,
0
);
}
else
if
(
!
fctx
->
filter
||
fctx
->
filter
(
fctx
,
data
))
{
/* Add response for target */
ret
=
xml_add_response
(
fctx
,
0
,
0
);
}
buf_free
(
&
fctx
->
msg_buf
);
fctx
->
record
=
NULL
;
fctx
->
data
=
NULL
;
return
ret
;
}
/* mboxlist_findall() callback to find props on a collection */
int
propfind_by_collection
(
char
*
mboxname
,
int
matchlen
,
int
maycreate
__attribute__
((
unused
)),
void
*
rock
)
{
struct
propfind_ctx
*
fctx
=
(
struct
propfind_ctx
*
)
rock
;
mbentry_t
*
mbentry
=
NULL
;
struct
buf
writebuf
=
BUF_INITIALIZER
;
struct
mboxname_parts
parts
;
struct
mailbox
*
mailbox
=
NULL
;
char
*
p
;
size_t
len
;
int
r
=
0
,
rights
,
root
=
1
;
mboxname_init_parts
(
&
parts
);
/* If this function is called outside of mboxlist_findall()
* with matchlen == 0, this is the root resource of the PROPFIND,
* otherwise it's just one of many found. Inbox and Outbox can't
* appear unless they are the root */
if
(
matchlen
)
{
p
=
strrchr
(
mboxname
,
'.'
);
if
(
!
p
)
goto
done
;
p
++
;
/* skip dot */
if
(
!
strncmp
(
p
,
SCHED_INBOX
,
strlen
(
SCHED_INBOX
)
-
1
))
goto
done
;
if
(
!
strncmp
(
p
,
SCHED_OUTBOX
,
strlen
(
SCHED_OUTBOX
)
-
1
))
goto
done
;
/* and while we're at it, reject the fricking top-level folders too.
* XXX - this is evil and bad and wrong */
if
(
*
p
==
'#'
)
goto
done
;
root
=
0
;
}
/* Check ACL on mailbox for current user */
r
=
mboxlist_lookup
(
mboxname
,
&
mbentry
,
NULL
);
if
(
r
==
IMAP_MAILBOX_NONEXISTENT
)
return
0
;
if
(
r
)
{
syslog
(
LOG_INFO
,
"mboxlist_lookup(%s) failed: %s"
,
mboxname
,
error_message
(
r
));
fctx
->
err
->
desc
=
error_message
(
r
);
*
fctx
->
ret
=
HTTP_SERVER_ERROR
;
goto
done
;
}
/* if finding all, we only match known types */
if
(
matchlen
&&
!
(
mbentry
->
mbtype
&
fctx
->
req_tgt
->
mboxtype
))
goto
done
;
rights
=
httpd_myrights
(
httpd_authstate
,
mbentry
->
acl
);
if
((
rights
&
fctx
->
reqd_privs
)
!=
fctx
->
reqd_privs
)
goto
done
;
/* Open mailbox for reading */
if
((
r
=
mailbox_open_irl
(
mboxname
,
&
mailbox
)))
{
syslog
(
LOG_INFO
,
"mailbox_open_irl(%s) failed: %s"
,
mboxname
,
error_message
(
r
));
fctx
->
err
->
desc
=
error_message
(
r
);
*
fctx
->
ret
=
HTTP_SERVER_ERROR
;
goto
done
;
}
fctx
->
mailbox
=
mailbox
;
fctx
->
record
=
NULL
;
if
(
!
fctx
->
req_tgt
->
resource
)
{
mboxname_to_parts
(
mboxname
,
&
parts
);
buf_setcstr
(
&
writebuf
,
fctx
->
req_tgt
->
prefix
);
if
(
parts
.
userid
)
{
buf_printf
(
&
writebuf
,
"/user/%s"
,
parts
.
userid
);
/* XXX - only if a domain... */
buf_printf
(
&
writebuf
,
"@%s"
,
parts
.
domain
?
parts
.
domain
:
httpd_extradomain
?
httpd_extradomain
:
config_defdomain
);
}
buf_putc
(
&
writebuf
,
'/'
);
len
=
writebuf
.
len
;
/* one day this will just be the final element of 'boxes' hopefully */
if
(
!
parts
.
box
)
goto
done
;
p
=
strrchr
(
parts
.
box
,
'.'
);
if
(
!
p
)
goto
done
;
/* OK, we're doing this mailbox */
buf_appendcstr
(
&
writebuf
,
p
+
1
);
/* don't forget the trailing slash */
buf_putc
(
&
writebuf
,
'/'
);
/* copy it all back into place... in theory we should check against
* 'last' and make sure it doesn't change from the original request.
* yay for micro-optimised memory usage... */
strlcpy
(
fctx
->
req_tgt
->
path
,
buf_cstring
(
&
writebuf
),
MAX_MAILBOX_PATH
);
p
=
fctx
->
req_tgt
->
path
+
len
;
fctx
->
req_tgt
->
collection
=
p
;
fctx
->
req_tgt
->
collen
=
strlen
(
p
);
/* If not filtering by calendar resource, and not excluding root,
add response for collection */
if
(
!
fctx
->
filter_crit
&&
(
!
root
||
(
fctx
->
depth
==
1
)
||
!
(
fctx
->
prefer
&
PREFER_NOROOT
))
&&
(
r
=
xml_add_response
(
fctx
,
0
,
0
)))
goto
done
;
}
if
(
fctx
->
depth
>
1
&&
fctx
->
open_db
)
{
// can't do davdb searches if no dav db
/* Resource(s) */
/* Open the DAV DB corresponding to the mailbox.
*
* Note we open the new one first before closing the old one, so we
* get refcounted retaining of the open database within a single user */
sqlite3
*
newdb
=
fctx
->
open_db
(
mailbox
);
if
(
fctx
->
davdb
)
fctx
->
close_db
(
fctx
->
davdb
);
fctx
->
davdb
=
newdb
;
if
(
fctx
->
req_tgt
->
resource
)
{
/* Add response for target resource */
void
*
data
;
/* Find message UID for the resource */
fctx
->
lookup_resource
(
fctx
->
davdb
,
mboxname
,
fctx
->
req_tgt
->
resource
,
0
,
&
data
,
0
);
/* XXX Check errors */
r
=
fctx
->
proc_by_resource
(
rock
,
data
);
}
else
{
/* Add responses for all contained resources */
fctx
->
foreach_resource
(
fctx
->
davdb
,
mboxname
,
fctx
->
proc_by_resource
,
rock
);
/* Started with NULL resource, end with NULL resource */
fctx
->
req_tgt
->
resource
=
NULL
;
fctx
->
req_tgt
->
reslen
=
0
;
}
}
done
:
buf_free
(
&
writebuf
);
mboxname_free_parts
(
&
parts
);
mboxlist_entry_free
(
&
mbentry
);
if
(
mailbox
)
mailbox_close
(
&
mailbox
);
return
r
;
}
/* Perform a PROPFIND request */
EXPORTED
int
meth_propfind
(
struct
transaction_t
*
txn
,
void
*
params
)
{
struct
meth_params
*
fparams
=
(
struct
meth_params
*
)
params
;
int
ret
=
0
,
r
;
const
char
**
hdr
;
unsigned
depth
;
xmlDocPtr
indoc
=
NULL
,
outdoc
=
NULL
;
xmlNodePtr
root
,
cur
=
NULL
,
props
=
NULL
;
xmlNsPtr
ns
[
NUM_NAMESPACE
];
struct
hash_table
ns_table
=
{
0
,
NULL
,
NULL
};
struct
propfind_ctx
fctx
;
struct
propfind_entry_list
*
elist
=
NULL
;
memset
(
&
fctx
,
0
,
sizeof
(
struct
propfind_ctx
));
/* Parse the path */
if
(
fparams
->
parse_path
)
{
r
=
fparams
->
parse_path
(
txn
->
req_uri
->
path
,
&
txn
->
req_tgt
,
&
txn
->
error
.
desc
);
if
(
r
)
return
r
;
}
/* Make sure method is allowed */
if
(
!
(
txn
->
req_tgt
.
allow
&
ALLOW_DAV
))
return
HTTP_NOT_ALLOWED
;
/* Check Depth */
hdr
=
spool_getheader
(
txn
->
req_hdrs
,
"Depth"
);
if
(
!
hdr
||
!
strcmp
(
hdr
[
0
],
"infinity"
))
{
depth
=
3
;
}
else
if
(
!
strcmp
(
hdr
[
0
],
"1"
))
{
depth
=
1
;
}
else
if
(
!
strcmp
(
hdr
[
0
],
"0"
))
{
depth
=
0
;
}
else
{
txn
->
error
.
desc
=
"Illegal Depth value
\r\n
"
;
return
HTTP_BAD_REQUEST
;
}
if
((
txn
->
req_tgt
.
namespace
!=
URL_NS_PRINCIPAL
)
&&
txn
->
req_tgt
.
userid
&&
txn
->
req_tgt
.
mboxname
&&
txn
->
req_tgt
.
mboxname
[
0
])
{
mbentry_t
*
mbentry
=
NULL
;
int
rights
;
/* Locate the mailbox */
r
=
http_mlookup
(
txn
->
req_tgt
.
mboxname
,
&
mbentry
,
NULL
);
if
(
r
)
{
syslog
(
LOG_ERR
,
"mlookup(%s) failed: %s"
,
txn
->
req_tgt
.
mboxname
,
error_message
(
r
));
txn
->
error
.
desc
=
error_message
(
r
);
switch
(
r
)
{
case
IMAP_PERMISSION_DENIED
:
return
HTTP_FORBIDDEN
;
case
IMAP_MAILBOX_NONEXISTENT
:
return
HTTP_NOT_FOUND
;
default
:
return
HTTP_SERVER_ERROR
;
}
}
/* Check ACL for current user */
rights
=
httpd_myrights
(
httpd_authstate
,
mbentry
->
acl
);
if
((
rights
&
DACL_READ
)
!=
DACL_READ
)
{
/* DAV:need-privileges */
txn
->
error
.
precond
=
DAV_NEED_PRIVS
;
txn
->
error
.
resource
=
txn
->
req_tgt
.
path
;
txn
->
error
.
rights
=
DACL_READ
;
mboxlist_entry_free
(
&
mbentry
);
ret
=
HTTP_NO_PRIVS
;
goto
done
;
}
if
(
mbentry
->
server
)
{
/* Remote mailbox */
struct
backend
*
be
;
be
=
proxy_findserver
(
mbentry
->
server
,
&
http_protocol
,
proxy_userid
,
&
backend_cached
,
NULL
,
NULL
,
httpd_in
);
mboxlist_entry_free
(
&
mbentry
);
if
(
!
be
)
return
HTTP_UNAVAILABLE
;
return
http_pipe_req_resp
(
be
,
txn
);
}
mboxlist_entry_free
(
&
mbentry
);
/* Local Mailbox */
}
/* Principal or Local Mailbox */
/* Normalize depth so that:
* 0 = home-set collection, 1+ = calendar collection, 2+ = calendar resource, 3+ = infinity!
*/
if
(
txn
->
req_tgt
.
collection
)
depth
++
;
if
(
txn
->
req_tgt
.
resource
)
depth
++
;
/* Parse the PROPFIND body, if exists */
ret
=
parse_xml_body
(
txn
,
&
root
);
if
(
ret
)
goto
done
;
if
(
!
root
)
{
/* Empty request */
fctx
.
mode
=
PROPFIND_ALL
;
}
else
{
indoc
=
root
->
doc
;
/* Make sure its a propfind element */
if
(
xmlStrcmp
(
root
->
name
,
BAD_CAST
"propfind"
))
{
txn
->
error
.
desc
=
"Missing propfind element in PROPFIND request
\r\n
"
;
ret
=
HTTP_BAD_REQUEST
;
goto
done
;
}
/* Find child element of propfind */
for
(
cur
=
root
->
children
;
cur
&&
cur
->
type
!=
XML_ELEMENT_NODE
;
cur
=
cur
->
next
);
if
(
!
cur
)
{
txn
->
error
.
desc
=
"Missing child node element in PROPFIND request
\r\n
"
;
ret
=
HTTP_BAD_REQUEST
;
goto
done
;
}
/* Add propfind type to our header cache */
spool_cache_header
(
xstrdup
(
":type"
),
xstrdup
((
const
char
*
)
cur
->
name
),
txn
->
req_hdrs
);
/* Make sure its a known element */
if
(
!
xmlStrcmp
(
cur
->
name
,
BAD_CAST
"allprop"
))
{
fctx
.
mode
=
PROPFIND_ALL
;
}
else
if
(
!
xmlStrcmp
(
cur
->
name
,
BAD_CAST
"propname"
))
{
fctx
.
mode
=
PROPFIND_NAME
;
fctx
.
prefer
=
PREFER_MIN
;
/* Don't want 404 (Not Found) */
}
else
if
(
!
xmlStrcmp
(
cur
->
name
,
BAD_CAST
"prop"
))
{
fctx
.
mode
=
PROPFIND_PROP
;
props
=
cur
->
children
;
}
else
{
ret
=
HTTP_BAD_REQUEST
;
goto
done
;
}
/* Check for extra elements */
for
(
cur
=
cur
->
next
;
cur
;
cur
=
cur
->
next
)
{
if
(
cur
->
type
==
XML_ELEMENT_NODE
)
{
if
((
fctx
.
mode
==
PROPFIND_ALL
)
&&
!
props
&&
/* Check for 'include' element */
!
xmlStrcmp
(
cur
->
name
,
BAD_CAST
"include"
))
{
props
=
cur
->
children
;
}
else
{
ret
=
HTTP_BAD_REQUEST
;
goto
done
;
}
}
}
}
/* Start construction of our multistatus response */
root
=
init_xml_response
(
"multistatus"
,
NS_DAV
,
root
,
ns
);
if
(
!
root
)
{
ret
=
HTTP_SERVER_ERROR
;
txn
->
error
.
desc
=
"Unable to create XML response
\r\n
"
;
goto
done
;
}
outdoc
=
root
->
doc
;
/* Populate our propfind context */
fctx
.
req_tgt
=
&
txn
->
req_tgt
;
fctx
.
depth
=
depth
;
fctx
.
prefer
|=
get_preferences
(
txn
);
fctx
.
req_hdrs
=
txn
->
req_hdrs
;
fctx
.
userid
=
proxy_userid
;
fctx
.
userisadmin
=
httpd_userisadmin
;
fctx
.
authstate
=
httpd_authstate
;
fctx
.
mailbox
=
NULL
;
fctx
.
record
=
NULL
;
fctx
.
reqd_privs
=
DACL_READ
;
fctx
.
filter
=
NULL
;
fctx
.
filter_crit
=
NULL
;
fctx
.
open_db
=
fparams
->
davdb
.
open_db
;
fctx
.
close_db
=
fparams
->
davdb
.
close_db
;
fctx
.
lookup_resource
=
fparams
->
davdb
.
lookup_resource
;
fctx
.
foreach_resource
=
fparams
->
davdb
.
foreach_resource
;
fctx
.
proc_by_resource
=
&
propfind_by_resource
;
fctx
.
elist
=
NULL
;
fctx
.
lprops
=
fparams
->
lprops
;
fctx
.
root
=
root
;
fctx
.
ns
=
ns
;
fctx
.
ns_table
=
&
ns_table
;
fctx
.
err
=
&
txn
->
error
;
fctx
.
ret
=
&
ret
;
fctx
.
fetcheddata
=
0
;
/* Parse the list of properties and build a list of callbacks */
preload_proplist
(
props
,
&
fctx
);
if
(
!
txn
->
req_tgt
.
collection
&&
(
!
depth
||
!
(
fctx
.
prefer
&
PREFER_NOROOT
)))
{
/* Add response for principal or home-set collection */
if
(
*
txn
->
req_tgt
.
mboxname
)
{
/* Open mailbox for reading */
if
((
r
=
mailbox_open_irl
(
txn
->
req_tgt
.
mboxname
,
&
fctx
.
mailbox
)))
{
syslog
(
LOG_INFO
,
"mailbox_open_irl(%s) failed: %s"
,
txn
->
req_tgt
.
mboxname
,
error_message
(
r
));
txn
->
error
.
desc
=
error_message
(
r
);
ret
=
HTTP_SERVER_ERROR
;
goto
done
;
}
}
xml_add_response
(
&
fctx
,
0
,
0
);
mailbox_close
(
&
fctx
.
mailbox
);
}
if
(
depth
>
0
)
{
/* Calendar collection(s) */
if
(
txn
->
req_tgt
.
collection
)
{
/* Add response for target calendar collection */
propfind_by_collection
(
txn
->
req_tgt
.
mboxname
,
0
,
0
,
&
fctx
);
}
else
{
char
*
base
=
NULL
;
/* Add responses for all contained calendar collections */
struct
mboxname_parts
parts
;
mboxname_userid_to_parts
(
httpd_userid
,
&
parts
);
if
(
parts
.
domain
)
base
=
strconcat
(
parts
.
domain
,
"!user.*"
,
(
const
char
*
)
NULL
);
else
base
=
xstrdup
(
"user.*"
);
mboxname_free_parts
(
&
parts
);
r
=
mboxlist_findall
(
NULL
,
/* internal namespace */
base
,
1
,
NULL
,
httpd_authstate
,
propfind_by_collection
,
&
fctx
);
free
(
base
);
}
if
(
fctx
.
davdb
)
fctx
.
close_db
(
fctx
.
davdb
);
ret
=
*
fctx
.
ret
;
}
/* Output the XML response */
if
(
!
ret
)
{
/* iCalendar data in response should not be transformed */
if
(
fctx
.
fetcheddata
)
txn
->
flags
.
cc
|=
CC_NOTRANSFORM
;
xml_response
(
HTTP_MULTI_STATUS
,
txn
,
outdoc
);
}
done
:
/* Free the entry list */
elist
=
fctx
.
elist
;
while
(
elist
)
{
struct
propfind_entry_list
*
freeme
=
elist
;
elist
=
elist
->
next
;
xmlFree
(
freeme
->
name
);
free
(
freeme
);
}
buf_free
(
&
fctx
.
buf
);
free_hash_table
(
&
ns_table
,
NULL
);
if
(
outdoc
)
xmlFreeDoc
(
outdoc
);
if
(
indoc
)
xmlFreeDoc
(
indoc
);
return
ret
;
}
/* Perform a PROPPATCH request
*
* preconditions:
* DAV:cannot-modify-protected-property
* CALDAV:valid-calendar-data (CALDAV:calendar-timezone)
*/
int
meth_proppatch
(
struct
transaction_t
*
txn
,
void
*
params
)
{
struct
meth_params
*
pparams
=
(
struct
meth_params
*
)
params
;
int
ret
=
0
,
r
=
0
,
rights
;
xmlDocPtr
indoc
=
NULL
,
outdoc
=
NULL
;
xmlNodePtr
root
,
instr
,
resp
;
xmlNsPtr
ns
[
NUM_NAMESPACE
];
struct
mailbox
*
mailbox
=
NULL
;
mbentry_t
*
mbentry
=
NULL
;
struct
proppatch_ctx
pctx
;
struct
index_record
record
;
void
*
davdb
=
NULL
;
memset
(
&
pctx
,
0
,
sizeof
(
struct
proppatch_ctx
));
/* Response should not be cached */
txn
->
flags
.
cc
|=
CC_NOCACHE
;
/* Parse the path */
if
((
r
=
pparams
->
parse_path
(
txn
->
req_uri
->
path
,
&
txn
->
req_tgt
,
&
txn
->
error
.
desc
)))
return
r
;
if
(
!
txn
->
req_tgt
.
collection
&&
!
txn
->
req_tgt
.
user
)
{
txn
->
error
.
desc
=
"PROPPATCH requires a collection"
;
return
HTTP_NOT_ALLOWED
;
}
/* Locate the mailbox */
r
=
http_mlookup
(
txn
->
req_tgt
.
mboxname
,
&
mbentry
,
NULL
);
if
(
r
)
{
syslog
(
LOG_ERR
,
"mlookup(%s) failed: %s"
,
txn
->
req_tgt
.
mboxname
,
error_message
(
r
));
txn
->
error
.
desc
=
error_message
(
r
);
switch
(
r
)
{
case
IMAP_PERMISSION_DENIED
:
return
HTTP_FORBIDDEN
;
case
IMAP_MAILBOX_NONEXISTENT
:
return
HTTP_NOT_FOUND
;
default
:
return
HTTP_SERVER_ERROR
;
}
}
/* Check ACL for current user */
rights
=
httpd_myrights
(
httpd_authstate
,
mbentry
->
acl
);
if
(
!
(
rights
&
DACL_WRITEPROPS
))
{
/* DAV:need-privileges */
txn
->
error
.
precond
=
DAV_NEED_PRIVS
;
txn
->
error
.
resource
=
txn
->
req_tgt
.
path
;
txn
->
error
.
rights
=
DACL_WRITEPROPS
;
mboxlist_entry_free
(
&
mbentry
);
return
HTTP_NO_PRIVS
;
}
if
(
mbentry
->
server
)
{
/* Remote mailbox */
struct
backend
*
be
;
be
=
proxy_findserver
(
mbentry
->
server
,
&
http_protocol
,
proxy_userid
,
&
backend_cached
,
NULL
,
NULL
,
httpd_in
);
mboxlist_entry_free
(
&
mbentry
);
if
(
!
be
)
return
HTTP_UNAVAILABLE
;
return
http_pipe_req_resp
(
be
,
txn
);
}
mboxlist_entry_free
(
&
mbentry
);
/* Local Mailbox */
r
=
mailbox_open_iwl
(
txn
->
req_tgt
.
mboxname
,
&
mailbox
);
if
(
r
)
{
syslog
(
LOG_ERR
,
"IOERROR: failed to open mailbox %s for proppatch"
,
txn
->
req_tgt
.
mboxname
);
return
HTTP_SERVER_ERROR
;
}
/* Parse the PROPPATCH body */
ret
=
parse_xml_body
(
txn
,
&
root
);
if
(
!
ret
&&
!
root
)
{
txn
->
error
.
desc
=
"Missing request body
\r\n
"
;
ret
=
HTTP_BAD_REQUEST
;
}
if
(
ret
)
goto
done
;
indoc
=
root
->
doc
;
/* Make sure its a propertyupdate element */
if
(
xmlStrcmp
(
root
->
name
,
BAD_CAST
"propertyupdate"
))
{
txn
->
error
.
desc
=
"Missing propertyupdate element in PROPPATCH request
\r\n
"
;
ret
=
HTTP_BAD_REQUEST
;
goto
done
;
}
instr
=
root
->
children
;
/* Start construction of our multistatus response */
if
(
!
(
root
=
init_xml_response
(
"multistatus"
,
NS_DAV
,
root
,
ns
)))
{
txn
->
error
.
desc
=
"Unable to create XML response
\r\n
"
;
ret
=
HTTP_SERVER_ERROR
;
goto
done
;
}
outdoc
=
root
->
doc
;
/* Add a response tree to 'root' for the specified href */
resp
=
xmlNewChild
(
root
,
NULL
,
BAD_CAST
"response"
,
NULL
);
if
(
!
resp
)
syslog
(
LOG_ERR
,
"new child response failed"
);
xmlNewChild
(
resp
,
NULL
,
BAD_CAST
"href"
,
BAD_CAST
txn
->
req_tgt
.
path
);
/* Populate our proppatch context */
pctx
.
req_tgt
=
&
txn
->
req_tgt
;
pctx
.
meth
=
txn
->
meth
;
pctx
.
mailbox
=
mailbox
;
pctx
.
record
=
NULL
;
pctx
.
lprops
=
pparams
->
lprops
;
pctx
.
root
=
resp
;
pctx
.
ns
=
ns
;
pctx
.
tid
=
NULL
;
pctx
.
err
=
&
txn
->
error
;
pctx
.
ret
=
&
r
;
if
(
txn
->
req_tgt
.
resource
)
{
struct
dav_data
*
ddata
;
/* gotta find the resource */
/* Open the DAV DB corresponding to the mailbox */
davdb
=
pparams
->
davdb
.
open_db
(
mailbox
);
/* Find message UID for the resource */
pparams
->
davdb
.
lookup_resource
(
davdb
,
txn
->
req_tgt
.
mboxname
,
txn
->
req_tgt
.
resource
,
0
,
(
void
**
)
&
ddata
,
0
);
if
(
!
ddata
->
imap_uid
)
{
ret
=
HTTP_NOT_FOUND
;
goto
done
;
}
memset
(
&
record
,
0
,
sizeof
(
struct
index_record
));
/* Mapped URL - Fetch index record for the resource */
r
=
mailbox_find_index_record
(
mailbox
,
ddata
->
imap_uid
,
&
record
,
NULL
);
if
(
r
)
{
ret
=
HTTP_NOT_FOUND
;
goto
done
;
}
pctx
.
record
=
&
record
;
}
/* Execute the property patch instructions */
ret
=
do_proppatch
(
&
pctx
,
instr
);
/* Output the XML response */
if
(
!
ret
)
{
if
(
get_preferences
(
txn
)
&
PREFER_MIN
)
ret
=
HTTP_OK
;
else
xml_response
(
HTTP_MULTI_STATUS
,
txn
,
outdoc
);
}
done
:
if
(
davdb
)
pparams
->
davdb
.
close_db
(
davdb
);
mailbox_close
(
&
mailbox
);
buf_free
(
&
pctx
.
buf
);
if
(
outdoc
)
xmlFreeDoc
(
outdoc
);
if
(
indoc
)
xmlFreeDoc
(
indoc
);
return
ret
;
}
/* Perform a POST request */
int
meth_post
(
struct
transaction_t
*
txn
,
void
*
params
)
{
struct
meth_params
*
pparams
=
(
struct
meth_params
*
)
params
;
static
unsigned
post_count
=
0
;
struct
strlist
*
action
;
int
r
,
ret
;
size_t
len
;
/* Response should not be cached */
txn
->
flags
.
cc
|=
CC_NOCACHE
;
/* Parse the path */
if
((
r
=
pparams
->
parse_path
(
txn
->
req_uri
->
path
,
&
txn
->
req_tgt
,
&
txn
->
error
.
desc
)))
return
r
;
/* Make sure method is allowed (only allowed on certain collections) */
if
(
!
(
txn
->
req_tgt
.
allow
&
ALLOW_POST
))
return
HTTP_NOT_ALLOWED
;
/* Do any special processing */
if
(
pparams
->
post
)
{
ret
=
pparams
->
post
(
txn
);
if
(
ret
!=
HTTP_CONTINUE
)
return
ret
;
}
action
=
hash_lookup
(
"action"
,
&
txn
->
req_qparams
);
if
(
!
action
||
action
->
next
||
strcmp
(
action
->
s
,
"add-member"
))
return
HTTP_FORBIDDEN
;
/* POST add-member to regular collection */
/* Append a unique resource name to URL path and perform a PUT */
len
=
strlen
(
txn
->
req_tgt
.
path
);
txn
->
req_tgt
.
resource
=
txn
->
req_tgt
.
path
+
len
;
txn
->
req_tgt
.
reslen
=
snprintf
(
txn
->
req_tgt
.
resource
,
MAX_MAILBOX_PATH
-
len
,
"%x-%d-%ld-%u.ics"
,
strhash
(
txn
->
req_tgt
.
path
),
getpid
(),
time
(
0
),
post_count
++
);
/* Tell client where to find the new resource */
txn
->
location
=
txn
->
req_tgt
.
path
;
ret
=
meth_put
(
txn
,
params
);
if
(
ret
!=
HTTP_CREATED
)
txn
->
location
=
NULL
;
return
ret
;
}
/* Perform a PUT request
*
* preconditions:
* *DAV:supported-address-data
*/
int
meth_put
(
struct
transaction_t
*
txn
,
void
*
params
)
{
struct
meth_params
*
pparams
=
(
struct
meth_params
*
)
params
;
int
ret
,
r
,
precond
,
rights
;
const
char
**
hdr
,
*
etag
;
struct
mime_type_t
*
mime
=
NULL
;
struct
buf
synctoken
=
BUF_INITIALIZER
;
struct
mailbox
*
mailbox
=
NULL
;
mbentry_t
*
mbentry
=
NULL
;
struct
dav_data
*
ddata
;
struct
index_record
oldrecord
;
quota_t
qdiffs
[
QUOTA_NUMRESOURCES
]
=
QUOTA_DIFFS_INITIALIZER
;
time_t
lastmod
;
unsigned
flags
=
0
;
void
*
davdb
=
NULL
,
*
obj
=
NULL
;
struct
buf
msg_buf
=
BUF_INITIALIZER
;
if
(
txn
->
meth
==
METH_PUT
)
{
/* Response should not be cached */
txn
->
flags
.
cc
|=
CC_NOCACHE
;
/* Parse the path */
if
((
r
=
pparams
->
parse_path
(
txn
->
req_uri
->
path
,
&
txn
->
req_tgt
,
&
txn
->
error
.
desc
)))
{
return
HTTP_FORBIDDEN
;
}
/* Make sure method is allowed (only allowed on resources) */
if
(
!
(
txn
->
req_tgt
.
allow
&
ALLOW_WRITE
))
return
HTTP_NOT_ALLOWED
;
}
/* Make sure Content-Range isn't specified */
if
(
spool_getheader
(
txn
->
req_hdrs
,
"Content-Range"
))
return
HTTP_BAD_REQUEST
;
/* Check Content-Type */
if
((
hdr
=
spool_getheader
(
txn
->
req_hdrs
,
"Content-Type"
)))
{
for
(
mime
=
pparams
->
mime_types
;
mime
->
content_type
;
mime
++
)
{
if
(
is_mediatype
(
mime
->
content_type
,
hdr
[
0
]))
break
;
}
}
if
(
!
mime
||
!
mime
->
content_type
)
{
txn
->
error
.
precond
=
pparams
->
put
.
supp_data_precond
;
return
HTTP_FORBIDDEN
;
}
/* Locate the mailbox */
r
=
http_mlookup
(
txn
->
req_tgt
.
mboxname
,
&
mbentry
,
NULL
);
if
(
r
)
{
syslog
(
LOG_ERR
,
"mlookup(%s) failed: %s"
,
txn
->
req_tgt
.
mboxname
,
error_message
(
r
));
txn
->
error
.
desc
=
error_message
(
r
);
switch
(
r
)
{
case
IMAP_PERMISSION_DENIED
:
return
HTTP_FORBIDDEN
;
case
IMAP_MAILBOX_NONEXISTENT
:
return
HTTP_NOT_FOUND
;
default
:
return
HTTP_SERVER_ERROR
;
}
}
/* Check ACL for current user */
rights
=
httpd_myrights
(
httpd_authstate
,
mbentry
->
acl
);
if
(
!
(
rights
&
DACL_WRITECONT
)
||
!
(
rights
&
DACL_ADDRSRC
))
{
/* DAV:need-privileges */
txn
->
error
.
precond
=
DAV_NEED_PRIVS
;
txn
->
error
.
resource
=
txn
->
req_tgt
.
path
;
txn
->
error
.
rights
=
!
(
rights
&
DACL_WRITECONT
)
?
DACL_WRITECONT
:
DACL_ADDRSRC
;
mboxlist_entry_free
(
&
mbentry
);
return
HTTP_NO_PRIVS
;
}
if
(
mbentry
->
server
)
{
/* Remote mailbox */
struct
backend
*
be
;
be
=
proxy_findserver
(
mbentry
->
server
,
&
http_protocol
,
proxy_userid
,
&
backend_cached
,
NULL
,
NULL
,
httpd_in
);
mboxlist_entry_free
(
&
mbentry
);
if
(
!
be
)
return
HTTP_UNAVAILABLE
;
return
http_pipe_req_resp
(
be
,
txn
);
}
mboxlist_entry_free
(
&
mbentry
);
/* Local Mailbox */
/* Read body */
txn
->
req_body
.
flags
|=
BODY_DECODE
;
ret
=
http_read_body
(
httpd_in
,
httpd_out
,
txn
->
req_hdrs
,
&
txn
->
req_body
,
&
txn
->
error
.
desc
);
if
(
ret
)
{
txn
->
flags
.
conn
=
CONN_CLOSE
;
return
ret
;
}
/* Make sure we have a body */
qdiffs
[
QUOTA_STORAGE
]
=
buf_len
(
&
txn
->
req_body
.
payload
);
if
(
!
qdiffs
[
QUOTA_STORAGE
])
{
txn
->
error
.
desc
=
"Missing request body
\r\n
"
;
return
HTTP_BAD_REQUEST
;
}
/* Check if we can append a new message to mailbox */
if
((
r
=
append_check
(
txn
->
req_tgt
.
mboxname
,
httpd_authstate
,
ACL_INSERT
,
qdiffs
)))
{
syslog
(
LOG_ERR
,
"append_check(%s) failed: %s"
,
txn
->
req_tgt
.
mboxname
,
error_message
(
r
));
txn
->
error
.
desc
=
error_message
(
r
);
return
HTTP_SERVER_ERROR
;
}
/* Open mailbox for writing */
r
=
mailbox_open_iwl
(
txn
->
req_tgt
.
mboxname
,
&
mailbox
);
if
(
r
)
{
syslog
(
LOG_ERR
,
"http_mailbox_open(%s) failed: %s"
,
txn
->
req_tgt
.
mboxname
,
error_message
(
r
));
txn
->
error
.
desc
=
error_message
(
r
);
return
HTTP_SERVER_ERROR
;
}
/* Open the DAV DB corresponding to the mailbox */
davdb
=
pparams
->
davdb
.
open_db
(
mailbox
);
/* Find message UID for the resource, if exists */
pparams
->
davdb
.
lookup_resource
(
davdb
,
txn
->
req_tgt
.
mboxname
,
txn
->
req_tgt
.
resource
,
0
,
(
void
*
)
&
ddata
,
0
);
/* XXX Check errors */
if
(
ddata
->
imap_uid
)
{
/* Overwriting existing resource */
/* Fetch index record for the resource */
r
=
mailbox_find_index_record
(
mailbox
,
ddata
->
imap_uid
,
&
oldrecord
,
NULL
);
if
(
r
)
{
syslog
(
LOG_ERR
,
"mailbox_find_index_record(%s, %u) failed: %s"
,
txn
->
req_tgt
.
mboxname
,
ddata
->
imap_uid
,
error_message
(
r
));
txn
->
error
.
desc
=
error_message
(
r
);
ret
=
HTTP_SERVER_ERROR
;
goto
done
;
}
etag
=
message_guid_encode
(
&
oldrecord
.
guid
);
lastmod
=
oldrecord
.
internaldate
;
}
else
if
(
ddata
->
rowid
)
{
/* Unmapped URL (empty resource) */
etag
=
NULL_ETAG
;
lastmod
=
ddata
->
creationdate
;
}
else
{
/* New resource */
etag
=
NULL
;
lastmod
=
0
;
}
/* Check any preferences */
if
(
get_preferences
(
txn
)
&
PREFER_REP
)
flags
|=
PREFER_REP
;
/* Check any preconditions */
ret
=
precond
=
pparams
->
check_precond
(
txn
,
ddata
,
etag
,
lastmod
);
switch
(
precond
)
{
case
HTTP_OK
:
/* Parse, validate, and store the resource */
obj
=
mime
->
from_string
(
buf_cstring
(
&
txn
->
req_body
.
payload
));
ret
=
pparams
->
put
.
proc
(
txn
,
obj
,
mailbox
,
txn
->
req_tgt
.
resource
,
davdb
,
flags
);
break
;
case
HTTP_PRECOND_FAILED
:
if
(
flags
&
PREFER_REP
)
{
unsigned
offset
;
/* Load message containing the resource */
mailbox_map_record
(
mailbox
,
&
oldrecord
,
&
msg_buf
);
/* Resource length doesn't include RFC 5322 header */
offset
=
oldrecord
.
header_size
;
/* Parse existing resource */
obj
=
pparams
->
mime_types
[
0
].
from_string
(
buf_base
(
&
msg_buf
)
+
offset
);
/* Fill in ETag and Last-Modified */
txn
->
resp_body
.
etag
=
etag
;
txn
->
resp_body
.
lastmod
=
lastmod
;
}
break
;
case
HTTP_LOCKED
:
txn
->
error
.
precond
=
DAV_NEED_LOCK_TOKEN
;
txn
->
error
.
resource
=
txn
->
req_tgt
.
path
;
default
:
/* We failed a precondition */
goto
done
;
}
if
(
flags
&
PREFER_REP
)
{
struct
resp_body_t
*
resp_body
=
&
txn
->
resp_body
;
const
char
**
hdr
;
char
*
data
;
unsigned
long
datalen
;
if
((
hdr
=
spool_getheader
(
txn
->
req_hdrs
,
"Accept"
)))
{
mime
=
get_accept_type
(
hdr
,
pparams
->
mime_types
);
if
(
!
mime
)
goto
done
;
}
switch
(
ret
)
{
case
HTTP_NO_CONTENT
:
ret
=
HTTP_OK
;
case
HTTP_CREATED
:
case
HTTP_PRECOND_FAILED
:
/* Convert into requested MIME type */
data
=
mime
->
to_string
(
obj
);
datalen
=
strlen
(
data
);
/* Fill in Content-Type, Content-Length */
resp_body
->
type
=
mime
->
content_type
;
resp_body
->
len
=
datalen
;
/* Fill in Content-Location */
resp_body
->
loc
=
txn
->
req_tgt
.
path
;
/* Fill in Expires and Cache-Control */
resp_body
->
maxage
=
3600
;
/* 1 hr */
txn
->
flags
.
cc
=
CC_MAXAGE
|
CC_REVALIDATE
/* don't use stale data */
|
CC_NOTRANSFORM
;
/* don't alter iCal data */
/* Output current representation */
write_body
(
ret
,
txn
,
data
,
datalen
);
free
(
data
);
ret
=
0
;
break
;
default
:
/* failure - do nothing */
break
;
}
}
get_synctoken
(
mailbox
,
&
synctoken
);
txn
->
resp_body
.
synctoken
=
buf_cstring
(
&
synctoken
);
write_body
(
ret
,
txn
,
NULL
,
0
);
buf_free
(
&
synctoken
);
done
:
if
(
obj
)
{
pparams
->
mime_types
[
0
].
free
(
obj
);
buf_free
(
&
msg_buf
);
}
if
(
davdb
)
pparams
->
davdb
.
close_db
(
davdb
);
mailbox_close
(
&
mailbox
);
return
ret
;
}
/* Compare modseq in index maps -- used for sorting */
static
int
map_modseq_cmp
(
const
struct
index_map
*
m1
,
const
struct
index_map
*
m2
)
{
if
(
m1
->
modseq
<
m2
->
modseq
)
return
-1
;
if
(
m1
->
modseq
>
m2
->
modseq
)
return
1
;
return
0
;
}
/* CALDAV:calendar-multiget/CARDDAV:addressbook-multiget REPORT */
int
report_multiget
(
struct
transaction_t
*
txn
,
struct
meth_params
*
rparams
,
xmlNodePtr
inroot
,
struct
propfind_ctx
*
fctx
)
{
int
r
,
ret
=
0
;
struct
mailbox
*
mailbox
=
NULL
;
xmlNodePtr
node
;
struct
buf
uri
=
BUF_INITIALIZER
;
/* Get props for each href */
for
(
node
=
inroot
->
children
;
node
;
node
=
node
->
next
)
{
if
((
node
->
type
==
XML_ELEMENT_NODE
)
&&
!
xmlStrcmp
(
node
->
name
,
BAD_CAST
"href"
))
{
xmlChar
*
href
=
xmlNodeListGetString
(
inroot
->
doc
,
node
->
children
,
1
);
int
len
=
xmlStrlen
(
href
);
struct
request_target_t
tgt
;
struct
dav_data
*
ddata
;
buf_ensure
(
&
uri
,
len
);
xmlURIUnescapeString
((
const
char
*
)
href
,
len
,
uri
.
s
);
xmlFree
(
href
);
/* Parse the path */
memset
(
&
tgt
,
0
,
sizeof
(
struct
request_target_t
));
tgt
.
namespace
=
txn
->
req_tgt
.
namespace
;
if
((
r
=
rparams
->
parse_path
(
uri
.
s
,
&
tgt
,
&
fctx
->
err
->
desc
)))
{
ret
=
r
;
goto
done
;
}
fctx
->
req_tgt
=
&
tgt
;
/* Check if we already have this mailbox open */
if
(
!
mailbox
||
strcmp
(
mailbox
->
name
,
tgt
.
mboxname
))
{
if
(
mailbox
)
mailbox_close
(
&
mailbox
);
/* Open mailbox for reading */
r
=
mailbox_open_irl
(
tgt
.
mboxname
,
&
mailbox
);
if
(
r
&&
r
!=
IMAP_MAILBOX_NONEXISTENT
)
{
syslog
(
LOG_ERR
,
"http_mailbox_open(%s) failed: %s"
,
tgt
.
mboxname
,
error_message
(
r
));
txn
->
error
.
desc
=
error_message
(
r
);
ret
=
HTTP_SERVER_ERROR
;
goto
done
;
}
fctx
->
mailbox
=
mailbox
;
}
if
(
!
fctx
->
mailbox
||
!
tgt
.
resource
)
{
/* Add response for missing target */
xml_add_response
(
fctx
,
HTTP_NOT_FOUND
,
0
);
continue
;
}
/* Open the DAV DB corresponding to the mailbox */
fctx
->
davdb
=
rparams
->
davdb
.
open_db
(
fctx
->
mailbox
);
/* Find message UID for the resource */
rparams
->
davdb
.
lookup_resource
(
fctx
->
davdb
,
tgt
.
mboxname
,
tgt
.
resource
,
0
,
(
void
**
)
&
ddata
);
ddata
->
resource
=
tgt
.
resource
;
/* XXX Check errors */
fctx
->
proc_by_resource
(
fctx
,
ddata
);
rparams
->
davdb
.
close_db
(
fctx
->
davdb
);
}
}
done
:
mailbox_close
(
&
mailbox
);
buf_free
(
&
uri
);
return
(
ret
?
ret
:
HTTP_MULTI_STATUS
);
}
/* DAV:sync-collection REPORT */
int
report_sync_col
(
struct
transaction_t
*
txn
,
struct
meth_params
*
rparams
__attribute__
((
unused
)),
xmlNodePtr
inroot
,
struct
propfind_ctx
*
fctx
)
{
int
ret
=
0
,
r
,
i
,
unbind_flag
=
-1
,
unchanged_flag
=
-1
;
struct
mailbox
*
mailbox
=
NULL
;
uint32_t
uidvalidity
=
0
;
modseq_t
syncmodseq
=
0
;
modseq_t
basemodseq
=
0
;
modseq_t
highestmodseq
=
0
;
modseq_t
respmodseq
=
0
;
uint32_t
limit
=
-1
;
uint32_t
recno
;
uint32_t
msgno
;
uint32_t
nresp
=
0
;
xmlNodePtr
node
;
struct
index_state
istate
;
struct
index_record
record
;
char
tokenuri
[
MAX_MAILBOX_PATH
+
1
];
/* XXX Handle Depth (cal-home-set at toplevel) */
memset
(
&
istate
,
0
,
sizeof
(
struct
index_state
));
istate
.
map
=
NULL
;
/* Open mailbox for reading */
r
=
mailbox_open_irl
(
txn
->
req_tgt
.
mboxname
,
&
mailbox
);
if
(
r
)
{
syslog
(
LOG_ERR
,
"http_mailbox_open(%s) failed: %s"
,
txn
->
req_tgt
.
mboxname
,
error_message
(
r
));
txn
->
error
.
desc
=
error_message
(
r
);
ret
=
HTTP_SERVER_ERROR
;
goto
done
;
}
fctx
->
mailbox
=
mailbox
;
highestmodseq
=
mailbox
->
i
.
highestmodseq
;
mailbox_user_flag
(
mailbox
,
DFLAG_UNBIND
,
&
unbind_flag
,
1
);
mailbox_user_flag
(
mailbox
,
DFLAG_UNCHANGED
,
&
unchanged_flag
,
1
);
/* Parse children element of report */
for
(
node
=
inroot
->
children
;
node
;
node
=
node
->
next
)
{
xmlNodePtr
node2
;
xmlChar
*
str
=
NULL
;
if
(
node
->
type
==
XML_ELEMENT_NODE
)
{
if
(
!
xmlStrcmp
(
node
->
name
,
BAD_CAST
"sync-token"
)
&&
(
str
=
xmlNodeListGetString
(
inroot
->
doc
,
node
->
children
,
1
)))
{
/* Parse sync-token */
r
=
sscanf
((
char
*
)
str
,
SYNC_TOKEN_URL_SCHEME
"%u-"
MODSEQ_FMT
"-"
MODSEQ_FMT
"%1s"
,
&
uidvalidity
,
&
syncmodseq
,
&
basemodseq
,
tokenuri
/* test for trailing junk */
);
syslog
(
LOG_ERR
,
"scanned token %s to %d %u %llu %llu"
,
str
,
r
,
uidvalidity
,
syncmodseq
,
basemodseq
);
/* Sanity check the token components */
if
(
r
<
2
||
r
>
3
||
(
uidvalidity
!=
mailbox
->
i
.
uidvalidity
)
||
(
syncmodseq
>
highestmodseq
))
{
fctx
->
err
->
desc
=
"Invalid sync-token"
;
}
else
if
(
r
==
3
)
{
/* Previous partial read token */
if
(
basemodseq
>
highestmodseq
)
{
fctx
->
err
->
desc
=
"Invalid sync-token"
;
}
else
if
(
basemodseq
<
mailbox
->
i
.
deletedmodseq
)
{
fctx
->
err
->
desc
=
"Stale sync-token"
;
}
}
else
{
/* Regular token */
if
(
syncmodseq
<
mailbox
->
i
.
deletedmodseq
)
{
fctx
->
err
->
desc
=
"Stale sync-token"
;
}
}
if
(
fctx
->
err
->
desc
)
{
/* DAV:valid-sync-token */
txn
->
error
.
precond
=
DAV_SYNC_TOKEN
;
ret
=
HTTP_FORBIDDEN
;
}
}
else
if
(
!
xmlStrcmp
(
node
->
name
,
BAD_CAST
"sync-level"
)
&&
(
str
=
xmlNodeListGetString
(
inroot
->
doc
,
node
->
children
,
1
)))
{
if
(
!
strcmp
((
char
*
)
str
,
"infinity"
))
{
fctx
->
err
->
desc
=
"This server DOES NOT support infinite depth requests"
;
ret
=
HTTP_SERVER_ERROR
;
}
else
if
((
sscanf
((
char
*
)
str
,
"%u"
,
&
fctx
->
depth
)
!=
1
)
||
(
fctx
->
depth
!=
1
))
{
fctx
->
err
->
desc
=
"Illegal sync-level"
;
ret
=
HTTP_BAD_REQUEST
;
}
}
else
if
(
!
xmlStrcmp
(
node
->
name
,
BAD_CAST
"limit"
))
{
for
(
node2
=
node
->
children
;
node2
;
node2
=
node2
->
next
)
{
if
((
node2
->
type
==
XML_ELEMENT_NODE
)
&&
!
xmlStrcmp
(
node2
->
name
,
BAD_CAST
"nresults"
)
&&
(
!
(
str
=
xmlNodeListGetString
(
inroot
->
doc
,
node2
->
children
,
1
))
||
(
sscanf
((
char
*
)
str
,
"%u"
,
&
limit
)
!=
1
)))
{
txn
->
error
.
precond
=
DAV_OVER_LIMIT
;
ret
=
HTTP_FORBIDDEN
;
}
}
}
if
(
str
)
xmlFree
(
str
);
if
(
ret
)
goto
done
;
}
}
/* Check Depth */
if
(
!
fctx
->
depth
)
{
fctx
->
err
->
desc
=
"Illegal sync-level"
;
ret
=
HTTP_BAD_REQUEST
;
goto
done
;
}
if
(
!
syncmodseq
)
{
/* Initial sync - set basemodseq in case client limits results */
basemodseq
=
highestmodseq
;
}
/* Construct array of records for sorting and/or fetching cached header */
istate
.
mailbox
=
mailbox
;
istate
.
map
=
xzmalloc
(
mailbox
->
i
.
num_records
*
sizeof
(
struct
index_map
));
/* Find which resources we need to report */
for
(
recno
=
1
;
recno
<=
mailbox
->
i
.
num_records
;
recno
++
)
{
/* XXX Corrupted record? Should we bail? */
if
(
mailbox_read_index_record
(
mailbox
,
recno
,
&
record
))
continue
;
/* Resource not added/removed since last sync */
if
(
record
.
modseq
<=
syncmodseq
)
continue
;
if
((
unbind_flag
>=
0
)
&&
record
.
user_flags
[
unbind_flag
/
32
]
&
(
1
<<
(
unbind_flag
&
31
)))
{
/* Resource replaced by a PUT, COPY, or MOVE - ignore it */
continue
;
}
if
((
record
.
modseq
-
syncmodseq
==
1
)
&&
(
unchanged_flag
>=
0
)
&&
(
record
.
user_flags
[
unchanged_flag
/
32
]
&
(
1
<<
(
unchanged_flag
&
31
))))
{
/* Resource has just had VTIMEZONEs stripped - ignore it */
continue
;
}
if
((
record
.
modseq
<=
basemodseq
)
&&
(
record
.
system_flags
&
FLAG_EXPUNGED
))
{
/* Initial sync - ignore unmapped resources */
continue
;
}
/* copy data into map (just like index.c - XXX helper fn? */
istate
.
map
[
nresp
].
recno
=
recno
;
istate
.
map
[
nresp
].
uid
=
record
.
uid
;
istate
.
map
[
nresp
].
modseq
=
record
.
modseq
;
istate
.
map
[
nresp
].
system_flags
=
record
.
system_flags
;
for
(
i
=
0
;
i
<
MAX_USER_FLAGS
/
32
;
i
++
)
istate
.
map
[
nresp
].
user_flags
[
i
]
=
record
.
user_flags
[
i
];
istate
.
map
[
nresp
].
cache_offset
=
record
.
cache_offset
;
nresp
++
;
}
if
(
limit
<
nresp
)
{
/* Need to truncate the responses */
struct
index_map
*
map
=
istate
.
map
;
/* Sort the response records by modseq */
qsort
(
map
,
nresp
,
sizeof
(
struct
index_map
),
(
int
(
*
)(
const
void
*
,
const
void
*
))
&
map_modseq_cmp
);
/* Our last response MUST be the last record with its modseq */
for
(
nresp
=
limit
;
nresp
&&
map
[
nresp
-1
].
modseq
==
map
[
nresp
].
modseq
;
nresp
--
);
if
(
!
nresp
)
{
/* DAV:number-of-matches-within-limits */
fctx
->
err
->
desc
=
"Unable to truncate results"
;
txn
->
error
.
precond
=
DAV_OVER_LIMIT
;
ret
=
HTTP_NO_STORAGE
;
goto
done
;
}
/* respmodseq will be modseq of last record we return */
respmodseq
=
map
[
nresp
-1
].
modseq
;
/* Tell client we truncated the responses */
xml_add_response
(
fctx
,
HTTP_NO_STORAGE
,
DAV_OVER_LIMIT
);
}
else
{
/* Full response - respmodseq will be highestmodseq of mailbox */
respmodseq
=
highestmodseq
;
}
/* Report the resources within the client requested limit (if any) */
for
(
msgno
=
1
;
msgno
<=
nresp
;
msgno
++
)
{
char
*
p
,
*
resource
=
NULL
;
struct
dav_data
ddata
;
if
(
mailbox_read_index_record
(
mailbox
,
istate
.
map
[
msgno
-1
].
recno
,
&
record
))
continue
;
/* Get resource filename from Content-Disposition header */
if
((
p
=
index_getheader
(
&
istate
,
msgno
,
"Content-Disposition"
)))
{
resource
=
strstr
(
p
,
"filename="
)
+
9
;
}
if
(
!
resource
)
continue
;
/* No filename */
if
(
*
resource
==
'\"'
)
{
resource
++
;
if
((
p
=
strchr
(
resource
,
'\"'
)))
*
p
=
'\0'
;
}
else
if
((
p
=
strchr
(
resource
,
';'
)))
*
p
=
'\0'
;
memset
(
&
ddata
,
0
,
sizeof
(
struct
dav_data
));
ddata
.
resource
=
resource
;
if
(
record
.
system_flags
&
FLAG_EXPUNGED
)
{
/* report as NOT FOUND
IMAP UID of 0 will cause index record to be ignored
propfind_by_resource() will append our resource name */
fctx
->
proc_by_resource
(
fctx
,
&
ddata
);
}
else
{
fctx
->
record
=
&
record
;
ddata
.
imap_uid
=
record
.
uid
;
fctx
->
proc_by_resource
(
fctx
,
&
ddata
);
}
}
/* Add sync-token element */
if
(
respmodseq
<
basemodseq
)
{
/* Client limited results of initial sync - include basemodseq */
snprintf
(
tokenuri
,
MAX_MAILBOX_PATH
,
SYNC_TOKEN_URL_SCHEME
"%u-"
MODSEQ_FMT
"-"
MODSEQ_FMT
,
mailbox
->
i
.
uidvalidity
,
respmodseq
,
basemodseq
);
}
else
{
snprintf
(
tokenuri
,
MAX_MAILBOX_PATH
,
SYNC_TOKEN_URL_SCHEME
"%u-"
MODSEQ_FMT
,
mailbox
->
i
.
uidvalidity
,
respmodseq
);
}
xmlNewChild
(
fctx
->
root
,
NULL
,
BAD_CAST
"sync-token"
,
BAD_CAST
tokenuri
);
done
:
if
(
istate
.
map
)
free
(
istate
.
map
);
mailbox_close
(
&
mailbox
);
return
(
ret
?
ret
:
HTTP_MULTI_STATUS
);
}
int
expand_property
(
xmlNodePtr
inroot
,
struct
propfind_ctx
*
fctx
,
const
char
*
href
,
parse_path_t
parse_path
,
const
struct
prop_entry
*
lprops
,
xmlNodePtr
root
,
int
depth
)
{
int
ret
=
0
,
r
;
struct
propfind_ctx
prev_ctx
;
struct
request_target_t
req_tgt
;
memcpy
(
&
prev_ctx
,
fctx
,
sizeof
(
struct
propfind_ctx
));
fctx
->
mode
=
PROPFIND_EXPAND
;
if
(
href
)
{
/* Parse the URL */
memset
(
&
req_tgt
,
0
,
sizeof
(
struct
request_target_t
));
parse_path
(
href
,
&
req_tgt
,
&
fctx
->
err
->
desc
);
fctx
->
req_tgt
=
&
req_tgt
;
}
fctx
->
lprops
=
lprops
;
fctx
->
elist
=
NULL
;
fctx
->
root
=
root
;
fctx
->
depth
=
depth
;
fctx
->
mailbox
=
NULL
;
ret
=
preload_proplist
(
inroot
->
children
,
fctx
);
if
(
ret
)
goto
done
;
if
(
!
fctx
->
req_tgt
->
collection
&&
!
fctx
->
depth
)
{
/* Add response for principal or home-set collection */
struct
mailbox
*
mailbox
=
NULL
;
if
(
*
fctx
->
req_tgt
->
mboxname
)
{
/* Open mailbox for reading */
if
((
r
=
mailbox_open_irl
(
fctx
->
req_tgt
->
mboxname
,
&
mailbox
)))
{
syslog
(
LOG_INFO
,
"mailbox_open_irl(%s) failed: %s"
,
fctx
->
req_tgt
->
mboxname
,
error_message
(
r
));
fctx
->
err
->
desc
=
error_message
(
r
);
ret
=
HTTP_SERVER_ERROR
;
goto
done
;
}
fctx
->
mailbox
=
mailbox
;
}
xml_add_response
(
fctx
,
0
,
0
);
mailbox_close
(
&
mailbox
);
}
if
(
fctx
->
depth
>
0
)
{
/* Calendar collection(s) */
if
(
fctx
->
req_tgt
->
collection
)
{
/* Add response for target calendar collection */
propfind_by_collection
(
fctx
->
req_tgt
->
mboxname
,
0
,
0
,
fctx
);
}
else
{
/* Add responses for all contained calendar collections */
strlcat
(
fctx
->
req_tgt
->
mboxname
,
".%"
,
sizeof
(
fctx
->
req_tgt
->
mboxname
));
r
=
mboxlist_findall
(
NULL
,
/* internal namespace */
fctx
->
req_tgt
->
mboxname
,
1
,
httpd_userid
,
httpd_authstate
,
propfind_by_collection
,
fctx
);
}
if
(
fctx
->
davdb
)
fctx
->
close_db
(
fctx
->
davdb
);
ret
=
*
fctx
->
ret
;
}
done
:
/* Free the entry list */
while
(
fctx
->
elist
)
{
struct
propfind_entry_list
*
freeme
=
fctx
->
elist
;
fctx
->
elist
=
freeme
->
next
;
xmlFree
(
freeme
->
name
);
free
(
freeme
);
}
fctx
->
mailbox
=
prev_ctx
.
mailbox
;
fctx
->
depth
=
prev_ctx
.
depth
;
fctx
->
root
=
prev_ctx
.
root
;
fctx
->
elist
=
prev_ctx
.
elist
;
fctx
->
lprops
=
prev_ctx
.
lprops
;
fctx
->
req_tgt
=
prev_ctx
.
req_tgt
;
if
(
root
!=
fctx
->
root
)
{
/* Move any defined namespaces up to the previous parent */
xmlNsPtr
nsDef
;
if
(
fctx
->
root
->
nsDef
)
{
/* Find last nsDef in list */
for
(
nsDef
=
fctx
->
root
->
nsDef
;
nsDef
->
next
;
nsDef
=
nsDef
->
next
);
nsDef
->
next
=
root
->
nsDef
;
}
else
fctx
->
root
->
nsDef
=
root
->
nsDef
;
root
->
nsDef
=
NULL
;
}
return
ret
;
}
/* DAV:expand-property REPORT */
int
report_expand_prop
(
struct
transaction_t
*
txn
__attribute__
((
unused
)),
struct
meth_params
*
rparams
__attribute__
((
unused
)),
xmlNodePtr
inroot
,
struct
propfind_ctx
*
fctx
)
{
int
ret
=
expand_property
(
inroot
,
fctx
,
NULL
,
NULL
,
fctx
->
lprops
,
fctx
->
root
,
fctx
->
depth
);
return
(
ret
?
ret
:
HTTP_MULTI_STATUS
);
}
/* DAV:acl-principal-prop-set REPORT */
int
report_acl_prin_prop
(
struct
transaction_t
*
txn
,
struct
meth_params
*
rparams
__attribute__
((
unused
)),
xmlNodePtr
inroot
,
struct
propfind_ctx
*
fctx
)
{
int
ret
=
0
,
r
;
struct
request_target_t
req_tgt
;
mbentry_t
*
mbentry
=
NULL
;
char
*
userid
,
*
nextid
;
xmlNodePtr
cur
;
/* Get ACL of resource */
r
=
http_mlookup
(
txn
->
req_tgt
.
mboxname
,
&
mbentry
,
NULL
);
if
(
r
)
{
syslog
(
LOG_ERR
,
"mlookup(%s) failed: %s"
,
txn
->
req_tgt
.
mboxname
,
error_message
(
r
));
txn
->
error
.
desc
=
error_message
(
r
);
switch
(
r
)
{
case
IMAP_PERMISSION_DENIED
:
ret
=
HTTP_FORBIDDEN
;
break
;
case
IMAP_MAILBOX_NONEXISTENT
:
ret
=
HTTP_NOT_FOUND
;
break
;
default
:
ret
=
HTTP_SERVER_ERROR
;
break
;
}
goto
done
;
}
/* Generate URL for user principal collection */
buf_reset
(
&
fctx
->
buf
);
buf_printf
(
&
fctx
->
buf
,
"%s/user/"
,
namespace_principal
.
prefix
);
/* Allowed properties are for principals, NOT the request URL */
memset
(
&
req_tgt
,
0
,
sizeof
(
struct
request_target_t
));
principal_parse_path
(
buf_cstring
(
&
fctx
->
buf
),
&
req_tgt
,
&
fctx
->
err
->
desc
);
fctx
->
req_tgt
=
&
req_tgt
;
fctx
->
lprops
=
principal_props
;
fctx
->
proc_by_resource
=
&
propfind_by_resource
;
/* Parse children element of report */
for
(
cur
=
inroot
->
children
;
cur
;
cur
=
cur
->
next
)
{
if
(
cur
->
type
==
XML_ELEMENT_NODE
&&
!
xmlStrcmp
(
cur
->
name
,
BAD_CAST
"prop"
))
{
if
((
ret
=
preload_proplist
(
cur
->
children
,
fctx
)))
goto
done
;
break
;
}
}
/* Parse the ACL string (userid/rights pairs) */
for
(
userid
=
mbentry
->
acl
;
userid
;
userid
=
nextid
)
{
char
*
rightstr
;
rightstr
=
strchr
(
userid
,
'\t'
);
if
(
!
rightstr
)
break
;
*
rightstr
++
=
'\0'
;
nextid
=
strchr
(
rightstr
,
'\t'
);
if
(
!
nextid
)
break
;
*
nextid
++
=
'\0'
;
if
(
strcmp
(
userid
,
"anyone"
)
&&
strcmp
(
userid
,
"anonymous"
))
{
/* Add userid to principal URL */
strcpy
(
req_tgt
.
tail
,
userid
);
req_tgt
.
user
=
userid
;
req_tgt
.
userlen
=
strlen
(
userid
);
/* Add response for URL */
xml_add_response
(
fctx
,
0
,
0
);
}
}
done
:
mboxlist_entry_free
(
&
mbentry
);
return
(
ret
?
ret
:
HTTP_MULTI_STATUS
);
}
struct
search_crit
{
struct
strlist
*
props
;
xmlChar
*
match
;
struct
search_crit
*
next
;
};
/* mboxlist_findall() callback to find user principals (has Inbox) */
static
int
principal_search
(
char
*
mboxname
,
int
matchlen
__attribute__
((
unused
)),
int
maycreate
__attribute__
((
unused
)),
void
*
rock
)
{
struct
propfind_ctx
*
fctx
=
(
struct
propfind_ctx
*
)
rock
;
const
char
*
userid
=
mboxname_to_userid
(
mboxname
);
struct
search_crit
*
search_crit
;
size_t
len
;
char
*
p
;
for
(
search_crit
=
(
struct
search_crit
*
)
fctx
->
filter_crit
;
search_crit
;
search_crit
=
search_crit
->
next
)
{
struct
strlist
*
prop
;
for
(
prop
=
search_crit
->
props
;
prop
;
prop
=
prop
->
next
)
{
if
(
!
strcmp
(
prop
->
s
,
"displayname"
))
{
if
(
!
xmlStrcasestr
(
BAD_CAST
userid
,
search_crit
->
match
))
return
0
;
}
else
if
(
!
strcmp
(
prop
->
s
,
"calendar-user-address-set"
))
{
char
email
[
MAX_MAILBOX_NAME
+
1
];
snprintf
(
email
,
MAX_MAILBOX_NAME
,
"%s@%s"
,
userid
,
config_servername
);
if
(
!
xmlStrcasestr
(
BAD_CAST
email
,
search_crit
->
match
))
return
0
;
}
else
if
(
!
strcmp
(
prop
->
s
,
"calendar-user-type"
))
{
if
(
!
xmlStrcasestr
(
BAD_CAST
"INDIVIDUAL"
,
search_crit
->
match
))
return
0
;
}
}
}
/* Append principal name to URL path */
len
=
strlen
(
namespace_principal
.
prefix
);
p
=
fctx
->
req_tgt
->
path
+
len
;
len
+=
strlcpy
(
p
,
"/user/"
,
MAX_MAILBOX_PATH
-
len
);
p
=
fctx
->
req_tgt
->
path
+
len
;
strlcpy
(
p
,
userid
,
MAX_MAILBOX_PATH
-
len
);
strlcpy
(
p
,
"/"
,
MAX_MAILBOX_PATH
-
len
);
free
(
fctx
->
req_tgt
->
userid
);
fctx
->
req_tgt
->
userid
=
xstrdup
(
userid
);
return
xml_add_response
(
fctx
,
0
,
0
);
}
static
const
struct
prop_entry
prin_search_props
[]
=
{
/* WebDAV (RFC 4918) properties */
{
"displayname"
,
NS_DAV
,
0
,
NULL
,
NULL
,
NULL
},
/* CalDAV Scheduling (RFC 6638) properties */
{
"calendar-user-address-set"
,
NS_CALDAV
,
0
,
NULL
,
NULL
,
NULL
},
{
"calendar-user-type"
,
NS_CALDAV
,
0
,
NULL
,
NULL
,
NULL
},
{
NULL
,
0
,
0
,
NULL
,
NULL
,
NULL
}
};
/* DAV:principal-property-search REPORT */
static
int
report_prin_prop_search
(
struct
transaction_t
*
txn
,
struct
meth_params
*
rparams
__attribute__
((
unused
)),
xmlNodePtr
inroot
,
struct
propfind_ctx
*
fctx
)
{
int
ret
=
0
;
xmlNodePtr
node
;
struct
search_crit
*
search_crit
,
*
next
;
unsigned
apply_prin_set
=
0
;
/* Parse children element of report */
fctx
->
filter_crit
=
NULL
;
for
(
node
=
inroot
->
children
;
node
;
node
=
node
->
next
)
{
if
(
node
->
type
==
XML_ELEMENT_NODE
)
{
if
(
!
xmlStrcmp
(
node
->
name
,
BAD_CAST
"property-search"
))
{
xmlNodePtr
search
;
search_crit
=
xzmalloc
(
sizeof
(
struct
search_crit
));
search_crit
->
next
=
fctx
->
filter_crit
;
fctx
->
filter_crit
=
search_crit
;
for
(
search
=
node
->
children
;
search
;
search
=
search
->
next
)
{
if
(
search
->
type
==
XML_ELEMENT_NODE
)
{
if
(
!
xmlStrcmp
(
search
->
name
,
BAD_CAST
"prop"
))
{
xmlNodePtr
prop
;
for
(
prop
=
search
->
children
;
prop
;
prop
=
prop
->
next
)
{
if
(
prop
->
type
==
XML_ELEMENT_NODE
)
{
const
struct
prop_entry
*
entry
;
for
(
entry
=
prin_search_props
;
entry
->
name
&&
xmlStrcmp
(
prop
->
name
,
BAD_CAST
entry
->
name
);
entry
++
);
if
(
!
entry
->
name
)
{
txn
->
error
.
desc
=
"Unsupported XML search prop"
;
ret
=
HTTP_BAD_REQUEST
;
goto
done
;
}
else
{
appendstrlist
(
&
search_crit
->
props
,
(
char
*
)
entry
->
name
);
}
}
}
}
else
if
(
!
xmlStrcmp
(
search
->
name
,
BAD_CAST
"match"
))
{
if
(
search_crit
->
match
)
{
txn
->
error
.
desc
=
"Too many DAV:match XML elements"
;
ret
=
HTTP_BAD_REQUEST
;
goto
done
;
}
search_crit
->
match
=
xmlNodeListGetString
(
inroot
->
doc
,
search
->
children
,
1
);
}
else
{
txn
->
error
.
desc
=
"Unknown XML element"
;
ret
=
HTTP_BAD_REQUEST
;
goto
done
;
}
}
}
if
(
!
search_crit
->
props
)
{
txn
->
error
.
desc
=
"Missing DAV:prop XML element"
;
ret
=
HTTP_BAD_REQUEST
;
goto
done
;
}
if
(
!
search_crit
->
match
)
{
txn
->
error
.
desc
=
"Missing DAV:match XML element"
;
ret
=
HTTP_BAD_REQUEST
;
goto
done
;
}
}
else
if
(
!
xmlStrcmp
(
node
->
name
,
BAD_CAST
"prop"
))
{
/* Already parsed in meth_report() */
}
else
if
(
!
xmlStrcmp
(
node
->
name
,
BAD_CAST
"apply-to-principal-collection-set"
))
{
apply_prin_set
=
1
;
}
else
{
txn
->
error
.
desc
=
"Unknown XML element"
;
ret
=
HTTP_BAD_REQUEST
;
goto
done
;
}
}
}
if
(
!
fctx
->
filter_crit
)
{
txn
->
error
.
desc
=
"Missing DAV:property-search XML element"
;
ret
=
HTTP_BAD_REQUEST
;
goto
done
;
}
/* Only search DAV:principal-collection-set */
if
(
apply_prin_set
||
!
fctx
->
req_tgt
->
userid
)
{
/* XXX Do LDAP/SQL lookup of CN/email-address(es) here */
ret
=
mboxlist_findall
(
NULL
,
/* internal namespace */
"user.%"
,
1
,
httpd_userid
,
httpd_authstate
,
principal_search
,
fctx
);
}
done
:
for
(
search_crit
=
fctx
->
filter_crit
;
search_crit
;
search_crit
=
next
)
{
next
=
search_crit
->
next
;
if
(
search_crit
->
match
)
xmlFree
(
search_crit
->
match
);
freestrlist
(
search_crit
->
props
);
free
(
search_crit
);
}
return
(
ret
?
ret
:
HTTP_MULTI_STATUS
);
}
/* DAV:principal-search-property-set REPORT */
static
int
report_prin_search_prop_set
(
struct
transaction_t
*
txn
,
struct
meth_params
*
rparams
__attribute__
((
unused
)),
xmlNodePtr
inroot
,
struct
propfind_ctx
*
fctx
)
{
xmlNodePtr
node
;
const
struct
prop_entry
*
entry
;
/* Look for child elements in request */
for
(
node
=
inroot
->
children
;
node
;
node
=
node
->
next
)
{
if
(
node
->
type
==
XML_ELEMENT_NODE
)
{
txn
->
error
.
desc
=
"DAV:principal-search-property-set XML element MUST be empty"
;
return
HTTP_BAD_REQUEST
;
}
}
for
(
entry
=
prin_search_props
;
entry
->
name
;
entry
++
)
{
node
=
xmlNewChild
(
fctx
->
root
,
NULL
,
BAD_CAST
"principal-search-property"
,
NULL
);
node
=
xmlNewChild
(
node
,
NULL
,
BAD_CAST
"prop"
,
NULL
);
ensure_ns
(
fctx
->
ns
,
entry
->
ns
,
fctx
->
root
,
known_namespaces
[
entry
->
ns
].
href
,
known_namespaces
[
entry
->
ns
].
prefix
);
xmlNewChild
(
node
,
fctx
->
ns
[
entry
->
ns
],
BAD_CAST
entry
->
name
,
NULL
);
}
return
HTTP_OK
;
}
/* Perform a REPORT request */
int
meth_report
(
struct
transaction_t
*
txn
,
void
*
params
)
{
struct
meth_params
*
rparams
=
(
struct
meth_params
*
)
params
;
int
ret
=
0
,
r
;
const
char
**
hdr
;
unsigned
depth
=
0
;
xmlNodePtr
inroot
=
NULL
,
outroot
=
NULL
,
cur
,
prop
=
NULL
,
props
=
NULL
;
const
struct
report_type_t
*
report
=
NULL
;
xmlNsPtr
ns
[
NUM_NAMESPACE
];
struct
hash_table
ns_table
=
{
0
,
NULL
,
NULL
};
struct
propfind_ctx
fctx
;
struct
propfind_entry_list
*
elist
=
NULL
;
memset
(
&
fctx
,
0
,
sizeof
(
struct
propfind_ctx
));
/* Parse the path */
if
((
r
=
rparams
->
parse_path
(
txn
->
req_uri
->
path
,
&
txn
->
req_tgt
,
&
txn
->
error
.
desc
)))
return
r
;
/* Make sure method is allowed */
if
(
!
(
txn
->
req_tgt
.
allow
&
ALLOW_DAV
))
return
HTTP_NOT_ALLOWED
;
/* Check Depth */
if
((
hdr
=
spool_getheader
(
txn
->
req_hdrs
,
"Depth"
)))
{
if
(
!
strcmp
(
hdr
[
0
],
"infinity"
))
{
depth
=
2
;
}
else
if
((
sscanf
(
hdr
[
0
],
"%u"
,
&
depth
)
!=
1
)
||
(
depth
>
1
))
{
txn
->
error
.
desc
=
"Illegal Depth value
\r\n
"
;
return
HTTP_BAD_REQUEST
;
}
}
/* Parse the REPORT body */
ret
=
parse_xml_body
(
txn
,
&
inroot
);
if
(
!
ret
&&
!
inroot
)
{
txn
->
error
.
desc
=
"Missing request body
\r\n
"
;
return
HTTP_BAD_REQUEST
;
}
if
(
ret
)
goto
done
;
/* Add report type to our header cache */
spool_cache_header
(
xstrdup
(
":type"
),
xstrdup
((
const
char
*
)
inroot
->
name
),
txn
->
req_hdrs
);
/* Check the report type against our supported list */
for
(
report
=
rparams
->
reports
;
report
&&
report
->
name
;
report
++
)
{
if
(
!
xmlStrcmp
(
inroot
->
name
,
BAD_CAST
report
->
name
))
break
;
}
if
(
!
report
||
!
report
->
name
)
{
syslog
(
LOG_WARNING
,
"REPORT %s"
,
inroot
->
name
);
/* DAV:supported-report */
txn
->
error
.
precond
=
DAV_SUPP_REPORT
;
ret
=
HTTP_FORBIDDEN
;
goto
done
;
}
/* Check any depth limit */
if
(
depth
&&
(
report
->
flags
&
REPORT_DEPTH_ZERO
))
{
txn
->
error
.
desc
=
"Depth header field MUST have value zero (0)"
;
ret
=
HTTP_BAD_REQUEST
;
goto
done
;
}
/* Normalize depth so that:
* 0 = home-set collection, 1+ = calendar collection, 2+ = calendar resource
*/
if
(
txn
->
req_tgt
.
collection
)
depth
++
;
if
(
txn
->
req_tgt
.
resource
)
depth
++
;
/* Check ACL and location of mailbox */
if
(
report
->
flags
&
REPORT_NEED_MBOX
)
{
mbentry_t
*
mbentry
=
NULL
;
int
rights
;
/* Locate the mailbox */
if
((
r
=
http_mlookup
(
txn
->
req_tgt
.
mboxname
,
&
mbentry
,
NULL
)))
{
syslog
(
LOG_ERR
,
"mlookup(%s) failed: %s"
,
txn
->
req_tgt
.
mboxname
,
error_message
(
r
));
txn
->
error
.
desc
=
error_message
(
r
);
switch
(
r
)
{
case
IMAP_PERMISSION_DENIED
:
ret
=
HTTP_FORBIDDEN
;
case
IMAP_MAILBOX_NONEXISTENT
:
ret
=
HTTP_NOT_FOUND
;
default
:
ret
=
HTTP_SERVER_ERROR
;
}
goto
done
;
}
/* Check ACL for current user */
rights
=
httpd_myrights
(
httpd_authstate
,
mbentry
->
acl
);
if
((
rights
&
report
->
reqd_privs
)
!=
report
->
reqd_privs
)
{
if
(
report
->
reqd_privs
==
DACL_READFB
)
ret
=
HTTP_NOT_FOUND
;
else
{
/* DAV:need-privileges */
txn
->
error
.
precond
=
DAV_NEED_PRIVS
;
txn
->
error
.
resource
=
txn
->
req_tgt
.
path
;
txn
->
error
.
rights
=
report
->
reqd_privs
;
ret
=
HTTP_NO_PRIVS
;
}
mboxlist_entry_free
(
&
mbentry
);
goto
done
;
}
if
(
mbentry
->
server
)
{
/* Remote mailbox */
struct
backend
*
be
;
be
=
proxy_findserver
(
mbentry
->
server
,
&
http_protocol
,
proxy_userid
,
&
backend_cached
,
NULL
,
NULL
,
httpd_in
);
mboxlist_entry_free
(
&
mbentry
);
if
(
!
be
)
ret
=
HTTP_UNAVAILABLE
;
else
ret
=
http_pipe_req_resp
(
be
,
txn
);
goto
done
;
}
mboxlist_entry_free
(
&
mbentry
);
/* Local Mailbox */
}
/* Principal or Local Mailbox */
if
(
report
->
flags
&
(
REPORT_NEED_PROPS
|
REPORT_ALLOW_PROPS
))
{
/* Parse children element of report */
for
(
cur
=
inroot
->
children
;
cur
;
cur
=
cur
->
next
)
{
if
(
cur
->
type
==
XML_ELEMENT_NODE
)
{
if
(
!
xmlStrcmp
(
cur
->
name
,
BAD_CAST
"allprop"
))
{
fctx
.
mode
=
PROPFIND_ALL
;
prop
=
cur
;
break
;
}
else
if
(
!
xmlStrcmp
(
cur
->
name
,
BAD_CAST
"propname"
))
{
fctx
.
mode
=
PROPFIND_NAME
;
fctx
.
prefer
=
PREFER_MIN
;
/* Don't want 404 (Not Found) */
prop
=
cur
;
break
;
}
else
if
(
!
xmlStrcmp
(
cur
->
name
,
BAD_CAST
"prop"
))
{
fctx
.
mode
=
PROPFIND_PROP
;
prop
=
cur
;
props
=
cur
->
children
;
break
;
}
}
}
if
(
!
prop
&&
(
report
->
flags
&
REPORT_NEED_PROPS
))
{
txn
->
error
.
desc
=
"Missing <prop> element in REPORT
\r\n
"
;
ret
=
HTTP_BAD_REQUEST
;
goto
done
;
}
}
/* Start construction of our multistatus response */
if
(
report
->
resp_root
&&
!
(
outroot
=
init_xml_response
(
report
->
resp_root
,
NS_DAV
,
inroot
,
ns
)))
{
txn
->
error
.
desc
=
"Unable to create XML response
\r\n
"
;
ret
=
HTTP_SERVER_ERROR
;
goto
done
;
}
/* Populate our propfind context */
fctx
.
req_tgt
=
&
txn
->
req_tgt
;
fctx
.
depth
=
depth
;
fctx
.
prefer
|=
get_preferences
(
txn
);
fctx
.
req_hdrs
=
txn
->
req_hdrs
;
fctx
.
userid
=
proxy_userid
;
fctx
.
userisadmin
=
httpd_userisadmin
;
fctx
.
authstate
=
httpd_authstate
;
fctx
.
mailbox
=
NULL
;
fctx
.
record
=
NULL
;
fctx
.
reqd_privs
=
report
->
reqd_privs
;
fctx
.
elist
=
NULL
;
fctx
.
lprops
=
rparams
->
lprops
;
fctx
.
root
=
outroot
;
fctx
.
ns
=
ns
;
fctx
.
ns_table
=
&
ns_table
;
fctx
.
err
=
&
txn
->
error
;
fctx
.
ret
=
&
ret
;
fctx
.
fetcheddata
=
0
;
/* Parse the list of properties and build a list of callbacks */
if
(
fctx
.
mode
)
{
fctx
.
proc_by_resource
=
&
propfind_by_resource
;
ret
=
preload_proplist
(
props
,
&
fctx
);
}
/* Process the requested report */
if
(
!
ret
)
ret
=
(
*
report
->
proc
)(
txn
,
rparams
,
inroot
,
&
fctx
);
/* Output the XML response */
if
(
outroot
)
{
switch
(
ret
)
{
case
HTTP_OK
:
case
HTTP_MULTI_STATUS
:
/* iCalendar data in response should not be transformed */
if
(
fctx
.
fetcheddata
)
txn
->
flags
.
cc
|=
CC_NOTRANSFORM
;
xml_response
(
ret
,
txn
,
outroot
->
doc
);
ret
=
0
;
break
;
default
:
break
;
}
}
done
:
/* Free the entry list */
elist
=
fctx
.
elist
;
while
(
elist
)
{
struct
propfind_entry_list
*
freeme
=
elist
;
elist
=
elist
->
next
;
xmlFree
(
freeme
->
name
);
free
(
freeme
);
}
buf_free
(
&
fctx
.
buf
);
free_hash_table
(
&
ns_table
,
NULL
);
if
(
inroot
)
xmlFreeDoc
(
inroot
->
doc
);
if
(
outroot
)
xmlFreeDoc
(
outroot
->
doc
);
return
ret
;
}
/* Perform a UNLOCK request
*
* preconditions:
* DAV:need-privileges
* DAV:lock-token-matches-request-uri
*/
int
meth_unlock
(
struct
transaction_t
*
txn
,
void
*
params
)
{
struct
meth_params
*
lparams
=
(
struct
meth_params
*
)
params
;
int
ret
=
HTTP_NO_CONTENT
,
r
,
precond
,
rights
;
const
char
**
hdr
,
*
token
;
struct
mailbox
*
mailbox
=
NULL
;
mbentry_t
*
mbentry
=
NULL
;
struct
dav_data
*
ddata
;
struct
index_record
record
;
const
char
*
etag
;
time_t
lastmod
;
size_t
len
;
void
*
davdb
=
NULL
;
/* Response should not be cached */
txn
->
flags
.
cc
|=
CC_NOCACHE
;
/* Parse the path */
if
((
r
=
lparams
->
parse_path
(
txn
->
req_uri
->
path
,
&
txn
->
req_tgt
,
&
txn
->
error
.
desc
)))
return
r
;
/* Make sure method is allowed (only allowed on resources) */
if
(
!
(
txn
->
req_tgt
.
allow
&
ALLOW_WRITE
))
return
HTTP_NOT_ALLOWED
;
/* Check for mandatory Lock-Token header */
if
(
!
(
hdr
=
spool_getheader
(
txn
->
req_hdrs
,
"Lock-Token"
)))
{
txn
->
error
.
desc
=
"Missing Lock-Token header"
;
return
HTTP_BAD_REQUEST
;
}
token
=
hdr
[
0
];
/* Locate the mailbox */
r
=
http_mlookup
(
txn
->
req_tgt
.
mboxname
,
&
mbentry
,
NULL
);
if
(
r
)
{
syslog
(
LOG_ERR
,
"mlookup(%s) failed: %s"
,
txn
->
req_tgt
.
mboxname
,
error_message
(
r
));
txn
->
error
.
desc
=
error_message
(
r
);
switch
(
r
)
{
case
IMAP_PERMISSION_DENIED
:
return
HTTP_FORBIDDEN
;
case
IMAP_MAILBOX_NONEXISTENT
:
return
HTTP_NOT_FOUND
;
default
:
return
HTTP_SERVER_ERROR
;
}
}
rights
=
httpd_myrights
(
httpd_authstate
,
mbentry
->
acl
);
if
(
mbentry
->
server
)
{
/* Remote mailbox */
struct
backend
*
be
;
be
=
proxy_findserver
(
mbentry
->
server
,
&
http_protocol
,
proxy_userid
,
&
backend_cached
,
NULL
,
NULL
,
httpd_in
);
mboxlist_entry_free
(
&
mbentry
);
if
(
!
be
)
return
HTTP_UNAVAILABLE
;
return
http_pipe_req_resp
(
be
,
txn
);
}
mboxlist_entry_free
(
&
mbentry
);
/* Local Mailbox */
/* Open mailbox for reading */
r
=
mailbox_open_irl
(
txn
->
req_tgt
.
mboxname
,
&
mailbox
);
if
(
r
)
{
syslog
(
LOG_ERR
,
"http_mailbox_open(%s) failed: %s"
,
txn
->
req_tgt
.
mboxname
,
error_message
(
r
));
txn
->
error
.
desc
=
error_message
(
r
);
ret
=
HTTP_SERVER_ERROR
;
goto
done
;
}
/* Open the DAV DB corresponding to the mailbox */
davdb
=
lparams
->
davdb
.
open_db
(
mailbox
);
/* Find message UID for the resource, if exists */
lparams
->
davdb
.
lookup_resource
(
davdb
,
txn
->
req_tgt
.
mboxname
,
txn
->
req_tgt
.
resource
,
1
,
(
void
**
)
&
ddata
,
0
);
if
(
!
ddata
->
rowid
)
{
ret
=
HTTP_NOT_FOUND
;
goto
done
;
}
/* Check if resource is locked */
if
(
ddata
->
lock_expire
<=
time
(
NULL
))
{
/* DAV:lock-token-matches-request-uri */
txn
->
error
.
precond
=
DAV_BAD_LOCK_TOKEN
;
ret
=
HTTP_CONFLICT
;
goto
done
;
}
/* Check if current user owns the lock */
if
(
strcmp
(
ddata
->
lock_ownerid
,
httpd_userid
))
{
/* Check ACL for current user */
if
(
!
(
rights
&
DACL_ADMIN
))
{
/* DAV:need-privileges */
txn
->
error
.
precond
=
DAV_NEED_PRIVS
;
txn
->
error
.
resource
=
txn
->
req_tgt
.
path
;
txn
->
error
.
rights
=
DACL_ADMIN
;
ret
=
HTTP_NO_PRIVS
;
goto
done
;
}
}
/* Check if lock token matches */
len
=
strlen
(
ddata
->
lock_token
);
if
(
token
[
0
]
!=
'<'
||
strlen
(
token
)
!=
len
+
2
||
token
[
len
+
1
]
!=
'>'
||
strncmp
(
token
+
1
,
ddata
->
lock_token
,
len
))
{
/* DAV:lock-token-matches-request-uri */
txn
->
error
.
precond
=
DAV_BAD_LOCK_TOKEN
;
ret
=
HTTP_CONFLICT
;
goto
done
;
}
if
(
ddata
->
imap_uid
)
{
/* Mapped URL - Fetch index record for the resource */
r
=
mailbox_find_index_record
(
mailbox
,
ddata
->
imap_uid
,
&
record
,
NULL
);
if
(
r
)
{
txn
->
error
.
desc
=
error_message
(
r
);
ret
=
HTTP_SERVER_ERROR
;
goto
done
;
}
etag
=
message_guid_encode
(
&
record
.
guid
);
lastmod
=
record
.
internaldate
;
}
else
{
/* Unmapped URL (empty resource) */
etag
=
NULL_ETAG
;
lastmod
=
ddata
->
creationdate
;
}
/* Check any preconditions */
precond
=
lparams
->
check_precond
(
txn
,
ddata
,
etag
,
lastmod
);
if
(
precond
!=
HTTP_OK
)
{
/* We failed a precondition - don't perform the request */
ret
=
precond
;
goto
done
;
}
if
(
ddata
->
imap_uid
)
{
/* Mapped URL - Remove the lock */
ddata
->
lock_token
=
NULL
;
ddata
->
lock_owner
=
NULL
;
ddata
->
lock_ownerid
=
NULL
;
ddata
->
lock_expire
=
0
;
lparams
->
davdb
.
write_resourceLOCKONLY
(
davdb
,
ddata
,
1
);
}
else
{
/* Unmapped URL - Treat as lock-null and delete mapping entry */
lparams
->
davdb
.
delete_resourceLOCKONLY
(
davdb
,
ddata
->
rowid
,
1
);
}
done
:
if
(
davdb
)
lparams
->
davdb
.
close_db
(
davdb
);
mailbox_close
(
&
mailbox
);
return
ret
;
}
static
char
*
strnchr
(
const
char
*
s
,
int
c
,
size_t
n
)
{
if
(
!
s
)
return
NULL
;
for
(;
n
;
n
--
,
s
++
)
if
(
*
s
==
c
)
return
((
char
*
)
s
);
return
NULL
;
}
static
const
char
*
spool_getheader_last
(
hdrcache_t
cache
,
const
char
*
phead
)
{
const
char
**
hdr
=
spool_getheader
(
cache
,
phead
);
if
(
!
hdr
)
return
NULL
;
while
(
*
(
hdr
+
1
))
hdr
++
;
return
*
hdr
;
}
int
dav_store_resource
(
struct
transaction_t
*
txn
,
const
char
*
data
,
size_t
datalen
,
struct
mailbox
*
mailbox
,
struct
index_record
*
oldrecord
,
strarray_t
*
imapflags
)
{
int
ret
=
HTTP_CREATED
,
r
;
hdrcache_t
hdrcache
=
txn
->
req_hdrs
;
struct
stagemsg
*
stage
;
FILE
*
f
=
NULL
;
const
char
*
hdr
,
*
cte
;
quota_t
qdiffs
[
QUOTA_NUMRESOURCES
]
=
QUOTA_DIFFS_DONTCARE_INITIALIZER
;
time_t
now
=
time
(
NULL
);
struct
appendstate
as
;
/* Prepare to stage the message */
if
(
!
(
f
=
append_newstage
(
mailbox
->
name
,
now
,
0
,
&
stage
)))
{
syslog
(
LOG_ERR
,
"append_newstage(%s) failed"
,
mailbox
->
name
);
txn
->
error
.
desc
=
"append_newstage() failed
\r\n
"
;
return
HTTP_SERVER_ERROR
;
}
/* Create RFC 5322 header for resource */
if
((
hdr
=
spool_getheader_last
(
hdrcache
,
"User-Agent"
)))
{
fprintf
(
f
,
"User-Agent: %s
\r\n
"
,
hdr
);
}
if
((
hdr
=
spool_getheader_last
(
hdrcache
,
"From"
)))
{
fprintf
(
f
,
"From: %s
\r\n
"
,
hdr
);
}
else
{
char
*
mimehdr
;
assert
(
!
buf_len
(
&
txn
->
buf
));
if
(
strchr
(
proxy_userid
,
'@'
))
{
/* XXX This needs to be done via an LDAP/DB lookup */
buf_printf
(
&
txn
->
buf
,
"<%s>"
,
proxy_userid
);
}
else
{
buf_printf
(
&
txn
->
buf
,
"<%s@%s>"
,
proxy_userid
,
config_servername
);
}
mimehdr
=
charset_encode_mimeheader
(
buf_cstring
(
&
txn
->
buf
),
buf_len
(
&
txn
->
buf
));
fprintf
(
f
,
"From: %s
\r\n
"
,
mimehdr
);
free
(
mimehdr
);
buf_reset
(
&
txn
->
buf
);
}
if
((
hdr
=
spool_getheader_last
(
hdrcache
,
"Subject"
)))
{
fprintf
(
f
,
"Subject: %s
\r\n
"
,
hdr
);
}
if
((
hdr
=
spool_getheader_last
(
hdrcache
,
"Date"
)))
{
fprintf
(
f
,
"Date: %s
\r\n
"
,
hdr
);
}
else
{
char
datestr
[
80
];
time_to_rfc822
(
now
,
datestr
,
sizeof
(
datestr
));
fprintf
(
f
,
"Date: %s
\r\n
"
,
datestr
);
}
if
((
hdr
=
spool_getheader_last
(
hdrcache
,
"Message-ID"
)))
{
fprintf
(
f
,
"Message-ID: %s
\r\n
"
,
hdr
);
}
if
((
hdr
=
spool_getheader_last
(
hdrcache
,
"Content-Type"
)))
{
fprintf
(
f
,
"Content-Type: %s
\r\n
"
,
hdr
);
}
else
fputs
(
"Content-Type: application/octet-stream
\r\n
"
,
f
);
if
(
!
datalen
)
{
datalen
=
strlen
(
data
);
cte
=
"8bit"
;
}
else
{
cte
=
strnchr
(
data
,
'\0'
,
datalen
)
?
"binary"
:
"8bit"
;
}
fprintf
(
f
,
"Content-Transfer-Encoding: %s
\r\n
"
,
cte
);
if
((
hdr
=
spool_getheader_last
(
hdrcache
,
"Content-Disposition"
)))
{
fprintf
(
f
,
"Content-Disposition: %s
\r\n
"
,
hdr
);
}
if
((
hdr
=
spool_getheader_last
(
hdrcache
,
"Content-Description"
)))
{
fprintf
(
f
,
"Content-Description: %s
\r\n
"
,
hdr
);
}
fprintf
(
f
,
"Content-Length: %u
\r\n
"
,
(
unsigned
)
datalen
);
fputs
(
"MIME-Version: 1.0
\r\n\r\n
"
,
f
);
/* Write the data to the file */
fwrite
(
data
,
datalen
,
1
,
f
);
qdiffs
[
QUOTA_STORAGE
]
=
ftell
(
f
);
fclose
(
f
);
qdiffs
[
QUOTA_MESSAGE
]
=
1
;
/* Prepare to append the message to the mailbox */
if
((
r
=
append_setup_mbox
(
&
as
,
mailbox
,
httpd_userid
,
httpd_authstate
,
0
,
qdiffs
,
0
,
0
,
EVENT_MESSAGE_NEW
|
EVENT_CALENDAR
)))
{
syslog
(
LOG_ERR
,
"append_setup(%s) failed: %s"
,
mailbox
->
name
,
error_message
(
r
));
ret
=
HTTP_SERVER_ERROR
;
txn
->
error
.
desc
=
"append_setup() failed
\r\n
"
;
}
else
{
struct
body
*
body
=
NULL
;
/* Append the message to the mailbox */
if
((
r
=
append_fromstage
(
&
as
,
&
body
,
stage
,
now
,
imapflags
,
0
,
0
)))
{
syslog
(
LOG_ERR
,
"append_fromstage(%s) failed: %s"
,
mailbox
->
name
,
error_message
(
r
));
ret
=
HTTP_SERVER_ERROR
;
txn
->
error
.
desc
=
"append_fromstage() failed
\r\n
"
;
}
if
(
body
)
{
message_free_body
(
body
);
free
(
body
);
}
if
(
r
)
append_abort
(
&
as
);
else
{
/* Commit the append to the mailbox */
if
((
r
=
append_commit
(
&
as
)))
{
syslog
(
LOG_ERR
,
"append_commit(%s) failed: %s"
,
mailbox
->
name
,
error_message
(
r
));
ret
=
HTTP_SERVER_ERROR
;
txn
->
error
.
desc
=
"append_commit() failed
\r\n
"
;
}
else
{
struct
index_record
newrecord
;
/* Read index record for new message (always the last one) */
mailbox_read_index_record
(
mailbox
,
mailbox
->
i
.
num_records
,
&
newrecord
);
if
(
oldrecord
)
{
/* Now that we have the replacement message in place
expunge the old one. */
int
userflag
;
ret
=
HTTP_NO_CONTENT
;
/* Perform the actual expunge */
r
=
mailbox_user_flag
(
mailbox
,
DFLAG_UNBIND
,
&
userflag
,
1
);
if
(
!
r
)
{
oldrecord
->
user_flags
[
userflag
/
32
]
|=
1
<<
(
userflag
&
31
);
oldrecord
->
system_flags
|=
FLAG_EXPUNGED
|
FLAG_UNLINKED
;
r
=
mailbox_rewrite_index_record
(
mailbox
,
oldrecord
);
}
if
(
r
)
{
syslog
(
LOG_ERR
,
"expunging record (%s) failed: %s"
,
mailbox
->
name
,
error_message
(
r
));
txn
->
error
.
desc
=
error_message
(
r
);
ret
=
HTTP_SERVER_ERROR
;
}
}
if
(
!
r
)
{
struct
resp_body_t
*
resp_body
=
&
txn
->
resp_body
;
/* Tell client about the new resource */
resp_body
->
lastmod
=
newrecord
.
internaldate
;
resp_body
->
etag
=
message_guid_encode
(
&
newrecord
.
guid
);
}
}
}
}
append_removestage
(
stage
);
return
ret
;
}
File Metadata
Details
Attached
Mime Type
text/x-c
Expires
Mon, Apr 6, 2:18 AM (1 w, 3 d ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
18831959
Default Alt Text
http_dav.c (183 KB)
Attached To
Mode
R111 cyrus-imapd
Attached
Detach File
Event Timeline