Fix for commit e2067d9 and sending SIGUSR1.
[pwmd.git] / src / xml.c
blob37daf8100bbf61bbcbb3cdb78ca2f686e35d116c
1 /* vim:tw=78:ts=8:sw=4:set ft=c: */
2 /*
3 Copyright (C) 2006-2007 Ben Kibbey <bjk@luxsci.net>
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <unistd.h>
22 #include <err.h>
23 #include <errno.h>
24 #include <string.h>
25 #include <sys/stat.h>
26 #include <fcntl.h>
27 #include <ctype.h>
28 #include <glib.h>
29 #include <gcrypt.h>
31 #ifdef HAVE_CONFIG_H
32 #include <config.h>
33 #endif
35 #include "pwmd_error.h"
36 #include "xml.h"
37 #include "common.h"
39 void log_write(const gchar *fmt, ...);
40 gboolean strv_printf(gchar ***array, const gchar *fmt, ...);
42 gboolean is_literal_element_str(gchar *element)
44 if (!element || !*element)
45 return FALSE;
47 return *element == '!' ? TRUE : FALSE;
51 * 'element' must be allocated.
53 gboolean is_literal_element(gchar **element)
55 gchar *p;
57 if (!element || !*element)
58 return FALSE;
60 if (*(*element) == '!') {
61 gchar *c;
63 for (p = *element, c = p+1; *c; c++)
64 *p++ = *c;
66 *p = 0;
67 return TRUE;
70 return FALSE;
74 * Fails if 'element' begins with punctuation or digit or contains whitespace.
75 * If the element is a literal element which was prefixed with '!', it should
76 * be trimmed before calling this function.
78 * I'm not sure about using g_unichar_isspace() rather than isspace()?
80 gboolean valid_xml_element(xmlChar *element)
82 gunichar c;
83 glong len;
84 gchar *p = (gchar *)element;
86 if (!element || !*element)
87 return FALSE;
89 len = g_utf8_strlen(p, -1) - 1;
90 c = g_utf8_get_char(p++);
92 if (g_unichar_ispunct(c) == TRUE || g_unichar_isdigit(c) == TRUE)
93 return FALSE;
95 while (*p && len--) {
96 c = g_utf8_get_char(p++);
98 if (g_unichar_isspace(c))
99 return FALSE;
102 return TRUE;
105 gpg_error_t new_account(xmlDocPtr doc, gchar *name)
107 xmlNodePtr root = xmlDocGetRootElement(doc);
108 xmlAttrPtr a;
109 xmlNodePtr n;
110 gchar *p = name;
112 if (!p || !root)
113 return EPWMD_LIBXML_ERROR;
115 if (is_literal_element_str(p)) {
116 p++;
119 * Event though the account name is an attribute and not an element,
120 * element syntax still applies.
122 if (valid_xml_element((xmlChar *)p) == FALSE)
123 return EPWMD_INVALID_ELEMENT;
126 n = xmlNewNode(NULL, (xmlChar *)"account");
127 n = xmlAddChild(root, n);
128 a = xmlNewProp(n, (xmlChar *)"name", (xmlChar *)p);
129 return 0;
132 xmlChar *new_document()
134 xmlChar *buf;
135 const xmlChar *line = (xmlChar *)
136 "<?xml version=\"1.0\"?>\n"
137 "<!DOCTYPE accounts [\n"
138 "<!ELEMENT accounts (account*)>\n"
139 "<!ATTLIST account name CDATA #REQUIRED>\n"
140 "]>\n"
141 "<accounts/>";
143 buf = gcry_calloc(1, xmlStrlen(line) + 1);
144 return buf ? xmlStrcat(buf, line) : NULL;
148 * The "target" attribute is ignored here.
150 gpg_error_t list_accounts(xmlDocPtr doc, GString **result)
152 xmlNodePtr n = NULL, t;
153 GSList *list = NULL;
154 gint total, i;
155 GString *string;
157 for (t = doc->children; t; t = t->next) {
158 if (t->type == XML_ELEMENT_NODE) {
159 if (xmlStrEqual(t->name, (xmlChar *)"accounts")) {
160 n = t;
161 break;
166 if (!n || !n->children)
167 return EPWMD_EMPTY_ELEMENT;
169 for (n = n->children; n; n = n->next) {
170 xmlAttrPtr a;
171 gchar *tmp;
172 GSList *tlist;
174 if (n->type != XML_ELEMENT_NODE)
175 continue;
177 a = xmlHasProp(n, (xmlChar *)"name");
179 if (!a || !a->children->content)
180 continue;
182 tmp = g_strdup((gchar *)a->children->content);
184 if (!tmp) {
185 g_slist_free(list);
186 return gpg_error_from_errno(ENOMEM);
189 tlist = g_slist_append(list, tmp);
191 if (!tlist) {
192 g_slist_free(list);
193 return gpg_error_from_errno(ENOMEM);
196 list = tlist;
199 total = g_slist_length(list);
201 if (!total)
202 return EPWMD_EMPTY_ELEMENT;
204 string = g_string_new(NULL);
206 if (!string) {
207 g_slist_free(list);
208 return gpg_error_from_errno(ENOMEM);
211 for (i = 0; i < total; i++) {
212 gchar *tmp = g_slist_nth_data(list, i);
214 g_string_append_printf(string, "%s\n", tmp);
215 g_free(tmp);
218 string = g_string_truncate(string, string->len - 1);
219 g_slist_free(list);
220 *result = string;
221 return 0;
224 gchar **split_input_line(gchar *str, gchar *delim, gint n)
226 if (!str || !*str)
227 return NULL;
229 return g_strsplit(str, delim, n);
232 static gchar **append_element_path(gchar **dst, gchar **src)
234 gchar **p;
235 gint i;
236 gchar **d;
238 if (!src)
239 return NULL;
241 d = g_strdupv(dst);
243 if (!d)
244 return NULL;
246 i = g_strv_length(d);
248 for (p = src; *p; p++) {
249 gchar **pa;
251 pa = g_realloc(d, (i + 2) * sizeof(gchar *));
253 if (!pa) {
254 g_strfreev(d);
255 return NULL;
258 d = pa;
259 d[i] = g_strdup(*p);
261 if (!d[i]) {
262 g_strfreev(d);
263 return NULL;
266 d[++i] = NULL;
269 return d;
273 * Alot like create_elements_cb() but doesn't use the last element of 'req' as
274 * content but as an element.
276 xmlNodePtr create_target_elements_cb(xmlNodePtr node, gchar **path,
277 gpg_error_t *error, void *data)
279 gint i;
280 char **req = path;
282 if (xmlStrEqual(node->name, (xmlChar *)*req))
283 req++;
285 for (i = 0; req[i]; i++) {
286 xmlNodePtr n;
288 if (valid_xml_element((xmlChar *)req[i]) == FALSE) {
289 *error = EPWMD_INVALID_ELEMENT;
290 return NULL;
293 if ((n = find_element(node, req[i])) == NULL) {
294 n = xmlNewNode(NULL, (xmlChar *)req[i]);
296 if (!n) {
297 *error = gpg_error_from_errno(ENOMEM);
298 return NULL;
301 node = xmlAddChild(node, n);
303 if (!node) {
304 *error = gpg_error_from_errno(ENOMEM);
305 return NULL;
308 else
309 node = n;
312 return node;
315 xmlNodePtr create_elements_cb(xmlNodePtr node, gchar **elements,
316 gpg_error_t *error, void *data)
318 gint i;
319 gchar **req = elements;
321 if (node->type == XML_TEXT_NODE)
322 node = node->parent;
324 if (node->name && xmlStrEqual(node->name, (xmlChar *)*req))
325 req++;
327 for (i = 0; req[i]; i++) {
328 xmlNodePtr n;
330 if (req[i+1]) {
332 * Strip the first '!' if needed. If there's another, it's an
333 * error.
335 is_literal_element(&req[i]);
337 if (valid_xml_element((xmlChar *)req[i]) == FALSE) {
338 *error = EPWMD_INVALID_ELEMENT;
339 return NULL;
344 * The value of the element tree.
346 if (!req[i+1]) {
347 if (node->children) {
348 n = node->children->next ? xmlCopyNode(node->children->next, 1) :
349 xmlCopyNode(node->children, 1);
351 if (!n) {
352 *error = gpg_error_from_errno(ENOMEM);
353 return NULL;
356 if (n->content) {
357 xmlFree(n->content);
358 n->content = NULL;
361 else
362 n = NULL;
364 if (req[i])
365 xmlNodeSetContent(node, (xmlChar *)req[i]);
366 else if (node->content) {
367 xmlFree(node->content);
368 node->content = NULL;
371 if (n) {
372 if (!xmlAddChild(node, n)) {
373 *error = gpg_error_from_errno(ENOMEM);
374 return NULL;
378 break;
381 n = find_element(node, req[i]);
383 if (!n) {
384 n = xmlNewNode(NULL, (xmlChar *)req[i]);
386 if (!n) {
387 *error = gpg_error_from_errno(ENOMEM);
388 return NULL;
391 node = xmlAddChild(node, n);
393 if (!node) {
394 *error = gpg_error_from_errno(ENOMEM);
395 return NULL;
398 else
399 node = n;
402 return node;
405 xmlNodePtr find_account(xmlDocPtr doc, gchar ***req, gpg_error_t *error,
406 gboolean *target)
408 xmlNodePtr n;
409 gint depth = 0;
410 gchar *account = g_strdup(*req[0]);
411 gboolean literal = is_literal_element(&account);
412 static int recursion_depth;
414 if (!account) {
415 *error = gpg_error_from_errno(ENOMEM);
416 return NULL;
419 *error = 0;
420 recursion_depth++;
422 if (max_recursion_depth >= 1 && recursion_depth > max_recursion_depth) {
423 recursion_depth--;
424 *error = EPWMD_LOOP;
425 return NULL;
428 for (n = doc->children; n;) {
429 if (n->type == XML_ELEMENT_NODE) {
430 if (depth == 0 && xmlStrEqual(n->name, (xmlChar *)"accounts")) {
431 n = n->children;
432 depth++;
433 continue;
436 if (depth == 1 && xmlStrEqual(n->name, (xmlChar *)"account")) {
437 xmlChar *content = node_has_attribute(n, (xmlChar *)"name");
439 if (!content)
440 continue;
442 if (xmlStrEqual(content, (xmlChar *)account)) {
443 gchar **nreq, **tmp = NULL;
445 if (literal == TRUE) {
446 g_free(account);
447 recursion_depth--;
448 return n;
451 content = node_has_attribute(n, (xmlChar *)"target");
453 if (!content) {
454 g_free(account);
455 recursion_depth--;
456 return n;
459 if (strchr((gchar *)content, '\t')) {
460 nreq = split_input_line((gchar *)content, "\t", 0);
462 #if 0
464 * FIXME ENOMEM
466 if (!nreq) {
467 *error = gpg_error_from_errno(ENOMEM);
468 return NULL;
470 #endif
472 tmp = *req;
473 tmp = append_element_path(nreq, tmp+1);
474 g_strfreev(nreq);
476 if (!tmp) {
477 *error = gpg_error_from_errno(ENOMEM);
478 return NULL;
481 g_strfreev(*req);
482 *req = tmp;
484 else {
485 if (strv_printf(&tmp, "%s", content) == FALSE) {
486 *error = gpg_error_from_errno(ENOMEM);
487 return NULL;
490 nreq = *req;
491 nreq = append_element_path(tmp, nreq+1);
492 g_strfreev(tmp);
494 if (!nreq) {
495 *error = gpg_error_from_errno(ENOMEM);
496 return NULL;
499 g_strfreev(*req);
500 *req = nreq;
503 if (target)
504 *target = TRUE;
506 g_free(account);
507 n = find_account(doc, req, error, target);
508 recursion_depth--;
509 return n;
514 n = n->next;
517 g_free(account);
518 *error = EPWMD_ELEMENT_NOT_FOUND;
519 recursion_depth--;
520 return NULL;
523 xmlNodePtr find_sibling(xmlNodePtr node, gchar *element)
525 xmlNodePtr n;
527 if (!node || !element)
528 return NULL;
530 for (n = node; n; n = n->next) {
531 if (n->type != XML_ELEMENT_NODE)
532 continue;
534 if (xmlStrEqual(n->name, (xmlChar *)element))
535 return n;
538 return NULL;
541 xmlNodePtr find_element(xmlNodePtr node, gchar *element)
543 if (!node || !element)
544 return NULL;
546 return find_sibling(node, element);
549 xmlChar *node_has_attribute(xmlNodePtr n, xmlChar *attr)
551 xmlAttrPtr a = xmlHasProp(n, attr);
553 if (!a)
554 return NULL;
556 if (!a->children || !a->children->content)
557 return NULL;
559 return a->children->content;
562 xmlNodePtr find_elements(xmlDocPtr doc, xmlNodePtr node,
563 gchar **req, gpg_error_t *error, gboolean *target,
564 xmlNodePtr (*found_fn)(xmlNodePtr, gchar **, gpg_error_t *, void *),
565 xmlNodePtr (*not_found_fn)(xmlNodePtr, gchar **, gpg_error_t *, void *),
566 void *data)
568 xmlNodePtr n, last, last_node;
569 gchar **p;
570 gint found = 0;
571 static int recursion_depth;
573 *error = 0;
574 recursion_depth++;
576 if (max_recursion_depth >= 1 && recursion_depth > max_recursion_depth) {
577 recursion_depth--;
578 *error = EPWMD_LOOP;
579 return NULL;
582 for (last_node = last = n = node, p = req; *p; p++) {
583 xmlNodePtr tmp;
584 gchar *t = g_strdup(*p);
585 gboolean literal;
587 if (!t) {
588 *error = gpg_error_from_errno(ENOMEM);
589 recursion_depth--;
590 return NULL;
593 literal = is_literal_element(&t);
594 n = find_element(last, t);
595 g_free(t);
597 if (!n) {
598 recursion_depth--;
600 if (not_found_fn)
601 return not_found_fn(found ? last_node : last_node->parent, p, error, data);
603 *error = EPWMD_ELEMENT_NOT_FOUND;
604 return NULL;
607 last = n->children;
608 last_node = n;
609 found = 1;
611 if (literal == FALSE) {
612 xmlChar *content = node_has_attribute(n, (xmlChar *)"target");
613 gchar **nreq = NULL, **nnreq;
615 if (!content)
616 continue;
618 if (strchr((gchar *)content, '\t') != NULL) {
619 if ((nreq = split_input_line((gchar *)content, "\t", 0)) == NULL) {
620 *error = EPWMD_INVALID_ELEMENT;
621 recursion_depth--;
622 return NULL;
625 else {
626 if ((nreq = split_input_line((gchar *)content, " ", 0)) == NULL) {
627 *error = EPWMD_INVALID_ELEMENT;
628 recursion_depth--;
629 return NULL;
633 tmp = find_account(doc, &nreq, error, target);
635 if (!tmp) {
636 g_strfreev(nreq);
637 recursion_depth--;
638 return NULL;
641 if (found_fn)
642 found_fn(n, nreq, error, data);
644 nnreq = append_element_path(nreq+1, p+1);
645 g_strfreev(nreq);
647 // FIXME ENOMEM
648 if (!nnreq || !*nnreq) {
649 if (nnreq)
650 g_strfreev(nnreq);
652 recursion_depth--;
653 return tmp;
656 if (target)
657 *target = TRUE;
659 n = find_elements(doc, tmp->children, nnreq, error, NULL, found_fn,
660 not_found_fn, data);
662 if (*(p+1)) {
663 gchar **zz = p+1, **qq = nnreq;
665 if (g_strv_length(nnreq) > g_strv_length(p+1))
666 qq = nnreq+1;
668 for (; *qq && *zz; zz++) {
669 g_free(*zz);
670 *zz = g_strdup(*qq++);
672 if (!*zz) {
673 *error = gpg_error_from_errno(ENOMEM);
674 n = NULL;
675 break;
680 g_strfreev(nnreq);
681 recursion_depth--;
682 return n;
686 recursion_depth--;
687 return n;
690 gboolean node_has_child_element(xmlNodePtr node)
692 xmlNodePtr n;
694 if (!node)
695 return FALSE;
697 for (n = node; n; n = n->next) {
698 if (n->type == XML_ELEMENT_NODE)
699 return TRUE;
701 if (n->children)
702 return node_has_child_element(n->children);
705 return FALSE;