--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/server/webdav/operation.c Sat Sep 24 16:26:10 2022 +0200 @@ -0,0 +1,753 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2019 Olaf Wintermann. 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. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <errno.h> + +#include <ucx/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; +} + +/**************************************************************************** + * + * PROPFIND OPERATION + * + ****************************************************************************/ + +WebdavOperation* webdav_create_propfind_operation( + Session *sn, + Request *rq, + WebdavBackend *dav, + WebdavPList *reqprops, + UcxList *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) +{ + // create WebdavResource object for requested resource + WebdavResource *resource = op->response->addresource(op->response, href); + if(!resource) { + return REQ_ABORTED; + } + + // store data that we need when the resource will be closed + op->stat = s; + op->parent = parent; + + // get first propfind object + WebdavPropfindRequest *propfind = op->requests->data; + + // execute propfind_do of the first backend for the first resource + int ret = REQ_PROCEED; + if(op->dav->propfind_do(propfind, op->response, NULL, resource, s)) { + ret = REQ_ABORTED; + } else { + // propfind_do successful, close resource if needed + // closing the resource will execute propfind_do of all remaining + // backends + if(!resource->isclosed) { + ret = resource->close(resource); + } + } + + return ret; +} + +typedef struct PathSearchElm { + char *href; + char *path; + size_t hreflen; + size_t pathlen; +} PathSearchElm; + +/* + * concats base + / + elm + * if baseinit is true, only elm is copied + */ +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; + } + + // check if new path + terminator fits in the buffer + 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 is true, the parent is already in the buffer + // and we don't need to memcpy it again + if(!(*baseinit)) { + memcpy(*buf, base, baselen); + (*buf)[baselen] = '/'; + *baseinit = TRUE; + } + // copy child and terminate string + 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->data; + return webdav_op_iterate_children( + vfs, request->depth, href, path, propfind_child_cb, op); +} + +int webdav_op_propfiond_close_resource( + WebdavOperation *op, + WebdavResource *resource) +{ + // start with second backend and request, because + // the first one was already called by webdav_op_propfind_begin + WebdavBackend *dav = op->dav->next; + UcxList *request = op->requests->next; + + // call propfind_do of all remaining backends + int ret = REQ_PROCEED; + while(dav && request) { + if(dav->propfind_do( + request->data, + op->response, + op->parent, + resource, + op->stat)) + { + ret = REQ_ABORTED; + } + + dav = dav->next; + request = request->next; + } + return ret; +} + +/* + * Executes propfind_finish for each Backend + */ +int webdav_op_propfind_finish(WebdavOperation *op) { + WebdavBackend *dav = op->dav; + UcxList *requests = op->requests; + + int ret = REQ_PROCEED; + while(dav && requests) { + if(dav->propfind_finish(requests->data)) { + ret = REQ_ABORTED; + } + + dav = dav->next; + requests = requests->next; + } + return ret; +} + +/**************************************************************************** + * + * PROPPATCH OPERATION + * + ****************************************************************************/ + +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; + UcxAllocator *a = session_get_allocator(op->sn); + + // create WebdavResource object for the requested resource + WebdavResource *resource = op->response->addresource(op->response, href); + if(!resource) { + return REQ_ABORTED; + } + + VFSContext *ctx = NULL; + VFSFile *file = NULL; + + // requests for each backends + 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; + + // iterate backends and execute proppatch_do + 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)) { + // clone failed, OOM + ret = REQ_ABORTED; + break; + } + + // create new WebdavProppatchRequest object for this backend + 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; + + // check if we need to open the file because the backend wants it + 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; + } + } + + // execute proppatch_do + if(dav->proppatch_do(req, resource, file, &set, &remove)) { + // return later, because we need do execute proppatch_finish + // for all successfully called backends + ret = REQ_ABORTED; + break; + } + + // proppatch_do should remove all handled props from set and remove + // in the next iteration, the backend must use these reduced lists + prev_set = set; + prev_remove = remove; + + requests[numrequests++] = req; + + // continue with next backend + dav = dav->next; + } + + WSBool commit = FALSE; + if(ret == REQ_PROCEED && resource->err == 0) { + // no errors, no properties with errors -> save the changes + commit = TRUE; + } + + // call proppatch_finish for each successfully called proppatch_do + 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; // NOP +} + + +/**************************************************************************** + * + * VFS OPERATION + * + ****************************************************************************/ + +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; + + // create VFS context + 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) +{ + UcxAllocator *a = session_get_allocator(vfs->sn); + 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; + + UcxList *stack = ucx_list_prepend_a(a, NULL, start_elm); + UcxList *stack_end = stack; + if(!stack) { + return 1; + } + + // reusable buffer for full child path and href + 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->data; + + // when newpath is initialized with the parent path + // set path_buf_init to TRUE + 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); + + // create new path and href for the child + 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; + + // execute callback func for this file + if(func(vfs, newhref, newpath, dir, &f.stat, userdata)) { + err = 1; + break; + } + + // depth of -1 means infinity + 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)); + new_elm->href = hrefcp; + new_elm->path = pathcp; + new_elm->hreflen = childhreflen; + new_elm->pathlen = childpathlen; + + // add the new_elm to the stack + // stack_end is always not NULL here, because we remove + // the first stack element at the end of the loop + UcxList *newlistelm = ucx_list_append_a(a, stack_end, new_elm); + if(!newlistelm) { + err = 1; + break; + } + stack_end = newlistelm; + } + } + + vfs_closedir(dir); + + pool_free(pool, cur_elm->path); + pool_free(pool, cur_elm->href); + pool_free(pool, cur_elm); + + stack = ucx_list_remove_a(a, stack, stack); + } + + // in case of an error, we have to free all remaining stack elements + UCX_FOREACH(elm, stack) { + char *data = elm->data; + if(data != path) { + pool_free(pool, data); + } + } + + return err; +} + + +int webdav_vfs_stat(WebdavVFSOperation *op) { + if(op->stat) { + return 0; + } else if(op->stat_errno != 0) { + // previous stat failed + return 1; + } + + // stat file + struct stat sbuf; + int ret = vfs_stat(op->vfs, op->path, &sbuf); + if(!ret) { + // save result in op->stat and in s + 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; + + // requests for each backends + WebdavVFSRequest **requests = pool_calloc( + op->sn->pool, + webdav_num_backends(op->dav), + sizeof(WebdavVFSRequest*)); + if(requests == NULL) { + return REQ_ABORTED; + } + + int ret = REQ_PROCEED; + + // call opt_* func for each backend + WebdavBackend *dav = op->dav; + int called_backends = 0; + while(dav) { + WebdavVFSRequest *request = NULL; + + // get vfs operation functions + 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) { + // we need a request object + 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; + } + + // exec backend func for this operation + // this will set 'done' to TRUE, if no further vfs call is required + 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 needed, call vfs func for this operation + if(exec_vfs) { + int r = 0; + if(type == WEBDAV_VFS_MKDIR) { + r = vfs_mkdir(op->vfs, op->path); + } else if(type == WEBDAV_VFS_DELETE) { + r = webdav_vfs_unlink(op); + } + + if(r) { + ret = REQ_ABORTED; + } + } + + WSBool success = ret == REQ_PROCEED ? TRUE : FALSE; + + // finish mkcol (cleanup) by calling opt_*_finish for each backend + dav = op->dav; + int i = 0; + while(dav && i < called_backends) { + // get vfs operation functions + 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; // don't exit loop + } + } + + dav = dav->next; + i++; + } + + return ret; +} + +int webdav_vfs_unlink(WebdavVFSOperation *op) { + // stat the file first, to check if the file is a directory + if(webdav_vfs_stat(op)) { + return 1; // error + } else { + if(!S_ISDIR(op->stat->st_mode)) { + return vfs_unlink(op->vfs, op->path); + } else { + return vfs_rmdir(op->vfs, op->path); + } + } +}