# HG changeset patch # User Olaf Wintermann # Date 1729672663 -7200 # Node ID 1ce14068ef31cfc72366e45b073f950fc1b506c3 # Parent 3ca3acefc66aa7d38c307e34953f0f217c547e24 update libidav, unify config.c with cmdutils diff -r 3ca3acefc66a -r 1ce14068ef31 application/Makefile --- 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 diff -r 3ca3acefc66a -r 1ce14068ef31 application/config.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 -#include -#include -#include -#include -#include -#include -#include - -#include "pwd.h" -#include "config.h" -#include "system.h" - -#include -#include - -#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 +#include +#include +#include +#include +#include +#include +#include + +#include "config.h" +#include "pwd.h" +#include "system.h" + +#include +#include + +#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; +} diff -r 3ca3acefc66a -r 1ce14068ef31 application/config.h --- 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 -#include -#include -#include "pwd.h" - -#include - -#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 +#include +#include + +#include +#include + +#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 */ + diff -r 3ca3acefc66a -r 1ce14068ef31 application/connect.c --- /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 +#include +#include + +#include "connect.h" + +#include + +#include +#include + +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; +} diff -r 3ca3acefc66a -r 1ce14068ef31 application/connect.h --- /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 */ + diff -r 3ca3acefc66a -r 1ce14068ef31 application/pwd.c --- 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 -#include -#include - -#include "pwd.h" - -#include -#include - -#ifdef _WIN32 -#include -#pragma comment(lib, "Ws2_32.lib") -#else -#include -#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; -} diff -r 3ca3acefc66a -r 1ce14068ef31 application/pwd.h --- 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 -#include - -#include -#include -#include -#include - -#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 */ - diff -r 3ca3acefc66a -r 1ce14068ef31 configure --- 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 diff -r 3ca3acefc66a -r 1ce14068ef31 libidav/Makefile --- 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)) diff -r 3ca3acefc66a -r 1ce14068ef31 libidav/pwdstore.c --- /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 +#include +#include + +#include +#include + +#ifdef _WIN32 +#include +#pragma comment(lib, "Ws2_32.lib") +#else +#include +#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; +} diff -r 3ca3acefc66a -r 1ce14068ef31 libidav/pwdstore.h --- /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 +#include + +#include +#include +#include +#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 */ + diff -r 3ca3acefc66a -r 1ce14068ef31 libidav/session.c --- 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; diff -r 3ca3acefc66a -r 1ce14068ef31 make/project.xml --- 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 @@ - OBJ_EXT = o + OBJ_EXT = .o LIB_EXT = .a PACKAGE_SCRIPT = package_osx.sh - OBJ_EXT = o + OBJ_EXT = .o LIB_EXT = .a PACKAGE_SCRIPT = package_unix.sh @@ -168,6 +168,7 @@ +