--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/server/plugins/postgresql/config.c Sat Sep 24 16:26:10 2022 +0200 @@ -0,0 +1,394 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2022 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 "config.h" + +#include "../../util/util.h" + +#include <libxml/tree.h> + +#define xstreq(a,b) xmlStrEqual(BAD_CAST a, BAD_CAST b) + +static int pg_load_ext_dav_config( + ServerConfiguration *cfg, + pool_handle_t *pool, + PgRepository *repo, + const char *file); + + +static const char *sql_get_repository_root = "select resource_id from Resource where parent_id is NULL and nodename = $1 ;"; + + +// Uses a PGconn to lookup the resource id of the specified root node +// if the lookup succeeds, the resource id is written to rootid +// in case of an error, an error message will be logged +// returns: 0 success, 1 error +int pg_lookup_root(ResourceData *res, const char *rootnode, int64_t *rootid) { + PGconn *connection = res->data; + + PGresult *result = PQexecParams( + connection, + sql_get_repository_root, + 1, // number of parameters + NULL, + &rootnode, // parameter value + NULL, + NULL, + 0); // 0: result in text format + + if(!result) { + log_ereport(LOG_FAILURE, "pg: root lookup failed: %s", PQerrorMessage(connection)); + return 1; + } + + int ret = 0; + + int nrows = PQntuples(result); + if(nrows == 1) { + char *resource_id_str = PQgetvalue(result, 0, 0); + if(resource_id_str) { + if(!util_strtoint(resource_id_str, rootid)) { + log_ereport(LOG_FAILURE, "pg: unexpected result for column resource_id", rootnode); + ret = 1; + } + } + } else { + log_ereport(LOG_FAILURE, "pg: cannot find root resource '%s'", rootnode); + ret = 1; + } + PQclear(result); + + return ret; +} + +PgRepository* pg_init_repo(ServerConfiguration *cfg, pool_handle_t *pool, WSConfigNode *config) { + UcxAllocator a = util_pool_allocator(pool); + + ConfigNode *pg = serverconfig_get_node(config, CONFIG_NODE_OBJECT, SC("Postgresql")); + if(!pg) { + log_ereport(LOG_MISCONFIG, "pg_init_repo: missing postgresql config object"); + return NULL; + } + + scstr_t cfg_respool = serverconfig_directive_value(pg, SC("ResourcePool")); + scstr_t cfg_rootid = serverconfig_directive_value(pg, SC("RootId")); + scstr_t cfg_rootnode = serverconfig_directive_value(pg, SC("RootNode")); + scstr_t cfg_dav = serverconfig_directive_value(pg, SC("PGDavConfig")); + + // minimum requirement is a resource pool + if(cfg_respool.length == 0) { + return NULL; + } + + // check rootid + int64_t root_id = 1; + if(cfg_rootid.length > 0) { + if(!util_strtoint(cfg_rootid.ptr, &root_id)) { + log_ereport(LOG_MISCONFIG, "pg_init_repo: RootId parameter is not an integer: %s", cfg_rootid.ptr); + return NULL; + } + } + + // warn if RootId and RootNode are specified + if(cfg_rootid.length > 0 && cfg_rootnode.length > 0) { + log_ereport(LOG_WARN, "log_init_repo: RootId and RootNode specified, RootNode ignored"); + } else if(cfg_rootnode.length > 0) { + // check root node + + // resolve rootnode + // therefore we first need to get a connection from the resourcepool + ResourceData *res = resourcepool_cfg_lookup(cfg, cfg_respool.ptr, 0); + if(!res) { + log_ereport(LOG_MISCONFIG, "pg_init_repo: resource lookup failed"); + return NULL; + } + // do lookup, if successful, root_id will be set + int lookup_err = pg_lookup_root(res, cfg_rootnode.ptr, &root_id); + // we don't need the connection anymore + resourcepool_free(NULL, NULL, res); + if(lookup_err) { + // no logging required, pg_lookup_root will log the error + return NULL; + } + } + + PgRepository *repo = pool_malloc(pool, sizeof(PgRepository)); + ZERO(repo, sizeof(PgRepository)); + + repo->resourcepool = sstrdup_a(&a, cfg_respool); + repo->root_resource_id = root_id; + + // check for extended pg dav config + if(cfg_dav.length > 0) { + // load extended config from config file + char *cfg_file_path = cfg_config_file_path(cfg_dav.ptr); + if(pg_load_ext_dav_config(cfg, pool, repo, cfg_file_path)) { + // error + repo = NULL; // no need to cleanup because everything is from the pool + } + free(cfg_file_path); + } + + return repo; +} + +static int pg_ext_get_config( + ServerConfiguration *cfg, + pool_handle_t *pool, + PgRepository *repo, + const char *file_path, + xmlNode *root); +static int pg_ext_get_extension( + PgExtParser *ext, + pool_handle_t *pool, + PgRepository *repo, + const char *file_path, + xmlNode *ext_node); + +static const char* pg_util_xml_get_text(const xmlNode *elm) { + xmlNode *node = elm->children; + while(node) { + if(node->type == XML_TEXT_NODE) { + return (const char*)node->content; + } + node = node->next; + } + return NULL; +} + +// load additional postgresql webdav config from the specified xml file +static int pg_load_ext_dav_config( + ServerConfiguration *cfg, + pool_handle_t *pool, + PgRepository *repo, + const char *file_path) +{ + UcxAllocator a = util_pool_allocator(pool); + + // check if the file exists and can be accessed + struct stat s; + if(stat(file_path, &s)) { + if(errno == ENOENT) { + log_ereport(LOG_FAILURE, "pg: config file %s not found", file_path); + } else { + log_ereport(LOG_FAILURE, "pg: cannot access config file %s", file_path); + } + + return 1; + } + + // prepare PgRepository + repo->prop_ext = ucx_map_new_a(&a, 8); + if(!repo->prop_ext) { + log_ereport(LOG_FAILURE, "pg: cannot load config file: OOM"); + return 1; + } + + // load xml document + xmlDoc *doc = xmlReadFile(file_path, NULL, 0); + if(!doc) { + log_ereport(LOG_FAILURE, "pg: cannot load config file %s", file_path); + return 1; + } + + // the root element must be <repository> + int ret = 0; + xmlNode *xml_root = xmlDocGetRootElement(doc); + if(xstreq(xml_root->name, "repository")) { + // parse config + ret = pg_ext_get_config(cfg, pool, repo, file_path, xml_root); + } else { + log_ereport(LOG_MISCONFIG, "pg: config %s: root element <repository> expected", file_path); + ret = 1; + } + xmlFreeDoc(doc); + + return ret; +} + + + + +static int pg_ext_get_config( + ServerConfiguration *cfg, + pool_handle_t *pool, + PgRepository *repo, + const char *file_path, + xmlNode *root) +{ + xmlNode *node = root->children; + int ret = 0; + + PgExtParser parserData; + parserData.table_lookup = ucx_map_new(8); + parserData.tables = NULL; + + while(node && !ret) { + // currently, the only possible config element is <extension> + if(node->type == XML_ELEMENT_NODE) { + if(xstreq(node->name, "extension")) { + ret = pg_ext_get_extension(&parserData, pool, repo, file_path, node); + } + } + node = node->next; + } + + // convert parserData + if(!ret) { + size_t ntables = ucx_list_size(parserData.tables); + repo->ntables = ntables; + repo->tables = pool_calloc(pool, ntables, sizeof(PgExtTable)); + if(repo->tables) { + int i = 0; + UCX_FOREACH(elm, parserData.tables) { + PgExtTable *tab = elm->data; + repo->tables[i++] = *tab; + } + } else { + ret = 1; + } + + } + + // cleanup parser + ucx_list_free_content(parserData.tables, free); + ucx_list_free(parserData.tables); + ucx_map_free(parserData.table_lookup); + + return ret; +} + +static int pg_ext_get_extension( + PgExtParser *ext, + pool_handle_t *pool, + PgRepository *repo, + const char *file_path, + xmlNode *ext_node) +{ + UcxAllocator a = util_pool_allocator(pool); + + xmlNode *node = ext_node->children; + + const char *table = NULL; + UcxList *properties = NULL; + + while(node) { + if(node->type == XML_ELEMENT_NODE) { + if(xstreq(node->name, "table")) { + const char *value = pg_util_xml_get_text(node); + if(!value) { + log_ereport(LOG_MISCONFIG, "pg: config %s: table: missing value", file_path, table); + return 1; + } else if(table) { + log_ereport(LOG_MISCONFIG, "pg: config %s: table %s already set", file_path, table); + return 1; + } + table = value; + } else if(xstreq(node->name, "properties")) { + // add all child elements to the properties list + xmlNode *ps = node->children; + while(ps) { + if(ps->type == XML_ELEMENT_NODE) { + // validate + // required: namespace, value + if(!ps->ns || !ps->ns->href) { + log_ereport(LOG_MISCONFIG, "pg: config %s: property %s: missing namespace", file_path, ps->name); + return 1; + } + const char *value = pg_util_xml_get_text(ps); + if(!value) { + log_ereport(LOG_MISCONFIG, "pg: config %s: no column specified for property %s", file_path, ps->name); + return 1; + } + properties = ucx_list_append_a(&a, properties, ps); + } + ps = ps->next; + } + } + } + node = node->next; + } + + // check if anything is missing + if(!table) { + log_ereport(LOG_MISCONFIG, "pg: config %s: missing table value for extension", file_path); + return 1; + } + if(!properties) { + log_ereport(LOG_MISCONFIG, "pg: config %s: no properties configured for extension", file_path); + return 1; + } + + // check if the table was already specified + if(ucx_map_cstr_get(ext->table_lookup, table)) { + log_ereport(LOG_MISCONFIG, "pg: config %s: extension table %s not unique", file_path, table); + return 1; + } + // mark table as used + // tabname will be used later, so ist must be allocated in the pool + char *tabname = pool_strdup(pool, table); + if(!tabname) return 1; + + // exttable is only used temporarily + PgExtTable *exttable = malloc(sizeof(PgExtTable)); + if(!exttable) return 1; + exttable->table = tabname; + exttable->isused = 0; // not relevant in config + int tableindex = (int)ucx_list_size(ext->tables); + ext->tables = ucx_list_append(ext->tables, exttable); + + if(ucx_map_cstr_put(ext->table_lookup, table, table)) { + return 1; + } + + UCX_FOREACH(elm, properties) { + xmlNode *ps = elm->data; + const char *value = pg_util_xml_get_text(ps); + + PgPropertyStoreExt *ext_col = pool_malloc(pool, sizeof(PgPropertyStoreExt)); + if(!ext_col) { + return 1; + } + ext_col->tableindex = tableindex; + ext_col->ns = pool_strdup(pool, (const char*)ps->ns->href); + ext_col->name = pool_strdup(pool, (const char*)ps->name); + ext_col->column = pool_strdup(pool, (const char*)value); + if(!ext_col->ns || !ext_col->name || !ext_col->column) { + return 1; + } + + UcxKey key = webdav_property_key(ext_col->ns, ext_col->name); + int err = ucx_map_put(repo->prop_ext, key, ext_col); + free((void*)key.data); + if(err) { + return 1; + } + } + + return 0; +}