update libidav, unify config.c with cmdutils

Wed, 23 Oct 2024 10:37:43 +0200

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Wed, 23 Oct 2024 10:37:43 +0200
changeset 55
1ce14068ef31
parent 54
3ca3acefc66a
child 56
294d5515583a

update libidav, unify config.c with cmdutils

application/Makefile file | annotate | diff | comparison | revisions
application/config.c file | annotate | diff | comparison | revisions
application/config.h file | annotate | diff | comparison | revisions
application/connect.c file | annotate | diff | comparison | revisions
application/connect.h file | annotate | diff | comparison | revisions
application/pwd.c file | annotate | diff | comparison | revisions
application/pwd.h file | annotate | diff | comparison | revisions
configure file | annotate | diff | comparison | revisions
libidav/Makefile file | annotate | diff | comparison | revisions
libidav/pwdstore.c file | annotate | diff | comparison | revisions
libidav/pwdstore.h file | annotate | diff | comparison | revisions
libidav/session.c file | annotate | diff | comparison | revisions
make/project.xml file | annotate | diff | comparison | revisions
--- a/application/Makefile	Mon Oct 21 15:45:12 2024 +0200
+++ b/application/Makefile	Wed Oct 23 10:37:43 2024 +0200
@@ -34,8 +34,8 @@
 SRC  = main.c
 SRC += application.c
 SRC += config.c
+SRC += connect.c
 SRC += davcontroller.c
-SRC += pwd.c
 SRC += system.c
 SRC += window.c
 
