Mon, 09 Aug 2021 19:01:02 +0200
use tcsetattr only if stdin is a tty
/* * 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 <sys/types.h> #include <ucx/map.h> #include <errno.h> #include <libxml/tree.h> #include "pwd.h" #include "config.h" #include "main.h" #include "pwd.h" #include <libidav/utils.h> #define xstreq(a,b) xmlStrEqual(BAD_CAST a, BAD_CAST b) #define xstrEQ(a,b) !xmlStrcasecmp(BAD_CAST a, BAD_CAST b) #define print_error(lineno, ...) \ do {\ fprintf(stderr, "Error (config.xml line %u): ", lineno); \ fprintf(stderr, __VA_ARGS__); \ fprintf(stderr, "Abort.\n"); \ } while(0); #define print_warning(lineno, ...) \ do {\ fprintf(stderr, "Warning (config.xml line %u): ", lineno); \ fprintf(stderr, __VA_ARGS__); \ } while(0); #ifdef _WIN32 #define ENV_HOME getenv("USERPROFILE") #else #define ENV_HOME getenv("HOME") #endif /* _WIN32 */ static UcxMap *repos; static UcxMap *keys; static PwdStore *pstore; static char *secretstore_unlock_cmd; static char *secretstore_lock_cmd; int check_config_dir(void) { char *file = util_concat_path(ENV_HOME, ".dav"); int ret = 0; if(util_mkdir(file, S_IRWXU)) { if(errno != EEXIST) { ret = 1; } } free(file); return ret; } static DavContext *context; void create_default_config(char *file) { xmlDoc *doc = xmlNewDoc(BAD_CAST "1.0"); xmlNode *root = xmlNewNode(NULL, BAD_CAST "configuration"); xmlDocSetRootElement(doc, root); xmlSaveFormatFileEnc(file, doc, "UTF-8", 1); xmlFreeDoc(doc); } char* config_file_path(char *name) { char *davd = util_concat_path(ENV_HOME, ".dav"); if(!davd) { return NULL; } char *path = util_concat_path(davd, name); free(davd); return path; } int load_config(DavContext *ctx) { context = ctx; // TODO: free the config somewhere repos = ucx_map_new(16); keys = ucx_map_new(16); char *pwfile = util_concat_path(ENV_HOME, ".dav/secrets.crypt"); pstore = pwdstore_open(pwfile); free(pwfile); char *file = util_concat_path(ENV_HOME, ".dav/config.xml"); struct stat s; if(stat(file, &s)) { switch(errno) { case ENOENT: { return 0; } default: { perror("Cannot load config.xml"); } } return 1; } xmlDoc *doc = xmlReadFile(file, NULL, 0); free(file); if(!doc) { fprintf(stderr, "Cannot load config.xml\n"); return 1; } xmlNode *xml_root = xmlDocGetRootElement(doc); xmlNode *node = xml_root->children; int ret = 0; while(node && !ret) { if(node->type == XML_ELEMENT_NODE) { if(xstreq(node->name, "repository")) { ret = load_repository(node); } else if(xstreq(node->name, "key")) { ret = load_key(node); } else if (xstreq(node->name, "http-proxy")) { ret = load_proxy(ctx->http_proxy, node, HTTP_PROXY); } else if (xstreq(node->name, "https-proxy")) { ret = load_proxy(ctx->https_proxy, node, HTTPS_PROXY); } else if (xstreq(node->name, "namespace")) { ret = load_namespace(node); } else if (xstreq(node->name, "secretstore")) { ret = load_secretstore(node); } else { fprintf(stderr, "Unknown config element: %s\n", node->name); ret = 1; } } node = node->next; } xmlFreeDoc(doc); return ret; } void free_config(void) { if(repos) { UcxMapIterator i = ucx_map_iterator(repos); UcxKey k; Repository *repo; UCX_MAP_FOREACH(k, repo, i) { if(repo->default_key) { free(repo->default_key); } if(repo->name) { free(repo->name); } if(repo->password) { free(repo->password); } if(repo->url) { free(repo->url); } if(repo->user) { free(repo->user); } if(repo->cert) { free(repo->cert); } free(repo); } ucx_map_free(repos); } if(keys) { ucx_map_free(keys); } } Repository* repository_new(void) { Repository *repo = calloc(1, sizeof(Repository)); repo->encrypt_name = false; repo->encrypt_content = false; repo->encrypt_properties = false; repo->decrypt_name = false; repo->decrypt_content = true; repo->decrypt_properties = false; repo->verification = true; repo->ssl_version = CURL_SSLVERSION_DEFAULT; repo->authmethods = CURLAUTH_BASIC; return repo; } static int repo_add_config(Repository *repo, const xmlNode* node) { unsigned short lineno = node->line; char *key = (char*)node->name; char *value = util_xml_get_text(node); /* every key needs a value */ if(!value) { /* TODO: maybe this should only be reported, if the key is valid * But this makes the code very ugly. */ print_error(lineno, "missing value for config element: %s\n", key); return 1; } if(xstreq(key, "name")) { repo->name = strdup(value); } else if(xstreq(key, "url")) { repo->url = strdup(value); } else if(xstreq(key, "user")) { repo->user = strdup(value); } else if(xstreq(key, "password")) { repo->password = util_base64decode(value); } else if(xstreq(key, "stored-user")) { repo->stored_user = strdup(value); } else if(xstreq(key, "default-key")) { repo->default_key = strdup(value); } else if(xstreq(key, "full-encryption")) { if(util_getboolean(value)) { repo->encrypt_name = true; repo->encrypt_content = true; repo->encrypt_properties = true; repo->decrypt_name = true; repo->decrypt_content = true; repo->decrypt_properties = true; } } else if(xstreq(key, "content-encryption")) { if(util_getboolean(value)) { repo->encrypt_content = true; repo->decrypt_content = true; } else { repo->encrypt_content = false; } } else if(xstreq(key, "decrypt-content")) { repo->decrypt_content = util_getboolean(value); } else if(xstreq(key, "decrypt-name")) { repo->decrypt_name = util_getboolean(value); } else if(xstreq(key, "cert")) { char *configdir = util_concat_path(ENV_HOME, ".dav"); char *certfile = util_concat_path(configdir, value); repo->cert = certfile; free(configdir); } else if(xstreq(key, "verification")) { repo->verification = util_getboolean(value); } else if(xstreq(key, "ssl-version")) { if(xstrEQ(value, "TLSv1")) { repo->ssl_version = CURL_SSLVERSION_TLSv1; } else if(xstrEQ(value, "SSLv2")) { repo->ssl_version = CURL_SSLVERSION_SSLv2; } else if(xstrEQ(value, "SSLv3")) { repo->ssl_version = CURL_SSLVERSION_SSLv3; } #if LIBCURL_VERSION_MAJOR >= 7 #if LIBCURL_VERSION_MINOR >= 34 else if(xstrEQ(value, "TLSv1.0")) { repo->ssl_version = CURL_SSLVERSION_TLSv1_0; } else if(xstrEQ(value, "TLSv1.1")) { repo->ssl_version = CURL_SSLVERSION_TLSv1_1; } else if(xstrEQ(value, "TLSv1.2")) { repo->ssl_version = CURL_SSLVERSION_TLSv1_2; } #endif #if LIBCURL_VERSION_MINOR >= 52 else if(xstrEQ(value, "TLSv1.3")) { repo->ssl_version = CURL_SSLVERSION_TLSv1_3; } #endif #endif else { print_warning(lineno, "unknown ssl version: %s\n", value); } } else if(xstreq(key, "authmethods")) { repo->authmethods = CURLAUTH_NONE; const char *delims = " \t\r\n"; char *meths = strdup(value); char *meth = strtok(meths, delims); while (meth) { if(xstrEQ(meth, "basic")) { repo->authmethods |= CURLAUTH_BASIC; } else if(xstrEQ(meth, "digest")) { repo->authmethods |= CURLAUTH_DIGEST; } else if(xstrEQ(meth, "negotiate")) { repo->authmethods |= CURLAUTH_GSSNEGOTIATE; } else if(xstrEQ(meth, "ntlm")) { repo->authmethods |= CURLAUTH_NTLM; } else if(xstrEQ(meth, "any")) { repo->authmethods = CURLAUTH_ANY; } else if(xstrEQ(meth, "none")) { /* skip */ } else { print_warning(lineno, "unknown authentication method: %s\n", meth); } meth = strtok(NULL, delims); } free(meths); } else { print_error(lineno, "unkown repository config element: %s\n", key); return 1; } return 0; } int load_repository(const xmlNode *reponode) { Repository *repo = repository_new(); { xmlNode *node = reponode->children; int ret = 0; while(node && !ret) { if(node->type == XML_ELEMENT_NODE) { ret = repo_add_config(repo, node); } node = node->next; } if(ret) { free(repo); return 1; } } if(!repo->name) { print_error(reponode->line, "missing name for repository.\n"); return 1; } if(!repo->url) { print_error(reponode->line, "missing url for repository '%s'.\n", repo->name); return 1; } ucx_map_cstr_put(repos, repo->name, repo); return 0; } int load_proxy(DavProxy *proxy, const xmlNode *proxynode, int type) { const char *stype; if(type == HTTPS_PROXY) { stype = "https"; } else if(type == HTTP_PROXY) { stype = "http"; } if(!proxy) { // no xml error - so report this directly via fprintf fprintf(stderr, "no memory reserved for %s proxy.\n", stype); return 1; } xmlNode *node = proxynode->children; int ret = 0; while(node && !ret) { if(node->type == XML_ELEMENT_NODE) { char *value = util_xml_get_text(node); int reportmissingvalue = 0; if(xstreq(node->name, "url")) { if(!(reportmissingvalue = !value)) { proxy->url = strdup(value); } } else if(xstreq(node->name, "user")) { if(!(reportmissingvalue = !value)) { proxy->username = strdup(value); } } else if(xstreq(node->name, "password")) { if(!(reportmissingvalue = !value)) { proxy->password = util_base64decode(value); } } else if(xstreq(node->name, "no")) { if(!(reportmissingvalue = !value)) { proxy->no_proxy = strdup(value); } } else { print_error(node->line, "invalid element for proxy config: %s\n", node->name); ret = 1; } if (reportmissingvalue) { print_error(node->line, "missing value for proxy configuration element: %s\n", node->name); ret = 1; } } node = node->next; } if(!ret && !proxy->url) { print_error(proxynode->line, "missing url for %s proxy.\n", stype); return 1; } return ret; } int load_key(const xmlNode *keynode) { xmlNode *node = keynode->children; Key *key = calloc(1, sizeof(Key)); key->type = KEY_AES256; int error = 0; while(node) { if(node->type == XML_ELEMENT_NODE) { char *value = util_xml_get_text(node); if(!value) { // next } else if(xstreq(node->name, "name")) { key->name = strdup(value); } else if(xstreq(node->name, "file")) { // load key file sstr_t key_data = load_key_file(value); if(key_data.length > 0) { key->data = key_data.ptr; key->length = key_data.length; } else { print_error(node->line, "cannot get key from file: %s\n", value); error = 1; } } else if(xstreq(node->name, "type")) { if(!strcmp(value, "aes128")) { key->type = KEY_AES128; } else if(!strcmp(value, "aes256")) { key->type = KEY_AES256; } else { print_error(node->line, "unknown key type %s\n", value); error = 1; } } } node = node->next; } if(!error && key->name) { error = 0; size_t expected_length = 0; if(key->type == KEY_AES128) { expected_length = 16; } if(key->type == KEY_AES256) { expected_length = 32; } if(key->length < expected_length) { print_error(keynode->line, "key %s is too small (%zu < %zu)\n", key->name, key->length, expected_length); error = 1; } // add key to context if(!error) { ucx_map_cstr_put(keys, key->name, key); dav_context_add_key(context, key); } } // cleanup if(error) { if(key->data) { free(key->data); } free(key); return 1; } else { return 0; } } sstr_t load_key_file(char *filename) { sstr_t k; k.ptr = NULL; k.length = 0; FILE *file = NULL; if(filename[0] == '/') { file = fopen(filename, "r"); } else { char *path = util_concat_path(ENV_HOME, ".dav/"); char *p2 = util_concat_path(path, filename); file = fopen(p2, "r"); free(path); free(p2); } if(!file) { return k; } char *data = malloc(256); size_t r = fread(data, 1, 256, file); k.ptr = data; k.length = r; fclose(file); return k; } static char* get_attr_content(xmlNode *node) { // TODO: remove code duplication (util_xml_get_text) while(node) { if(node->type == XML_TEXT_NODE) { return (char*)node->content; } node = node->next; } return NULL; } int load_namespace(const xmlNode *node) { const char *prefix = NULL; const char *uri = NULL; xmlAttr *attr = node->properties; while(attr) { if(attr->type == XML_ATTRIBUTE_NODE) { char *value = get_attr_content(attr->children); if(!value) { print_error( node->line, "missing value for attribute %s\n", (char*)attr->name); return 1; } if(xstreq(attr->name, "prefix")) { prefix = value; } else if(xstreq(attr->name, "uri")) { uri = value; } else { print_error( node->line, "unexpected attribute %s\n", (char*)attr->name); return 1; } } attr = attr->next; } if(!prefix) { print_error(node->line, "missing prefix attribute\n"); return 1; } if(!uri) { print_error(node->line, "missing uri attribute\n"); return 1; } if(dav_get_namespace(context, prefix)) { print_error(node->line, "namespace prefix '%s' already used\n", prefix); return 1; } return dav_add_namespace(context, prefix, uri); } int load_secretstore(const xmlNode *node) { // currently only one secretstore is supported if(!pstore) { return 0; } node = node->children; int error = 0; while(node) { if(node->type == XML_ELEMENT_NODE) { char *value = util_xml_get_text(node); if(value) { if(xstreq(node->name, "unlock-command")) { pstore->unlock_cmd = strdup(value); } else if(xstreq(node->name, "lock-command")) { pstore->lock_cmd = strdup(value); } } } node = node->next; } return error; } Repository* get_repository(sstr_t name) { if(!name.ptr) { return NULL; } return ucx_map_sstr_get(repos, name); } int get_repository_flags(Repository *repo) { int flags = 0; if(repo->decrypt_content) { flags |= DAV_SESSION_DECRYPT_CONTENT; } if(repo->decrypt_name) { flags |= DAV_SESSION_DECRYPT_NAME; } if(repo->decrypt_properties) { flags |= DAV_SESSION_DECRYPT_PROPERTIES; } if(repo->encrypt_content) { flags |= DAV_SESSION_ENCRYPT_CONTENT; } if(repo->encrypt_name) { flags |= DAV_SESSION_ENCRYPT_NAME; } if(repo->encrypt_properties) { flags |= DAV_SESSION_ENCRYPT_PROPERTIES; } return flags; } Key* get_key(char *name) { if(!name) { return NULL; } return ucx_map_cstr_get(keys, name); } int add_repository(Repository *repo) { if(check_config_dir()) { fprintf(stderr, "Cannot create .dav directory\n"); return 1; } char *file = util_concat_path(ENV_HOME, ".dav/config.xml"); struct stat s; if(stat(file, &s)) { switch(errno) { case ENOENT: { create_default_config(file); break; } default: { perror("Cannot load config.xml"); free(file); return 1; } } } xmlDoc *doc = xmlReadFile(file, NULL, 0); if(!doc) { free(file); fprintf(stderr, "Cannot load config.xml\n"); return 1; } xmlNode *root = xmlDocGetRootElement(doc); if(!root) { fprintf(stderr, "Missing root node in config.xml\n"); xmlFreeDoc(doc); free(file); return 1; } xmlNode *repoNode = xmlNewNode(NULL, BAD_CAST "repository"); xmlNodeAddContent(repoNode, BAD_CAST "\n\t\t"); xmlNewTextChild(repoNode, NULL, BAD_CAST "name", BAD_CAST repo->name); xmlNodeAddContent(repoNode, BAD_CAST "\n\t\t"); xmlNewTextChild(repoNode, NULL, BAD_CAST "url", BAD_CAST repo->url); xmlNodeAddContent(repoNode, BAD_CAST "\n"); if(repo->user) { xmlNodeAddContent(repoNode, BAD_CAST "\t\t"); xmlNewChild(repoNode, NULL, BAD_CAST "user", BAD_CAST repo->user); xmlNodeAddContent(repoNode, BAD_CAST "\n"); if(repo->password) { char *pwenc = util_base64encode( repo->password, strlen(repo->password)); xmlNodeAddContent(repoNode, BAD_CAST "\t\t"); xmlNewTextChild(repoNode, NULL, BAD_CAST "password", BAD_CAST pwenc); free(pwenc); xmlNodeAddContent(repoNode, BAD_CAST "\n"); } } xmlNodeAddContent(repoNode, BAD_CAST "\t"); xmlNodeAddContent(root, BAD_CAST "\n\t"); xmlAddChild(root, repoNode); xmlNodeAddContent(root, BAD_CAST "\n"); int ret = (xmlSaveFormatFileEnc(file, doc, "UTF-8", 1) == -1) ? 1 : 0; xmlFreeDoc(doc); free(file); return ret; } int remove_repository(Repository *repo) { char *file = util_concat_path(ENV_HOME, ".dav/config.xml"); struct stat s; if(stat(file, &s)) { perror("Cannot access config.xml"); free(file); return 1; } xmlDoc *doc = xmlReadFile(file, NULL, 0); if(!doc) { free(file); fprintf(stderr, "Cannot load config.xml\n"); return 1; } xmlNodePtr root = xmlDocGetRootElement(doc); if(!root) { fprintf(stderr, "Missing root node in config.xml\n"); xmlFreeDoc(doc); free(file); return 1; } xmlNodePtr repoNode = root->children; xmlNodePtr matchedRepoNode = NULL; while(!matchedRepoNode && repoNode) { if(repoNode->type == XML_ELEMENT_NODE && xstreq(repoNode->name, "repository")) { xmlNodePtr nameNode = repoNode->children; while(!matchedRepoNode && nameNode) { if (nameNode->type == XML_ELEMENT_NODE && xstreq(nameNode->name, "name")) { char *reponame = util_xml_get_text(nameNode); if(!strcmp(repo->name, reponame)) { matchedRepoNode = repoNode; } } nameNode = nameNode->next; } } repoNode = repoNode->next; } if(matchedRepoNode) { xmlNodePtr prev = matchedRepoNode->prev; xmlNodePtr next = matchedRepoNode->next; if(prev && prev->type == XML_TEXT_NODE) { sstr_t content = sstr((char*)prev->content); sstr_t lf = sstrrchr(content, '\n'); if(lf.length > 0) { *lf.ptr = '\0'; char* newcontent = sstrdup(content).ptr; xmlNodeSetContent(prev, (xmlChar*)newcontent); free(newcontent); } } if(next && next->type == XML_TEXT_NODE) { sstr_t lf = sstrchr(sstr((char*)next->content), '\n'); if(lf.length > 0) { char* newcontent = malloc(lf.length); memcpy(newcontent, lf.ptr+1, lf.length-1); newcontent[lf.length-1] = '\0'; xmlNodeSetContent(next, (xmlChar*)newcontent); free(newcontent); } } xmlUnlinkNode(matchedRepoNode); xmlFreeNode(matchedRepoNode); } int ret = (xmlSaveFormatFileEnc(file, doc, "UTF-8", 1) == -1) ? 1 : 0; xmlFreeDoc(doc); free(file); return ret; } int list_repositories(void) { UcxMapIterator i = ucx_map_iterator(repos); Repository *repo; UCX_MAP_FOREACH(key, repo, i) { printf("%s\n", repo->name); } return 0; } UcxList* get_repositories(void) { UcxList *list = NULL; UcxMapIterator i = ucx_map_iterator(repos); Repository *repo; UCX_MAP_FOREACH(key, repo, i) { list = ucx_list_append(list, repo); } return list; } PwdStore* get_pwdstore(void) { return pstore; } int pwdstore_save(PwdStore *pwdstore) { if(check_config_dir()) { return 1; } char *pwfile = util_concat_path(ENV_HOME, ".dav/secrets.crypt"); int ret = pwdstore_store(pwdstore, pwfile); free(pwfile); return ret; } 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; } Repository* url2repo(char *url, char **path) { return url2repo_s(sstr(url), path); } static int decrypt_secrets(CmdArgs *a, PwdStore *secrets) { if(cmd_getoption(a, "noinput")) { return 1; } char *ps_password = NULL; if(secrets->unlock_cmd && strlen(secrets->unlock_cmd) > 0) { UcxBuffer *cmd_out = ucx_buffer_new(NULL, 128, UCX_BUFFER_AUTOEXTEND); if(!util_exec_command(secrets->unlock_cmd, cmd_out)) { // command successful, get first line from output without newline // and use that as password for the secretstore size_t len = 0; for(size_t i=0;i<=cmd_out->size;i++) { if(i == cmd_out->size || cmd_out->space[i] == '\n') { len = i; break; } } if(len > 0) { ps_password = malloc(len + 1); memcpy(ps_password, cmd_out->space, len); ps_password[len] = 0; } } ucx_buffer_free(cmd_out); } if(!ps_password) { 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; } 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_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; } 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; } DavSession* connect_to_repo(DavContext *ctx, Repository *repo, char *path, dav_auth_func authfunc, 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, authfunc, repo); } return sn; } int request_auth(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; }