UNIXworkcode

1 /* 2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. 3 * 4 * Copyright 2022 Olaf Wintermann. All rights reserved. 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions are met: 8 * 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 20 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 26 * POSSIBILITY OF SUCH DAMAGE. 27 */ 28 29 #include "config.h" 30 31 #include "../../util/util.h" 32 33 #include <libxml/tree.h> 34 35 #define xstreq(a,b) xmlStrEqual(BAD_CAST a, BAD_CAST b) 36 37 static int pg_load_ext_dav_config( 38 ServerConfiguration *cfg, 39 pool_handle_t *pool, 40 PgRepository *repo, 41 const char *file); 42 43 44 static const char *sql_get_repository_root = "select resource_id from Resource where parent_id is NULL and nodename = $1 ;"; 45 46 47 // Uses a PGconn to lookup the resource id of the specified root node 48 // if the lookup succeeds, the resource id is written to rootid 49 // in case of an error, an error message will be logged 50 // returns: 0 success, 1 error 51 int pg_lookup_root(ResourceData *res, const char *rootnode, int64_t *rootid) { 52 PGconn *connection = res->data; 53 54 PGresult *result = PQexecParams( 55 connection, 56 sql_get_repository_root, 57 1, // number of parameters 58 NULL, 59 &rootnode, // parameter value 60 NULL, 61 NULL, 62 0); // 0: result in text format 63 64 if(!result) { 65 log_ereport(LOG_FAILURE, "pg: root lookup failed: %s", PQerrorMessage(connection)); 66 return 1; 67 } 68 69 int ret = 0; 70 71 int nrows = PQntuples(result); 72 if(nrows == 1) { 73 char *resource_id_str = PQgetvalue(result, 0, 0); 74 if(resource_id_str) { 75 if(!util_strtoint(resource_id_str, rootid)) { 76 log_ereport(LOG_FAILURE, "pg: unexpected result for column resource_id", rootnode); 77 ret = 1; 78 } 79 } 80 } else { 81 log_ereport(LOG_FAILURE, "pg: cannot find root resource ''%s''", rootnode); 82 ret = 1; 83 } 84 PQclear(result); 85 86 return ret; 87 } 88 89 PgRepository* pg_init_repo(ServerConfiguration *cfg, pool_handle_t *pool, WSConfigNode *config) { 90 CxAllocator *a = pool_allocator(pool); 91 92 ConfigNode *pg = serverconfig_get_node(config, CONFIG_NODE_OBJECT, cx_str("Postgresql")); 93 if(!pg) { 94 log_ereport(LOG_MISCONFIG, "pg_init_repo: missing postgresql config object"); 95 return NULL; 96 } 97 98 cxstring cfg_respool = serverconfig_object_directive_value(pg, cx_str("ResourcePool")); 99 cxstring cfg_rootid = serverconfig_object_directive_value(pg, cx_str("RootId")); 100 cxstring cfg_rootnode = serverconfig_object_directive_value(pg, cx_str("RootNode")); 101 cxstring cfg_dav = serverconfig_object_directive_value(pg, cx_str("PGDavConfig")); 102 103 // minimum requirement is a resource pool 104 if(cfg_respool.length == 0) { 105 return NULL; 106 } 107 108 // check rootid 109 int64_t root_id = 1; 110 if(cfg_rootid.length > 0) { 111 if(!util_strtoint(cfg_rootid.ptr, &root_id)) { 112 log_ereport(LOG_MISCONFIG, "pg_init_repo: RootId parameter is not an integer: %s", cfg_rootid.ptr); 113 return NULL; 114 } 115 } 116 117 // warn if RootId and RootNode are specified 118 if(cfg_rootid.length > 0 && cfg_rootnode.length > 0) { 119 log_ereport(LOG_WARN, "log_init_repo: RootId and RootNode specified, RootNode ignored"); 120 } else if(cfg_rootnode.length > 0) { 121 // check root node 122 123 // resolve rootnode 124 // therefore we first need to get a connection from the resourcepool 125 ResourceData *res = resourcepool_cfg_lookup(cfg, cfg_respool.ptr, 0); 126 if(!res) { 127 log_ereport(LOG_MISCONFIG, "pg_init_repo: resource lookup failed"); 128 return NULL; 129 } 130 // do lookup, if successful, root_id will be set 131 int lookup_err = pg_lookup_root(res, cfg_rootnode.ptr, &root_id); 132 // we don't need the connection anymore 133 resourcepool_free(NULL, NULL, res); 134 if(lookup_err) { 135 // no logging required, pg_lookup_root will log the error 136 return NULL; 137 } 138 } 139 140 PgRepository *repo = pool_malloc(pool, sizeof(PgRepository)); 141 ZERO(repo, sizeof(PgRepository)); 142 143 repo->resourcepool = cx_strdup_a(a, cfg_respool); 144 repo->root_resource_id = root_id; 145 146 // check for extended pg dav config 147 if(cfg_dav.length > 0) { 148 // load extended config from config file 149 char *cfg_file_path = cfg_config_file_path(cfg_dav.ptr); 150 if(pg_load_ext_dav_config(cfg, pool, repo, cfg_file_path)) { 151 // error 152 repo = NULL; // no need to cleanup because everything is from the pool 153 } 154 free(cfg_file_path); 155 } 156 157 return repo; 158 } 159 160 static int pg_ext_get_config( 161 ServerConfiguration *cfg, 162 pool_handle_t *pool, 163 PgRepository *repo, 164 const char *file_path, 165 xmlNode *root); 166 static int pg_ext_get_extension( 167 PgExtParser *ext, 168 pool_handle_t *pool, 169 PgRepository *repo, 170 const char *file_path, 171 xmlNode *ext_node); 172 173 static const char* pg_util_xml_get_text(const xmlNode *elm) { 174 xmlNode *node = elm->children; 175 while(node) { 176 if(node->type == XML_TEXT_NODE) { 177 return (const char*)node->content; 178 } 179 node = node->next; 180 } 181 return NULL; 182 } 183 184 // load additional postgresql webdav config from the specified xml file 185 static int pg_load_ext_dav_config( 186 ServerConfiguration *cfg, 187 pool_handle_t *pool, 188 PgRepository *repo, 189 const char *file_path) 190 { 191 CxAllocator *a = pool_allocator(pool); 192 193 // check if the file exists and can be accessed 194 struct stat s; 195 if(stat(file_path, &s)) { 196 if(errno == ENOENT) { 197 log_ereport(LOG_FAILURE, "pg: config file %s not found", file_path); 198 } else { 199 log_ereport(LOG_FAILURE, "pg: cannot access config file %s", file_path); 200 } 201 202 return 1; 203 } 204 205 // prepare PgRepository 206 repo->prop_ext = cxHashMapCreate(a, CX_STORE_POINTERS, 8); 207 if(!repo->prop_ext) { 208 log_ereport(LOG_FAILURE, "pg: cannot load config file: OOM"); 209 return 1; 210 } 211 212 // load xml document 213 xmlDoc *doc = xmlReadFile(file_path, NULL, 0); 214 if(!doc) { 215 log_ereport(LOG_FAILURE, "pg: cannot load config file %s", file_path); 216 return 1; 217 } 218 219 // the root element must be <repository> 220 int ret = 0; 221 xmlNode *xml_root = xmlDocGetRootElement(doc); 222 if(xstreq(xml_root->name, "repository")) { 223 // parse config 224 ret = pg_ext_get_config(cfg, pool, repo, file_path, xml_root); 225 } else { 226 log_ereport(LOG_MISCONFIG, "pg: config %s: root element <repository> expected", file_path); 227 ret = 1; 228 } 229 xmlFreeDoc(doc); 230 231 return ret; 232 } 233 234 235 236 237 static int pg_ext_get_config( 238 ServerConfiguration *cfg, 239 pool_handle_t *pool, 240 PgRepository *repo, 241 const char *file_path, 242 xmlNode *root) 243 { 244 xmlNode *node = root->children; 245 int ret = 0; 246 247 PgExtParser parserData; 248 parserData.table_lookup = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 8); 249 parserData.tables = cxLinkedListCreate(cxDefaultAllocator, NULL, sizeof(PgExtTable)); 250 251 while(node && !ret) { 252 // currently, the only possible config element is <extension> 253 if(node->type == XML_ELEMENT_NODE) { 254 if(xstreq(node->name, "extension")) { 255 ret = pg_ext_get_extension(&parserData, pool, repo, file_path, node); 256 } 257 } 258 node = node->next; 259 } 260 261 // convert parserData 262 if(!ret) { 263 size_t ntables = parserData.tables->size; 264 repo->ntables = ntables; 265 repo->tables = pool_calloc(pool, ntables, sizeof(PgExtTable)); 266 if(repo->tables) { 267 int i = 0; 268 CxIterator iter = cxListIterator(parserData.tables); 269 cx_foreach(PgExtTable *, tab, iter) { 270 repo->tables[i++] = *tab; 271 } 272 } else { 273 ret = 1; 274 } 275 276 } 277 278 // cleanup parser 279 cxListDestroy(parserData.tables); 280 cxMapDestroy(parserData.table_lookup); 281 282 return ret; 283 } 284 285 static int pg_ext_get_extension( 286 PgExtParser *ext, 287 pool_handle_t *pool, 288 PgRepository *repo, 289 const char *file_path, 290 xmlNode *ext_node) 291 { 292 CxAllocator *a = pool_allocator(pool); 293 294 xmlNode *node = ext_node->children; 295 296 const char *table = NULL; 297 CxList *properties = cxLinkedListCreate(a, NULL, CX_STORE_POINTERS); 298 299 while(node) { 300 if(node->type == XML_ELEMENT_NODE) { 301 if(xstreq(node->name, "table")) { 302 const char *value = pg_util_xml_get_text(node); 303 if(!value) { 304 log_ereport(LOG_MISCONFIG, "pg: config %s: table: missing value", file_path, table); 305 return 1; 306 } else if(table) { 307 log_ereport(LOG_MISCONFIG, "pg: config %s: table %s already set", file_path, table); 308 return 1; 309 } 310 table = value; 311 } else if(xstreq(node->name, "properties")) { 312 // add all child elements to the properties list 313 xmlNode *ps = node->children; 314 while(ps) { 315 if(ps->type == XML_ELEMENT_NODE) { 316 // validate 317 // required: namespace, value 318 if(!ps->ns || !ps->ns->href) { 319 log_ereport(LOG_MISCONFIG, "pg: config %s: property %s: missing namespace", file_path, ps->name); 320 return 1; 321 } 322 const char *value = pg_util_xml_get_text(ps); 323 if(!value) { 324 log_ereport(LOG_MISCONFIG, "pg: config %s: no column specified for property %s", file_path, ps->name); 325 return 1; 326 } 327 cxListAdd(properties, ps); 328 } 329 ps = ps->next; 330 } 331 } 332 } 333 node = node->next; 334 } 335 336 // check if anything is missing 337 if(!table) { 338 log_ereport(LOG_MISCONFIG, "pg: config %s: missing table value for extension", file_path); 339 return 1; 340 } 341 if(!properties) { 342 log_ereport(LOG_MISCONFIG, "pg: config %s: no properties configured for extension", file_path); 343 return 1; 344 } 345 346 // check if the table was already specified 347 if(cxMapGet(ext->table_lookup, cx_hash_key_str(table))) { 348 log_ereport(LOG_MISCONFIG, "pg: config %s: extension table %s not unique", file_path, table); 349 return 1; 350 } 351 // mark table as used 352 // tabname will be used later, so ist must be allocated in the pool 353 char *tabname = pool_strdup(pool, table); 354 if(!tabname) return 1; 355 356 // exttable is only used temporarily 357 PgExtTable exttable; 358 exttable.table = tabname; 359 exttable.isused = 0; // not relevant in config 360 int tableindex = (int)ext->tables->size; 361 cxListAdd(ext->tables, &exttable); 362 363 if(cxMapPut(ext->table_lookup, cx_hash_key_str(table), (void*)table)) { 364 return 1; 365 } 366 367 CxIterator iter = cxListIterator(properties); 368 cx_foreach(xmlNode *, ps, iter) { 369 const char *value = pg_util_xml_get_text(ps); 370 371 PgPropertyStoreExt *ext_col = pool_malloc(pool, sizeof(PgPropertyStoreExt)); 372 if(!ext_col) { 373 return 1; 374 } 375 ext_col->tableindex = tableindex; 376 ext_col->ns = pool_strdup(pool, (const char*)ps->ns->href); 377 ext_col->name = pool_strdup(pool, (const char*)ps->name); 378 ext_col->column = pool_strdup(pool, (const char*)value); 379 if(!ext_col->ns || !ext_col->name || !ext_col->column) { 380 return 1; 381 } 382 383 CxHashKey key = webdav_property_key(ext_col->ns, ext_col->name); 384 int err = cxMapPut(repo->prop_ext, key, ext_col); 385 free((void*)key.data); 386 if(err) { 387 return 1; 388 } 389 } 390 391 return 0; 392 } 393