dav/config.c

Tue, 12 Dec 2017 23:58:54 +0100

author
Mike Becker <universe@uap-core.de>
date
Tue, 12 Dec 2017 23:58:54 +0100
branch
v1.1
changeset 343
b9c6e0be5774
parent 321
eb8885a87866
child 347
b6ff6be7aa91
permissions
-rw-r--r--

fixes infinite loop when trying to overwrite a collection with a regular file

/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright 2016 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 <ucx/map.h>
#include <errno.h>
#include <libxml/tree.h>

#include "config.h"
#include "main.h"
#include <libidav/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 UcxMap *repos;
static UcxMap *keys;

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;
}

int load_config(DavContext *ctx) {
    context = ctx;
    // TODO: free the config somewhere
    repos = ucx_map_new(16);
    keys = ucx_map_new(16);
    
    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;
    }
    
    xmlDoc *doc = xmlReadFile(file, NULL, 0);
    free(file);
    if(!doc) {
        fprintf(stderr, "Cannot load config.xml\n");
        return 1;
    }
    
    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(node);
            } else if(xstreq(node->name, "key")) {
                ret = load_key(node);
            } else if (xstreq(node->name, "http-proxy")) {
                ret = load_proxy(ctx->http_proxy, node, HTTP_PROXY);
            } else if (xstreq(node->name, "https-proxy")) {
                ret = load_proxy(ctx->https_proxy, node, HTTPS_PROXY);
            } else if (xstreq(node->name, "namespace")) {
                ret = load_namespace(node);
            } else {
                fprintf(stderr, "Unknown config element: %s\n", node->name);
                ret = 1;
            }
        }
        node = node->next;
    }
    
    xmlFreeDoc(doc);
    return ret;
}

void free_config(void) {
    if(repos) {
        UcxMapIterator i = ucx_map_iterator(repos);
        UcxKey k;
        Repository *repo;
        UCX_MAP_FOREACH(k, repo, i) {
            if(repo->default_key) {
                free(repo->default_key);
            }
            if(repo->name) {
                free(repo->name);
            }
            if(repo->password) {
                free(repo->password);
            }
            if(repo->url) {
                free(repo->url);
            }
            if(repo->user) {
                free(repo->user);
            }
            if(repo->cert) {
                free(repo->cert);
            }      
            free(repo);
        }
        ucx_map_free(repos);
    }
    if(keys) {
        ucx_map_free(keys);
    }
}

Repository* repository_new(void) {
    Repository *repo = calloc(1, sizeof(Repository));
    repo->encrypt_name = false;
    repo->encrypt_content = false;
    repo->decrypt_name = false;
    repo->decrypt_content = true;
    repo->verification = true;
    repo->ssl_version = CURL_SSLVERSION_DEFAULT;
    repo->authmethods = CURLAUTH_BASIC;
    return repo;
}

static int repo_add_config(Repository *repo, const 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")) {
        repo->name = strdup(value);
    } else if(xstreq(key, "url")) {
        repo->url = strdup(value);
    } else if(xstreq(key, "user")) {
        repo->user = strdup(value);
    } else if(xstreq(key, "password")) {
        repo->password = util_base64decode(value);
    } else if(xstreq(key, "default-key")) {
        repo->default_key = strdup(value);
    } else if(xstreq(key, "full-encryption")) {
        if(util_getboolean(value)) {
            repo->encrypt_name = true;
            repo->encrypt_content = true;
            repo->decrypt_name = true;
            repo->decrypt_content = true;
        }
    } else if(xstreq(key, "content-encryption")) {
        if(util_getboolean(value)) {
            repo->encrypt_content = true;
            repo->decrypt_content = true;
        } else {
            repo->encrypt_content = false;
        }
    } else if(xstreq(key, "decrypt-content")) {
        repo->decrypt_content = util_getboolean(value);
    } else if(xstreq(key, "decrypt-name")) {
        repo->decrypt_name = util_getboolean(value);
    } else if(xstreq(key, "cert")) {
        char *configdir = util_concat_path(ENV_HOME, ".dav");
        char *certfile = util_concat_path(configdir, value);
        repo->cert = certfile;
        free(configdir);
    } else if(xstreq(key, "verification")) {
        repo->verification = util_getboolean(value);
    } else if(xstreq(key, "ssl-version")) {
        if(xstrEQ(value, "TLSv1")) {
            repo->ssl_version = CURL_SSLVERSION_TLSv1;
        } else if(xstrEQ(value, "SSLv2")) {
            repo->ssl_version = CURL_SSLVERSION_SSLv2;
        } else if(xstrEQ(value, "SSLv3")) {
            repo->ssl_version = CURL_SSLVERSION_SSLv3;
        }
#if LIBCURL_VERSION_MAJOR >= 7
#if LIBCURL_VERSION_MINOR >= 34
        else if(xstrEQ(value, "TLSv1.0")) {
            repo->ssl_version = CURL_SSLVERSION_TLSv1_0;
        } else if(xstrEQ(value, "TLSv1.1")) {
            repo->ssl_version = CURL_SSLVERSION_TLSv1_1;
        } else if(xstrEQ(value, "TLSv1.2")) {
            repo->ssl_version = CURL_SSLVERSION_TLSv1_2;
        }
#endif
#if LIBCURL_VERSION_MINOR >= 52
        else if(xstrEQ(value, "TLSv1.3")) {
            repo->ssl_version = CURL_SSLVERSION_TLSv1_3;
        }
#endif
#endif
        else {
            print_warning(lineno, "unknown ssl version: %s\n", value);
        }
    } else if(xstreq(key, "authmethods")) {
        repo->authmethods = CURLAUTH_NONE;
        const char *delims = " \r\n";
        char *meths = strdup(value);
        char *meth = strtok(meths, delims);
        while (meth) {
            if(xstrEQ(value, "basic")) {
                repo->authmethods |= CURLAUTH_BASIC;
            } else if(xstrEQ(value, "digest")) {
                repo->authmethods |= CURLAUTH_DIGEST;
            } else if(xstrEQ(value, "negotiate")) {
                repo->authmethods |= CURLAUTH_GSSNEGOTIATE;
            } else if(xstrEQ(value, "ntlm")) {
                repo->authmethods |= CURLAUTH_NTLM;
            } else if(xstrEQ(value, "any")) {
                repo->authmethods = CURLAUTH_ANY;
            } else if(xstrEQ(value, "none")) {
                /* skip */
            } else {
                print_warning(lineno,
                        "unknown authentication method: %s\n", value);
            }
            meth = strtok(NULL, delims);
        }
        free(meths);
    } else {
        print_error(lineno, "unkown repository config element: %s\n", key);
        return 1;
    }
    return 0;
}