--- 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;
+}
--- a/application/config.h	Mon Oct 21 15:45:12 2024 +0200
+++ b/application/config.h	Wed Oct 23 10:37:43 2024 +0200
@@ -1,74 +1,73 @@
-/*
- * 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.
- */
-
-#ifndef CONFIG_H
-#define	CONFIG_H
-
-#include <cx/string.h>
-#include <stdbool.h>
-#include <libidav/webdav.h>
-#include "pwd.h"
-
-#include <libidav/config.h>
-
-#ifdef	__cplusplus
-extern "C" {
-#endif
-
-
-#define HTTP_PROXY 1
-#define HTTPS_PROXY 2
-
-
-int check_config_dir(void);
-
-char* config_file_path(char* name);
-
-cxmutstr config_load_file(const char* path);
-
-int load_config(DavContext* ctx);
-DavConfig* get_config(void);
-int store_config(void);
-void free_config(void);
-
-cxmutstr load_key_file(const char* filename);
-
-PwdStore* get_pwdstore(void);
-int pwdstore_save(PwdStore* pwdstore);
-
-
-int request_auth(DavSession* sn, void* userdata);
-
-DavSession* connect_to_repo(DavContext *ctx, DavCfgRepository *repo, const char *path, dav_auth_func authfunc);
-
-#ifdef	__cplusplus
-}
-#endif
-
-#endif	/* CONFIG_H */
-
+/*
+ * 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.
+ */
+
+#ifndef CONFIG_H
+#define	CONFIG_H
+
+#include <cx/string.h>
+#include <stdbool.h>
+#include <libidav/webdav.h>
+
+#include <libidav/config.h>
+#include <libidav/pwdstore.h>
+
+#ifdef	__cplusplus
+extern "C" {
+#endif
+
+
+#define HTTP_PROXY 1
+#define HTTPS_PROXY 2
+
+
+int check_config_dir(void);
+
+char* config_file_path(char *name);
+
+cxmutstr config_load_file(const char *path);
+    
+int load_config(DavContext *ctx);
+DavConfig* get_config(void);
+int store_config(void);
+void free_config(void);
+
+cxmutstr load_key_file(const char *filename);
+
+PwdStore* get_pwdstore(void);
+int pwdstore_save(PwdStore *pwdstore);
+
+int get_stored_credentials(char *credid, char **user, char **password);
+int get_location_credentials(DavCfgRepository *repo, const char *path, char **user, char **password);
+
+
+#ifdef	__cplusplus
+}
+#endif
+
+#endif	/* CONFIG_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/application/connect.c	Wed Oct 23 10:37:43 2024 +0200
@@ -0,0 +1,110 @@
+/*
+ * 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 "connect.h"
+
+#include <libidav/utils.h>
+
+#include <cx/string.h>
+#include <cx/utils.h>
+
+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);
+    // TODO: reactivate
+    //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 || 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) {
+    DavCfgRepository *repo = userdata;
+    
+    cxstring user = {NULL, 0};
+    char ubuf[256];
+    if(repo->user.value.ptr) {
+       user = cx_strcast(repo->user.value);
+    } else {
+        fprintf(stderr, "User: ");
+        fflush(stderr);
+        user = cx_str(fgets(ubuf, 256, stdin));
+    }
+    if(!user.ptr) {
+        return 0;
+    }
+    if(user.length > 0 && user.ptr[user.length-1] == '\n') {
+        user.length--;
+    }
+    if(user.length > 0 && user.ptr[user.length-1] == '\r') {
+        user.length--;
+    }
+    
+    char *password = util_password_input("Password: ");
+    if(!password || strlen(password) == 0) {
+        return 0;
+    }
+    
+    dav_session_set_auth_s(sn, user, cx_str(password));
+    free(password);
+    
+    return 0;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/application/connect.h	Wed Oct 23 10:37:43 2024 +0200
@@ -0,0 +1,46 @@
+/*
+ * 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.
+ */
+
+#ifndef DAV_CONNECT_H
+#define DAV_CONNECT_H
+
+#include "config.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+DavSession* connect_to_repo(DavContext *ctx, DavCfgRepository *repo, const char *path, dav_auth_func authfunc);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* DAV_CONNECT_H */
+
--- a/application/pwd.c	Mon Oct 21 15:45:12 2024 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,463 +0,0 @@
-/*
- * 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 "pwd.h"
-
-#include <cx/utils.h>
-#include <cx/hash_map.h>
-
-#ifdef _WIN32
-#include <winsock.h>
-#pragma comment(lib, "Ws2_32.lib")
-#else
-#include <netinet/in.h>
-#endif
-
-PwdStore* pwdstore_open(const char *file) {
-    FILE *in = fopen(file, "r");
-    if(!in) {
-        return NULL;
-    }
-    
-    CxBuffer *buf = cxBufferCreate(NULL, 2048, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
-    cx_stream_copy(in, buf, (cx_read_func)fread, (cx_write_func)cxBufferWrite);
-    fclose(in);
-    
-    if(buf->size < PWDS_HEADER_SIZE || buf->space[0] != PWDS_MAGIC_CHAR) {
-        cxBufferFree(buf);
-        return NULL;
-    }
-    
-    PwdStore *p = malloc(sizeof(PwdStore));
-    p->ids = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 16);
-    p->locations = cxLinkedListCreateSimple(CX_STORE_POINTERS);
-    p->noloc = cxLinkedListCreateSimple(CX_STORE_POINTERS);
-    p->index = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 16);
-    p->content = buf;
-    p->key = NULL;
-    p->unlock_cmd = NULL;
-    p->lock_cmd = NULL;
-    p->encoffset = PWDS_HEADER_SIZE;
-    p->isdecrypted = 0;
-    
-    if(pwdstore_getindex(p)) {
-        pwdstore_free(p);
-        return NULL;
-    }
-    
-    return p;
-}
-
-PwdStore* pwdstore_new(void) {
-    PwdStore *p = calloc(1, sizeof(PwdStore));
-    p->ids = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 16);
-    p->locations = cxLinkedListCreateSimple(CX_STORE_POINTERS);
-    p->noloc = cxLinkedListCreateSimple(CX_STORE_POINTERS);
-    p->index = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 16);
-    p->content = cxBufferCreate(NULL, PWDS_HEADER_SIZE, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
-    PWDS_MAGIC(p) = PWDS_MAGIC_CHAR;
-    PWDS_VERSION(p) = 1;
-    PWDS_ENC(p) = DAV_KEY_AES256;
-    PWDS_PWFUNC(p) = DAV_PWFUNC_PBKDF2_SHA256;
-    dav_rand_bytes((unsigned char*)p->content->space+4, 16);
-    p->isdecrypted = 1;
-    p->encoffset = PWDS_HEADER_SIZE;
-    return p;
-}
-
-static int readval(CxBuffer *in, char **val, int allowzero) {
-    // value  = length string
-    // length = uint32
-    // string = bytes
-    
-    *val = NULL;
-    
-    // get length
-    uint32_t length = 0;
-    if(cxBufferRead(&length, 1, sizeof(uint32_t), in) != sizeof(uint32_t)) {
-        return 0;
-    }
-    length = ntohl(length); // convert from BE to host byte order
-    if(length == 0) {
-        if(allowzero) {
-            return 1;
-        } else {
-            return 0;
-        }
-    }
-    if(length > PWDSTORE_MAX_LEN) {
-        return 0;
-    }
-    
-    // get value
-    char *value = malloc(length + 1);
-    value[length] = 0;
-    if(cxBufferRead(value, 1, length, in) != length) {
-        free(value);
-        return 0;
-    }
-    
-    *val = value;
-    return 1;
-}
-
-static int read_indexentry(PwdStore *p, CxBuffer *in) {
-    // read type of index element
-    int type = cxBufferGet(in);
-    if(type == EOF || type != 0) {
-        // only type 0 supported yet
-        return 0;
-    }
-      
-    char *id = NULL;
-    CxList *locations = cxLinkedListCreateSimple(CX_STORE_POINTERS);
-    cxDefineDestructor(locations, free);
-    
-    // get id (required)
-    int ret = 0;
-    if(readval(in, &id, FALSE)) {
-        // get locations
-        char *location = NULL;
-        while((ret = readval(in, &location, TRUE)) == 1) {
-            if(!location) {
-                break;
-            }
-            cxListAdd(locations, location);
-        }
-    }
-    
-    if(ret) {
-        pwdstore_put_index(p, id, locations);
-    } else {
-        if(id) free(id);
-        cxListDestroy(locations);
-    }
-    
-    return ret;
-}
-
-static int read_pwdentry(PwdStore *p, CxBuffer *in) {
-    int type = cxBufferGet(in);
-    if(type == EOF || type != 0) {
-        // only type 0 supported yet
-        return 0;
-    }
-    
-    char *id = NULL;
-    char *user = NULL;
-    char *password = NULL;
-    
-    int ret = 0;
-    if(readval(in, &id, FALSE)) {
-        if(readval(in, &user, FALSE)) {
-            if(readval(in, &password, FALSE)) {
-                pwdstore_put(p, id, user, password);
-                ret = 1;
-            }
-        }
-    }
-    
-    if(id) free(id);
-    if(user) free(user);
-    if(password) free(password);
-    
-    return ret;
-}
-
-static void remove_list_entries(PwdStore *s, const char *id) {
-    CxIterator i = cxListMutIterator(s->locations);
-    cx_foreach(PwdIndexEntry*, ie, i) {
-        if(!strcmp(ie->id, id)) {
-            cxIteratorFlagRemoval(i);
-            cxIteratorNext(i);
-            break;
-        }
-    }
-    i = cxListMutIterator(s->noloc);
-    cx_foreach(PwdIndexEntry*, ie, i) {
-        if(!strcmp(ie->id, id)) {
-            cxIteratorFlagRemoval(i);
-            cxIteratorNext(i);
-            break;
-        }
-    }
-}
-
-void pwdstore_remove_entry(PwdStore *s, const char *id) {
-    remove_list_entries(s, id);
-    
-    CxHashKey key = cx_hash_key_str(id);
-    PwdIndexEntry *i = cxMapRemoveAndGet(s->index, key);
-    PwdEntry *e = cxMapRemoveAndGet(s->ids, key);
-    
-    if(i) {
-        cxListDestroy(i->locations);
-        free(i->id);
-        free(i);
-    }
-    if(e) {
-        free(e->id);
-        free(e->user);
-        free(e->password);
-        free(e);
-    }
-}
-
-int pwdstore_getindex(PwdStore *s) {
-    uint32_t netindexlen;
-    
-    // set the position to the last 4 bytes of the header
-    // for reading index length
-    s->content->pos = PWDS_HEADER_SIZE - sizeof(uint32_t);
-    
-    // read indexlen and convert to host byte order
-    if(cxBufferRead(&netindexlen, 1, sizeof(uint32_t), s->content) != sizeof(uint32_t)) {
-        return 1;
-    }
-    uint32_t indexlen = ntohl(netindexlen);
-    
-    // integer overflow check
-    if(UINT32_MAX - PWDS_HEADER_SIZE < indexlen) {
-        return 1;
-    }
-    if(s->content->size < PWDS_HEADER_SIZE + indexlen) {
-        return 1;
-    }
-    // encrypted content starts after the index content
-    s->encoffset = PWDS_HEADER_SIZE + indexlen;
-    
-    // the index starts after the header
-    CxBuffer *index = cxBufferCreate(s->content->space+PWDS_HEADER_SIZE, indexlen, cxDefaultAllocator, 0);
-    index->size = indexlen;
-    
-    // read index
-    while(read_indexentry(s, index)) {}
-    
-    // free index buffer structure (not the content)
-    cxBufferFree(index);
-    
-    return 0;
-}
-
-int pwdstore_decrypt(PwdStore *p) {
-    if(!p->key) {
-        return 1;
-    }
-    if(p->isdecrypted) {
-        return 0;
-    }
-    
-    // decrypt contet
-    size_t encsz = p->content->size - p->encoffset;
-    CxBuffer *enc = cxBufferCreate(p->content->space + p->encoffset, encsz, cxDefaultAllocator, 0);
-    enc->size = encsz;
-    enc->size = p->content->size - p->encoffset;
-    CxBuffer *content = aes_decrypt_buffer(enc, p->key);
-    cxBufferFree(enc);
-    if(!content) {
-        return 1;
-    }
-    
-    while(read_pwdentry(p, content)) {}
-    
-    cxBufferFree(content);
-    
-    return 0;
-}
-
-int pwdstore_setpassword(PwdStore *p, const char *password) {
-    DavKey *key = dav_pw2key(
-            password,
-            (unsigned char*)(p->content->space + 4),
-            16,
-            PWDS_PWFUNC(p),
-            PWDS_ENC(p));
-    if(!key) {
-        return 1;
-    }
-    
-    p->key = key;
-    return 0;
-}
-
-void pwdstore_encsettings(PwdStore *p, uint8_t enc, uint8_t pwfunc) {
-    PWDS_ENC(p) = enc;
-    PWDS_PWFUNC(p) = pwfunc;
-}
-
-void pwdstore_free_entry(PwdEntry *e) {
-    if(e->id) free(e->id);
-    if(e->user) free(e->user);
-    if(e->password) free(e->password);
-    free(e);
-}
-
-void pwdstore_free(PwdStore* p) {
-    cxDefineDestructor(p->ids, pwdstore_free_entry);
-    cxMapDestroy(p->ids);
-    
-    cxListDestroy(p->locations);
-    
-    if(p->content) {
-        cxBufferFree(p->content);
-    }
-    
-    free(p);
-}
-
-int pwdstore_has_id(PwdStore *s, const char *id) {
-    return cxMapGet(s->index, cx_hash_key_str(id)) ? 1 : 0;
-}
-
-PwdEntry* pwdstore_get(PwdStore *p, const char *id) {
-    PwdEntry *e = cxMapGet(p->ids, cx_hash_key_str(id));
-    if(e && e->user && e->password) {
-        return e;
-    } else {
-        return NULL;
-    }
-}
-
-void pwdstore_put(PwdStore *p, const char *id, const char *username, const char *password) {
-    PwdEntry *entry = malloc(sizeof(PwdEntry));
-    entry->id = strdup(id);
-    entry->user = strdup(username);
-    entry->password = strdup(password);
-    cxMapPut(p->ids, cx_hash_key_str(id), entry);
-}
-
-void pwdstore_put_index(PwdStore *p, char *id, CxList *locations) {
-    PwdIndexEntry *e = cxMapGet(p->index, cx_hash_key_str(id));
-    if(e) {
-        return;
-    }
-    PwdIndexEntry *newentry = malloc(sizeof(PwdIndexEntry));
-    newentry->id = id;
-    if(locations) {
-        newentry->locations = locations;
-        cxListAdd(p->locations, newentry);
-    } else {
-        newentry->locations = NULL;
-        cxListAdd(p->noloc, newentry);
-    }
-    cxMapPut(p->index, cx_hash_key_str(id), newentry);
-}
-
-void write_index_entry(CxBuffer *out, PwdIndexEntry *e) {
-    uint32_t idlen = strlen(e->id);
-    uint32_t netidlen = htonl(idlen);
-    
-    cxBufferPut(out, 0); // type
-
-    cxBufferWrite(&netidlen, 1, sizeof(uint32_t), out);
-    cxBufferWrite(e->id, 1, idlen, out);
-    
-    CxIterator i = cxListIterator(e->locations);
-    cx_foreach(char *, location, i) {
-        uint32_t locationlen = strlen(location);
-        uint32_t netlocationlen = htonl(locationlen);
-        
-        cxBufferWrite(&netlocationlen, 1, sizeof(uint32_t), out);
-        cxBufferWrite(location, 1, locationlen, out);
-    }
-    
-    uint32_t terminate = 0;
-    cxBufferWrite(&terminate, 1, sizeof(uint32_t), out);
-}
-
-int pwdstore_store(PwdStore *p, const char *file) {
-    if(!p->key) {
-        return 1;
-    }
-    
-    CxBuffer *index = cxBufferCreate(NULL, 2048, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
-    CxBuffer *content = cxBufferCreate(NULL, 2048, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
-    
-    // create index
-    CxIterator i = cxListIterator(p->noloc);
-    cx_foreach(PwdIndexEntry*, e, i) {
-        write_index_entry(index, e);
-    }
-    i = cxListIterator(p->locations);
-    cx_foreach(PwdIndexEntry*, e, i) {
-        write_index_entry(index, e);
-    }
-    
-    i = cxMapIteratorValues(p->ids);
-    cx_foreach(PwdEntry*, value, i) {
-        if(!value->id || !value->user || !value->password) {
-            continue;
-        }
-        
-        uint32_t idlen = strlen(value->id);
-        uint32_t ulen = strlen(value->user);
-        uint32_t plen = strlen(value->password);
-        uint32_t netidlen = htonl(idlen);
-        uint32_t netulen = htonl(ulen);
-        uint32_t netplen = htonl(plen);
-        
-        // content buffer
-        cxBufferPut(content, 0); // type
-        
-        cxBufferWrite(&netidlen, 1, sizeof(uint32_t), content);
-        cxBufferWrite(value->id, 1, idlen, content);
-        cxBufferWrite(&netulen, 1, sizeof(uint32_t), content);
-        cxBufferWrite(value->user, 1, ulen, content);
-        cxBufferWrite(&netplen, 1, sizeof(uint32_t), content);
-        cxBufferWrite(value->password, 1, plen, content);
-    }
-      
-    content->pos = 0;
-    CxBuffer *enc = aes_encrypt_buffer(content, p->key);
-
-    p->content->pos = PWDS_HEADER_SIZE - sizeof(uint32_t);
-    p->content->size = PWDS_HEADER_SIZE;
-    
-    // add index after header
-    uint32_t netindexlen = htonl((uint32_t)index->size);
-    cxBufferWrite(&netindexlen, 1, sizeof(uint32_t), p->content);
-    cxBufferWrite(index->space, 1, index->size, p->content);
-    
-    // add encrypted buffer
-    cxBufferWrite(enc->space, 1, enc->size, p->content);
-    
-    cxBufferFree(enc);
-    
-    FILE *out = fopen(file, "w");
-    if(!out) {
-        return 1;
-    }
-    fwrite(p->content->space, 1, p->content->size, out);
-    fclose(out);
-    
-    return 0;
-}
--- a/application/pwd.h	Mon Oct 21 15:45:12 2024 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,197 +0,0 @@
-/*
- * 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.
- */
-
-#ifndef PWD_H
-#define PWD_H
-
-#include <stdlib.h>
-#include <inttypes.h>
-
-#include <cx/map.h>
-#include <cx/buffer.h>
-#include <cx/linked_list.h>
-#include <libidav/crypto.h>
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-#define PWDSTORE_MAX_LEN 4096
-    
-/*
- * File Format:
- * 
- * file = header, index, enc_content
- * header = magic, version, enc, pwfunc, salt, indexlen
- * magic = 1 byte
- * version = 1 byte
- * enc = 1 byte
- * pwfunc = 1 byte
- * salt = 16 bytes
- * indexlen = uint32
- * index = { itype length id locations zero }
- * enc_content = iv bytes
- * iv = 16 bytes
- * content = { entry }
- * entry = itype length id length username length password
- * length = uint32
- * zero = 4 zero bytes
- * itype = 1 byte
- * id = string
- * locations = { length string }
- * username = string
- * password = string
- * 
- * The content is AES encrypted with a key derived from a password
- * and the salt. The first 16 bytes are the aes iv.
- * 
- * All integers are big endian
- */
-    
-#define PWDS_HEADER_SIZE 24
-    
-typedef struct PwdStore        PwdStore;
-typedef struct PwdEntry        PwdEntry;
-typedef struct PwdIndexEntry   PwdIndexEntry;
-
-struct PwdStore {
-    /*
-     * map of all credentials
-     * key is the username
-     * value is PwdEntry*
-     */
-    CxMap *ids;
-    
-    /*
-     * list of all credentials with location
-     * value is PwdIndexEntry*
-     */
-    CxList *locations;
-    
-    /*
-     * list of all credentials without location
-     * value is PwdIndexEntry*
-     */
-    CxList *noloc;
-    
-    /*
-     * index map that contains all elements from the lists
-     * 'locations' and 'noloc'
-     */
-    CxMap *index;
-    
-    /*
-     * a buffer containing the complete file content
-     */
-    CxBuffer *content;
-    
-    /*
-     * key used for encryption/decryption
-     */
-    DavKey *key;
-    
-    /*
-     * optional shell command, that is used for getting the master password
-     */
-    char *unlock_cmd;
-    
-    /*
-     * optional shell command, that is exected when the secretstore is closed
-     */
-    char *lock_cmd;
-    
-    /*
-     * start offset of the encrypted buffer
-     */
-    uint32_t encoffset;
-    
-    /*
-     * indicates if the PwdStore is decrypted with pwdstore_decrypt
-     */
-    uint8_t isdecrypted;
-};
-
-#define PWDS_MAGIC(p) (p)->content->space[0]
-#define PWDS_VERSION(p) (p)->content->space[1]
-#define PWDS_ENC(p) (p)->content->space[2]
-#define PWDS_PWFUNC(p) (p)->content->space[3]
-
-#define PWDS_MAGIC_CHAR 'P'
-
-struct PwdEntry {
-    char *id;
-    char *user;
-    char *password;
-};
-
-struct PwdIndexEntry {
-    char *id;
-    CxList *locations;
-};
-
-/*
- * opens the password store
- * the content is still encrypted and must be decrypted using pwdstore_decrypt
- */
-PwdStore* pwdstore_open(const char *file);
-
-PwdStore* pwdstore_new(void);
-
-/*
- * decrypts the password store with a password
- */
-int pwdstore_decrypt(PwdStore *p);
-
-int pwdstore_setpassword(PwdStore *p, const char *password);
-
-void pwdstore_encsettings(PwdStore *p, uint8_t enc, uint8_t pwfunc);
-
-void pwdstore_free_entry(PwdEntry *e);
-void pwdstore_free(PwdStore* p);
-
-int pwdstore_has_id(PwdStore *s, const char *id);
-int pwdstore_has_location(PwdStore *s, const char *location);
-
-PwdEntry* pwdstore_get(PwdStore *p, const char *id);
-
-void pwdstore_put(PwdStore *p, const char *id, const char *username, const char *password);
-void pwdstore_put_index(PwdStore *p, char *id, CxList *locations);
-
-void pwdstore_remove_entry(PwdStore *s, const char *id);
-
-int pwdstore_store(PwdStore *p, const char *file);
-
-/* private */
-int pwdstore_getindex(PwdStore *s);
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif /* PWD_H */
-
--- a/configure	Mon Oct 21 15:45:12 2024 +0200
+++ b/configure	Wed Oct 23 10:37:43 2024 +0200
@@ -719,7 +719,7 @@
     do
 
         cat >> "$TEMP_DIR/make.mk" << __EOF__
