src/server/daemon/config.c

Tue, 13 Aug 2024 19:59:42 +0200

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Tue, 13 Aug 2024 19:59:42 +0200
changeset 545
720893ec7d48
parent 490
d218607f5a7e
child 556
b036ccad4b49
permissions
-rw-r--r--

new linux event_send implementation, replace event pipes with eventfd

/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright 2013 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 "../public/nsapi.h"

#include <stdio.h>
#include <stdlib.h>

#include <fcntl.h>
#include <sys/types.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <sys/mman.h>

#include <cx/string.h>
#include <cx/utils.h>
#include <cx/hash_map.h>
#include <cx/linked_list.h>
#include <cx/compare.h>

#include "httplistener.h"
#include "config.h"
#include "func.h"
#include "log.h"
#include "event.h"
#include "threadpools.h"
#include "ldap_auth.h"
#include "configmanager.h"
#include "resourcepool.h"

#include "vserver.h"
#include "../util/pblock.h"
#include "../util/util.h"
#include "../util/atomic.h"
#include "cx/buffer.h"

pool_handle_t *init_pool;

char* cfg_config_file_path(const char *file) {
    cxstring base = CX_STR("config/");
    cxmutstr path = cx_strcat(2, base, cx_str(file));
    return path.ptr;
}

InitConfig* load_init_conf(const char *file) {
    log_ereport(LOG_VERBOSE, "load_init_conf");

    InitConfig *cfg = initconfig_load(file);
    if(cfg == NULL) {
        log_ereport(LOG_FAILURE, "Cannot load init.conf");
        return NULL;;
    }
    
    return cfg;
}

int apply_init_conf(InitConfig *cfg) {
    init_pool = pool_create();
    
    ConfigNode *dir = cfg->root->children_begin;
    while(dir) {
        if(dir->type != CONFIG_NODE_DIRECTIVE) {
            // dir is just space/comment
            dir = dir->next;
            continue;
        }
        
        // create NSAPI directive
        
        // The parser checks the directive name and makes sure it is "Init"
        // if more than one init directive type is introduced, the parser
        // must be extended and also dir->name must be checked here
        
        directive *d = pool_malloc(init_pool, sizeof(directive));
        d->param = pblock_create_pool(init_pool, 8);
        
        ConfigParam *param = dir->args;
        while(param != NULL) {
            pblock_nvlinsert(
                    param->name.ptr,
                    param->name.length,
                    param->value.ptr,
                    param->value.length,
                    d->param);
            
            param = param->next;
        }
        
        // get function
        // the parser makes sure that an "fn" parameter always exists
        char *func_name = pblock_findval("fn", d->param);
        d->func = get_function(func_name);
        if(d->func == NULL) {
            log_ereport(
                    LOG_MISCONFIG,
                    "Cannot find Init function %s",
                    func_name);
            return 1;
        }
        
        // execute init directive
        int ret = d->func->func(d->param, NULL, NULL);
        if(ret != REQ_PROCEED && ret != REQ_NOACTION) {
            log_ereport(
                    LOG_FAILURE,
                    "Error running Init function %s",
                    func_name);
            return 1;
        }
        
        dir = dir->next;
    }
    
    return 0;
}

void free_init_conf(InitConfig *cfg) {
    initconfig_free(cfg);
}

ServerConfiguration* load_server_conf(CfgManager *mgr, char *file) {
    log_ereport(LOG_VERBOSE, "load_server_conf");

    ServerConfig *serverconf = serverconfig_load(file);
    if(!serverconf) {
        log_ereport(LOG_FAILURE, "Cannot load server.conf");
        return NULL;
    }
    mgr->serverconf = serverconf;
    
    pool_handle_t *pool = pool_create();
    
    
    ServerConfiguration *serverconfig = pool_calloc(pool, 1, sizeof(ServerConfiguration));
    serverconfig->ref = 1;
    serverconfig->pool = pool;
    
    CxAllocator *allocator = pool_allocator(serverconfig->pool);
    serverconfig->a = allocator;
    
    serverconfig->listeners = cxLinkedListCreate(serverconfig->a, NULL, CX_STORE_POINTERS);
    serverconfig->logfiles = cxLinkedListCreate(serverconfig->a, NULL, CX_STORE_POINTERS);
    serverconfig->host_vs = cxHashMapCreate(serverconfig->a, CX_STORE_POINTERS, 16);
    serverconfig->authdbs = cxHashMapCreate(serverconfig->a, CX_STORE_POINTERS, 16);
    serverconfig->resources = cxHashMapCreate(serverconfig->a, CX_STORE_POINTERS, 16);
    serverconfig->dav = cxHashMapCreate(serverconfig->a, CX_STORE_POINTERS, 16);
    
    // STAGE 1 load_server_conf:
    // At stage 1 we load the file and get the Runtime infos for changing
    // the uid, which must be done before most steps.
    // Before the uid can be changed, we also need to bind listeners,
    // therefore we need to get the listener config and all dependencies.
    // 
    // Runtime
    // Listener (dependencies: Threadpool, EventHandler)
    
    // load Runtime config
    CxList *list = serverconfig_get_node_list(serverconf->root, CONFIG_NODE_OBJECT, cx_str("Runtime"));
    CxIterator iter = cxListIterator(list);
    cx_foreach(ConfigNode *, runtimeobj, iter) {
        if(cfg_handle_runtime(serverconfig, runtimeobj)) {
            // error
            log_ereport(LOG_FAILURE, "server.conf runtime");
            return NULL;
        }
    }
    cxListDestroy(list);
    
    // load threadpool config
    log_ereport(LOG_DEBUG, "apply config: Threadpool");
    list = serverconfig_get_node_list(serverconf->root, CONFIG_NODE_OBJECT, cx_str("Threadpool"));
    iter = cxListIterator(list);
    cx_foreach(ConfigNode *, elm, iter) {
        if(cfg_handle_threadpool(serverconfig, elm)) {
            log_ereport(LOG_FAILURE, "server.conf threadpool");
            return NULL;
        }
    }
    cxListDestroy(list);
    // check thread pool config
    if(check_thread_pool_cfg() != 0) {
        /* critical error */
        return NULL;
    }
    
    // load eventhandler config
    log_ereport(LOG_DEBUG, "apply config: EventHandler");
    list = serverconfig_get_node_list(serverconf->root, CONFIG_NODE_OBJECT, cx_str("EventHandler"));
    iter = cxListIterator(list);
    cx_foreach(ConfigNode *, elm, iter) {
        if(cfg_handle_eventhandler(serverconfig, elm)) {
            // error
            log_ereport(LOG_FAILURE, "cannot create event handler");
            return NULL;
        }
    }
    // check event handler config
    if(check_event_handler_cfg() != 0) {
        /* critical error */
        return NULL;
    }
    cxListDestroy(list);
     
    // load Listener config
    log_ereport(LOG_DEBUG, "apply config: Listener");
    list = serverconfig_get_node_list(serverconf->root, CONFIG_NODE_OBJECT, cx_str("Listener"));
    iter = cxListIterator(list);
    cx_foreach(ConfigNode *, scfgobj, iter) {
        if(cfg_handle_listener(serverconfig, scfgobj)) {
            return NULL;
        }
    }
    cxListDestroy(list);
    
    // we return here, to let the webserver use the runtime info to
    // change the uid if needed
    return serverconfig;
}
   
