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