application/config.c

changeset 55
1ce14068ef31
parent 50
9c25e2616bfa
--- a/application/config.c	Mon Oct 21 15:45:12 2024 +0200
+++ b/application/config.c	Wed Oct 23 10:37:43 2024 +0200
@@ -1,539 +1,369 @@
-/*
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
- *
- * Copyright 2024 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 <cx/hash_map.h>
-#include <cx/utils.h>
-#include <errno.h>
-#include <libxml/tree.h>
-
-#include "pwd.h"
-#include "config.h"
-#include "system.h"
-
-#include <libidav/utils.h>
-#include <libidav/config.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 CxMap* repos;
-static CxMap* keys;
-
-static DavConfig* davconfig;
-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;
-}
-
-cxmutstr config_load_file(const char* path) {
-    FILE* file = sys_fopen(path, "r");
-    if (!file) {
-        return (cxmutstr) { NULL, 0 };
-    }
-
-    CxBuffer buf;
-    cxBufferInit(&buf, NULL, 1024, cxDefaultAllocator, CX_BUFFER_AUTO_EXTEND);
-    cx_stream_copy(file, &buf, (cx_read_func)fread, (cx_write_func)cxBufferWrite);
-    fclose(file);
-
-    return cx_mutstrn(buf.space, buf.size);
-}
-
-int load_config(DavContext *ctx) {
-    context = ctx;
-    // TODO: free the config somewhere
-    repos = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 16);
-    keys = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 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: {
-                davconfig = dav_config_new(NULL);
-                return 0;
-            }
-            default: {
-                perror("Cannot load config.xml");
-            }
-        }
-        return 1;
-    }
-    
-    cxmutstr config_content = config_load_file(file);
-    int config_error;
-    davconfig = dav_config_load(config_content, &config_error);
-    free(config_content.ptr);
-    free(file);
-    
-    if(!davconfig) {
-        fprintf(stderr, "Cannot load config.xml\n");
-        return 1;
-    }
-    
-    if(dav_config_register_namespaces(davconfig, ctx)) {
-        return 1;
-    }
-    
-    return dav_config_register_keys(davconfig, ctx, load_key_file);
-}
-
-DavConfig* get_config(void) {
-    return davconfig;
-}
-
-int store_config(void) {
-    if (check_config_dir()) {
-        return 1;
-    }
-
-    CxBuffer* buf = dav_config2buf(davconfig);
-    if (!buf) {
-        return 1;
-    }
-
-    char* file = util_concat_path(ENV_HOME, ".dav/config.xml");
-    FILE* cout = sys_fopen(file, "w");
-    if (!cout) {
-        cxBufferFree(buf);
-        return 1;
-    }
-
-    // should only fail if we run out of disk space or something like that
-    // in that case, the config file is only destroyed
-    // could only be prevented, if we write to a temp file first and than
-    // rename it
-    fwrite(buf->space, buf->size, 1, cout);
-
-    cxBufferFree(buf);
-    fclose(cout);
-
-    return 0;
-}
-
-void free_config(void) {
-    if (davconfig) {
-        dav_config_free(davconfig);
-    }
-}
-
-cxmutstr load_key_file(const char* filename) {
-    cxmutstr k;
-    k.ptr = NULL;
-    k.length = 0;
-
-    FILE* file = NULL;
-    if (filename[0] == '/') {
-        file = sys_fopen(filename, "r");
-    }
-    else {
-        char* path = util_concat_path(ENV_HOME, ".dav/");
-        char* p2 = util_concat_path(path, filename);
-        file = sys_fopen(p2, "r");
-        free(path);
-        free(p2);
-    }
-
-    if (!file) {
-        fprintf(stderr, "Error: cannot load keyfile %s\n", filename);
-        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;
-}
-
-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;
-}
-
-
-
-static int decrypt_secrets(PwdStore *secrets) {
-    char *ps_password = NULL;
-    if(secrets->unlock_cmd && strlen(secrets->unlock_cmd) > 0) {
-        CxBuffer *cmd_out = cxBufferCreate(NULL, 128, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
-        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;
-            }
-        }
-        cxBufferFree(cmd_out);
-    }
-
-    return 1;
-    // TODO
-    /*
-    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(char *credid, char **user, char **password) {
-    return 0;
-    // TODO
-    /*
-    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(DavCfgRepository *repo, const 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
-    */
-    CxList *locations = cxLinkedListCreate(cxDefaultAllocator, (cx_compare_func)cmp_url_cred_entry, CX_STORE_POINTERS);
-    cxDefineDestructor(locations, free_cred_location);
-    CxIterator i = cxListIterator(secrets->locations);
-    cx_foreach(PwdIndexEntry*, e, i) {
-        CxIterator entry_iter = cxListIterator(e->locations);
-        cx_foreach(char *, loc, entry_iter) {
-            cxmutstr rpath;
-            DavCfgRepository *r = dav_config_url2repo_s(davconfig, cx_str(loc), &rpath);
-            CredLocation *urlentry = calloc(1, sizeof(CredLocation));
-            urlentry->id = e->id;
-            urlentry->location = util_concat_path_s(cx_strcast(r->url.value), cx_strcast(rpath)).ptr;
-            cxListAdd(locations, urlentry);
-            free(rpath.ptr);
-        }
-    }
-    // the list must be sorted
-    cxListSort(locations);
-
-    // create full request url string and remove protocol prefix
-    cxmutstr req_url_proto = util_concat_path_s(cx_strcast(repo->url.value), cx_str(path));
-    cxstring req_url = cx_strcast(req_url_proto);
-    if(cx_strprefix(req_url, CX_STR("http://"))) {
-        req_url = cx_strsubs(req_url, 7);
-    } else if(cx_strprefix(req_url, CX_STR("https://"))) {
-        req_url = cx_strsubs(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;
-    i = cxListIterator(locations);
-    cx_foreach(CredLocation*, cred, i) {
-        cxstring cred_url = cx_str(cred->location);
-
-        // remove protocol prefix
-        if(cx_strprefix(cred_url, CX_STR("http://"))) {
-            cred_url = cx_strsubs(cred_url, 7);
-        } else if(cx_strprefix(cred_url, CX_STR("https://"))) {
-            cred_url = cx_strsubs(cred_url, 8);
-        }
-
-        if(cx_strprefix(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(secrets))) {
-        PwdEntry *cred = pwdstore_get(secrets, id);
-        if(cred) {
-            *user = cred->user;
-            *password = cred->password;
-            ret = 1;
-        }
-    }
-
-    free(req_url_proto.ptr);
-    cxListDestroy(locations);
-
-    return ret;
-}
-
-
-DavSession* connect_to_repo(DavContext *ctx, DavCfgRepository *repo, const char *path, dav_auth_func authfunc) {
-    cxmutstr decodedpw = dav_repository_get_decodedpassword(repo);
-
-    char *user = repo->user.value.ptr;
-    char *password = decodedpw.ptr;
-
-    if(!user && !password) {
-        if(!get_stored_credentials(repo->stored_user.value.ptr, &user, &password)) {
-            get_location_credentials(repo, path, &user, &password);
-        }
-    }
-
-    DavSession *sn = dav_session_new_auth(ctx, repo->url.value.ptr, user, password);
-    if(password) {
-        free(password);
-    }
-
-    sn->flags = dav_repository_get_flags(repo);
-    sn->key = dav_context_get_key(ctx, repo->default_key.value.ptr);
-    curl_easy_setopt(sn->handle, CURLOPT_HTTPAUTH, repo->authmethods);
-    curl_easy_setopt(sn->handle, CURLOPT_SSLVERSION, repo->ssl_version);
-    if(repo->cert.value.ptr) {
-        curl_easy_setopt(sn->handle, CURLOPT_CAINFO, repo->cert.value.ptr);
-    }
-    if(!repo->verification.value) {
-        curl_easy_setopt(sn->handle, CURLOPT_SSL_VERIFYPEER, 0);
-        curl_easy_setopt(sn->handle, CURLOPT_SSL_VERIFYHOST, 0);
-    }
-    
-    return sn;
-}
+/*
+ * 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 <cx/hash_map.h>
+#include <cx/utils.h>
+#include <errno.h>
+#include <libxml/tree.h>
+
+#include "config.h"
+#include "pwd.h"
+#include "system.h"
+
+#include <libidav/utils.h>
+#include <libidav/config.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 CxMap *repos;
+static CxMap *keys;
+
+static DavConfig *davconfig;
+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;
+}
+
+cxmutstr config_load_file(const char *path) {
+    FILE *file = sys_fopen(path, "r");
+    if(!file) {
+        return (cxmutstr){NULL,0};
+    }
+    
+    CxBuffer buf;
+    cxBufferInit(&buf, NULL, 1024, cxDefaultAllocator, CX_BUFFER_AUTO_EXTEND);
+    cx_stream_copy(file, &buf, (cx_read_func)fread, (cx_write_func)cxBufferWrite);
+    fclose(file);
+    
+    return cx_mutstrn(buf.space, buf.size);
+}
+
+int load_config(DavContext *ctx) {
+    context = ctx;
+    // TODO: free the config somewhere
+    repos = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 16);
+    keys = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 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: {
+                davconfig = dav_config_new(NULL);
+                return 0;
+            }
+            default: {
+                perror("Cannot load config.xml");
+            }
+        }
+        return 1;
+    }
+    
+    cxmutstr config_content = config_load_file(file);
+    int config_error;
+    davconfig = dav_config_load(config_content, &config_error);
+    free(config_content.ptr);
+    free(file);
+    
+    if(!davconfig) {
+        fprintf(stderr, "Cannot load config.xml\n");
+        return 1;
+    }
+    
+    if(dav_config_register_namespaces(davconfig, ctx)) {
+        return 1;
+    }
+    
+    return dav_config_register_keys(davconfig, ctx, load_key_file);
+}
+
+DavConfig* get_config(void) {
+    return davconfig;
+}
+
+int store_config(void) {
+    if(check_config_dir()) {
+        return 1;
+    }
+    
+    CxBuffer *buf = dav_config2buf(davconfig);
+    if(!buf) {
+        return 1;
+    }
+    
+    char *file = util_concat_path(ENV_HOME, ".dav/config.xml");
+    FILE *cout = sys_fopen(file, "w");
+    if(!cout) {
+        cxBufferFree(buf);
+        return 1;
+    }
+    
+    // should only fail if we run out of disk space or something like that
+    // in that case, the config file is only destroyed
+    // could only be prevented, if we write to a temp file first and than
+    // rename it
+    fwrite(buf->space, buf->size, 1, cout);
+    
+    cxBufferFree(buf);
+    fclose(cout);
+    
+    return 0;
+}
+
+void free_config(void) {
+    if(davconfig) {
+        dav_config_free(davconfig);
+    }
+}
+
+cxmutstr load_key_file(const char *filename) {
+    cxmutstr k;
+    k.ptr = NULL;
+    k.length = 0;
+    
+    FILE *file = NULL;
+    if(filename[0] == '/') {
+        file = sys_fopen(filename, "r");
+    } else {
+        char *path = util_concat_path(ENV_HOME, ".dav/");
+        char *p2 = util_concat_path(path, filename);
+        file = sys_fopen(p2, "r");
+        free(path);
+        free(p2);
+    }
+    
+    if(!file) {
+        fprintf(stderr, "Error: cannot load keyfile %s\n", filename);
+        return k;
+    }
+    
+    char *data = malloc(256);
+    size_t r = fread(data, 1, 256, file);
+    k.ptr = data;
+    k.length = r;
+    
+    fclose(file);
+    return k;
+}
+
+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; 
+}
+
+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);
+}
+
+int get_stored_credentials(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(pwdstore_decrypt_secrets(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;
+}
+
+
+int get_location_credentials(DavCfgRepository *repo, const 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
+     */
+    CxList *locations = cxLinkedListCreate(cxDefaultAllocator, (cx_compare_func)cmp_url_cred_entry, CX_STORE_POINTERS);
+    cxDefineDestructor(locations, free_cred_location);
+    CxIterator i = cxListIterator(secrets->locations);
+    cx_foreach(PwdIndexEntry*, e, i) {
+        CxIterator entry_iter = cxListIterator(e->locations);
+        cx_foreach(char *, loc, entry_iter) {
+            cxmutstr rpath;
+            DavCfgRepository *r = dav_config_url2repo_s(davconfig, cx_str(loc), &rpath);
+            CredLocation *urlentry = calloc(1, sizeof(CredLocation));
+            urlentry->id = e->id;
+            urlentry->location = util_concat_path_s(cx_strcast(r->url.value), cx_strcast(rpath)).ptr;
+            cxListAdd(locations, urlentry);
+            free(rpath.ptr);
+        }
+    }
+    // the list must be sorted
+    cxListSort(locations);
+    
+    // create full request url string and remove protocol prefix
+    cxmutstr req_url_proto = util_concat_path_s(cx_strcast(repo->url.value), cx_str(path));
+    cxstring req_url = cx_strcast(req_url_proto);
+    if(cx_strprefix(req_url, CX_STR("http://"))) {
+        req_url = cx_strsubs(req_url, 7);
+    } else if(cx_strprefix(req_url, CX_STR("https://"))) {
+        req_url = cx_strsubs(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;
+    i = cxListIterator(locations);
+    cx_foreach(CredLocation*, cred, i) {
+        cxstring cred_url = cx_str(cred->location);
+        
+        // remove protocol prefix
+        if(cx_strprefix(cred_url, CX_STR("http://"))) {
+            cred_url = cx_strsubs(cred_url, 7);
+        } else if(cx_strprefix(cred_url, CX_STR("https://"))) {
+            cred_url = cx_strsubs(cred_url, 8);
+        }
+        
+        if(cx_strprefix(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 || !pwdstore_decrypt_secrets(secrets))) {
+        PwdEntry *cred = pwdstore_get(secrets, id);
+        if(cred) {
+            *user = cred->user;
+            *password = cred->password;
+            ret = 1;
+        }
+    }
+    
+    free(req_url_proto.ptr);
+    cxListDestroy(locations);
+    
+    return ret;
+}

mercurial