ServerConfiguration* apply_server_conf(CfgManager *mgr) {
    ServerConfig *serverconf = mgr->serverconf;
    ServerConfiguration *serverconfig = mgr->cfg;
    
    /*
     * convert ServerConfig to ServerConfiguration
     * 
     * its important to do this in the correct order:
     * LogFile (open log file first to log possible errors)
     * Threadpool
     * EventHandler
     * AuthDB
     * Listener (we set the VirtualServer later)
     * VirtualServer (dependencies: Listener)
     */
    
    // init logfile first
    CxList *list;
    
    log_ereport(LOG_DEBUG, "apply config: LogFile");
    list = serverconfig_get_node_list(serverconf->root, CONFIG_NODE_OBJECT, cx_str("LogFile"));
    CxIterator iter = cxListIterator(list);
    cx_foreach(ConfigNode *, logobj, iter) {
        if(!logobj) {
            // error
            cxListDestroy(list);
            return NULL;
        }
        
        int ret = cfg_handle_logfile(serverconfig, logobj);
        if(ret != 0) {
            // cannot initialize log file
            cxListDestroy(list);
            return NULL;
        }
    }
    cxListDestroy(list);
    
    log_ereport(LOG_DEBUG, "apply config: AccessLog");
    list = serverconfig_get_node_list(serverconf->root, CONFIG_NODE_OBJECT, cx_str("AccessLog"));
    iter = cxListIterator(list);
    cx_foreach(ConfigNode *, scfgobj, iter) {
        if(cfg_handle_accesslog(serverconfig, scfgobj)) {
            return NULL;
        }
    }
    cxListDestroy(list);
    
    log_ereport(LOG_DEBUG, "apply config: AuthDB");
    list = serverconfig_get_node_list(serverconf->root, CONFIG_NODE_OBJECT, cx_str("AuthDB"));
    iter = cxListIterator(list);
    cx_foreach(ConfigNode *, scfgobj, iter) {
        if(cfg_handle_authdb(serverconfig, scfgobj)) {
            return NULL;
        }
    }
    cxListDestroy(list);
    
    log_ereport(LOG_DEBUG, "apply config: VirtualServer");
    list = serverconfig_get_node_list(serverconf->root, CONFIG_NODE_OBJECT, cx_str("VirtualServer"));
    iter = cxListIterator(list);
    cx_foreach(ConfigNode *, scfgobj, iter) {
        if(cfg_handle_vs(serverconfig, scfgobj)) {
            return NULL;
        }
    }
    cxListDestroy(list);
    
    log_ereport(LOG_DEBUG, "apply config: ResourcePool");
    list = serverconfig_get_node_list(serverconf->root, CONFIG_NODE_OBJECT, cx_str("ResourcePool"));
    iter = cxListIterator(list);
    cx_foreach(ConfigNode *, scfgobj, iter) {
        if(cfg_handle_resourcepool(serverconfig, scfgobj)) {
            return NULL;
        }
    }
    cxListDestroy(list);
    
    log_ereport(LOG_DEBUG, "apply config: Dav");
    list = serverconfig_get_node_list(serverconf->root, CONFIG_NODE_OBJECT, cx_str("Dav"));
    iter = cxListIterator(list);
    cx_foreach(ConfigNode *, scfgobj, iter) {
        if(cfg_handle_dav(serverconfig, scfgobj)) {
            return NULL;
        }
    }
    cxListDestroy(list);

    // set VirtualServer for all listeners
    CxList *ls = serverconfig->listeners;
    iter = cxListIterator(ls);
    cx_foreach(HttpListener *, listener, iter) {
        cxstring vsname = cx_str(listener->default_vs.vs_name);

        // search for VirtualServer
        //int b = 0;
        CxIterator map_iter = cxMapIteratorValues(serverconfig->host_vs);
        cx_foreach(VirtualServer *, vs, map_iter) {
            if(!cx_strcmp(vsname, (cxstring){vs->name.ptr, vs->name.length})) {
                listener->default_vs.vs = vs;
                break;
            }
        }
    }
    
    serverconfig_free(serverconf);
    
    return serverconfig;
}

