1 /* vim:tw=78:ts=8:sw=4:set ft=c: */
3 Copyright (C) 2006-2008 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
30 #include <libxml/xmlwriter.h>
36 #include "pwmd_error.h"
40 void log_write(const gchar
*fmt
, ...);
41 static xmlNodePtr
find_element(xmlNodePtr node
, gchar
*element
, xmlNodePtr stop
);
42 static xmlChar
*node_has_attribute(xmlNodePtr n
, xmlChar
*attr
);
45 * 'element' must be allocated.
47 gboolean
is_literal_element(gchar
**element
)
51 if (!element
|| !*element
)
54 if (*(*element
) == '!') {
57 for (p
= *element
, c
= p
+1; *c
; c
++)
68 * Fails if 'element' begins with punctuation or digit or contains whitespace.
70 * I'm not sure about using g_unichar_isspace() rather than isspace()?
72 gboolean
valid_xml_element(xmlChar
*element
)
76 gchar
*p
= (gchar
*)element
;
78 if (!element
|| !*element
)
84 len
= g_utf8_strlen(p
, -1) - 1;
85 c
= g_utf8_get_char(p
++);
87 if (g_unichar_ispunct(c
) == TRUE
|| g_unichar_isdigit(c
) == TRUE
||
88 g_unichar_isspace(c
) == TRUE
)
92 c
= g_utf8_get_char(p
++);
94 if (g_unichar_isspace(c
))
101 gboolean
valid_element_path(gchar
**path
, gboolean has_value
)
105 for (p
= path
; *p
; p
++) {
107 * An empty element is valid and don't check the syntax of the
110 if (has_value
== TRUE
&& (!*(p
+1) || !*p
[0]))
113 if (valid_xml_element((xmlChar
*)*p
) == FALSE
)
120 gpg_error_t
new_account(xmlDocPtr doc
, gchar
*name
)
122 xmlNodePtr root
= xmlDocGetRootElement(doc
);
128 return EPWMD_LIBXML_ERROR
;
133 n
= xmlNewNode(NULL
, (xmlChar
*)"root");
134 n
= xmlAddChild(root
, n
);
135 a
= xmlNewProp(n
, (xmlChar
*)"name", (xmlChar
*)p
);
139 xmlDocPtr
create_dtd(xmlDocPtr doc
)
143 gboolean is_new
= TRUE
;
146 n
= xmlDocGetRootElement(doc
);
151 wr
= xmlNewTextWriterTree(doc
, n
, 0);
156 wr
= xmlNewTextWriterDoc(&doc
, 0);
163 if (xmlTextWriterStartDTD(wr
, (xmlChar
*)"pwmd", NULL
, NULL
) == -1)
166 if (xmlTextWriterWriteDTDElement(wr
, (xmlChar
*)"pwmd",
167 (xmlChar
*)"(root)") == -1)
170 xmlTextWriterEndDTDElement(wr
);
172 if (xmlTextWriterWriteDTDAttlist(wr
, (xmlChar
*)"root",
173 (xmlChar
*)"name CDATA #REQUIRED") == -1)
176 xmlTextWriterEndDTDAttlist(wr
);
177 xmlTextWriterEndDTD(wr
);
180 xmlTextWriterStartElement(wr
, (xmlChar
*)"pwmd");
181 xmlTextWriterEndElement(wr
);
184 xmlTextWriterEndDocument(wr
);
185 xmlFreeTextWriter(wr
);
189 xmlTextWriterEndDocument(wr
);
190 xmlFreeTextWriter(wr
);
194 xmlChar
*new_document()
198 xmlDocPtr doc
= create_dtd(NULL
);
204 /* The memory allocators set in main() all use xmalloc,xfree,etc. When
205 * MEM_DEBUG is set, a segfault will occur do to an invalid free if this
206 * code isn't included.
212 xmlDocDumpMemory(doc
, &tmp
, &tmp_len
);
213 xml
= gcry_calloc(1, tmp_len
+1);
220 memcpy(xml
, tmp
, tmp_len
);
224 xmlDocDumpMemory(doc
, &xml
, &len
);
231 * Lists root account element names. If there's a target attribute both
232 * literal and non-literal element names will be added.
234 gpg_error_t
list_accounts(xmlDocPtr doc
, GString
**result
)
242 n
= xmlDocGetRootElement(doc
);
244 if (!n
|| !n
->children
)
245 return EPWMD_EMPTY_ELEMENT
;
247 for (n
= n
->children
; n
; n
= n
->next
) {
249 xmlChar
*val
, *target
;
253 if (n
->type
!= XML_ELEMENT_NODE
)
256 a
= xmlHasProp(n
, (xmlChar
*)"name");
258 if (!a
|| !a
->children
->content
)
261 val
= xmlNodeGetContent(a
->children
);
264 rc
= gpg_error_from_errno(ENOMEM
);
268 tmp
= g_strdup_printf("!%s", (gchar
*)val
);
272 rc
= gpg_error_from_errno(ENOMEM
);
276 tlist
= g_slist_append(list
, tmp
);
280 rc
= gpg_error_from_errno(ENOMEM
);
285 target
= node_has_attribute(n
, (xmlChar
*)"target");
288 gchar
*t
= g_strdup((gchar
*)val
);
293 rc
= gpg_error_from_errno(ENOMEM
);
297 tlist
= g_slist_append(list
, t
);
302 rc
= gpg_error_from_errno(ENOMEM
);
313 total
= g_slist_length(list
);
316 return EPWMD_EMPTY_ELEMENT
;
318 string
= g_string_new(NULL
);
321 rc
= gpg_error_from_errno(ENOMEM
);
325 for (i
= 0; i
< total
; i
++) {
326 gchar
*val
= g_slist_nth_data(list
, i
);
328 g_string_append_printf(string
, "%s\n", val
);
331 string
= g_string_truncate(string
, string
->len
- 1);
335 total
= g_slist_length(list
);
337 for (i
= 0; i
< total
; i
++)
338 g_free(g_slist_nth_data(list
, i
));
344 // FIXME return a gboolean in case of memory allocation failure
345 gchar
**split_input_line(gchar
*str
, gchar
*delim
, gint n
)
350 return g_strsplit(str
, delim
, n
);
354 * Prevents a sibling element past the current element path with the same
357 static xmlNodePtr
find_stop_node(xmlNodePtr node
)
361 for (n
= node
->parent
->children
; n
; n
= n
->next
) {
370 * Alot like create_elements_cb() but doesn't use the last element of 'req' as
371 * content but as an element.
373 xmlNodePtr
create_target_elements_cb(xmlNodePtr node
, gchar
**path
,
374 gpg_error_t
*rc
, void *data
)
379 if (xmlStrEqual(node
->name
, (xmlChar
*)*req
))
382 for (i
= 0; req
[i
]; i
++) {
385 if ((n
= find_element(node
, req
[i
], find_stop_node(node
))) == NULL
||
386 (n
&& n
->parent
== node
->parent
)) {
387 is_literal_element(&req
[i
]);
388 n
= xmlNewNode(NULL
, (xmlChar
*)req
[i
]);
391 *rc
= gpg_error_from_errno(ENOMEM
);
395 node
= xmlAddChild(node
, n
);
398 *rc
= gpg_error_from_errno(ENOMEM
);
409 xmlNodePtr
find_text_node(xmlNodePtr node
)
413 if (n
&& n
->type
== XML_TEXT_NODE
)
416 for (n
= node
; n
; n
= n
->next
) {
417 if (n
->type
== XML_TEXT_NODE
)
424 xmlNodePtr
create_elements_cb(xmlNodePtr node
, gchar
**elements
,
425 gpg_error_t
*rc
, void *data
)
428 gchar
**req
= elements
;
430 if (node
->type
== XML_TEXT_NODE
)
433 if (node
->name
&& xmlStrEqual(node
->name
, (xmlChar
*)*req
))
436 for (i
= 0; req
[i
]; i
++) {
441 * Strip the first '!' if needed. If there's another, it's an
442 * rc. The syntax has already been checked before calling this
445 is_literal_element(&req
[i
]);
449 * The value of the element tree.
452 n
= find_text_node(node
->children
);
455 /* Use AddContent here to prevent overwriting any children. */
456 xmlNodeAddContent(node
, (xmlChar
*)req
[i
]);
457 else if (n
&& !*req
[i
])
458 xmlNodeSetContent(n
, NULL
);
460 xmlNodeSetContent(n
, (xmlChar
*)req
[i
]);
465 n
= find_element(node
, req
[i
], find_stop_node(node
));
468 * If the found element has the same parent as the current element,
469 * they are siblings and the new element needs to be created as a
470 * child of the current element (node).
472 if (n
&& n
->parent
== node
->parent
)
476 n
= xmlNewNode(NULL
, (xmlChar
*)req
[i
]);
479 *rc
= gpg_error_from_errno(ENOMEM
);
483 node
= xmlAddChild(node
, n
);
486 *rc
= gpg_error_from_errno(ENOMEM
);
497 xmlNodePtr
find_account(xmlDocPtr doc
, gchar
***req
, gpg_error_t
*rc
,
498 gboolean
*target
, gint recursion_depth
)
500 xmlNodePtr n
= xmlDocGetRootElement(doc
);
502 gchar
*account
= g_strdup(*req
[0]);
503 gboolean literal
= is_literal_element(&account
);
506 *rc
= gpg_error_from_errno(ENOMEM
);
513 if (max_recursion_depth
>= 1 && recursion_depth
> max_recursion_depth
) {
514 xmlChar
*t
= xmlGetNodePath(n
);
516 log_write("%s: %s", pwmd_strerror(EPWMD_LOOP
), t
);
523 if (n
->type
== XML_ELEMENT_NODE
) {
524 if (depth
== 0 && xmlStrEqual(n
->name
, (xmlChar
*)"pwmd")) {
530 if (depth
== 1 && xmlStrEqual(n
->name
, (xmlChar
*)"root")) {
531 xmlChar
*content
= node_has_attribute(n
, (xmlChar
*)"name");
536 if (xmlStrEqual(content
, (xmlChar
*)account
)) {
537 gchar
**nreq
, **tmp
= NULL
;
539 if (literal
== TRUE
) {
546 content
= node_has_attribute(n
, (xmlChar
*)"target");
553 if (strchr((gchar
*)content
, '\t')) {
554 nreq
= split_input_line((gchar
*)content
, "\t", 0);
562 *rc
= gpg_error_from_errno(ENOMEM
);
568 tmp
= strvcatv(nreq
, tmp
+1);
572 *rc
= gpg_error_from_errno(ENOMEM
);
580 if (strv_printf(&tmp
, "%s", content
) == FALSE
) {
582 *rc
= gpg_error_from_errno(ENOMEM
);
588 nreq
= strvcatv(tmp
, nreq
+1);
592 *rc
= gpg_error_from_errno(ENOMEM
);
604 n
= find_account(doc
, req
, rc
, target
, recursion_depth
);
614 *rc
= EPWMD_ELEMENT_NOT_FOUND
;
618 static xmlNodePtr
find_element(xmlNodePtr node
, gchar
*element
, xmlNodePtr stop
)
622 if (!node
|| !element
)
625 for (n
= node
; n
; n
= n
->next
) {
626 if (n
->type
!= XML_ELEMENT_NODE
)
632 if (xmlStrEqual(n
->name
, (xmlChar
*)element
))
639 static xmlChar
*node_has_attribute(xmlNodePtr n
, xmlChar
*attr
)
641 xmlAttrPtr a
= xmlHasProp(n
, attr
);
646 if (!a
->children
|| !a
->children
->content
)
649 return xmlGetProp(n
, attr
);
652 static gboolean
element_to_literal(gchar
**element
)
654 gchar
*p
= g_strdup_printf("!%s", *element
);
664 xmlNodePtr
find_elements(xmlDocPtr doc
, xmlNodePtr node
,
665 gchar
**req
, gpg_error_t
*rc
, gboolean
*target
,
666 xmlNodePtr (*found_fn
)(xmlNodePtr
, gchar
**, gpg_error_t
*, gchar
**, void *),
667 xmlNodePtr (*not_found_fn
)(xmlNodePtr
, gchar
**, gpg_error_t
*, void *),
668 gboolean is_list_command
, gint recursion_depth
, void *data
)
670 xmlNodePtr n
, last
, last_node
;
677 if (max_recursion_depth
>= 1 && recursion_depth
> max_recursion_depth
) {
678 xmlChar
*t
= xmlGetNodePath(node
);
680 log_write("%s: %s", pwmd_strerror(EPWMD_LOOP
), t
);
687 for (last_node
= last
= n
= node
, p
= req
; *p
; p
++) {
689 gchar
*t
= g_strdup(*p
);
693 *rc
= gpg_error_from_errno(ENOMEM
);
697 literal
= is_literal_element(&t
);
698 n
= find_element(last
, t
, NULL
);
703 return not_found_fn(found
? last_node
: last_node
->parent
, p
, rc
, data
);
705 *rc
= EPWMD_ELEMENT_NOT_FOUND
;
713 if (literal
== FALSE
) {
714 xmlChar
*content
= node_has_attribute(n
, (xmlChar
*)"target");
715 gchar
**nreq
= NULL
, **nnreq
;
718 if (is_list_command
== TRUE
) {
719 if (element_to_literal(&(*p
)) == FALSE
) {
720 *rc
= gpg_error_from_errno(ENOMEM
);
728 if (strchr((gchar
*)content
, '\t') != NULL
) {
729 if ((nreq
= split_input_line((gchar
*)content
, "\t", 0)) == NULL
) {
731 *rc
= EPWMD_INVALID_ELEMENT
;
736 if ((nreq
= split_input_line((gchar
*)content
, " ", 0)) == NULL
) {
738 *rc
= EPWMD_INVALID_ELEMENT
;
744 tmp
= find_account(doc
, &nreq
, rc
, target
, 0);
752 found_fn(tmp
, nreq
, rc
, p
+1, data
);
760 nnreq
= strvcatv(nreq
+1, p
+1);
764 if (!nnreq
|| !*nnreq
) {
774 n
= find_elements(doc
, tmp
->children
, nnreq
, rc
, NULL
, found_fn
,
775 not_found_fn
, is_list_command
, recursion_depth
, data
);
778 gchar
**zz
= p
+1, **qq
= nnreq
;
780 if (g_strv_length(nnreq
) > g_strv_length(p
+1))
783 for (; *qq
&& *zz
; zz
++) {
785 *zz
= g_strdup(*qq
++);
788 *rc
= gpg_error_from_errno(ENOMEM
);
803 static gboolean
update_element_list(struct element_list_s
*elements
)
808 if (!elements
|| !elements
->elements
)
811 line
= g_strjoinv("\t", elements
->elements
);
816 g_strfreev(elements
->elements
);
817 elements
->elements
= NULL
;
818 l
= g_slist_append(elements
->list
, line
);
827 static gpg_error_t
path_list_recurse(xmlDocPtr doc
, xmlNodePtr node
,
828 struct element_list_s
*elements
)
833 for (n
= node
; n
; n
= n
->next
) {
834 xmlChar
*target
= NULL
;
836 if (n
->type
!= XML_ELEMENT_NODE
)
839 if (strv_printf(&elements
->elements
, "%s\t!%s", elements
->prefix
, n
->name
) == FALSE
)
840 return gpg_err_code_from_errno(ENOMEM
);
842 if (update_element_list(elements
) == FALSE
)
843 return gpg_err_code_from_errno(ENOMEM
);
845 target
= node_has_attribute(n
, (xmlChar
*)"target");
849 gchar
*save
= elements
->prefix
;
850 gboolean r
= elements
->resolving
;
854 if (max_recursion_depth
>= 1 && elements
->depth
> max_recursion_depth
) {
855 xmlChar
*t
= xmlGetNodePath(n
);
856 log_write("%s: %s", pwmd_strerror(EPWMD_LOOP
), t
);
862 if (strv_printf(&elements
->elements
, "%s\t%s", elements
->prefix
, n
->name
) == FALSE
) {
864 return gpg_err_code_from_errno(ENOMEM
);
867 tmp
= g_strjoinv("\t", elements
->elements
);
871 return gpg_err_code_from_errno(ENOMEM
);
874 if (update_element_list(elements
) == FALSE
) {
876 return gpg_err_code_from_errno(ENOMEM
);
879 elements
->prefix
= tmp
;
880 elements
->resolving
= TRUE
;
881 rc
= create_path_list(doc
, elements
, (gchar
*)target
);
883 elements
->resolving
= r
;
886 elements
->prefix
= save
;
894 gchar
*tmp
= g_strdup_printf("%s\t!%s", elements
->prefix
, n
->name
);
895 gchar
*save
= elements
->prefix
;
898 return gpg_err_code_from_errno(ENOMEM
);
900 elements
->prefix
= tmp
;
901 rc
= path_list_recurse(doc
, n
->children
, elements
);
902 g_free(elements
->prefix
);
903 elements
->prefix
= save
;
914 * From the element path 'path', find sub-nodes and append them to the list.
916 gpg_error_t
create_path_list(xmlDocPtr doc
, struct element_list_s
*elements
,
920 gchar
**req
, **req_orig
;
922 gboolean a_target
= FALSE
;
924 req
= split_input_line(path
, "\t", 0);
927 req
= split_input_line(path
, " ", 0);
930 return EPWMD_COMMAND_SYNTAX
;
933 req_orig
= g_strdupv(req
);
936 rc
= gpg_err_code_from_errno(ENOMEM
);
940 n
= find_account(doc
, &req
, &rc
, &a_target
, 0);
942 if (!n
&& rc
== EPWMD_ELEMENT_NOT_FOUND
&& elements
->resolving
== TRUE
) {
949 if (a_target
== TRUE
) {
951 *req
= g_strdup(*req_orig
);
955 gboolean e_target
= FALSE
;
957 n
= find_elements(doc
, n
->children
, req
+1, &rc
, &e_target
, NULL
, NULL
, TRUE
, 0, NULL
);
959 if (!n
&& rc
== EPWMD_ELEMENT_NOT_FOUND
&& elements
->resolving
== TRUE
) {
967 if (!elements
->prefix
) {
971 * If any req_orig element contains no target the element should be
972 * prefixed with the literal character. Not really crucial if the
973 * client isn't human because child elements are prefixed for the
974 * current path. But may be confusing if editing by hand.
976 elements
->prefix
= g_strjoinv("\t", req_orig
);
978 if (!elements
->prefix
) {
979 rc
= gpg_err_code_from_errno(ENOMEM
);
983 if (strv_printf(&elements
->elements
, "%s", elements
->prefix
) == FALSE
) {
984 rc
= gpg_err_code_from_errno(ENOMEM
);
988 if (update_element_list(elements
) == FALSE
) {
989 rc
= gpg_err_code_from_errno(ENOMEM
);
994 rc
= path_list_recurse(doc
, n
->children
, elements
);
998 g_strfreev(req_orig
);
1004 gpg_error_t
recurse_xpath_nodeset(xmlDocPtr doc
, xmlNodeSetPtr nodes
,
1005 xmlChar
*value
, xmlBufferPtr
*result
)
1007 gint i
= value
? nodes
->nodeNr
- 1 : 0;
1010 buf
= xmlBufferCreate();
1013 return gpg_err_code_from_errno(ENOMEM
);
1015 for (; value
? i
>= 0 : i
< nodes
->nodeNr
; value
? i
-- : i
++) {
1016 xmlNodePtr n
= nodes
->nodeTab
[i
];
1022 if (xmlNodeDump(buf
, doc
, n
, 0, 0) == -1) {
1024 return EPWMD_LIBXML_ERROR
;
1030 xmlNodeSetContent(n
, value
);
1037 /* Updates the DTD and renames the root "accounts" and "account" elements. */
1038 gpg_error_t
convert_xml(gchar
**xml
, gsize
*len
)
1040 gpg_error_t rc
= EPWMD_LIBXML_ERROR
;
1044 doc
= xmlReadMemory(*xml
, *len
, NULL
, "UTF-8", XML_PARSE_NOBLANKS
);
1047 return EPWMD_LIBXML_ERROR
;
1049 doc
= create_dtd(doc
);
1054 n
= xmlDocGetRootElement(doc
);
1055 xmlNodeSetName(n
, (xmlChar
*)"pwmd");
1057 for (n
= n
->children
; n
; n
= n
->next
) {
1058 if (xmlStrcmp(n
->name
, (xmlChar
*)"account") == 0)
1059 xmlNodeSetName(n
, (xmlChar
*)"root");
1064 /* The memory allocators set in main() all use xmalloc,xfree,etc. When
1065 * MEM_DEBUG is set, a segfault will occur do to an invalid free if this
1066 * code isn't included.
1072 xmlDocDumpMemory(doc
, &tmp
, &tmp_len
);
1073 *xml
= gcry_calloc(1, tmp_len
+1);
1076 rc
= gpg_error_from_errno(ENOMEM
);
1080 memcpy(*xml
, tmp
, tmp_len
);
1085 xmlDocDumpMemory(doc
, (xmlChar
**)xml
, (gint
*)len
);