Tue, 13 Nov 2018 20:35:30 +0100
fixes cmd_mkdir() not verifying the presence of a key + specifying a key with '-k' without requesting encryption is now considered an error
/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright 2018 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 <stdio.h> #include <stdlib.h> #include <string.h> #include <stdbool.h> #include <errno.h> #include <unistd.h> #include <time.h> #include <sys/types.h> #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 "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" void test() { } int main(int argc, char **argv) { if(argc < 2) { fprintf(stderr, "Missing command\n"); print_usage(argv[0]); return -1; } char *cmd = argv[1]; CmdArgs *args = cmd_parse_args(argc - 2, argv + 2); if(!args) { print_usage(argv[0]); return -1; } 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, "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, "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, "version") || !strcasecmp(cmd, "-version") || !strcasecmp(cmd, "--version")) { fprintf(stderr, "dav %s\n", DAV_VERSION); } else if(!strcasecmp(cmd, "complete")) { if(args->argc < 2) { return 1; } char *index_str = args->argv[0]; int64_t index = 0; if(!util_strtoint(index_str, &index)) { return 1; } if(args->argc + 2 != argc) { // we have to fix the index for(int i=2;i<args->argc;i++) { if(index == i-2) { break; } if(strcmp(argv[i+2], args->argv[i])) { index--; } } } ret = shell_completion(args, index); } else { print_usage(argv[0]); } } dav_context_destroy(ctx); cmd_args_free(args); free_config(); xmlCleanupParser(); curl_global_cleanup(); 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>", "mkdir [-pc] [-k <key>] [-L <lock>] <url>", "remove [-pc] [-L <lock>] <url>", "copy [-pcO] [-L <lock>] <url> <url>", "move [-pcO] [-L <lock>] <url> <url>", "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) { // TODO: use scstr_t after UCX update sstr_t c = sstr((char*)cmd); for(int i=0;;i++) { char *str = cmdusageinfo[i]; if(!str) { break; } sstr_t u = sstr(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, "\n"); 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, " -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\n"); fprintf(stderr, " remove-repository\n"); fprintf(stderr, " list-repositories\n"); fprintf(stderr, " repository-url\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(char *url, char **path) { size_t ulen = strlen(url); *path = NULL; int s; if(ulen > 7 && !strncasecmp(url, "http://", 7)) { s = 7; } else if(ulen > 8 && !strncasecmp(url, "https://", 8)) { s = 8; } else { s = 1; } sstr_t r = sstr(url); sstr_t p = sstr("/"); for(int i=s;i<ulen;i++) { char c = url[i]; if(c == '/') { r = sstrn(url, i); p = sstrsubs(sstr(url), i); if(p.length == 0) { p = sstrn("/", 1); } break; } } 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[ulen-1] == '/') { repo->url = strdup(url); *path = strdup("/"); } else if (strchr(url, '/')) { repo->url = util_parent_path(url); // TODO: check/fix *path = strdup(util_resource_name(url)-1); } else { repo->url = strdup(url); *path = strdup("/"); } } return repo; } 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); *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; } 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); } 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_put(CmdArgs *a, DavBool import) { if(a->argc != 2) { // TODO: change, when put supports multiple files (however it should do) fprintf(stderr, "Too %s arguments\n", a->argc < 2 ? "few":"many"); fprintf(stderr, "Usage: dav %s\n", find_usage_str("put")); return -1; } if(import) { ucx_map_cstr_put(a->options, "resursive", ""); } char *url = a->argv[0]; char *file = a->argv[1]; 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; } int ret; if(!import) { if(!strcmp(file, "-")) { FILE *in = stdin; ret = put_file(repo, a, sn, path, "stdin", in, 0); } else { ret = put_entry(repo, a, sn, path, file, TRUE); } } else { ret = put_tar(repo, a, sn, file, path); } free(path); return ret; } int put_entry(Repository *repo, CmdArgs *a, DavSession *sn, char *path, char *file, DavBool root) { 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) { 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, FALSE); 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(recursive) { printf("put: %s\n", file); } /* * use stdin if the input file is - */ 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, 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; } 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; } int put_file(Repository *repo, CmdArgs *a, DavSession *sn, char *path, char *name, 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); free(newpath); int ret = put_file(repo, a, sn, res->path, NULL, in, len); // TODO: free res return ret; } 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; } int cmd_remove(CmdArgs *a) { if(a->argc != 1) { // TODO: change, when removal of multiple files is supported fprintf(stderr, "Too %s arguments\n", a->argc < 1 ? "few":"many"); fprintf(stderr, "Usage: dav %s\n", find_usage_str("remove")); 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(!res) { fprintf(stderr, "error\n"); return -1; } if(dav_delete(res)) { print_resource_error(sn, res->path); fprintf(stderr, "Cannot delete resource.\n"); return -1; } free(path); return 0; } int cmd_mkdir(CmdArgs *a) { if(a->argc != 1) { // TODO: change, when creation of multiple dirs is supported fprintf(stderr, "Too %s arguments\n", a->argc < 1 ? "few":"many"); fprintf(stderr, "Usage: dav %s\n", find_usage_str("mkdir")); 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); if(check_encryption_key(a, sn)) { // TODO: free return -1; } DavResource *res = dav_resource_new(sn, path); if(!res) { fprintf(stderr, "error\n"); // TODO: free return -1; } res->iscollection = 1; if(dav_create(res)) { print_resource_error(sn, res->path); fprintf(stderr, "Cannot create collection.\n"); // TODO: free return -1; } free(path); return 0; } 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; } 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 { timeout = (time_t)atoi(timeoutstr); // TODO: use strtol } } 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; } int cmd_add_user(CmdArgs *args) { char *master_pw = util_password_input("Master password: "); if(!master_pw) { return 1; } PwdStore *secrets = get_pwdstore(); if(!secrets) { secrets = pwdstore_new(); } if(pwdstore_setpassword(secrets, master_pw)) { 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"); } char *id = assistant_getcfg("Credentials identifier"); if(id && pwdstore_get(secrets, id)) { fprintf(stderr, "Credentials with this id already exist"); return 1; } char *user = assistant_getcfg("User"); char *password = util_password_input("Password: "); char *location = assistant_getoptcfg("Location"); UcxList *locations = location ? ucx_list_append(NULL, location) : NULL; int ret = 1; if(user && password) { pwdstore_put_index(secrets, id, locations); pwdstore_put(secrets, id, user, password); int ret = pwdstore_save(secrets); if(ret) { fprintf(stderr, "Error: saving srcrets store failed.\n"); } } pwdstore_free(secrets); if(id) free(id); if(user) free(user); if(password) free(password); if(location) free(location); if(locations) ucx_list_free(locations); return ret; } int shell_completion(CmdArgs *args, int index) { if(args->argc < 2 || args->argc < 3) { return 1; } if(index == 1) { sstr_t prefix = { NULL, 0 }; if(args->argc > 2) { prefix = sstr(args->argv[2]); } 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; } char *cmd = args->argv[2]; if(!strcmp(cmd, "date")) { return 0; } // get already typed URL or NULL, if the user hasn't started typing yet char *url = args->argc > 3 ? args->argv[3] : 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); //printf("file: {%s}\n", file); if(index == 2) { // url completion return url_completion(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(url); } } return 0; } int url_completion(char *u) { sstr_t url; url.ptr = u; url.length = u ? strlen(u) : 0; // 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)) { printf("%s/\n", repo->name); } } } else { // url completion CmdArgs a; memset(&a, 0, sizeof(CmdArgs)); a.options = ucx_map_new(4); ucx_map_cstr_put(a.options, "noinput", ""); char *path = NULL; Repository *repo = url2repo(u, &path); DavSession *sn = connect_to_repo(repo, path, &a); ucx_map_free(a.options); if(!sn) { 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++) { if(elm->path[i] == ' ') { 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, '/'); } } printf("%.*s\n", (int)out->pos, out->space); ucx_buffer_free(out); } elm = elm->next; } free(lspath); dav_session_destroy(sn); } return 10; }