int migrate_server_conf(ServerConfiguration *old_cfg, ServerConfiguration *new_cfg) {
    old_cfg->next = new_cfg;
    cfg_ref(new_cfg); // new cfg should not be freed until old cfg is freed
    
    // compare old/new listeners and set next listener, if they are using
    // the same socket
    CxIterator old_listeners = cxListIterator(old_cfg->listeners);
    cx_foreach(HttpListener*, oldls, old_listeners) {
        if(oldls->next) {
            // maybe we can remove this check
            log_ereport(LOG_WARN, "migrate_server_conf: oldls->next not NULL");
            continue;
        }
               
        CxIterator new_listeners = cxListIterator(new_cfg->listeners);
        cx_foreach(HttpListener*, newls, new_listeners) {
            if(http_listener_socket_eq(oldls, newls)) {
                http_listener_set_next(oldls, newls);
                break;
            }
        }
        http_listener_shutdown_acceptors(oldls);
        
        // TODO: wait until old listener is shut down
    }
    
    return 0;
}

void cfg_ref(ServerConfiguration *cfg) {
    ws_atomic_inc32(&cfg->ref);
}

void cfg_unref(ServerConfiguration *cfg) {
    uint32_t ref = ws_atomic_dec32(&cfg->ref);
    if(ref == 0) {
        if(cfg->next) {
            cfg_unref(cfg->next);
        }
        log_ereport(LOG_VERBOSE, "destroy configuration %p", cfg);
        
        CxIterator i = cxListIterator(cfg->listeners);
        cx_foreach(HttpListener*, listener, i) {
            http_listener_destroy(listener);
        }
        
        pool_destroy(cfg->pool);
    }
}


void init_server_config_parser() {
    
}

int cfg_handle_runtime(ServerConfiguration *cfg, ConfigNode *obj) { 
    cxstring user = serverconfig_object_directive_value(obj, cx_str("User"));
    if(user.ptr) {
        cfg->user = cx_strdup_a(cfg->a, user);
    }
    cxstring tmp = serverconfig_object_directive_value(obj, cx_str("Temp"));
    if(tmp.ptr) {
        cfg->tmp = cx_strdup_a(cfg->a, tmp);
    } else {
        // TODO: do this check after all config loading is done
        log_ereport(LOG_MISCONFIG, "no temporary directory specified");
        return -1;
    }
    
    // mime file
    cxstring mf = serverconfig_object_directive_value(obj, cx_str("MimeFile"));  
    cxstring base = cx_str("config/"); 
    cxmutstr file = cx_strcat(2, base, mf);
    
    if(mime_conf_load(cfg, file)) {
        return -1;
    }
    
    free(file.ptr);
    return 0;
}

int cfg_handle_logfile(ServerConfiguration *cfg, ConfigNode *obj) {
    cxstring file = serverconfig_object_directive_value(obj, cx_str("File"));
    cxstring lvl = serverconfig_object_directive_value(obj, cx_str("Level"));
    
    int err = 0;
    if(file.ptr == NULL) {
        err = 1;
        log_ereport(LOG_MISCONFIG, "LogFile: parameter missing: File");
    }
    if(lvl.ptr == NULL) {
        err = 1;
        log_ereport(LOG_MISCONFIG, "LogFile: parameter missing: Level");
    }
    if(err) {
        return -1;
    }
    
    LogConfig logcfg;
    logcfg.file = file.ptr;
    logcfg.level = lvl.ptr;
    logcfg.log_stdout = 0;
    logcfg.log_stderr = 0;
    /* TODO: stdout, stderr config */
    
    int ret = init_log_file(&logcfg);

    return ret;
}

int cfg_handle_threadpool(ServerConfiguration *cfg, ConfigNode *obj) {
    ThreadPoolConfig poolcfg;
    poolcfg.min_threads = 4;
    poolcfg.min_threads = 4;
    poolcfg.max_threads = 8;
    poolcfg.queue_size = 64;
    poolcfg.stack_size = 262144;
    
    cxstring name  = serverconfig_object_directive_value(obj, cx_str("Name"));
    cxstring min   = serverconfig_object_directive_value(obj, cx_str("MinThreads"));
    cxstring max   = serverconfig_object_directive_value(obj, cx_str("MaxThreads"));
    cxstring stack = serverconfig_object_directive_value(obj, cx_str("StackSize"));
    cxstring queue = serverconfig_object_directive_value(obj, cx_str("QueueSize"));
    // TODO: Type
    
    if(name.length == 0) {
        // TODO: log error
        return 1;
    }
    
    if(min.length != 0) {
        int64_t value;
        if(util_strtoint(min.ptr, &value)) {
            poolcfg.min_threads = value;
        } else {
            log_ereport(LOG_MISCONFIG, "Threadpool: MinThreads not an integer");
            return 1;
        }
    }
    
    if(max.length != 0) {
        int64_t value;
        if(util_strtoint(max.ptr, &value)) {
            poolcfg.max_threads = value;
        } else {
            log_ereport(LOG_MISCONFIG, "Threadpool: MaxThreads not an integer");
            return 1;
        }
    }
    
    if(stack.length != 0) {
        int64_t value;
        if(util_strtoint(stack.ptr, &value)) {
            poolcfg.stack_size = value;
        } else {
            log_ereport(LOG_MISCONFIG, "Threadpool: StackSize not an integer");
        }
    }
    
    if(queue.length != 0) {
        int64_t value;
        if(util_strtoint(queue.ptr, &value)) {
            poolcfg.queue_size = value;
        } else {
            log_ereport(LOG_MISCONFIG, "Threadpool: QueueSize not an integer");
        }
    }
    
    create_threadpool(name, &poolcfg);
    
    return 0;
}

