--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/application/config.c Mon Jan 29 10:41:00 2024 +0100 @@ -0,0 +1,328 @@ +/* + * 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: { + 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; + } + + 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; +}