application/config.c

Wed, 23 Oct 2024 18:25:28 +0200

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Wed, 23 Oct 2024 18:25:28 +0200
changeset 57
d5a7dbc945ef
parent 55
1ce14068ef31
permissions
-rw-r--r--

store elm path with trailing path separator

/*
 * 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 <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 "config.h"
#include "pwd.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: {
                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;
}

mercurial