Sat, 25 Mar 2023 17:18:51 +0100
fix PUT could potentially return a wrong status code
/* * 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 <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; } /**************************************************************************** * * PROPFIND OPERATION * ****************************************************************************/ 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) { // 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->propfind; // 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; struct PathSearchElm *next; } 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->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) { // start with second backend and request, because // the first one was already called by webdav_op_propfind_begin WebdavBackend *dav = op->dav->next; WebdavPropfindRequestList *request = op->requests->next; // call propfind_do of all remaining backends 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; } /* * Executes propfind_finish for each Backend */ 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; } /**************************************************************************** * * 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; CxAllocator *a = pool_allocator(op->sn->pool); // create WebdavResource object for the requested resource WebdavResource *resource = op->response->addresource(op->response, href); if(!resource) { return REQ_ABORTED; } // check ACL if(acl_evaluate(op->sn, op->rq, ACL_WRITE_XATTR)) { // ACL check failed, either unauthorized or forbidden // acl_evaluate() sets the http status code and may add // response headers for authentication if(op->rq->status_num == PROTOCOL_UNAUTHORIZED) { return REQ_ABORTED; // return here to send an authenticate response } // send multistatus response with status code 403 for each property 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; // OOM } plist = plist->next; } plist = op->proppatch->remove; } if(resource->close(resource)) { ret = REQ_ABORTED; // OOM } return ret; } 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) { 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; // 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; // 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)); 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; // add the new_elm to the stack // stack_end is always not NULL here, because the loop is // running as long as we have a stack and we remove // the first stack element at the end of the loop 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); } // in case of an error, we have to free all remaining stack elements 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) { // 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); } } }