Sun, 17 Dec 2023 15:33:50 +0100
fix faulty string to int conversion utilities
Probably it was expected that errno is set to EINVAL when illegal characters are encountered. But this is not standard and does not happen on every system, allowing illegal strings to be parsed as valid integers.
/* * 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)); 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"; } 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; }