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 (disable_list_and_dump
== TRUE
)
885 return send_error(ctx
, EPWMD_EMPTY_ELEMENT
);
887 if (!file_modified(client
, &error
)) {
888 log_write("%s: %s", client
->filename
, pwmd_strerror(error
));
889 return send_error(ctx
, error
);
893 if (reset_reader(client
->doc
, &client
->reader
) == FALSE
) {
894 xml_error
= xmlGetLastError();
895 log_write("%s(%i): %s", __FILE__
, __LINE__
, xml_error
->message
);
896 return send_error(ctx
, EPWMD_LIBXML_ERROR
);
899 if (list_accounts(client
->reader
, &dst
, &error
) == FALSE
)
900 return send_error(ctx
, error
);
902 error
= assuan_send_data(ctx
, dst
, g_utf8_strlen(dst
, -1));
909 if (strchr(p
, '\t') != NULL
) {
910 if ((req
= split_input_line(p
, "\t", 0)) == NULL
)
911 return send_error(ctx
, EPWMD_COMMAND_SYNTAX
);
913 if (find_account(FALSE
, client
->doc
, &client
->reader
, &req
, &error
) == FALSE
) {
915 return send_error(ctx
, error
);
919 error
= find_elements(FALSE
, client
->doc
, &client
->reader
, req
+1, NULL
, NULL
);
927 depth
= xmlTextReaderDepth(client
->reader
);
930 if ((req
= split_input_line(p
, " ", 0)) == NULL
)
931 return send_error(ctx
, EPWMD_COMMAND_SYNTAX
);
933 if (find_account(FALSE
, client
->doc
, &client
->reader
, &req
, &error
) == FALSE
)
934 return send_error(ctx
, error
);
937 if ((n
= xmlTextReaderCurrentNode(client
->reader
)) == NULL
) {
938 xml_error
= xmlGetLastError();
939 log_write("%s(%i): %s", __FILE__
, __LINE__
, xml_error
->message
);
940 return send_error(ctx
, EPWMD_LIBXML_ERROR
);
943 account
= g_strdup(req
[0]);
948 while (xmlTextReaderNext(client
->reader
) == 1) {
949 gint type
= xmlTextReaderNodeType(client
->reader
);
951 if ((n
= xmlTextReaderCurrentNode(client
->reader
)) == NULL
) {
952 xml_error
= xmlGetLastError();
953 log_write("%s(%i): %s", __FILE__
, __LINE__
, xml_error
->message
);
954 return send_error(ctx
, EPWMD_LIBXML_ERROR
);
957 if (xmlTextReaderDepth(client
->reader
) == 1 &&
958 xmlStrcmp(n
->name
, (xmlChar
*)"account") == 0 &&
959 type
== XML_READER_TYPE_END_ELEMENT
)
962 if (depth
&& depth
== xmlTextReaderDepth(client
->reader
) &&
963 type
== XML_READER_TYPE_END_ELEMENT
)
966 if (type
== XML_READER_TYPE_TEXT
) {
967 xmlChar
*np
= xmlGetNodePath(n
);
969 line
= element_path_to_req(account
, np
);
971 append_to_array(&elements
, &total
, line
);
972 memset(line
, 0, g_utf8_strlen(line
, -1));
979 return send_error(ctx
, EPWMD_EMPTY_ELEMENT
);
983 line
= g_strjoinv("\n", elements
);
984 g_strfreev(elements
);
985 error
= assuan_send_data(ctx
, line
, g_utf8_strlen(line
, -1));
991 * The client->reader handle should be at the element in the document where
992 * the attribute will be created or modified.
994 static gpg_error_t
add_attribute(xmlTextReaderPtr reader
, const gchar
*name
,
999 xmlErrorPtr xml_error
;
1001 if ((n
= xmlTextReaderCurrentNode(reader
)) == NULL
) {
1002 xml_error
= xmlGetLastError();
1003 log_write("%s(%i): %s", __FILE__
, __LINE__
, xml_error
->message
);
1004 return EPWMD_LIBXML_ERROR
;
1007 if ((a
= xmlHasProp(n
, (xmlChar
*)name
)) == NULL
)
1008 a
= xmlNewProp(n
, (xmlChar
*)name
, (xmlChar
*)value
);
1010 xmlNodeSetContent(a
->children
, (xmlChar
*)value
);
1016 * req[0] - element path
1018 static int attribute_list(assuan_context_t ctx
, gchar
**req
)
1020 struct client_s
*client
= assuan_get_pointer(ctx
);
1021 gchar
**attrlist
= NULL
;
1023 gchar
**epath
= NULL
;
1027 xmlErrorPtr xml_error
;
1031 if (!req
|| !req
[0])
1032 return EPWMD_COMMAND_SYNTAX
;
1034 if ((epath
= split_input_line(req
[0], "\t", 0)) == NULL
) {
1036 * The first argument may be only an account.
1038 if ((epath
= split_input_line(req
[0], " ", 0)) == NULL
)
1039 return EPWMD_COMMAND_SYNTAX
;
1042 literal
= is_literal_req(epath
);
1044 if (find_account(literal
, client
->doc
, &client
->reader
, &epath
, &error
) == FALSE
) {
1050 error
= find_elements(literal
, client
->doc
, &client
->reader
, epath
+1, NULL
, NULL
);
1060 if ((n
= xmlTextReaderCurrentNode(client
->reader
)) == NULL
) {
1061 xml_error
= xmlGetLastError();
1062 log_write("%s(%i): %s", __FILE__
, __LINE__
, xml_error
->message
);
1063 return EPWMD_LIBXML_ERROR
;
1066 for (a
= n
->properties
; a
; a
= a
->next
) {
1067 if ((attrlist
= g_realloc(attrlist
, (i
+ 2) * sizeof(gchar
*))) == NULL
) {
1069 log_write("%s(%i): g_realloc() failed", __FILE__
, __LINE__
);
1072 g_strfreev(attrlist
);
1074 return gpg_error_from_errno(error
);
1078 attrlist
[i
++] = g_strdup_printf("%s\t%s", (gchar
*)a
->name
, (gchar
*)an
->content
);
1083 return EPWMD_EMPTY_ELEMENT
;
1085 line
= g_strjoinv("\n", attrlist
);
1086 error
= assuan_send_data(ctx
, line
, g_utf8_strlen(line
, -1));
1088 g_strfreev(attrlist
);
1093 * req[0] - attribute
1094 * req[1] - element path
1096 static int attribute_delete(struct client_s
*client
, gchar
**req
)
1100 gchar
**epath
= NULL
;
1101 xmlErrorPtr xml_error
;
1105 if (!req
|| !req
[0] || !req
[1])
1106 return EPWMD_COMMAND_SYNTAX
;
1108 if ((epath
= split_input_line(req
[1], "\t", 0)) == NULL
) {
1110 * The first argument may be only an account.
1112 if ((epath
= split_input_line(req
[1], " ", 0)) == NULL
)
1113 return EPWMD_COMMAND_SYNTAX
;
1117 * Don't remove the NAME attribute for the account element. To remove an
1118 * account use DELETE <account>.
1120 if (!epath
[1] && g_ascii_strcasecmp(req
[0], "NAME") == 0) {
1121 error
= EPWMD_ATTR_SYNTAX
;
1125 literal
= is_literal_req(epath
);
1127 if (find_account(literal
, client
->doc
, &client
->reader
, &epath
, &error
) == FALSE
)
1131 error
= find_elements(literal
, client
->doc
, &client
->reader
, epath
+1, NULL
, NULL
);
1139 if ((n
= xmlTextReaderCurrentNode(client
->reader
)) == NULL
) {
1140 xml_error
= xmlGetLastError();
1141 log_write("%s(%i): %s", __FILE__
, __LINE__
, xml_error
->message
);
1142 return EPWMD_LIBXML_ERROR
;
1145 if ((a
= xmlHasProp(n
, (xmlChar
*)req
[0])) == NULL
)
1146 return EPWMD_ATTR_NOT_FOUND
;
1148 if (xmlRemoveProp(a
) == -1) {
1149 xml_error
= xmlGetLastError();
1150 log_write("%s(%i): %s", __FILE__
, __LINE__
, xml_error
->message
);
1151 return EPWMD_LIBXML_ERROR
;
1162 * Creates a "target" attribute. When other commands encounter an element with
1163 * this attribute, the element path is modified to the target value. If the
1164 * source element path doesn't exist when using 'ATTR SET target', it is
1165 * created, but the destination element path must exist.
1167 * req[0] - source element path
1168 * req[1] - destination element path
1170 static gpg_error_t
target_attribute(struct client_s
*client
, gchar
**req
)
1172 gchar
**src
, **dst
, *line
;
1173 xmlErrorPtr xml_error
;
1177 if (!req
|| !req
[0] || !req
[1])
1178 return EPWMD_COMMAND_SYNTAX
;
1180 if ((src
= split_input_line(req
[0], "\t", 0)) == NULL
) {
1182 * The first argument may be only an account.
1184 if ((src
= split_input_line(req
[0], " ", 0)) == NULL
)
1185 return EPWMD_COMMAND_SYNTAX
;
1188 if ((dst
= split_input_line(req
[1], "\t", 0)) == NULL
) {
1190 * The first argument may be only an account.
1192 if ((dst
= split_input_line(req
[1], " ", 0)) == NULL
) {
1193 error
= EPWMD_COMMAND_SYNTAX
;
1198 literal
= is_literal_req(dst
);
1201 * Make sure the destination element path exists.
1203 if (find_account(literal
, client
->doc
, &client
->reader
, &dst
, &error
) == FALSE
)
1207 error
= find_elements(literal
, client
->doc
, &client
->reader
, dst
+1, NULL
, NULL
);
1213 literal
= is_literal_req(src
);
1216 if (find_account(literal
, client
->doc
, &client
->reader
, &src
, &error
) == FALSE
) {
1217 if (error
== EPWMD_ELEMENT_NOT_FOUND
) {
1218 error
= new_account(client
->doc
, src
[0]);
1221 if (error
== EPWMD_LIBXML_ERROR
) {
1222 xml_error
= xmlGetLastError();
1223 log_write("%s(%i): %s", __FILE__
, __LINE__
, xml_error
->message
);
1236 error
= find_elements(literal
, client
->doc
, &client
->reader
, src
+1,
1237 create_target_elements_cb
, NULL
);
1243 * Reset the reader to the position of the element tree now that the
1244 * elements have been created.
1246 if (find_account(literal
, client
->doc
, &client
->reader
, &src
, &error
) == FALSE
)
1249 error
= find_elements(literal
, client
->doc
, &client
->reader
, src
+1, NULL
, NULL
);
1255 line
= g_strjoinv("\t", dst
);
1256 error
= add_attribute(client
->reader
, "target", line
);
1275 * req[0] - account name
1278 static gpg_error_t
name_attribute(struct client_s
*client
, gchar
**req
)
1284 literal
= is_literal_req(req
);
1285 tmp
= g_strdupv(req
);
1287 if (find_account(literal
, client
->doc
, &client
->reader
, &tmp
, &error
) == FALSE
) {
1289 return EPWMD_ELEMENT_NOT_FOUND
;
1294 if (strcmp(req
[0], req
[1]) == 0)
1298 * Will not overwrite an existing account.
1300 tmp
= g_strdupv(req
+1);
1302 if (find_account(literal
, client
->doc
, &client
->reader
, &tmp
, &error
) == TRUE
) {
1304 return EPWMD_ACCOUNT_EXISTS
;
1310 * Whitespace not allowed in account names.
1312 if (contains_whitespace(req
[1]) == TRUE
)
1313 return EPWMD_ATTR_SYNTAX
;
1315 tmp
= g_strdupv(req
);
1317 if (find_account(literal
, client
->doc
, &client
->reader
, &tmp
, &error
) == FALSE
) {
1319 return EPWMD_ELEMENT_NOT_FOUND
;
1323 return add_attribute(client
->reader
, "name", req
[1]);
1327 * req[0] - attribute
1328 * req[1] - element path
1330 * If the element has a "target" attribute it won't be "followed".
1332 static int attribute_get(assuan_context_t ctx
, gchar
**req
)
1334 struct client_s
*client
= assuan_get_pointer(ctx
);
1337 gchar
**nreq
= NULL
;
1338 xmlErrorPtr xml_error
;
1342 if (!req
|| !req
[0] || !req
[1])
1343 return EPWMD_COMMAND_SYNTAX
;
1345 if (strchr(req
[1], '\t')) {
1346 if ((nreq
= split_input_line(req
[1], "\t", 0)) == NULL
)
1347 return EPWMD_COMMAND_SYNTAX
;
1350 if ((nreq
= split_input_line(req
[1], " ", 0)) == NULL
)
1351 return EPWMD_COMMAND_SYNTAX
;
1354 literal
= is_literal_req(nreq
);
1356 if (find_account(literal
, client
->doc
, &client
->reader
, &nreq
, &error
) == FALSE
)
1360 error
= find_elements(literal
, client
->doc
, &client
->reader
, nreq
+1, NULL
, NULL
);
1368 if ((n
= xmlTextReaderCurrentNode(client
->reader
)) == NULL
) {
1369 xml_error
= xmlGetLastError();
1370 log_write("%s(%i): %s", __FILE__
, __LINE__
, xml_error
->message
);
1371 return EPWMD_LIBXML_ERROR
;
1374 if ((a
= xmlGetProp(n
, (xmlChar
*)req
[0])) == NULL
)
1375 return EPWMD_ATTR_NOT_FOUND
;
1377 error
= assuan_send_data(ctx
, a
, xmlStrlen(a
));
1387 * req[0] - attribute
1388 * req[1] - element path
1391 static int attribute_set(struct client_s
*client
, gchar
**req
)
1393 gchar
**epath
= NULL
;
1397 if (!req
|| !req
[0] || !req
[1] || !req
[2])
1398 return EPWMD_COMMAND_SYNTAX
;
1401 * Reserved attribute names.
1403 if (strcmp(req
[0], "name") == 0) {
1405 * Only reserved for the account element. Not the rest of the
1408 if (strchr(req
[1], '\t') == NULL
)
1409 return name_attribute(client
, req
+ 1);
1411 else if (strcmp(req
[0], "target") == 0)
1412 return target_attribute(client
, req
+ 1);
1414 if ((epath
= split_input_line(req
[1], "\t", 0)) == NULL
) {
1416 * The first argument may be only an account.
1418 if ((epath
= split_input_line(req
[1], " ", 0)) == NULL
)
1419 return EPWMD_COMMAND_SYNTAX
;
1422 literal
= is_literal_req(epath
);
1424 if (find_account(literal
, client
->doc
, &client
->reader
, &epath
, &error
) == FALSE
)
1428 error
= find_elements(literal
, client
->doc
, &client
->reader
, epath
+1, NULL
, NULL
);
1435 return add_attribute(client
->reader
, req
[0], req
[2]);
1444 * req[1] - attribute name or element path if command is LIST
1445 * req[2] - element path
1446 * req[2] - element path or value
1448 static int attr_command(assuan_context_t ctx
, char *line
)
1450 struct client_s
*client
= assuan_get_pointer(ctx
);
1451 gchar
**req
= split_input_line(line
, " ", 4);
1452 gpg_error_t error
= 0;
1454 if (!file_modified(client
, &error
)) {
1455 log_write("%s: %s", client
->filename
, pwmd_strerror(error
));
1457 return send_error(ctx
, error
);
1460 if (!req
|| !req
[0] || !req
[1]) {
1462 return send_error(ctx
, EPWMD_COMMAND_SYNTAX
);
1465 if (g_ascii_strcasecmp(req
[0], "SET") == 0)
1466 error
= attribute_set(client
, req
+1);
1467 else if (g_ascii_strcasecmp(req
[0], "GET") == 0)
1468 error
= attribute_get(ctx
, req
+1);
1469 else if (g_ascii_strcasecmp(req
[0], "DELETE") == 0)
1470 error
= attribute_delete(client
, req
+1);
1471 else if (g_ascii_strcasecmp(req
[0], "LIST") == 0)
1472 error
= attribute_list(ctx
, req
+1);
1474 error
= EPWMD_COMMAND_SYNTAX
;
1477 return send_error(ctx
, error
);
1480 static gboolean
file_exists(const gchar
*filename
)
1483 gchar filebuf
[PATH_MAX
], *p
, *p2
;
1485 p
= get_key_file_string("default", "data_directory");
1486 p2
= expand_homedir(p
);
1488 snprintf(filebuf
, sizeof(filebuf
), "%s/%s", p2
, filename
);
1491 if (access(filebuf
, R_OK
) == -1)
1496 if (st
.st_size
== 0)
1502 static int iscached_command(assuan_context_t ctx
, char *line
)
1504 gchar
**req
= split_input_line(line
, " ", 0);
1507 if (!req
|| !*req
) {
1509 return send_error(ctx
, EPWMD_COMMAND_SYNTAX
);
1512 if (file_exists(req
[0]) == FALSE
) {
1514 return send_error(ctx
, EPWMD_FILE_NOT_FOUND
);
1517 gcry_md_hash_buffer(GCRY_MD_MD5
, md5file
, req
[0], strlen(req
[0]));
1520 if (cache_iscached(md5file
) == FALSE
)
1521 return send_error(ctx
, EPWMD_CACHE_NOT_FOUND
);
1526 static int clearcache_command(assuan_context_t ctx
, char *line
)
1528 struct client_s
*client
= assuan_get_pointer(ctx
);
1529 gchar
**req
= split_input_line(line
, " ", 0);
1532 if (!req
|| !*req
) {
1534 cache_clear(client
->md5file
, 2);
1538 gcry_md_hash_buffer(GCRY_MD_MD5
, md5file
, req
[0], strlen(req
[0]));
1541 if (cache_clear(md5file
, 1) == FALSE
)
1542 return send_error(ctx
, EPWMD_CACHE_NOT_FOUND
);
1547 static int cachetimeout_command(assuan_context_t ctx
, char *line
)
1551 gchar
**req
= split_input_line(line
, " ", 0);
1554 if (!req
|| !*req
|| !req
[1]) {
1556 return send_error(ctx
, EPWMD_COMMAND_SYNTAX
);
1560 timeout
= strtol(req
[0], &p
, 10);
1562 if (errno
!= 0 || *p
!= 0) {
1564 return send_error(ctx
, EPWMD_COMMAND_SYNTAX
);
1567 if (file_exists(req
[1]) == FALSE
) {
1569 return send_error(ctx
, EPWMD_FILE_NOT_FOUND
);
1572 gcry_md_hash_buffer(GCRY_MD_MD5
, md5file
, req
[1], strlen(req
[1]));
1575 if (cache_set_timeout(md5file
, timeout
) == FALSE
)
1576 return send_error(ctx
, EPWMD_CACHE_NOT_FOUND
);
1581 static int dump_command(assuan_context_t ctx
, char *line
)
1585 struct client_s
*client
= assuan_get_pointer(ctx
);
1588 if (disable_list_and_dump
== TRUE
)
1589 return send_error(ctx
, EPWMD_EMPTY_ELEMENT
);
1591 if (!file_modified(client
, &error
)) {
1592 log_write("%s: %s", client
->filename
, pwmd_strerror(error
));
1593 return send_error(ctx
, error
);
1596 xmlDocDumpFormatMemory(client
->doc
, &xml
, &len
, 1);
1597 error
= assuan_send_data(ctx
, xml
, len
);
1602 static void cleanup_assuan(assuan_context_t ctx
)
1604 struct client_s
*cl
= assuan_get_pointer(ctx
);
1610 gcry_cipher_close(cl
->gh
);
1613 xmlFreeDoc(cl
->doc
);
1616 xmlFreeTextReader(cl
->reader
);
1619 g_free(cl
->filename
);
1624 gpg_error_t
register_commands(assuan_context_t ctx
)
1628 int (*handler
)(assuan_context_t
, char *line
);
1630 { "OPEN", open_command
},
1631 { "SAVE", save_command
},
1632 { "LIST", list_command
},
1633 { "STORE", store_command
},
1634 { "DELETE", delete_command
},
1635 { "GET", get_command
},
1636 { "ATTR", attr_command
},
1637 { "ISCACHED", iscached_command
},
1638 { "CLEARCACHE", clearcache_command
},
1639 { "CACHETIMEOUT", cachetimeout_command
},
1640 { "DUMP", dump_command
},
1647 for (i
=0; table
[i
].name
; i
++) {
1648 rc
= assuan_register_command (ctx
, table
[i
].name
, table
[i
].handler
);
1654 return assuan_register_bye_notify(ctx
, cleanup_assuan
);