int load_repository(const xmlNode *reponode) {
    Repository *repo = repository_new();
    {
        xmlNode *node = reponode->children;
        int ret = 0;
        while(node && !ret) {
            if(node->type == XML_ELEMENT_NODE) {
                ret = repo_add_config(repo, node);
            }
            node = node->next;
        }
        if(ret) {
            free(repo);
            return 1;
        }
    }
    
    if(!repo->name) {
        print_error(reponode->line, "missing name for repository.\n");
        return 1;
    }
    if(!repo->url) {
        print_error(reponode->line,
                "missing url for repository '%s'.\n", repo->name);
        return 1;
    }
    
    ucx_map_cstr_put(repos, repo->name, repo);
    return 0;
}

int load_proxy(DavProxy *proxy, const xmlNode *proxynode, int type) {
    const char *stype;
    if(type == HTTPS_PROXY) {
        stype = "https";
    } else if(type == 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) {
            char *value = util_xml_get_text(node);
            int reportmissingvalue = 0;
            if(xstreq(node->name, "url")) {
                if(!(reportmissingvalue = !value)) {
                    proxy->url = strdup(value);
                }
            } else if(xstreq(node->name, "user")) {
                if(!(reportmissingvalue = !value)) {
                    proxy->username = strdup(value);
                }
            } else if(xstreq(node->name, "password")) {
                if(!(reportmissingvalue = !value)) {
                    proxy->password = util_base64decode(value);
                }
            } else if(xstreq(node->name, "no")) {
                if(!(reportmissingvalue = !value)) {
                    proxy->no_proxy = strdup(value);
                }
            } else {
                print_error(node->line,
                        "invalid element for proxy config: %s\n", node->name);
                ret = 1;
            }
            if (reportmissingvalue) {
                print_error(node->line,
                        "missing value for proxy configuration element: %s\n",
                        node->name);
                ret = 1;
            }
        }
        node = node->next;
    }
    
    if(!ret && !proxy->url) {
        print_error(proxynode->line, "missing url for %s proxy.\n", stype);
        return 1;
    }
    
    return ret;
}

int load_key(const xmlNode *keynode) {
    xmlNode *node = keynode->children;
    Key *key = calloc(1, sizeof(Key));
    key->type = KEY_AES256;
    
    int error = 0;
    while(node) {
        if(node->type == XML_ELEMENT_NODE) {
            char *value = util_xml_get_text(node);
            if(!value) {
                // next
            } else if(xstreq(node->name, "name")) {
                key->name = strdup(value);
            } else if(xstreq(node->name, "file")) {
                // load key file
                sstr_t key_data = load_key_file(value);
                if(key_data.length > 0) {
                    key->data = key_data.ptr;
                    key->length = key_data.length;
                } else {
                    print_error(node->line,
                            "cannot get key from file: %s\n", value);
                    error = 1;
                }
            } else if(xstreq(node->name, "type")) {
                if(!strcmp(value, "aes128")) {
                    key->type = KEY_AES128;
                } else if(!strcmp(value, "aes256")) {
                    key->type = KEY_AES256;
                } else {
                    print_error(node->line, "unknown key type %s\n", value);
                    error = 1;
                }
            }
                
        }
        node = node->next;
    }
    
    if(!error && key->name) {
        error = 0;
        size_t expected_length = 0;
        if(key->type == KEY_AES128) {
            expected_length = 16;
        }
        if(key->type == KEY_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) {
            ucx_map_cstr_put(keys, key->name, key);
            dav_context_add_key(context, key);
        }
    }
    
    // cleanup
    if(error) {
        if(key->data) {
            free(key->data);
        }
        free(key);
        return 1;
    } else {
        return 0;
    }
}

