Thu, 03 Oct 2024 18:08:29 +0200
implement action_newfile
/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright 2023 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 "config.h" #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <cx/hash_map.h> #include <errno.h> #include <libxml/tree.h> #include "utils.h" #define xstreq(a,b) xmlStrEqual(BAD_CAST a, BAD_CAST b) #define xstrEQ(a,b) !xmlStrcasecmp(BAD_CAST a, BAD_CAST b) #define print_error(lineno, ...) \ do {\ fprintf(stderr, "Error (config.xml line %u): ", lineno); \ fprintf(stderr, __VA_ARGS__); \ fprintf(stderr, "Abort.\n"); \ } while(0); #define print_warning(lineno, ...) \ do {\ fprintf(stderr, "Warning (config.xml line %u): ", lineno); \ fprintf(stderr, __VA_ARGS__); \ } while(0); #ifdef _WIN32 #define ENV_HOME getenv("USERPROFILE") #else #define ENV_HOME getenv("HOME") #endif /* _WIN32 */ static int load_repository( DavConfig *config, DavCfgRepository **list_begin, DavCfgRepository **list_end, xmlNode *reponode); static int load_key( DavConfig *config, DavCfgKey **list_begin, DavCfgKey **list_end, xmlNode *keynode); static int load_proxy( DavConfig *config, DavCfgProxy *proxy, xmlNode *proxynode, int type); static int load_namespace( DavConfig *config, DavCfgNamespace **list_begin, DavCfgNamespace **list_end, xmlNode *node); static int load_secretstore(DavConfig *config, xmlNode *node); int dav_cfg_string_set_value(DavConfig *config, CfgString *str, xmlNode *node) { str->node = node; char *value = util_xml_get_text(node); if(value) { str->value = cx_strdup_a(config->mp->allocator, cx_str(value)); return 0; } else { str->value = (cxmutstr){NULL, 0}; return 1; } } void dav_cfg_bool_set_value(DavConfig *config, CfgBool *cbool, xmlNode *node) { cbool->node = node; char *value = util_xml_get_text(node); cbool->value = util_getboolean(value); } DavConfig* dav_config_load(cxmutstr xmlfilecontent, int *error) { xmlDoc *doc = xmlReadMemory(xmlfilecontent.ptr, xmlfilecontent.length, NULL, NULL, 0); if(!doc) { if(error) { *error = DAV_CONFIG_ERROR_XML; } return NULL; } CxMempool *cfg_mp = cxMempoolCreate(128, NULL); cxMempoolRegister(cfg_mp, doc, (cx_destructor_func)xmlFreeDoc); DavConfig *config = cxMalloc(cfg_mp->allocator, sizeof(DavConfig)); memset(config, 0, sizeof(DavConfig)); config->mp = cfg_mp; config->doc = doc; DavCfgRepository *repos_begin = NULL; DavCfgRepository *repos_end = NULL; DavCfgKey *keys_begin = NULL; DavCfgKey *keys_end = NULL; DavCfgNamespace *namespaces_begin = NULL; DavCfgNamespace *namespaces_end = NULL; xmlNode *xml_root = xmlDocGetRootElement(doc); xmlNode *node = xml_root->children; int ret = 0; while(node && !ret) { if(node->type == XML_ELEMENT_NODE) { if(xstreq(node->name, "repository")) { ret = load_repository(config, &repos_begin, &repos_end, node); } else if(xstreq(node->name, "key")) { ret = load_key(config, &keys_begin, &keys_end, node); } else if (xstreq(node->name, "http-proxy")) { config->http_proxy = cxCalloc(config->mp->allocator, 1, sizeof(DavCfgProxy)); ret = load_proxy(config, config->http_proxy, node, DAV_HTTP_PROXY); } else if (xstreq(node->name, "https-proxy")) { config->https_proxy = cxCalloc(config->mp->allocator, 1, sizeof(DavCfgProxy)); ret = load_proxy(config, config->https_proxy, node, DAV_HTTPS_PROXY); } else if (xstreq(node->name, "namespace")) { ret = load_namespace(config, &namespaces_begin, &namespaces_end, node); } else if (xstreq(node->name, "secretstore")) { ret = load_secretstore(config, node); } else { fprintf(stderr, "Unknown config element: %s\n", node->name); ret = 1; } } node = node->next; } config->repositories = repos_begin; config->keys = keys_begin; config->namespaces = namespaces_begin; if(ret != 0 && error) { *error = ret; cxMempoolDestroy(cfg_mp); } return config; } void dav_config_free(DavConfig *config) { cxMempoolDestroy(config->mp); } CxBuffer* dav_config2buf(DavConfig *config) { xmlChar* xmlText = NULL; int textLen = 0; xmlDocDumpFormatMemory(config->doc, &xmlText, &textLen, 1); if(!xmlText) { return NULL; } CxBuffer *buf = cxBufferCreate(NULL, textLen, cxDefaultAllocator, CX_BUFFER_AUTO_EXTEND|CX_BUFFER_FREE_CONTENTS); cxBufferWrite(xmlText, 1, textLen, buf); xmlFree(xmlText); return buf; } static int repo_add_config( DavConfig *config, DavCfgRepository *repo, xmlNode* node) { unsigned short lineno = node->line; char *key = (char*)node->name; char *value = util_xml_get_text(node); /* every key needs a value */ if(!value) { /* TODO: maybe this should only be reported, if the key is valid * But this makes the code very ugly. */ print_error(lineno, "missing value for config element: %s\n", key); return 1; } if(xstreq(key, "name")) { dav_cfg_string_set_value(config, &repo->name, node); } else if(xstreq(key, "url")) { dav_cfg_string_set_value(config, &repo->url, node); } else if(xstreq(key, "user")) { dav_cfg_string_set_value(config, &repo->user, node); } else if(xstreq(key, "password")) { dav_cfg_string_set_value(config, &repo->password, node); } else if(xstreq(key, "stored-user")) { dav_cfg_string_set_value(config, &repo->stored_user, node); } else if(xstreq(key, "default-key")) { dav_cfg_string_set_value(config, &repo->default_key, node); } else if(xstreq(key, "full-encryption")) { dav_cfg_bool_set_value(config, &repo->full_encryption, node); } else if(xstreq(key, "content-encryption")) { dav_cfg_bool_set_value(config, &repo->content_encryption, node); } else if(xstreq(key, "decrypt-content")) { dav_cfg_bool_set_value(config, &repo->decrypt_content, node); } else if(xstreq(key, "decrypt-name")) { dav_cfg_bool_set_value(config, &repo->decrypt_name, node); } else if(xstreq(key, "cert")) { dav_cfg_string_set_value(config, &repo->cert, node); } else if(xstreq(key, "verification")) { dav_cfg_bool_set_value(config, &repo->verification, node); } else if(xstreq(key, "ssl-version")) { repo->ssl_version.node = node; if(xstrEQ(value, "TLSv1")) { repo->ssl_version.value = CURL_SSLVERSION_TLSv1; } else if(xstrEQ(value, "SSLv2")) { repo->ssl_version.value = CURL_SSLVERSION_SSLv2; } else if(xstrEQ(value, "SSLv3")) { repo->ssl_version.value = CURL_SSLVERSION_SSLv3; } #if LIBCURL_VERSION_MAJOR * 1000 + LIBCURL_VERSION_MINOR >= 7034 else if(xstrEQ(value, "TLSv1.0")) { repo->ssl_version.value = CURL_SSLVERSION_TLSv1_0; } else if(xstrEQ(value, "TLSv1.1")) { repo->ssl_version.value = CURL_SSLVERSION_TLSv1_1; } else if(xstrEQ(value, "TLSv1.2")) { repo->ssl_version.value = CURL_SSLVERSION_TLSv1_2; } #endif #if LIBCURL_VERSION_MAJOR * 1000 + LIBCURL_VERSION_MINOR >= 7052 else if(xstrEQ(value, "TLSv1.3")) { repo->ssl_version.value = CURL_SSLVERSION_TLSv1_3; } #endif else { print_warning(lineno, "unknown ssl version: %s\n", value); repo->ssl_version.value = CURL_SSLVERSION_DEFAULT; } } else if(xstreq(key, "authmethods")) { repo->authmethods.node = node; repo->authmethods.value = CURLAUTH_NONE; const char *delims = " \t\r\n"; char *meths = strdup(value); char *meth = strtok(meths, delims); while (meth) { if(xstrEQ(meth, "basic")) { repo->authmethods.value |= CURLAUTH_BASIC; } else if(xstrEQ(meth, "digest")) { repo->authmethods.value |= CURLAUTH_DIGEST; } else if(xstrEQ(meth, "negotiate")) { repo->authmethods.value |= CURLAUTH_GSSNEGOTIATE; } else if(xstrEQ(meth, "ntlm")) { repo->authmethods.value |= CURLAUTH_NTLM; } else if(xstrEQ(meth, "any")) { repo->authmethods.value = CURLAUTH_ANY; } else if(xstrEQ(meth, "none")) { /* skip */ } else { print_warning(lineno, "unknown authentication method: %s\n", meth); } meth = strtok(NULL, delims); } free(meths); } else { print_error(lineno, "unkown repository config element: %s\n", key); return 1; } return 0; } static int load_repository( DavConfig *config, DavCfgRepository **list_begin, DavCfgRepository **list_end, xmlNode *reponode) { DavCfgRepository *repo = dav_repository_new(config); repo->node = reponode; // add repo config from child nodes xmlNode *node = reponode->children; int ret = 0; while(node && !ret) { if(node->type == XML_ELEMENT_NODE) { ret = repo_add_config(config, repo, node); } node = node->next; } // success: add repo to the configuration, error: free repo if(ret) { return 1; } else { cx_linked_list_add( (void**)list_begin, (void**)list_end, offsetof(DavCfgRepository, prev), offsetof(DavCfgRepository, next), repo); } return 0; } static xmlNode* addXmlNode(xmlNode *node, const char *name, cxmutstr content) { xmlNode *text1 = xmlNewDocText(node->doc, BAD_CAST "\t\t"); xmlAddChild(node, text1); cxmutstr ctn = cx_strdup(cx_strcast(content)); xmlNode *newNode = xmlNewChild(node, NULL, BAD_CAST name, BAD_CAST ctn.ptr); free(ctn.ptr); xmlNode *text2 = xmlNewDocText(node->doc, BAD_CAST "\n"); xmlAddChild(node, text2); return newNode; } void dav_config_add_repository(DavConfig *config, DavCfgRepository *repo) { if(repo->node) { fprintf(stderr, "Error: dav_config_add_repository: node already exists\n"); return; } xmlNode *repoNode = xmlNewNode(NULL, BAD_CAST "repository"); xmlNode *rtext1 = xmlNewDocText(config->doc, BAD_CAST "\n"); xmlAddChild(repoNode, rtext1); if(repo->name.value.ptr) { repo->name.node = addXmlNode(repoNode, "name", repo->name.value); } if(repo->url.value.ptr) { repo->url.node = addXmlNode(repoNode, "url", repo->url.value); } if(repo->user.value.ptr) { repo->user.node = addXmlNode(repoNode, "user", repo->user.value); } if(repo->password.value.ptr) { repo->password.node = addXmlNode(repoNode, "password", repo->password.value); } if(repo->stored_user.value.ptr) { repo->stored_user.node = addXmlNode(repoNode, "stored-user", repo->stored_user.value); } if(repo->default_key.value.ptr) { repo->default_key.node = addXmlNode(repoNode, "default-key", repo->default_key.value); } if(repo->cert.value.ptr) { repo->cert.node = addXmlNode(repoNode, "cert", repo->cert.value); } // TODO: implement booleans // indent closing tag xmlNode *rtext2 = xmlNewDocText(config->doc, BAD_CAST "\t"); xmlAddChild(repoNode, rtext2); // add repository to internal list DavCfgRepository **list_begin = &config->repositories; cx_linked_list_add( (void**)list_begin, NULL, offsetof(DavCfgRepository, prev), offsetof(DavCfgRepository, next), repo); // add repository element to the xml document xmlNode *xml_root = xmlDocGetRootElement(config->doc); xmlNode *text1 = xmlNewDocText(config->doc, BAD_CAST "\n\t"); xmlAddChild(xml_root, text1); xmlAddChild(xml_root, repoNode); xmlNode *text2 = xmlNewDocText(config->doc, BAD_CAST "\n"); xmlAddChild(xml_root, text2); } DavCfgRepository* dav_repository_new(DavConfig *config) { DavCfgRepository *repo = cxMalloc(config->mp->allocator, sizeof(DavCfgRepository)); memset(repo, 0, sizeof(DavCfgRepository)); repo->decrypt_name.value = false; repo->decrypt_content.value = true; repo->decrypt_properties.value = false; repo->verification.value = true; repo->ssl_version.value = CURL_SSLVERSION_DEFAULT; repo->authmethods.value = CURLAUTH_BASIC; return repo; } void dav_repository_free(DavConfig *config, DavCfgRepository *repo) { // TODO } void dav_repository_remove_and_free(DavConfig *config, DavCfgRepository *repo) { if(repo->prev) { repo->prev->next = repo->next; } if(repo->next) { repo->next->prev = repo->prev; } if(repo->node) { // TODO: remove newline after repo node xmlUnlinkNode(repo->node); xmlFreeNode(repo->node); } } int dav_repository_get_flags(DavCfgRepository *repo) { int flags = 0; DavBool encrypt_content = FALSE; DavBool encrypt_name = FALSE; DavBool encrypt_properties = FALSE; DavBool decrypt_content = FALSE; DavBool decrypt_name = FALSE; DavBool decrypt_properties = FALSE; if(repo->full_encryption.value) { encrypt_content = TRUE; encrypt_name = TRUE; encrypt_properties = TRUE; decrypt_content = TRUE; decrypt_name = TRUE; decrypt_properties = TRUE; } else if(repo->content_encryption.value) { encrypt_content = TRUE; decrypt_content = TRUE; } if(decrypt_content) { flags |= DAV_SESSION_DECRYPT_CONTENT; } if(decrypt_name) { flags |= DAV_SESSION_DECRYPT_NAME; } if(decrypt_properties) { flags |= DAV_SESSION_DECRYPT_PROPERTIES; } if(encrypt_content) { flags |= DAV_SESSION_ENCRYPT_CONTENT; } if(encrypt_name) { flags |= DAV_SESSION_ENCRYPT_NAME; } if(encrypt_properties) { flags |= DAV_SESSION_ENCRYPT_PROPERTIES; } return flags; } void dav_repository_set_url(DavConfig *config, DavCfgRepository *repo, cxstring newurl) { if(repo->url.value.ptr) { cxFree(config->mp->allocator, repo->url.value.ptr); } repo->url.value = cx_strdup_a(config->mp->allocator, newurl); } void dav_repository_set_auth(DavConfig *config, DavCfgRepository *repo, cxstring user, cxstring password) { const CxAllocator *a = config->mp->allocator; repo->user.value = cx_strdup_a(a, user); char *pwenc = util_base64encode(password.ptr, password.length); repo->password.value = cx_strdup_a(a, cx_str(pwenc)); free(pwenc); } cxmutstr dav_repository_get_decodedpassword(DavCfgRepository *repo) { cxmutstr pw = { NULL, 0 }; if(repo->password.value.ptr) { pw = cx_mutstr(util_base64decode(repo->password.value.ptr)); } return pw; } static int load_key( DavConfig *config, DavCfgKey **list_begin, DavCfgKey **list_end, xmlNode *keynode) { xmlNode *node = keynode->children; DavCfgKey *key = cxMalloc(config->mp->allocator, sizeof(DavCfgKey)); memset(key, 0, sizeof(DavCfgKey)); key->type = DAV_KEY_TYPE_AES256; int error = 0; while(node) { if(node->type == XML_ELEMENT_NODE) { if(xstreq(node->name, "name")) { dav_cfg_string_set_value(config, &key->name, node); } else if(xstreq(node->name, "file")) { dav_cfg_string_set_value(config, &key->file, node); } else if(xstreq(node->name, "type")) { const char *value = util_xml_get_text(node); key->type_node = node; if(!strcmp(value, "aes128")) { key->type = DAV_KEY_TYPE_AES128; } else if(!strcmp(value, "aes256")) { key->type = DAV_KEY_TYPE_AES256; } else { print_error(node->line, "unknown key type %s\n", value); error = 1; } } else { key->unknown_elements++; } } node = node->next; } if(!key->name.value.ptr) { error = 1; } if(!error) { error = 0; size_t expected_length = 0; if(key->type == DAV_KEY_TYPE_AES128) { expected_length = 16; } if(key->type == DAV_KEY_TYPE_AES256) { expected_length = 32; } /* if(key->length < expected_length) { print_error(keynode->line, "key %s is too small (%zu < %zu)\n", key->name, key->length, expected_length); error = 1; } // add key to context if(!error) { cxMapPut(keys, cx_hash_key_str(key->name), key); dav_context_add_key(context, key); } */ } // cleanup if(error) { return 1; } else { // add key to the configuration cx_linked_list_add( (void**)list_begin, (void**)list_end, offsetof(DavCfgKey, prev), offsetof(DavCfgKey, next), key); return 0; } } static int load_proxy( DavConfig *config, DavCfgProxy *proxy, xmlNode *proxynode, int type) { const char *stype; if(type == DAV_HTTPS_PROXY) { stype = "https"; } else if(type == DAV_HTTP_PROXY) { stype = "http"; } else { fprintf(stderr, "unknown proxy type\n"); return 1; } if(!proxy) { // no xml error - so report this directly via fprintf fprintf(stderr, "no memory reserved for %s proxy.\n", stype); return 1; } xmlNode *node = proxynode->children; int ret = 0; while(node && !ret) { if(node->type == XML_ELEMENT_NODE) { int reportmissingvalue = 0; if(xstreq(node->name, "url")) { reportmissingvalue = dav_cfg_string_set_value(config, &proxy->url, node); } else if(xstreq(node->name, "user")) { reportmissingvalue = dav_cfg_string_set_value(config, &proxy->user, node); } else if(xstreq(node->name, "password")) { reportmissingvalue = dav_cfg_string_set_value(config, &proxy->password, node); } else if(xstreq(node->name, "no")) { reportmissingvalue = dav_cfg_string_set_value(config, &proxy->noproxy, node); } else { proxy->unknown_elements++; } if (reportmissingvalue) { print_error(node->line, "missing value for proxy configuration element: %s\n", node->name); ret = 1; break; } } node = node->next; } if(!ret && !proxy->url.value.ptr) { print_error(proxynode->line, "missing url for %s proxy.\n", stype); return 1; } return ret; } 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; } static int load_namespace( DavConfig *config, DavCfgNamespace **list_begin, DavCfgNamespace **list_end, 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; } DavCfgNamespace *ns = cxMalloc(config->mp->allocator, sizeof(DavCfgNamespace)); memset(ns, 0, sizeof(DavCfgNamespace)); ns->node = node; ns->prefix = cx_strdup_a(config->mp->allocator, cx_str(prefix)); ns->uri = cx_strdup_a(config->mp->allocator, cx_str(uri)); cx_linked_list_add( (void**)list_begin, (void**)list_end, offsetof(DavCfgNamespace, prev), offsetof(DavCfgNamespace, next), ns); return 0; } static int load_secretstore(DavConfig *config, xmlNode *node) { // currently only one secretstore is supported if(config->secretstore) { return 1; } config->secretstore = cxCalloc(config->mp->allocator, 1, sizeof(DavCfgSecretStore)); node = node->children; int error = 0; while(node) { if(node->type == XML_ELEMENT_NODE) { if(xstreq(node->name, "unlock-command")) { dav_cfg_string_set_value(config, &config->secretstore->unlock_cmd, node); } else if(xstreq(node->name, "lock-command")) { dav_cfg_string_set_value(config, &config->secretstore->lock_cmd, node); } } node = node->next; } return error; } DavCfgRepository* dav_config_get_repository(DavConfig *config, cxstring name) { DavCfgRepository *repo = config->repositories; while(repo) { if(!cx_strcmp(cx_strcast(repo->name.value), name)) { return repo; } repo = repo->next; } return NULL; } DavCfgRepository* dav_config_url2repo(DavConfig *config, const char *url, char **path) { cxmutstr p; DavCfgRepository *repo = dav_config_url2repo_s(config, cx_str(url), &p); *path = p.ptr; return repo; } DavCfgRepository* dav_config_url2repo_s(DavConfig *config, cxstring url, cxmutstr *path) { path->ptr = NULL; path->length = 0; int s; if(cx_strprefix(url, CX_STR("http://"))) { s = 7; } else if(cx_strprefix(url, CX_STR("https://"))) { s = 8; } else { s = 1; } // split URL into repository and path cxstring r = cx_strsubs(url, s); cxstring p = cx_strchr(r, '/'); r = cx_strsubsl(url, 0, url.length-p.length); if(p.length == 0) { p = cx_strn("/", 1); } DavCfgRepository *repo = dav_config_get_repository(config, r); if(repo) { *path = cx_strdup(p); } else { // TODO: who is responsible for freeing this repository? // how can the callee know, if he has to call free()? repo = dav_repository_new(config); repo->name.value = cx_strdup_a(config->mp->allocator, CX_STR("")); if(url.ptr[url.length-1] == '/') { repo->url.value = cx_strdup_a(config->mp->allocator, url); *path = cx_strdup(CX_STR("/")); } else if (cx_strchr(url, '/').length > 0) { // TODO: fix the following workaround after // fixing the inconsistent behavior of util_url_*() cxstring repo_url = util_url_base_s(url); repo->url.value = cx_strdup_a(config->mp->allocator, repo_url); *path = cx_strdup(util_url_path_s(url)); } else { repo->url.value = cx_strdup(url); *path = cx_strdup(CX_STR("/")); } } return repo; } int dav_config_keytype(DavCfgKeyType type) { switch(type) { default: break; case DAV_KEY_TYPE_AES256: return DAV_KEY_AES256; case DAV_KEY_TYPE_AES128: return DAV_KEY_AES128; } return 0; } int dav_config_register_keys(DavConfig *config, DavContext *ctx, dav_loadkeyfile_func loadkey) { for(DavCfgKey *key=config->keys;key;key=key->next) { char *file = cx_strdup_m(key->file.value).ptr; cxmutstr keycontent = loadkey(file); free(file); // TODO: check key length if(!keycontent.ptr) { return 1; } DavKey *davkey = calloc(1, sizeof(DavKey)); davkey->name = cx_strdup_m(key->name.value).ptr; davkey->type = dav_config_keytype(key->type); davkey->data = keycontent.ptr; davkey->length = keycontent.length; dav_context_add_key(ctx, davkey); } return 0; }