1 /* vim:tw=78:ts=8:sw=4:set ft=c: */
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
24 #include <sys/types.h>
29 #include <glib/gprintf.h>
38 #include "pwmd_error.h"
42 static gpg_error_t
do_get_command(assuan_context_t ctx
, xmlTextReaderPtr
*reader
,
43 gchar
***req
, xmlChar
**content
, gint quiet
, gint list
);
46 * Determines whether the element path request follows the "target" attribute.
48 static gboolean
is_literal_req(gchar
**req
)
55 len
= strlen(*req
) - 1;
58 memmove(req
[0], req
[0] + 1, len
);
66 static gchar
*file_modified(struct client_s
*client
, gpg_error_t
*error
)
69 static gchar path
[PATH_MAX
];
72 if (client
->state
!= STATE_OPEN
) {
73 *error
= EPWMD_NO_FILE
;
77 p
= get_key_file_string("default", "data_directory");
78 p2
= expand_homedir(p
);
80 snprintf(path
, sizeof(path
), "%s/%s", p2
, client
->filename
);
83 if (stat(path
, &st
) == 0 && client
->mtime
) {
84 if (client
->mtime
!= st
.st_mtime
) {
85 *error
= EPWMD_FILE_MODIFIED
;
93 static gboolean
encrypt_xml(gcry_cipher_hd_t gh
, void *outbuf
, gsize outsize
,
94 void *inbuf
, gsize insize
)
96 if ((gcryerrno
= gcry_cipher_encrypt(gh
, outbuf
, outsize
, inbuf
, insize
))) {
97 log_write("%s(%i): %s", __FUNCTION__
, __LINE__
, gcry_strerror(gcryerrno
));
104 gboolean
decrypt_xml(gcry_cipher_hd_t gh
, void *outbuf
, gsize outsize
,
105 void *inbuf
, gsize insize
)
107 if ((gcryerrno
= gcry_cipher_decrypt(gh
, outbuf
, outsize
, inbuf
, insize
))) {
108 log_write("%s(%i): %s", __FUNCTION__
, __LINE__
, gcry_strerror(gcryerrno
));
115 static gboolean
parse_xml(assuan_context_t ctx
)
117 xmlErrorPtr xml_error
;
118 struct client_s
*client
= assuan_get_pointer(ctx
);
120 switch (open_xml(client
->xml
, client
->len
, &client
->doc
, &client
->reader
)) {
122 xml_error
= xmlGetLastError();
123 log_write("%s(%i): %s", __FILE__
, __LINE__
, xml_error
->message
);
124 return send_error(ctx
, EPWMD_LIBXML_ERROR
);
126 xml_error
= xmlGetLastError();
127 log_write("%s(%i): %s", __FILE__
, __LINE__
, xml_error
->message
);
128 return send_error(ctx
, EPWMD_LIBXML_ERROR
);
136 static gboolean
valid_filename(const gchar
*filename
)
140 if (!filename
|| !*filename
)
143 for (p
= filename
; *p
; p
++) {
144 if (g_ascii_isalnum(*p
) == FALSE
)
151 gint
open_file(const gchar
*filename
, struct stat
*st
)
155 if ((fd
= open(filename
, O_RDONLY
)) == -1)
158 if (stat(filename
, st
) == -1) {
166 static void cleanup(struct client_s
*client
)
169 xmlFreeDoc(client
->doc
);
172 xmlFreeTextReader(client
->reader
);
177 if (client
->filename
)
178 g_free(client
->filename
);
180 gcry_cipher_close(client
->gh
);
181 memset(client
, 0, sizeof(struct client_s
));
182 client
->state
= STATE_CONNECTED
;
185 static int open_command(assuan_context_t ctx
, char *line
)
190 guchar shakey
[gcrykeysize
];
191 guchar tkey
[gcrykeysize
];
196 gchar filebuf
[PATH_MAX
], *p
, *p2
;
198 struct file_header_s
{
200 guchar iv
[gcryblocksize
];
202 struct client_s
*client
= assuan_get_pointer(ctx
);
204 gchar
*filename
= NULL
;
206 if ((req
= split_input_line(line
, " ", 2)) != NULL
)
209 if (!filename
|| !*filename
) {
211 return send_error(ctx
, EPWMD_COMMAND_SYNTAX
);
214 if (valid_filename(filename
) == FALSE
) {
216 return send_error(ctx
, EPWMD_INVALID_FILENAME
);
219 if (client
->state
== STATE_OPEN
)
222 if ((gcryerrno
= gcry_cipher_open(&client
->gh
, GCRY_CIPHER_AES256
, GCRY_CIPHER_MODE_CBC
, 0))) {
224 log_write("%s(%i): %s", __FUNCTION__
, __LINE__
, gcry_strerror(gcryerrno
));
225 cleanup(client
); return send_error(ctx
, gcryerrno
);
228 p
= get_key_file_string("default", "data_directory");
229 p2
= expand_homedir(p
);
231 snprintf(filebuf
, sizeof(filebuf
), "%s/%s", p2
, filename
);
234 if (stat(filebuf
, &st
) == 0) {
235 if (!S_ISREG(st
.st_mode
) && !S_ISLNK(st
.st_mode
)) {
236 log_write("%s: %s", filename
, pwmd_strerror(EPWMD_INVALID_FILENAME
));
238 cleanup(client
); return send_error(ctx
, EPWMD_INVALID_FILENAME
);
241 client
->mtime
= st
.st_mtime
;
244 gcry_md_hash_buffer(GCRY_MD_MD5
, client
->md5file
, filename
, strlen(filename
));
247 * New files don't need a key.
249 if (access(filebuf
, R_OK
|W_OK
) != 0) {
250 if (errno
!= ENOENT
) {
252 log_write("%s: %s", filename
, strerror(errno
));
254 cleanup(client
); return send_syserror(ctx
, error
);
257 if ((client
->xml
= new_document()) == NULL
) {
259 log_write("%s", strerror(errno
));
261 cleanup(client
); return send_syserror(ctx
, error
);
264 client
->len
= strlen(client
->xml
);
266 if (cache_add_file(client
->md5file
, NULL
) == FALSE
) {
267 log_write("%s(%i): %s", __FILE__
, __LINE__
, pwmd_strerror(EPWMD_MAX_SLOTS
));
269 cleanup(client
); return send_error(ctx
, EPWMD_MAX_SLOTS
);
272 client
->filename
= g_strdup(filename
);
275 gcry_md_hash_buffer(GCRY_MD_SHA256
, shakey
, req
[1], strlen(req
[1]));
276 memset(req
[1], 0, strlen(req
[1]));
283 if ((fd
= open_file(filebuf
, &st
)) == -1) {
285 log_write("%s: %s", filename
, strerror(errno
));
287 cleanup(client
); return send_syserror(ctx
, error
);
293 if (cache_get_key(client
->md5file
, shakey
) == TRUE
)
297 * No key specified and no matching filename found in the cache.
299 if (!req
[1] || !*req
[1]) {
302 cleanup(client
); return send_error(ctx
, EPWMD_KEY
);
306 insize
= st
.st_size
- sizeof(struct file_header_s
);
307 read(fd
, &file_header
, sizeof(struct file_header_s
));
308 inbuf
= gcry_malloc(insize
);
309 read(fd
, inbuf
, insize
);
314 gcry_md_hash_buffer(GCRY_MD_SHA256
, shakey
, req
[1], strlen(req
[1]));
315 memset(req
[1], 0, strlen(req
[1]));
318 if ((gcryerrno
= gcry_cipher_setiv(client
->gh
, file_header
.iv
,
319 sizeof(file_header
.iv
)))) {
321 memset(shakey
, 0, sizeof(shakey
));
322 log_write("%s(%i): %s", __FUNCTION__
, __LINE__
, gcry_strerror(gcryerrno
));
324 cleanup(client
); return gcryerrno
;
327 if ((gcryerrno
= gcry_cipher_setkey(client
->gh
, shakey
, gcrykeysize
))) {
329 memset(shakey
, 0, sizeof(shakey
));
330 log_write("%s(%i): %s", __FUNCTION__
, __LINE__
, gcry_strerror(gcryerrno
));
332 cleanup(client
); return gcryerrno
;
335 if (decrypt_xml(client
->gh
, inbuf
, insize
, NULL
, 0) == FALSE
) {
341 memset(shakey
, 0, sizeof(shakey
));
343 log_write("%s(%i): %s", __FUNCTION__
, __LINE__
, gcry_strerror(gcryerrno
));
345 cleanup(client
); return gcryerrno
;
348 memcpy(tkey
, shakey
, sizeof(tkey
));
351 if ((gcryerrno
= gcry_cipher_setkey(client
->gh
, tkey
, gcrykeysize
))) {
352 memset(tkey
, 0, sizeof(tkey
));
354 log_write("%s(%i): %s", __FUNCTION__
, __LINE__
, gcry_strerror(gcryerrno
));
356 cleanup(client
); return gcryerrno
;
359 iter
= file_header
.iter
;
362 if ((gcryerrno
= gcry_cipher_setiv(client
->gh
, file_header
.iv
,
363 sizeof(file_header
.iv
)))) {
364 memset(tkey
, 0, sizeof(tkey
));
366 log_write("%s(%i): %s", __FUNCTION__
, __LINE__
, gcry_strerror(gcryerrno
));
368 cleanup(client
); return gcryerrno
;
371 if (decrypt_xml(client
->gh
, inbuf
, insize
, NULL
, 0) == FALSE
) {
377 memset(tkey
, 0, sizeof(tkey
));
379 log_write("%s(%i): %s", __FUNCTION__
, __LINE__
, gcry_strerror(gcryerrno
));
381 cleanup(client
); return gcryerrno
;
385 memset(tkey
, 0, sizeof(tkey
));
387 client
->len
= insize
;
389 if (g_strncasecmp(client
->xml
, "<?xml version=\"1.0\"?>", 21) != 0) {
391 cleanup(client
); return send_error(ctx
, EPWMD_BADKEY
);
394 client
->filename
= g_strdup(filename
);
398 if (cache_update_key(client
->md5file
, shakey
) == FALSE
) {
399 memset(shakey
, 0, sizeof(shakey
));
400 log_write("%s(%i): %s", __FILE__
, __LINE__
, pwmd_strerror(EPWMD_MAX_SLOTS
));
402 cleanup(client
); return send_error(ctx
, EPWMD_MAX_SLOTS
);
405 timeout
= get_key_file_integer(client
->filename
, "cache_timeout");
406 cache_reset_timeout(client
->md5file
, timeout
);
409 cache_set_timeout(client
->md5file
, -2);
411 memset(shakey
, 0, sizeof(shakey
));
415 error
= parse_xml(ctx
);
418 gcry_free(client
->xml
);
423 client
->state
= STATE_OPEN
;
428 gpg_error_t
do_xml_encrypt(struct client_s
*client
, gcry_cipher_hd_t gh
,
429 const gchar
*filename
, const xmlChar
*data
, size_t insize
,
430 const guchar
*shakey
, guint iter
)
435 guchar tkey
[gcrykeysize
];
438 struct file_header_s
{
440 guchar iv
[gcryblocksize
];
443 if (insize
/ gcryblocksize
) {
444 len
= (insize
/ gcryblocksize
) * gcryblocksize
;
446 if (insize
% gcryblocksize
)
447 len
+= gcryblocksize
;
450 inbuf
= gcry_calloc(1, len
);
451 memcpy(inbuf
, data
, insize
);
453 gcry_create_nonce(file_header
.iv
, sizeof(file_header
.iv
));
454 memcpy(tkey
, shakey
, sizeof(tkey
));
457 if ((gcryerrno
= gcry_cipher_setkey(gh
, tkey
, gcrykeysize
))) {
459 log_write("%s(%i): %s", __FUNCTION__
, __LINE__
, gcry_strerror(gcryerrno
));
463 file_header
.iter
= iter
;
466 if ((gcryerrno
= gcry_cipher_setiv(gh
, file_header
.iv
,
467 sizeof(file_header
.iv
)))) {
469 log_write("%s(%i): %s", __FUNCTION__
, __LINE__
, gcry_strerror(gcryerrno
));
473 if (encrypt_xml(gh
, inbuf
, insize
, NULL
, 0)
476 log_write("%s(%i): %s", __FUNCTION__
, __LINE__
, gcry_strerror(gcryerrno
));
481 if ((gcryerrno
= gcry_cipher_setiv(gh
, file_header
.iv
,
482 sizeof(file_header
.iv
)))) {
484 log_write("%s(%i): %s", __FUNCTION__
, __LINE__
, gcry_strerror(gcryerrno
));
488 if ((gcryerrno
= gcry_cipher_setkey(gh
, shakey
, gcrykeysize
))) {
490 log_write("%s(%i): %s", __FUNCTION__
, __LINE__
, gcry_strerror(gcryerrno
));
494 if (encrypt_xml(gh
, inbuf
, insize
, NULL
, 0)
497 log_write("%s(%i): %s", __FUNCTION__
, __LINE__
, gcry_strerror(gcryerrno
));
502 if ((fd
= open(filename
, O_WRONLY
|O_CREAT
|O_TRUNC
, 0600)) == -1) {
505 p
= strrchr(filename
, '/');
507 log_write("%s: %s", p
, strerror(errno
));
508 return gpg_error_from_errno(error
);
513 * xml_import() from command line.
517 write(fd
, &file_header
, sizeof(struct file_header_s
));
518 write(fd
, inbuf
, insize
);
527 static int save_command(assuan_context_t ctx
, char *line
)
532 guchar shakey
[gcrykeysize
];
536 struct client_s
*client
= assuan_get_pointer(ctx
);
540 filepath
= file_modified(client
, &error
);
543 log_write("%s: %s", client
->filename
, pwmd_strerror(error
));
544 return send_error(ctx
, error
);
547 if (!line
|| !*line
) {
548 if (cache_get_key(client
->md5file
, shakey
) == FALSE
)
549 return send_error(ctx
, EPWMD_KEY
);
554 gcry_md_hash_buffer(GCRY_MD_SHA256
, shakey
, line
, strlen(line
));
555 memset(line
, 0, strlen(line
));
558 xmlDocDumpFormatMemory(client
->doc
, &xmlbuf
, &len
, 0);
560 if ((iter
= get_key_file_integer(client
->filename
, "iterations")) == -1)
563 error
= do_xml_encrypt(client
, client
->gh
, filepath
, xmlbuf
, len
, shakey
, iter
);
566 memset(shakey
, 0, sizeof(shakey
));
568 return send_error(ctx
, error
);
573 client
->mtime
= st
.st_mtime
;
574 timeout
= get_key_file_integer(client
->filename
, "cache_timeout");
577 memset(shakey
, 0, sizeof(shakey
));
578 cache_reset_timeout(client
->md5file
, timeout
);
582 if (cache_update_key(client
->md5file
, shakey
) == FALSE
) {
583 memset(shakey
, 0, sizeof(shakey
));
584 log_write("%s(%i): %s", __FILE__
, __LINE__
, pwmd_strerror(EPWMD_MAX_SLOTS
));
585 return send_error(ctx
, EPWMD_MAX_SLOTS
);
588 memset(shakey
, 0, sizeof(shakey
));
589 cache_reset_timeout(client
->md5file
, timeout
);
593 static gboolean
contains_whitespace(const gchar
*str
)
595 const gchar
*p
= str
;
598 if (g_ascii_isspace(*p
) == TRUE
) {
606 static void delete_node(xmlNodePtr n
)
612 static int delete_command(assuan_context_t ctx
, char *line
)
615 struct client_s
*client
= assuan_get_pointer(ctx
);
616 gchar
**req
= split_input_line(line
, "\t", 0);
620 if (!file_modified(client
, &error
)) {
621 log_write("%s: %s", client
->filename
, pwmd_strerror(error
));
623 return send_error(ctx
, error
);
628 return send_error(ctx
, EPWMD_COMMAND_SYNTAX
);
631 literal
= is_literal_req(req
);
633 if (find_account(literal
, client
->doc
, &client
->reader
, &req
, &error
) == FALSE
) {
635 return send_error(ctx
, EPWMD_ELEMENT_NOT_FOUND
);
638 n
= xmlTextReaderCurrentNode(client
->reader
);
641 * No sub-node defined. Remove the entire node (account).
651 error
= find_elements(literal
, client
->doc
, &client
->reader
, req
+1, NULL
, NULL
);
657 n
= xmlTextReaderCurrentNode(client
->reader
);
665 static int store_command(assuan_context_t ctx
, char *line
)
667 struct client_s
*client
= assuan_get_pointer(ctx
);
674 if (!file_modified(client
, &error
)) {
675 log_write("%s: %s", client
->filename
, pwmd_strerror(error
));
676 return send_error(ctx
, error
);
679 error
= assuan_inquire(ctx
, "STORE", &result
, &len
, 0);
682 return send_error(ctx
, error
);
684 req
= split_input_line(result
, "\t", 0);
687 return send_error(ctx
, EPWMD_COMMAND_SYNTAX
);
689 literal
= is_literal_req(req
);
692 if (find_account(literal
, client
->doc
, &client
->reader
, &req
, &error
) == FALSE
) {
693 if (contains_whitespace(req
[0]) == TRUE
) {
695 return send_error(ctx
, EPWMD_INVALID_ELEMENT
);
698 error
= new_account(client
->doc
, req
[0]);
702 return send_error(ctx
, error
);
709 error
= find_elements(literal
, client
->doc
, &client
->reader
, req
+1,
710 create_elements_cb
, NULL
);
713 return send_error(ctx
, error
);
716 static gpg_error_t
do_get_command(assuan_context_t ctx
, xmlTextReaderPtr
*reader
,
717 gchar
***req
, xmlChar
**content
, gint quiet
, gint list
)
720 xmlErrorPtr xml_error
;
723 struct client_s
*client
= assuan_get_pointer(ctx
);
726 literal
= is_literal_req(*req
);
728 if (find_account(literal
, client
->doc
, reader
, req
, &error
) == FALSE
)
732 error
= find_elements(literal
, client
->doc
, reader
, tmp
+1, NULL
, NULL
);
737 if ((n
= xmlTextReaderCurrentNode(*reader
)) == NULL
)
738 return EPWMD_LIBXML_ERROR
;
740 switch (xmlTextReaderNext(*reader
)) {
742 xml_error
= xmlGetLastError();
743 log_write("%s(%i): %s", __FILE__
, __LINE__
, xml_error
->message
);
744 return EPWMD_LIBXML_ERROR
;
746 return EPWMD_EMPTY_ELEMENT
;
751 switch (xmlTextReaderNodeType(*reader
)) {
752 case XML_READER_TYPE_END_ELEMENT
:
754 * May be an empty element after an ATTR DELETE target command.
757 case XML_READER_TYPE_TEXT
:
760 return EPWMD_LIBXML_ERROR
;
763 if (n
->children
&& !list
)
764 return EPWMD_TRAILING_ELEMENT
;
765 else if (!n
->children
&& !list
)
766 return EPWMD_INVALID_ELEMENT
;
774 if ((*content
= xmlNodeGetContent(n
)) == NULL
)
775 return EPWMD_EMPTY_ELEMENT
;
781 * Retrieves the value associated with the element tree 'req'.
783 static gpg_error_t
get(assuan_context_t ctx
, xmlTextReaderPtr
*reader
,
784 gchar
***req
, gint quiet
)
786 xmlChar
*content
= NULL
;
789 error
= do_get_command(ctx
, reader
, req
, &content
, quiet
, 0);
799 return EPWMD_EMPTY_ELEMENT
;
801 error
= assuan_send_data(ctx
, content
, xmlStrlen(content
));
806 static int get_command(assuan_context_t ctx
, char *line
)
808 struct client_s
*client
= assuan_get_pointer(ctx
);
809 gchar
**req
= split_input_line(line
, "\t", 0);
812 if (!file_modified(client
, &error
)) {
813 log_write("%s: %s", client
->filename
, pwmd_strerror(error
));
815 return send_error(ctx
, error
);
820 return send_error(ctx
, EPWMD_COMMAND_SYNTAX
);
823 error
= get(ctx
, &client
->reader
, &req
, 0);
825 return send_error(ctx
, error
);
828 static gchar
*element_path_to_req(const gchar
*account
, xmlChar
*path
)
837 for (n
= 0; *p
&& n
< 3; p
++) {
842 if (strstr((gchar
*)p
, "text()") != NULL
)
843 p
[xmlStrlen(p
) - 7] = 0;
845 for (n
= 0; p
[n
]; n
++) {
850 buf
= g_strdup_printf("%s\t%s", account
, p
);
854 static gboolean
append_to_array(gchar
***array
, gint
*total
, const gchar
*str
)
859 if ((a
= g_realloc(*array
, (t
+ 2) * sizeof(gchar
*))) == NULL
)
862 a
[t
++] = g_strdup(str
);
869 static int list_command(assuan_context_t ctx
, char *str
)
873 gchar
**elements
= NULL
;
879 xmlErrorPtr xml_error
;
881 struct client_s
*client
= assuan_get_pointer(ctx
);
884 if (!file_modified(client
, &error
)) {
885 log_write("%s: %s", client
->filename
, pwmd_strerror(error
));
886 return send_error(ctx
, error
);
890 if (reset_reader(client
->doc
, &client
->reader
) == FALSE
) {
891 xml_error
= xmlGetLastError();
892 log_write("%s(%i): %s", __FILE__
, __LINE__
, xml_error
->message
);
893 return send_error(ctx
, EPWMD_LIBXML_ERROR
);
896 if (list_accounts(client
->reader
, &dst
, &error
) == FALSE
)
897 return send_error(ctx
, error
);
899 error
= assuan_send_data(ctx
, dst
, g_utf8_strlen(dst
, -1));
906 if (strchr(p
, '\t') != NULL
) {
907 if ((req
= split_input_line(p
, "\t", 0)) == NULL
)
908 return send_error(ctx
, EPWMD_COMMAND_SYNTAX
);
910 if (find_account(FALSE
, client
->doc
, &client
->reader
, &req
, &error
) == FALSE
) {
912 return send_error(ctx
, error
);
916 error
= find_elements(FALSE
, client
->doc
, &client
->reader
, req
+1, NULL
, NULL
);
924 depth
= xmlTextReaderDepth(client
->reader
);
927 if ((req
= split_input_line(p
, " ", 0)) == NULL
)
928 return send_error(ctx
, EPWMD_COMMAND_SYNTAX
);
930 if (find_account(FALSE
, client
->doc
, &client
->reader
, &req
, &error
) == FALSE
)
931 return send_error(ctx
, error
);
934 if ((n
= xmlTextReaderCurrentNode(client
->reader
)) == NULL
) {
935 xml_error
= xmlGetLastError();
936 log_write("%s(%i): %s", __FILE__
, __LINE__
, xml_error
->message
);
937 return send_error(ctx
, EPWMD_LIBXML_ERROR
);
940 account
= g_strdup(req
[0]);
945 while (xmlTextReaderNext(client
->reader
) == 1) {
946 gint type
= xmlTextReaderNodeType(client
->reader
);
948 if ((n
= xmlTextReaderCurrentNode(client
->reader
)) == NULL
) {
949 xml_error
= xmlGetLastError();
950 log_write("%s(%i): %s", __FILE__
, __LINE__
, xml_error
->message
);
951 return send_error(ctx
, EPWMD_LIBXML_ERROR
);
954 if (xmlTextReaderDepth(client
->reader
) == 1 &&
955 xmlStrcmp(n
->name
, (xmlChar
*)"account") == 0 &&
956 type
== XML_READER_TYPE_END_ELEMENT
)
959 if (depth
&& depth
== xmlTextReaderDepth(client
->reader
) &&
960 type
== XML_READER_TYPE_END_ELEMENT
)
963 if (type
== XML_READER_TYPE_TEXT
) {
964 xmlChar
*np
= xmlGetNodePath(n
);
966 line
= element_path_to_req(account
, np
);
968 append_to_array(&elements
, &total
, line
);
969 memset(line
, 0, g_utf8_strlen(line
, -1));
976 return send_error(ctx
, EPWMD_EMPTY_ELEMENT
);
980 line
= g_strjoinv("\n", elements
);
981 g_strfreev(elements
);
982 error
= assuan_send_data(ctx
, line
, g_utf8_strlen(line
, -1));
988 * The client->reader handle should be at the element in the document where
989 * the attribute will be created or modified.
991 static gpg_error_t
add_attribute(xmlTextReaderPtr reader
, const gchar
*name
,
996 xmlErrorPtr xml_error
;
998 if ((n
= xmlTextReaderCurrentNode(reader
)) == NULL
) {
999 xml_error
= xmlGetLastError();
1000 log_write("%s(%i): %s", __FILE__
, __LINE__
, xml_error
->message
);
1001 return EPWMD_LIBXML_ERROR
;
1004 if ((a
= xmlHasProp(n
, (xmlChar
*)name
)) == NULL
)
1005 a
= xmlNewProp(n
, (xmlChar
*)name
, (xmlChar
*)value
);
1007 xmlNodeSetContent(a
->children
, (xmlChar
*)value
);
1013 * req[0] - element path
1015 static int attribute_list(assuan_context_t ctx
, gchar
**req
)
1017 struct client_s
*client
= assuan_get_pointer(ctx
);
1018 gchar
**attrlist
= NULL
;
1020 gchar
**epath
= NULL
;
1024 xmlErrorPtr xml_error
;
1028 if (!req
|| !req
[0])
1029 return EPWMD_COMMAND_SYNTAX
;
1031 if ((epath
= split_input_line(req
[0], "\t", 0)) == NULL
) {
1033 * The first argument may be only an account.
1035 if ((epath
= split_input_line(req
[0], " ", 0)) == NULL
)
1036 return EPWMD_COMMAND_SYNTAX
;
1039 literal
= is_literal_req(epath
);
1041 if (find_account(literal
, client
->doc
, &client
->reader
, &epath
, &error
) == FALSE
) {
1047 error
= find_elements(literal
, client
->doc
, &client
->reader
, epath
+1, NULL
, NULL
);
1057 if ((n
= xmlTextReaderCurrentNode(client
->reader
)) == NULL
) {
1058 xml_error
= xmlGetLastError();
1059 log_write("%s(%i): %s", __FILE__
, __LINE__
, xml_error
->message
);
1060 return EPWMD_LIBXML_ERROR
;
1063 for (a
= n
->properties
; a
; a
= a
->next
) {
1064 if ((attrlist
= g_realloc(attrlist
, (i
+ 2) * sizeof(gchar
*))) == NULL
) {
1066 log_write("%s(%i): g_realloc() failed", __FILE__
, __LINE__
);
1069 g_strfreev(attrlist
);
1071 return gpg_error_from_errno(error
);
1075 attrlist
[i
++] = g_strdup_printf("%s\t%s", (gchar
*)a
->name
, (gchar
*)an
->content
);
1080 return EPWMD_EMPTY_ELEMENT
;
1082 line
= g_strjoinv("\n", attrlist
);
1083 error
= assuan_send_data(ctx
, line
, g_utf8_strlen(line
, -1));
1085 g_strfreev(attrlist
);
1090 * req[0] - attribute
1091 * req[1] - element path
1093 static int attribute_delete(struct client_s
*client
, gchar
**req
)
1097 gchar
**epath
= NULL
;
1098 xmlErrorPtr xml_error
;
1102 if (!req
|| !req
[0] || !req
[1])
1103 return EPWMD_COMMAND_SYNTAX
;
1105 if ((epath
= split_input_line(req
[1], "\t", 0)) == NULL
) {
1107 * The first argument may be only an account.
1109 if ((epath
= split_input_line(req
[1], " ", 0)) == NULL
)
1110 return EPWMD_COMMAND_SYNTAX
;
1114 * Don't remove the NAME attribute for the account element. To remove an
1115 * account use DELETE <account>.
1117 if (!epath
[1] && g_ascii_strcasecmp(req
[0], "NAME") == 0) {
1118 error
= EPWMD_ATTR_SYNTAX
;
1122 literal
= is_literal_req(epath
);
1124 if (find_account(literal
, client
->doc
, &client
->reader
, &epath
, &error
) == FALSE
)
1128 error
= find_elements(literal
, client
->doc
, &client
->reader
, epath
+1, NULL
, NULL
);
1136 if ((n
= xmlTextReaderCurrentNode(client
->reader
)) == NULL
) {
1137 xml_error
= xmlGetLastError();
1138 log_write("%s(%i): %s", __FILE__
, __LINE__
, xml_error
->message
);
1139 return EPWMD_LIBXML_ERROR
;
1142 if ((a
= xmlHasProp(n
, (xmlChar
*)req
[0])) == NULL
)
1143 return EPWMD_ATTR_NOT_FOUND
;
1145 if (xmlRemoveProp(a
) == -1) {
1146 xml_error
= xmlGetLastError();
1147 log_write("%s(%i): %s", __FILE__
, __LINE__
, xml_error
->message
);
1148 return EPWMD_LIBXML_ERROR
;
1159 * Creates a "target" attribute. When other commands encounter an element with
1160 * this attribute, the element path is modified to the target value. If the
1161 * source element path doesn't exist when using 'ATTR SET target', it is
1162 * created, but the destination element path must exist.
1164 * req[0] - source element path
1165 * req[1] - destination element path
1167 static gpg_error_t
target_attribute(struct client_s
*client
, gchar
**req
)
1169 gchar
**src
, **dst
, *line
;
1170 xmlErrorPtr xml_error
;
1174 if (!req
|| !req
[0] || !req
[1])
1175 return EPWMD_COMMAND_SYNTAX
;
1177 if ((src
= split_input_line(req
[0], "\t", 0)) == NULL
) {
1179 * The first argument may be only an account.
1181 if ((src
= split_input_line(req
[0], " ", 0)) == NULL
)
1182 return EPWMD_COMMAND_SYNTAX
;
1185 if ((dst
= split_input_line(req
[1], "\t", 0)) == NULL
) {
1187 * The first argument may be only an account.
1189 if ((dst
= split_input_line(req
[1], " ", 0)) == NULL
) {
1190 error
= EPWMD_COMMAND_SYNTAX
;
1195 literal
= is_literal_req(dst
);
1198 * Make sure the destination element path exists.
1200 if (find_account(literal
, client
->doc
, &client
->reader
, &dst
, &error
) == FALSE
)
1204 error
= find_elements(literal
, client
->doc
, &client
->reader
, dst
+1, NULL
, NULL
);
1210 literal
= is_literal_req(src
);
1213 if (find_account(literal
, client
->doc
, &client
->reader
, &src
, &error
) == FALSE
) {
1214 if (error
== EPWMD_ELEMENT_NOT_FOUND
) {
1215 error
= new_account(client
->doc
, src
[0]);
1218 if (error
== EPWMD_LIBXML_ERROR
) {
1219 xml_error
= xmlGetLastError();
1220 log_write("%s(%i): %s", __FILE__
, __LINE__
, xml_error
->message
);
1233 error
= find_elements(literal
, client
->doc
, &client
->reader
, src
+1,
1234 create_target_elements_cb
, NULL
);
1240 * Reset the reader to the position of the element tree now that the
1241 * elements have been created.
1243 if (find_account(literal
, client
->doc
, &client
->reader
, &src
, &error
) == FALSE
)
1246 error
= find_elements(literal
, client
->doc
, &client
->reader
, src
+1, NULL
, NULL
);
1252 line
= g_strjoinv("\t", dst
);
1253 error
= add_attribute(client
->reader
, "target", line
);
1272 * req[0] - account name
1275 static gpg_error_t
name_attribute(struct client_s
*client
, gchar
**req
)
1281 literal
= is_literal_req(req
);
1282 tmp
= g_strdupv(req
);
1284 if (find_account(literal
, client
->doc
, &client
->reader
, &tmp
, &error
) == FALSE
) {
1286 return EPWMD_ELEMENT_NOT_FOUND
;
1291 if (strcmp(req
[0], req
[1]) == 0)
1295 * Will not overwrite an existing account.
1297 tmp
= g_strdupv(req
+1);
1299 if (find_account(literal
, client
->doc
, &client
->reader
, &tmp
, &error
) == TRUE
) {
1301 return EPWMD_ACCOUNT_EXISTS
;
1307 * Whitespace not allowed in account names.
1309 if (contains_whitespace(req
[1]) == TRUE
)
1310 return EPWMD_ATTR_SYNTAX
;
1312 tmp
= g_strdupv(req
);
1314 if (find_account(literal
, client
->doc
, &client
->reader
, &tmp
, &error
) == FALSE
) {
1316 return EPWMD_ELEMENT_NOT_FOUND
;
1320 return add_attribute(client
->reader
, "name", req
[1]);
1324 * req[0] - attribute
1325 * req[1] - element path
1327 * If the element has a "target" attribute it won't be "followed".
1329 static int attribute_get(assuan_context_t ctx
, gchar
**req
)
1331 struct client_s
*client
= assuan_get_pointer(ctx
);
1334 gchar
**nreq
= NULL
;
1335 xmlErrorPtr xml_error
;
1339 if (!req
|| !req
[0] || !req
[1])
1340 return EPWMD_COMMAND_SYNTAX
;
1342 if (strchr(req
[1], '\t')) {
1343 if ((nreq
= split_input_line(req
[1], "\t", 0)) == NULL
)
1344 return EPWMD_COMMAND_SYNTAX
;
1347 if ((nreq
= split_input_line(req
[1], " ", 0)) == NULL
)
1348 return EPWMD_COMMAND_SYNTAX
;
1351 literal
= is_literal_req(nreq
);
1353 if (find_account(literal
, client
->doc
, &client
->reader
, &nreq
, &error
) == FALSE
)
1357 error
= find_elements(literal
, client
->doc
, &client
->reader
, nreq
+1, NULL
, NULL
);
1365 if ((n
= xmlTextReaderCurrentNode(client
->reader
)) == NULL
) {
1366 xml_error
= xmlGetLastError();
1367 log_write("%s(%i): %s", __FILE__
, __LINE__
, xml_error
->message
);
1368 return EPWMD_LIBXML_ERROR
;
1371 if ((a
= xmlGetProp(n
, (xmlChar
*)req
[0])) == NULL
)
1372 return EPWMD_ATTR_NOT_FOUND
;
1374 error
= assuan_send_data(ctx
, a
, xmlStrlen(a
));
1384 * req[0] - attribute
1385 * req[1] - element path
1388 static int attribute_set(struct client_s
*client
, gchar
**req
)
1390 gchar
**epath
= NULL
;
1394 if (!req
|| !req
[0] || !req
[1] || !req
[2])
1395 return EPWMD_COMMAND_SYNTAX
;
1398 * Reserved attribute names.
1400 if (strcmp(req
[0], "name") == 0) {
1402 * Only reserved for the account element. Not the rest of the
1405 if (strchr(req
[1], '\t') == NULL
)
1406 return name_attribute(client
, req
+ 1);
1408 else if (strcmp(req
[0], "target") == 0)
1409 return target_attribute(client
, req
+ 1);
1411 if ((epath
= split_input_line(req
[1], "\t", 0)) == NULL
) {
1413 * The first argument may be only an account.
1415 if ((epath
= split_input_line(req
[1], " ", 0)) == NULL
)
1416 return EPWMD_COMMAND_SYNTAX
;
1419 literal
= is_literal_req(epath
);
1421 if (find_account(literal
, client
->doc
, &client
->reader
, &epath
, &error
) == FALSE
)
1425 error
= find_elements(literal
, client
->doc
, &client
->reader
, epath
+1, NULL
, NULL
);
1432 return add_attribute(client
->reader
, req
[0], req
[2]);
1441 * req[1] - attribute name or element path if command is LIST
1442 * req[2] - element path
1443 * req[2] - element path or value
1445 static int attr_command(assuan_context_t ctx
, char *line
)
1447 struct client_s
*client
= assuan_get_pointer(ctx
);
1448 gchar
**req
= split_input_line(line
, " ", 4);
1449 gpg_error_t error
= 0;
1451 if (!file_modified(client
, &error
)) {
1452 log_write("%s: %s", client
->filename
, pwmd_strerror(error
));
1454 return send_error(ctx
, error
);
1457 if (!req
|| !req
[0] || !req
[1]) {
1459 return send_error(ctx
, EPWMD_COMMAND_SYNTAX
);
1462 if (g_ascii_strcasecmp(req
[0], "SET") == 0)
1463 error
= attribute_set(client
, req
+1);
1464 else if (g_ascii_strcasecmp(req
[0], "GET") == 0)
1465 error
= attribute_get(ctx
, req
+1);
1466 else if (g_ascii_strcasecmp(req
[0], "DELETE") == 0)
1467 error
= attribute_delete(client
, req
+1);
1468 else if (g_ascii_strcasecmp(req
[0], "LIST") == 0)
1469 error
= attribute_list(ctx
, req
+1);
1471 error
= EPWMD_COMMAND_SYNTAX
;
1474 return send_error(ctx
, error
);
1477 static gboolean
file_exists(const gchar
*filename
)
1480 gchar filebuf
[PATH_MAX
], *p
, *p2
;
1482 p
= get_key_file_string("default", "data_directory");
1483 p2
= expand_homedir(p
);
1485 snprintf(filebuf
, sizeof(filebuf
), "%s/%s", p2
, filename
);
1488 if (access(filebuf
, R_OK
) == -1)
1493 if (st
.st_size
== 0)
1499 static int iscached_command(assuan_context_t ctx
, char *line
)
1501 gchar
**req
= split_input_line(line
, " ", 0);
1504 if (!req
|| !*req
) {
1506 return send_error(ctx
, EPWMD_COMMAND_SYNTAX
);
1509 if (file_exists(req
[0]) == FALSE
) {
1511 return send_error(ctx
, EPWMD_FILE_NOT_FOUND
);
1514 gcry_md_hash_buffer(GCRY_MD_MD5
, md5file
, req
[0], strlen(req
[0]));
1517 if (cache_iscached(md5file
) == FALSE
)
1518 return send_error(ctx
, EPWMD_CACHE_NOT_FOUND
);
1523 static int clearcache_command(assuan_context_t ctx
, char *line
)
1525 struct client_s
*client
= assuan_get_pointer(ctx
);
1526 gchar
**req
= split_input_line(line
, " ", 0);
1529 if (!req
|| !*req
) {
1531 cache_clear(client
->md5file
, 2);
1535 gcry_md_hash_buffer(GCRY_MD_MD5
, md5file
, req
[0], strlen(req
[0]));
1538 if (cache_clear(md5file
, 1) == FALSE
)
1539 return send_error(ctx
, EPWMD_CACHE_NOT_FOUND
);
1544 static int cachetimeout_command(assuan_context_t ctx
, char *line
)
1548 gchar
**req
= split_input_line(line
, " ", 0);
1551 if (!req
|| !*req
|| !req
[1]) {
1553 return send_error(ctx
, EPWMD_COMMAND_SYNTAX
);
1557 timeout
= strtol(req
[0], &p
, 10);
1559 if (errno
!= 0 || *p
!= 0) {
1561 return send_error(ctx
, EPWMD_COMMAND_SYNTAX
);
1564 if (file_exists(req
[1]) == FALSE
) {
1566 return send_error(ctx
, EPWMD_FILE_NOT_FOUND
);
1569 gcry_md_hash_buffer(GCRY_MD_MD5
, md5file
, req
[1], strlen(req
[1]));
1572 if (cache_set_timeout(md5file
, timeout
) == FALSE
)
1573 return send_error(ctx
, EPWMD_CACHE_NOT_FOUND
);
1578 static int dump_command(assuan_context_t ctx
, char *line
)
1582 struct client_s
*client
= assuan_get_pointer(ctx
);
1585 if (!file_modified(client
, &error
)) {
1586 log_write("%s: %s", client
->filename
, pwmd_strerror(error
));
1587 return send_error(ctx
, error
);
1590 xmlDocDumpFormatMemory(client
->doc
, &xml
, &len
, 1);
1591 error
= assuan_send_data(ctx
, xml
, len
);
1596 static void cleanup_assuan(assuan_context_t ctx
)
1598 struct client_s
*cl
= assuan_get_pointer(ctx
);
1604 gcry_cipher_close(cl
->gh
);
1607 xmlFreeDoc(cl
->doc
);
1610 xmlFreeTextReader(cl
->reader
);
1613 g_free(cl
->filename
);
1618 gpg_error_t
register_commands(assuan_context_t ctx
)
1622 int (*handler
)(assuan_context_t
, char *line
);
1624 { "OPEN", open_command
},
1625 { "SAVE", save_command
},
1626 { "LIST", list_command
},
1627 { "STORE", store_command
},
1628 { "DELETE", delete_command
},
1629 { "GET", get_command
},
1630 { "ATTR", attr_command
},
1631 { "ISCACHED", iscached_command
},
1632 { "CLEARCACHE", clearcache_command
},
1633 { "CACHETIMEOUT", cachetimeout_command
},
1634 { "DUMP", dump_command
},
1641 for (i
=0; table
[i
].name
; i
++) {
1642 rc
= assuan_register_command (ctx
, table
[i
].name
, table
[i
].handler
);
1648 return assuan_register_bye_notify(ctx
, cleanup_assuan
);