sstr_t load_key_file(char *filename) {
    sstr_t k;
    k.ptr = NULL;
    k.length = 0;
    
    FILE *file = NULL;
    if(filename[0] == '/') {
        file = fopen(filename, "r");
    } else {
        char *path = util_concat_path(ENV_HOME, ".dav/");
        char *p2 = util_concat_path(path, filename);
        file = fopen(p2, "r");
        free(path);
        free(p2);
    }
    
    if(!file) {
        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);
}

Repository* get_repository(sstr_t name) {
    if(!name.ptr) {
        return NULL;
    }
    return ucx_map_sstr_get(repos, name);
}

int get_repository_flags(Repository *repo) {
    int flags = 0;
    if(repo->decrypt_content) {
        flags |= DAV_SESSION_DECRYPT_CONTENT;
    }
    if(repo->decrypt_name) {
        flags |= DAV_SESSION_DECRYPT_NAME;
    }
    if(repo->encrypt_content) {
        flags |= DAV_SESSION_ENCRYPT_CONTENT;
    }
    if(repo->encrypt_name) {
        flags |= DAV_SESSION_ENCRYPT_NAME;
    }
    return flags;
}


Key* get_key(char *name) {
    if(!name) {
        return NULL;
    }
    return ucx_map_cstr_get(keys, name);
}

int add_repository(Repository *repo) {
    if(check_config_dir()) {
        fprintf(stderr, "Cannot create .dav directory\n");
        return 1;
    }
    
    char *file = util_concat_path(ENV_HOME, ".dav/config.xml");
    struct stat s;
    if(stat(file, &s)) {
        switch(errno) {
            case ENOENT: {
                create_default_config(file);
                break;
            }
            default: {
                perror("Cannot load config.xml");
                free(file);
                return 1;
            }
        }
    }
    
    xmlDoc *doc = xmlReadFile(file, NULL, 0);
    if(!doc) {
        free(file);
        fprintf(stderr, "Cannot load config.xml\n");
        return 1;
    }
    
    xmlNode *root = xmlDocGetRootElement(doc);
    
    xmlNode *repoNode = xmlNewNode(NULL, BAD_CAST "repository");
    xmlNodeAddContent(repoNode, BAD_CAST "\n\t\t");
    xmlNewTextChild(repoNode, NULL, BAD_CAST "name", BAD_CAST repo->name);
    xmlNodeAddContent(repoNode, BAD_CAST "\n\t\t");
    xmlNewTextChild(repoNode, NULL, BAD_CAST "url", BAD_CAST repo->url);
    xmlNodeAddContent(repoNode, BAD_CAST "\n");
    if(repo->user) {
        xmlNodeAddContent(repoNode, BAD_CAST "\t\t");
        xmlNewChild(repoNode, NULL, BAD_CAST "user", BAD_CAST repo->user);
        xmlNodeAddContent(repoNode, BAD_CAST "\n");
        if(repo->password) {
            char *pwenc = util_base64encode(
                    repo->password,
                    strlen(repo->password));
            xmlNodeAddContent(repoNode, BAD_CAST "\t\t");
            xmlNewTextChild(repoNode, NULL, BAD_CAST "password", BAD_CAST pwenc);
            free(pwenc);
            xmlNodeAddContent(repoNode, BAD_CAST "\n");
        }
    }
    xmlNodeAddContent(repoNode, BAD_CAST "\t");
    
    xmlNodeAddContent(root, BAD_CAST "\n\t");
    xmlAddChild(root, repoNode);
    xmlNodeAddContent(root, BAD_CAST "\n");
    
    int ret = 0;
    if(xmlSaveFormatFileEnc(file, doc, "UTF-8", 1) == -1) {
        ret = 1;
    }
    xmlFreeDoc(doc);
    free(file);
    
    return ret;
}

int list_repositories() {
    UcxMapIterator i = ucx_map_iterator(repos);
    Repository *repo;
    UCX_MAP_FOREACH(key, repo, i) {
        printf("%s\n", repo->name);
    }
    return 0;
}

UcxList* get_repositories(void) {
    UcxList *list = NULL;
    UcxMapIterator i = ucx_map_iterator(repos);
    Repository *repo;
    UCX_MAP_FOREACH(key, repo, i) {
        list = ucx_list_append(list, repo);
    }
    return list;
}

mercurial