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 <assert.h> 30 #include <stdio.h> 31 #include <stdlib.h> 32 #include <string.h> 33 #include <stdbool.h> 34 #include <errno.h> 35 #include <time.h> 36 #include <sys/types.h> 37 #ifndef _WIN32 38 #include <sys/wait.h> 39 #include <unistd.h> 40 #endif 41 #include <cx/string.h> 42 #include <cx/utils.h> 43 #include <cx/printf.h> 44 #include <cx/hash_map.h> 45 #include <cx/linked_list.h> 46 47 48 #include <libidav/utils.h> 49 #include <libidav/crypto.h> 50 #include <libidav/session.h> 51 #include <libidav/xml.h> 52 #include "config.h" 53 #include "error.h" 54 #include "assistant.h" 55 #include "system.h" 56 #include "pwd.h" 57 #include "finfo.h" 58 #include "main.h" 59 60 static DavContext *ctx; 61 62 static int printxmlerror = 1; 63 static void xmlerrorfnc(void * c, const char * msg, ... ) { 64 if(printxmlerror) { 65 va_list ap; 66 va_start(ap, msg); 67 vfprintf(stderr, msg, ap); 68 va_end(ap); 69 } 70 } 71 72 //define DO_THE_TEST 73 //include <libidav/davqlparser.h> 74 //include <libidav/davqlexec.h> 75 //include "tags.h" 76 //include <libidav/resource.h> 77 78 void test(CmdArgs *a) { 79 80 } 81 82 int dav_main(int argc, char **argv); 83 84 #ifdef _WIN32 85 86 #define strcasecmp _stricmp 87 88 static char* wchar2utf8(const wchar_t *wstr, size_t wlen) { 89 size_t maxlen = wlen * 4; 90 char *ret = malloc(maxlen + 1); 91 int ret_len = WideCharToMultiByte( 92 CP_UTF8, 93 0, 94 wstr, 95 wlen, 96 ret, 97 maxlen, 98 NULL, 99 NULL); 100 ret[ret_len] = 0; 101 return ret; 102 } 103 104 int wmain(int argc, wchar_t **argv) { 105 char **argv_utf8 = calloc(argc, sizeof(char*)); 106 for(int i=0;i<argc;i++) { 107 argv_utf8[i] = wchar2utf8(argv[i], wcslen(argv[i])); 108 } 109 110 int ret = dav_main(argc, argv_utf8); 111 112 113 for(int i=0;i<argc;i++) { 114 free(argv_utf8[i]); 115 } 116 free(argv_utf8); 117 118 return ret; 119 } 120 #else 121 int main(int argc, char **argv) { 122 return dav_main(argc, argv); 123 } 124 #endif 125 126 127 int dav_main(int argc, char **argv) { 128 if(argc < 2) { 129 fprintf(stderr, "Missing command\n"); 130 print_usage(argv[0]); 131 return -1; 132 } 133 134 putenv("LC_TIME=C"); 135 136 char *cmd = argv[1]; 137 CmdArgs *args = cmd_parse_args(argc - 2, argv + 2); 138 if(!args) { 139 print_usage(argv[0]); 140 return -1; 141 } 142 143 sys_init(); 144 xmlGenericErrorFunc fnc = xmlerrorfnc; 145 initGenericErrorDefaultFunc(&fnc); 146 ctx = dav_context_new(); 147 dav_add_namespace(ctx, "apache", "http://apache.org/dav/props/"); 148 int cfgret = load_config(ctx); 149 int ret = EXIT_FAILURE; 150 printxmlerror = 0; 151 #ifdef DO_THE_TEST 152 test(args); 153 return 0; 154 #endif 155 if(!strcmp(cmd, "check") || !strcmp(cmd, "check-config")) { 156 if(!cfgret) { 157 fprintf(stdout, "Configuration OK.\n"); 158 ret = EXIT_SUCCESS; 159 } else { 160 /* no output, the warnings are written by load_config */ 161 ret = EXIT_FAILURE; 162 } 163 } else if(!cfgret) { 164 if(!strcasecmp(cmd, "list") || !strcasecmp(cmd, "ls")) { 165 ret = cmd_list(args); 166 } else if(!strcasecmp(cmd, "get")) { 167 ret = cmd_get(args, FALSE); 168 } else if(!strcasecmp(cmd, "cat")) { 169 cxMapPut(args->options, cx_hash_key_str("output"), "-"); 170 ret = cmd_get(args, FALSE); 171 } else if(!strcasecmp(cmd, "edit")) { 172 ret = cmd_edit(args); 173 } else if(!strcasecmp(cmd, "put")) { 174 ret = cmd_put(args, FALSE); 175 } else if( 176 !strcasecmp(cmd, "remove") || 177 !strcasecmp(cmd, "rm") || 178 !strcasecmp(cmd, "delete")) 179 { 180 ret = cmd_remove(args); 181 } else if(!strcasecmp(cmd, "mkdir") || !strcasecmp(cmd, "mkcol")) { 182 ret = cmd_mkdir(args); 183 } else if(!strcasecmp(cmd, "copy") || !strcasecmp(cmd, "cp")) { 184 ret = cmd_move(args, true); 185 } else if(!strcasecmp(cmd, "move") || !strcasecmp(cmd, "mv")) { 186 ret = cmd_move(args, false); 187 } else if(!strcasecmp(cmd, "rename")) { 188 ret = cmd_rename(args); 189 } else if(!strcasecmp(cmd, "export")) { 190 ret = cmd_get(args, TRUE); 191 } else if(!strcasecmp(cmd, "import")) { 192 ret = cmd_put(args, TRUE); 193 } else if(!strcasecmp(cmd, "date")) { 194 ret = cmd_date(args); 195 } else if(!strcasecmp(cmd, "set-property")) { 196 ret = cmd_set_property(args); 197 } else if(!strcasecmp(cmd, "get-property")) { 198 ret = cmd_get_property(args); 199 } else if(!strcasecmp(cmd, "remove-property")) { 200 ret = cmd_remove_property(args); 201 } else if(!strcasecmp(cmd, "lock")) { 202 ret = cmd_lock(args); 203 } else if(!strcasecmp(cmd, "unlock")) { 204 ret = cmd_unlock(args); 205 } else if(!strcasecmp(cmd, "info")) { 206 ret = cmd_info(args); 207 } else if(!strcasecmp(cmd, "checkout")) { 208 ret = cmd_checkout(args); 209 } else if(!strcasecmp(cmd, "checkin")) { 210 ret = cmd_checkin(args); 211 } else if(!strcasecmp(cmd, "uncheckout")) { 212 ret = cmd_uncheckout(args); 213 } else if(!strcasecmp(cmd, "versioncontrol")) { 214 ret = cmd_versioncontrol(args); 215 } else if(!strcasecmp(cmd, "list-versions") || !strcasecmp(cmd, "lsv")) { 216 ret = cmd_list_versions(args); 217 } else if(!strcasecmp(cmd, "add-repository") 218 || !strcasecmp(cmd, "add-repo")) { 219 ret = cmd_add_repository(args); 220 } else if(!strcasecmp(cmd, "remove-repository") 221 || !strcasecmp(cmd, "remove-repo") 222 || !strcasecmp(cmd, "rm-repo")) { 223 ret = cmd_remove_repository(args); 224 } else if(!strcasecmp(cmd, "list-repositories") 225 || !strcasecmp(cmd, "list-repos")) { 226 ret = cmd_list_repositories(); 227 } else if(!strcasecmp(cmd, "repository-url") 228 || !strcasecmp(cmd, "repo-url")) { 229 ret = cmd_repository_url(args); 230 } else if(!strcasecmp(cmd, "add-user")) { 231 ret = cmd_add_user(args); 232 } else if(!strcasecmp(cmd, "list-users")) { 233 ret = cmd_list_users(args); 234 } else if(!strcasecmp(cmd, "remove-user")) { 235 ret = cmd_remove_user(args); 236 } else if(!strcasecmp(cmd, "edit-user")) { 237 ret = cmd_edit_user(args); 238 } else if(!strcasecmp(cmd, "set-master-password") || !strcasecmp(cmd, "set-master-pw")) { 239 ret = cmd_set_master_password(args); 240 } else if(!strcasecmp(cmd, "version") || !strcasecmp(cmd, "-version") 241 || !strcasecmp(cmd, "--version")) { 242 fprintf(stderr, "dav %s\n", DAV_VERSION); 243 } else if(!strcasecmp(cmd, "complete")) { 244 ret = cmd_complete(args); 245 } else { 246 print_usage(argv[0]); 247 } 248 } 249 250 dav_context_destroy(ctx); 251 cmd_args_free(args); 252 free_config(); 253 xmlCleanupParser(); 254 curl_global_cleanup(); 255 sys_uninit(); 256 257 return ret; 258 } 259 260 static char *cmdusageinfo[] = { 261 "list [-altdepcR] [-u <date>] <url>", 262 "get [-pcRK] [-o <file>] [-u <date>] [-V <version>] <url>", 263 "put [-pcR] [-k <key>] [-L <lock>] <url> <file...>", 264 "edit [-pc] [-k <key>] [-V <version>] [-L <lock>] <url>", 265 "mkdir [-pc] [-k <key>] [-L <lock>] <url> [file...]", 266 "remove [-pc] [-L <lock>] <url> [file...]", 267 "copy [-pcO] [-L <lock>] <url> <url>", 268 "move [-pcO] [-L <lock>] <url> <url>", 269 "rename [-pcO] [-L <lock>] <url> <name>", 270 "export [-pc] [-o <file>] [-u <date>] <url>", 271 "import [-pc] [-k <key>] [-L <lock>] <url> <file>", 272 "get-property [-pcx] [-V <version>] [-n <uri>] <url> <property>", 273 "set-property [-pcx] [-L <lock>] [-n <uri>] <url> <property> [value]", 274 "remove-property [-pc] [-n <uri>] <url> <property>", 275 "lock [-pc] [-T timeout] <url>", 276 "unlock [-pc] [-L <lock>] <url>", 277 "info [-pc] [-V <version>] <url>", 278 "date [url]", 279 NULL 280 }; 281 282 char* find_usage_str(const char *cmd) { 283 cxstring c = cx_str(cmd); 284 for(int i=0;;i++) { 285 char *str = cmdusageinfo[i]; 286 if(!str) { 287 break; 288 } 289 cxstring u = cx_str(str); 290 if(cx_strprefix(u, c)) { 291 return str; 292 } 293 } 294 return NULL; 295 } 296 297 void print_usage(char *cmd) { 298 fprintf(stderr, "Usage: %s command [options] arguments...\n\n", cmd); 299 300 fprintf(stderr, "Commands:\n"); 301 for(int i=0;;i++) { 302 char *str = cmdusageinfo[i]; 303 if(!str) { 304 break; 305 } 306 fprintf(stderr, " %s\n", str); 307 } 308 fprintf(stderr, "Options:\n"); 309 fprintf(stderr, 310 " -k <key> Key to use for encryption\n"); 311 fprintf(stderr, " -p Don''t encrypt or decrypt files\n"); 312 fprintf(stderr, " -c Enable full encryption\n"); 313 fprintf(stderr, 314 " -R " 315 "Recursively do the operation for all children\n"); 316 fprintf(stderr, " -K Keep already present files\n"); 317 fprintf(stderr, " -o <file> Write output to file (use ''-'' for stdout)\n"); 318 fprintf( 319 stderr, 320 " -u <date> " 321 "Get resources which are modified since the specified date\n"); 322 fprintf(stderr, " -V <version> Download a specific version of a resource\n"); 323 fprintf(stderr, " -a show all files\n"); 324 fprintf(stderr, " -l print resources in long list format\n"); 325 fprintf(stderr, " -t print content type\n"); 326 fprintf(stderr, " -d order by last modified date\n"); 327 fprintf(stderr, " -e show extended flags\n"); 328 fprintf(stderr, " -O override resources\n"); 329 fprintf(stderr, " -L <lock> specificy lock token\n"); 330 fprintf(stderr, " -T <sec> timeout in seconds\n"); 331 fprintf(stderr, " -n <uri> specify namespace uri\n"); 332 fprintf(stderr, " -x xml property content\n"); 333 fprintf(stderr, " -N disable authentication prompt (all commands)\n"); 334 fprintf(stderr, " -i disable cert verification (all commands)\n"); 335 fprintf(stderr, " -v verbose output (all commands)\n"); 336 fprintf(stderr, "\n"); 337 fprintf(stderr, "Advanced commands:\n"); 338 fprintf(stderr, " versioncontrol list-versions checkout checkin uncheckout\n\n"); 339 fprintf(stderr, "Config commands:\n"); 340 fprintf(stderr, " add-repository remove-repository list-repositories repository-url\n"); 341 fprintf(stderr, " add-user remove-user edit-user list-users set-master-password\n"); 342 fprintf(stderr, " check-config\n"); 343 fprintf(stderr, "\n"); 344 fprintf(stderr, 345 "Instead of an url you can pass a repository name " 346 "with an optional path:\n"); 347 fprintf(stderr, " <repository>/path/\n"); 348 fprintf(stderr, "\n"); 349 } 350 351 352 353 static int set_session_config(DavSession *sn, CmdArgs *a) { 354 char *plain = cmd_getoption(a, "plain"); 355 char *crypt = cmd_getoption(a, "crypt"); 356 357 if(plain && crypt) { 358 fprintf(stderr, "Error: -p and -c option set\n"); 359 return 1; 360 } 361 362 if (plain) { 363 sn->flags &= ~DAV_SESSION_FULL_ENCRYPTION; 364 } else if(crypt) { 365 sn->flags |= DAV_SESSION_FULL_ENCRYPTION; 366 } 367 368 if (cmd_getoption(a, "verbose")) { 369 curl_easy_setopt(sn->handle, CURLOPT_VERBOSE, 1L); 370 curl_easy_setopt(sn->handle, CURLOPT_STDERR, stderr); 371 sn->logfunc = dav_verbose_log; 372 } 373 374 return 0; 375 } 376 377 static void set_session_lock(DavSession *sn, CmdArgs *a) { 378 char *locktoken = cmd_getoption(a, "lock"); 379 if(locktoken) { 380 DavLock *lock = dav_create_lock(sn, locktoken, NULL); 381 dav_add_collection_lock(sn, "/", lock); 382 } 383 } 384 385 386 int update_progress(DavResource *res, int64_t total, int64_t now, Progress *p) { 387 int ret = 0; 388 if(res != p->last_resource) { 389 p->cur += p->last_res_total - p->last_res_cur; 390 ret = 1; 391 } else { 392 p->cur += now - p->last_res_cur; 393 } 394 395 p->last_resource = res; 396 p->last_res_cur = now; 397 p->last_res_total = total; 398 399 return ret; 400 } 401 402 void download_progress(DavResource *res, int64_t total, int64_t now, void *data) { 403 Progress *p = data; 404 int newres = update_progress(res, total, now, p); 405 406 time_t newts = time(NULL); 407 if((p->ts != newts)) { 408 fprintf(p->out, "[%s]: %" PRId64 "k/%" PRId64 "k total: %" PRId64 "M/%" PRId64 "M\n", res->name, now/1024, total/1024, p->cur/(1024*1024), p->total/(1024*1024)); 409 fflush(p->out); 410 } 411 p->ts = newts; 412 } 413 414 415 #define LIST_QUERY_ORDER_BY_NAME "select `idav:crypto-name`,`idav:crypto-key`,D:lockdiscovery,apache:executable from %s with depth = %d where lastmodified > %t order by iscollection desc, name" 416 #define LIST_QUERY_ORDER_BY_DATE "select `idav:crypto-name`,`idav:crypto-key`,D:lockdiscovery,apache:executable from %s with depth = %d where lastmodified > %t order by iscollection desc, lastmodified desc" 417 418 int cmd_list(CmdArgs *a) { 419 if(a->argc != 1) { 420 fprintf(stderr, "Too %s arguments\n", a->argc < 1 ? "few":"many"); 421 fprintf(stderr, "Usage: dav %s\n", find_usage_str("list")); 422 return -1; 423 } 424 425 char *url = a->argv[0]; 426 char *path = NULL; 427 DavCfgRepository *repo = dav_config_url2repo(get_config(), url, &path); 428 DavSession *sn = connect_to_repo(ctx, repo, path, request_auth, a); 429 430 if(set_session_config(sn, a)) { 431 return -1; 432 } 433 434 char *update = cmd_getoption(a, "update"); 435 char *date = cmd_getoption(a, "date"); 436 time_t t = -1; 437 if(update) { 438 t = util_parse_lastmodified(update); 439 } 440 441 int depth = cmd_getoption(a, "recursive") ? -1 : 1; 442 int ret = 0; 443 444 DavResource *ls = dav_query( 445 sn, 446 date ? LIST_QUERY_ORDER_BY_DATE : LIST_QUERY_ORDER_BY_NAME, 447 path, 448 depth, 449 t); 450 if(ls) { 451 // parameters 452 void (*print_func)(DavResource*, char *, CmdArgs *); 453 if(cmd_getoption(a, "list") || cmd_getoption(a, "extended")) { 454 print_func = ls_print_list_elm; 455 } else { 456 print_func = ls_print_elm; 457 } 458 459 DavResource *child = ls->children; 460 while(child) { 461 print_func(child, path, a); 462 child = child->next; 463 } 464 } else { 465 print_resource_error(sn, path); 466 ret = -1; 467 } 468 469 free(path); 470 //free(base); 471 472 dav_session_destroy(sn); 473 474 return ret; 475 } 476 477 static char* ls_name(char *parent, char *path, int *len) { 478 if(parent) { 479 path += strlen(parent); 480 } 481 if(path[0] == '/') { 482 path++; 483 } 484 int pathlen = strlen(path); 485 if(path[pathlen-1] == '/') { 486 pathlen--; 487 } 488 *len = pathlen; 489 return path; 490 } 491 492 void ls_print_list_elm(DavResource *res, char *parent, CmdArgs *a) { 493 int recursive = cmd_getoption(a, "recursive") ? 1 : 0; 494 int show_all = cmd_getoption(a, "all") ? 1 : 0; 495 if(res->name[0] == '.' && !show_all) { 496 return; 497 } 498 499 char flags[16]; 500 memset(flags, '-', 15); 501 502 int type_width = 0; 503 char *type = res->contenttype; 504 505 if(res->iscollection) { 506 flags[0] = 'd'; 507 type = ""; 508 } 509 char *keyprop = dav_get_string_property_ns( 510 res, 511 DAV_NS, 512 "crypto-key"); 513 if(keyprop) { 514 flags[1] = 'c'; 515 } 516 517 if(cmd_getoption(a, "extended")) { 518 flags[6] = '\0'; 519 if(dav_get_string_property(res, "D:lockdiscovery")) { 520 flags[2] = 'l'; 521 } 522 char *executable = dav_get_string_property_ns( 523 res, 524 "http://apache.org/dav/props/", 525 "executable"); 526 if(executable && util_getboolean(executable)) { 527 flags[3] = 'x'; 528 } 529 } else { 530 flags[2] = '\0'; 531 } 532 533 if(cmd_getoption(a, "type")) { 534 type_width = 20; 535 } 536 if(type == NULL || type_width == 0) { 537 type = ""; 538 } 539 540 char *date = util_date_str(res->lastmodified); 541 char *size = util_size_str(res->iscollection, res->contentlength); 542 int namelen = strlen(res->name); 543 char *name = recursive ? ls_name(parent, res->path, &namelen) : res->name; 544 545 //char *name = recursive ? res->path+1 : res->name; 546 printf( 547 "%s %*s %10s %12s %.*s\n", 548 flags, 549 type_width, type, 550 size, 551 date, 552 namelen, 553 name); 554 free(date); 555 free(size); 556 557 if(recursive) { 558 DavResource *child = res->children; 559 while(child) { 560 //ls_print_list_elm(child, a); 561 if(child->name[0] != '.' || show_all) { 562 ls_print_list_elm(child, parent, a); 563 } 564 child = child->next; 565 } 566 } 567 } 568 569 void ls_print_elm(DavResource *res, char *parent, CmdArgs *a) { 570 int recursive = cmd_getoption(a, "recursive") ? 1 : 0; 571 int show_all = cmd_getoption(a, "all") ? 1 : 0; 572 if(res->name[0] == '.' && !show_all) { 573 return; 574 } 575 576 int namelen = strlen(res->name); 577 char *name = recursive ? ls_name(parent, res->path, &namelen) : res->name; 578 printf("%.*s\n", namelen, name); 579 if(recursive) { 580 DavResource *child = res->children; 581 while(child) { 582 ls_print_elm(child, parent, a); 583 child = child->next; 584 } 585 } 586 } 587 588 static void free_getres(void *r) { 589 GetResource *getres = r; 590 free(getres->path); 591 free(getres); 592 } 593 594 int cmd_get(CmdArgs *a, DavBool export) { 595 if(a->argc != 1) { 596 // TODO: change this, when get supports retrieval of multiple files 597 fprintf(stderr, "Too %s arguments\n", a->argc < 1 ? "few":"many"); 598 fprintf(stderr, "Usage: dav %s\n", find_usage_str("get")); 599 return -1; 600 } 601 if(export) { 602 cxMapPut(a->options, cx_hash_key_str("recursive"), ""); 603 } 604 605 char *url = a->argv[0]; 606 char *path = NULL; 607 DavCfgRepository *repo = dav_config_url2repo(get_config(), url, &path); 608 DavSession *sn = connect_to_repo(ctx, repo, path, request_auth, a); 609 610 if(set_session_config(sn, a)) { 611 return -1; 612 } 613 614 char *progressfile = cmd_getoption(a, "progressfile"); 615 Progress pdata; 616 memset(&pdata, 0, sizeof(Progress)); 617 if(progressfile) { 618 if(!strcmp(progressfile, "-")) { 619 pdata.out = stdout; 620 pdata.isstdout = 1; 621 } else { 622 pdata.out = fopen(progressfile, "w"); 623 } 624 if(pdata.out) { 625 dav_session_set_progresscallback(sn, download_progress, NULL, &pdata); 626 } 627 } 628 629 char *update = cmd_getoption(a, "update"); 630 time_t t = -1; 631 if(update) { 632 t = util_parse_lastmodified(update); 633 if (t == 0) { 634 fprintf(stderr, 635 "Invalid date format. Possible formats are:\n" 636 " RFC-1123 - example: Thu, 29 Nov 2012 21:35:35 GMT\n" 637 " RFC-3339 - example: 2012-11-29T21:35:35Z\n"); 638 return -1; 639 } 640 } 641 642 int recursive = cmd_getoption(a, "recursive") ? 1 : 0; 643 char *version = cmd_getoption(a, "version"); 644 645 if(recursive && version) { 646 fprintf(stderr, "-V option can only be used without -R option\n"); 647 return -1; 648 } 649 650 DavResource *res; 651 652 int depth = recursive ? -1 : 1; 653 res = dav_query( 654 sn, 655 "select - from %s with depth = %d where iscollection or lastmodified > %t", 656 path, 657 depth, 658 t); 659 if(!res) { 660 print_resource_error(sn, path); 661 return -1; 662 } 663 if(!recursive && res->iscollection) { 664 fprintf(stderr, "Resource %s is a collection.\n", res->path); 665 fprintf(stderr, "Use the -R option to download collections.\n"); 666 return -1; 667 } 668 669 if(version) { 670 DavResource *vres = find_version(res, version); 671 if(!vres) { 672 fprintf(stderr, "Cannot find version ''%s'' for resource.\n", version); 673 return -1; 674 } 675 dav_resource_free_all(res); 676 res = vres; 677 } 678 679 /* 680 * determine the output file 681 * use stdout if the output file is - 682 */ 683 char *outfile = cmd_getoption(a, "output"); 684 char *basepath = outfile; 685 if(!outfile) { 686 if(res->iscollection) { 687 basepath = ""; 688 } else { 689 basepath = res->name; 690 } 691 if(export) { 692 outfile = "-"; 693 } 694 } else if(export) { 695 basepath = ""; 696 } else if(res->iscollection && !strcmp(outfile, "-")) { 697 fprintf( 698 stderr, 699 "Cannot write output to stdout " 700 "if the requested resource is a collection.\n"); 701 return -1; 702 } 703 704 // get list of resources 705 CxList *reslist = cxLinkedListCreateSimple(CX_STORE_POINTERS); 706 reslist->simple_destructor = (cx_destructor_func)free_getres; 707 uint64_t totalsize = 0; 708 uint64_t rescount = 0; 709 710 GetResource *getres = malloc(sizeof(GetResource)); 711 getres->res = res; 712 getres->path = strdup(basepath); 713 714 char *structure = cmd_getoption(a, "structure"); 715 716 // iterate over resource tree 717 CxList *stack = cxLinkedListCreateSimple(CX_STORE_POINTERS); 718 cxListInsert(stack, 0, getres); 719 while(stack->size > 0) { 720 GetResource *g = cxListAt(stack, 0); 721 cxListRemove(stack, 0); 722 723 if(g->res->iscollection) { 724 DavResource *child = g->res->children; 725 while(child) { 726 // add resource to stack 727 size_t pathlen = strlen(g->path); 728 GetResource *newres = malloc(sizeof(GetResource)); 729 newres->res = child; 730 newres->path = pathlen > 0 ? 731 util_concat_path(g->path, child->name) : strdup(child->name); 732 733 cxListInsert(stack, 0, newres); 734 735 child = child->next; 736 } 737 } else { 738 if(structure) { 739 // download only directory structure 740 // this is a hidden feature and will be replaced in the future 741 continue; // skip non-collection resource 742 } 743 totalsize += g->res->contentlength; 744 rescount++; 745 } 746 747 if(strlen(g->path) == 0) { 748 free_getres(g); 749 } else { 750 cxListAdd(reslist, g); 751 } 752 } 753 cxListDestroy(stack); 754 755 // download resources 756 pdata.total = totalsize; 757 758 int ret; 759 getfunc get; 760 TarOutputStream *tout = NULL; 761 if(export) { 762 get = (getfunc)resource2tar; 763 FILE *tarfile = strcmp(outfile, "-") ? fopen(outfile, "wb") : stdout; 764 if(!tarfile) { 765 perror("Cannot open tar output file"); 766 return -1; 767 } 768 tout = tar_open(tarfile); 769 } else { 770 get = get_resource; 771 } 772 CxIterator i = cxListIterator(reslist); 773 cx_foreach(GetResource *, getres, i) { 774 ret = get(repo, getres, a, tout); 775 if(ret) { 776 break; 777 } 778 } 779 if(export) { 780 // close tar stream 781 if(tar_close(tout)) { 782 fprintf(stderr, "tar stream broken\n"); 783 ret = -1; 784 } 785 } 786 787 cxListDestroy(reslist); 788 free(path); 789 790 if(pdata.out && !pdata.isstdout) { 791 fclose(pdata.out); 792 } 793 return ret; 794 } 795 796 static int file_seek(FILE *f, curl_off_t offset, int origin) { 797 int ret = fseek(f, offset, origin); 798 return ret == 0 ? CURL_SEEKFUNC_OK : CURL_SEEKFUNC_CANTSEEK; 799 } 800 801 static int check_encryption_key(CmdArgs *a, DavSession *sn) { 802 // override the session key if the -k option is specified 803 char *keyname = cmd_getoption(a, "key"); 804 if(keyname) { 805 DavKey *key = dav_context_get_key(ctx, keyname); 806 if(key) { 807 sn->key = key; 808 } else { 809 fprintf(stderr, "Key %s not found!\nAbort.\n", keyname); 810 return 1; 811 } 812 813 /* 814 * If a key is explicitly specified, we can safely assume that the user 815 * wants to encrypt. For security reasons we report an error, if no 816 * encryption is enabled. 817 */ 818 if(!DAV_IS_ENCRYPTED(sn)) { 819 fprintf(stderr, "A key has been explicitly specified, but no " 820 "encryption is requested.\n" 821 "You have the following options:\n" 822 " - pass ''-c'' as command line argument to request encryption\n" 823 " - activate encryption in the config.xml\n" 824 " - don''t use ''-k <key>'' " 825 "(warning: encryption will NOT happen)\n"); 826 return 1; 827 } 828 } 829 830 // if encryption is requested, but we still don't know the key, report error 831 if(DAV_IS_ENCRYPTED(sn) && !(sn->key)) { 832 fprintf(stderr, "Encryption has been requested, " 833 "but no default key is configured.\n" 834 "You may specify a custom key with the ''-k'' option.\n"); 835 return 1; 836 } 837 838 return 0; 839 } 840 841 int cmd_edit(CmdArgs *a) { 842 #ifdef _WIN32 843 fprintf(stderr, "This feature is not supported on your platform.\n"); 844 return -1; 845 #else 846 if(a->argc != 1) { 847 fprintf(stderr, "Too %s arguments\n", a->argc < 1 ? "few":"many"); 848 fprintf(stderr, "Usage: dav %s\n", find_usage_str("edit")); 849 return -1; 850 } 851 852 char *url = a->argv[0]; 853 char *path = NULL; 854 DavCfgRepository *repo = dav_config_url2repo(get_config(), url, &path); 855 DavSession *sn = connect_to_repo(ctx, repo, path, request_auth, a); 856 857 if(set_session_config(sn, a)) { 858 return -1; 859 } 860 set_session_lock(sn, a); 861 862 if(check_encryption_key(a, sn)) { 863 return -1; 864 } 865 866 char *version = cmd_getoption(a, "version"); 867 DavResource *res; 868 869 res = dav_resource_new(sn, path); 870 int fresh_resource = !dav_exists(res); 871 if(fresh_resource) { 872 // create the resource to check if the resource can be created 873 // we don't want the user to invest time in editing and fail afterwards 874 if(dav_create(res)) { 875 fprintf(stderr, "Resource does not exist and cannot be created.\n"); 876 return -1; 877 } 878 } else { 879 if(!res) { 880 print_resource_error(sn, path); 881 return -1; 882 } 883 if(res->iscollection) { 884 fprintf(stderr, "Resource %s is a collection " 885 "and cannot be opened in an editor.\n", res->path); 886 return -1; 887 } 888 889 if(version) { 890 DavResource *vres = find_version(res, version); 891 if(!vres) { 892 fprintf(stderr, "Cannot find version ''%s'' for resource.\n", version); 893 return -1; 894 } 895 dav_resource_free_all(res); 896 res = vres; 897 } 898 } 899 900 // get temp dir 901 char* envtmp = getenv("TMPDIR"); 902 char* outfile; 903 if(envtmp) { 904 size_t len = strlen(envtmp); 905 outfile = malloc(len+24); 906 memcpy(outfile, envtmp, len+1); 907 if(outfile[len-1] != '/') { 908 outfile[len] = '/'; 909 outfile[len+1] = 0; 910 } 911 } else { 912 outfile = malloc(24); 913 strncpy(outfile, "/tmp/", 24); 914 } 915 916 // check temp dir and fall back to $HOME 917 if(access(outfile, W_OK)) { 918 char* home = getenv("HOME"); 919 if(home) { 920 size_t len = strlen(home); 921 outfile = malloc(len+24); 922 memcpy(outfile, home, len+1); 923 if(outfile[len-1] != '/') { 924 outfile[len] = '/'; 925 outfile[len+1] = 0; 926 } 927 } else { 928 // fallback did not work, report last error from access() 929 perror("Cannot write to temporary location"); 930 free(outfile); 931 return -1; 932 } 933 } 934 935 // create temp file and open it 936 outfile = strncat(outfile, ".dav-edit-XXXXXX", 23); 937 int tmp_fd = mkstemp(outfile); 938 if(tmp_fd < 0) { 939 perror("Cannot open temporary file"); 940 return -1; 941 } 942 943 // get resource 944 if(!fresh_resource) { 945 FILE* tmp_stream = sys_fopen(outfile, "wb"); 946 if(!tmp_stream) { 947 perror("Cannot open temporary file"); 948 free(outfile); 949 close(tmp_fd); 950 return -1; 951 } 952 if(dav_get_content(res, tmp_stream, (dav_write_func)fwrite)) { 953 print_resource_error(sn, path); 954 free(outfile); 955 close(tmp_fd); 956 sys_unlink(outfile); 957 return -1; 958 } 959 fclose(tmp_stream); 960 } 961 962 // remember time s.t. we can later check if the file has changed 963 SYS_STAT tmp_stat; 964 if(sys_stat(outfile, &tmp_stat)) { 965 perror("Cannot stat temporary file"); 966 free(outfile); 967 close(tmp_fd); 968 sys_unlink(outfile); 969 return -1; 970 } 971 time_t dl_mtime = tmp_stat.st_mtime; 972 973 // open in editor 974 char* default_editor = "vi"; 975 char* editor = getenv("EDITOR"); 976 if(!editor) editor = default_editor; 977 char* viargs[3] = {editor, outfile, NULL}; 978 979 int ret = 0; 980 pid_t pid = fork(); 981 if(pid < 0) { 982 perror("Cannot create process for editor"); 983 ret = -1; 984 } else if(pid == 0) { 985 if(execvp(viargs[0], viargs)) { 986 perror("Opening the editor failed"); 987 return -1; 988 } 989 } else { 990 int status = -1; 991 ret = waitpid(pid, &status, 0); 992 if(ret < 0) { 993 perror("Error waiting for editor"); 994 } else if(WEXITSTATUS(status)) { 995 fprintf(stderr, 996 "Editor closed abnormally - file will not be uploaded.\n"); 997 ret = -1; 998 } else { 999 // check if the file has changed 1000 if (sys_stat(outfile, &tmp_stat)) { 1001 // very rare case when someone concurrently changes permissions 1002 perror("Cannot stat temporary file"); 1003 ret = -1; 1004 } else if (dl_mtime < tmp_stat.st_mtime) { 1005 // upload changed file 1006 FILE* tmp_stream = sys_fopen(outfile, "rb"); 1007 if(!tmp_stream) { 1008 perror("Cannot open temporary file"); 1009 ret = -1; 1010 } else { 1011 dav_set_content(res, tmp_stream, 1012 (dav_read_func)fread, 1013 (dav_seek_func)file_seek); 1014 dav_set_content_length(res, tmp_stat.st_size); 1015 ret = dav_store(res); 1016 fclose(tmp_stream); 1017 if(ret) { 1018 print_resource_error(sn, path); 1019 } 1020 } 1021 } else { 1022 printf("No changes by user - file will not be uploaded.\n"); 1023 ret = 0; 1024 } 1025 } 1026 } 1027 1028 close(tmp_fd); 1029 if(ret) { 1030 // if someone went wrong, we are most likely unable to unlink anyway 1031 fprintf(stderr, "File location: %s\n", outfile); 1032 } else { 1033 sys_unlink(outfile); 1034 } 1035 free(outfile); 1036 free(path); 1037 1038 return ret; 1039 #endif 1040 } 1041 1042 int get_resource(DavCfgRepository *repo, GetResource *getres, CmdArgs *a, void *unused) { 1043 DavResource *res = getres->res; 1044 char *out = getres->path; 1045 1046 if(res->iscollection) { 1047 printf("get: %s\n", res->path); 1048 1049 int ret = sys_mkdir(out); 1050 if(ret != 0 && errno != EEXIST) { 1051 fprintf(stderr, "Cannot create directory ''%s'': ", out); 1052 perror(""); 1053 return 1; 1054 } 1055 1056 return 0; 1057 } 1058 1059 int isstdout = !strcmp(out, "-"); 1060 if(cmd_getoption(a, "keep") && !isstdout) { 1061 SYS_STAT s; 1062 if(sys_stat(out, &s)) { 1063 if(errno != ENOENT) { 1064 perror("stat"); 1065 } 1066 } else { 1067 if(cmd_getoption(a, "recursive")) { 1068 printf("skip: %s\n", res->path); 1069 } 1070 return 0; 1071 } 1072 } 1073 1074 // print some status message in recursive mode 1075 if(cmd_getoption(a, "recursive")) { 1076 printf("get: %s\n", res->path); 1077 } 1078 1079 FILE *fout = isstdout ? stdout : sys_fopen(out, "wb"); 1080 if(!fout) { 1081 fprintf(stderr, "cannot open output file\n"); 1082 return -1; 1083 } 1084 1085 int ret = dav_get_content(res, fout, (dav_write_func)fwrite); 1086 fclose(fout); 1087 if(ret && strcmp(out, "-")) { 1088 print_resource_error(res->session, res->path); 1089 //if(strcmp(out, "-")) { 1090 // unlink(out); 1091 //} 1092 } 1093 1094 return 0; 1095 } 1096 1097 #define DEFAULT_DIR_MODE T_IRUSR | T_IWUSR | T_IXUSR | T_IRGRP | T_IXGRP | T_IROTH | T_IXOTH 1098 #define DEFAULT_FILE_MODE T_IRUSR | T_IWUSR | T_IRGRP | T_IROTH 1099 1100 int resource2tar(DavCfgRepository *repo, GetResource *res, CmdArgs *a, TarOutputStream *tar) { 1101 DavResource *d = res->res; 1102 1103 if(d->iscollection) { 1104 fprintf(stderr, "add d: %s\n", res->path); 1105 return tar_add_dir(tar, res->path, DEFAULT_DIR_MODE, d->lastmodified); 1106 } 1107 1108 fprintf(stderr, "add f: %s\n", res->path); 1109 1110 // add tar file header 1111 if(tar_begin_file(tar, res->path, DEFAULT_FILE_MODE, d->contentlength, d->lastmodified)) { 1112 fprintf(stderr, "TAR Error: %s\n", tar_error2str(tar->error)); 1113 return -1; 1114 } 1115 1116 if(dav_get_content(d, tar, (dav_write_func)tar_fwrite)) { 1117 print_resource_error(d->session, d->path); 1118 return -1; 1119 } 1120 1121 // download content 1122 1123 return tar_end_file(tar); 1124 } 1125 1126 int cmd_put(CmdArgs *a, DavBool import) { 1127 if(a->argc < 2) { 1128 fprintf(stderr, "Too few arguments\n"); 1129 fprintf(stderr, 1130 "Usage: dav %s\n", 1131 find_usage_str(import ? "import" : "put")); 1132 return -1; 1133 } 1134 DavBool use_stdin = FALSE; 1135 for(int i=1;i<a->argc;i++) { 1136 if(!strcmp(a->argv[i], "-")) { 1137 if(use_stdin) { 1138 fprintf(stderr, "Error: stdin can only occur once in input list\n"); 1139 return -1; 1140 } else { 1141 use_stdin = TRUE; 1142 } 1143 } 1144 } 1145 1146 if(import) { 1147 cxMapPut(a->options, cx_hash_key_str("resursive"), ""); 1148 } 1149 1150 char *url = a->argv[0]; 1151 char *path = NULL; 1152 DavCfgRepository *repo = dav_config_url2repo(get_config(), url, &path); 1153 DavSession *sn = connect_to_repo(ctx, repo, path, request_auth, a); 1154 1155 if(set_session_config(sn, a)) { 1156 return -1; 1157 } 1158 set_session_lock(sn, a); 1159 1160 if(check_encryption_key(a, sn)) { 1161 // TODO: free 1162 return -1; 1163 } 1164 1165 DavBool printfile = FALSE; 1166 DavBool ignoredirerr = FALSE; 1167 if(a->argc > 2) { 1168 printfile = TRUE; 1169 ignoredirerr = TRUE; 1170 } else if(cmd_getoption(a, "recursive")) { 1171 printfile = TRUE; 1172 } 1173 1174 char *finfo_str = cmd_getoption(a, "finfo"); 1175 uint32_t finfo = 0; 1176 if(finfo_str) { 1177 finfo = parse_finfo_settings(finfo_str, NULL); 1178 } 1179 1180 int ret; 1181 for(int i=1;i<a->argc;i++) { 1182 char *file = a->argv[i]; 1183 if(!import) { 1184 if(!strcmp(file, "-")) { 1185 FILE *in = stdin; 1186 ret = put_file(repo, a, sn, path, "stdin", 0, NULL, in, 0); 1187 } else { 1188 ret = put_entry( 1189 repo, 1190 a, 1191 sn, 1192 path, 1193 file, 1194 finfo, 1195 TRUE, 1196 printfile, 1197 ignoredirerr); 1198 } 1199 } else { 1200 ret = put_tar(repo, a, sn, file, path); 1201 } 1202 if(ret) { 1203 break; 1204 } 1205 } 1206 1207 free(path); 1208 dav_session_destroy(sn); 1209 return ret; 1210 } 1211 1212 #if !defined(S_ISREG) && defined(S_IFMT) && defined(S_IFREG) 1213 #define S_ISREG(m) (((m) & S_IFMT) == S_IFREG) 1214 #endif 1215 1216 #ifdef _WIN32 1217 #ifndef S_ISDIR 1218 #define S_ISDIR(mode) ((mode) & _S_IFMT) == _S_IFDIR 1219 #define S_ISREG(mode) ((mode) & _S_IFMT) == _S_IFREG 1220 #endif 1221 #endif 1222 1223 1224 int put_entry( 1225 DavCfgRepository *repo, 1226 CmdArgs *a, 1227 DavSession *sn, 1228 char *path, 1229 char *file, 1230 uint32_t finfo, 1231 DavBool root, 1232 DavBool printfile, 1233 DavBool ignoredirerr) 1234 { 1235 int recursive = cmd_getoption(a, "recursive") ? 1 : 0; 1236 SYS_STAT s; 1237 if(sys_stat(file, &s)) { 1238 perror("stat"); 1239 fprintf(stderr, "cannot stat file %s\n", file); 1240 return -1; 1241 } 1242 1243 int ret = 0; 1244 if(S_ISDIR(s.st_mode)) { 1245 if(!recursive) { 1246 if(ignoredirerr) { 1247 printf("skip: %s\n", file); 1248 } else { 1249 fprintf( 1250 stderr, 1251 "%s is a directory.\nUse the -R option to upload directories.\n", 1252 file); 1253 return 1; 1254 } 1255 } 1256 1257 if(!root) { 1258 printf("mkcol: %s\n", file); 1259 DavResource *res = dav_resource_new(sn, path); 1260 res->iscollection = TRUE; 1261 if(!dav_exists(res)) { 1262 if(dav_create(res)) { 1263 fprintf(stderr, "Cannot create collection %s\n", path); 1264 print_resource_error(sn, res->path); 1265 dav_resource_free(res); 1266 return 1; 1267 } 1268 } 1269 dav_resource_free(res); 1270 } 1271 1272 SYS_DIR dir = sys_opendir(file); 1273 if(!dir) { 1274 // error 1275 } 1276 SysDirEnt *entry; 1277 int nument = 0; 1278 while((entry = sys_readdir(dir)) != NULL) { 1279 if(!strcmp(entry->name, ".") || !strcmp(entry->name, "..")) { 1280 continue; 1281 } 1282 nument++; 1283 char *entry_file = util_concat_path(file, entry->name); 1284 char *entry_path = util_concat_path(path, entry->name); 1285 int r = put_entry( 1286 repo, 1287 a, 1288 sn, 1289 entry_path, 1290 entry_file, 1291 finfo, 1292 FALSE, 1293 printfile, 1294 ignoredirerr); 1295 free(entry_path); 1296 free(entry_file); 1297 if(r) { 1298 ret = 1; 1299 break; 1300 } 1301 } 1302 sys_closedir(dir); 1303 } else if(S_ISREG(s.st_mode)) { 1304 if(printfile) { 1305 printf("put: %s\n", file); 1306 } 1307 1308 FILE *in = sys_fopen(file, "rb"); 1309 if(!in) { 1310 fprintf(stderr, "cannot open input file\n"); 1311 return -1; 1312 } 1313 const char *filename = util_resource_name(file); 1314 //path = util_concat_path(path, filename); 1315 ret = put_file(repo, a, sn, path, filename, finfo, file, in, s.st_size); 1316 //free(path); 1317 fclose(in); 1318 } 1319 1320 return ret; 1321 } 1322 1323 int put_tar(DavCfgRepository *repo, CmdArgs *a, DavSession *sn, char *tarfile, char *path) { 1324 int isstdin = !strcmp(tarfile, "-"); 1325 FILE *in = isstdin ? stdin : fopen(tarfile, "rb"); 1326 if(!in) { 1327 perror("Cannot open tar file"); 1328 return -1; 1329 } 1330 1331 DavResource *col = dav_query(sn, "select - from %s", path); 1332 if(!col) { 1333 if(sn->error == DAV_NOT_FOUND) { 1334 col = dav_resource_new(sn, path); 1335 col->iscollection = TRUE; 1336 if(dav_create(col)) { 1337 print_resource_error(sn, path); 1338 return -1; 1339 } 1340 } else { 1341 print_resource_error(sn, path); 1342 return -1; 1343 } 1344 } else if(!col->iscollection) { 1345 fprintf(stderr, "%s is not a collection\n", col->href); 1346 return -1; 1347 } 1348 1349 1350 int ret = 0; 1351 TarInputStream *tar = tar_inputstream_open(in); 1352 TarEntry *e = NULL; 1353 while((e = tar_read_entry(tar)) != NULL) { 1354 char *newpath = util_concat_path(path, e->path); 1355 if(e->type == TAR_TYPE_FILE) { 1356 fprintf(stderr, "put: %s\n", e->path); 1357 DavResource *res = dav_resource_new(sn, newpath); 1358 dav_set_content(res, tar, (dav_read_func)tar_fread, (dav_seek_func)tar_seek); 1359 dav_set_content_length(res, (size_t)e->size); 1360 1361 if(dav_store(res)) { 1362 print_resource_error(sn, res->path); 1363 fprintf(stderr, "Cannot upload file.\n"); 1364 if(sn->errorstr) { 1365 fprintf(stderr, "%s\n", sn->errorstr); 1366 } 1367 return -1; 1368 } 1369 1370 } else if(e->type == TAR_TYPE_DIRECTORY) { 1371 printf("mkcol: %s\n", e->path); 1372 DavResource *res = dav_resource_new(sn, newpath); 1373 res->iscollection = TRUE; 1374 if(!dav_exists(res)) { 1375 if(dav_create(res)) { 1376 fprintf(stderr, "Cannot create collection %s\n", newpath); 1377 print_resource_error(sn, res->path); 1378 ret = 1; 1379 free(newpath); 1380 break; 1381 } 1382 } 1383 } else { 1384 fprintf(stderr, "skip: %s\n", e->path); 1385 } 1386 free(newpath); 1387 } 1388 if(tar->error != TAR_OK) { 1389 ret = -1; 1390 } 1391 1392 if(!isstdin) { 1393 fclose(in); 1394 } 1395 1396 return ret; 1397 } 1398 1399 int put_file( 1400 DavCfgRepository *repo, 1401 CmdArgs *a, 1402 DavSession *sn, 1403 const char *path, 1404 const char *name, 1405 uint32_t finfo, 1406 const char *fpath, 1407 FILE *in, 1408 off_t len) 1409 { 1410 DavResource *res = dav_query(sn, "select - from %s", path); 1411 1412 if(!res) { 1413 if(sn->error == DAV_NOT_FOUND) { 1414 res = dav_resource_new(sn, path); 1415 if(dav_create(res)) { 1416 fprintf(stderr, "Cannot create resource.\n"); 1417 return -1; 1418 } 1419 } else { 1420 print_resource_error(sn, path); 1421 return -1; 1422 } 1423 } else if(res->iscollection) { 1424 // TODO: free res 1425 char *newpath = util_concat_path(path, name); 1426 1427 if (!strcmp(path, newpath)) { 1428 // TODO: free res 1429 fprintf(stderr, "Cannot put file, because a collection with " 1430 "that name already exists.\n"); 1431 free(newpath); 1432 return -1; 1433 } 1434 1435 path = newpath; 1436 res = dav_resource_new(sn, path); 1437 int ret = put_file(repo, a, sn, res->path, NULL, finfo, fpath, in, len); 1438 // TODO: free res 1439 free(newpath); 1440 return ret; 1441 } 1442 1443 if(resource_set_finfo(fpath, res, finfo)) { 1444 fprintf(stderr, "Cannot set finfo: %s.\n", strerror(errno)); 1445 } 1446 if((finfo & FINFO_XATTR) == FINFO_XATTR) { 1447 XAttributes *xattr = file_get_attributes(fpath, NULL, NULL); 1448 if(xattr) { 1449 resource_set_xattr(res, xattr); 1450 } 1451 } 1452 1453 dav_set_content(res, in, (dav_read_func)fread, (dav_seek_func)file_seek); 1454 if(len > 0 && len < 0x7d000000) { 1455 dav_set_content_length(res, (size_t)len); 1456 } 1457 1458 if(dav_store(res)) { 1459 print_resource_error(sn, res->path); 1460 fprintf(stderr, "Cannot upload file.\n"); 1461 if(sn->errorstr) { 1462 fprintf(stderr, "%s\n", sn->errorstr); 1463 } 1464 return -1; 1465 } 1466 return 0; 1467 } 1468 1469 static int dav_create_col(DavResource *res) { 1470 res->iscollection = 1; 1471 return dav_create(res); 1472 } 1473 1474 static void print_cannoterr(DavSession *sn, const char *message) { 1475 switch(sn->error) { 1476 case DAV_UNSUPPORTED_PROTOCOL: 1477 case DAV_COULDNT_RESOLVE_PROXY: 1478 case DAV_COULDNT_RESOLVE_HOST: 1479 case DAV_COULDNT_CONNECT: 1480 case DAV_TIMEOUT: 1481 case DAV_SSL_ERROR: break; 1482 default: fprintf(stderr, "Cannot %s.\n", message); 1483 } 1484 } 1485 1486 static int cmd_operation_on_resources(CmdArgs* a, 1487 int(*operation)(DavResource*), 1488 const char* command, 1489 const char* message, 1490 DavBool check_key) 1491 { 1492 if(a->argc < 1) { 1493 fprintf(stderr, "Too few arguments\n"); 1494 fprintf(stderr, "Usage: dav %s\n", find_usage_str(command)); 1495 return -1; 1496 } 1497 1498 char *url = a->argv[0]; 1499 char *path = NULL; 1500 DavCfgRepository *repo = dav_config_url2repo(get_config(), url, &path); 1501 DavSession *sn = connect_to_repo(ctx, repo, path, request_auth, a); 1502 1503 int exit_code = -1; 1504 assert(!!path && !!sn); 1505 1506 if(set_session_config(sn, a)) { 1507 goto cmd_oponres_exit; 1508 } 1509 1510 set_session_lock(sn, a); 1511 1512 if(check_key && check_encryption_key(a, sn)) { 1513 goto cmd_oponres_exit; 1514 } 1515 1516 DavResource *res = dav_resource_new(sn, path); 1517 assert(!!res); 1518 res->iscollection = 1; 1519 1520 if(a->argc == 1) { 1521 if(operation(res)) { 1522 print_cannoterr(sn, message); 1523 print_resource_error(sn, res->path); 1524 goto cmd_oponres_exit; 1525 } 1526 } else { 1527 for(int i = 1 ; i < a->argc ;++i) { 1528 DavResource *child = dav_resource_new_child(sn, res, a->argv[i]); 1529 assert(!!child); 1530 child->iscollection = 1; 1531 if(operation(child)) { 1532 print_cannoterr(sn, message); 1533 print_resource_error(sn, child->path); 1534 goto cmd_oponres_exit; 1535 } 1536 } 1537 } 1538 1539 exit_code = 0; 1540 cmd_oponres_exit: 1541 free(path); 1542 dav_session_destroy(sn); 1543 return exit_code; 1544 } 1545 1546 int cmd_remove(CmdArgs *a) { 1547 return cmd_operation_on_resources(a, dav_delete, 1548 "remove", "delete resource", FALSE); 1549 } 1550 1551 int cmd_mkdir(CmdArgs *a) { 1552 return cmd_operation_on_resources(a, dav_create_col, 1553 "mkdir", "create collection", TRUE); 1554 } 1555 1556 int cmd_move(CmdArgs *a, int cp) { 1557 const char* actionstr = cp ? "copy" : "move"; 1558 1559 if(a->argc != 2) { 1560 // TODO: change, when creation of multiple dirs is supported 1561 fprintf(stderr, "Too %s arguments\n", a->argc < 2 ? "few":"many"); 1562 fprintf(stderr, "Usage: dav %s\n", find_usage_str(actionstr)); 1563 return -1; 1564 } 1565 1566 char *srcurl = a->argv[0]; 1567 char *srcpath = NULL; 1568 DavCfgRepository *srcrepo = dav_config_url2repo(get_config(), srcurl, &srcpath); 1569 1570 DavSession *srcsn = connect_to_repo(ctx, srcrepo, srcpath, request_auth, a); 1571 if(set_session_config(srcsn, a)) { 1572 return -1; 1573 } 1574 set_session_lock(srcsn, a); 1575 1576 DavBool override = cmd_getoption(a, "override") ? true : false; 1577 1578 char *desturl = a->argv[1]; 1579 char *destpath = NULL; 1580 DavCfgRepository *destrepo = dav_config_url2repo(get_config(), desturl, &destpath); 1581 1582 if(srcrepo == destrepo) { 1583 DavResource *res = dav_resource_new(srcsn, srcpath); 1584 int err = cp ? dav_copy_o(res, destpath, override) 1585 : dav_move_o(res, destpath, override); 1586 if(err) { 1587 print_resource_error(srcsn, res->path); 1588 fprintf(stderr, "Cannot %s resource.\n", actionstr); 1589 return -1; 1590 } 1591 } else { 1592 char *srchost = util_url_base(srcrepo->url.value.ptr); 1593 char *desthost = util_url_base(destrepo->url.value.ptr); 1594 if(!strcmp(srchost, desthost)) { 1595 DavSession *destsn = connect_to_repo(ctx, destrepo, destpath, request_auth, a); 1596 if(set_session_config(destsn, a)) { 1597 return -1; 1598 } 1599 DavResource *dest = dav_resource_new(destsn, destpath); 1600 char *desthref = dav_resource_get_href(dest); 1601 char *desturl = util_get_url(destsn, desthref); 1602 1603 DavResource *res = dav_resource_new(srcsn, srcpath); 1604 int err = cp ? dav_copyto(res, desturl, override) 1605 : dav_moveto(res, desturl, override); 1606 1607 free(desturl); 1608 dav_session_destroy(destsn); 1609 1610 if(err) { 1611 print_resource_error(srcsn, res->path); 1612 fprintf(stderr, "Cannot %s resource.\n", actionstr); 1613 return -1; 1614 } 1615 } else { 1616 fprintf(stderr, "Cannot %s between different hosts.\n", actionstr); 1617 return -1; 1618 } 1619 } 1620 1621 dav_session_destroy(srcsn); 1622 1623 return 0; 1624 } 1625 1626 int cmd_rename(CmdArgs *a) { 1627 if(a->argc != 2) { 1628 // TODO: change, when creation of multiple dirs is supported 1629 fprintf(stderr, "Too %s arguments\n", a->argc < 2 ? "few":"many"); 1630 fprintf(stderr, "Usage: dav %s\n", find_usage_str("rename")); 1631 return -1; 1632 } 1633 1634 char *name = a->argv[1]; 1635 size_t namelen = strlen(name); 1636 for(size_t i=0;i<namelen;i++) { 1637 char c = name[i]; 1638 if(c == '/') { 1639 fprintf(stderr, "Illegal character in name: ''/''\n"); 1640 return 1; 1641 } 1642 } 1643 1644 char *url = a->argv[0]; 1645 char *path = NULL; 1646 DavCfgRepository *repo = dav_config_url2repo(get_config(), url, &path); 1647 DavSession *sn = connect_to_repo(ctx, repo, path, request_auth, a); 1648 1649 if(set_session_config(sn, a)) { 1650 return -1; 1651 } 1652 set_session_lock(sn, a); 1653 1654 int ret = 0; 1655 DavResource *res = dav_get(sn, path, NULL); 1656 if(res) { 1657 char *cryptoname = dav_get_string_property_ns(res, DAV_NS, "crypto-name"); 1658 char *cryptokey = dav_get_string_property_ns(res, DAV_NS, "crypto-key"); 1659 if(cryptoname && cryptokey) { 1660 // encrypted resource with an encrypted name 1661 // renaming is done by simply setting the crypto-name property 1662 1663 DavKey *key = dav_context_get_key(ctx, cryptokey); 1664 if(key) { 1665 // check if a resource with this name already exists 1666 char *parent = util_parent_path(res->path); 1667 char *newpath = util_concat_path(parent, name); 1668 DavResource *testres = dav_resource_new(sn, newpath); 1669 if(dav_exists(testres)) { 1670 fprintf(stderr, "A resource with this name already exists.\nAbort.\n"); 1671 ret = 1; 1672 } else { 1673 char *crname = aes_encrypt(name, namelen, key); 1674 dav_set_string_property_ns(res, DAV_NS, "crypto-name", crname); 1675 free(crname); 1676 if(dav_store(res)) { 1677 print_resource_error(sn, res->path); 1678 fprintf(stderr, "Cannot store crypto-name property.\n"); 1679 ret = 1; 1680 } 1681 } 1682 free(parent); 1683 free(newpath); 1684 } else { 1685 fprintf(stderr, "Key %s not found.\n", cryptokey); 1686 } 1687 } else { 1688 // rename the resource by changing the url mapping with MOVE 1689 1690 char *parent = util_parent_path(res->href); 1691 char *new_href = util_concat_path(parent, name); 1692 char *dest = util_get_url(sn, new_href); 1693 free(parent); 1694 free(new_href); 1695 if(dav_moveto(res, dest, false)) { 1696 print_resource_error(sn, path); 1697 fprintf(stderr, "Cannot rename resource.\n"); 1698 ret = 1; 1699 } 1700 free(dest); 1701 } 1702 } else { 1703 print_resource_error(sn, path); 1704 fprintf(stderr, "Cannot rename resource.\n"); 1705 ret = 1; 1706 } 1707 1708 1709 dav_session_destroy(sn); 1710 free(path); 1711 return ret; 1712 } 1713 1714 1715 static size_t get_date_header_cb(void *header, int s, int n, void *data) { 1716 char **date_str = (char**)data; 1717 1718 //printf("header: %.*s\n", s*n, header); 1719 cxstring h = cx_strn(header, s*n); 1720 if(cx_strprefix(h, CX_STR("Date:"))) { 1721 cxstring v = cx_strsubs(h, 5); 1722 *date_str = cx_strdup(cx_strtrim(v)).ptr; 1723 } 1724 return s*n; 1725 } 1726 1727 int cmd_date(CmdArgs *a) { 1728 if(a->argc < 1) { 1729 time_t now = time(NULL); 1730 struct tm *date = gmtime(&now); 1731 char str[32]; 1732 putenv("LC_TIME=C"); 1733 size_t len = strftime(str, 32, "%a, %d %b %Y %H:%M:%S GMT\n", date); 1734 fwrite(str, 1, len, stdout); 1735 } else if (a->argc == 1) { 1736 char *url = a->argv[0]; 1737 char *path = NULL; 1738 DavCfgRepository *repo = dav_config_url2repo(get_config(), url, &path); 1739 DavSession *sn = connect_to_repo(ctx, repo, path, request_auth, a); 1740 1741 DavResource *res = dav_resource_new(sn, path); 1742 char *date = NULL; 1743 curl_easy_setopt(sn->handle, CURLOPT_HEADERFUNCTION, get_date_header_cb); 1744 curl_easy_setopt(sn->handle, CURLOPT_WRITEHEADER, &date); 1745 if(dav_exists(res) && date) { 1746 printf("%s\n", date); 1747 } else { 1748 return -1; 1749 } 1750 free(path); 1751 return 0; 1752 } else { 1753 fprintf(stderr, "Too many arguments\n"); 1754 fprintf(stderr, "Usage: dav %s\n", find_usage_str("date")); 1755 return -1; 1756 } 1757 return 0; 1758 } 1759 1760 int cmd_get_property(CmdArgs *a) { 1761 if(a->argc < 2) { 1762 fprintf(stderr, "Too few arguments\n"); 1763 fprintf(stderr, "Usage: dav %s\n", find_usage_str("get-property")); 1764 return -1; 1765 } 1766 1767 char *url = a->argv[0]; 1768 char *path = NULL; 1769 DavCfgRepository *repo = dav_config_url2repo(get_config(), url, &path); 1770 DavSession *sn = connect_to_repo(ctx, repo, path, request_auth, a); 1771 1772 if(set_session_config(sn, a)) { 1773 return -1; 1774 } 1775 1776 char *namespace = cmd_getoption(a, "namespace"); 1777 char *property = a->argv[1]; 1778 1779 char *version = cmd_getoption(a, "version"); 1780 1781 DavPropName propname; 1782 if(namespace) { 1783 propname.ns = namespace; 1784 propname.name = property; 1785 } else { 1786 dav_get_property_namespace_str(ctx, property, &propname.ns, &propname.name); 1787 if(!propname.ns || !propname.name) { 1788 fprintf(stderr, "Error: unknown namespace prefix\n"); 1789 return -1; 1790 } 1791 } 1792 1793 DavResource *res = dav_resource_new(sn, path); 1794 if(version) { 1795 DavResource *vres = find_version(res, version); 1796 if(!vres) { 1797 fprintf(stderr, "Cannot find version ''%s'' for resource.\n", version); 1798 return -1; 1799 } 1800 dav_resource_free_all(res); 1801 res = vres; 1802 } 1803 1804 if(dav_load_prop(res, &propname, 1)) { 1805 print_resource_error(sn, res->path); 1806 return -1; 1807 } 1808 free(path); 1809 1810 DavXmlNode *x = dav_get_property_ns(res, propname.ns, propname.name); 1811 if(!x) { 1812 fprintf(stderr, "Error: no property value.\n"); 1813 return -1; 1814 } 1815 1816 if(cmd_getoption(a, "xml")) { 1817 // print a real xml document on stdout 1818 printxmldoc(stdout, propname.name, propname.ns, x); 1819 } else { 1820 // in this mode a simple string is printed on stdout 1821 // or simplified and nicely formatted xml is printed on stderr 1822 if(dav_xml_isstring(x)) { 1823 printf("%s\n", dav_xml_getstring(x)); 1824 } else { 1825 char *str = xml2str(x); 1826 fprintf(stderr, "%s", str); 1827 free(str); 1828 } 1829 } 1830 1831 return 0; 1832 } 1833 1834 int cmd_set_property(CmdArgs *a) { 1835 if(a->argc < 2) { 1836 fprintf(stderr, "Too few arguments\n"); 1837 fprintf(stderr, "Usage: dav %s\n", find_usage_str("set-property")); 1838 return -1; 1839 } 1840 1841 char *url = a->argv[0]; 1842 char *path = NULL; 1843 DavCfgRepository *repo = dav_config_url2repo(get_config(), url, &path); 1844 DavSession *sn = connect_to_repo(ctx, repo, path, request_auth, a); 1845 1846 if(set_session_config(sn, a)) { 1847 return -1; 1848 } 1849 set_session_lock(sn, a); 1850 1851 DavResource *res = dav_resource_new(sn, path); 1852 if(!dav_exists(res)) { 1853 print_resource_error(sn, res->path); 1854 return -1; 1855 } 1856 1857 char *namespace = cmd_getoption(a, "namespace"); 1858 char *xml = cmd_getoption(a, "xml"); 1859 1860 char *property = a->argv[1]; 1861 char *value = a->argc > 2 ? a->argv[2] : stdin2str(); 1862 1863 int ret = 0; 1864 if(xml) { 1865 DavXmlNode *xmlvalue = dav_parse_xml(sn, value, strlen(value)); 1866 if(xmlvalue) { 1867 if(namespace) { 1868 dav_set_property_ns(res, namespace, property, xmlvalue->children); 1869 } else { 1870 dav_set_property(res, property, xmlvalue->children); 1871 } 1872 } else { 1873 fprintf(stderr, "Error: property content is not valid xml\n"); 1874 ret = 1; 1875 } 1876 } else { 1877 if(namespace) { 1878 dav_set_string_property_ns(res, namespace, property, value); 1879 } else { 1880 dav_set_string_property(res, property, value); 1881 } 1882 } 1883 1884 if(ret == 0) { 1885 if(dav_store(res)) { 1886 print_resource_error(sn, res->path); 1887 fprintf(stderr, "Cannot set property.\n"); 1888 ret = -1; 1889 } 1890 } else 1891 1892 free(path); 1893 dav_session_destroy(sn); 1894 return ret; 1895 } 1896 1897 int cmd_remove_property(CmdArgs *a) { 1898 if(a->argc < 2) { 1899 fprintf(stderr, "Too few arguments\n"); 1900 fprintf(stderr, "Usage: dav %s\n", find_usage_str("remove-property")); 1901 return -1; 1902 } 1903 1904 char *url = a->argv[0]; 1905 char *path = NULL; 1906 DavCfgRepository *repo = dav_config_url2repo(get_config(), url, &path); 1907 DavSession *sn = connect_to_repo(ctx, repo, path, request_auth, a); 1908 1909 if(set_session_config(sn, a)) { 1910 return -1; 1911 } 1912 1913 char *namespace = cmd_getoption(a, "namespace"); 1914 char *property = a->argv[1]; 1915 1916 DavPropName propname; 1917 if(namespace) { 1918 propname.ns = namespace; 1919 propname.name = property; 1920 } else { 1921 dav_get_property_namespace_str(ctx, property, &propname.ns, &propname.name); 1922 } 1923 1924 int ret = 0; 1925 DavResource *res = dav_resource_new(sn, path); 1926 dav_remove_property_ns(res, propname.ns, propname.name); 1927 1928 if(dav_store(res)) { 1929 print_resource_error(sn, res->path); 1930 fprintf(stderr, "Cannot set property.\n"); 1931 ret = -1; 1932 } 1933 1934 free(path); 1935 return ret; 1936 } 1937 1938 int cmd_lock(CmdArgs *a) { 1939 if(a->argc != 1) { 1940 fprintf(stderr, "Too %s arguments\n", a->argc > 1 ? "many" : "few"); 1941 fprintf(stderr, "Usage: dav %s\n", find_usage_str("lock")); 1942 return -1; 1943 } 1944 1945 char *url = a->argv[0]; 1946 char *path = NULL; 1947 DavCfgRepository *repo = dav_config_url2repo(get_config(), url, &path); 1948 DavSession *sn = connect_to_repo(ctx, repo, path, request_auth, a); 1949 cxMempoolRegister(sn->mp, path, free); 1950 1951 if(set_session_config(sn, a)) { 1952 return -1; 1953 } 1954 1955 time_t timeout = 0; 1956 char *timeoutstr = cmd_getoption(a, "timeout"); 1957 if(timeoutstr) { 1958 if(!cx_strcasecmp(cx_str(timeoutstr), CX_STR("infinite"))) { 1959 timeout = -1; 1960 } else { 1961 uint64_t i; 1962 if(util_strtouint(timeoutstr, &i)) { 1963 timeout = (time_t)i; 1964 } else { 1965 fprintf(stderr, "Error: -T option has invalid value\n"); 1966 return -1; 1967 } 1968 } 1969 } 1970 1971 DavResource *res = dav_resource_new(sn, path); 1972 if(dav_lock_t(res, timeout)) { 1973 print_resource_error(sn, res->path); 1974 return -1; 1975 } 1976 1977 DavLock *lock = dav_get_lock(sn, res->path); 1978 if(!lock) { 1979 // this should really not happen 1980 // do some damage control 1981 dav_unlock(res); 1982 fprintf(stderr, "Error: Cannot find lock token for %s\n", res->path); 1983 return -1; 1984 } 1985 1986 printf("%s\n", lock->token); 1987 1988 dav_session_destroy(sn); 1989 return 0; 1990 } 1991 1992 static char* read_line() { 1993 CxBuffer buf; 1994 cxBufferInit(&buf, NULL, 128, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND); 1995 int c; 1996 while((c = getchar()) != EOF) { 1997 if(c == '\n') { 1998 break; 1999 } 2000 cxBufferPut(&buf, c); 2001 } 2002 char *str = NULL; 2003 cxstring line = cx_strtrim(cx_strn(buf.space, buf.size)); 2004 if(line.length != 0) { 2005 str = cx_strdup(line).ptr; 2006 } 2007 cxBufferDestroy(&buf); 2008 return str; 2009 } 2010 2011 int cmd_unlock(CmdArgs *a) { 2012 if(a->argc != 1) { 2013 fprintf(stderr, "Too %s arguments\n", a->argc > 1 ? "many" : "few"); 2014 fprintf(stderr, "Usage: dav %s\n", find_usage_str("unlock")); 2015 return -1; 2016 } 2017 2018 char *url = a->argv[0]; 2019 char *path = NULL; 2020 DavCfgRepository *repo = dav_config_url2repo(get_config(), url, &path); 2021 DavSession *sn = connect_to_repo(ctx, repo, path, request_auth, a); 2022 cxMempoolRegister(sn->mp, path, free); 2023 if(set_session_config(sn, a)) { 2024 return -1; 2025 } 2026 2027 char *locktoken = cmd_getoption(a, "lock"); 2028 if(locktoken) { 2029 DavLock *lock = dav_create_lock(sn, locktoken, NULL); 2030 dav_add_collection_lock(sn, "/", lock); 2031 } else { 2032 locktoken = read_line(); 2033 if(!locktoken) { 2034 fprintf(stderr, "No lock token specified.\nAbort.\n"); 2035 return -1; 2036 } 2037 DavLock *lock = dav_create_lock(sn, locktoken, NULL); 2038 dav_add_collection_lock(sn, "/", lock); 2039 free(locktoken); 2040 } 2041 2042 int ret = 0; 2043 DavResource *res = dav_resource_new(sn, path); 2044 if(dav_unlock(res)) { 2045 print_resource_error(sn, res->path); 2046 ret = -1; 2047 } 2048 2049 dav_session_destroy(sn); 2050 return ret; 2051 } 2052 2053 static int count_children(DavResource *res) { 2054 DavResource *child = res->children; 2055 int count = 0; 2056 while(child) { 2057 count++; 2058 child = child->next; 2059 } 2060 return count; 2061 } 2062 2063 void print_xml_infostr(DavXmlNode *xml) { 2064 if(xml->children) { 2065 printf("<%s>...</%s>", xml->name, xml->name); 2066 } else { 2067 printf("<%s/>", xml->name); 2068 } 2069 } 2070 2071 int cmd_info(CmdArgs *a) { 2072 if(a->argc < 1) { 2073 fprintf(stderr, "Too few arguments\n"); 2074 fprintf(stderr, "Usage: dav %s\n", find_usage_str("info")); 2075 return -1; 2076 } 2077 2078 char *url = a->argv[0]; 2079 char *path = NULL; 2080 DavCfgRepository *repo = dav_config_url2repo(get_config(), url, &path); 2081 DavSession *sn = connect_to_repo(ctx, repo, path, request_auth, a); 2082 2083 if(set_session_config(sn, a)) { 2084 return -1; 2085 } 2086 2087 char *version = cmd_getoption(a, "version"); 2088 2089 DavResource *res = dav_resource_new(sn, path); 2090 if(version) { 2091 DavResource *vres = find_version(res, version); 2092 if(!vres) { 2093 fprintf(stderr, "Cannot find version ''%s'' for resource.\n", version); 2094 return -1; 2095 } 2096 dav_resource_free_all(res); 2097 res = vres; 2098 } 2099 2100 if(!dav_load(res)) { 2101 printf("name: %s\n", res->name); 2102 printf("path: %s\n", res->path); 2103 2104 char *server = util_url_base(sn->base_url); 2105 char *url = util_concat_path(server, res->href); 2106 printf("url: %s\n", url); 2107 free(url); 2108 free(server); 2109 2110 if(res->iscollection) { 2111 printf("type: collection\n"); 2112 printf("size: %d\n", count_children(res)); 2113 } else { 2114 printf("type: resource\n"); 2115 char *len = util_size_str(res->iscollection, res->contentlength); 2116 printf("size: %s\n", len); 2117 free(len); 2118 } 2119 2120 size_t count = 0; 2121 DavPropName *properties = dav_get_property_names(res, &count); 2122 2123 char *last_ns = NULL; 2124 for(int i=0;i<count;i++) { 2125 DavPropName p = properties[i]; 2126 if(!last_ns || strcmp(last_ns, p.ns)) { 2127 printf("\nnamespace: %s\n", p.ns); 2128 last_ns = p.ns; 2129 } 2130 2131 DavXmlNode *xval = dav_get_property_ns(res, p.ns, p.name); 2132 if(dav_xml_isstring(xval)) { 2133 cxstring value = cx_str(dav_xml_getstring(xval)); 2134 printf(" %s: %.*s\n", p.name, (int)value.length, value.ptr); 2135 } else { 2136 // find some xml elements 2137 printf(" %s: ", p.name); 2138 DavXmlNode *x = xval->type == DAV_XML_ELEMENT ? xval : dav_xml_nextelm(xval); 2139 for(int i=0;i<3;i++) { 2140 if(x) { 2141 if(i == 2) { 2142 printf(" ..."); 2143 break; 2144 } else { 2145 print_xml_infostr(x); 2146 } 2147 } else { 2148 break; 2149 } 2150 x = dav_xml_nextelm(x); 2151 } 2152 printf("\n"); 2153 2154 2155 } 2156 } 2157 2158 dav_session_free(sn, properties); 2159 return 0; 2160 } else { 2161 print_resource_error(sn, res->path); 2162 } 2163 2164 return -1; 2165 } 2166 2167 int cmd_checkout(CmdArgs *a) { 2168 if(a->argc < 1) { 2169 fprintf(stderr, "Too few arguments\n"); 2170 fprintf(stderr, "Usage: dav %s\n", "checkout [-pc] <url>"); 2171 return -1; 2172 } 2173 2174 char *url = a->argv[0]; 2175 char *path = NULL; 2176 DavCfgRepository *repo = dav_config_url2repo(get_config(), url, &path); 2177 DavSession *sn = connect_to_repo(ctx, repo, path, request_auth, a); 2178 2179 if(set_session_config(sn, a)) { 2180 return -1; 2181 } 2182 set_session_lock(sn, a); 2183 2184 int ret = 0; 2185 DavResource *res = dav_resource_new(sn, path); 2186 if(dav_checkout(res)) { 2187 print_resource_error(sn, res->path); 2188 ret = 1; 2189 } 2190 2191 return ret; 2192 } 2193 2194 int cmd_checkin(CmdArgs *a) { 2195 if(a->argc < 1) { 2196 fprintf(stderr, "Too few arguments\n"); 2197 fprintf(stderr, "Usage: dav %s\n", "checkin [-pc] <url>"); 2198 return -1; 2199 } 2200 2201 char *url = a->argv[0]; 2202 char *path = NULL; 2203 DavCfgRepository *repo = dav_config_url2repo(get_config(), url, &path); 2204 DavSession *sn = connect_to_repo(ctx, repo, path, request_auth, a); 2205 2206 if(set_session_config(sn, a)) { 2207 return -1; 2208 } 2209 set_session_lock(sn, a); 2210 2211 int ret = 0; 2212 DavResource *res = dav_resource_new(sn, path); 2213 if(dav_checkin(res)) { 2214 print_resource_error(sn, res->path); 2215 ret = 1; 2216 } 2217 2218 return ret; 2219 } 2220 2221 int cmd_uncheckout(CmdArgs *a) { 2222 if(a->argc < 1) { 2223 fprintf(stderr, "Too few arguments\n"); 2224 fprintf(stderr, "Usage: dav %s\n", "uncheckout [-pc] <url>"); 2225 return -1; 2226 } 2227 2228 char *url = a->argv[0]; 2229 char *path = NULL; 2230 DavCfgRepository *repo = dav_config_url2repo(get_config(), url, &path); 2231 DavSession *sn = connect_to_repo(ctx, repo, path, request_auth, a); 2232 2233 if(set_session_config(sn, a)) { 2234 return -1; 2235 } 2236 set_session_lock(sn, a); 2237 2238 int ret = 0; 2239 DavResource *res = dav_resource_new(sn, path); 2240 if(dav_uncheckout(res)) { 2241 print_resource_error(sn, res->path); 2242 ret = 1; 2243 } 2244 2245 return ret; 2246 } 2247 int cmd_versioncontrol(CmdArgs *a) { 2248 if(a->argc < 1) { 2249 fprintf(stderr, "Too few arguments\n"); 2250 fprintf(stderr, "Usage: dav %s\n", "versioncontrol [-pc] <url>"); 2251 return -1; 2252 } 2253 2254 char *url = a->argv[0]; 2255 char *path = NULL; 2256 DavCfgRepository *repo = dav_config_url2repo(get_config(), url, &path); 2257 DavSession *sn = connect_to_repo(ctx, repo, path, request_auth, a); 2258 2259 if(set_session_config(sn, a)) { 2260 return -1; 2261 } 2262 set_session_lock(sn, a); 2263 2264 int ret = 0; 2265 DavResource *res = dav_resource_new(sn, path); 2266 if(dav_versioncontrol(res)) { 2267 print_resource_error(sn, res->path); 2268 ret = 1; 2269 } 2270 2271 return ret; 2272 } 2273 2274 int cmd_list_versions(CmdArgs *a) { 2275 if(a->argc < 1) { 2276 fprintf(stderr, "Too few arguments\n"); 2277 fprintf(stderr, "Usage: dav %s\n", "list-versions [-pc] <url>"); 2278 return -1; 2279 } 2280 2281 char *url = a->argv[0]; 2282 char *path = NULL; 2283 DavCfgRepository *repo = dav_config_url2repo(get_config(), url, &path); 2284 DavSession *sn = connect_to_repo(ctx, repo, path, request_auth, a); 2285 2286 if(set_session_config(sn, a)) { 2287 return -1; 2288 } 2289 2290 DavResource *res = dav_resource_new(sn, path); 2291 2292 int ret = 0; 2293 DavResource *list = dav_versiontree(res, NULL); 2294 if(list) { 2295 char* longlist = cmd_getoption(a, "list"); 2296 2297 DavResource *v = list; 2298 int addnl = 0; 2299 while(v) { 2300 char *vname = dav_get_string_property(v, "D:version-name"); 2301 2302 if(longlist) { 2303 if(addnl) { 2304 putchar('\n'); 2305 } 2306 printf("name: %s\n", vname); 2307 printf("href: %s\n", v->href); 2308 addnl = 1; 2309 } else { 2310 printf("%s\n", vname); 2311 } 2312 v = v->next; 2313 } 2314 } else if(sn->error != DAV_OK) { 2315 print_resource_error(sn, path); 2316 ret = 1; 2317 } 2318 2319 return ret; 2320 } 2321 2322 DavResource* find_version(DavResource *res, char *version) { 2323 DavResource *list = dav_versiontree(res, NULL); 2324 DavResource *ret = NULL; 2325 while(list) { 2326 DavResource *next = list->next; 2327 if(!ret) { 2328 char *vname = dav_get_string_property(list, "D:version-name"); 2329 if(vname && !strcmp(vname, version)) { 2330 ret = list; 2331 } 2332 } 2333 if(list != ret) { 2334 dav_resource_free(list); 2335 } 2336 list = next; 2337 } 2338 return ret; 2339 } 2340 2341 2342 char* stdin2str() { 2343 CxBuffer buf; 2344 cxBufferInit(&buf, NULL, 1024, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND); 2345 size_t size = cx_stream_copy(stdin, &buf, (cx_read_func)fread, (cx_write_func)cxBufferWrite); 2346 if(size == 0) { 2347 cxBufferDestroy(&buf); 2348 return NULL; 2349 } else { 2350 cxBufferPut(&buf, '\0'); 2351 return buf.space; 2352 } 2353 } 2354 2355 static void xml2str_i(DavXmlNode *node, CxBuffer *buf, int indent) { 2356 while(node) { 2357 if(node->type == DAV_XML_ELEMENT) { 2358 if(node->children) { 2359 if(dav_xml_isstring(node->children)) { 2360 cxstring s = cx_strtrim(cx_str(dav_xml_getstring(node->children))); 2361 cx_bprintf( 2362 buf, 2363 "%*s<%s>%.*s</%s>\n", 2364 indent, 2365 "", 2366 node->name, 2367 (int)s.length, 2368 s.ptr, 2369 node->name); 2370 } else { 2371 cx_bprintf(buf, "%*s<%s>\n", indent, "", node->name); 2372 xml2str_i(node->children, buf, indent+2); 2373 cx_bprintf(buf, "%*s</%s>\n", indent, "", node->name); 2374 } 2375 } else { 2376 cx_bprintf(buf, "%*s<%s />", indent, "", node->name); 2377 cxBufferPut(buf, '\n'); 2378 } 2379 } else if(node->type == DAV_XML_TEXT) { 2380 cxstring val = cx_strtrim(cx_strn(node->content, node->contentlength)); 2381 if(val.length > 0) { 2382 cx_bprintf(buf, "%*.*s", indent, (int)val.length, val.ptr); 2383 } 2384 } 2385 2386 node = node->next; 2387 } 2388 } 2389 2390 char* xml2str(DavXmlNode *node) { 2391 CxBuffer buf; 2392 cxBufferInit(&buf, NULL, 256, cxDefaultAllocator, CX_BUFFER_AUTO_EXTEND); 2393 xml2str_i(node, &buf, 0); 2394 cxBufferPut(&buf, 0); 2395 return buf.space; 2396 } 2397 2398 void printxmldoc(FILE *out, char *root, char *rootns, DavXmlNode *content) { 2399 CxMap *nsmap = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 16); 2400 nsmap->simple_destructor = free; 2401 2402 cxMapPut(nsmap, cx_hash_key_str(rootns), "x0"); 2403 fprintf(out, "%s", "<?xml version=\"1.0\"?>\n"); 2404 fprintf(out, "<x0:%s xmlns:x0=\"%s\">", root, rootns); 2405 2406 dav_print_node(out, (cx_write_func)fwrite, nsmap, content); 2407 2408 fprintf(out, "</x0:%s>\n", root); 2409 2410 // cleanup namespace map 2411 cxMapRemove(nsmap, cx_hash_key_str(rootns)); 2412 cxMapDestroy(nsmap); 2413 } 2414 2415 2416 /* ---------- config commands ---------- */ 2417 2418 int cmd_add_repository(CmdArgs *args) { 2419 printf("Each repository must have an unique name.\n"); 2420 char *name = assistant_getcfg("name"); 2421 if(!name) { 2422 fprintf(stderr, "Abort\n"); 2423 return -1; 2424 } 2425 if(dav_config_get_repository(get_config(), cx_str(name))) { 2426 fprintf(stderr, "Repository %s already exists.\nAbort\n", name); 2427 return -1; 2428 } 2429 2430 printf("\nSpecify the repository base url.\n"); 2431 char *url = assistant_getcfg("url"); 2432 if(!url) { 2433 fprintf(stderr, "Abort\n"); 2434 return -1; 2435 } 2436 2437 printf("\nUser for HTTP authentication.\n"); 2438 char *user = assistant_getoptcfg("user"); 2439 2440 char *password = NULL; 2441 if(user) { 2442 password = assistant_gethiddenoptcfg("password"); 2443 } 2444 printf("\n"); 2445 2446 DavConfig *config = get_config(); 2447 const CxAllocator *a = config->mp->allocator; 2448 DavCfgRepository *repo = dav_repository_new(config); 2449 2450 repo->name.value = cx_strdup_a(a, cx_str(name)); 2451 repo->url.value = cx_strdup_a(a, cx_str(url)); 2452 dav_repository_set_auth(config, repo, cx_str(user), cx_str(password)); 2453 2454 dav_config_add_repository(config, repo); 2455 2456 int ret = 0; 2457 if(store_config()) { 2458 fprintf(stderr, "Cannot write config.xml\n"); 2459 ret = -1; 2460 } else { 2461 printf("\nAdded repository: %s (%s)\n", name, url); 2462 } 2463 2464 free(name); 2465 free(url); 2466 if(user) { 2467 free(user); 2468 } 2469 if(password) { 2470 free(password); 2471 } 2472 2473 return ret; 2474 } 2475 2476 int cmd_remove_repository(CmdArgs *args) { 2477 if(args->argc < 1) { 2478 fprintf(stderr, "Too few arguments\n"); 2479 fprintf(stderr, "Usage: dav remove-repository <name...>\n"); 2480 return -1; 2481 } 2482 2483 DavConfig *config = get_config(); 2484 2485 DavBool store = FALSE; 2486 for(int i = 0 ; i < args->argc ; i++) { 2487 cxstring reponame = cx_str(args->argv[i]); 2488 DavCfgRepository* repo = dav_config_get_repository(config, reponame); 2489 if(repo) { 2490 dav_repository_remove_and_free(config, repo); 2491 store = TRUE; 2492 } else { 2493 fprintf(stderr, "Repository %s does not exist - skipped.\n", 2494 reponame.ptr); 2495 return -1; 2496 } 2497 } 2498 2499 if(store) { 2500 return store_config(); 2501 } else { 2502 return -1; 2503 } 2504 } 2505 2506 int cmd_list_repositories(void) { 2507 DavConfig *config = get_config(); 2508 if(!config) { 2509 return 1; 2510 } 2511 for(DavCfgRepository *repo=config->repositories;repo;repo=repo->next) { 2512 printf("%.*s\n", (int)repo->name.value.length, repo->name.value.ptr); 2513 } 2514 return 0; 2515 } 2516 2517 int cmd_repository_url(CmdArgs *args) { 2518 if(args->argc != 1) { 2519 fprintf(stderr, "Too few arguments\n"); 2520 fprintf(stderr, "Usage: dav repository-url [-p] <name>\n"); 2521 return -1; 2522 } 2523 2524 cxstring reponame = cx_str(args->argv[0]); 2525 DavCfgRepository* repo = dav_config_get_repository(get_config(), reponame); 2526 if(repo) { 2527 cxstring url = cx_strcast(repo->url.value); 2528 if(repo->user.value.ptr && !cmd_getoption(args, "plain")) { 2529 int hostindex = 0; 2530 if(cx_strprefix(url, CX_STR("https://"))) { 2531 printf("https://"); 2532 hostindex = 8; 2533 } else if(cx_strprefix(url, CX_STR("http://"))) { 2534 printf("http://"); 2535 hostindex = 7; 2536 } 2537 printf("%.*s", (int)repo->user.value.length, repo->user.value.ptr); 2538 if(repo->password.value.ptr) { 2539 cxmutstr pw_decoded = dav_repository_get_decodedpassword(repo); 2540 CURL *curl = curl_easy_init(); 2541 char *pw = curl_easy_escape( 2542 curl, 2543 pw_decoded.ptr, 2544 pw_decoded.length); 2545 printf(":%s", pw); 2546 curl_free(pw); 2547 curl_easy_cleanup(curl); 2548 free(pw_decoded.ptr); 2549 } 2550 putchar('@'); 2551 printf("%.*s", (int)url.length-hostindex, url.ptr+hostindex); 2552 } else { 2553 printf("%s", url.ptr); 2554 } 2555 if(url.ptr[url.length-1] != '/') { 2556 putchar('/'); 2557 } 2558 putchar('\n'); 2559 } else { 2560 fprintf(stderr, "Repository %s does not exist.\n", reponame.ptr); 2561 return -1; 2562 } 2563 return 0; 2564 } 2565 2566 2567 typedef int(*sscmd_func)(CmdArgs *, PwdStore *, void *userdata); 2568 2569 static int secretstore_after_decrypt( 2570 CmdArgs *args, 2571 PwdStore *secrets, 2572 sscmd_func cb, 2573 void *userdata); 2574 2575 2576 /* 2577 * opens the secret store, executes a callback func before and after 2578 * decryption 2579 * Aborts if a callback returns 1 2580 */ 2581 static int secretstore_cmd( 2582 CmdArgs *args, 2583 DavBool create, 2584 sscmd_func beforedecrypt, 2585 sscmd_func afterdecrypt, 2586 void *userdata) 2587 { 2588 PwdStore *secrets = get_pwdstore(); 2589 if(!secrets) { 2590 if(create) { 2591 secrets = pwdstore_new(); 2592 } else { 2593 return 1; 2594 } 2595 } 2596 2597 int ret = 0; 2598 if(beforedecrypt) { 2599 ret = beforedecrypt(args, secrets, userdata); 2600 if(ret) { 2601 afterdecrypt = NULL; // exit 2602 } 2603 } 2604 2605 if(afterdecrypt) { 2606 ret = secretstore_after_decrypt(args, secrets, afterdecrypt, userdata); 2607 } 2608 2609 pwdstore_free(secrets); 2610 2611 return ret; 2612 } 2613 2614 static int secretstore_after_decrypt( 2615 CmdArgs *args, 2616 PwdStore *secrets, 2617 sscmd_func cb, 2618 void *userdata) 2619 { 2620 char *master_pw = util_password_input("Master password: "); 2621 if(!master_pw) { 2622 fprintf(stderr, "Error: master password required.\nAbort.\n"); 2623 return 1; 2624 } 2625 2626 int err = pwdstore_setpassword(secrets, master_pw); 2627 free(master_pw); 2628 if(err) { 2629 fprintf(stderr, "Error: Cannot generate key from password.\nAbort.\n"); 2630 return 1; 2631 } 2632 2633 if(pwdstore_decrypt(secrets)) { 2634 fprintf(stderr, "Error: Cannot decrypt secrets store.\nAbort.\n"); 2635 return 1; 2636 } 2637 2638 return cb(args, secrets, userdata); 2639 } 2640 2641 static int cmd_ss_add_user(CmdArgs *Args, PwdStore *secrets, void *userdata) { 2642 char *id = assistant_getcfg("Credentials identifier"); 2643 if(!id) { 2644 fprintf(stderr, "Identifier required.\n"); 2645 return 1; 2646 } 2647 if(pwdstore_get(secrets, id)) { 2648 fprintf(stderr, "Credentials with this id already exist.\n"); 2649 return 1; 2650 } 2651 2652 // get user name and password (required) 2653 char *user = assistant_getcfg("User"); 2654 char *password = util_password_input("Password: "); 2655 2656 // optionally, get one or more locations 2657 char *location = NULL; 2658 CxList *locations = cxLinkedListCreateSimple(CX_STORE_POINTERS); 2659 locations->simple_destructor = free; 2660 while((location = assistant_getoptcfg("Location"))) { 2661 cxListAdd(locations, location); 2662 } 2663 2664 int ret = 1; 2665 if(user && password) { 2666 pwdstore_put_index(secrets, id, locations); 2667 pwdstore_put(secrets, id, user, password); 2668 ret = pwdstore_save(secrets); 2669 if(ret) { 2670 fprintf(stderr, "Error: saving srcrets store failed.\n"); 2671 } 2672 } 2673 2674 if(id) free(id); 2675 if(user) free(user); 2676 if(password) free(password); 2677 2678 cxListDestroy(locations); 2679 2680 return ret; 2681 } 2682 2683 int cmd_add_user(CmdArgs *args) { 2684 return secretstore_cmd(args, TRUE, NULL, cmd_ss_add_user, NULL); 2685 } 2686 2687 /* 2688 * called before the secret store is decrypted 2689 */ 2690 static int cmd_ss_list_users_bc(CmdArgs *Args, PwdStore *secrets, int *ret) { 2691 if(secrets->index->size == 0) { 2692 return 1; // abort, because the secret store is empty 2693 } 2694 // set ret to 1, because decrypt could fail and this should be an error 2695 *ret = 1; 2696 return 0; 2697 } 2698 2699 /* 2700 * called after the secret store is decrypted 2701 */ 2702 static int cmd_ss_list_users(CmdArgs *args, PwdStore *secrets, int *ret) { 2703 *ret = 0; 2704 2705 CxList *list = secrets->locations; 2706 for(int i=0;i<2;i++) { 2707 if(list) { 2708 CxIterator i = cxListIterator(list); 2709 cx_foreach(PwdIndexEntry*, index, i) { 2710 PwdEntry *e = cxMapGet(secrets->ids, cx_hash_key_str(index->id)); 2711 if(e) { 2712 printf("Id: %s\n", e->id); 2713 printf("User: %s\n", e->user); 2714 if(index->locations) { 2715 CxIterator loc_iter = cxListIterator(index->locations); 2716 cx_foreach(char *, location, loc_iter) { 2717 printf("Location: %s\n", location); 2718 } 2719 printf("\n"); 2720 } 2721 } else { 2722 // broken index 2723 fprintf(stderr, 2724 "Warning: id ''%s'' not in secret store.\n", 2725 index->id); 2726 } 2727 } 2728 } 2729 list = secrets->noloc; 2730 } 2731 2732 2733 return 0; 2734 } 2735 2736 int cmd_list_users(CmdArgs *args) { 2737 int ret = 0; 2738 secretstore_cmd(args, FALSE, (sscmd_func)cmd_ss_list_users_bc, (sscmd_func)cmd_ss_list_users, &ret); 2739 return ret; 2740 } 2741 2742 2743 static int cmd_ss_remove_user(CmdArgs *args, PwdStore *secrets, void *ud) { 2744 char *id = assistant_getcfg("Credentials identifier"); 2745 if(!id) { 2746 fprintf(stderr, "Identifier required.\n"); 2747 return 1; 2748 } 2749 if(!pwdstore_get(secrets, id)) { 2750 fprintf(stderr, "Credentials with this id doesn''t exist.\n"); 2751 free(id); 2752 return 1; 2753 } 2754 2755 pwdstore_remove_entry(secrets, id); 2756 2757 int ret = pwdstore_save(secrets); 2758 if(ret) { 2759 fprintf(stderr, "Error: saving srcrets store failed.\n"); 2760 } 2761 free(id); 2762 return ret; 2763 } 2764 2765 int cmd_remove_user(CmdArgs *args) { 2766 return secretstore_cmd(args, FALSE, NULL, cmd_ss_remove_user, NULL); 2767 } 2768 2769 static void secrets_print_user_info(PwdStore *secrets, const char *id) { 2770 PwdEntry *entry = pwdstore_get(secrets, id); 2771 if(!entry) { 2772 return; 2773 } 2774 2775 PwdIndexEntry *index = cxMapGet(secrets->index, cx_hash_key_str(id)); 2776 if(!index) { 2777 return; 2778 } 2779 2780 printf("Id: %s\n", entry->id); 2781 printf("User: %s\n", entry->user); 2782 if(index->locations) { 2783 CxIterator loc_iter = cxListIterator(index->locations); 2784 cx_foreach(char *, location, loc_iter) { 2785 printf("Location: %s\n", location); 2786 } 2787 } 2788 } 2789 2790 static void secrets_remove_location(PwdIndexEntry *index) { 2791 if(!index->locations || index->locations->size == 0) { 2792 printf("no locations\n"); 2793 return; 2794 } 2795 2796 printf("0: abort\n"); 2797 int i = 1; 2798 CxIterator loc_iter = cxListIterator(index->locations); 2799 cx_foreach(char *, location, loc_iter) { 2800 printf("%d: %s\n", i, location); 2801 i++; 2802 } 2803 2804 char *input = assistant_getcfg("Choose location"); 2805 if(!input) { 2806 return; 2807 } 2808 2809 int64_t ln = 0; 2810 if(util_strtoint(input, &ln) && (ln >= 0 && ln < i)) { 2811 if(ln == 0) { 2812 return; 2813 } else { 2814 char *location = cxListAt(index->locations, ln - 1); 2815 if(location) { 2816 free(location); 2817 cxListRemove(index->locations, ln - 1); 2818 } 2819 } 2820 } else { 2821 printf("illegal input, choose 0 - %d\n", i-1); 2822 secrets_remove_location(index); // try again 2823 } 2824 } 2825 2826 static int cmd_ss_edit_user(CmdArgs *args, PwdStore *secrets, void *ud) { 2827 char *id = assistant_getcfg("Credentials identifier"); 2828 if(!id) { 2829 fprintf(stderr, "Identifier required.\n"); 2830 return 1; 2831 } 2832 PwdEntry *entry = pwdstore_get(secrets, id); 2833 PwdIndexEntry *index = cxMapGet(secrets->index, cx_hash_key_str(id)); 2834 if(!entry || !index) { 2835 fprintf(stderr, "Credentials with this id doesn''t exist.\n"); 2836 return 1; 2837 } 2838 2839 secrets_print_user_info(secrets, id); 2840 2841 int save = 0; 2842 int loop = 1; 2843 while(loop) { 2844 printf("\n"); 2845 printf("0: change user name\n"); 2846 printf("1: change password\n"); 2847 printf("2: add location\n"); 2848 printf("3: remove location\n"); 2849 printf("4: list locations\n"); 2850 printf("5: save and exit\n"); 2851 printf("6: exit without saving\n"); 2852 2853 char *opt = assistant_getcfg("Choose action"); 2854 if(!opt) { 2855 break; 2856 } 2857 int64_t mnu = 0; 2858 if(util_strtoint(opt, &mnu) && (mnu >= 0 && mnu <= 6)) { 2859 printf("\n"); 2860 switch(mnu) { 2861 case 0: { 2862 // change user name 2863 char *user = assistant_getcfg("User"); 2864 if(user) { 2865 if(entry->user) { 2866 free(entry->user); 2867 } 2868 entry->user = user; 2869 } 2870 break; 2871 } 2872 case 1: { 2873 // change password 2874 char *password = util_password_input("Password: "); 2875 if(password) { 2876 if(entry->password) { 2877 free(entry->password); 2878 } 2879 entry->password = password; 2880 } 2881 break; 2882 } 2883 case 2: { 2884 // add location 2885 char *location = assistant_getoptcfg("Location"); 2886 if(location) { 2887 cxListAdd(index->locations, location); 2888 } 2889 break; 2890 } 2891 case 3: { 2892 // remove location 2893 secrets_remove_location(index); 2894 break; 2895 } 2896 case 4: { 2897 // list locations 2898 if(!index->locations || index->locations->size == 0) { 2899 printf("no locations\n"); 2900 } else { 2901 CxIterator i = cxListIterator(index->locations); 2902 cx_foreach(char *, location, i) { 2903 printf("Location: %s\n", location); 2904 } 2905 } 2906 break; 2907 } 2908 case 5: { 2909 // save and exit 2910 loop = 0; 2911 save = 1; 2912 break; 2913 } 2914 case 6: { 2915 // exit without saving 2916 loop = 0; 2917 break; 2918 } 2919 } 2920 } else { 2921 printf("illegal input, choose 0 - 5\n"); 2922 } 2923 free(opt); 2924 } 2925 2926 int ret = 0; 2927 if(save) { 2928 ret = pwdstore_save(secrets); 2929 if(ret) { 2930 fprintf(stderr, "Error: saving srcrets store failed.\n"); 2931 } 2932 } 2933 return ret; 2934 } 2935 2936 int cmd_edit_user(CmdArgs *args) { 2937 return secretstore_cmd(args, FALSE, NULL, cmd_ss_edit_user, NULL); 2938 } 2939 2940 2941 static int cmd_ss_set_master_pw(CmdArgs *args, PwdStore *secrets, void *ud) { 2942 char *new_master_pw = util_password_input("New master password: "); 2943 int ret = pwdstore_setpassword(secrets, new_master_pw); 2944 if(ret) { 2945 fprintf(stderr, "Error: failed to set new master password\n"); 2946 } 2947 2948 ret = pwdstore_save(secrets); 2949 if(ret) { 2950 fprintf(stderr, "Error: saving srcrets store failed.\n"); 2951 } 2952 return ret; 2953 } 2954 2955 int cmd_set_master_password(CmdArgs *args) { 2956 return secretstore_cmd(args, FALSE, NULL, cmd_ss_set_master_pw, NULL); 2957 } 2958 2959 static char** read_args_from_stdin(int *argc) { 2960 // read stdin into buffer 2961 CxBuffer *in = cxBufferCreate(NULL, 1024, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND); 2962 cx_stream_copy(stdin, in, (cx_read_func)fread, (cx_write_func)cxBufferWrite); 2963 2964 // split input into lines 2965 ssize_t count = 0; 2966 cxmutstr *lines; 2967 count = cx_strsplit_ma(cxDefaultAllocator, cx_mutstrn(in->space, in->pos), CX_STR("\n"), INT_MAX, &lines); 2968 2969 char **args = NULL; 2970 if(count > 0) { 2971 args = calloc(count, sizeof(char*)); 2972 for(int i=0;i<count;i++) { 2973 args[i] = lines[i].ptr; 2974 } 2975 free(lines); 2976 2977 *argc = count; 2978 } else { 2979 *argc = 0; 2980 } 2981 2982 // cleanup 2983 cxBufferFree(in); 2984 2985 return args; 2986 } 2987 2988 int cmd_complete(CmdArgs *args) { 2989 if(args->argc != 1) { 2990 return 1; 2991 } 2992 char *index_str = args->argv[0]; 2993 int64_t index = 0; 2994 if(!util_strtoint(index_str, &index)) { 2995 return 1; 2996 } 2997 2998 // The completion bash script passes the input words to stdin 2999 int comp_argc; 3000 char **comp_argv = read_args_from_stdin(&comp_argc); 3001 3002 // Try to parse the args 3003 char *cmd = NULL; 3004 if(comp_argc > 1) { 3005 cmd = comp_argv[1]; 3006 } 3007 CmdArgs *comp_args = cmd_parse_args(comp_argc - 2, comp_argv + 2); 3008 if(comp_args) { 3009 // check whether the arglist contains options 3010 if(comp_args->argc + 2 != comp_argc) { 3011 // index points to the arg in the raw arglist, however we have to 3012 // know the index for this item in comp_args->argv 3013 // any arg that is an option or an option value creates a 3014 // difference between the two lists 3015 3016 // adjust index to comp_args->argv 3017 int j = 0; 3018 for(int i=0;i<comp_argc-2;i++) { 3019 if(index == i-2) { 3020 break; 3021 } 3022 3023 if(strcmp(comp_argv[i+2], comp_args->argv[j])) { 3024 index--; 3025 } else { 3026 j++; 3027 } 3028 } 3029 } 3030 } else { 3031 comp_args = NULL; 3032 } 3033 3034 // generate output for shell completion 3035 int ret = 1; 3036 if(comp_args) { 3037 ret = shell_completion(cmd, comp_args, index); 3038 } 3039 3040 // cleanup 3041 cmd_args_free(comp_args); 3042 free(comp_argv); 3043 return ret; 3044 3045 } 3046 3047 int shell_completion(char *cmd, CmdArgs *args, int index) { 3048 if(index == 1) { 3049 cxstring prefix = { NULL, 0 }; 3050 if(cmd) { 3051 prefix = cx_str(cmd); 3052 } 3053 for(int i=0;;i++) { 3054 char *str = cmdusageinfo[i]; 3055 if(!str) { 3056 break; 3057 } 3058 int len = (int)strlen(str); 3059 int maxlen = len; 3060 for(int w=0;w<len;w++) { 3061 if(str[w] == ' ') { 3062 maxlen = w; 3063 break; 3064 } 3065 } 3066 if(prefix.ptr) { 3067 if(!cx_strprefix(cx_strn(str, maxlen), prefix)) { 3068 continue; 3069 } 3070 } 3071 printf("%.*s\n", (int)maxlen, str); 3072 } 3073 return 0; 3074 } 3075 3076 if(!strcmp(cmd, "date")) { 3077 return 0; 3078 } 3079 3080 // get already typed URL or NULL, if the user hasn't started typing yet 3081 char *url = args->argc > 0 ? args->argv[0] : NULL; 3082 3083 //printf("index: {%s}\n", args->argv[0]); 3084 //printf("dav: {%s}\n", args->argv[1]); 3085 //printf("cmd: {%s}\n", cmd); 3086 //printf("url: {%s}\n", url); 3087 3088 if(index == 2) { 3089 // url completion 3090 return url_completion(args, url); 3091 } else if (index == 3) { 3092 if(!strcmp(cmd, "put") || !strcmp(cmd, "import")) { 3093 // file completion 3094 return 12; 3095 } else if(!strcmp(cmd, "copy") || !strcmp(cmd, "cp") || !strcmp(cmd, "move") || !strcmp(cmd, "mv")) { 3096 // url completion 3097 return url_completion(args, url); 3098 } 3099 } 3100 3101 return 0; 3102 } 3103 3104 int url_completion(CmdArgs *args, char *u) { 3105 cxstring url; 3106 url.ptr = u; 3107 url.length = u ? strlen(u) : 0; 3108 3109 // if the user wants the URL to be quoted, we conform to their wish 3110 // a null-char is an indicator, that the strings shall not be quoted 3111 char quote = '\0'; 3112 if(url.length > 0 && (url.ptr[0] == '\'' || url.ptr[0] == '\"' )) { 3113 quote = url.ptr[0]; 3114 3115 // for completing the url, we want to proceed without the quote 3116 url.ptr++; 3117 url.length--; 3118 3119 // the user may have also prepared the ending quote, remove it for now 3120 if (url.ptr[url.length-1] == quote) { 3121 url.length--; 3122 } 3123 } 3124 3125 // repo completion 3126 int repocomp = 1; 3127 for(int i=0;i<url.length;i++) { 3128 if(url.ptr[i] == '/') { 3129 repocomp = 0; 3130 break; 3131 } 3132 } 3133 if(repocomp) { 3134 DavConfig *config = get_config(); 3135 for(DavCfgRepository *repo=config->repositories; repo; repo=repo->next) { 3136 if(cx_strprefix(cx_strcast(repo->name.value), url)) { 3137 if(quote == '\0') { 3138 printf("%s/\n", repo->name.value.ptr); 3139 } else { 3140 printf("%c%s/%c\n", quote, repo->name.value.ptr, quote); 3141 } 3142 } 3143 3144 } 3145 } else { 3146 // url completion 3147 cxMapPut(args->options, cx_hash_key_str("noinput"), ""); 3148 3149 char *path = NULL; 3150 DavCfgRepository *repo = dav_config_url2repo(get_config(), url.ptr, &path); 3151 DavSession *sn = connect_to_repo(ctx, repo, path, request_auth, args); 3152 if(!sn) { 3153 return 0; 3154 } 3155 if(set_session_config(sn, args)) { 3156 return 0; 3157 } 3158 3159 size_t plen = strlen(path); 3160 3161 cxstring filter; 3162 char *lspath = NULL; 3163 if(path[plen-1] == '/') { 3164 lspath = strdup(path); 3165 filter = CX_STR(""); 3166 } else { 3167 lspath = util_parent_path(path); 3168 filter = cx_str(util_resource_name(path)); 3169 } 3170 3171 DavResource *ls = dav_query(sn, "select - from %s order by name", lspath); 3172 DavResource *elm = ls ? ls->children : NULL; 3173 while(elm) { 3174 cxstring name = cx_str(elm->name); 3175 if(cx_strprefix(name, filter)) { 3176 int space = 0; 3177 for(int i=0;i<name.length;i++) { 3178 if(name.ptr[i] == ' ') { 3179 space = 1; 3180 break; 3181 } 3182 } 3183 3184 CxBuffer *out = cxBufferCreate(NULL, 512, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND); 3185 cxBufferWrite(repo->name.value.ptr, repo->name.value.length, 1, out); 3186 if(space) { 3187 size_t l = strlen(elm->path); 3188 for(int i=0;i<l;i++) { 3189 // only if we do not quote, we have to escape 3190 char nextc = elm->path[i]; 3191 if(quote == '\0' && NULL != strchr( 3192 "!\"#$&''()*,;<>?[\\]^`{|}~ ", nextc)) { 3193 cxBufferPut(out, '\\'); 3194 } 3195 cxBufferPut(out, nextc); 3196 } 3197 } else { 3198 cxBufferPutString(out, elm->path); 3199 } 3200 if(elm->iscollection) { 3201 if(out->space[out->pos-1] != '/') { 3202 cxBufferPut(out, '/'); 3203 } 3204 } 3205 if (quote == '\0') { 3206 printf("%.*s\n", (int)out->pos, out->space); 3207 } else { 3208 printf("%c%.*s%c\n", 3209 quote, (int)out->pos, out->space, quote); 3210 } 3211 3212 cxBufferFree(out); 3213 } 3214 elm = elm->next; 3215 } 3216 3217 free(lspath); 3218 3219 dav_session_destroy(sn); 3220 } 3221 3222 return 10; 3223 } 3224