UNIXworkcode

1 /* 2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. 3 * 4 * Copyright 2023 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 30 #include "xattrbackend.h" 31 32 #include "webdav.h" 33 34 #include "../util/util.h" 35 #include "../util/libxattr.h" 36 #include "../util/pblock.h" 37 38 #include <inttypes.h> 39 40 #include <cx/hash_map.h> 41 #include <cx/buffer.h> 42 #include <cx/utils.h> 43 #include <cx/printf.h> 44 #include <libxml/tree.h> 45 46 47 48 static WebdavBackend webdav_xattr_backend = { 49 webdav_xattr_propfind_init, 50 webdav_xattr_propfind_do, 51 webdav_xattr_propfind_finish, 52 webdav_xattr_proppatch_do, 53 webdav_xattr_proppatch_finish, 54 NULL, // opt_mkcol 55 NULL, // opt_mkcol_finish 56 NULL, // opt_delete 57 NULL, // opt_delete_finish 58 0, // settings 59 NULL, // instance 60 NULL // next 61 }; 62 63 int webdav_init_xattr_backend(void) { 64 65 66 if(webdav_register_backend("xattr", webdav_xattr_init, webdav_xattr_create)) { 67 return 1; 68 } 69 return 0; 70 } 71 72 void* webdav_xattr_init(ServerConfiguration *cfg, pool_handle_t *pool, WSConfigNode *config) { 73 WebdavXAttrRepository *repo = pool_malloc(pool, sizeof(WebdavXAttrRepository)); 74 if(!repo) { 75 return NULL; 76 } 77 78 // TODO: config 79 repo->xattr_name = "webdav_properties"; 80 81 return repo; 82 } 83 84 WebdavBackend* webdav_xattr_create(Session *sn, Request *rq, pblock *pb, void *initData) { 85 WebdavBackend *dav = pool_malloc(sn->pool, sizeof(WebdavBackend)); 86 if(!dav) { 87 return NULL; 88 } 89 90 WebdavXAttrBackend *instance = pool_malloc(sn->pool, sizeof(WebdavXAttrBackend)); 91 if(!instance) { 92 return NULL; 93 } 94 instance->repo = initData; 95 96 *dav = webdav_xattr_backend; 97 dav->instance = instance; 98 99 return dav; 100 } 101 102 103 104 /* -------------------- webdav backend imlementation ----------------------*/ 105 106 int webdav_xattr_propfind_init( 107 WebdavPropfindRequest *rq, 108 const char *path, 109 const char *href, 110 WebdavPList **outplist) 111 { 112 // make sure the sys vfs is used, because currently only 113 // native sysfs xattr is supported 114 if(rq->rq->vfs) { 115 log_ereport(LOG_FAILURE, "webdav-propfind: xattr backend unsupported with non-native VFS"); 116 return 1; 117 } 118 119 XAttrPropfind *xprop = pool_malloc(rq->sn->pool, sizeof(XAttrPropfind)); 120 if(!xprop) { 121 return 1; 122 } 123 rq->userdata = xprop; 124 125 xprop->base_href = href; 126 xprop->base_path = path; 127 128 return 0; 129 } 130 131 int webdav_xattr_propfind_do( 132 WebdavPropfindRequest *request, 133 WebdavResponse *response, 134 VFS_DIR parent, 135 WebdavResource *resource, 136 struct stat *s) 137 { 138 Session *sn = request->sn; 139 Request *rq = request->rq; 140 CxAllocator *a = pool_allocator(sn->pool); 141 142 WebdavXAttrBackend *xdav = request->dav->instance; 143 WebdavXAttrRepository *repo = xdav->repo; 144 XAttrPropfind *xprop = request->userdata; 145 146 // get resource path 147 const char *path; 148 char *path_dp = NULL; 149 if(!parent) { 150 // use base path 151 path = xprop->base_path; 152 } else { 153 size_t base_href_len = strlen(xprop->base_href); 154 size_t base_path_len = strlen(xprop->base_path); 155 char *res_path = resource->href + base_href_len; 156 size_t res_path_len = strlen(res_path); 157 158 path_dp = pool_malloc(sn->pool, base_path_len + res_path_len + 2); 159 memcpy(path_dp, xprop->base_path, base_path_len); 160 int s = 0; 161 if(path_dp[base_path_len-1] != '/' && res_path[0] != '/') { 162 path_dp[base_path_len] = '/'; 163 s = 1; 164 } 165 memcpy(path_dp + base_path_len + s, res_path, res_path_len); 166 path_dp[base_path_len + s + res_path_len] = 0; 167 168 path = path_dp; 169 } 170 171 ssize_t xattr_data_len = 0; 172 char *xattr_data = xattr_get_alloc( 173 sn->pool, 174 (libxattr_malloc_func)pool_malloc, 175 (libxattr_free_func)pool_free, 176 path, 177 repo->xattr_name, 178 &xattr_data_len); 179 180 if(!xattr_data) { 181 // no properties for this resource 182 return 0; 183 } 184 185 CxMap *pmap = webdav_xattr_parse_data(a, xattr_data, xattr_data_len, path); 186 pool_free(sn->pool, xattr_data); 187 if(!pmap) { 188 return 1; 189 } 190 191 int err; 192 if(request->allprop || request->propname) { 193 err = webdav_xattr_propfind_allprop(request, resource, a, pmap); 194 } else { 195 err = webdav_xattr_propfind_get_requested_properties(request, resource, a, pmap); 196 } 197 198 return err; 199 } 200 201 int webdav_xattr_propfind_finish(WebdavPropfindRequest *rq) { 202 return 0; 203 } 204 205 // helper functions for propfind_do 206 207 int webdav_xattr_propfind_get_requested_properties( 208 WebdavPropfindRequest *request, 209 WebdavResource *resource, 210 CxAllocator *a, 211 CxMap *pmap) 212 { 213 WebdavPListIterator i = webdav_plist_iterator(&request->properties); 214 WebdavPList *cur; 215 while(webdav_plist_iterator_next(&i, &cur)) { 216 WebdavProperty *reqprop = cur->property; 217 218 CxHashKey key = webdav_property_key_a( 219 a, 220 (const char*)reqprop->namespace->href, 221 (const char*)reqprop->name); 222 if(!key.data) { 223 return 1; 224 } 225 226 WebdavProperty *prop = cxMapGet(pmap, key); 227 if(prop) { 228 if(resource->addproperty(resource, prop, 200)) { 229 return 1; 230 } 231 } 232 } 233 234 return 0; 235 } 236 237 int webdav_xattr_propfind_allprop( 238 WebdavPropfindRequest *request, 239 WebdavResource *resource, 240 CxAllocator *a, 241 CxMap *pmap) 242 { 243 CxIterator i = cxMapIteratorValues(pmap); 244 cx_foreach(WebdavProperty*, prop, i) { 245 if(request->propname) { 246 prop->vtype = WS_VALUE_NO_TYPE; 247 } 248 if(resource->addproperty(resource, prop, 200)) { 249 return 1; 250 } 251 } 252 return 0; 253 } 254 255 256 257 int webdav_xattr_proppatch_do( 258 WebdavProppatchRequest *request, 259 WebdavResource *response, 260 VFSFile *file, 261 WebdavPList **setInOut, 262 WebdavPList **removeInOut) 263 { 264 Session *sn = request->sn; 265 Request *rq = request->rq; 266 CxAllocator *a = pool_allocator(sn->pool); 267 268 WebdavXAttrBackend *xdav = request->dav->instance; 269 WebdavXAttrRepository *repo = xdav->repo; 270 271 char *path = pblock_findkeyval(pb_key_path, rq->vars); 272 273 // How xattr proppatch works: 274 // 1. get existing property data from the file's xattr 275 // 2. do set/remove operations to the existing data 276 // 3. write new property data to the xattr (webdav_xattr_proppatch_finish) 277 278 // TODO: proppatch file lock (in-process userspace lock) 279 280 XAttrProppatch *xprop = pool_malloc(sn->pool, sizeof(XAttrProppatch)); 281 if(!xprop) { 282 return 1; 283 } 284 request->userdata = xprop; 285 286 // get existing property data 287 ssize_t xattr_data_len = 0; 288 char *xattr_data = xattr_get_alloc( 289 sn->pool, 290 (libxattr_malloc_func)pool_malloc, 291 (libxattr_free_func)pool_free, 292 path, 293 repo->xattr_name, 294 &xattr_data_len); 295 296 CxMap *pmap; 297 if(xattr_data) { 298 pmap = webdav_xattr_parse_data(a, xattr_data, xattr_data_len, path); 299 pool_free(sn->pool, xattr_data); 300 } else { 301 // empty map 302 pmap = cxHashMapCreate(a, CX_STORE_POINTERS, request->setcount + 8); 303 } 304 if(!pmap) { 305 return 1; 306 } 307 xprop->properties = pmap; 308 309 // remove properties 310 WebdavPListIterator i = webdav_plist_iterator(removeInOut); 311 WebdavPList *cur; 312 while(webdav_plist_iterator_next(&i, &cur)) { 313 WebdavProperty *prop = cur->property; 314 if(!prop->namespace || !prop->namespace->prefix) { 315 // not sure if this check is required 316 log_ereport(LOG_WARN, "webdav_xattr_proppatch_do: property %s has missing namespace infos", prop->name); 317 continue; 318 } 319 320 CxHashKey key = webdav_property_key_a( 321 a, 322 (const char*)prop->namespace->href, 323 (const char*)prop->name); 324 if(!key.data) { 325 cxMapDestroy(pmap); 326 return 1; 327 } 328 void *rmprop = cxMapRemoveAndGet(pmap, key); 329 cxFree(a, (void*)key.data); 330 331 // TODO: free rmprop 332 333 if(rmprop) { 334 webdav_plist_iterator_remove_current(&i); 335 } 336 } 337 338 i = webdav_plist_iterator(setInOut); 339 while(webdav_plist_iterator_next(&i, &cur)) { 340 WebdavProperty *prop = cur->property; 341 if(!prop->namespace || !prop->namespace->prefix) { 342 // not sure if this check is required 343 log_ereport(LOG_WARN, "webdav_xattr_proppatch_do: property %s has missing namespace infos", prop->name); 344 continue; 345 } 346 347 if(webdav_xattr_put_prop(pmap, prop)) { 348 cxMapDestroy(pmap); 349 return 1; 350 } 351 352 webdav_plist_iterator_remove_current(&i); 353 } 354 355 return 0; 356 } 357 358 int webdav_xattr_proppatch_finish( 359 WebdavProppatchRequest *request, 360 WebdavResource *response, 361 VFSFile *file, 362 WSBool commit) 363 { 364 Session *sn = request->sn; 365 Request *rq = request->rq; 366 CxAllocator *a = pool_allocator(sn->pool); 367 368 WebdavXAttrBackend *xdav = request->dav->instance; 369 WebdavXAttrRepository *repo = xdav->repo; 370 371 XAttrProppatch *xprop = request->userdata; 372 373 char *path = pblock_findkeyval(pb_key_path, rq->vars); 374 375 int ret = 0; 376 if(commit) { 377 cxmutstr new_data = webdav_xattr_serialze_map(a, xprop->properties); 378 if(new_data.ptr) { 379 if(xattr_set(path, repo->xattr_name, new_data.ptr, new_data.length)) { 380 ret = 1; 381 } 382 } else { 383 ret = 1; 384 } 385 } 386 387 return 0; 388 } 389 390 391 /* ----------------------- properties xattr data ----------------------- */ 392 393 static int get_next_line(cxstring data, size_t *pos, cxstring *line) { 394 size_t p = *pos; 395 cxstring str = cx_strsubs(data, p); 396 size_t i; 397 int skip = 0; 398 for(i=0;i<str.length;i++) { 399 if(str.ptr[i] == '\n') { 400 skip = 1; 401 break; 402 } 403 } 404 p += i; 405 *pos = p + skip; 406 *line = cx_strsubsl(str, 0, i); 407 return i > 0 ? TRUE : FALSE; 408 } 409 410 static int webdav_xattr_parse_elm(cxstring line, cxstring *name, cxstring *prefix, cxstring *xmlns, cxstring *lang) { 411 cxstring s_xmlns = CX_STR("xmlns:"); 412 413 // check if line starts with 'xmlns:' 414 if(!cx_strprefix(line, s_xmlns)) { 415 return 1; 416 } 417 line.ptr += 6; 418 line.length -= 6; 419 420 421 // format: <prefix>="<href>" 422 // find '=' 423 size_t i; 424 size_t token_end = 0; 425 for(i=0;i<line.length;i++) { 426 if(line.ptr[i] == '=') { 427 token_end = i; 428 break; 429 } 430 } 431 if(token_end == 0) { 432 return 1; 433 } 434 *prefix = cx_strn(line.ptr, token_end); 435 436 // make sure the line length is big enough 437 if(token_end + 4 > line.length) { 438 return 1; 439 } 440 if(line.ptr[token_end + 1] != '\"') { 441 return 1; 442 } 443 line.ptr += token_end + 2; 444 line.length -= token_end + 2; 445 446 // get <href> 447 int escape = 0; 448 token_end = 0; 449 for(i=0;i<line.length;i++) { 450 if(line.ptr[i] == '\\') { 451 escape = 1; 452 continue; 453 } else if(!escape && line.ptr[i] == '\"') { 454 token_end = i; 455 break; 456 } 457 escape = 0; 458 } 459 if(token_end == 0) { 460 return 1; 461 } 462 *xmlns = cx_strn(line.ptr, token_end); 463 464 // check length 465 if(token_end + 2 > line.length) { 466 return 1; 467 } 468 line.ptr += token_end + 2; 469 line.length -= token_end + 2; 470 *name = cx_strtrim(line); 471 472 if(name->length == 0) { 473 return 1; 474 } 475 if(prefix->length == 0) { 476 return 1; 477 } 478 if(xmlns->length == 0) { 479 return 1; 480 } 481 482 return 0; 483 } 484 485 static int webdav_xattr_parse_ns(cxstring line, cxstring *prefix, cxstring *href) { 486 size_t i = 0; 487 for(i=1;i<line.length;i++) { 488 if(line.ptr[i] == ':') { 489 break; 490 } 491 } 492 if(i == 0 || i+1 >= line.length) { 493 // error: ':' not found or ':' is the last character 494 return 1; 495 } 496 497 cxstring pre; 498 pre.ptr = line.ptr; 499 pre.length = i; 500 *prefix = pre; 501 502 *href = cx_strsubs(line, i+1); 503 504 return 0; 505 } 506 507 int webdav_xattr_put_prop(CxMap *pmap, WebdavProperty *prop) { 508 CxHashKey key = webdav_property_key_a( 509 pmap->allocator, 510 (const char*)prop->namespace->href, 511 (const char*)prop->name); 512 if(!key.data) { 513 return 1; 514 } 515 int ret = cxMapPut(pmap, key, prop); 516 cxFree(pmap->allocator, (void*)key.data); 517 return ret; 518 } 519 520 CxMap* webdav_xattr_parse_data(CxAllocator *a, const char *data, size_t len, const char *path) { 521 CxMap *pmap = cxHashMapCreate(a, CX_STORE_POINTERS, 32); 522 if(!pmap) { 523 return NULL; 524 } 525 cxstring dat = cx_strn(data, len); 526 527 cxstring s_elm = CX_STR("prop "); 528 cxstring s_ns = CX_STR("ns "); 529 cxstring s_data = CX_STR("data "); 530 531 WebdavProperty *prop = NULL; 532 WebdavNSList *ns_begin = NULL; 533 WebdavNSList *ns_end = NULL; 534 535 int error = 0; 536 537 size_t elmno = 0; 538 cxstring line; 539 size_t pos = 0; 540 while(get_next_line(dat, &pos, &line)) { 541 if(cx_strprefix(line, s_elm)) { 542 if(prop) { 543 if(webdav_xattr_put_prop(pmap, prop)) { 544 error = 1; 545 break; 546 } 547 } 548 549 line = cx_strsubs(line, 5); 550 551 // get prop name, namespace and lang 552 cxstring name; 553 cxstring prefix; 554 cxstring xmlns; 555 cxstring lang; 556 if(webdav_xattr_parse_elm(line, &name, &prefix, &xmlns, &lang)) { 557 log_ereport( 558 LOG_FAILURE, 559 "webdav xattr backend: file %s: invalid xattr format[%d]: cannot parse elm line", 560 path, 561 elmno); 562 error = 1; 563 break; 564 } 565 566 // create property 567 prop = cxMalloc(a, sizeof(WebdavProperty)); 568 if(!prop) { 569 error = 1; 570 break; 571 } 572 ZERO(prop, sizeof(WebdavProperty)); 573 ns_begin = NULL; 574 ns_end = NULL; 575 576 WSNamespace *ns = cxMalloc(a, sizeof(WSNamespace)); 577 if(!ns) { 578 error = 1; 579 break; 580 } 581 ZERO(ns, sizeof(WSNamespace)); 582 583 char *name_str = cx_strdup_a(a, name).ptr; 584 char *prefix_str = cx_strdup_a(a, prefix).ptr; 585 char *xmlns_str = cx_strdup_a(a, xmlns).ptr; 586 if(!(name_str && prefix_str && xmlns_str)) { 587 error = 1; 588 break; 589 } 590 591 ns->prefix = (const xmlChar*)prefix_str; 592 ns->href = (const xmlChar*)xmlns_str; 593 594 prop->name = name_str; 595 prop->namespace = ns; 596 597 elmno++; 598 } else if(prop) { 599 if(cx_strprefix(line, s_ns)) { 600 line = cx_strsubs(line, 3); 601 602 cxstring prefix; 603 cxstring href; 604 if(webdav_xattr_parse_ns(line, &prefix, &href)) { 605 log_ereport( 606 LOG_FAILURE, 607 "webdav xattr backend: file %s: invalid xattr format[%d]: cannot parse ns", 608 path, 609 elmno); 610 error = 1; 611 break; 612 } 613 614 WSNamespace *ns_decl = cxMalloc(a, sizeof(WSNamespace)); 615 if(!ns_decl) { 616 error = 1; 617 break; 618 } 619 ZERO(ns_decl, sizeof(WSNamespace)); 620 621 ns_decl->prefix = (const xmlChar*)cx_strdup_a(a, prefix).ptr; 622 ns_decl->href = (const xmlChar*)cx_strdup_a(a, href).ptr; 623 if(!(ns_decl->prefix && ns_decl->href)) { 624 error = 1; 625 break; 626 } 627 628 if(webdav_nslist_add(a->data, &ns_begin, &ns_end, ns_decl)) { 629 error = 1; 630 break; 631 } 632 } else if(cx_strprefix(line, s_data)) { 633 line = cx_strsubs(line, 5); 634 635 // util_strtoint just works here, because the line ends with \n 636 // the xattr content data is also garanteed to be 0-terminated 637 int64_t data_len = 0; 638 if(!util_strtoint(line.ptr, &data_len)) { 639 log_ereport( 640 LOG_FAILURE, 641 "webdav xattr backend: file %s: invalid xattr format[%d]: number expected after data", 642 path, 643 elmno); 644 error = 1; 645 break; 646 } 647 648 // get data 649 if(data_len > 0) { 650 if(data_len > dat.length - pos) { 651 log_ereport( 652 LOG_FAILURE, 653 "webdav xattr backend: file %s: invalid data length %" PRId64 " in prop %d", 654 path, 655 data_len, 656 elmno); 657 error = 1; 658 break; 659 } 660 661 cxstring propdata; 662 propdata.ptr = dat.ptr + pos; 663 propdata.length = data_len; 664 pos += data_len; 665 666 cxmutstr propdata_cp = cx_strdup_a(a, propdata); 667 if(!propdata_cp.ptr) { 668 error = 1; 669 break; 670 } 671 prop->vtype = WS_VALUE_XML_DATA; 672 prop->value.data.namespaces = ns_begin; 673 prop->value.data.data = propdata_cp.ptr; 674 prop->value.data.length = propdata_cp.length; 675 676 if(pos < dat.length && dat.ptr[pos] == '\n') { 677 pos++; 678 } 679 } 680 } else { 681 log_ereport( 682 LOG_FAILURE, 683 "webdav xattr backend: file %s: invalid xattr format[%d]: unknown element", 684 path, 685 elmno); 686 error = 1; 687 break; 688 } 689 } 690 } 691 692 // add last property 693 if(prop) { 694 if(webdav_xattr_put_prop(pmap, prop)) { 695 error = 1; 696 } 697 } 698 699 if(error) { 700 // TODO: free pmap content 701 cxMapDestroy(pmap); 702 pmap = NULL; 703 } 704 705 return pmap; 706 } 707 708 cxmutstr webdav_xattr_serialze_map(CxAllocator *a, CxMap *pmap) { 709 pool_handle_t *pool = a->data; 710 711 CxBuffer buf; 712 if(cxBufferInit(&buf, NULL, 8192, a, CX_BUFFER_AUTO_EXTEND)) { 713 return (cxmutstr){NULL,0}; 714 } 715 716 CxIterator i = cxMapIteratorValues(pmap); 717 cx_foreach(WebdavProperty*, prop, i) { 718 WSXmlData *property_value = NULL; 719 if(prop->vtype == WS_VALUE_XML_NODE) { 720 property_value = wsxml_node2data(pool, prop->value.node); 721 } else if(prop->vtype == WS_VALUE_XML_DATA) { 722 property_value = &prop->value.data; 723 } 724 725 int ret = cx_bprintf(&buf, "prop xmlns:%s=\"%s\" %s\n", prop->namespace->prefix, prop->namespace->href, prop->name); 726 if(ret <= 0) { 727 pool_free(pool, buf.space); 728 return (cxmutstr){NULL,0}; 729 } 730 if(property_value) { 731 WebdavNSList *ns = property_value->namespaces; 732 while(ns) { 733 ret = cx_bprintf(&buf, "ns %s:%s\n", ns->namespace->prefix, ns->namespace->href); 734 if(ret <= 0) { 735 pool_free(pool, buf.space); 736 return (cxmutstr){NULL,0}; 737 } 738 ns = ns->next; 739 } 740 741 ret = cx_bprintf(&buf, "data %zu\n", property_value->length); 742 if(ret <= 0) { 743 pool_free(pool, buf.space); 744 return (cxmutstr){NULL,0}; 745 } 746 747 cxBufferWrite(property_value->data, 1, property_value->length, &buf); 748 if(cxBufferPut(&buf, '\n') < 0) { 749 pool_free(pool, buf.space); 750 return (cxmutstr){NULL,0}; 751 } 752 } 753 } 754 755 return (cxmutstr){buf.space, buf.size}; 756 } 757 758