UNIXworkcode

1 /* 2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. 3 * 4 * Copyright 2019 Olaf Wintermann. All rights reserved. 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions are met: 8 * 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 20 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 26 * POSSIBILITY OF SUCH DAMAGE. 27 */ 28 29 #include <stdio.h> 30 #include <stdlib.h> 31 #include <errno.h> 32 33 #include <cx/list.h> 34 35 #include "../daemon/session.h" 36 #include "../util/pblock.h" 37 38 #include "operation.h" 39 40 #define WEBDAV_PATH_MAX 8192 41 42 43 size_t webdav_num_backends(WebdavBackend *dav) { 44 size_t count = 0; 45 while(dav) { 46 count++; 47 dav = dav->next; 48 } 49 return count; 50 } 51 52 /**************************************************************************** 53 * 54 * PROPFIND OPERATION 55 * 56 ****************************************************************************/ 57 58 WebdavOperation* webdav_create_propfind_operation( 59 Session *sn, 60 Request *rq, 61 WebdavBackend *dav, 62 WebdavPList *reqprops, 63 WebdavPropfindRequestList *requests, 64 WebdavResponse *response) 65 { 66 WebdavOperation *op = pool_malloc(sn->pool, sizeof(WebdavOperation)); 67 ZERO(op, sizeof(WebdavOperation)); 68 op->dav = dav; 69 op->sn = sn; 70 op->rq = rq; 71 op->reqprops = reqprops; 72 op->requests = requests; 73 op->response = response; 74 op->response_close = webdav_op_propfiond_close_resource; 75 response->op = op; 76 77 return op; 78 } 79 80 int webdav_op_propfind_begin( 81 WebdavOperation *op, 82 const char *href, 83 VFS_DIR parent, 84 struct stat *s) 85 { 86 // create WebdavResource object for requested resource 87 WebdavResource *resource = op->response->addresource(op->response, href); 88 if(!resource) { 89 return REQ_ABORTED; 90 } 91 92 // store data that we need when the resource will be closed 93 op->stat = s; 94 op->parent = parent; 95 96 // get first propfind object 97 WebdavPropfindRequest *propfind = op->requests->propfind; 98 99 // execute propfind_do of the first backend for the first resource 100 int ret = REQ_PROCEED; 101 if(op->dav->propfind_do(propfind, op->response, NULL, resource, s)) { 102 ret = REQ_ABORTED; 103 } else { 104 // propfind_do successful, close resource if needed 105 // closing the resource will execute propfind_do of all remaining 106 // backends 107 if(!resource->isclosed) { 108 ret = resource->close(resource); 109 } 110 } 111 112 return ret; 113 } 114 115 typedef struct PathSearchElm { 116 char *href; 117 char *path; 118 size_t hreflen; 119 size_t pathlen; 120 struct PathSearchElm *next; 121 } PathSearchElm; 122 123 /* 124 * concats base + / + elm 125 * if baseinit is true, only elm is copied 126 */ 127 static int path_buf_concat( 128 pool_handle_t *pool, 129 char **buf, 130 size_t * restrict len, 131 WSBool * restrict baseinit, 132 const char *base, 133 size_t baselen, 134 const char *elm, 135 size_t elmlen) 136 { 137 if(base[baselen-1] == '/') { 138 baselen--; 139 } 140 141 size_t newlen = baselen + elmlen + 1; 142 if(newlen > WEBDAV_PATH_MAX) { 143 log_ereport(LOG_FAILURE, "webdav: maximal path length exceeded"); 144 return 1; 145 } 146 147 // check if new path + terminator fits in the buffer 148 if(newlen + 1 > *len) { 149 *len = newlen + 128; 150 char *newbuf = pool_realloc(pool, *buf, newlen); 151 if(newbuf) { 152 log_ereport(LOG_FAILURE, "webdav: path memory allocation failed"); 153 return 1; 154 } 155 *baseinit = FALSE; 156 157 *buf = newbuf; 158 } 159 160 // if baseinit is true, the parent is already in the buffer 161 // and we don't need to memcpy it again 162 if(!(*baseinit)) { 163 memcpy(*buf, base, baselen); 164 (*buf)[baselen] = '/'; 165 *baseinit = TRUE; 166 } 167 // copy child and terminate string 168 memcpy((*buf) + baselen + 1, elm, elmlen); 169 (*buf)[newlen] = '\0'; 170 171 return 0; 172 } 173 174 static int propfind_child_cb( 175 VFSContext *vfs, 176 const char *href, 177 const char *path, 178 VFSDir *parent, 179 struct stat *s, 180 void *op) 181 { 182 return webdav_op_propfind_begin(op, href, parent, s); 183 } 184 185 int webdav_op_propfind_children( 186 WebdavOperation *op, 187 VFSContext *vfs, 188 const char *href, 189 const char *path) 190 { 191 WebdavPropfindRequest *request = op->requests->propfind; 192 return webdav_op_iterate_children( 193 vfs, request->depth, href, path, propfind_child_cb, op); 194 } 195 196 int webdav_op_propfiond_close_resource( 197 WebdavOperation *op, 198 WebdavResource *resource) 199 { 200 // start with second backend and request, because 201 // the first one was already called by webdav_op_propfind_begin 202 WebdavBackend *dav = op->dav->next; 203 WebdavPropfindRequestList *request = op->requests->next; 204 205 // call propfind_do of all remaining backends 206 int ret = REQ_PROCEED; 207 while(dav && request) { 208 if(dav->propfind_do( 209 request->propfind, 210 op->response, 211 op->parent, 212 resource, 213 op->stat)) 214 { 215 ret = REQ_ABORTED; 216 } 217 218 dav = dav->next; 219 request = request->next; 220 } 221 return ret; 222 } 223 224 /* 225 * Executes propfind_finish for each Backend 226 */ 227 int webdav_op_propfind_finish(WebdavOperation *op) { 228 WebdavBackend *dav = op->dav; 229 WebdavPropfindRequestList *requests = op->requests; 230 231 int ret = REQ_PROCEED; 232 while(dav && requests) { 233 if(dav->propfind_finish(requests->propfind)) { 234 ret = REQ_ABORTED; 235 } 236 237 dav = dav->next; 238 requests = requests->next; 239 } 240 return ret; 241 } 242 243 /**************************************************************************** 244 * 245 * PROPPATCH OPERATION 246 * 247 ****************************************************************************/ 248 249 WebdavOperation* webdav_create_proppatch_operation( 250 Session *sn, 251 Request *rq, 252 WebdavBackend *dav, 253 WebdavProppatchRequest *proppatch, 254 WebdavResponse *response) 255 { 256 WebdavOperation *op = pool_malloc(sn->pool, sizeof(WebdavOperation)); 257 ZERO(op, sizeof(WebdavOperation)); 258 op->dav = dav; 259 op->sn = sn; 260 op->rq = rq; 261 op->reqprops = NULL; 262 op->response = response; 263 op->proppatch = proppatch; 264 op->response_close = webdav_op_proppatch_close_resource; 265 response->op = op; 266 267 return op; 268 } 269 270 271 272 int webdav_op_proppatch( 273 WebdavOperation *op, 274 const char *href, 275 const char *path) 276 { 277 WebdavProppatchRequest *orig_request = op->proppatch; 278 CxAllocator *a = pool_allocator(op->sn->pool); 279 280 // create WebdavResource object for the requested resource 281 WebdavResource *resource = op->response->addresource(op->response, href); 282 if(!resource) { 283 return REQ_ABORTED; 284 } 285 286 // check ACL 287 if(acl_evaluate(op->sn, op->rq, ACL_WRITE_XATTR)) { 288 // ACL check failed, either unauthorized or forbidden 289 // acl_evaluate() sets the http status code and may add 290 // response headers for authentication 291 if(op->rq->status_num == PROTOCOL_UNAUTHORIZED) { 292 return REQ_ABORTED; // return here to send an authenticate response 293 } 294 295 // send multistatus response with status code 403 for each property 296 log_ereport(LOG_VERBOSE, "webdav-proppatch: access forbidden"); 297 int ret = REQ_PROCEED; 298 WebdavPList *plist = op->proppatch->set; 299 for(int i=0;i<2;i++) { 300 while(plist) { 301 if(resource->addproperty(resource, plist->property, PROTOCOL_FORBIDDEN)) { 302 ret = REQ_ABORTED; 303 break; // OOM 304 } 305 plist = plist->next; 306 } 307 plist = op->proppatch->remove; 308 } 309 310 if(resource->close(resource)) { 311 ret = REQ_ABORTED; // OOM 312 } 313 return ret; 314 } 315 316 VFSContext *ctx = NULL; 317 VFSFile *file = NULL; 318 319 // requests for each backends 320 WebdavProppatchRequest **requests = pool_calloc( 321 op->sn->pool, 322 webdav_num_backends(op->dav), 323 sizeof(WebdavProppatchRequest*)); 324 if(requests == NULL) { 325 return REQ_ABORTED; 326 } 327 328 WebdavPList *prev_set = orig_request->set; 329 WebdavPList *prev_remove = orig_request->remove; 330 size_t set_count = orig_request->setcount; 331 size_t remove_count = orig_request->removecount; 332 333 int ret = REQ_PROCEED; 334 335 // iterate backends and execute proppatch_do 336 WebdavBackend *dav = op->dav; 337 size_t numrequests = 0; 338 while(dav) { 339 WebdavPList *set = webdav_plist_clone_s( 340 op->sn->pool, 341 prev_set, 342 &set_count); 343 WebdavPList *remove = webdav_plist_clone_s( 344 op->sn->pool, 345 prev_remove, 346 &remove_count); 347 if((prev_set && !set) || (prev_remove && !remove)) { 348 // clone failed, OOM 349 ret = REQ_ABORTED; 350 break; 351 } 352 353 // create new WebdavProppatchRequest object for this backend 354 WebdavProppatchRequest *req = pool_malloc( 355 op->sn->pool, 356 sizeof(WebdavProppatchRequest)); 357 memcpy(req, orig_request, sizeof(WebdavProppatchRequest)); 358 req->dav = dav; 359 req->set = orig_request->set; 360 req->setcount = orig_request->setcount; 361 req->remove = orig_request->remove; 362 req->removecount = orig_request->removecount; 363 req->userdata = NULL; 364 365 // check if we need to open the file because the backend wants it 366 if(!file && (dav->settings & WS_WEBDAV_PROPPATCH_USE_VFS) 367 == WS_WEBDAV_PROPPATCH_USE_VFS) 368 { 369 ctx = vfs_request_context(op->sn, op->rq); 370 if(!ctx) { 371 ret = REQ_ABORTED; 372 break; 373 } 374 375 file = vfs_open(ctx, path, O_RDONLY); 376 if(!file) { 377 protocol_status( 378 op->sn, 379 op->rq, 380 util_errno2status(ctx->vfs_errno), 381 NULL); 382 ret = REQ_ABORTED; 383 } 384 } 385 386 // execute proppatch_do 387 if(dav->proppatch_do(req, resource, file, &set, &remove)) { 388 // return later, because we need do execute proppatch_finish 389 // for all successfully called backends 390 ret = REQ_ABORTED; 391 break; 392 } 393 394 // proppatch_do should remove all handled props from set and remove 395 // in the next iteration, the backend must use these reduced lists 396 prev_set = set; 397 prev_remove = remove; 398 399 requests[numrequests++] = req; 400 401 // continue with next backend 402 dav = dav->next; 403 } 404 405 WSBool commit = FALSE; 406 if(ret == REQ_PROCEED && resource->err == 0) { 407 // no errors, no properties with errors -> save the changes 408 commit = TRUE; 409 } 410 411 // call proppatch_finish for each successfully called proppatch_do 412 dav = op->dav; 413 int i = 0; 414 while(dav && i < numrequests) { 415 if(dav->proppatch_finish(requests[i], resource, file, commit)) { 416 ret = REQ_ABORTED; 417 } 418 i++; 419 dav = dav->next; 420 } 421 422 if(file) { 423 vfs_close(file); 424 } 425 426 if(resource->close(resource)) { 427 ret = REQ_ABORTED; 428 } 429 430 return ret; 431 } 432 433 int webdav_op_proppatch_close_resource( 434 WebdavOperation *op, 435 WebdavResource *resource) 436 { 437 return 0; // NOP 438 } 439 440 441 /**************************************************************************** 442 * 443 * VFS OPERATION 444 * 445 ****************************************************************************/ 446 447 WebdavVFSOperation* webdav_vfs_op( 448 Session *sn, 449 Request *rq, 450 WebdavBackend *dav, 451 WSBool precondition) 452 { 453 WebdavVFSOperation *op = pool_malloc(sn->pool, sizeof(WebdavVFSOperation)); 454 if(!op) { 455 return NULL; 456 } 457 ZERO(op, sizeof(WebdavVFSOperation)); 458 459 op->sn = sn; 460 op->rq = rq; 461 op->dav = dav; 462 op->stat = NULL; 463 op->stat_errno = 0; 464 465 // create VFS context 466 VFSContext *vfs = vfs_request_context(sn, rq); 467 if(!vfs) { 468 pool_free(sn->pool, op); 469 return NULL; 470 } 471 op->vfs = vfs; 472 473 char *path = pblock_findkeyval(pb_key_path, rq->vars); 474 op->path = path; 475 476 return op; 477 } 478 479 WebdavVFSOperation webdav_vfs_sub_op( 480 WebdavVFSOperation *op, 481 char *path, 482 struct stat *s) 483 { 484 WebdavVFSOperation sub; 485 sub.dav = op->dav; 486 sub.path = path; 487 sub.sn = op->sn; 488 sub.vfs = op->vfs; 489 sub.path = path; 490 sub.stat = s; 491 sub.stat_errno = 0; 492 return sub; 493 } 494 495 int webdav_op_iterate_children( 496 VFSContext *vfs, 497 int depth, 498 const char *href, 499 const char *path, 500 vfs_op_child_func func, 501 void *userdata) 502 { 503 pool_handle_t *pool = vfs->sn->pool; 504 505 PathSearchElm *start_elm = pool_malloc(pool, sizeof(PathSearchElm)); 506 start_elm->href = pool_strdup(pool, href ? href : ""); 507 start_elm->path = pool_strdup(pool, path ? path : ""); 508 start_elm->hreflen = href ? strlen(href) : 0; 509 start_elm->pathlen = path ? strlen(path) : 0; 510 start_elm->next = NULL; 511 512 PathSearchElm *stack = start_elm; 513 PathSearchElm *stack_end = start_elm; 514 515 // reusable buffer for full child path and href 516 char *newpath = pool_malloc(pool, 256); 517 size_t newpathlen = 256; 518 519 char *newhref = pool_malloc(pool, 256); 520 size_t newhreflen = 256; 521 522 int err = 0; 523 while(stack && !err) { 524 PathSearchElm *cur_elm = stack; 525 526 // when newpath is initialized with the parent path 527 // set path_buf_init to TRUE 528 WSBool href_buf_init = FALSE; 529 WSBool path_buf_init = FALSE; 530 531 VFS_DIR dir = vfs_opendir(vfs, cur_elm->path); 532 if(!dir) { 533 log_ereport( 534 LOG_FAILURE, 535 "webdav: propfind: cannot open directory %d", 536 vfs->vfs_errno); 537 err = 1; 538 break; 539 } 540 541 VFS_ENTRY f; 542 while(vfs_readdir_stat(dir, &f)) { 543 if(f.stat_errno != 0) { 544 continue; 545 } 546 547 size_t child_len = strlen(f.name); 548 549 // create new path and href for the child 550 if(path_buf_concat( 551 pool, 552 &newhref, 553 &newhreflen, 554 &href_buf_init, 555 cur_elm->href, 556 cur_elm->hreflen, 557 f.name, 558 child_len)) 559 { 560 err = 1; 561 break; 562 } 563 if(path_buf_concat( 564 pool, 565 &newpath, 566 &newpathlen, 567 &path_buf_init, 568 cur_elm->path, 569 cur_elm->pathlen, 570 f.name, 571 child_len)) 572 { 573 err = 1; 574 break; 575 } 576 size_t childhreflen = cur_elm->hreflen + 1 + child_len; 577 size_t childpathlen = cur_elm->pathlen + 1 + child_len; 578 579 // execute callback func for this file 580 if(func(vfs, newhref, newpath, dir, &f.stat, userdata)) { 581 err = 1; 582 break; 583 } 584 585 // depth of -1 means infinity 586 if(depth == -1 && S_ISDIR(f.stat.st_mode)) { 587 char *hrefcp = pool_malloc(pool, childhreflen + 1); 588 memcpy(hrefcp, newhref, childhreflen + 1); 589 hrefcp[childhreflen] = '\0'; 590 591 char *pathcp = pool_malloc(pool, childpathlen + 1); 592 memcpy(pathcp, newpath, childpathlen + 1); 593 pathcp[childpathlen] = '\0'; 594 595 PathSearchElm *new_elm = pool_malloc(pool, 596 sizeof(PathSearchElm)); 597 if(!new_elm) { 598 err = 1; 599 break; 600 } 601 new_elm->href = hrefcp; 602 new_elm->path = pathcp; 603 new_elm->hreflen = childhreflen; 604 new_elm->pathlen = childpathlen; 605 new_elm->next = NULL; 606 607 // add the new_elm to the stack 608 // stack_end is always not NULL here, because the loop is 609 // running as long as we have a stack and we remove 610 // the first stack element at the end of the loop 611 stack_end->next = new_elm; 612 stack_end = new_elm; 613 } 614 } 615 616 vfs_closedir(dir); 617 618 stack = stack->next; 619 620 pool_free(pool, cur_elm->path); 621 pool_free(pool, cur_elm->href); 622 pool_free(pool, cur_elm); 623 } 624 625 // in case of an error, we have to free all remaining stack elements 626 for(PathSearchElm *elm=stack;elm;) { 627 PathSearchElm *next_elm = elm->next; 628 pool_free(pool, elm->path); 629 pool_free(pool, elm->href); 630 pool_free(pool, elm); 631 elm = next_elm; 632 } 633 634 return err; 635 } 636 637 638 int webdav_vfs_stat(WebdavVFSOperation *op) { 639 if(op->stat) { 640 return 0; 641 } else if(op->stat_errno != 0) { 642 // previous stat failed 643 return 1; 644 } 645 646 // stat file 647 struct stat sbuf; 648 int ret = vfs_stat(op->vfs, op->path, &sbuf); 649 if(!ret) { 650 // save result in op->stat and in s 651 op->stat = pool_malloc(op->sn->pool, sizeof(struct stat)); 652 if(op->stat) { 653 memcpy(op->stat, &sbuf, sizeof(struct stat)); 654 } else { 655 ret = 1; 656 op->stat_errno = ENOMEM; 657 } 658 } else { 659 op->stat_errno = errno; 660 } 661 662 return ret; 663 } 664 665 int webdav_vfs_op_do(WebdavVFSOperation *op, WebdavVFSOpType type) { 666 WSBool exec_vfs = TRUE; 667 668 // requests for each backends 669 WebdavVFSRequest **requests = pool_calloc( 670 op->sn->pool, 671 webdav_num_backends(op->dav), 672 sizeof(WebdavVFSRequest*)); 673 if(requests == NULL) { 674 return REQ_ABORTED; 675 } 676 677 int ret = REQ_PROCEED; 678 679 // call opt_* func for each backend 680 WebdavBackend *dav = op->dav; 681 int called_backends = 0; 682 while(dav) { 683 WebdavVFSRequest *request = NULL; 684 685 // get vfs operation functions 686 vfs_op_func op_func = NULL; 687 vfs_op_finish_func op_finish_func = NULL; 688 689 if(type == WEBDAV_VFS_MKDIR) { 690 op_func = dav->opt_mkcol; 691 op_finish_func = dav->opt_mkcol_finish; 692 } else if(type == WEBDAV_VFS_DELETE) { 693 op_func = dav->opt_delete; 694 op_finish_func = dav->opt_delete_finish; 695 } 696 697 if(op_func || op_finish_func) { 698 // we need a request object 699 request = pool_malloc(op->sn->pool, sizeof(WebdavVFSRequest)); 700 if(!request) { 701 exec_vfs = FALSE; 702 ret = REQ_ABORTED; 703 break; 704 } 705 request->sn = op->sn; 706 request->rq = op->rq; 707 request->path = op->path; 708 request->userdata = NULL; 709 710 requests[called_backends] = request; 711 } 712 713 // exec backend func for this operation 714 // this will set 'done' to TRUE, if no further vfs call is required 715 WSBool done = FALSE; 716 called_backends++; 717 if(op_func) { 718 if(op_func(request, &done)) { 719 exec_vfs = FALSE; 720 ret = REQ_ABORTED; 721 break; 722 } 723 } 724 if(done) { 725 exec_vfs = FALSE; 726 } 727 728 dav = dav->next; 729 } 730 731 // if needed, call vfs func for this operation 732 if(exec_vfs) { 733 int r = 0; 734 if(type == WEBDAV_VFS_MKDIR) { 735 r = vfs_mkdir(op->vfs, op->path); 736 if(r) { 737 // mkcol specific status codes 738 switch(op->vfs->vfs_errno) { 739 case ENOENT: { 740 op->rq->status_num = 409; 741 break; 742 } 743 case EEXIST: { 744 op->rq->status_num = 405; 745 break; 746 } 747 case EACCES: { 748 op->rq->status_num = 403; 749 break; 750 } 751 default: op->rq->status_num = 500; 752 } 753 } 754 } else if(type == WEBDAV_VFS_DELETE) { 755 r = webdav_vfs_unlink(op); 756 } 757 758 if(r) { 759 ret = REQ_ABORTED; 760 } 761 } 762 763 WSBool success = ret == REQ_PROCEED ? TRUE : FALSE; 764 765 // finish mkcol (cleanup) by calling opt_*_finish for each backend 766 dav = op->dav; 767 int i = 0; 768 while(dav && i < called_backends) { 769 // get vfs operation functions 770 vfs_op_finish_func op_finish_func = NULL; 771 772 if(type == WEBDAV_VFS_MKDIR) { 773 op_finish_func = dav->opt_mkcol_finish; 774 } else if(type == WEBDAV_VFS_DELETE) { 775 op_finish_func = dav->opt_delete_finish; 776 } 777 778 if(op_finish_func) { 779 if(op_finish_func(requests[i], success)) { 780 ret = REQ_ABORTED; // don't exit loop 781 } 782 } 783 784 dav = dav->next; 785 i++; 786 } 787 788 return ret; 789 } 790 791 int webdav_vfs_unlink(WebdavVFSOperation *op) { 792 // stat the file first, to check if the file is a directory 793 if(webdav_vfs_stat(op)) { 794 return 1; // error 795 } else { 796 if(!S_ISDIR(op->stat->st_mode)) { 797 return vfs_unlink(op->vfs, op->path); 798 } else { 799 return vfs_rmdir(op->vfs, op->path); 800 } 801 } 802 } 803