Sun, 10 Nov 2024 15:30:46 +0100
update toolkit
/* * 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 "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* load_config_file(void) { 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 NULL; } default: { perror("Cannot load config.xml"); } } return NULL; } cxmutstr config_content = config_load_file(file); int config_error; DavConfig *cfg = dav_config_load(config_content, &config_error); free(config_content.ptr); free(file); return cfg; } void set_config(DavConfig *cfg) { if(davconfig) { dav_config_free(davconfig); } davconfig = cfg; } 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 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; } void set_pwdstore(PwdStore *newstore) { if(pstore) { // TODO: free } pstore = newstore; } 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; }