Sat, 18 Mar 2023 11:44:37 +0100
fix nsapi_error_request() could send empty error messages with http status 200, if the request status code wasn't set
/* * 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 = cxPointerLinkedListCreate(serverconfig->a, cx_cmp_ptr); serverconfig->logfiles = cxPointerLinkedListCreate(serverconfig->a, cx_cmp_ptr); serverconfig->host_vs = cxHashMapCreate(serverconfig->a, 16); serverconfig->authdbs = cxHashMapCreate(serverconfig->a, 16); serverconfig->resources = cxHashMapCreate(serverconfig->a, 16); serverconfig->dav = cxHashMapCreate(serverconfig->a, 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, 0); 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, 0); 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, 0); 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, 0); 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, 0); 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, 0); 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, 0); 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, 0); 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, 0); 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, 0); 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, 0); 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, 0); 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, 0); 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, 0); 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 = cxPointerLinkedListCreate(a, cx_cmp_ptr); // 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 = cxPointerLinkedListCreate(a, cx_cmp_ptr); // value type: WebdavBackendInitData* // initialize backends CxIterator i = cxListIterator(backends, 0); 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), cx_cmp_ptr, 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, (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, 0); 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; }