UNIXworkcode

/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright 2019 Olaf Wintermann. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include <assert.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdbool.h> #include <errno.h> #include <time.h> #include <sys/types.h> #ifndef _WIN32 #include <sys/wait.h> #include <unistd.h> #endif #include <cx/string.h> #include <cx/utils.h> #include <cx/printf.h> #include <cx/hash_map.h> #include <cx/linked_list.h> #include <libidav/utils.h> #include <libidav/crypto.h> #include <libidav/session.h> #include <libidav/xml.h> #include "config.h" #include "error.h" #include "assistant.h" #include "system.h" #include "finfo.h" #include "main.h" #include "connect.h" static DavContext *ctx; static int printxmlerror = 1; static void xmlerrorfnc(void * c, const char * msg, ... ) { if(printxmlerror) { va_list ap; va_start(ap, msg); vfprintf(stderr, msg, ap); va_end(ap); } } //define DO_THE_TEST //include <libidav/davqlparser.h> //include <libidav/davqlexec.h> //include "tags.h" //include <libidav/resource.h> void test(CmdArgs *a) { } int dav_main(int argc, char **argv); #ifdef _WIN32 #define strcasecmp _stricmp static char* wchar2utf8(const wchar_t *wstr, size_t wlen) { size_t maxlen = wlen * 4; char *ret = malloc(maxlen + 1); int ret_len = WideCharToMultiByte( CP_UTF8, 0, wstr, wlen, ret, maxlen, NULL, NULL); ret[ret_len] = 0; return ret; } int wmain(int argc, wchar_t **argv) { char **argv_utf8 = calloc(argc, sizeof(char*)); for(int i=0;i<argc;i++) { argv_utf8[i] = wchar2utf8(argv[i], wcslen(argv[i])); } int ret = dav_main(argc, argv_utf8); for(int i=0;i<argc;i++) { free(argv_utf8[i]); } free(argv_utf8); return ret; } #else int main(int argc, char **argv) { return dav_main(argc, argv); } #endif int dav_main(int argc, char **argv) { if(argc < 2) { fprintf(stderr, "Missing command\n"); print_usage(argv[0]); return -1; } putenv("LC_TIME=C"); char *cmd = argv[1]; CmdArgs *args = cmd_parse_args(argc - 2, argv + 2); if(!args) { print_usage(argv[0]); return -1; } if(cmd_getoption(args, "noinput")) { pwdstore_set_pwinput_func(NULL, NULL); } sys_init(); xmlGenericErrorFunc fnc = xmlerrorfnc; initGenericErrorDefaultFunc(&fnc); ctx = dav_context_new(); dav_add_namespace(ctx, "apache", "http://apache.org/dav/props/"); int cfgret = load_config(ctx); int ret = EXIT_FAILURE; printxmlerror = 0; #ifdef DO_THE_TEST test(args); return 0; #endif if(!strcmp(cmd, "check") || !strcmp(cmd, "check-config")) { if(!cfgret) { fprintf(stdout, "Configuration OK.\n"); ret = EXIT_SUCCESS; } else { /* no output, the warnings are written by load_config */ ret = EXIT_FAILURE; } } else if(!cfgret) { if(!strcasecmp(cmd, "list") || !strcasecmp(cmd, "ls")) { ret = cmd_list(args); } else if(!strcasecmp(cmd, "get")) { ret = cmd_get(args, FALSE); } else if(!strcasecmp(cmd, "cat")) { cxMapPut(args->options, cx_hash_key_str("output"), "-"); ret = cmd_get(args, FALSE); } else if(!strcasecmp(cmd, "edit")) { ret = cmd_edit(args); } else if(!strcasecmp(cmd, "put")) { ret = cmd_put(args, FALSE); } else if( !strcasecmp(cmd, "remove") || !strcasecmp(cmd, "rm") || !strcasecmp(cmd, "delete")) { ret = cmd_remove(args); } else if(!strcasecmp(cmd, "mkdir") || !strcasecmp(cmd, "mkcol")) { ret = cmd_mkdir(args); } else if(!strcasecmp(cmd, "copy") || !strcasecmp(cmd, "cp")) { ret = cmd_move(args, true); } else if(!strcasecmp(cmd, "move") || !strcasecmp(cmd, "mv")) { ret = cmd_move(args, false); } else if(!strcasecmp(cmd, "rename")) { ret = cmd_rename(args); } else if(!strcasecmp(cmd, "export")) { ret = cmd_get(args, TRUE); } else if(!strcasecmp(cmd, "import")) { ret = cmd_put(args, TRUE); } else if(!strcasecmp(cmd, "date")) { ret = cmd_date(args); } else if(!strcasecmp(cmd, "set-property")) { ret = cmd_set_property(args); } else if(!strcasecmp(cmd, "get-property")) { ret = cmd_get_property(args); } else if(!strcasecmp(cmd, "remove-property")) { ret = cmd_remove_property(args); } else if(!strcasecmp(cmd, "lock")) { ret = cmd_lock(args); } else if(!strcasecmp(cmd, "unlock")) { ret = cmd_unlock(args); } else if(!strcasecmp(cmd, "info")) { ret = cmd_info(args); } else if(!strcasecmp(cmd, "checkout")) { ret = cmd_checkout(args); } else if(!strcasecmp(cmd, "checkin")) { ret = cmd_checkin(args); } else if(!strcasecmp(cmd, "uncheckout")) { ret = cmd_uncheckout(args); } else if(!strcasecmp(cmd, "versioncontrol")) { ret = cmd_versioncontrol(args); } else if(!strcasecmp(cmd, "list-versions") || !strcasecmp(cmd, "lsv")) { ret = cmd_list_versions(args); } else if(!strcasecmp(cmd, "add-repository") || !strcasecmp(cmd, "add-repo")) { ret = cmd_add_repository(args); } else if(!strcasecmp(cmd, "remove-repository") || !strcasecmp(cmd, "remove-repo") || !strcasecmp(cmd, "rm-repo")) { ret = cmd_remove_repository(args); } else if(!strcasecmp(cmd, "list-repositories") || !strcasecmp(cmd, "list-repos")) { ret = cmd_list_repositories(); } else if(!strcasecmp(cmd, "repository-url") || !strcasecmp(cmd, "repo-url")) { ret = cmd_repository_url(args); } else if(!strcasecmp(cmd, "add-user")) { ret = cmd_add_user(args); } else if(!strcasecmp(cmd, "list-users")) { ret = cmd_list_users(args); } else if(!strcasecmp(cmd, "remove-user")) { ret = cmd_remove_user(args); } else if(!strcasecmp(cmd, "edit-user")) { ret = cmd_edit_user(args); } else if(!strcasecmp(cmd, "set-master-password") || !strcasecmp(cmd, "set-master-pw")) { ret = cmd_set_master_password(args); } else if(!strcasecmp(cmd, "version") || !strcasecmp(cmd, "-version") || !strcasecmp(cmd, "--version")) { fprintf(stderr, "dav %s\n", DAV_VERSION); } else if(!strcasecmp(cmd, "complete")) { ret = cmd_complete(args); } else { print_usage(argv[0]); } } dav_context_destroy(ctx); cmd_args_free(args); free_config(); xmlCleanupParser(); curl_global_cleanup(); sys_uninit(); return ret; } static char *cmdusageinfo[] = { "list [-altdepcR] [-u <date>] <url>", "get [-pcRK] [-o <file>] [-u <date>] [-V <version>] <url>", "put [-pcR] [-k <key>] [-L <lock>] <url> <file...>", "edit [-pc] [-k <key>] [-V <version>] [-L <lock>] <url>", "mkdir [-pc] [-k <key>] [-L <lock>] <url> [file...]", "remove [-pc] [-L <lock>] <url> [file...]", "copy [-pcO] [-L <lock>] <url> <url>", "move [-pcO] [-L <lock>] <url> <url>", "rename [-pcO] [-L <lock>] <url> <name>", "export [-pc] [-o <file>] [-u <date>] <url>", "import [-pc] [-k <key>] [-L <lock>] <url> <file>", "get-property [-pcx] [-V <version>] [-n <uri>] <url> <property>", "set-property [-pcx] [-L <lock>] [-n <uri>] <url> <property> [value]", "remove-property [-pc] [-n <uri>] <url> <property>", "lock [-pc] [-T timeout] <url>", "unlock [-pc] [-L <lock>] <url>", "info [-pc] [-V <version>] <url>", "date [url]", NULL }; char* find_usage_str(const char *cmd) { cxstring c = cx_str(cmd); for(int i=0;;i++) { char *str = cmdusageinfo[i]; if(!str) { break; } cxstring u = cx_str(str); if(cx_strprefix(u, c)) { return str; } } return NULL; } void print_usage(char *cmd) { fprintf(stderr, "Usage: %s command [options] arguments...\n\n", cmd); fprintf(stderr, "Commands:\n"); for(int i=0;;i++) { char *str = cmdusageinfo[i]; if(!str) { break; } fprintf(stderr, " %s\n", str); } fprintf(stderr, "Options:\n"); fprintf(stderr, " -k <key> Key to use for encryption\n"); fprintf(stderr, " -p Don''t encrypt or decrypt files\n"); fprintf(stderr, " -c Enable full encryption\n"); fprintf(stderr, " -R " "Recursively do the operation for all children\n"); fprintf(stderr, " -K Keep already present files\n"); fprintf(stderr, " -o <file> Write output to file (use ''-'' for stdout)\n"); fprintf( stderr, " -u <date> " "Get resources which are modified since the specified date\n"); fprintf(stderr, " -V <version> Download a specific version of a resource\n"); fprintf(stderr, " -a show all files\n"); fprintf(stderr, " -l print resources in long list format\n"); fprintf(stderr, " -t print content type\n"); fprintf(stderr, " -d order by last modified date\n"); fprintf(stderr, " -e show extended flags\n"); fprintf(stderr, " -O override resources\n"); fprintf(stderr, " -L <lock> specificy lock token\n"); fprintf(stderr, " -T <sec> timeout in seconds\n"); fprintf(stderr, " -n <uri> specify namespace uri\n"); fprintf(stderr, " -x xml property content\n"); fprintf(stderr, " -N disable authentication prompt (all commands)\n"); fprintf(stderr, " -i disable cert verification (all commands)\n"); fprintf(stderr, " -v verbose output (all commands)\n"); fprintf(stderr, "\n"); fprintf(stderr, "Advanced commands:\n"); fprintf(stderr, " versioncontrol list-versions checkout checkin uncheckout\n\n"); fprintf(stderr, "Config commands:\n"); fprintf(stderr, " add-repository remove-repository list-repositories repository-url\n"); fprintf(stderr, " add-user remove-user edit-user list-users set-master-password\n"); fprintf(stderr, " check-config\n"); fprintf(stderr, "\n"); fprintf(stderr, "Instead of an url you can pass a repository name " "with an optional path:\n"); fprintf(stderr, " <repository>/path/\n"); fprintf(stderr, "\n"); } static int set_session_config(DavSession *sn, CmdArgs *a) { char *plain = cmd_getoption(a, "plain"); char *crypt = cmd_getoption(a, "crypt"); if(plain && crypt) { fprintf(stderr, "Error: -p and -c option set\n"); return 1; } if (plain) { sn->flags &= ~DAV_SESSION_FULL_ENCRYPTION; } else if(crypt) { sn->flags |= DAV_SESSION_FULL_ENCRYPTION; } if (cmd_getoption(a, "verbose")) { curl_easy_setopt(sn->handle, CURLOPT_VERBOSE, 1L); curl_easy_setopt(sn->handle, CURLOPT_STDERR, stderr); sn->logfunc = dav_verbose_log; } return 0; } static void set_session_lock(DavSession *sn, CmdArgs *a) { char *locktoken = cmd_getoption(a, "lock"); if(locktoken) { DavLock *lock = dav_create_lock(sn, locktoken, NULL); dav_add_collection_lock(sn, "/", lock); } } int update_progress(DavResource *res, int64_t total, int64_t now, Progress *p) { int ret = 0; if(res != p->last_resource) { p->cur += p->last_res_total - p->last_res_cur; ret = 1; } else { p->cur += now - p->last_res_cur; } p->last_resource = res; p->last_res_cur = now; p->last_res_total = total; return ret; } void download_progress(DavResource *res, int64_t total, int64_t now, void *data) { Progress *p = data; int newres = update_progress(res, total, now, p); time_t newts = time(NULL); if((p->ts != newts)) { 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)); fflush(p->out); } p->ts = newts; } #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" #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" int cmd_list(CmdArgs *a) { if(a->argc != 1) { fprintf(stderr, "Too %s arguments\n", a->argc < 1 ? "few":"many"); fprintf(stderr, "Usage: dav %s\n", find_usage_str("list")); return -1; } char *url = a->argv[0]; char *path = NULL; DavCfgRepository *repo = dav_config_url2repo(get_config(), url, &path); DavSession *sn = connect_to_repo(ctx, repo, path, request_auth, a); if(set_session_config(sn, a)) { return -1; } char *update = cmd_getoption(a, "update"); char *date = cmd_getoption(a, "date"); time_t t = -1; if(update) { t = util_parse_lastmodified(update); } int depth = cmd_getoption(a, "recursive") ? -1 : 1; int ret = 0; DavResource *ls = dav_query( sn, date ? LIST_QUERY_ORDER_BY_DATE : LIST_QUERY_ORDER_BY_NAME, path, depth, t); if(ls) { // parameters void (*print_func)(DavResource*, char *, CmdArgs *); if(cmd_getoption(a, "list") || cmd_getoption(a, "extended")) { print_func = ls_print_list_elm; } else { print_func = ls_print_elm; } DavResource *child = ls->children; while(child) { print_func(child, path, a); child = child->next; } } else { print_resource_error(sn, path); ret = -1; } free(path); //free(base); dav_session_destroy(sn); return ret; } static char* ls_name(char *parent, char *path, int *len) { if(parent) { path += strlen(parent); } if(path[0] == '/') { path++; } int pathlen = strlen(path); if(path[pathlen-1] == '/') { pathlen--; } *len = pathlen; return path; } void ls_print_list_elm(DavResource *res, char *parent, CmdArgs *a) { int recursive = cmd_getoption(a, "recursive") ? 1 : 0; int show_all = cmd_getoption(a, "all") ? 1 : 0; if(res->name[0] == '.' && !show_all) { return; } char flags[16]; memset(flags, '-', 15); int type_width = 0; char *type = res->contenttype; if(res->iscollection) { flags[0] = 'd'; type = ""; } char *keyprop = dav_get_string_property_ns( res, DAV_NS, "crypto-key"); if(keyprop) { flags[1] = 'c'; } if(cmd_getoption(a, "extended")) { flags[6] = '\0'; if(dav_get_string_property(res, "D:lockdiscovery")) { flags[2] = 'l'; } char *executable = dav_get_string_property_ns( res, "http://apache.org/dav/props/", "executable"); if(executable && util_getboolean(executable)) { flags[3] = 'x'; } } else { flags[2] = '\0'; } if(cmd_getoption(a, "type")) { type_width = 20; } if(type == NULL || type_width == 0) { type = ""; } char *date = util_date_str(res->lastmodified); char *size = util_size_str(res->iscollection, res->contentlength); int namelen = strlen(res->name); char *name = recursive ? ls_name(parent, res->path, &namelen) : res->name; //char *name = recursive ? res->path+1 : res->name; printf( "%s %*s %10s %12s %.*s\n", flags, type_width, type, size, date, namelen, name); free(date); free(size); if(recursive) { DavResource *child = res->children; while(child) { //ls_print_list_elm(child, a); if(child->name[0] != '.' || show_all) { ls_print_list_elm(child, parent, a); } child = child->next; } } } void ls_print_elm(DavResource *res, char *parent, CmdArgs *a) { int recursive = cmd_getoption(a, "recursive") ? 1 : 0; int show_all = cmd_getoption(a, "all") ? 1 : 0; if(res->name[0] == '.' && !show_all) { return; } int namelen = strlen(res->name); char *name = recursive ? ls_name(parent, res->path, &namelen) : res->name; printf("%.*s\n", namelen, name); if(recursive) { DavResource *child = res->children; while(child) { ls_print_elm(child, parent, a); child = child->next; } } } static void free_getres(void *r) { GetResource *getres = r; free(getres->path); free(getres); } int cmd_get(CmdArgs *a, DavBool export) { if(a->argc != 1) { // TODO: change this, when get supports retrieval of multiple files fprintf(stderr, "Too %s arguments\n", a->argc < 1 ? "few":"many"); fprintf(stderr, "Usage: dav %s\n", find_usage_str("get")); return -1; } if(export) { cxMapPut(a->options, cx_hash_key_str("recursive"), ""); } char *url = a->argv[0]; char *path = NULL; DavCfgRepository *repo = dav_config_url2repo(get_config(), url, &path); DavSession *sn = connect_to_repo(ctx, repo, path, request_auth, a); if(set_session_config(sn, a)) { return -1; } char *progressfile = cmd_getoption(a, "progressfile"); Progress pdata; memset(&pdata, 0, sizeof(Progress)); if(progressfile) { if(!strcmp(progressfile, "-")) { pdata.out = stdout; pdata.isstdout = 1; } else { pdata.out = fopen(progressfile, "w"); } if(pdata.out) { dav_session_set_progresscallback(sn, download_progress, NULL, &pdata); } } char *update = cmd_getoption(a, "update"); time_t t = -1; if(update) { t = util_parse_lastmodified(update); if (t == 0) { fprintf(stderr, "Invalid date format. Possible formats are:\n" " RFC-1123 - example: Thu, 29 Nov 2012 21:35:35 GMT\n" " RFC-3339 - example: 2012-11-29T21:35:35Z\n"); return -1; } } int recursive = cmd_getoption(a, "recursive") ? 1 : 0; char *version = cmd_getoption(a, "version"); if(recursive && version) { fprintf(stderr, "-V option can only be used without -R option\n"); return -1; } DavResource *res; int depth = recursive ? -1 : 1; res = dav_query( sn, "select - from %s with depth = %d where iscollection or lastmodified > %t", path, depth, t); if(!res) { print_resource_error(sn, path); return -1; } if(!recursive && res->iscollection) { fprintf(stderr, "Resource %s is a collection.\n", res->path); fprintf(stderr, "Use the -R option to download collections.\n"); return -1; } if(version) { DavResource *vres = find_version(res, version); if(!vres) { fprintf(stderr, "Cannot find version ''%s'' for resource.\n", version); return -1; } dav_resource_free_all(res); res = vres; } /* * determine the output file * use stdout if the output file is - */ char *outfile = cmd_getoption(a, "output"); char *basepath = outfile; if(!outfile) { if(res->iscollection) { basepath = ""; } else { basepath = res->name; } if(export) { outfile = "-"; } } else if(export) { basepath = ""; } else if(res->iscollection && !strcmp(outfile, "-")) { fprintf( stderr, "Cannot write output to stdout " "if the requested resource is a collection.\n"); return -1; } // get list of resources CxList *reslist = cxLinkedListCreateSimple(CX_STORE_POINTERS); cxDefineDestructor(reslist, free_getres); uint64_t totalsize = 0; uint64_t rescount = 0; GetResource *getres = malloc(sizeof(GetResource)); getres->res = res; getres->path = strdup(basepath); char *structure = cmd_getoption(a, "structure"); // iterate over resource tree CxList *stack = cxLinkedListCreateSimple(CX_STORE_POINTERS); cxListInsert(stack, 0, getres); while(cxListSize(stack) > 0) { GetResource *g = cxListAt(stack, 0); cxListRemove(stack, 0); if(g->res->iscollection) { DavResource *child = g->res->children; while(child) { // add resource to stack size_t pathlen = strlen(g->path); GetResource *newres = malloc(sizeof(GetResource)); newres->res = child; newres->path = pathlen > 0 ? util_concat_path(g->path, child->name) : strdup(child->name); cxListInsert(stack, 0, newres); child = child->next; } } else { if(structure) { // download only directory structure // this is a hidden feature and will be replaced in the future continue; // skip non-collection resource } totalsize += g->res->contentlength; rescount++; } if(strlen(g->path) == 0) { free_getres(g); } else { cxListAdd(reslist, g); } } cxListDestroy(stack); // download resources pdata.total = totalsize; int ret; getfunc get; TarOutputStream *tout = NULL; if(export) { get = (getfunc)resource2tar; FILE *tarfile = strcmp(outfile, "-") ? fopen(outfile, "wb") : stdout; if(!tarfile) { perror("Cannot open tar output file"); return -1; } tout = tar_open(tarfile); } else { get = get_resource; } CxIterator iter = cxListIterator(reslist); cx_foreach(GetResource *, res_item, iter) { ret = get(repo, res_item, a, tout); if(ret) { break; } } if(export) { // close tar stream if(tar_close(tout)) { fprintf(stderr, "tar stream broken\n"); ret = -1; } } cxListDestroy(reslist); free(path); if(pdata.out && !pdata.isstdout) { fclose(pdata.out); } return ret; } static int file_seek(FILE *f, curl_off_t offset, int origin) { int ret = fseek(f, offset, origin); return ret == 0 ? CURL_SEEKFUNC_OK : CURL_SEEKFUNC_CANTSEEK; } static int check_encryption_key(CmdArgs *a, DavSession *sn) { // override the session key if the -k option is specified char *keyname = cmd_getoption(a, "key"); if(keyname) { DavKey *key = dav_context_get_key(ctx, keyname); if(key) { sn->key = key; } else { fprintf(stderr, "Key %s not found!\nAbort.\n", keyname); return 1; } /* * If a key is explicitly specified, we can safely assume that the user * wants to encrypt. For security reasons we report an error, if no * encryption is enabled. */ if(!DAV_IS_ENCRYPTED(sn)) { fprintf(stderr, "A key has been explicitly specified, but no " "encryption is requested.\n" "You have the following options:\n" " - pass ''-c'' as command line argument to request encryption\n" " - activate encryption in the config.xml\n" " - don''t use ''-k <key>'' " "(warning: encryption will NOT happen)\n"); return 1; } } // if encryption is requested, but we still don't know the key, report error if(DAV_IS_ENCRYPTED(sn) && !(sn->key)) { fprintf(stderr, "Encryption has been requested, " "but no default key is configured.\n" "You may specify a custom key with the ''-k'' option.\n"); return 1; } return 0; } int cmd_edit(CmdArgs *a) { #ifdef _WIN32 fprintf(stderr, "This feature is not supported on your platform.\n"); return -1; #else if(a->argc != 1) { fprintf(stderr, "Too %s arguments\n", a->argc < 1 ? "few":"many"); fprintf(stderr, "Usage: dav %s\n", find_usage_str("edit")); return -1; } char *url = a->argv[0]; char *path = NULL; DavCfgRepository *repo = dav_config_url2repo(get_config(), url, &path); DavSession *sn = connect_to_repo(ctx, repo, path, request_auth, a); if(set_session_config(sn, a)) { return -1; } set_session_lock(sn, a); if(check_encryption_key(a, sn)) { return -1; } char *version = cmd_getoption(a, "version"); DavResource *res; res = dav_resource_new(sn, path); int fresh_resource = !dav_exists(res); if(fresh_resource) { // create the resource to check if the resource can be created // we don't want the user to invest time in editing and fail afterwards if(dav_create(res)) { fprintf(stderr, "Resource does not exist and cannot be created.\n"); return -1; } } else { if(!res) { print_resource_error(sn, path); return -1; } if(res->iscollection) { fprintf(stderr, "Resource %s is a collection " "and cannot be opened in an editor.\n", res->path); return -1; } if(version) { DavResource *vres = find_version(res, version); if(!vres) { fprintf(stderr, "Cannot find version ''%s'' for resource.\n", version); return -1; } dav_resource_free_all(res); res = vres; } } // get temp dir char* envtmp = getenv("TMPDIR"); char* outfile; if(envtmp) { size_t len = strlen(envtmp); outfile = malloc(len+24); memcpy(outfile, envtmp, len+1); if(outfile[len-1] != '/') { outfile[len] = '/'; outfile[len+1] = 0; } } else { outfile = malloc(24); strncpy(outfile, "/tmp/", 24); } // check temp dir and fall back to $HOME if(access(outfile, W_OK)) { char* home = getenv("HOME"); if(home) { size_t len = strlen(home); outfile = malloc(len+24); memcpy(outfile, home, len+1); if(outfile[len-1] != '/') { outfile[len] = '/'; outfile[len+1] = 0; } } else { // fallback did not work, report last error from access() perror("Cannot write to temporary location"); free(outfile); return -1; } } // create temp file and open it outfile = strncat(outfile, ".dav-edit-XXXXXX", 23); int tmp_fd = mkstemp(outfile); if(tmp_fd < 0) { perror("Cannot open temporary file"); return -1; } // get resource if(!fresh_resource) { FILE* tmp_stream = sys_fopen(outfile, "wb"); if(!tmp_stream) { perror("Cannot open temporary file"); free(outfile); close(tmp_fd); return -1; } if(dav_get_content(res, tmp_stream, (dav_write_func)fwrite)) { print_resource_error(sn, path); free(outfile); close(tmp_fd); sys_unlink(outfile); return -1; } fclose(tmp_stream); } // remember time s.t. we can later check if the file has changed SYS_STAT tmp_stat; if(sys_stat(outfile, &tmp_stat)) { perror("Cannot stat temporary file"); free(outfile); close(tmp_fd); sys_unlink(outfile); return -1; } time_t dl_mtime = tmp_stat.st_mtime; // open in editor char* default_editor = "vi"; char* editor = getenv("EDITOR"); if(!editor) editor = default_editor; char* viargs[3] = {editor, outfile, NULL}; int ret = 0; pid_t pid = fork(); if(pid < 0) { perror("Cannot create process for editor"); ret = -1; } else if(pid == 0) { if(execvp(viargs[0], viargs)) { perror("Opening the editor failed"); return -1; } } else { int status = -1; ret = waitpid(pid, &status, 0); if(ret < 0) { perror("Error waiting for editor"); } else if(WEXITSTATUS(status)) { fprintf(stderr, "Editor closed abnormally - file will not be uploaded.\n"); ret = -1; } else { // check if the file has changed if (sys_stat(outfile, &tmp_stat)) { // very rare case when someone concurrently changes permissions perror("Cannot stat temporary file"); ret = -1; } else if (dl_mtime < tmp_stat.st_mtime) { // upload changed file FILE* tmp_stream = sys_fopen(outfile, "rb"); if(!tmp_stream) { perror("Cannot open temporary file"); ret = -1; } else { dav_set_content(res, tmp_stream, (dav_read_func)fread, (dav_seek_func)file_seek); dav_set_content_length(res, tmp_stat.st_size); ret = dav_store(res); fclose(tmp_stream); if(ret) { print_resource_error(sn, path); } } } else { printf("No changes by user - file will not be uploaded.\n"); ret = 0; } } } close(tmp_fd); if(ret) { // if someone went wrong, we are most likely unable to unlink anyway fprintf(stderr, "File location: %s\n", outfile); } else { sys_unlink(outfile); } free(outfile); free(path); return ret; #endif } int get_resource(DavCfgRepository *repo, GetResource *getres, CmdArgs *a, void *unused) { DavResource *res = getres->res; char *out = getres->path; if(res->iscollection) { printf("get: %s\n", res->path); int ret = sys_mkdir(out); if(ret != 0 && errno != EEXIST) { fprintf(stderr, "Cannot create directory ''%s'': ", out); perror(""); return 1; } return 0; } int isstdout = !strcmp(out, "-"); if(cmd_getoption(a, "keep") && !isstdout) { SYS_STAT s; if(sys_stat(out, &s)) { if(errno != ENOENT) { perror("stat"); } } else { if(cmd_getoption(a, "recursive")) { printf("skip: %s\n", res->path); } return 0; } } // print some status message in recursive mode if(cmd_getoption(a, "recursive")) { printf("get: %s\n", res->path); } FILE *fout = isstdout ? stdout : sys_fopen(out, "wb"); if(!fout) { fprintf(stderr, "cannot open output file\n"); return -1; } int ret = dav_get_content(res, fout, (dav_write_func)fwrite); fclose(fout); if(ret && strcmp(out, "-")) { print_resource_error(res->session, res->path); //if(strcmp(out, "-")) { // unlink(out); //} } return 0; } #define DEFAULT_DIR_MODE T_IRUSR | T_IWUSR | T_IXUSR | T_IRGRP | T_IXGRP | T_IROTH | T_IXOTH #define DEFAULT_FILE_MODE T_IRUSR | T_IWUSR | T_IRGRP | T_IROTH int resource2tar(DavCfgRepository *repo, GetResource *res, CmdArgs *a, TarOutputStream *tar) { DavResource *d = res->res; if(d->iscollection) { fprintf(stderr, "add d: %s\n", res->path); return tar_add_dir(tar, res->path, DEFAULT_DIR_MODE, d->lastmodified); } fprintf(stderr, "add f: %s\n", res->path); // add tar file header if(tar_begin_file(tar, res->path, DEFAULT_FILE_MODE, d->contentlength, d->lastmodified)) { fprintf(stderr, "TAR Error: %s\n", tar_error2str(tar->error)); return -1; } if(dav_get_content(d, tar, (dav_write_func)tar_fwrite)) { print_resource_error(d->session, d->path); return -1; } // download content return tar_end_file(tar); } int cmd_put(CmdArgs *a, DavBool import) { if(a->argc < 2) { fprintf(stderr, "Too few arguments\n"); fprintf(stderr, "Usage: dav %s\n", find_usage_str(import ? "import" : "put")); return -1; } DavBool use_stdin = FALSE; for(int i=1;i<a->argc;i++) { if(!strcmp(a->argv[i], "-")) { if(use_stdin) { fprintf(stderr, "Error: stdin can only occur once in input list\n"); return -1; } else { use_stdin = TRUE; } } } if(import) { cxMapPut(a->options, cx_hash_key_str("resursive"), ""); } char *url = a->argv[0]; char *path = NULL; DavCfgRepository *repo = dav_config_url2repo(get_config(), url, &path); DavSession *sn = connect_to_repo(ctx, repo, path, request_auth, a); if(set_session_config(sn, a)) { return -1; } set_session_lock(sn, a); if(check_encryption_key(a, sn)) { // TODO: free return -1; } DavBool printfile = FALSE; DavBool ignoredirerr = FALSE; if(a->argc > 2) { printfile = TRUE; ignoredirerr = TRUE; } else if(cmd_getoption(a, "recursive")) { printfile = TRUE; } char *finfo_str = cmd_getoption(a, "finfo"); uint32_t finfo = 0; if(finfo_str) { finfo = parse_finfo_settings(finfo_str, NULL); } int ret; for(int i=1;i<a->argc;i++) { char *file = a->argv[i]; if(!import) { if(!strcmp(file, "-")) { FILE *in = stdin; ret = put_file(repo, a, sn, path, "stdin", 0, NULL, in, 0); } else { ret = put_entry( repo, a, sn, path, file, finfo, TRUE, printfile, ignoredirerr); } } else { ret = put_tar(repo, a, sn, file, path); } if(ret) { break; } } free(path); dav_session_destroy(sn); return ret; } #if !defined(S_ISREG) && defined(S_IFMT) && defined(S_IFREG) #define S_ISREG(m) (((m) & S_IFMT) == S_IFREG) #endif #ifdef _WIN32 #ifndef S_ISDIR #define S_ISDIR(mode) ((mode) & _S_IFMT) == _S_IFDIR #define S_ISREG(mode) ((mode) & _S_IFMT) == _S_IFREG #endif #endif int put_entry( DavCfgRepository *repo, CmdArgs *a, DavSession *sn, char *path, char *file, uint32_t finfo, DavBool root, DavBool printfile, DavBool ignoredirerr) { int recursive = cmd_getoption(a, "recursive") ? 1 : 0; SYS_STAT s; if(sys_stat(file, &s)) { perror("stat"); fprintf(stderr, "cannot stat file %s\n", file); return -1; } int ret = 0; if(S_ISDIR(s.st_mode)) { if(!recursive) { if(ignoredirerr) { printf("skip: %s\n", file); } else { fprintf( stderr, "%s is a directory.\nUse the -R option to upload directories.\n", file); return 1; } } if(!root) { printf("mkcol: %s\n", file); DavResource *res = dav_resource_new(sn, path); res->iscollection = TRUE; if(!dav_exists(res)) { if(dav_create(res)) { fprintf(stderr, "Cannot create collection %s\n", path); print_resource_error(sn, res->path); dav_resource_free(res); return 1; } } dav_resource_free(res); } SYS_DIR dir = sys_opendir(file); if(!dir) { // error } SysDirEnt *entry; int nument = 0; while((entry = sys_readdir(dir)) != NULL) { if(!strcmp(entry->name, ".") || !strcmp(entry->name, "..")) { continue; } nument++; char *entry_file = util_concat_path(file, entry->name); char *entry_path = util_concat_path(path, entry->name); int r = put_entry( repo, a, sn, entry_path, entry_file, finfo, FALSE, printfile, ignoredirerr); free(entry_path); free(entry_file); if(r) { ret = 1; break; } } sys_closedir(dir); } else if(S_ISREG(s.st_mode)) { if(printfile) { printf("put: %s\n", file); } FILE *in = sys_fopen(file, "rb"); if(!in) { fprintf(stderr, "cannot open input file\n"); return -1; } const char *filename = util_resource_name(file); //path = util_concat_path(path, filename); ret = put_file(repo, a, sn, path, filename, finfo, file, in, s.st_size); //free(path); fclose(in); } return ret; } int put_tar(DavCfgRepository *repo, CmdArgs *a, DavSession *sn, char *tarfile, char *path) { int isstdin = !strcmp(tarfile, "-"); FILE *in = isstdin ? stdin : fopen(tarfile, "rb"); if(!in) { perror("Cannot open tar file"); return -1; } DavResource *col = dav_query(sn, "select - from %s", path); if(!col) { if(sn->error == DAV_NOT_FOUND) { col = dav_resource_new(sn, path); col->iscollection = TRUE; if(dav_create(col)) { print_resource_error(sn, path); return -1; } } else { print_resource_error(sn, path); return -1; } } else if(!col->iscollection) { fprintf(stderr, "%s is not a collection\n", col->href); return -1; } int ret = 0; TarInputStream *tar = tar_inputstream_open(in); TarEntry *e = NULL; while((e = tar_read_entry(tar)) != NULL) { char *newpath = util_concat_path(path, e->path); if(e->type == TAR_TYPE_FILE) { fprintf(stderr, "put: %s\n", e->path); DavResource *res = dav_resource_new(sn, newpath); dav_set_content(res, tar, (dav_read_func)tar_fread, (dav_seek_func)tar_seek); dav_set_content_length(res, (size_t)e->size); if(dav_store(res)) { print_resource_error(sn, res->path); fprintf(stderr, "Cannot upload file.\n"); if(sn->errorstr) { fprintf(stderr, "%s\n", sn->errorstr); } return -1; } } else if(e->type == TAR_TYPE_DIRECTORY) { printf("mkcol: %s\n", e->path); DavResource *res = dav_resource_new(sn, newpath); res->iscollection = TRUE; if(!dav_exists(res)) { if(dav_create(res)) { fprintf(stderr, "Cannot create collection %s\n", newpath); print_resource_error(sn, res->path); ret = 1; free(newpath); break; } } } else { fprintf(stderr, "skip: %s\n", e->path); } free(newpath); } if(tar->error != TAR_OK) { ret = -1; } if(!isstdin) { fclose(in); } return ret; } int put_file( DavCfgRepository *repo, CmdArgs *a, DavSession *sn, const char *path, const char *name, uint32_t finfo, const char *fpath, FILE *in, off_t len) { DavResource *res = dav_query(sn, "select - from %s", path); if(!res) { if(sn->error == DAV_NOT_FOUND) { res = dav_resource_new(sn, path); if(dav_create(res)) { fprintf(stderr, "Cannot create resource.\n"); return -1; } } else { print_resource_error(sn, path); return -1; } } else if(res->iscollection) { // TODO: free res char *newpath = util_concat_path(path, name); if (!strcmp(path, newpath)) { // TODO: free res fprintf(stderr, "Cannot put file, because a collection with " "that name already exists.\n"); free(newpath); return -1; } path = newpath; res = dav_resource_new(sn, path); int ret = put_file(repo, a, sn, res->path, NULL, finfo, fpath, in, len); // TODO: free res free(newpath); return ret; } if(resource_set_finfo(fpath, res, finfo)) { fprintf(stderr, "Cannot set finfo: %s.\n", strerror(errno)); } if((finfo & FINFO_XATTR) == FINFO_XATTR) { XAttributes *xattr = file_get_attributes(fpath, NULL, NULL); if(xattr) { resource_set_xattr(res, xattr); } } dav_set_content(res, in, (dav_read_func)fread, (dav_seek_func)file_seek); if(len > 0 && len < 0x7d000000) { dav_set_content_length(res, (size_t)len); } if(dav_store(res)) { print_resource_error(sn, res->path); fprintf(stderr, "Cannot upload file.\n"); if(sn->errorstr) { fprintf(stderr, "%s\n", sn->errorstr); } return -1; } return 0; } static int dav_create_col(DavResource *res) { res->iscollection = 1; return dav_create(res); } static void print_cannoterr(DavSession *sn, const char *message) { switch(sn->error) { case DAV_UNSUPPORTED_PROTOCOL: case DAV_COULDNT_RESOLVE_PROXY: case DAV_COULDNT_RESOLVE_HOST: case DAV_COULDNT_CONNECT: case DAV_TIMEOUT: case DAV_SSL_ERROR: break; default: fprintf(stderr, "Cannot %s.\n", message); } } static int cmd_operation_on_resources(CmdArgs* a, int(*operation)(DavResource*), const char* command, const char* message, DavBool check_key) { if(a->argc < 1) { fprintf(stderr, "Too few arguments\n"); fprintf(stderr, "Usage: dav %s\n", find_usage_str(command)); return -1; } char *url = a->argv[0]; char *path = NULL; DavCfgRepository *repo = dav_config_url2repo(get_config(), url, &path); DavSession *sn = connect_to_repo(ctx, repo, path, request_auth, a); int exit_code = -1; assert(!!path && !!sn); if(set_session_config(sn, a)) { goto cmd_oponres_exit; } set_session_lock(sn, a); if(check_key && check_encryption_key(a, sn)) { goto cmd_oponres_exit; } DavResource *res = dav_resource_new(sn, path); assert(!!res); res->iscollection = 1; if(a->argc == 1) { if(operation(res)) { print_cannoterr(sn, message); print_resource_error(sn, res->path); goto cmd_oponres_exit; } } else { for(int i = 1 ; i < a->argc ;++i) { DavResource *child = dav_resource_new_child(sn, res, a->argv[i]); assert(!!child); child->iscollection = 1; if(operation(child)) { print_cannoterr(sn, message); print_resource_error(sn, child->path); goto cmd_oponres_exit; } } } exit_code = 0; cmd_oponres_exit: free(path); dav_session_destroy(sn); return exit_code; } int cmd_remove(CmdArgs *a) { return cmd_operation_on_resources(a, dav_delete, "remove", "delete resource", FALSE); } int cmd_mkdir(CmdArgs *a) { return cmd_operation_on_resources(a, dav_create_col, "mkdir", "create collection", TRUE); } int cmd_move(CmdArgs *a, int cp) { const char* actionstr = cp ? "copy" : "move"; if(a->argc != 2) { // TODO: change, when creation of multiple dirs is supported fprintf(stderr, "Too %s arguments\n", a->argc < 2 ? "few":"many"); fprintf(stderr, "Usage: dav %s\n", find_usage_str(actionstr)); return -1; } char *srcurl = a->argv[0]; char *srcpath = NULL; DavCfgRepository *srcrepo = dav_config_url2repo(get_config(), srcurl, &srcpath); DavSession *srcsn = connect_to_repo(ctx, srcrepo, srcpath, request_auth, a); if(set_session_config(srcsn, a)) { return -1; } set_session_lock(srcsn, a); DavBool override = cmd_getoption(a, "override") ? true : false; char *desturl = a->argv[1]; char *destpath = NULL; DavCfgRepository *destrepo = dav_config_url2repo(get_config(), desturl, &destpath); if(srcrepo == destrepo) { DavResource *res = dav_resource_new(srcsn, srcpath); int err = cp ? dav_copy_o(res, destpath, override) : dav_move_o(res, destpath, override); if(err) { print_resource_error(srcsn, res->path); fprintf(stderr, "Cannot %s resource.\n", actionstr); return -1; } } else { char *srchost = util_url_base(srcrepo->url.value.ptr); char *desthost = util_url_base(destrepo->url.value.ptr); if(!strcmp(srchost, desthost)) { DavSession *destsn = connect_to_repo(ctx, destrepo, destpath, request_auth, a); if(set_session_config(destsn, a)) { return -1; } DavResource *dest = dav_resource_new(destsn, destpath); char *desthref = dav_resource_get_href(dest); desturl = util_get_url(destsn, desthref); DavResource *res = dav_resource_new(srcsn, srcpath); int err = cp ? dav_copyto(res, desturl, override) : dav_moveto(res, desturl, override); free(desturl); dav_session_destroy(destsn); if(err) { print_resource_error(srcsn, res->path); fprintf(stderr, "Cannot %s resource.\n", actionstr); return -1; } } else { fprintf(stderr, "Cannot %s between different hosts.\n", actionstr); return -1; } } dav_session_destroy(srcsn); return 0; } int cmd_rename(CmdArgs *a) { if(a->argc != 2) { // TODO: change, when creation of multiple dirs is supported fprintf(stderr, "Too %s arguments\n", a->argc < 2 ? "few":"many"); fprintf(stderr, "Usage: dav %s\n", find_usage_str("rename")); return -1; } char *name = a->argv[1]; size_t namelen = strlen(name); for(size_t i=0;i<namelen;i++) { char c = name[i]; if(c == '/') { fprintf(stderr, "Illegal character in name: ''/''\n"); return 1; } } char *url = a->argv[0]; char *path = NULL; DavCfgRepository *repo = dav_config_url2repo(get_config(), url, &path); DavSession *sn = connect_to_repo(ctx, repo, path, request_auth, a); if(set_session_config(sn, a)) { return -1; } set_session_lock(sn, a); int ret = 0; DavResource *res = dav_get(sn, path, NULL); if(res) { char *cryptoname = dav_get_string_property_ns(res, DAV_NS, "crypto-name"); char *cryptokey = dav_get_string_property_ns(res, DAV_NS, "crypto-key"); if(cryptoname && cryptokey) { // encrypted resource with an encrypted name // renaming is done by simply setting the crypto-name property DavKey *key = dav_context_get_key(ctx, cryptokey); if(key) { // check if a resource with this name already exists char *parent = util_parent_path(res->path); char *newpath = util_concat_path(parent, name); DavResource *testres = dav_resource_new(sn, newpath); if(dav_exists(testres)) { fprintf(stderr, "A resource with this name already exists.\nAbort.\n"); ret = 1; } else { char *crname = aes_encrypt(name, namelen, key); dav_set_string_property_ns(res, DAV_NS, "crypto-name", crname); free(crname); if(dav_store(res)) { print_resource_error(sn, res->path); fprintf(stderr, "Cannot store crypto-name property.\n"); ret = 1; } } free(parent); free(newpath); } else { fprintf(stderr, "Key %s not found.\n", cryptokey); } } else { // rename the resource by changing the url mapping with MOVE char *parent = util_parent_path(res->href); char *new_href = util_concat_path(parent, name); char *dest = util_get_url(sn, new_href); free(parent); free(new_href); if(dav_moveto(res, dest, false)) { print_resource_error(sn, path); fprintf(stderr, "Cannot rename resource.\n"); ret = 1; } free(dest); } } else { print_resource_error(sn, path); fprintf(stderr, "Cannot rename resource.\n"); ret = 1; } dav_session_destroy(sn); free(path); return ret; } static size_t get_date_header_cb(void *header, int s, int n, void *data) { char **date_str = (char**)data; //printf("header: %.*s\n", s*n, header); cxstring h = cx_strn(header, s*n); if(cx_strprefix(h, CX_STR("Date:"))) { cxstring v = cx_strsubs(h, 5); *date_str = cx_strdup(cx_strtrim(v)).ptr; } return s*n; } int cmd_date(CmdArgs *a) { if(a->argc < 1) { time_t now = time(NULL); struct tm *date = gmtime(&now); char str[32]; putenv("LC_TIME=C"); size_t len = strftime(str, 32, "%a, %d %b %Y %H:%M:%S GMT\n", date); fwrite(str, 1, len, stdout); } else if (a->argc == 1) { char *url = a->argv[0]; char *path = NULL; DavCfgRepository *repo = dav_config_url2repo(get_config(), url, &path); DavSession *sn = connect_to_repo(ctx, repo, path, request_auth, a); DavResource *res = dav_resource_new(sn, path); char *date = NULL; curl_easy_setopt(sn->handle, CURLOPT_HEADERFUNCTION, get_date_header_cb); curl_easy_setopt(sn->handle, CURLOPT_WRITEHEADER, &date); if(dav_exists(res) && date) { printf("%s\n", date); } else { return -1; } free(path); return 0; } else { fprintf(stderr, "Too many arguments\n"); fprintf(stderr, "Usage: dav %s\n", find_usage_str("date")); return -1; } return 0; } int cmd_get_property(CmdArgs *a) { if(a->argc < 2) { fprintf(stderr, "Too few arguments\n"); fprintf(stderr, "Usage: dav %s\n", find_usage_str("get-property")); return -1; } char *url = a->argv[0]; char *path = NULL; DavCfgRepository *repo = dav_config_url2repo(get_config(), url, &path); DavSession *sn = connect_to_repo(ctx, repo, path, request_auth, a); if(set_session_config(sn, a)) { return -1; } char *namespace = cmd_getoption(a, "namespace"); char *property = a->argv[1]; char *version = cmd_getoption(a, "version"); DavPropName propname; if(namespace) { propname.ns = namespace; propname.name = property; } else { dav_get_property_namespace_str(ctx, property, &propname.ns, &propname.name); if(!propname.ns || !propname.name) { fprintf(stderr, "Error: unknown namespace prefix\n"); return -1; } } DavResource *res = dav_resource_new(sn, path); if(version) { DavResource *vres = find_version(res, version); if(!vres) { fprintf(stderr, "Cannot find version ''%s'' for resource.\n", version); return -1; } dav_resource_free_all(res); res = vres; } if(dav_load_prop(res, &propname, 1)) { print_resource_error(sn, res->path); return -1; } free(path); DavXmlNode *x = dav_get_property_ns(res, propname.ns, propname.name); if(!x) { fprintf(stderr, "Error: no property value.\n"); return -1; } if(cmd_getoption(a, "xml")) { // print a real xml document on stdout printxmldoc(stdout, propname.name, propname.ns, x); } else { // in this mode a simple string is printed on stdout // or simplified and nicely formatted xml is printed on stderr if(dav_xml_isstring(x)) { printf("%s\n", dav_xml_getstring(x)); } else { char *str = xml2str(x); fprintf(stderr, "%s", str); free(str); } } return 0; } int cmd_set_property(CmdArgs *a) { if(a->argc < 2) { fprintf(stderr, "Too few arguments\n"); fprintf(stderr, "Usage: dav %s\n", find_usage_str("set-property")); return -1; } char *url = a->argv[0]; char *path = NULL; DavCfgRepository *repo = dav_config_url2repo(get_config(), url, &path); DavSession *sn = connect_to_repo(ctx, repo, path, request_auth, a); if(set_session_config(sn, a)) { return -1; } set_session_lock(sn, a); DavResource *res = dav_resource_new(sn, path); if(!dav_exists(res)) { print_resource_error(sn, res->path); return -1; } char *namespace = cmd_getoption(a, "namespace"); char *xml = cmd_getoption(a, "xml"); char *property = a->argv[1]; char *value = a->argc > 2 ? a->argv[2] : stdin2str(); int ret = 0; if(xml) { DavXmlNode *xmlvalue = dav_parse_xml(sn, value, strlen(value)); if(xmlvalue) { if(namespace) { dav_set_property_ns(res, namespace, property, xmlvalue->children); } else { dav_set_property(res, property, xmlvalue->children); } } else { fprintf(stderr, "Error: property content is not valid xml\n"); ret = 1; } } else { if(namespace) { dav_set_string_property_ns(res, namespace, property, value); } else { if(dav_set_string_property(res, property, value)) { fprintf(stderr, "%s\n", res->session->errorstr); } } } if(ret == 0) { if(dav_store(res)) { print_resource_error(sn, res->path); fprintf(stderr, "Cannot set property.\n"); ret = -1; } } else free(path); dav_session_destroy(sn); return ret; } int cmd_remove_property(CmdArgs *a) { if(a->argc < 2) { fprintf(stderr, "Too few arguments\n"); fprintf(stderr, "Usage: dav %s\n", find_usage_str("remove-property")); return -1; } char *url = a->argv[0]; char *path = NULL; DavCfgRepository *repo = dav_config_url2repo(get_config(), url, &path); DavSession *sn = connect_to_repo(ctx, repo, path, request_auth, a); if(set_session_config(sn, a)) { return -1; } char *namespace = cmd_getoption(a, "namespace"); char *property = a->argv[1]; DavPropName propname; if(namespace) { propname.ns = namespace; propname.name = property; } else { dav_get_property_namespace_str(ctx, property, &propname.ns, &propname.name); } int ret = 0; DavResource *res = dav_resource_new(sn, path); dav_remove_property_ns(res, propname.ns, propname.name); if(dav_store(res)) { print_resource_error(sn, res->path); fprintf(stderr, "Cannot set property.\n"); ret = -1; } free(path); return ret; } int cmd_lock(CmdArgs *a) { if(a->argc != 1) { fprintf(stderr, "Too %s arguments\n", a->argc > 1 ? "many" : "few"); fprintf(stderr, "Usage: dav %s\n", find_usage_str("lock")); return -1; } char *url = a->argv[0]; char *path = NULL; DavCfgRepository *repo = dav_config_url2repo(get_config(), url, &path); DavSession *sn = connect_to_repo(ctx, repo, path, request_auth, a); cxMempoolRegister(sn->mp, path, free); if(set_session_config(sn, a)) { return -1; } time_t timeout = 0; char *timeoutstr = cmd_getoption(a, "timeout"); if(timeoutstr) { if(!cx_strcasecmp(cx_str(timeoutstr), CX_STR("infinite"))) { timeout = -1; } else { uint64_t i; if(util_strtouint(timeoutstr, &i)) { timeout = (time_t)i; } else { fprintf(stderr, "Error: -T option has invalid value\n"); return -1; } } } DavResource *res = dav_resource_new(sn, path); if(dav_lock_t(res, timeout)) { print_resource_error(sn, res->path); return -1; } DavLock *lock = dav_get_lock(sn, res->path); if(!lock) { // this should really not happen // do some damage control dav_unlock(res); fprintf(stderr, "Error: Cannot find lock token for %s\n", res->path); return -1; } printf("%s\n", lock->token); dav_session_destroy(sn); return 0; } static char* read_line() { CxBuffer buf; cxBufferInit(&buf, NULL, 128, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND); int c; while((c = getchar()) != EOF) { if(c == '\n') { break; } cxBufferPut(&buf, c); } char *str = NULL; cxstring line = cx_strtrim(cx_strn(buf.space, buf.size)); if(line.length != 0) { str = cx_strdup(line).ptr; } cxBufferDestroy(&buf); return str; } int cmd_unlock(CmdArgs *a) { if(a->argc != 1) { fprintf(stderr, "Too %s arguments\n", a->argc > 1 ? "many" : "few"); fprintf(stderr, "Usage: dav %s\n", find_usage_str("unlock")); return -1; } char *url = a->argv[0]; char *path = NULL; DavCfgRepository *repo = dav_config_url2repo(get_config(), url, &path); DavSession *sn = connect_to_repo(ctx, repo, path, request_auth, a); cxMempoolRegister(sn->mp, path, free); if(set_session_config(sn, a)) { return -1; } char *locktoken = cmd_getoption(a, "lock"); if(locktoken) { DavLock *lock = dav_create_lock(sn, locktoken, NULL); dav_add_collection_lock(sn, "/", lock); } else { locktoken = read_line(); if(!locktoken) { fprintf(stderr, "No lock token specified.\nAbort.\n"); return -1; } DavLock *lock = dav_create_lock(sn, locktoken, NULL); dav_add_collection_lock(sn, "/", lock); free(locktoken); } int ret = 0; DavResource *res = dav_resource_new(sn, path); if(dav_unlock(res)) { print_resource_error(sn, res->path); ret = -1; } dav_session_destroy(sn); return ret; } static int count_children(DavResource *res) { DavResource *child = res->children; int count = 0; while(child) { count++; child = child->next; } return count; } void print_xml_infostr(DavXmlNode *xml) { if(xml->children) { printf("<%s>...</%s>", xml->name, xml->name); } else { printf("<%s/>", xml->name); } } int cmd_info(CmdArgs *a) { if(a->argc < 1) { fprintf(stderr, "Too few arguments\n"); fprintf(stderr, "Usage: dav %s\n", find_usage_str("info")); return -1; } char *url = a->argv[0]; char *path = NULL; DavCfgRepository *repo = dav_config_url2repo(get_config(), url, &path); DavSession *sn = connect_to_repo(ctx, repo, path, request_auth, a); if(set_session_config(sn, a)) { return -1; } char *version = cmd_getoption(a, "version"); DavResource *res = dav_resource_new(sn, path); if(version) { DavResource *vres = find_version(res, version); if(!vres) { fprintf(stderr, "Cannot find version ''%s'' for resource.\n", version); return -1; } dav_resource_free_all(res); res = vres; } if(!dav_load(res)) { printf("name: %s\n", res->name); printf("path: %s\n", res->path); char *server = util_url_base(sn->base_url); url = util_concat_path(server, res->href); printf("url: %s\n", url); free(url); free(server); if(res->iscollection) { printf("type: collection\n"); printf("size: %d\n", count_children(res)); } else { printf("type: resource\n"); char *len = util_size_str(res->iscollection, res->contentlength); printf("size: %s\n", len); free(len); } size_t count = 0; DavPropName *properties = dav_get_property_names(res, &count); char *last_ns = NULL; for(int i=0;i<count;i++) { DavPropName p = properties[i]; if(!last_ns || strcmp(last_ns, p.ns)) { printf("\nnamespace: %s\n", p.ns); last_ns = p.ns; } DavXmlNode *xval = dav_get_property_ns(res, p.ns, p.name); if(dav_xml_isstring(xval)) { cxstring value = cx_str(dav_xml_getstring(xval)); printf(" %s: %.*s\n", p.name, (int)value.length, value.ptr); } else { // find some xml elements printf(" %s: ", p.name); DavXmlNode *x = xval->type == DAV_XML_ELEMENT ? xval : dav_xml_nextelm(xval); for(int j=0;j<3;j++) { if(x) { if(j == 2) { printf(" ..."); break; } else { print_xml_infostr(x); } } else { break; } x = dav_xml_nextelm(x); } printf("\n"); } } dav_session_free(sn, properties); return 0; } else { print_resource_error(sn, res->path); } return -1; } int cmd_checkout(CmdArgs *a) { if(a->argc < 1) { fprintf(stderr, "Too few arguments\n"); fprintf(stderr, "Usage: dav %s\n", "checkout [-pc] <url>"); return -1; } char *url = a->argv[0]; char *path = NULL; DavCfgRepository *repo = dav_config_url2repo(get_config(), url, &path); DavSession *sn = connect_to_repo(ctx, repo, path, request_auth, a); if(set_session_config(sn, a)) { return -1; } set_session_lock(sn, a); int ret = 0; DavResource *res = dav_resource_new(sn, path); if(dav_checkout(res)) { print_resource_error(sn, res->path); ret = 1; } return ret; } int cmd_checkin(CmdArgs *a) { if(a->argc < 1) { fprintf(stderr, "Too few arguments\n"); fprintf(stderr, "Usage: dav %s\n", "checkin [-pc] <url>"); return -1; } char *url = a->argv[0]; char *path = NULL; DavCfgRepository *repo = dav_config_url2repo(get_config(), url, &path); DavSession *sn = connect_to_repo(ctx, repo, path, request_auth, a); if(set_session_config(sn, a)) { return -1; } set_session_lock(sn, a); int ret = 0; DavResource *res = dav_resource_new(sn, path); if(dav_checkin(res)) { print_resource_error(sn, res->path); ret = 1; } return ret; } int cmd_uncheckout(CmdArgs *a) { if(a->argc < 1) { fprintf(stderr, "Too few arguments\n"); fprintf(stderr, "Usage: dav %s\n", "uncheckout [-pc] <url>"); return -1; } char *url = a->argv[0]; char *path = NULL; DavCfgRepository *repo = dav_config_url2repo(get_config(), url, &path); DavSession *sn = connect_to_repo(ctx, repo, path, request_auth, a); if(set_session_config(sn, a)) { return -1; } set_session_lock(sn, a); int ret = 0; DavResource *res = dav_resource_new(sn, path); if(dav_uncheckout(res)) { print_resource_error(sn, res->path); ret = 1; } return ret; } int cmd_versioncontrol(CmdArgs *a) { if(a->argc < 1) { fprintf(stderr, "Too few arguments\n"); fprintf(stderr, "Usage: dav %s\n", "versioncontrol [-pc] <url>"); return -1; } char *url = a->argv[0]; char *path = NULL; DavCfgRepository *repo = dav_config_url2repo(get_config(), url, &path); DavSession *sn = connect_to_repo(ctx, repo, path, request_auth, a); if(set_session_config(sn, a)) { return -1; } set_session_lock(sn, a); int ret = 0; DavResource *res = dav_resource_new(sn, path); if(dav_versioncontrol(res)) { print_resource_error(sn, res->path); ret = 1; } return ret; } int cmd_list_versions(CmdArgs *a) { if(a->argc < 1) { fprintf(stderr, "Too few arguments\n"); fprintf(stderr, "Usage: dav %s\n", "list-versions [-pc] <url>"); return -1; } char *url = a->argv[0]; char *path = NULL; DavCfgRepository *repo = dav_config_url2repo(get_config(), url, &path); DavSession *sn = connect_to_repo(ctx, repo, path, request_auth, a); if(set_session_config(sn, a)) { return -1; } DavResource *res = dav_resource_new(sn, path); int ret = 0; DavResource *list = dav_versiontree(res, NULL); if(list) { char* longlist = cmd_getoption(a, "list"); DavResource *v = list; int addnl = 0; while(v) { char *vname = dav_get_string_property(v, "D:version-name"); if(longlist) { if(addnl) { putchar('\n'); } printf("name: %s\n", vname); printf("href: %s\n", v->href); addnl = 1; } else { printf("%s\n", vname); } v = v->next; } } else if(sn->error != DAV_OK) { print_resource_error(sn, path); ret = 1; } return ret; } DavResource* find_version(DavResource *res, char *version) { DavResource *list = dav_versiontree(res, NULL); DavResource *ret = NULL; while(list) { DavResource *next = list->next; if(!ret) { char *vname = dav_get_string_property(list, "D:version-name"); if(vname && !strcmp(vname, version)) { ret = list; } } if(list != ret) { dav_resource_free(list); } list = next; } return ret; } char* stdin2str() { CxBuffer buf; cxBufferInit(&buf, NULL, 1024, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND); size_t size = cx_stream_copy(stdin, &buf, (cx_read_func)fread, (cx_write_func)cxBufferWrite); if(size == 0) { cxBufferDestroy(&buf); return NULL; } else { cxBufferPut(&buf, '\0'); return buf.space; } } static void xml2str_i(DavXmlNode *node, CxBuffer *buf, int indent) { while(node) { if(node->type == DAV_XML_ELEMENT) { if(node->children) { if(dav_xml_isstring(node->children)) { cxstring s = cx_strtrim(cx_str(dav_xml_getstring(node->children))); cx_bprintf( buf, "%*s<%s>%.*s</%s>\n", indent, "", node->name, (int)s.length, s.ptr, node->name); } else { cx_bprintf(buf, "%*s<%s>\n", indent, "", node->name); xml2str_i(node->children, buf, indent+2); cx_bprintf(buf, "%*s</%s>\n", indent, "", node->name); } } else { cx_bprintf(buf, "%*s<%s />", indent, "", node->name); cxBufferPut(buf, '\n'); } } else if(node->type == DAV_XML_TEXT) { cxstring val = cx_strtrim(cx_strn(node->content, node->contentlength)); if(val.length > 0) { cx_bprintf(buf, "%*.*s", indent, (int)val.length, val.ptr); } } node = node->next; } } char* xml2str(DavXmlNode *node) { CxBuffer buf; cxBufferInit(&buf, NULL, 256, cxDefaultAllocator, CX_BUFFER_AUTO_EXTEND); xml2str_i(node, &buf, 0); cxBufferPut(&buf, 0); return buf.space; } void printxmldoc(FILE *out, char *root, char *rootns, DavXmlNode *content) { CxMap *nsmap = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 16); cxDefineDestructor(nsmap, free); cxMapPut(nsmap, cx_hash_key_str(rootns), "x0"); fprintf(out, "%s", "<?xml version=\"1.0\"?>\n"); fprintf(out, "<x0:%s xmlns:x0=\"%s\">", root, rootns); dav_print_node(out, (cx_write_func)fwrite, nsmap, content); fprintf(out, "</x0:%s>\n", root); // cleanup namespace map cxMapRemove(nsmap, cx_hash_key_str(rootns)); cxMapDestroy(nsmap); } /* ---------- config commands ---------- */ int cmd_add_repository(CmdArgs *args) { printf("Each repository must have an unique name.\n"); char *name = assistant_getcfg("name"); if(!name) { fprintf(stderr, "Abort\n"); return -1; } if(dav_config_get_repository(get_config(), cx_str(name))) { fprintf(stderr, "Repository %s already exists.\nAbort\n", name); return -1; } printf("\nSpecify the repository base url.\n"); char *url = assistant_getcfg("url"); if(!url) { fprintf(stderr, "Abort\n"); return -1; } printf("\nUser for HTTP authentication.\n"); char *user = assistant_getoptcfg("user"); char *password = NULL; if(user) { password = assistant_gethiddenoptcfg("password"); } printf("\n"); DavConfig *config = get_config(); const CxAllocator *a = config->mp->allocator; DavCfgRepository *repo = dav_repository_new(config); repo->name.value = cx_strdup_a(a, cx_str(name)); repo->url.value = cx_strdup_a(a, cx_str(url)); cxstring user_s = user ? cx_str(user) : cx_strn(NULL, 0); cxstring password_s = password ? cx_str(password) : cx_strn(NULL, 0); dav_repository_set_auth(config, repo, user_s, password_s); dav_config_add_repository(config, repo); int ret = 0; if(store_config()) { fprintf(stderr, "Cannot write config.xml\n"); ret = -1; } else { printf("\nAdded repository: %s (%s)\n", name, url); } free(name); free(url); if(user) { free(user); } if(password) { free(password); } return ret; } int cmd_remove_repository(CmdArgs *args) { if(args->argc < 1) { fprintf(stderr, "Too few arguments\n"); fprintf(stderr, "Usage: dav remove-repository <name...>\n"); return -1; } DavConfig *config = get_config(); DavBool store = FALSE; for(int i = 0 ; i < args->argc ; i++) { cxstring reponame = cx_str(args->argv[i]); DavCfgRepository* repo = dav_config_get_repository(config, reponame); if(repo) { dav_repository_remove_and_free(config, repo); store = TRUE; } else { fprintf(stderr, "Repository %s does not exist - skipped.\n", reponame.ptr); return -1; } } if(store) { return store_config(); } else { return -1; } } int cmd_list_repositories(void) { DavConfig *config = get_config(); if(!config) { return 1; } for(DavCfgRepository *repo=config->repositories;repo;repo=repo->next) { printf("%.*s\n", (int)repo->name.value.length, repo->name.value.ptr); } return 0; } int cmd_repository_url(CmdArgs *args) { if(args->argc != 1) { fprintf(stderr, "Too few arguments\n"); fprintf(stderr, "Usage: dav repository-url [-p] <name>\n"); return -1; } cxstring reponame = cx_str(args->argv[0]); DavCfgRepository* repo = dav_config_get_repository(get_config(), reponame); if(repo) { cxstring url = cx_strcast(repo->url.value); if(repo->user.value.ptr && !cmd_getoption(args, "plain")) { int hostindex = 0; if(cx_strprefix(url, CX_STR("https://"))) { printf("https://"); hostindex = 8; } else if(cx_strprefix(url, CX_STR("http://"))) { printf("http://"); hostindex = 7; } printf("%.*s", (int)repo->user.value.length, repo->user.value.ptr); if(repo->password.value.ptr) { cxmutstr pw_decoded = dav_repository_get_decodedpassword(repo); CURL *curl = curl_easy_init(); char *pw = curl_easy_escape( curl, pw_decoded.ptr, pw_decoded.length); printf(":%s", pw); curl_free(pw); curl_easy_cleanup(curl); free(pw_decoded.ptr); } putchar('@'); printf("%.*s", (int)url.length-hostindex, url.ptr+hostindex); } else { printf("%s", url.ptr); } if(url.ptr[url.length-1] != '/') { putchar('/'); } putchar('\n'); } else { fprintf(stderr, "Repository %s does not exist.\n", reponame.ptr); return -1; } return 0; } typedef int(*sscmd_func)(CmdArgs *, PwdStore *, void *userdata); static int secretstore_after_decrypt( CmdArgs *args, PwdStore *secrets, sscmd_func cb, void *userdata); /* * opens the secret store, executes a callback func before and after * decryption * Aborts if a callback returns 1 */ static int secretstore_cmd( CmdArgs *args, DavBool create, sscmd_func beforedecrypt, sscmd_func afterdecrypt, void *userdata) { PwdStore *secrets = get_pwdstore(); if(!secrets) { if(create) { secrets = pwdstore_new(); } else { return 1; } } int ret = 0; if(beforedecrypt) { ret = beforedecrypt(args, secrets, userdata); if(ret) { afterdecrypt = NULL; // exit } } if(afterdecrypt) { ret = secretstore_after_decrypt(args, secrets, afterdecrypt, userdata); } pwdstore_free(secrets); return ret; } static int secretstore_after_decrypt( CmdArgs *args, PwdStore *secrets, sscmd_func cb, void *userdata) { char *master_pw = util_password_input("Master password: "); if(!master_pw) { fprintf(stderr, "Error: master password required.\nAbort.\n"); return 1; } int err = pwdstore_setpassword(secrets, master_pw); free(master_pw); if(err) { fprintf(stderr, "Error: Cannot generate key from password.\nAbort.\n"); return 1; } if(pwdstore_decrypt(secrets)) { fprintf(stderr, "Error: Cannot decrypt secrets store.\nAbort.\n"); return 1; } return cb(args, secrets, userdata); } static int cmd_ss_add_user(CmdArgs *Args, PwdStore *secrets, void *userdata) { char *id = assistant_getcfg("Credentials identifier"); if(!id) { fprintf(stderr, "Identifier required.\n"); return 1; } if(pwdstore_get(secrets, id)) { fprintf(stderr, "Credentials with this id already exist.\n"); return 1; } // get user name and password (required) char *user = assistant_getcfg("User"); char *password = util_password_input("Password: "); // optionally, get one or more locations char *location = NULL; CxList *locations = cxLinkedListCreateSimple(CX_STORE_POINTERS); cxDefineDestructor(locations, free); while((location = assistant_getoptcfg("Location"))) { cxListAdd(locations, location); } int ret = 1; if(user && password) { pwdstore_put_index(secrets, id, locations); pwdstore_put(secrets, id, user, password); ret = pwdstore_save(secrets); if(ret) { fprintf(stderr, "Error: saving srcrets store failed.\n"); } if(cxListSize(locations) == 0) { cxListDestroy(locations); } } else { cxListDestroy(locations); } if(id) free(id); if(user) free(user); if(password) free(password); return ret; } int cmd_add_user(CmdArgs *args) { return secretstore_cmd(args, TRUE, NULL, cmd_ss_add_user, NULL); } /* * called before the secret store is decrypted */ static int cmd_ss_list_users_bc(CmdArgs *Args, PwdStore *secrets, int *ret) { if(cxMapSize(secrets->index) == 0) { return 1; // abort, because the secret store is empty } // set ret to 1, because decrypt could fail and this should be an error *ret = 1; return 0; } /* * called after the secret store is decrypted */ static int cmd_ss_list_users(CmdArgs *args, PwdStore *secrets, int *ret) { *ret = 0; CxList *list = secrets->locations; for(int i=0;i<2;i++) { if(list) { CxIterator iter = cxListIterator(list); cx_foreach(PwdIndexEntry*, index, iter) { PwdEntry *e = cxMapGet(secrets->ids, cx_hash_key_str(index->id)); if(e) { printf("Id: %s\n", e->id); printf("User: %s\n", e->user); if(index->locations) { CxIterator loc_iter = cxListIterator(index->locations); cx_foreach(char *, location, loc_iter) { printf("Location: %s\n", location); } printf("\n"); } } else { // broken index fprintf(stderr, "Warning: id ''%s'' not in secret store.\n", index->id); } } } list = secrets->noloc; } return 0; } int cmd_list_users(CmdArgs *args) { int ret = 0; secretstore_cmd(args, FALSE, (sscmd_func)cmd_ss_list_users_bc, (sscmd_func)cmd_ss_list_users, &ret); return ret; } static int cmd_ss_remove_user(CmdArgs *args, PwdStore *secrets, void *ud) { char *id = assistant_getcfg("Credentials identifier"); if(!id) { fprintf(stderr, "Identifier required.\n"); return 1; } if(!pwdstore_get(secrets, id)) { fprintf(stderr, "Credentials with this id doesn''t exist.\n"); free(id); return 1; } pwdstore_remove_entry(secrets, id); int ret = pwdstore_save(secrets); if(ret) { fprintf(stderr, "Error: saving srcrets store failed.\n"); } free(id); return ret; } int cmd_remove_user(CmdArgs *args) { return secretstore_cmd(args, FALSE, NULL, cmd_ss_remove_user, NULL); } static void secrets_print_user_info(PwdStore *secrets, const char *id) { PwdEntry *entry = pwdstore_get(secrets, id); if(!entry) { return; } PwdIndexEntry *index = cxMapGet(secrets->index, cx_hash_key_str(id)); if(!index) { return; } printf("Id: %s\n", entry->id); printf("User: %s\n", entry->user); if(index->locations) { CxIterator loc_iter = cxListIterator(index->locations); cx_foreach(char *, location, loc_iter) { printf("Location: %s\n", location); } } } static void secrets_remove_location(PwdIndexEntry *index) { if(!index->locations || cxListSize(index->locations) == 0) { printf("no locations\n"); return; } printf("0: abort\n"); int i = 1; CxIterator loc_iter = cxListIterator(index->locations); cx_foreach(char *, location, loc_iter) { printf("%d: %s\n", i, location); i++; } char *input = assistant_getcfg("Choose location"); if(!input) { return; } int64_t ln = 0; if(util_strtoint(input, &ln) && (ln >= 0 && ln < i)) { if(ln == 0) { return; } else { char *location = cxListAt(index->locations, ln - 1); if(location) { free(location); cxListRemove(index->locations, ln - 1); } } } else { printf("illegal input, choose 0 - %d\n", i-1); secrets_remove_location(index); // try again } } static int cmd_ss_edit_user(CmdArgs *args, PwdStore *secrets, void *ud) { char *id = assistant_getcfg("Credentials identifier"); if(!id) { fprintf(stderr, "Identifier required.\n"); return 1; } PwdEntry *entry = pwdstore_get(secrets, id); PwdIndexEntry *index = cxMapGet(secrets->index, cx_hash_key_str(id)); if(!entry || !index) { fprintf(stderr, "Credentials with this id doesn''t exist.\n"); return 1; } secrets_print_user_info(secrets, id); int save = 0; int loop = 1; while(loop) { printf("\n"); printf("0: change user name\n"); printf("1: change password\n"); printf("2: add location\n"); printf("3: remove location\n"); printf("4: list locations\n"); printf("5: save and exit\n"); printf("6: exit without saving\n"); char *opt = assistant_getcfg("Choose action"); if(!opt) { break; } int64_t mnu = 0; if(util_strtoint(opt, &mnu) && (mnu >= 0 && mnu <= 6)) { printf("\n"); switch(mnu) { case 0: { // change user name char *user = assistant_getcfg("User"); if(user) { if(entry->user) { free(entry->user); } entry->user = user; } break; } case 1: { // change password char *password = util_password_input("Password: "); if(password) { if(entry->password) { free(entry->password); } entry->password = password; } break; } case 2: { // add location char *location = assistant_getoptcfg("Location"); if(location) { cxListAdd(index->locations, location); } break; } case 3: { // remove location secrets_remove_location(index); break; } case 4: { // list locations if(!index->locations || cxListSize(index->locations)) { printf("no locations\n"); } else { CxIterator i = cxListIterator(index->locations); cx_foreach(char *, location, i) { printf("Location: %s\n", location); } } break; } case 5: { // save and exit loop = 0; save = 1; break; } case 6: { // exit without saving loop = 0; break; } } } else { printf("illegal input, choose 0 - 5\n"); } free(opt); } int ret = 0; if(save) { ret = pwdstore_save(secrets); if(ret) { fprintf(stderr, "Error: saving srcrets store failed.\n"); } } return ret; } int cmd_edit_user(CmdArgs *args) { return secretstore_cmd(args, FALSE, NULL, cmd_ss_edit_user, NULL); } static int cmd_ss_set_master_pw(CmdArgs *args, PwdStore *secrets, void *ud) { char *new_master_pw = util_password_input("New master password: "); int ret = pwdstore_setpassword(secrets, new_master_pw); if(ret) { fprintf(stderr, "Error: failed to set new master password\n"); } ret = pwdstore_save(secrets); if(ret) { fprintf(stderr, "Error: saving srcrets store failed.\n"); } return ret; } int cmd_set_master_password(CmdArgs *args) { return secretstore_cmd(args, FALSE, NULL, cmd_ss_set_master_pw, NULL); } static char** read_args_from_stdin(int *argc) { // read stdin into buffer CxBuffer *in = cxBufferCreate(NULL, 1024, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND); cx_stream_copy(stdin, in, (cx_read_func)fread, (cx_write_func)cxBufferWrite); // split input into lines ssize_t count = 0; cxmutstr *lines; count = cx_strsplit_ma(cxDefaultAllocator, cx_mutstrn(in->space, in->pos), CX_STR("\n"), INT_MAX, &lines); char **args = NULL; if(count > 0) { args = calloc(count, sizeof(char*)); for(int i=0;i<count;i++) { args[i] = lines[i].ptr; } free(lines); *argc = count; } else { *argc = 0; } // cleanup cxBufferFree(in); return args; } int cmd_complete(CmdArgs *args) { if(args->argc != 1) { return 1; } char *index_str = args->argv[0]; int64_t index = 0; if(!util_strtoint(index_str, &index)) { return 1; } // The completion bash script passes the input words to stdin int comp_argc; char **comp_argv = read_args_from_stdin(&comp_argc); // Try to parse the args char *cmd = NULL; if(comp_argc > 1) { cmd = comp_argv[1]; } CmdArgs *comp_args = cmd_parse_args(comp_argc - 2, comp_argv + 2); if(comp_args) { // check whether the arglist contains options if(comp_args->argc + 2 != comp_argc) { // index points to the arg in the raw arglist, however we have to // know the index for this item in comp_args->argv // any arg that is an option or an option value creates a // difference between the two lists // adjust index to comp_args->argv int j = 0; for(int i=0;i<comp_argc-2;i++) { if(index == i-2) { break; } if(strcmp(comp_argv[i+2], comp_args->argv[j])) { index--; } else { j++; } } } } else { comp_args = NULL; } // generate output for shell completion int ret = 1; if(comp_args) { ret = shell_completion(cmd, comp_args, index); } // cleanup cmd_args_free(comp_args); free(comp_argv); return ret; } int shell_completion(char *cmd, CmdArgs *args, int index) { if(index == 1) { cxstring prefix = { NULL, 0 }; if(cmd) { prefix = cx_str(cmd); } for(int i=0;;i++) { char *str = cmdusageinfo[i]; if(!str) { break; } int len = (int)strlen(str); int maxlen = len; for(int w=0;w<len;w++) { if(str[w] == ' ') { maxlen = w; break; } } if(prefix.ptr) { if(!cx_strprefix(cx_strn(str, maxlen), prefix)) { continue; } } printf("%.*s\n", (int)maxlen, str); } return 0; } if(!strcmp(cmd, "date")) { return 0; } // get already typed URL or NULL, if the user hasn't started typing yet char *url = args->argc > 0 ? args->argv[0] : NULL; //printf("index: {%s}\n", args->argv[0]); //printf("dav: {%s}\n", args->argv[1]); //printf("cmd: {%s}\n", cmd); //printf("url: {%s}\n", url); if(index == 2) { // url completion return url_completion(args, url); } else if (index == 3) { if(!strcmp(cmd, "put") || !strcmp(cmd, "import")) { // file completion return 12; } else if(!strcmp(cmd, "copy") || !strcmp(cmd, "cp") || !strcmp(cmd, "move") || !strcmp(cmd, "mv")) { // url completion return url_completion(args, url); } } return 0; } int url_completion(CmdArgs *args, char *u) { cxstring url; url.ptr = u; url.length = u ? strlen(u) : 0; // if the user wants the URL to be quoted, we conform to their wish // a null-char is an indicator, that the strings shall not be quoted char quote = '\0'; if(url.length > 0 && (url.ptr[0] == '\'' || url.ptr[0] == '\"' )) { quote = url.ptr[0]; // for completing the url, we want to proceed without the quote url.ptr++; url.length--; // the user may have also prepared the ending quote, remove it for now if (url.ptr[url.length-1] == quote) { url.length--; } } // repo completion int repocomp = 1; for(int i=0;i<url.length;i++) { if(url.ptr[i] == '/') { repocomp = 0; break; } } if(repocomp) { DavConfig *config = get_config(); for(DavCfgRepository *repo=config->repositories; repo; repo=repo->next) { if(cx_strprefix(cx_strcast(repo->name.value), url)) { if(quote == '\0') { printf("%s/\n", repo->name.value.ptr); } else { printf("%c%s/%c\n", quote, repo->name.value.ptr, quote); } } } } else { // url completion cxMapPut(args->options, cx_hash_key_str("noinput"), ""); char *path = NULL; DavCfgRepository *repo = dav_config_url2repo(get_config(), url.ptr, &path); DavSession *sn = connect_to_repo(ctx, repo, path, request_auth, args); if(!sn) { return 0; } if(set_session_config(sn, args)) { return 0; } size_t plen = strlen(path); cxstring filter; char *lspath = NULL; if(path[plen-1] == '/') { lspath = strdup(path); filter = CX_STR(""); } else { lspath = util_parent_path(path); filter = cx_str(util_resource_name(path)); } DavResource *ls = dav_query(sn, "select - from %s order by name", lspath); DavResource *elm = ls ? ls->children : NULL; while(elm) { cxstring name = cx_str(elm->name); if(cx_strprefix(name, filter)) { int space = 0; for(int i=0;i<name.length;i++) { if(name.ptr[i] == ' ') { space = 1; break; } } CxBuffer *out = cxBufferCreate(NULL, 512, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND); cxBufferWrite(repo->name.value.ptr, repo->name.value.length, 1, out); if(space) { size_t l = strlen(elm->path); for(int i=0;i<l;i++) { // only if we do not quote, we have to escape char nextc = elm->path[i]; if(quote == '\0' && NULL != strchr( "!\"#$&''()*,;<>?[\\]^`{|}~ ", nextc)) { cxBufferPut(out, '\\'); } cxBufferPut(out, nextc); } } else { cxBufferPutString(out, elm->path); } if(elm->iscollection) { if(out->space[out->pos-1] != '/') { cxBufferPut(out, '/'); } } if (quote == '\0') { printf("%.*s\n", (int)out->pos, out->space); } else { printf("%c%.*s%c\n", quote, (int)out->pos, out->space, quote); } cxBufferFree(out); } elm = elm->next; } free(lspath); dav_session_destroy(sn); } return 10; }