#define EV_MAX_THREADS 2048
int cfg_handle_eventhandler(ServerConfiguration *c, ConfigNode *obj) {
    EventHandlerConfig evcfg;
    
    cxstring name      = serverconfig_object_directive_value(obj, cx_str("Name"));
    cxstring threads   = serverconfig_object_directive_value(obj, cx_str("Threads"));
    cxstring isdefault = serverconfig_object_directive_value(obj, cx_str("Default"));
    
    evcfg.name = name;
    
    int64_t value;
    if(!util_strtoint(threads.ptr, &value)) {
        log_ereport(LOG_MISCONFIG, "EventHandler: Threads: '%s' is not an integer", threads.ptr);
        return 1;
    }
    if(value < 1 || value > EV_MAX_THREADS) {
        log_ereport(LOG_MISCONFIG, "EventHandler: Invalid number of threads (1 .. %d)", EV_MAX_THREADS);
        return 1;
    }
    
    evcfg.nthreads = value;
    
    evcfg.isdefault = util_getboolean(isdefault.ptr, 0);
    
    return create_event_handler(&evcfg);
}

int cfg_handle_resourcepool(ServerConfiguration *cfg, ConfigNode *obj) {
    cxstring name = serverconfig_object_directive_value(obj, cx_str("Name"));
    cxstring type = serverconfig_object_directive_value(obj, cx_str("Type"));
    
    int ret = 0;
    if(resourcepool_new(cfg, type, name, obj)) {
        ret = 1;
    }
    
    return ret;
}

int cfg_handle_accesslog(ServerConfiguration *cfg, ConfigNode *obj) {
    // TODO: use a name to identify the log file
    
    cxstring file = serverconfig_object_directive_value(obj, cx_str("File"));
    if(file.ptr == NULL) {
        return 0;
    }
    cxmutstr format;
    format.ptr = NULL;
    format.length = 0;
    
    //AccessLog *log = get_access_log(file, format);
    LogFile *log_file = get_access_log_file(file);
    if(!log_file) {
        // TODO: error/warning
        return 0;
    }
    AccessLog *log = pool_malloc(cfg->pool, sizeof(AccessLog));
    log->file = cx_strdup_a(cfg->a, file);
    log->format = format;
    log->log = log_file;
    cxListAdd(cfg->logfiles, log);
    
    if(!cfg->default_log) {
        cfg->default_log = log;
    }
    
    return 0;
}

int cfg_handle_authdb(ServerConfiguration *cfg, ConfigNode *obj) {
    cxstring name = serverconfig_object_directive_value(obj, cx_str("Name"));
    cxstring type = serverconfig_object_directive_value(obj, cx_str("Type"));
    
    AuthDB *authdb = NULL;
    
    if(!cx_strcmp(type, cx_str("ldap"))) {
        authdb = create_ldap_authdb(cfg, name.ptr, obj);      
    } else if(!cx_strcmp(type, cx_str("keyfile"))) {
        // we only need the file parameter
        cxstring file = serverconfig_object_directive_value(obj, cx_str("File"));
        if(file.length == 0) {
            log_ereport(
                    LOG_MISCONFIG,
                    "missing File parameter for keyfile authdb");
            return 1;
        }
        
        // load keyfile
        authdb = keyfile_load(cfg, file);
    }

    if(authdb) {
        if(cxMapPut(cfg->authdbs, cx_hash_key_bytes((const unsigned char*)name.ptr, name.length), authdb)) {
            return -1;
        }
    }
    
    return 0;
}

int cfg_handle_listener(ServerConfiguration *cfg, ConfigNode *obj) {
    ListenerConfig lc;
    ZERO(&lc, sizeof(ListenerConfig));
    lc.cfg = cfg;
    lc.port = 8080;
    lc.nacceptors = 1;
    
    cxstring name = serverconfig_object_directive_value(obj, cx_str("Name"));
    cxstring port = serverconfig_object_directive_value(obj, cx_str("Port"));
    cxstring vs   = serverconfig_object_directive_value(obj, cx_str("DefaultVS"));
    cxstring thrp = serverconfig_object_directive_value(obj, cx_str("Threadpool"));
    cxstring blck = serverconfig_object_directive_value(obj, cx_str("BlockingIO"));
    
    // TODO: use cx_strdup_pool?
    int64_t port_value;
    if(!util_strtoint(port.ptr, &port_value)) {
        log_ereport(LOG_MISCONFIG, "Listener: Invalid argument for parameter 'Port': '%s'", port.ptr);
        return 1;
    }
    if(port_value < 1 || port_value > 65535) {
        log_ereport(LOG_MISCONFIG, "Listener: Port number out of range (1 .. 65535)");
        return 1;
    }
    
    lc.name = cx_strdup(name);
    lc.port = port_value;
    lc.vs = cx_strdup(vs);
    lc.threadpool = cx_strdup(thrp);
    
    lc.blockingio = util_getboolean_s(blck, WS_FALSE);
    
    cxstring ssl = serverconfig_object_directive_value(obj, cx_str("SSL"));
    if(util_getboolean_s(ssl, WS_FALSE)) {
        cxstring cert        = serverconfig_object_directive_value(obj, cx_str("Cert"));
        cxstring privkey     = serverconfig_object_directive_value(obj, cx_str("Key"));
        cxstring chain       = serverconfig_object_directive_value(obj, cx_str("CertChain"));
        cxstring disableprot = serverconfig_object_directive_value(obj, cx_str("SSLDisableProtocol"));
        
        WSBool config_ok = WS_TRUE;
        // TODO: log error
        if(!cert.ptr && !chain.ptr) {
            log_ereport(
                    LOG_MISCONFIG,
                    "SSL Listener %s: Missing Cert or ChainCert directive",
                    lc.name.ptr);
            config_ok = WS_FALSE;
        }
        if(!privkey.ptr) {
            log_ereport(
                    LOG_MISCONFIG,
                    "SSL Listener %s: Missing Key directive",
                    lc.name.ptr);
            config_ok = WS_FALSE;
        }
        
        if(config_ok) {
            lc.certfile = cert;
            lc.privkeyfile = privkey;
            lc.chainfile = chain;
            lc.disable_proto = disableprot;
            lc.ssl = WS_TRUE;
        }
    } else {
        lc.ssl = WS_FALSE;
    }
    
    // TODO: check if all important configs are set
    
    HttpListener *listener = http_listener_create(&lc);
    if(!listener) {
        return 1;
    }
    
    listener->default_vs.vs_name = cx_strdup_a(cfg->a, (cxstring){lc.vs.ptr, lc.vs.length}).ptr;
    cxListAdd(cfg->listeners, listener);
    
    return 0;
}