-OBJ_EXT = o
+OBJ_EXT = .o
 LIB_EXT = .a
 PACKAGE_SCRIPT = package_osx.sh
 __EOF__
@@ -739,7 +739,7 @@
     do
 
         cat >> "$TEMP_DIR/make.mk" << __EOF__
-OBJ_EXT = o
+OBJ_EXT = .o
 LIB_EXT = .a
 PACKAGE_SCRIPT = package_unix.sh
 __EOF__
@@ -982,6 +982,11 @@
             break
         fi
         fi
+        if checkopt_toolkit_gtk4 ; then
+            echo "  toolkit: gtk4" >> "$TEMP_DIR/options"
+            ERROR=0
+            break
+        fi
         if checkopt_toolkit_gtk3 ; then
             echo "  toolkit: gtk3" >> "$TEMP_DIR/options"
             ERROR=0
--- a/libidav/Makefile	Mon Oct 21 15:45:12 2024 +0200
+++ b/libidav/Makefile	Wed Oct 23 10:37:43 2024 +0200
@@ -40,6 +40,7 @@
 SRC += xml.c
 SRC += versioning.c
 SRC += config.c
+SRC += pwdstore.c
 
 OBJ = $(SRC:%.c=../build/libidav/%$(OBJ_EXT))
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libidav/pwdstore.c	Wed Oct 23 10:37:43 2024 +0200
@@ -0,0 +1,525 @@
+/*
+ * 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 "pwdstore.h"
+
+#include "utils.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <cx/utils.h>
+#include <cx/hash_map.h>
+
+#ifdef _WIN32
+#include <winsock.h>
+#pragma comment(lib, "Ws2_32.lib")
+#else
+#include <netinet/in.h>
+#endif
+
+
+static pwdstore_pwinput_func pw_input = (pwdstore_pwinput_func)pwdstore_default_pwinput;
+static void *pw_input_data = "Master password: ";
+
+char * pwdstore_default_pwinput(char *prompt) {
+    return util_password_input(prompt);
+}
+
+void pwdstore_set_pwinput_func(pwdstore_pwinput_func func, void *userdata) {
+    pw_input = func;
+    pw_input_data = userdata;
+}
+
+PwdStore* pwdstore_open(const char *file) {
+    FILE *in = fopen(file, "r");
+    if(!in) {
+        return NULL;
+    }
+    
+    CxBuffer *buf = cxBufferCreate(NULL, 2048, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
+    cx_stream_copy(in, buf, (cx_read_func)fread, (cx_write_func)cxBufferWrite);
+    fclose(in);
+    
+    if(buf->size < PWDS_HEADER_SIZE || buf->space[0] != PWDS_MAGIC_CHAR) {
+        cxBufferFree(buf);
+        return NULL;
+    }
+    
+    PwdStore *p = malloc(sizeof(PwdStore));
+    p->ids = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 16);
+    p->locations = cxLinkedListCreateSimple(CX_STORE_POINTERS);
+    p->noloc = cxLinkedListCreateSimple(CX_STORE_POINTERS);
+    p->index = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 16);
+    p->content = buf;
+    p->key = NULL;
+    p->unlock_cmd = NULL;
+    p->lock_cmd = NULL;
+    p->encoffset = PWDS_HEADER_SIZE;
+    p->isdecrypted = 0;
+    
+    if(pwdstore_getindex(p)) {
+        pwdstore_free(p);
+        return NULL;
+    }
+    
+    return p;
+}
+
+PwdStore* pwdstore_new(void) {
+    PwdStore *p = calloc(1, sizeof(PwdStore));
+    p->ids = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 16);
+    p->locations = cxLinkedListCreateSimple(CX_STORE_POINTERS);
+    p->noloc = cxLinkedListCreateSimple(CX_STORE_POINTERS);
+    p->index = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 16);
+    p->content = cxBufferCreate(NULL, PWDS_HEADER_SIZE, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
+    PWDS_MAGIC(p) = PWDS_MAGIC_CHAR;
+    PWDS_VERSION(p) = 1;
+    PWDS_ENC(p) = DAV_KEY_AES256;
+    PWDS_PWFUNC(p) = DAV_PWFUNC_PBKDF2_SHA256;
+    dav_rand_bytes((unsigned char*)p->content->space+4, 16);
+    p->isdecrypted = 1;
+    p->encoffset = PWDS_HEADER_SIZE;
+    return p;
+}
+
+static int readval(CxBuffer *in, char **val, int allowzero) {
+    // value  = length string
+    // length = uint32
+    // string = bytes
+    
+    *val = NULL;
+    
+    // get length
+    uint32_t length = 0;
+    if(cxBufferRead(&length, 1, sizeof(uint32_t), in) != sizeof(uint32_t)) {
+        return 0;
+    }
+    length = ntohl(length); // convert from BE to host byte order
+    if(length == 0) {
+        if(allowzero) {
+            return 1;
+        } else {
+            return 0;
+        }
+    }
+    if(length > PWDSTORE_MAX_LEN) {
+        return 0;
+    }
+    
+    // get value
+    char *value = malloc(length + 1);
+    value[length] = 0;
+    if(cxBufferRead(value, 1, length, in) != length) {
+        free(value);
+        return 0;
+    }
+    
+    *val = value;
+    return 1;
+}
+
+static int read_indexentry(PwdStore *p, CxBuffer *in) {
+    // read type of index element
+    int type = cxBufferGet(in);
+    if(type == EOF || type != 0) {
+        // only type 0 supported yet
+        return 0;
+    }
+      
+    char *id = NULL;
+    CxList *locations = cxLinkedListCreateSimple(CX_STORE_POINTERS);
+    cxDefineDestructor(locations, free);
+    
+    // get id (required)
+    int ret = 0;
+    if(readval(in, &id, FALSE)) {
+        // get locations
+        char *location = NULL;
+        while((ret = readval(in, &location, TRUE)) == 1) {
+            if(!location) {
+                break;
+            }
+            cxListAdd(locations, location);
+        }
+    }
+    
+    if(ret) {
+        pwdstore_put_index(p, id, locations);
+    } else {
+        if(id) free(id);
+        cxListDestroy(locations);
+    }
+    
+    return ret;
+}
+
+static int read_pwdentry(PwdStore *p, CxBuffer *in) {
+    int type = cxBufferGet(in);
+    if(type == EOF || type != 0) {
+        // only type 0 supported yet
+        return 0;
+    }
+    
+    char *id = NULL;
+    char *user = NULL;
+    char *password = NULL;
+    
+    int ret = 0;
+    if(readval(in, &id, FALSE)) {
+        if(readval(in, &user, FALSE)) {
+            if(readval(in, &password, FALSE)) {
+                pwdstore_put(p, id, user, password);
+                ret = 1;
+            }
+        }
+    }
+    
+    if(id) free(id);
+    if(user) free(user);
+    if(password) free(password);
+    
+    return ret;
+}
+
+static void remove_list_entries(PwdStore *s, const char *id) {
+    CxIterator i = cxListMutIterator(s->locations);
+    cx_foreach(PwdIndexEntry*, ie, i) {
+        if(!strcmp(ie->id, id)) {
+            cxIteratorFlagRemoval(i);
+            cxIteratorNext(i);
+            break;
+        }
+    }
+    i = cxListMutIterator(s->noloc);
+    cx_foreach(PwdIndexEntry*, ie, i) {
+        if(!strcmp(ie->id, id)) {
+            cxIteratorFlagRemoval(i);
+            cxIteratorNext(i);
+            break;
+        }
+    }
+}
+
+void pwdstore_remove_entry(PwdStore *s, const char *id) {
+    remove_list_entries(s, id);
+    
+    CxHashKey key = cx_hash_key_str(id);
+    PwdIndexEntry *i = cxMapRemoveAndGet(s->index, key);
+    PwdEntry *e = cxMapRemoveAndGet(s->ids, key);
+    
+    if(i) {
+        cxListDestroy(i->locations);
+        free(i->id);
+        free(i);
+    }
+    if(e) {
+        free(e->id);
+        free(e->user);
+        free(e->password);
+        free(e);
+    }
+}
+
+int pwdstore_getindex(PwdStore *s) {
+    uint32_t netindexlen;
+    
+    // set the position to the last 4 bytes of the header
+    // for reading index length
+    s->content->pos = PWDS_HEADER_SIZE - sizeof(uint32_t);
+    
+    // read indexlen and convert to host byte order
+    if(cxBufferRead(&netindexlen, 1, sizeof(uint32_t), s->content) != sizeof(uint32_t)) {
+        return 1;
+    }
+    uint32_t indexlen = ntohl(netindexlen);
+    
+    // integer overflow check
+    if(UINT32_MAX - PWDS_HEADER_SIZE < indexlen) {
+        return 1;
+    }
+    if(s->content->size < PWDS_HEADER_SIZE + indexlen) {
+        return 1;
+    }
+    // encrypted content starts after the index content
+    s->encoffset = PWDS_HEADER_SIZE + indexlen;
+    
+    // the index starts after the header
+    CxBuffer *index = cxBufferCreate(s->content->space+PWDS_HEADER_SIZE, indexlen, cxDefaultAllocator, 0);
+    index->size = indexlen;
+    
+    // read index
+    while(read_indexentry(s, index)) {}
+    
+    // free index buffer structure (not the content)
+    cxBufferFree(index);
+    
+    return 0;
+}
+
+int pwdstore_decrypt(PwdStore *p) {
+    if(!p->key) {
+        return 1;
+    }
+    if(p->isdecrypted) {
+        return 0;
+    }
+    
+    // decrypt contet
+    size_t encsz = p->content->size - p->encoffset;
+    CxBuffer *enc = cxBufferCreate(p->content->space + p->encoffset, encsz, cxDefaultAllocator, 0);
+    enc->size = encsz;
+    enc->size = p->content->size - p->encoffset;
+    CxBuffer *content = aes_decrypt_buffer(enc, p->key);
+    cxBufferFree(enc);
+    if(!content) {
+        return 1;
+    }
+    
+    while(read_pwdentry(p, content)) {}
+    
+    cxBufferFree(content);
+    
+    return 0;
+}
+
+int pwdstore_setpassword(PwdStore *p, const char *password) {
+    DavKey *key = dav_pw2key(
+            password,
+            (unsigned char*)(p->content->space + 4),
+            16,
+            PWDS_PWFUNC(p),
+            PWDS_ENC(p));
+    if(!key) {
+        return 1;
+    }
+    
+    p->key = key;
+    return 0;
+}
+
+void pwdstore_encsettings(PwdStore *p, uint8_t enc, uint8_t pwfunc) {
+    PWDS_ENC(p) = enc;
+    PWDS_PWFUNC(p) = pwfunc;
+}
+
+void pwdstore_free_entry(PwdEntry *e) {
+    if(e->id) free(e->id);
+    if(e->user) free(e->user);
+    if(e->password) free(e->password);
+    free(e);
+}
+
+void pwdstore_free(PwdStore* p) {
+    cxDefineDestructor(p->ids, pwdstore_free_entry);
+    cxMapDestroy(p->ids);
+    
+    cxListDestroy(p->locations);
+    
+    if(p->content) {
+        cxBufferFree(p->content);
+    }
+    
+    free(p);
+}
+
+int pwdstore_has_id(PwdStore *s, const char *id) {
+    return cxMapGet(s->index, cx_hash_key_str(id)) ? 1 : 0;
+}
+
+PwdEntry* pwdstore_get(PwdStore *p, const char *id) {
+    PwdEntry *e = cxMapGet(p->ids, cx_hash_key_str(id));
+    if(e && e->user && e->password) {
+        return e;
+    } else {
+        return NULL;
+    }
+}
+
+void pwdstore_put(PwdStore *p, const char *id, const char *username, const char *password) {
+    PwdEntry *entry = malloc(sizeof(PwdEntry));
+    entry->id = strdup(id);
+    entry->user = strdup(username);
+    entry->password = strdup(password);
+    cxMapPut(p->ids, cx_hash_key_str(id), entry);
+}
+
+void pwdstore_put_index(PwdStore *p, char *id, CxList *locations) {
+    PwdIndexEntry *e = cxMapGet(p->index, cx_hash_key_str(id));
+    if(e) {
+        return;
+    }
+    PwdIndexEntry *newentry = malloc(sizeof(PwdIndexEntry));
+    newentry->id = id;
+    if(locations) {
+        newentry->locations = locations;
+        cxListAdd(p->locations, newentry);
+    } else {
+        newentry->locations = NULL;
+        cxListAdd(p->noloc, newentry);
+    }
+    cxMapPut(p->index, cx_hash_key_str(id), newentry);
+}
+
+void write_index_entry(CxBuffer *out, PwdIndexEntry *e) {
+    uint32_t idlen = strlen(e->id);
+    uint32_t netidlen = htonl(idlen);
+    
+    cxBufferPut(out, 0); // type
+
+    cxBufferWrite(&netidlen, 1, sizeof(uint32_t), out);
+    cxBufferWrite(e->id, 1, idlen, out);
+    
+    CxIterator i = cxListIterator(e->locations);
+    cx_foreach(char *, location, i) {
+        uint32_t locationlen = strlen(location);
+        uint32_t netlocationlen = htonl(locationlen);
+        
+        cxBufferWrite(&netlocationlen, 1, sizeof(uint32_t), out);
+        cxBufferWrite(location, 1, locationlen, out);
+    }
+    
+    uint32_t terminate = 0;
+    cxBufferWrite(&terminate, 1, sizeof(uint32_t), out);
+}
+
+int pwdstore_store(PwdStore *p, const char *file) {
+    if(!p->key) {
+        return 1;
+    }
+    
+    CxBuffer *index = cxBufferCreate(NULL, 2048, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
+    CxBuffer *content = cxBufferCreate(NULL, 2048, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
+    
+    // create index
+    CxIterator i = cxListIterator(p->noloc);
+    cx_foreach(PwdIndexEntry*, e, i) {
+        write_index_entry(index, e);
+    }
+    i = cxListIterator(p->locations);
+    cx_foreach(PwdIndexEntry*, e, i) {
+        write_index_entry(index, e);
+    }
+    
+    i = cxMapIteratorValues(p->ids);
+    cx_foreach(PwdEntry*, value, i) {
+        if(!value->id || !value->user || !value->password) {
+            continue;
+        }
+        
+        uint32_t idlen = strlen(value->id);
+        uint32_t ulen = strlen(value->user);
+        uint32_t plen = strlen(value->password);
+        uint32_t netidlen = htonl(idlen);
+        uint32_t netulen = htonl(ulen);
+        uint32_t netplen = htonl(plen);
+        
+        // content buffer
+        cxBufferPut(content, 0); // type
+        
+        cxBufferWrite(&netidlen, 1, sizeof(uint32_t), content);
+        cxBufferWrite(value->id, 1, idlen, content);
+        cxBufferWrite(&netulen, 1, sizeof(uint32_t), content);
+        cxBufferWrite(value->user, 1, ulen, content);
+        cxBufferWrite(&netplen, 1, sizeof(uint32_t), content);
+        cxBufferWrite(value->password, 1, plen, content);
+    }
+      
+    content->pos = 0;
+    CxBuffer *enc = aes_encrypt_buffer(content, p->key);
+
+    p->content->pos = PWDS_HEADER_SIZE - sizeof(uint32_t);
+    p->content->size = PWDS_HEADER_SIZE;
+    
+    // add index after header
+    uint32_t netindexlen = htonl((uint32_t)index->size);
+    cxBufferWrite(&netindexlen, 1, sizeof(uint32_t), p->content);
+    cxBufferWrite(index->space, 1, index->size, p->content);
+    
+    // add encrypted buffer
+    cxBufferWrite(enc->space, 1, enc->size, p->content);
+    
+    cxBufferFree(enc);
+    
+    FILE *out = fopen(file, "w");
+    if(!out) {
+        return 1;
+    }
+    fwrite(p->content->space, 1, p->content->size, out);
+    fclose(out);
+    
+    return 0;
+}
+
+int pwdstore_decrypt_secrets(PwdStore *secrets) {
+    if(!pw_input) {
+        return 1;
+    }
+    
+    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);
+    }
+    
+    if(!ps_password) {
+        ps_password = pw_input(pw_input_data);
+        if(!ps_password) {
+            return 1;
+        }
+    }
+    
+    int err = pwdstore_setpassword(secrets, ps_password);
+    free(ps_password);
+    if(err) {
+        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;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libidav/pwdstore.h	Wed Oct 23 10:37:43 2024 +0200
@@ -0,0 +1,217 @@
+/*
+ * 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.
+ */
+
+#ifndef LIBIDAV_PWDSTORE_H
+#define LIBIDAV_PWDSTORE_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdlib.h>
+#include <inttypes.h>
+
+#include <cx/map.h>
+#include <cx/buffer.h>
+#include <cx/linked_list.h>
+#include "crypto.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define PWDSTORE_MAX_LEN 4096
+    
+/*
+ * File Format:
+ * 
+ * file = header, index, enc_content
+ * header = magic, version, enc, pwfunc, salt, indexlen
+ * magic = 1 byte
+ * version = 1 byte
+ * enc = 1 byte
+ * pwfunc = 1 byte
+ * salt = 16 bytes
+ * indexlen = uint32
+ * index = { itype length id locations zero }
+ * enc_content = iv bytes
+ * iv = 16 bytes
+ * content = { entry }
+ * entry = itype length id length username length password
+ * length = uint32
+ * zero = 4 zero bytes
+ * itype = 1 byte
+ * id = string
+ * locations = { length string }
+ * username = string
+ * password = string
+ * 
+ * The content is AES encrypted with a key derived from a password
+ * and the salt. The first 16 bytes are the aes iv.
+ * 
+ * All integers are big endian
+ */
+    
+#define PWDS_HEADER_SIZE 24
+    
+typedef struct PwdStore        PwdStore;
+typedef struct PwdEntry        PwdEntry;
+typedef struct PwdIndexEntry   PwdIndexEntry;
+
+struct PwdStore {
+    /*
+     * map of all credentials
+     * key is the username
+     * value is PwdEntry*
+     */
+    CxMap *ids;
+    
+    /*
+     * list of all credentials with location
+     * value is PwdIndexEntry*
+     */
+    CxList *locations;
+    
+    /*
+     * list of all credentials without location
+     * value is PwdIndexEntry*
+     */
+    CxList *noloc;
+    
+    /*
+     * index map that contains all elements from the lists
+     * 'locations' and 'noloc'
+     */
+    CxMap *index;
+    
+    /*
+     * a buffer containing the complete file content
+     */
+    CxBuffer *content;
+    
+    /*
+     * key used for encryption/decryption
+     */
+    DavKey *key;
+    
+    /*
+     * optional shell command, that is used for getting the master password
+     */
+    char *unlock_cmd;
+    
+    /*
+     * optional shell command, that is exected when the secretstore is closed
+     */
+    char *lock_cmd;
+    
+    /*
+     * start offset of the encrypted buffer
+     */
+    uint32_t encoffset;
+    
+    /*
+     * indicates if the PwdStore is decrypted with pwdstore_decrypt
+     */
+    uint8_t isdecrypted;
+};
+
+#define PWDS_MAGIC(p) (p)->content->space[0]
+#define PWDS_VERSION(p) (p)->content->space[1]
+#define PWDS_ENC(p) (p)->content->space[2]
+#define PWDS_PWFUNC(p) (p)->content->space[3]
+
+#define PWDS_MAGIC_CHAR 'P'
+
+struct PwdEntry {
+    char *id;
+    char *user;
+    char *password;
+};
+
+struct PwdIndexEntry {
+    char *id;
+    CxList *locations;
+};
+
+/*
+ * opens the password store
+ * the content is still encrypted and must be decrypted using pwdstore_decrypt
+ */
+PwdStore* pwdstore_open(const char *file);
+
+PwdStore* pwdstore_new(void);
+
+/*
+ * decrypts the password store with the previously set password
+ */
+int pwdstore_decrypt(PwdStore *p);
+
+int pwdstore_setpassword(PwdStore *p, const char *password);
+
+void pwdstore_encsettings(PwdStore *p, uint8_t enc, uint8_t pwfunc);
+
+void pwdstore_free_entry(PwdEntry *e);
+void pwdstore_free(PwdStore* p);
+
+int pwdstore_has_id(PwdStore *s, const char *id);
+int pwdstore_has_location(PwdStore *s, const char *location);
+
+PwdEntry* pwdstore_get(PwdStore *p, const char *id);
+
+void pwdstore_put(PwdStore *p, const char *id, const char *username, const char *password);
+void pwdstore_put_index(PwdStore *p, char *id, CxList *locations);
+
+void pwdstore_remove_entry(PwdStore *s, const char *id);
+
+int pwdstore_store(PwdStore *p, const char *file);
+
+
+int pwdstore_decrypt_secrets(PwdStore *secrets);
+
+
+
+
+
+
+typedef char*(*pwdstore_pwinput_func)(void *userdata);
+
+void pwdstore_set_pwinput_func(pwdstore_pwinput_func func, void *userdata);
+
+char * pwdstore_default_pwinput(char *prompt);
+
+
+
+/* private */
+int pwdstore_getindex(PwdStore *s);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* LIBIDAV_PWDSTORE_H */
+
--- a/libidav/session.c	Mon Oct 21 15:45:12 2024 +0200
+++ b/libidav/session.c	Wed Oct 23 10:37:43 2024 +0200
@@ -119,7 +119,7 @@
     DavSession *newsn = malloc(sizeof(DavSession));
     memset(newsn, 0, sizeof(DavSession));
     newsn->mp = cxBasicMempoolCreate(DAV_SESSION_MEMPOOL_SIZE);
