Sun, 05 Apr 2020 10:11:46 +0200
cmd_edit. fixes wrong variable in second strlen() call
/* * 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 <unistd.h> #include <time.h> #include <sys/types.h> #ifndef _WIN32 #include <sys/wait.h> #endif #include <ucx/string.h> #include <ucx/utils.h> #include <dirent.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 "pwd.h" #include "finfo.h" #include "main.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() { } int dav_main(int argc, char **argv); #ifdef _WIN32 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; } 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(); 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")) { ucx_map_cstr_put(args->options, "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 = 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, "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>] <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) { scstr_t c = scstr(cmd); for(int i=0;;i++) { char *str = cmdusageinfo[i]; if(!str) { break; } scstr_t u = scstr(str); if(sstrprefix(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\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"); } int request_auth2(DavSession *sn, void *userdata) { Repository *repo = userdata; char *user = NULL; char ubuf[256]; if(repo->user) { user = repo->user; } else { fprintf(stderr, "User: "); fflush(stderr); user = fgets(ubuf, 256, stdin); } if(!user) { return 0; } char *password = util_password_input("Password: "); if(!password || strlen(password) == 0) { return 0; } size_t ulen = strlen(user); if(user[ulen-1] == '\n') { user[ulen-1] = '\0'; } dav_session_set_auth(sn, user, password); free(password); return 0; } static Repository* url2repo_s(sstr_t url, char **path) { *path = NULL; int s; if(sstrprefix(url, SC("http://"))) { s = 7; } else if(sstrprefix(url, SC("https://"))) { s = 8; } else { s = 1; } // split URL into repository and path sstr_t r = sstrsubs(url, s); sstr_t p = sstrchr(r, '/'); r = sstrsubsl(url, 0, url.length-p.length); if(p.length == 0) { p = sstrn("/", 1); } Repository *repo = get_repository(r); if(repo) { *path = sstrdup(p).ptr; } else { // TODO: who is responsible for freeing this repository? // how can the callee know, if he has to call free()? repo = calloc(1, sizeof(Repository)); repo->name = strdup(""); repo->decrypt_content = true; repo->verification = true; repo->authmethods = CURLAUTH_BASIC; if(url.ptr[url.length-1] == '/') { repo->url = sstrdup(url).ptr; *path = strdup("/"); } else if (sstrchr(url, '/').length > 0) { // TODO: fix the following workaround after // fixing the inconsistent behavior of util_url_*() repo->url = util_url_base_s(url); sstr_t truncated = sstrdup(url); *path = strdup(util_url_path(truncated.ptr)); free(truncated.ptr); } else { repo->url = sstrdup(url).ptr; *path = strdup("/"); } } return repo; } static Repository* url2repo(char *url, char **path) { return url2repo_s(sstr(url), path); } 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); } 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); } } static int decrypt_secrets(CmdArgs *a, PwdStore *secrets) { if(cmd_getoption(a, "noinput")) { return 1; } char *ps_password = util_password_input("Master password: "); if(!ps_password) { return 1; } if(pwdstore_setpassword(secrets, ps_password)) { fprintf(stderr, "Error: cannot create key from password\n"); return 1; } if(pwdstore_decrypt(secrets)) { fprintf(stderr, "Error: cannot decrypt secrets store\n"); return 1; } return 0; } static int get_stored_credentials(CmdArgs *a, char *credid, char **user, char **password) { if(!credid) { return 0; } PwdStore *secrets = get_pwdstore(); if(!secrets) { fprintf(stderr, "Error: no secrets store available\n"); return 0; } if(pwdstore_has_id(secrets, credid)) { if(!secrets->isdecrypted) { if(decrypt_secrets(a, secrets)) { return 0; } } PwdEntry *s_cred = pwdstore_get(secrets, credid); if(s_cred) { *user = s_cred->user; *password = s_cred->password; return 1; } } else { fprintf(stderr, "Error: credentials id '%s' not found\n", credid); } return 0; } typedef struct CredLocation { char *id; char *location; } CredLocation; static int cmp_url_cred_entry(CredLocation *e1, CredLocation *e2, void *n) { return strcmp(e2->location, e1->location); } static void free_cred_location(CredLocation *c) { // c->id is not a copy, therefore we don't have to free it free(c->location); free(c); } static int get_location_credentials(CmdArgs *a, Repository *repo, char *path, char **user, char **password) { PwdStore *secrets = get_pwdstore(); if(!secrets) { return 0; } /* * The list secrets->location contains urls or repo names as * location strings. We need a list, that contains only urls */ UcxList *locations = NULL; UCX_FOREACH(elm, secrets->locations) { PwdIndexEntry *e = elm->data; UCX_FOREACH(loc, e->locations) { char *path; Repository *r = url2repo(loc->data, &path); CredLocation *urlentry = calloc(1, sizeof(CredLocation)); urlentry->id = e->id; urlentry->location = util_concat_path(r->url, path); locations = ucx_list_append(locations, urlentry); } } // the list must be sorted locations = ucx_list_sort(locations, (cmp_func)cmp_url_cred_entry, NULL); // create full request url string and remove protocol prefix sstr_t req_url_proto = sstr(util_concat_path(repo->url, path)); sstr_t req_url = req_url_proto; if(sstrprefix(req_url, S("http://"))) { req_url = sstrsubs(req_url, 7); } else if(sstrprefix(req_url, S("https://"))) { req_url = sstrsubs(req_url, 8); } // iterate over sorted locations and check if a location is a prefix // of the requested url char *id = NULL; int ret = 0; UCX_FOREACH(elm, locations) { CredLocation *cred = elm->data; sstr_t cred_url = sstr(cred->location); // remove protocol prefix if(sstrprefix(cred_url, S("http://"))) { cred_url = sstrsubs(cred_url, 7); } else if(sstrprefix(cred_url, S("https://"))) { cred_url = sstrsubs(cred_url, 8); } if(sstrprefix(req_url, cred_url)) { id = cred->id; break; } } // if an id is found and we can access the decrypted secret store // we can set the user/password if(id && (secrets->isdecrypted || !decrypt_secrets(a, secrets))) { PwdEntry *cred = pwdstore_get(secrets, id); if(cred) { *user = cred->user; *password = cred->password; ret = 1; } } free(req_url_proto.ptr); ucx_list_free_content(locations, (ucx_destructor)free_cred_location); ucx_list_free(locations); return ret; } static DavSession* connect_to_repo(Repository *repo, char *path, CmdArgs *a) { char *user = repo->user; char *password = repo->password; if(!user && !password) { if(!get_stored_credentials(a, repo->stored_user, &user, &password)) { get_location_credentials(a, repo, path, &user, &password); } } DavSession *sn = dav_session_new_auth(ctx, repo->url, user, password); sn->flags = get_repository_flags(repo); sn->key = dav_context_get_key(ctx, repo->default_key); curl_easy_setopt(sn->handle, CURLOPT_HTTPAUTH, repo->authmethods); curl_easy_setopt(sn->handle, CURLOPT_SSLVERSION, repo->ssl_version); if(repo->cert) { curl_easy_setopt(sn->handle, CURLOPT_CAINFO, repo->cert); } if(!repo->verification || cmd_getoption(a, "insecure")) { curl_easy_setopt(sn->handle, CURLOPT_SSL_VERIFYPEER, 0); curl_easy_setopt(sn->handle, CURLOPT_SSL_VERIFYHOST, 0); } if(!cmd_getoption(a, "noinput")) { dav_session_set_authcallback(sn, request_auth2, repo); } return sn; } 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; Repository *repo = url2repo(url, &path); DavSession *sn = connect_to_repo(repo, path, 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_date_str(time_t tm) { struct tm t; struct tm n; time_t now = time(NULL); #ifdef _WIN32 memcpy(&t, localtime(&tm), sizeof(struct tm)); memcpy(&n, localtime(&now), sizeof(struct tm)); #else localtime_r(&tm, &t); localtime_r(&now, &n); #endif /* _WIN32 */ char *str = malloc(16); if(t.tm_year == n.tm_year) { strftime(str, 16, "%b %d %H:%M", &t); } else { strftime(str, 16, "%b %d %Y", &t); } return str; } static char* ls_size_str(DavResource *res) { char *str = malloc(16); uint64_t size = res->contentlength; if(res->iscollection) { str[0] = '\0'; // currently no information for collections } else if(size < 0x400) { snprintf(str, 16, "%" PRIu64 " bytes", size); } else if(size < 0x100000) { float s = (float)size/0x400; int diff = (s*100 - (int)s*100); if(diff > 90) { diff = 0; s += 0.10f; } if(size < 0x2800 && diff != 0) { // size < 10 KiB snprintf(str, 16, "%.1f KiB", s); } else { snprintf(str, 16, "%.0f KiB", s); } } else if(size < 0x40000000) { float s = (float)size/0x100000; int diff = (s*100 - (int)s*100); if(diff > 90) { diff = 0; s += 0.10f; } if(size < 0xa00000 && diff != 0) { // size < 10 MiB snprintf(str, 16, "%.1f MiB", s); } else { size /= 0x100000; snprintf(str, 16, "%.0f MiB", s); } } else if(size < 0x1000000000ULL) { float s = (float)size/0x40000000; int diff = (s*100 - (int)s*100); if(diff > 90) { diff = 0; s += 0.10f; } if(size < 0x280000000 && diff != 0) { // size < 10 GiB snprintf(str, 16, "%.1f GiB", s); } else { size /= 0x40000000; snprintf(str, 16, "%.0f GiB", s); } } else { size /= 1024; float s = (float)size/0x40000000; int diff = (s*100 - (int)s*100); if(diff > 90) { diff = 0; s += 0.10f; } if(size < 0x280000000 && diff != 0) { // size < 10 TiB snprintf(str, 16, "%.1f TiB", s); } else { size /= 0x40000000; snprintf(str, 16, "%.0f TiB", s); } } return str; } 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 = ls_date_str(res->lastmodified); char *size = ls_size_str(res); 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) { ucx_map_cstr_put(a->options, "recursive", ""); } char *url = a->argv[0]; char *path = NULL; Repository *repo = url2repo(url, &path); DavSession *sn = connect_to_repo(repo, path, 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 UcxList *reslist = NULL; 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 UcxList *stack = ucx_list_prepend(NULL, getres); while(stack) { GetResource *g = stack->data; stack = ucx_list_remove(stack, stack); 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); stack = ucx_list_prepend(stack, 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 { reslist = ucx_list_append(reslist, g); } } // 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; } UCX_FOREACH(elm, reslist) { GetResource *getres = elm->data; ret = get(repo, getres, a, tout); if(ret) { break; } } if(export) { // close tar stream if(tar_close(tout)) { fprintf(stderr, "tar stream broken\n"); ret = -1; } } ucx_list_free_content(reslist, free_getres); ucx_list_free(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; Repository *repo = url2repo(url, &path); DavSession *sn = connect_to_repo(repo, path, a); if(set_session_config(sn, a)) { return -1; } // TODO: implement locking feature 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"); } } } 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(Repository *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(Repository *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) { ucx_map_cstr_put(a->options, "resursive", ""); } char *url = a->argv[0]; char *path = NULL; Repository *repo = url2repo(url, &path); DavSession *sn = connect_to_repo(repo, path, 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(ucx_map_cstr_get(a->options, "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; } int put_entry( Repository *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; struct stat s; if(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); } DIR *dir = opendir(file); if(!dir) { // error } struct dirent *entry; int nument = 0; while((entry = readdir(dir)) != NULL) { if(!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..")) { continue; } nument++; char *entry_file = util_concat_path(file, entry->d_name); char *entry_path = util_concat_path(path, entry->d_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; } } closedir(dir); if(nument == 0) { // create empty directory 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); ret = 1; } } dav_resource_free(res); } } else if(S_ISREG(s.st_mode)) { if(printfile) { printf("put: %s\n", file); } FILE *in = fopen(file, "rb"); if(!in) { fprintf(stderr, "cannot open input file\n"); return -1; } 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(Repository *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( Repository *repo, CmdArgs *a, DavSession *sn, char *path, 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; Repository *repo = url2repo(url, &path); DavSession *sn = connect_to_repo(repo, path, 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; Repository *srcrepo = url2repo(srcurl, &srcpath); DavSession *srcsn = connect_to_repo(srcrepo, srcpath, 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; Repository *destrepo = url2repo(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); char *desthost = util_url_base(destrepo->url); if(!strcmp(srchost, desthost)) { DavSession *destsn = connect_to_repo(destrepo, destpath, a); if(set_session_config(destsn, a)) { return -1; } DavResource *dest = dav_resource_new(destsn, destpath); char *desthref = dav_resource_get_href(dest); char *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; Repository *repo = url2repo(url, &path); DavSession *sn = connect_to_repo(repo, path, 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); sstr_t h = sstrn(header, s*n); if(sstrprefix(h, S("Date:"))) { sstr_t v = sstrsubs(h, 5); v = sstrdup(sstrtrim(v)); *date_str = 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; Repository *repo = url2repo(url, &path); DavSession *sn = connect_to_repo(repo, path, 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; Repository *repo = url2repo(url, &path); DavSession *sn = connect_to_repo(repo, path, 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; Repository *repo = url2repo(url, &path); DavSession *sn = connect_to_repo(repo, path, 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 { dav_set_string_property(res, property, value); } } 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; Repository *repo = url2repo(url, &path); DavSession *sn = connect_to_repo(repo, path, 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; Repository *repo = url2repo(url, &path); DavSession *sn = connect_to_repo(repo, path, a); ucx_mempool_reg_destr(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(!sstrcasecmp(sstr(timeoutstr), S("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() { UcxBuffer *buf = ucx_buffer_new(NULL, 128, UCX_BUFFER_AUTOEXTEND); int c; while((c = getchar()) != EOF) { if(c == '\n') { break; } ucx_buffer_putc(buf, c); } char *str = NULL; sstr_t line = sstrtrim(sstrn(buf->space, buf->size)); if(line.length != 0) { str = sstrdup(line).ptr; } ucx_buffer_free(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; Repository *repo = url2repo(url, &path); DavSession *sn = connect_to_repo(repo, path, a); ucx_mempool_reg_destr(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; Repository *repo = url2repo(url, &path); DavSession *sn = connect_to_repo(repo, path, 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); char *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 = ls_size_str(res); 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)) { sstr_t value = sstr(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 i=0;i<3;i++) { if(x) { if(i == 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; Repository *repo = url2repo(url, &path); DavSession *sn = connect_to_repo(repo, path, 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; Repository *repo = url2repo(url, &path); DavSession *sn = connect_to_repo(repo, path, 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; Repository *repo = url2repo(url, &path); DavSession *sn = connect_to_repo(repo, path, 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; Repository *repo = url2repo(url, &path); DavSession *sn = connect_to_repo(repo, path, 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; Repository *repo = url2repo(url, &path); DavSession *sn = connect_to_repo(repo, path, 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() { UcxBuffer *buf = ucx_buffer_new(NULL, 1024, UCX_BUFFER_AUTOEXTEND); size_t size = ucx_stream_copy(stdin, buf, fread, ucx_buffer_write); if(size == 0) { ucx_buffer_free(buf); return NULL; } else { ucx_buffer_putc(buf, '\0'); char *str = buf->space; free(buf); return str; } } static void xml2str_i(DavXmlNode *node, UcxBuffer *buf, int indent) { while(node) { if(node->type == DAV_XML_ELEMENT) { if(node->children) { if(dav_xml_isstring(node->children)) { sstr_t s = sstrtrim(sstr(dav_xml_getstring(node->children))); ucx_bprintf( buf, "%*s<%s>%.*s</%s>\n", indent, "", node->name, (int)s.length, s.ptr, node->name); } else { ucx_bprintf(buf, "%*s<%s>\n", indent, "", node->name); xml2str_i(node->children, buf, indent+2); ucx_bprintf(buf, "%*s</%s>\n", indent, "", node->name); } } else { ucx_bprintf(buf, "%*s<%s />", indent, "", node->name); ucx_buffer_putc(buf, '\n'); } } else if(node->type == DAV_XML_TEXT) { sstr_t val = sstrtrim(sstrn(node->content, node->contentlength)); if(val.length > 0) { ucx_bprintf(buf, "%*.*s", indent, (int)val.length, val.ptr); } } node = node->next; } } char* xml2str(DavXmlNode *node) { char *str = malloc(256); UcxBuffer *buf = ucx_buffer_new(str, 256, UCX_BUFFER_AUTOEXTEND); xml2str_i(node, buf, 0); ucx_buffer_putc(buf, 0); char *space = buf->space; ucx_buffer_free(buf); return space; } void printxmldoc(FILE *out, char *root, char *rootns, DavXmlNode *content) { UcxMap *nsmap = ucx_map_new(16); ucx_map_cstr_put(nsmap, rootns, "x0"); fprintf(out, "%s", "<?xml version=\"1.0\"?>\n"); fprintf(out, "<x0:%s xmlns:x0=\"%s\">", root, rootns); dav_print_node(out, (write_func)fwrite, nsmap, content); fprintf(out, "</x0:%s>\n", root); // cleanup namespace map ucx_map_cstr_remove(nsmap, rootns); ucx_map_free_content(nsmap, free); ucx_map_free(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(get_repository(sstr(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"); Repository repo; memset(&repo, 0, sizeof(Repository)); repo.name = name; repo.url = url; repo.user = user; repo.password = password; int ret = 0; if(add_repository(&repo)) { 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; } for(int i = 0 ; i < args->argc ; i++) { sstr_t reponame = sstr(args->argv[i]); Repository* repo = get_repository(reponame); if(repo) { if(remove_repository(repo)) { fprintf(stderr, "Cannot write config.xml\n"); return -1; } } else { fprintf(stderr, "Repository %s does not exist - skipped.\n", reponame.ptr); return -1; } } return -1; } 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; } sstr_t reponame = sstr(args->argv[0]); Repository* repo = get_repository(reponame); if(repo) { sstr_t url = sstr(repo->url); if(repo->user && !cmd_getoption(args, "plain")) { int hostindex = 0; if(sstrprefix(url, S("https://"))) { printf("https://"); hostindex = 8; } else if(sstrprefix(url, S("http://"))) { printf("http://"); hostindex = 7; } printf("%s", repo->user); if(repo->password) { CURL *curl = curl_easy_init(); char *pw = curl_easy_escape( curl, repo->password, strlen(repo->password)); printf(":%s", pw); curl_free(pw); curl_easy_cleanup(curl); } 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; UcxList *locations = NULL; while((location = assistant_getoptcfg("Location"))) { locations = ucx_list_append(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(id) free(id); if(user) free(user); if(password) free(password); ucx_list_free_content(locations, free); ucx_list_free(locations); 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(secrets->index->count == 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; UcxList *list = secrets->locations; for(int i=0;i<2;i++) { UCX_FOREACH(elm, list) { PwdIndexEntry *index = elm->data; PwdEntry *e = ucx_map_cstr_get(secrets->ids, index->id); if(e) { printf("Id: %s\n", e->id); printf("User: %s\n", e->user); UCX_FOREACH(loc, index->locations) { char *location = loc->data; 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 = ucx_map_cstr_get(secrets->index, id); if(!index) { return; } printf("Id: %s\n", entry->id); printf("User: %s\n", entry->user); UCX_FOREACH(elm, index->locations) { printf("Location: %s\n", (char*)elm->data); } } static void secrets_remove_location(PwdIndexEntry *index) { if(!index->locations) { printf("no locations\n"); return; } printf("0: abort\n"); int i = 1; UCX_FOREACH(elm, index->locations) { printf("%d: %s\n", i, (char*)elm->data); 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 { UcxList *elm = ucx_list_get(index->locations, ln - 1); if(elm) { free(elm->data); index->locations = ucx_list_remove(index->locations, elm); } } } 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 = ucx_map_cstr_get(secrets->index, 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) { index->locations = ucx_list_append(index->locations, location); } break; } case 3: { // remove location secrets_remove_location(index); break; } case 4: { // list locations if(!index->locations) { printf("no locations\n"); } else { UCX_FOREACH(elm, index->locations) { printf("Location: %s\n", (char*)elm->data); } } 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 char** read_args_from_stdin(int *argc) { // read stdin into buffer UcxBuffer *in = ucx_buffer_new(NULL, 1024, UCX_BUFFER_AUTOEXTEND); ucx_stream_copy(stdin, in, (read_func)fread, (write_func)ucx_buffer_write); // split input into lines ssize_t count = 0; sstr_t *lines = scstrsplit(scstrn(in->space, in->pos), SC("\n"), &count); 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 ucx_buffer_free(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) { sstr_t prefix = { NULL, 0 }; if(cmd) { prefix = sstr(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(!sstrprefix(sstrn(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) { sstr_t 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) { UcxList *repos = get_repositories(); UCX_FOREACH(elm, repos) { Repository *repo = elm->data; if(sstrprefix(sstr(repo->name), url)) { if(quote == '\0') { printf("%s/\n", repo->name); } else { printf("%c%s/%c\n", quote, repo->name, quote); } } } } else { // url completion ucx_map_cstr_put(args->options, "noinput", ""); char *path = NULL; Repository *repo = url2repo_s(url, &path); DavSession *sn = connect_to_repo(repo, path, args); if(!sn) { return 0; } if(set_session_config(sn, args)) { return 0; } size_t plen = strlen(path); sstr_t filter; char *lspath = NULL; if(path[plen-1] == '/') { lspath = strdup(path); filter = S(""); } else { lspath = util_parent_path(path); filter = sstr(util_resource_name(path)); } DavResource *ls = dav_query(sn, "select - from %s order by name", lspath); DavResource *elm = ls ? ls->children : NULL; while(elm) { sstr_t name = sstr(elm->name); if(sstrprefix(name, filter)) { int space = 0; for(int i=0;i<name.length;i++) { if(name.ptr[i] == ' ') { space = 1; break; } } UcxBuffer *out = ucx_buffer_new(NULL, 512, UCX_BUFFER_AUTOEXTEND); ucx_buffer_puts(out, repo->name); 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 spaces if(elm->path[i] == ' ' && quote == '\0') { ucx_buffer_puts(out, "\\ "); } else { ucx_buffer_putc(out, elm->path[i]); } } } else { ucx_buffer_puts(out, elm->path); } if(elm->iscollection) { if(out->space[out->pos-1] != '/') { ucx_buffer_putc(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); } ucx_buffer_free(out); } elm = elm->next; } free(lspath); dav_session_destroy(sn); } return 10; }