int cfg_handle_vs(ServerConfiguration *cfg, ConfigNode *obj) {
    VirtualServer *vs = vs_new(cfg->pool);

    vs->name = cx_strdup_a(cfg->a, serverconfig_object_directive_value(obj, cx_str("Name")));
    vs->host = cx_strdup_a(cfg->a, serverconfig_object_directive_value(obj, cx_str("Host")));
    vs->document_root = cx_strdup_a(cfg->a, serverconfig_object_directive_value(obj, cx_str("DocRoot")));
    
    cxstring objfile = serverconfig_object_directive_value(obj, cx_str("ObjectFile"));
    cxstring aclfile = serverconfig_object_directive_value(obj, cx_str("ACLFile"));
    
    // load the object config file
    cxstring base = cx_str("config/");
    // cx_strcat with allocator because we want to keep the string
    cxmutstr file = cx_strcat_a(cfg->a, 2, base, objfile);

    HTTPObjectConfig *httpobj = objconf_load(cfg, file);
    if(!httpobj) {
        return -1;
    }
    vs->objectfile = file;
    vs->objects = httpobj;
    
    
    // load acl config file
    cxmutstr acl_filepath = cx_strcat(2, base, aclfile);
    
    ACLData *acldata = acl_conf_load(cfg, acl_filepath.ptr);
    free(acl_filepath.ptr);
    if(!acldata) {
        return -1;
    }
    vs->acls = acldata;
    
    
    // set the access log for the virtual server
    // TODO: don't always use the default
    vs->log = cfg->default_log;

    cxMapPut(cfg->host_vs, cx_hash_key_bytes((unsigned const char*)vs->host.ptr, vs->host.length), vs);
    
    return 0;
}

int cfg_handle_dav(ServerConfiguration *cfg, ConfigNode *obj) {
    CxAllocator *a = pool_allocator(cfg->pool);
    CxList *backends = cxLinkedListCreate(a, NULL, CX_STORE_POINTERS); // list of ConfigParam*
    int init_error;
    
    // parse args
    char *uri = NULL;
    char *ppath = NULL;
    char *name = NULL;
    for(ConfigParam *arg=obj->args;arg;arg=arg->next) {
        cxstring arg_name = (cxstring){ arg->name.ptr, arg->name.length };
        if(arg->name.ptr == NULL) {
            // default: uri
            uri = arg->value.ptr;
        } else if(!cx_strcasecmp(arg_name, cx_str("uri"))) {
            uri = arg->value.ptr;
        } else if(!cx_strcasecmp(arg_name, cx_str("ppath"))) {
            ppath = arg->value.ptr;
        } else if(!cx_strcasecmp(arg_name, cx_str("name"))) {
            name = arg->value.ptr;
        }
    }
    if(!uri && !ppath && !name) {
        return 1;
    }
    
    // get a list of all DavBackends
    for(ConfigNode *node=obj->children_begin;node;node=node->next) {
        cxstring node_name = cx_strn(node->name.ptr, node->name.length);
        if(!cx_strcasecmp(node_name, cx_str("DavBackend"))) {
            if(node->type == CONFIG_NODE_DIRECTIVE) {
                if(CFG_NUM_PARAMS(node->args) == 1) {
                    cxListAdd(backends, node->args);
                } else {
                    log_ereport(LOG_MISCONFIG, "DavBackend must have only one value");
                    cxListDestroy(backends);
                    return 1;
                }
            } else {
                log_ereport(LOG_MISCONFIG, "DavBackend must be a directive");
                cxListDestroy(backends);
                return 1;
            }
        }
    }
    
    int ret = 0;
    WebdavRepository *repository = pool_malloc(cfg->pool, sizeof(WebdavRepository));
    repository->vfs = NULL;
    repository->vfsInitData = NULL;
    repository->davBackends = cxLinkedListCreate(a, NULL, CX_STORE_POINTERS); // value type: WebdavBackendInitData*
    
    // initialize backends
    CxIterator i = cxListIterator(backends);
    cx_foreach(ConfigParam *, backendArg, i) {
        // the DavBackend value should contain the dav class name
        
        WebdavType *dav = webdav_get_type((cxstring){backendArg->value.ptr, backendArg->value.length});
        if(!dav) {
            log_ereport(LOG_MISCONFIG, "Unknown webdav backend type '%s'", backendArg->value.ptr);
            ret = 1;
            break;
        }
        
        // call backend init
        // init_data can be NULL, errors will be indicated by init_error
        void *init_data = webdav_init_backend(cfg, cfg->pool, dav, obj, &init_error);
        if(init_error) {
            log_ereport(LOG_FAILURE, "Failed to initialize webdav backend %s", backendArg->value.ptr);
            ret = 1;
            break;
        }
        
        WebdavBackendInitData *davInit = pool_malloc(cfg->pool, sizeof(WebdavBackendInitData));
        if(!davInit) {
            log_ereport(LOG_FAILURE, "Failed to initialize webdav backend %s: OOM", backendArg->value.ptr);
            ret = 1;
            break;
        }
        davInit->davType = dav;
        davInit->davInitData = init_data;
        
        cxListAdd(repository->davBackends, davInit);
    }
    cxListDestroy(backends);
    
    // initialize vfs
    cxstring vfs_class = serverconfig_object_directive_value(obj, cx_str("VFS"));
    if(vfs_class.length > 0) {
        VfsType *vfs = vfs_get_type((cxstring){vfs_class.ptr, vfs_class.length});
        if(vfs) {
            repository->vfs = vfs;
            repository->vfsInitData = vfs_init_backend(cfg, cfg->pool, vfs, obj, &init_error);
            if(!ret) {
                ret = init_error;
            }
        } else {
            log_ereport(LOG_FAILURE, "Unknown vfs type '%s'", vfs_class.ptr);
            ret = 1;
        }
    }
    
    cxstring object = serverconfig_object_directive_value(obj, cx_str("Object"));
    if(object.length > 0) {
        repository->object = cx_strdup_a(a, object);
        if(repository->object.length != object.length) {
            // OOM
            log_ereport(LOG_FAILURE, "Cannot create webdav repository: OOM");
            ret = 1;
        }
    }
    
    if(!ret) {
        if(name) {
            cxMapPut(cfg->dav, cx_hash_key_str(name), repository);
        } else {
            log_ereport(LOG_FAILURE, "TODO: location based dav repositories not implemented");
            ret = 1;
        }
    }
    
    return ret;    
}

