#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <cx/list.h>
#include "../daemon/session.h"
#include "../util/pblock.h"
#include "operation.h"
#define WEBDAV_PATH_MAX 8192
size_t webdav_num_backends(WebdavBackend *dav) {
size_t count =
0;
while(dav) {
count++;
dav = dav->next;
}
return count;
}
WebdavOperation* webdav_create_propfind_operation(
Session *sn,
Request *rq,
WebdavBackend *dav,
WebdavPList *reqprops,
WebdavPropfindRequestList *requests,
WebdavResponse *response)
{
WebdavOperation *op = pool_malloc(sn->pool,
sizeof(WebdavOperation));
ZERO(op,
sizeof(WebdavOperation));
op->dav = dav;
op->sn = sn;
op->rq = rq;
op->reqprops = reqprops;
op->requests = requests;
op->response = response;
op->response_close = webdav_op_propfiond_close_resource;
response->op = op;
return op;
}
int webdav_op_propfind_begin(
WebdavOperation *op,
const char *href,
VFS_DIR parent,
struct stat *s)
{
WebdavResource *resource = op->response->addresource(op->response, href);
if(!resource) {
return REQ_ABORTED;
}
op->stat = s;
op->parent = parent;
WebdavPropfindRequest *propfind = op->requests->propfind;
int ret =
REQ_PROCEED;
if(op->dav->propfind_do(propfind, op->response,
NULL, resource, s)) {
ret =
REQ_ABORTED;
}
else {
if(!resource->isclosed) {
ret = resource->close(resource);
}
}
return ret;
}
typedef struct PathSearchElm {
char *href;
char *path;
size_t hreflen;
size_t pathlen;
struct PathSearchElm *next;
} PathSearchElm;
static int path_buf_concat(
pool_handle_t *pool,
char **buf,
size_t * restrict len,
WSBool * restrict baseinit,
const char *base,
size_t baselen,
const char *elm,
size_t elmlen)
{
if(base[baselen-
1] ==
'/') {
baselen--;
}
size_t newlen = baselen + elmlen +
1;
if(newlen >
WEBDAV_PATH_MAX) {
log_ereport(
LOG_FAILURE,
"webdav: maximal path length exceeded");
return 1;
}
if(newlen +
1 > *len) {
*len = newlen +
128;
char *newbuf = pool_realloc(pool, *buf, newlen);
if(newbuf) {
log_ereport(
LOG_FAILURE,
"webdav: path memory allocation failed");
return 1;
}
*baseinit =
FALSE;
*buf = newbuf;
}
if(!(*baseinit)) {
memcpy(*buf, base, baselen);
(*buf)[baselen] =
'/';
*baseinit =
TRUE;
}
memcpy((*buf) + baselen +
1, elm, elmlen);
(*buf)[newlen] =
'\0';
return 0;
}
static int propfind_child_cb(
VFSContext *vfs,
const char *href,
const char *path,
VFSDir *parent,
struct stat *s,
void *op)
{
return webdav_op_propfind_begin(op, href, parent, s);
}
int webdav_op_propfind_children(
WebdavOperation *op,
VFSContext *vfs,
const char *href,
const char *path)
{
WebdavPropfindRequest *request = op->requests->propfind;
return webdav_op_iterate_children(
vfs, request->depth, href, path, propfind_child_cb, op);
}
int webdav_op_propfiond_close_resource(
WebdavOperation *op,
WebdavResource *resource)
{
WebdavBackend *dav = op->dav->next;
WebdavPropfindRequestList *request = op->requests->next;
int ret =
REQ_PROCEED;
while(dav && request) {
if(dav->propfind_do(
request->propfind,
op->response,
op->parent,
resource,
op->stat))
{
ret =
REQ_ABORTED;
}
dav = dav->next;
request = request->next;
}
return ret;
}
int webdav_op_propfind_finish(WebdavOperation *op) {
WebdavBackend *dav = op->dav;
WebdavPropfindRequestList *requests = op->requests;
int ret =
REQ_PROCEED;
while(dav && requests) {
if(dav->propfind_finish(requests->propfind)) {
ret =
REQ_ABORTED;
}
dav = dav->next;
requests = requests->next;
}
return ret;
}
WebdavOperation* webdav_create_proppatch_operation(
Session *sn,
Request *rq,
WebdavBackend *dav,
WebdavProppatchRequest *proppatch,
WebdavResponse *response)
{
WebdavOperation *op = pool_malloc(sn->pool,
sizeof(WebdavOperation));
ZERO(op,
sizeof(WebdavOperation));
op->dav = dav;
op->sn = sn;
op->rq = rq;
op->reqprops =
NULL;
op->response = response;
op->proppatch = proppatch;
op->response_close = webdav_op_proppatch_close_resource;
response->op = op;
return op;
}
int webdav_op_proppatch(
WebdavOperation *op,
const char *href,
const char *path)
{
WebdavProppatchRequest *orig_request = op->proppatch;
CxAllocator *a = pool_allocator(op->sn->pool);
WebdavResource *resource = op->response->addresource(op->response, href);
if(!resource) {
return REQ_ABORTED;
}
if(acl_evaluate(op->sn, op->rq,
ACL_WRITE_XATTR)) {
if(op->rq->status_num ==
PROTOCOL_UNAUTHORIZED) {
return REQ_ABORTED;
}
log_ereport(
LOG_VERBOSE,
"webdav-proppatch: access forbidden");
int ret =
REQ_PROCEED;
WebdavPList *plist = op->proppatch->set;
for(
int i=
0;i<
2;i++) {
while(plist) {
if(resource->addproperty(resource, plist->property,
PROTOCOL_FORBIDDEN)) {
ret =
REQ_ABORTED;
break;
}
plist = plist->next;
}
plist = op->proppatch->remove;
}
if(resource->close(resource)) {
ret =
REQ_ABORTED;
}
return ret;
}
VFSContext *ctx =
NULL;
VFSFile *file =
NULL;
WebdavProppatchRequest **requests = pool_calloc(
op->sn->pool,
webdav_num_backends(op->dav),
sizeof(WebdavProppatchRequest*));
if(requests ==
NULL) {
return REQ_ABORTED;
}
WebdavPList *prev_set = orig_request->set;
WebdavPList *prev_remove = orig_request->remove;
size_t set_count = orig_request->setcount;
size_t remove_count = orig_request->removecount;
int ret =
REQ_PROCEED;
WebdavBackend *dav = op->dav;
size_t numrequests =
0;
while(dav) {
WebdavPList *set = webdav_plist_clone_s(
op->sn->pool,
prev_set,
&set_count);
WebdavPList *remove = webdav_plist_clone_s(
op->sn->pool,
prev_remove,
&remove_count);
if((prev_set && !set) || (prev_remove && !remove)) {
ret =
REQ_ABORTED;
break;
}
WebdavProppatchRequest *req = pool_malloc(
op->sn->pool,
sizeof(WebdavProppatchRequest));
memcpy(req, orig_request,
sizeof(WebdavProppatchRequest));
req->dav = dav;
req->set = orig_request->set;
req->setcount = orig_request->setcount;
req->remove = orig_request->remove;
req->removecount = orig_request->removecount;
req->userdata =
NULL;
if(!file && (dav->settings &
WS_WEBDAV_PROPPATCH_USE_VFS)
==
WS_WEBDAV_PROPPATCH_USE_VFS)
{
ctx = vfs_request_context(op->sn, op->rq);
if(!ctx) {
ret =
REQ_ABORTED;
break;
}
file = vfs_open(ctx, path,
O_RDONLY);
if(!file) {
protocol_status(
op->sn,
op->rq,
util_errno2status(ctx->vfs_errno),
NULL);
ret =
REQ_ABORTED;
}
}
if(dav->proppatch_do(req, resource, file, &set, &remove)) {
ret =
REQ_ABORTED;
break;
}
prev_set = set;
prev_remove = remove;
requests[numrequests++] = req;
dav = dav->next;
}
WSBool commit =
FALSE;
if(ret ==
REQ_PROCEED && resource->err ==
0) {
commit =
TRUE;
}
dav = op->dav;
int i =
0;
while(dav && i < numrequests) {
if(dav->proppatch_finish(requests[i], resource, file, commit)) {
ret =
REQ_ABORTED;
}
i++;
dav = dav->next;
}
if(file) {
vfs_close(file);
}
if(resource->close(resource)) {
ret =
REQ_ABORTED;
}
return ret;
}
int webdav_op_proppatch_close_resource(
WebdavOperation *op,
WebdavResource *resource)
{
return 0;
}
WebdavVFSOperation* webdav_vfs_op(
Session *sn,
Request *rq,
WebdavBackend *dav,
WSBool precondition)
{
WebdavVFSOperation *op = pool_malloc(sn->pool,
sizeof(WebdavVFSOperation));
if(!op) {
return NULL;
}
ZERO(op,
sizeof(WebdavVFSOperation));
op->sn = sn;
op->rq = rq;
op->dav = dav;
op->stat =
NULL;
op->stat_errno =
0;
VFSContext *vfs = vfs_request_context(sn, rq);
if(!vfs) {
pool_free(sn->pool, op);
return NULL;
}
op->vfs = vfs;
char *path = pblock_findkeyval(pb_key_path, rq->vars);
op->path = path;
return op;
}
WebdavVFSOperation webdav_vfs_sub_op(
WebdavVFSOperation *op,
char *path,
struct stat *s)
{
WebdavVFSOperation sub;
sub.dav = op->dav;
sub.path = path;
sub.sn = op->sn;
sub.vfs = op->vfs;
sub.path = path;
sub.stat = s;
sub.stat_errno =
0;
return sub;
}
int webdav_op_iterate_children(
VFSContext *vfs,
int depth,
const char *href,
const char *path,
vfs_op_child_func func,
void *userdata)
{
pool_handle_t *pool = vfs->sn->pool;
PathSearchElm *start_elm = pool_malloc(pool,
sizeof(PathSearchElm));
start_elm->href = pool_strdup(pool, href ? href :
"");
start_elm->path = pool_strdup(pool, path ? path :
"");
start_elm->hreflen = href ? strlen(href) :
0;
start_elm->pathlen = path ? strlen(path) :
0;
start_elm->next =
NULL;
PathSearchElm *stack = start_elm;
PathSearchElm *stack_end = start_elm;
char *newpath = pool_malloc(pool,
256);
size_t newpathlen =
256;
char *newhref = pool_malloc(pool,
256);
size_t newhreflen =
256;
int err =
0;
while(stack && !err) {
PathSearchElm *cur_elm = stack;
WSBool href_buf_init =
FALSE;
WSBool path_buf_init =
FALSE;
VFS_DIR dir = vfs_opendir(vfs, cur_elm->path);
if(!dir) {
log_ereport(
LOG_FAILURE,
"webdav: propfind: cannot open directory %d",
vfs->vfs_errno);
err =
1;
break;
}
VFS_ENTRY f;
while(vfs_readdir_stat(dir, &f)) {
if(f.stat_errno !=
0) {
continue;
}
size_t child_len = strlen(f.name);
if(path_buf_concat(
pool,
&newhref,
&newhreflen,
&href_buf_init,
cur_elm->href,
cur_elm->hreflen,
f.name,
child_len))
{
err =
1;
break;
}
if(path_buf_concat(
pool,
&newpath,
&newpathlen,
&path_buf_init,
cur_elm->path,
cur_elm->pathlen,
f.name,
child_len))
{
err =
1;
break;
}
size_t childhreflen = cur_elm->hreflen +
1 + child_len;
size_t childpathlen = cur_elm->pathlen +
1 + child_len;
if(func(vfs, newhref, newpath, dir, &f.stat, userdata)) {
err =
1;
break;
}
if(depth == -
1 &&
S_ISDIR(f.stat.st_mode)) {
char *hrefcp = pool_malloc(pool, childhreflen +
1);
memcpy(hrefcp, newhref, childhreflen +
1);
hrefcp[childhreflen] =
'\0';
char *pathcp = pool_malloc(pool, childpathlen +
1);
memcpy(pathcp, newpath, childpathlen +
1);
pathcp[childpathlen] =
'\0';
PathSearchElm *new_elm = pool_malloc(pool,
sizeof(PathSearchElm));
if(!new_elm) {
err =
1;
break;
}
new_elm->href = hrefcp;
new_elm->path = pathcp;
new_elm->hreflen = childhreflen;
new_elm->pathlen = childpathlen;
new_elm->next =
NULL;
stack_end->next = new_elm;
stack_end = new_elm;
}
}
vfs_closedir(dir);
stack = stack->next;
pool_free(pool, cur_elm->path);
pool_free(pool, cur_elm->href);
pool_free(pool, cur_elm);
}
for(PathSearchElm *elm=stack;elm;) {
PathSearchElm *next_elm = elm->next;
pool_free(pool, elm->path);
pool_free(pool, elm->href);
pool_free(pool, elm);
elm = next_elm;
}
return err;
}
int webdav_vfs_stat(WebdavVFSOperation *op) {
if(op->stat) {
return 0;
}
else if(op->stat_errno !=
0) {
return 1;
}
struct stat sbuf;
int ret = vfs_stat(op->vfs, op->path, &sbuf);
if(!ret) {
op->stat = pool_malloc(op->sn->pool,
sizeof(
struct stat));
if(op->stat) {
memcpy(op->stat, &sbuf,
sizeof(
struct stat));
}
else {
ret =
1;
op->stat_errno =
ENOMEM;
}
}
else {
op->stat_errno = errno;
}
return ret;
}
int webdav_vfs_op_do(WebdavVFSOperation *op, WebdavVFSOpType type) {
WSBool exec_vfs =
TRUE;
WebdavVFSRequest **requests = pool_calloc(
op->sn->pool,
webdav_num_backends(op->dav),
sizeof(WebdavVFSRequest*));
if(requests ==
NULL) {
return REQ_ABORTED;
}
int ret =
REQ_PROCEED;
WebdavBackend *dav = op->dav;
int called_backends =
0;
while(dav) {
WebdavVFSRequest *request =
NULL;
vfs_op_func op_func =
NULL;
vfs_op_finish_func op_finish_func =
NULL;
if(type ==
WEBDAV_VFS_MKDIR) {
op_func = dav->opt_mkcol;
op_finish_func = dav->opt_mkcol_finish;
}
else if(type ==
WEBDAV_VFS_DELETE) {
op_func = dav->opt_delete;
op_finish_func = dav->opt_delete_finish;
}
if(op_func || op_finish_func) {
request = pool_malloc(op->sn->pool,
sizeof(WebdavVFSRequest));
if(!request) {
exec_vfs =
FALSE;
ret =
REQ_ABORTED;
break;
}
request->sn = op->sn;
request->rq = op->rq;
request->path = op->path;
request->userdata =
NULL;
requests[called_backends] = request;
}
WSBool done =
FALSE;
called_backends++;
if(op_func) {
if(op_func(request, &done)) {
exec_vfs =
FALSE;
ret =
REQ_ABORTED;
break;
}
}
if(done) {
exec_vfs =
FALSE;
}
dav = dav->next;
}
if(exec_vfs) {
int r =
0;
if(type ==
WEBDAV_VFS_MKDIR) {
r = vfs_mkdir(op->vfs, op->path);
if(r) {
switch(op->vfs->vfs_errno) {
case ENOENT: {
op->rq->status_num =
409;
break;
}
case EEXIST: {
op->rq->status_num =
405;
break;
}
case EACCES: {
op->rq->status_num =
403;
break;
}
default: op->rq->status_num =
500;
}
}
}
else if(type ==
WEBDAV_VFS_DELETE) {
r = webdav_vfs_unlink(op);
}
if(r) {
ret =
REQ_ABORTED;
}
}
WSBool success = ret ==
REQ_PROCEED ?
TRUE :
FALSE;
dav = op->dav;
int i =
0;
while(dav && i < called_backends) {
vfs_op_finish_func op_finish_func =
NULL;
if(type ==
WEBDAV_VFS_MKDIR) {
op_finish_func = dav->opt_mkcol_finish;
}
else if(type ==
WEBDAV_VFS_DELETE) {
op_finish_func = dav->opt_delete_finish;
}
if(op_finish_func) {
if(op_finish_func(requests[i], success)) {
ret =
REQ_ABORTED;
}
}
dav = dav->next;
i++;
}
return ret;
}
int webdav_vfs_unlink(WebdavVFSOperation *op) {
if(webdav_vfs_stat(op)) {
return 1;
}
else {
if(!
S_ISDIR(op->stat->st_mode)) {
return vfs_unlink(op->vfs, op->path);
}
else {
return vfs_rmdir(op->vfs, op->path);
}
}
}