-    newsn->pathcache = cxHashMapCreate(sn->mp->allocator, CX_STORE_POINTERS, DAV_PATH_CACHE_SIZE);
+    newsn->pathcache = cxHashMapCreate(newsn->mp->allocator, CX_STORE_POINTERS, DAV_PATH_CACHE_SIZE);
     newsn->key = sn->key;
     newsn->errorstr = NULL;
     newsn->error = DAV_OK;
--- a/make/project.xml	Mon Oct 21 15:45:12 2024 +0200
+++ b/make/project.xml	Wed Oct 23 10:37:43 2024 +0200
@@ -106,12 +106,12 @@
 	</dependency>
 	
 	<dependency platform="macos">
-		<make>OBJ_EXT = o</make>
+		<make>OBJ_EXT = .o</make>
 		<make>LIB_EXT = .a</make>
 		<make>PACKAGE_SCRIPT = package_osx.sh</make>
 	</dependency>
 	<dependency platform="unix" not="macos">
-		<make>OBJ_EXT = o</make>
+		<make>OBJ_EXT = .o</make>
 		<make>LIB_EXT = .a</make>
 		<make>PACKAGE_SCRIPT = package_unix.sh</make>
 	</dependency>
@@ -168,6 +168,7 @@
 			</value>
 			<default value="winui" platform="windows" />
 			<default value="cocoa" platform="macos" />
+			<default value="gtk4" />
 			<default value="gtk3" />
 			<default value="qt5" />
 			<default value="gtk2" />

mercurial