// condition depth limit, evaluated in recursive convert_objconf_directives calls
#define OBJ_CONF_MAX_CONDITION_DEPTH 500

static int set_client_condition(pool_handle_t *pool, ConfigNode *node, Condition *condition) {
    return 0;
}

static int set_if_condition(pool_handle_t *pool, ConfigNode *node, Condition *condition) { 
    // convert to parameters to a list of tokens
    // usually, one parameter is one token, however the config parser
    // converts name=value pairs to one ConfigParam
    
    // list of cxmutstr, however the expression parser will use this
    // as list of cxstring, but that is totally fine
    CxList *tokens = cxLinkedListCreate(pool_allocator(pool), NULL, sizeof(cxmutstr));
    ConfigParam *arg = node->args;
    while(arg) {
        if(arg->name.length > 0) {
            // arg text is name=value, therefore we add 3 tokens
            // name, "=", value
            cxListAdd(tokens, &arg->name);
            cxmutstr op = (cxmutstr){ "=", 1 };
            cxListAdd(tokens, &op);
        }
        if(cxListAdd(tokens, &arg->value)) {
            cxListDestroy(tokens);
            return 1; // OOM
        }
        arg = arg->next;
    }
    
    int ret = 0;
    condition->expression = condition_create(pool, tokens);
    if(!condition->expression) {
        ret = 1;
    }
    
    // don't need the token list anymore
    cxListDestroy(tokens);
    
    return ret;
}

// convert a condition 
static Condition* convert_objconf_condition(
        pool_handle_t *pool,
        ConfigNode *node,
        Condition *prev_condition,
        Condition *parent_condition,
        int *condition_index)
{
    const char *condnames[] = { "Client", "If", "ElseIf", "Else" };
    size_t typeindex;
    if(serverconfig_validate_directive_name(node, condnames, 4, &typeindex)) {
        // probably node->name is "Object", but nested objects are not allowed
        // maybe there should be a special error message in this case
        return NULL;
    }
    
    // "ElseIf" and "Else" require, that a previous "If" or "ElseIf" node exists
    if((typeindex == 2 || typeindex == 3) && prev_condition == NULL) {
        return NULL;
    }
    Condition *condition = pool_malloc(pool, sizeof(Condition));
    ZERO(condition, sizeof(Condition));
    
    condition->index = *condition_index;
    condition->parent = parent_condition;
    
    if(typeindex == 0) {
        // "Client"
        if(set_client_condition(pool, node, condition)) {
            return NULL;
        }
    } else {
        condition->ifnot = prev_condition;
        
        // set expression for "If" or "ElseIf"
        if(typeindex != 4 && set_if_condition(pool, node, condition)) {
            return NULL;
        }
    }
    
    (*condition_index)++;
    return condition;
}

