|
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 UcxAllocator a = util_pool_allocator(pool); |
|
91 |
|
92 ConfigNode *pg = serverconfig_get_node(config, CONFIG_NODE_OBJECT, SC("Postgresql")); |
|
93 if(!pg) { |
|
94 log_ereport(LOG_MISCONFIG, "pg_init_repo: missing postgresql config object"); |
|
95 return NULL; |
|
96 } |
|
97 |
|
98 scstr_t cfg_respool = serverconfig_directive_value(pg, SC("ResourcePool")); |
|
99 scstr_t cfg_rootid = serverconfig_directive_value(pg, SC("RootId")); |
|
100 scstr_t cfg_rootnode = serverconfig_directive_value(pg, SC("RootNode")); |
|
101 scstr_t cfg_dav = serverconfig_directive_value(pg, SC("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 = sstrdup_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 UcxAllocator a = util_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 = ucx_map_new_a(&a, 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 = ucx_map_new(8); |
|
249 parserData.tables = NULL; |
|
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 = ucx_list_size(parserData.tables); |
|
264 repo->ntables = ntables; |
|
265 repo->tables = pool_calloc(pool, ntables, sizeof(PgExtTable)); |
|
266 if(repo->tables) { |
|
267 int i = 0; |
|
268 UCX_FOREACH(elm, parserData.tables) { |
|
269 PgExtTable *tab = elm->data; |
|
270 repo->tables[i++] = *tab; |
|
271 } |
|
272 } else { |
|
273 ret = 1; |
|
274 } |
|
275 |
|
276 } |
|
277 |
|
278 // cleanup parser |
|
279 ucx_list_free_content(parserData.tables, free); |
|
280 ucx_list_free(parserData.tables); |
|
281 ucx_map_free(parserData.table_lookup); |
|
282 |
|
283 return ret; |
|
284 } |
|
285 |
|
286 static int pg_ext_get_extension( |
|
287 PgExtParser *ext, |
|
288 pool_handle_t *pool, |
|
289 PgRepository *repo, |
|
290 const char *file_path, |
|
291 xmlNode *ext_node) |
|
292 { |
|
293 UcxAllocator a = util_pool_allocator(pool); |
|
294 |
|
295 xmlNode *node = ext_node->children; |
|
296 |
|
297 const char *table = NULL; |
|
298 UcxList *properties = NULL; |
|
299 |
|
300 while(node) { |
|
301 if(node->type == XML_ELEMENT_NODE) { |
|
302 if(xstreq(node->name, "table")) { |
|
303 const char *value = pg_util_xml_get_text(node); |
|
304 if(!value) { |
|
305 log_ereport(LOG_MISCONFIG, "pg: config %s: table: missing value", file_path, table); |
|
306 return 1; |
|
307 } else if(table) { |
|
308 log_ereport(LOG_MISCONFIG, "pg: config %s: table %s already set", file_path, table); |
|
309 return 1; |
|
310 } |
|
311 table = value; |
|
312 } else if(xstreq(node->name, "properties")) { |
|
313 // add all child elements to the properties list |
|
314 xmlNode *ps = node->children; |
|
315 while(ps) { |
|
316 if(ps->type == XML_ELEMENT_NODE) { |
|
317 // validate |
|
318 // required: namespace, value |
|
319 if(!ps->ns || !ps->ns->href) { |
|
320 log_ereport(LOG_MISCONFIG, "pg: config %s: property %s: missing namespace", file_path, ps->name); |
|
321 return 1; |
|
322 } |
|
323 const char *value = pg_util_xml_get_text(ps); |
|
324 if(!value) { |
|
325 log_ereport(LOG_MISCONFIG, "pg: config %s: no column specified for property %s", file_path, ps->name); |
|
326 return 1; |
|
327 } |
|
328 properties = ucx_list_append_a(&a, properties, ps); |
|
329 } |
|
330 ps = ps->next; |
|
331 } |
|
332 } |
|
333 } |
|
334 node = node->next; |
|
335 } |
|
336 |
|
337 // check if anything is missing |
|
338 if(!table) { |
|
339 log_ereport(LOG_MISCONFIG, "pg: config %s: missing table value for extension", file_path); |
|
340 return 1; |
|
341 } |
|
342 if(!properties) { |
|
343 log_ereport(LOG_MISCONFIG, "pg: config %s: no properties configured for extension", file_path); |
|
344 return 1; |
|
345 } |
|
346 |
|
347 // check if the table was already specified |
|
348 if(ucx_map_cstr_get(ext->table_lookup, table)) { |
|
349 log_ereport(LOG_MISCONFIG, "pg: config %s: extension table %s not unique", file_path, table); |
|
350 return 1; |
|
351 } |
|
352 // mark table as used |
|
353 // tabname will be used later, so ist must be allocated in the pool |
|
354 char *tabname = pool_strdup(pool, table); |
|
355 if(!tabname) return 1; |
|
356 |
|
357 // exttable is only used temporarily |
|
358 PgExtTable *exttable = malloc(sizeof(PgExtTable)); |
|
359 if(!exttable) return 1; |
|
360 exttable->table = tabname; |
|
361 exttable->isused = 0; // not relevant in config |
|
362 int tableindex = (int)ucx_list_size(ext->tables); |
|
363 ext->tables = ucx_list_append(ext->tables, exttable); |
|
364 |
|
365 if(ucx_map_cstr_put(ext->table_lookup, table, table)) { |
|
366 return 1; |
|
367 } |
|
368 |
|
369 UCX_FOREACH(elm, properties) { |
|
370 xmlNode *ps = elm->data; |
|
371 const char *value = pg_util_xml_get_text(ps); |
|
372 |
|
373 PgPropertyStoreExt *ext_col = pool_malloc(pool, sizeof(PgPropertyStoreExt)); |
|
374 if(!ext_col) { |
|
375 return 1; |
|
376 } |
|
377 ext_col->tableindex = tableindex; |
|
378 ext_col->ns = pool_strdup(pool, (const char*)ps->ns->href); |
|
379 ext_col->name = pool_strdup(pool, (const char*)ps->name); |
|
380 ext_col->column = pool_strdup(pool, (const char*)value); |
|
381 if(!ext_col->ns || !ext_col->name || !ext_col->column) { |
|
382 return 1; |
|
383 } |
|
384 |
|
385 UcxKey key = webdav_property_key(ext_col->ns, ext_col->name); |
|
386 int err = ucx_map_put(repo->prop_ext, key, ext_col); |
|
387 free((void*)key.data); |
|
388 if(err) { |
|
389 return 1; |
|
390 } |
|
391 } |
|
392 |
|
393 return 0; |
|
394 } |