Use thread-safe libgcrypt and libgpg-error functions. Only call the new
[pwmd.git] / src / xml.c
blob0c3b67aa3ab19133b9097ad4416b8f1579d092a2
1 /* vim:tw=78:ts=8:sw=4:set ft=c: */
2 /*
3 Copyright (C) 2006-2009 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 02110-1301 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 "misc.h"
37 #include "xml.h"
39 void log_write(const gchar *fmt, ...);
40 static xmlNodePtr find_element(xmlNodePtr node, gchar *element, xmlNodePtr stop);
41 static xmlChar *node_has_attribute(xmlNodePtr n, xmlChar *attr);
44 * 'element' must be allocated.
46 gboolean is_literal_element(gchar **element)
48 gchar *p;
50 if (!element || !*element)
51 return FALSE;
53 if (*(*element) == '!') {
54 gchar *c;
56 for (p = *element, c = p+1; *c; c++)
57 *p++ = *c;
59 *p = 0;
60 return TRUE;
63 return FALSE;
67 * Fails if 'element' begins with punctuation or digit or contains whitespace.
69 * I'm not sure about using g_unichar_isspace() rather than isspace()?
71 gboolean valid_xml_element(xmlChar *element)
73 gunichar c;
74 glong len;
75 gchar *p = (gchar *)element;
77 if (!element || !*element)
78 return FALSE;
80 if (*p == '!')
81 p++;
83 len = g_utf8_strlen(p, -1) - 1;
84 c = g_utf8_get_char(p++);
86 if (g_unichar_ispunct(c) == TRUE || g_unichar_isdigit(c) == TRUE ||
87 g_unichar_isspace(c) == TRUE)
88 return FALSE;
90 while (*p && len--) {
91 c = g_utf8_get_char(p++);
93 if (g_unichar_isspace(c))
94 return FALSE;
97 return TRUE;
100 gboolean valid_element_path(gchar **path, gboolean has_value)
102 gchar **p;
104 for (p = path; *p; p++) {
106 * An empty element is valid and don't check the syntax of the
107 * content.
109 if (has_value == TRUE && (!*(p+1) || !*p[0]))
110 break;
112 if (valid_xml_element((xmlChar *)*p) == FALSE)
113 return FALSE;
116 return TRUE;
119 gpg_error_t new_account(xmlDocPtr doc, gchar *name)
121 xmlNodePtr root = xmlDocGetRootElement(doc);
122 xmlAttrPtr a;
123 xmlNodePtr n;
124 gchar *p = name;
126 if (!p || !root)
127 return EPWMD_LIBXML_ERROR;
129 if (*p == '!')
130 p++;
132 n = xmlNewNode(NULL, (xmlChar *)"account");
133 n = xmlAddChild(root, n);
134 a = xmlNewProp(n, (xmlChar *)"name", (xmlChar *)p);
135 return 0;
138 xmlChar *new_document()
140 xmlChar *buf;
141 const xmlChar *line = (xmlChar *)
142 "<?xml version=\"1.0\"?>"
143 "<!DOCTYPE accounts ["
144 "<!ELEMENT accounts (account*)>"
145 "<!ATTLIST account name CDATA #REQUIRED>"
146 "]>"
147 "<accounts/>";
149 buf = gcry_calloc(1, xmlStrlen(line) + 1);
150 return buf ? xmlStrcat(buf, line) : NULL;
154 * Lists root account element names. If there's a target attribute both
155 * literal and non-literal element names will be added.
157 gpg_error_t list_accounts(xmlDocPtr doc, GString **result)
159 xmlNodePtr n = NULL;
160 GSList *list = NULL;
161 gint total, i;
162 GString *string;
163 gpg_error_t rc = 0;
165 n = xmlDocGetRootElement(doc);
167 if (!n || !n->children)
168 return EPWMD_EMPTY_ELEMENT;
170 for (n = n->children; n; n = n->next) {
171 xmlAttrPtr a;
172 xmlChar *val, *target;
173 GSList *tlist;
174 gchar *tmp;
176 if (n->type != XML_ELEMENT_NODE)
177 continue;
179 a = xmlHasProp(n, (xmlChar *)"name");
181 if (!a || !a->children->content)
182 continue;
184 val = xmlNodeGetContent(a->children);
186 if (!val) {
187 rc = gpg_error_from_errno(ENOMEM);
188 goto fail;
191 tmp = g_strdup_printf("!%s", (gchar *)val);
193 if (!tmp) {
194 xmlFree(val);
195 rc = gpg_error_from_errno(ENOMEM);
196 goto fail;
199 tlist = g_slist_append(list, tmp);
201 if (!tlist) {
202 xmlFree(val);
203 rc = gpg_error_from_errno(ENOMEM);
204 goto fail;
207 list = tlist;
208 target = node_has_attribute(n, (xmlChar *)"target");
210 if (target) {
211 gchar *t = g_strdup((gchar *)val);
213 if (!t) {
214 xmlFree(val);
215 xmlFree(target);
216 rc = gpg_error_from_errno(ENOMEM);
217 goto fail;
220 tlist = g_slist_append(list, t);
222 if (!tlist) {
223 g_free(t);
224 xmlFree(target);
225 rc = gpg_error_from_errno(ENOMEM);
226 goto fail;
229 list = tlist;
232 xmlFree(val);
233 xmlFree(target);
236 total = g_slist_length(list);
238 if (!total)
239 return EPWMD_EMPTY_ELEMENT;
241 string = g_string_new(NULL);
243 if (!string) {
244 rc = gpg_error_from_errno(ENOMEM);
245 goto fail;
248 for (i = 0; i < total; i++) {
249 gchar *val = g_slist_nth_data(list, i);
251 g_string_append_printf(string, "%s\n", val);
254 string = g_string_truncate(string, string->len - 1);
255 *result = string;
257 fail:
258 total = g_slist_length(list);
260 for (i = 0; i < total; i++)
261 g_free(g_slist_nth_data(list, i));
263 g_slist_free(list);
264 return rc;
267 // FIXME return a gboolean in case of memory allocation failure
268 gchar **split_input_line(gchar *str, gchar *delim, gint n)
270 if (!str || !*str)
271 return NULL;
273 return g_strsplit(str, delim, n);
277 * Prevents a sibling element past the current element path with the same
278 * element name.
280 static xmlNodePtr find_stop_node(xmlNodePtr node)
282 xmlNodePtr n;
284 for (n = node->parent->children; n; n = n->next) {
285 if (n == node)
286 return n->next;
289 return NULL;
293 * Alot like create_elements_cb() but doesn't use the last element of 'req' as
294 * content but as an element.
296 xmlNodePtr create_target_elements_cb(xmlNodePtr node, gchar **path,
297 gpg_error_t *rc, void *data)
299 gint i;
300 char **req = path;
302 if (xmlStrEqual(node->name, (xmlChar *)*req))
303 req++;
305 for (i = 0; req[i]; i++) {
306 xmlNodePtr n;
308 if ((n = find_element(node, req[i], find_stop_node(node))) == NULL ||
309 (n && n->parent == node->parent)) {
310 is_literal_element(&req[i]);
311 n = xmlNewNode(NULL, (xmlChar *)req[i]);
313 if (!n) {
314 *rc = gpg_error_from_errno(ENOMEM);
315 return NULL;
318 node = xmlAddChild(node, n);
320 if (!node) {
321 *rc = gpg_error_from_errno(ENOMEM);
322 return NULL;
325 else
326 node = n;
329 return node;
332 xmlNodePtr find_text_node(xmlNodePtr node)
334 xmlNodePtr n = node;
336 if (n && n->type == XML_TEXT_NODE)
337 return n;
339 for (n = node; n; n = n->next) {
340 if (n->type == XML_TEXT_NODE)
341 return n;
344 return NULL;
347 xmlNodePtr create_elements_cb(xmlNodePtr node, gchar **elements,
348 gpg_error_t *rc, void *data)
350 gint i;
351 gchar **req = elements;
353 if (node->type == XML_TEXT_NODE)
354 node = node->parent;
356 if (node->name && xmlStrEqual(node->name, (xmlChar *)*req))
357 req++;
359 for (i = 0; req[i]; i++) {
360 xmlNodePtr n;
362 if (req[i+1]) {
364 * Strip the first '!' if needed. If there's another, it's an
365 * rc. The syntax has already been checked before calling this
366 * function.
368 is_literal_element(&req[i]);
372 * The value of the element tree.
374 if (!req[i+1]) {
375 n = find_text_node(node->children);
377 if (!n)
378 /* Use AddContent here to prevent overwriting any children. */
379 xmlNodeAddContent(node, (xmlChar *)req[i]);
380 else if (n && !*req[i])
381 xmlNodeSetContent(n, NULL);
382 else
383 xmlNodeSetContent(n, (xmlChar *)req[i]);
385 break;
388 n = find_element(node, req[i], find_stop_node(node));
391 * If the found element has the same parent as the current element,
392 * they are siblings and the new element needs to be created as a
393 * child of the current element (node).
395 if (n && n->parent == node->parent)
396 n = NULL;
398 if (!n) {
399 n = xmlNewNode(NULL, (xmlChar *)req[i]);
401 if (!n) {
402 *rc = gpg_error_from_errno(ENOMEM);
403 return NULL;
406 node = xmlAddChild(node, n);
408 if (!node) {
409 *rc = gpg_error_from_errno(ENOMEM);
410 return NULL;
413 else
414 node = n;
417 return node;
420 xmlNodePtr find_account(xmlDocPtr doc, gchar ***req, gpg_error_t *rc,
421 gboolean *target, gint recursion_depth)
423 xmlNodePtr n = xmlDocGetRootElement(doc);
424 gint depth = 0;
425 gchar *account = g_strdup(*req[0]);
426 gboolean literal = is_literal_element(&account);
428 if (!account) {
429 *rc = gpg_error_from_errno(ENOMEM);
430 return NULL;
433 *rc = 0;
434 recursion_depth++;
436 if (max_recursion_depth >= 1 && recursion_depth > max_recursion_depth) {
437 xmlChar *t = xmlGetNodePath(n);
439 log_write("%s: %s", pwmd_strerror(EPWMD_LOOP), t);
440 xmlFree(t);
441 *rc = EPWMD_LOOP;
442 return NULL;
445 while (n) {
446 if (n->type == XML_ELEMENT_NODE) {
447 if (depth == 0 && xmlStrEqual(n->name, (xmlChar *)"accounts")) {
448 n = n->children;
449 depth++;
450 continue;
453 if (depth == 1 && xmlStrEqual(n->name, (xmlChar *)"account")) {
454 xmlChar *content = node_has_attribute(n, (xmlChar *)"name");
456 if (!content)
457 continue;
459 if (xmlStrEqual(content, (xmlChar *)account)) {
460 gchar **nreq, **tmp = NULL;
462 if (literal == TRUE) {
463 xmlFree(content);
464 g_free(account);
465 return n;
468 xmlFree(content);
469 content = node_has_attribute(n, (xmlChar *)"target");
471 if (!content) {
472 g_free(account);
473 return n;
476 if (strchr((gchar *)content, '\t')) {
477 nreq = split_input_line((gchar *)content, "\t", 0);
478 xmlFree(content);
480 #if 0
482 * FIXME ENOMEM
484 if (!nreq) {
485 *rc = gpg_error_from_errno(ENOMEM);
486 return NULL;
488 #endif
490 tmp = *req;
491 tmp = strvcatv(nreq, tmp+1);
492 g_strfreev(nreq);
494 if (!tmp) {
495 *rc = gpg_error_from_errno(ENOMEM);
496 return NULL;
499 g_strfreev(*req);
500 *req = tmp;
502 else {
503 if (strv_printf(&tmp, "%s", content) == FALSE) {
504 xmlFree(content);
505 *rc = gpg_error_from_errno(ENOMEM);
506 return NULL;
509 xmlFree(content);
510 nreq = *req;
511 nreq = strvcatv(tmp, nreq+1);
512 g_strfreev(tmp);
514 if (!nreq) {
515 *rc = gpg_error_from_errno(ENOMEM);
516 return NULL;
519 g_strfreev(*req);
520 *req = nreq;
523 if (target)
524 *target = TRUE;
526 g_free(account);
527 n = find_account(doc, req, rc, target, recursion_depth);
528 return n;
533 n = n->next;
536 g_free(account);
537 *rc = EPWMD_ELEMENT_NOT_FOUND;
538 return NULL;
541 static xmlNodePtr find_element(xmlNodePtr node, gchar *element, xmlNodePtr stop)
543 xmlNodePtr n;
545 if (!node || !element)
546 return NULL;
548 for (n = node; n; n = n->next) {
549 if (n->type != XML_ELEMENT_NODE)
550 continue;
552 if (n == stop)
553 break;
555 if (xmlStrEqual(n->name, (xmlChar *)element))
556 return n;
559 return NULL;
562 static xmlChar *node_has_attribute(xmlNodePtr n, xmlChar *attr)
564 xmlAttrPtr a = xmlHasProp(n, attr);
566 if (!a)
567 return NULL;
569 if (!a->children || !a->children->content)
570 return NULL;
572 return xmlGetProp(n, attr);
575 static gboolean element_to_literal(gchar **element)
577 gchar *p = g_strdup_printf("!%s", *element);
579 if (!p)
580 return FALSE;
582 g_free(*element);
583 *element = p;
584 return TRUE;
587 xmlNodePtr find_elements(xmlDocPtr doc, xmlNodePtr node,
588 gchar **req, gpg_error_t *rc, gboolean *target,
589 xmlNodePtr (*found_fn)(xmlNodePtr, gchar **, gpg_error_t *, gchar **, void *),
590 xmlNodePtr (*not_found_fn)(xmlNodePtr, gchar **, gpg_error_t *, void *),
591 gboolean is_list_command, gint recursion_depth, void *data)
593 xmlNodePtr n, last, last_node;
594 gchar **p;
595 gint found = 0;
597 *rc = 0;
598 recursion_depth++;
600 if (max_recursion_depth >= 1 && recursion_depth > max_recursion_depth) {
601 xmlChar *t = xmlGetNodePath(node);
603 log_write("%s: %s", pwmd_strerror(EPWMD_LOOP), t);
604 xmlFree(t);
605 recursion_depth--;
606 *rc = EPWMD_LOOP;
607 return NULL;
610 for (last_node = last = n = node, p = req; *p; p++) {
611 xmlNodePtr tmp;
612 gchar *t = g_strdup(*p);
613 gboolean literal;
615 if (!t) {
616 *rc = gpg_error_from_errno(ENOMEM);
617 return NULL;
620 literal = is_literal_element(&t);
621 n = find_element(last, t, NULL);
622 g_free(t);
624 if (!n) {
625 if (not_found_fn)
626 return not_found_fn(found ? last_node : last_node->parent, p, rc, data);
628 *rc = EPWMD_ELEMENT_NOT_FOUND;
629 return NULL;
632 last = n->children;
633 last_node = n;
634 found = 1;
636 if (literal == FALSE) {
637 xmlChar *content = node_has_attribute(n, (xmlChar *)"target");
638 gchar **nreq = NULL, **nnreq;
640 if (!content) {
641 if (is_list_command == TRUE) {
642 if (element_to_literal(&(*p)) == FALSE) {
643 *rc = gpg_error_from_errno(ENOMEM);
644 return NULL;
648 continue;
651 if (strchr((gchar *)content, '\t') != NULL) {
652 if ((nreq = split_input_line((gchar *)content, "\t", 0)) == NULL) {
653 xmlFree(content);
654 *rc = EPWMD_INVALID_ELEMENT;
655 return NULL;
658 else {
659 if ((nreq = split_input_line((gchar *)content, " ", 0)) == NULL) {
660 xmlFree(content);
661 *rc = EPWMD_INVALID_ELEMENT;
662 return NULL;
666 xmlFree(content);
667 tmp = find_account(doc, &nreq, rc, target, 0);
669 if (!tmp) {
670 g_strfreev(nreq);
671 return NULL;
674 if (found_fn) {
675 found_fn(tmp, nreq, rc, p+1, data);
677 if (*rc) {
678 g_strfreev(nreq);
679 return NULL;
683 nnreq = strvcatv(nreq+1, p+1);
684 g_strfreev(nreq);
686 // FIXME ENOMEM
687 if (!nnreq || !*nnreq) {
688 if (nnreq)
689 g_strfreev(nnreq);
691 return tmp;
694 if (target)
695 *target = TRUE;
697 n = find_elements(doc, tmp->children, nnreq, rc, NULL, found_fn,
698 not_found_fn, is_list_command, recursion_depth, data);
700 if (*(p+1)) {
701 gchar **zz = p+1, **qq = nnreq;
703 if (g_strv_length(nnreq) > g_strv_length(p+1))
704 qq = nnreq+1;
706 for (; *qq && *zz; zz++) {
707 g_free(*zz);
708 *zz = g_strdup(*qq++);
710 if (!*zz) {
711 *rc = gpg_error_from_errno(ENOMEM);
712 n = NULL;
713 break;
718 g_strfreev(nnreq);
719 return n;
723 return n;
726 static gboolean update_element_list(struct element_list_s *elements)
728 gchar *line;
729 GSList *l;
731 if (!elements || !elements->elements)
732 return TRUE;
734 line = g_strjoinv("\t", elements->elements);
736 if (!line)
737 return FALSE;
739 g_strfreev(elements->elements);
740 elements->elements = NULL;
741 l = g_slist_append(elements->list, line);
743 if (!l)
744 return FALSE;
746 elements->list = l;
747 return TRUE;
750 static gpg_error_t path_list_recurse(xmlDocPtr doc, xmlNodePtr node,
751 struct element_list_s *elements)
753 gpg_error_t rc = 0;
754 xmlNodePtr n;
756 for (n = node; n; n = n->next) {
757 xmlChar *target = NULL;
759 if (n->type != XML_ELEMENT_NODE)
760 goto children;
762 if (strv_printf(&elements->elements, "%s\t!%s", elements->prefix, n->name) == FALSE)
763 return gpg_err_code_from_errno(ENOMEM);
765 if (update_element_list(elements) == FALSE)
766 return gpg_err_code_from_errno(ENOMEM);
768 target = node_has_attribute(n, (xmlChar *)"target");
770 if (target) {
771 gchar *tmp;
772 gchar *save = elements->prefix;
773 gboolean r = elements->resolving;
775 elements->depth++;
777 if (max_recursion_depth >= 1 && elements->depth > max_recursion_depth) {
778 xmlChar *t = xmlGetNodePath(n);
779 log_write("%s: %s", pwmd_strerror(EPWMD_LOOP), t);
780 xmlFree(t);
781 xmlFree(target);
782 return EPWMD_LOOP;
785 if (strv_printf(&elements->elements, "%s\t%s", elements->prefix, n->name) == FALSE) {
786 xmlFree(target);
787 return gpg_err_code_from_errno(ENOMEM);
790 tmp = g_strjoinv("\t", elements->elements);
792 if (!tmp) {
793 xmlFree(target);
794 return gpg_err_code_from_errno(ENOMEM);
797 if (update_element_list(elements) == FALSE) {
798 xmlFree(target);
799 return gpg_err_code_from_errno(ENOMEM);
802 elements->prefix = tmp;
803 elements->resolving = TRUE;
804 rc = create_path_list(doc, elements, (gchar *)target);
805 xmlFree(target);
806 elements->resolving = r;
807 elements->depth--;
808 g_free(tmp);
809 elements->prefix = save;
811 if (rc)
812 return rc;
815 children:
816 if (n->children) {
817 gchar *tmp = g_strdup_printf("%s\t!%s", elements->prefix, n->name);
818 gchar *save = elements->prefix;
820 if (!tmp)
821 return gpg_err_code_from_errno(ENOMEM);
823 elements->prefix = tmp;
824 rc = path_list_recurse(doc, n->children, elements);
825 g_free(elements->prefix);
826 elements->prefix = save;
828 if (rc)
829 return rc;
833 return rc;
837 * From the element path 'path', find sub-nodes and append them to the list.
839 gpg_error_t create_path_list(xmlDocPtr doc, struct element_list_s *elements,
840 gchar *path)
842 gpg_error_t rc;
843 gchar **req, **req_orig;
844 xmlNodePtr n;
845 gboolean a_target = FALSE;
847 req = split_input_line(path, "\t", 0);
849 if (!req) {
850 req = split_input_line(path, " ", 0);
852 if (!req)
853 return EPWMD_COMMAND_SYNTAX;
856 req_orig = g_strdupv(req);
858 if (!req_orig) {
859 rc = gpg_err_code_from_errno(ENOMEM);
860 goto fail;
863 n = find_account(doc, &req, &rc, &a_target, 0);
865 if (!n && rc == EPWMD_ELEMENT_NOT_FOUND && elements->resolving == TRUE) {
866 rc = 0;
867 goto fail;
869 else if (!n)
870 goto fail;
872 if (a_target == TRUE) {
873 g_free(*req);
874 *req = g_strdup(*req_orig);
877 if (*(req+1)) {
878 gboolean e_target = FALSE;
880 n = find_elements(doc, n->children, req+1, &rc, &e_target, NULL, NULL, TRUE, 0, NULL);
882 if (!n && rc == EPWMD_ELEMENT_NOT_FOUND && elements->resolving == TRUE) {
883 rc = 0;
884 goto fail;
886 else if (!n)
887 goto fail;
890 if (!elements->prefix) {
892 * FIXME
894 * If any req_orig element contains no target the element should be prefixed with
895 * the literal character. Not really crucial if the client isn't human
896 * because child elements are prefixed for the current path. But may
897 * be confusing if editing by hand.
899 elements->prefix = g_strjoinv("\t", req_orig);
901 if (!elements->prefix) {
902 rc = gpg_err_code_from_errno(ENOMEM);
903 goto fail;
906 if (strv_printf(&elements->elements, "%s", elements->prefix) == FALSE) {
907 rc = gpg_err_code_from_errno(ENOMEM);
908 goto fail;
911 if (update_element_list(elements) == FALSE) {
912 rc = gpg_err_code_from_errno(ENOMEM);
913 goto fail;
917 rc = path_list_recurse(doc, n->children, elements);
919 fail:
920 if (req_orig)
921 g_strfreev(req_orig);
923 g_strfreev(req);
924 return rc;
927 gpg_error_t recurse_xpath_nodeset(xmlDocPtr doc, xmlNodeSetPtr nodes,
928 xmlChar *value, xmlBufferPtr *result)
930 gint i = value ? nodes->nodeNr - 1 : 0;
931 xmlBufferPtr buf;
933 buf = xmlBufferCreate();
935 if (!buf)
936 return gpg_err_code_from_errno(ENOMEM);
938 for (; value ? i >= 0 : i < nodes->nodeNr; value ? i-- : i++) {
939 xmlNodePtr n = nodes->nodeTab[i];
941 if (!n)
942 continue;
944 if (!value) {
945 if (xmlNodeDump(buf, doc, n, 0, 0) == -1) {
946 *result = buf;
947 return EPWMD_LIBXML_ERROR;
950 continue;
953 xmlNodeSetContent(n, value);
956 *result = buf;
957 return 0;