// add directives to the httpd_object
// node->type can be CONFIG_NODE_DIRECTIVE or CONFIG_NODE_OBJECT
// CONFIG_NODE_DIRECTIVE can translated directly to directive*
// CONFIG_NODE_OBJECT node must be a condition (If/ElseIf/Else/Client)
static int convert_objconf_directives(
        pool_handle_t *pool,
        const char *file,
        httpd_object *obj,
        ConfigNode *node,
        Condition *parent_condition,
        int *condition_index)
{
    // previous condition, if it was the previous node
    // this is needed to link the "ElseIf" or "Else" node with the previous
    // "If" node
    Condition *prev_condition = NULL;
    
    for(;node;node=node->next) {
        if(node->type == CONFIG_NODE_OBJECT) {
            Condition *condition = convert_objconf_condition(pool, node, prev_condition, parent_condition, condition_index);
            if(!condition) {
                return 1;
            }
            // add children of condition node
            if(convert_objconf_directives(pool, file, obj, node->children_begin, condition, condition_index)) {
                return 1;
            }
            
            // previous condition is used to link "If" "IfElse" and "Else"
            // if the current node is "Else", the if-else block is complete
            // "Client" is unrelated to if-else
            if(!strcmp(node->name.ptr, "If") || !strcmp(node->name.ptr, "ElseIf")) {
                prev_condition = condition;
            } else {
                prev_condition = NULL;
            }
        } else if(node->type == CONFIG_NODE_DIRECTIVE) {
            directive *d = pool_malloc(pool, sizeof(directive));
            if(!d) return -1;
            d->param = pblock_create_pool(pool, 8);
            
            d->cond = parent_condition;

            // add params
            ConfigParam *param = node->args;
            while(param != NULL) {
                pblock_nvlinsert(
                        param->name.ptr,
                        param->name.length,
                        param->value.ptr,
                        param->value.length,
                        d->param);
                param = param->next;
            }

            // get function
            char *func_name = pblock_findval("fn", d->param);
            if(!func_name) {
                log_ereport(LOG_MISCONFIG, "%s: Missing fn parameter", file);
                return -1;
            }
            d->func = get_function(func_name);
            if(!d->func) {
                log_ereport(LOG_MISCONFIG, "func %s not found", func_name);
                return -1;
            }

            // add function to dtable
            int dir_type = cfg_get_directive_type_num(cx_strcast(node->name));
            if(dir_type < 0) {
                log_ereport(LOG_MISCONFIG, "unknown directive type %s", node->name);
            }
            object_add_directive(obj, d, dir_type);
            
            prev_condition = NULL;
        }
    }
    
    return 0;
}

static int convert_objconf(ServerConfiguration *scfg, ObjectConfig2 *cfg, HTTPObjectConfig *conf, cxmutstr file) {
    pool_handle_t *pool = conf->pool;
    
    int condition_index = 0;
    
    int i = 0;
    for(ConfigNode *objnode=cfg->root->children_begin;objnode;objnode=objnode->next) {
        if(objnode->type != CONFIG_NODE_OBJECT) {
            if(objnode->type == CONFIG_NODE_DIRECTIVE) {
                // error
                return 1;
            }
            continue;
        }
        
        if(strcmp(objnode->name.ptr, "Object")) {
            // error: not an object
            return 1;
        }
        
        // get name and ppath
        cxmutstr cfg_name = cfg_param_get(objnode->args, cx_str("name"));
        cxmutstr cfg_ppath = cfg_param_get(objnode->args, cx_str("ppath"));
        
        char *name = NULL;
        char *ppath = NULL;
        
        if(cfg_name.length > 0) {
            name = cx_strdup_pool(pool, cfg_name).ptr;
            if(!name) return -1;
        }
        if(cfg_ppath.length > 0) {
            ppath = cx_strdup_pool(pool, cfg_ppath).ptr;
            if(!ppath) return -1;
        }
        
        // create and add object
        httpd_object *obj = object_new(pool, name);
        if(!obj) return -1;
        obj->path = NULL;
        
        conf->objects[i] = obj;
        
        // add directives
        if(convert_objconf_directives(pool, file.ptr, obj, objnode->children_begin, NULL, &condition_index)) {
            return 1;
        }   

        // next
        i++;
    }
    
    return 0;
}

HTTPObjectConfig* objconf_load(ServerConfiguration *scfg, cxmutstr file) {
    log_ereport(LOG_VERBOSE, "load_obj_conf");
    
    int ret = 0;

    // create object config
    pool_handle_t *pool = scfg->pool;
    HTTPObjectConfig *conf = pool_calloc(pool, sizeof(HTTPObjectConfig), 1);
    if(!conf) {
        return NULL;
    }
    conf->pool = pool;
    
    // load obj config file
    ObjectConfig2 *cfg = objectconf_load(file.ptr);
    if(!cfg) {
        return NULL;
    }

    // convert ObjectConfig to HTTPObjectConfig

    // add objects
    conf->nobj = serverconfig_children_count(cfg->root, CONFIG_NODE_OBJECT);
    conf->objects = pool_calloc(pool, conf->nobj, sizeof(httpd_object*));
    if(conf->objects) {
        ret = convert_objconf(scfg, cfg, conf, file);
    } else {
        ret = -1;
    }

    objectconf_free(cfg);

    return !ret ? conf : NULL;
}

int mime_conf_load(ServerConfiguration *cfg, cxmutstr file) {
    MimeConfig *mimecfg = load_mime_config(file.ptr);
    if(!mimecfg) {
        return -1;
    }
    
    int ret = 0;
    
    // cleanup in case of errors is done by the allocator
    MimeMap *mimemap = cxMalloc(cfg->a, sizeof(MimeMap));
    CxMap *map = cxHashMapCreate(cfg->a, CX_STORE_POINTERS, (mimecfg->ntypes * 3) / 2);
    
    if(mimemap && map) {
        mimemap->map = map;
        
        // add ext type pairs
        for(MimeDirective *d=mimecfg->directives_begin;d;d=d->next) {
            // add the type for each extension to the map
            for(int i=0;i<d->nextensions;i++) {
                cxstring ext = d->extensions[i];
                cxmutstr value = cx_strdup(cx_strn(d->type.ptr, d->type.length));
                if(cxMapPut(map, cx_hash_key_bytes((const unsigned char *)ext.ptr, ext.length), value.ptr)) {
                    log_ereport(LOG_CATASTROPHE, "OOM");
                    ret = -1;
                    break;
                }
            }
            if(ret) {
                break;
            }
        }
        
        cfg->mimetypes = mimemap;
    } else {
        log_ereport(LOG_CATASTROPHE, "OOM");
        ret = -1;
    }
    
    free_mime_config(mimecfg);
    return ret;
}



ACLData* acl_conf_load(ServerConfiguration *cfg, const char *file) {
    ACLFile *aclfile = load_acl_file(file);
    if(!aclfile) {
        log_ereport(LOG_FAILURE, "Cannot load acl file %s", file);
        return NULL;
    }
    
    // TODO: malloc return checks
    
    ACLData *acldata = acl_data_new(cfg->a);
    CxIterator iter = cxListIterator(aclfile->namedACLs);
    cx_foreach(ACLConfig *, ac, iter) {
        ACLList *acl = acl_config_convert(cfg, ac);
        log_ereport(LOG_VERBOSE, "add acl: %.*s", (int)ac->id.length, ac->id.ptr);
        cxMapPut(acldata->namedACLs, cx_hash_key(ac->id.ptr, ac->id.length), acl);
    }
    free_acl_file(aclfile);
    
    return acldata;
}

ACLList* acl_config_convert(ServerConfiguration *cfg, ACLConfig *acl) {
    CxAllocator *a = cfg->a;
    
    WSAcl *acllist = cxMalloc(cfg->a, sizeof(WSAcl));
    acllist->acl.check = (acl_check_f)wsacl_check;
    acllist->acl.authdb = NULL;
    acllist->acl.authprompt = NULL;
    acllist->acl.isextern = 0;
    acllist->ace = NULL;
    acllist->ece = NULL;
    
    if(acl->type.ptr && !cx_strcmp(cx_strn(acl->type.ptr, acl->type.length), cx_str("fs"))) {
        acllist->acl.isextern = 1;
    }
    
    size_t s = CFG_ACE_LIST_SIZE(acl->entries);
    WSAce **tmp_aces = calloc(s, sizeof(WSAce*));
    WSAce **tmp_eces = calloc(s, sizeof(WSAce*));
    int ai = 0;
    int ei = 0;
    
    // convert entries
    for(ACEConfig *acecfg=acl->entries;acecfg;acecfg=acecfg->next) {
        // copy data
        WSAce *ace = cxMalloc(a, sizeof(WSAce));
        ace->access_mask = acecfg->access_mask;
        ace->flags = acecfg->flags;
        ace->type = acecfg->type;
        ace->who = cx_strdup_a(a, cx_strcast(acecfg->who)).ptr;
        
        // add the entry to the correct array
        if(ace->type >= ACL_TYPE_AUDIT) {
            tmp_eces[ei] = ace;
            ei++;
        } else {
            tmp_aces[ai] = ace;
            ai++;
        }
    }
    
    // create new entrie arrays with perfect fitting size
    if(ai > 0) {
        acllist->ace = cxCalloc(a, ai, sizeof(WSAce*));
    }
    if(ei > 0) {
        acllist->ece = cxCalloc(a, ei, sizeof(WSAce*));
    }
    memcpy(acllist->ace, tmp_aces, ai*sizeof(WSAce*));
    memcpy(acllist->ece, tmp_eces, ei*sizeof(WSAce*));
    acllist->acenum = ai;
    acllist->ecenum = ei;
    
    free(tmp_aces);
    free(tmp_eces);
    
    // get authentication information
    if(acl->authparam) {
        cxmutstr authdb_str = cfg_param_get(acl->authparam, cx_str("authdb"));
        cxmutstr prompt_str = cfg_param_get(acl->authparam, cx_str("prompt"));
        
        if(authdb_str.ptr) {
            AuthDB *authdb = cxMapGet(cfg->authdbs, cx_hash_key(authdb_str.ptr, authdb_str.length));
            acllist->acl.authdb = authdb;
            if(authdb && prompt_str.ptr) {
                acllist->acl.authprompt = cx_strdup_a(a, cx_strcast(prompt_str)).ptr;
            }
        }
    }
    
    return &acllist->acl;
}

AuthDB* keyfile_load(ServerConfiguration *cfg, cxstring file) {
    Keyfile *keyfile = keyfile_new(cfg->a);
    if(!keyfile) {
        return NULL;
    }
    
    KeyfileConfig *conf = load_keyfile_config(file.ptr);
    if(!conf) {
        return NULL;
    }
    
    AuthDB *ret = &keyfile->authdb;
    
    for(KeyfileEntry *user=conf->users_begin;user;user=user->next) {
        if(keyfile_add_user(
                keyfile,
                user->name,
                user->hashtype,
                user->hashdata,
                user->groups,
                user->numgroups))
        {
            ret = NULL;
            break;
        }
    }
    
    free_keyfile_config(conf);
    
    return ret;
}

pblock* config_obj2pblock(pool_handle_t *pool, ConfigNode *obj) {
    pblock *pb = pblock_create_pool(pool, 8);
    for(ConfigNode *d=obj->children_begin;d;d=d->next) {
        if(d->type == CONFIG_NODE_DIRECTIVE && d->name.length > 0 && CFG_NUM_PARAMS(d->args) == 1) {
            ConfigParam *arg = d->args;
            pblock_nvlinsert(d->name.ptr, d->name.length, arg->value.ptr, arg->value.length, pb);
        }
    }
    return pb;
}

mercurial