Check the return value of assuan_send_data().
[pwmd.git] / src / commands.c
blob4e5513b1f00cf753752b7698476b603961acfc5e
1 /* vim:tw=78:ts=8:sw=4:set ft=c: */
2 /*
3 Copyright (C) 2006-2007 Ben Kibbey <bjk@luxsci.net>
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <unistd.h>
22 #include <err.h>
23 #include <errno.h>
24 #include <sys/types.h>
25 #include <sys/stat.h>
26 #include <fcntl.h>
27 #include <ctype.h>
28 #include <glib.h>
29 #include <glib/gprintf.h>
30 #include <gcrypt.h>
32 #ifdef HAVE_CONFIG_H
33 #include <config.h>
34 #endif
36 #include "xml.h"
37 #include "common.h"
38 #include "pwmd_error.h"
39 #include "cache.h"
40 #include "commands.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)
50 gint len;
52 if (!req || !*req)
53 return FALSE;
55 len = strlen(*req) - 1;
57 if (*req[0] == '!') {
58 memmove(req[0], req[0] + 1, len);
59 req[0][len] = 0;
60 return TRUE;
63 return FALSE;
66 static gchar *file_modified(struct client_s *client, gpg_error_t *error)
68 gchar *p, *p2;
69 static gchar path[PATH_MAX];
70 struct stat st;
72 if (client->state != STATE_OPEN) {
73 *error = EPWMD_NO_FILE;
74 return NULL;
77 p = get_key_file_string("default", "data_directory");
78 p2 = expand_homedir(p);
79 g_free(p);
80 snprintf(path, sizeof(path), "%s/%s", p2, client->filename);
81 g_free(p2);
83 if (stat(path, &st) == 0 && client->mtime) {
84 if (client->mtime != st.st_mtime) {
85 *error = EPWMD_FILE_MODIFIED;
86 return NULL;
90 return path;
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));
98 return FALSE;
101 return TRUE;
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));
109 return FALSE;
112 return TRUE;
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)) {
121 case 1:
122 xml_error = xmlGetLastError();
123 log_write("%s(%i): %s", __FILE__, __LINE__, xml_error->message);
124 return send_error(ctx, EPWMD_LIBXML_ERROR);
125 case 2:
126 xml_error = xmlGetLastError();
127 log_write("%s(%i): %s", __FILE__, __LINE__, xml_error->message);
128 return send_error(ctx, EPWMD_LIBXML_ERROR);
129 default:
130 break;
133 return 0;
136 static gboolean valid_filename(const gchar *filename)
138 const gchar *p;
140 if (!filename || !*filename)
141 return FALSE;
143 for (p = filename; *p; p++) {
144 if (g_ascii_isalnum(*p) == FALSE)
145 return FALSE;
148 return TRUE;
151 gint open_file(const gchar *filename, struct stat *st)
153 gint fd;
155 if ((fd = open(filename, O_RDONLY)) == -1)
156 return -1;
158 if (stat(filename, st) == -1) {
159 close(fd);
160 return -1;
163 return fd;
166 static void cleanup(struct client_s *client)
168 if (client->doc)
169 xmlFreeDoc(client->doc);
171 if (client->reader)
172 xmlFreeTextReader(client->reader);
174 if (client->xml)
175 g_free(client->xml);
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)
187 gint fd;
188 struct stat st;
189 gchar *inbuf;
190 guchar shakey[gcrykeysize];
191 guchar tkey[gcrykeysize];
192 gint cached = 0;
193 gsize insize = 0;
194 guint iter;
195 gint timeout;
196 gchar filebuf[PATH_MAX], *p, *p2;
197 gpg_error_t error;
198 struct file_header_s {
199 guint iter;
200 guchar iv[gcryblocksize];
201 } file_header;
202 struct client_s *client = assuan_get_pointer(ctx);
203 gchar **req;
204 gchar *filename = NULL;
206 if ((req = split_input_line(line, " ", 2)) != NULL)
207 filename = req[0];
209 if (!filename || !*filename) {
210 g_strfreev(req);
211 return send_error(ctx, EPWMD_COMMAND_SYNTAX);
214 if (valid_filename(filename) == FALSE) {
215 g_strfreev(req);
216 return send_error(ctx, EPWMD_INVALID_FILENAME);
219 if (client->state == STATE_OPEN)
220 cleanup(client);
222 if ((gcryerrno = gcry_cipher_open(&client->gh, GCRY_CIPHER_AES256, GCRY_CIPHER_MODE_CBC, 0))) {
223 g_strfreev(req);
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);
230 g_free(p);
231 snprintf(filebuf, sizeof(filebuf), "%s/%s", p2, filename);
232 g_free(p2);
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));
237 g_strfreev(req);
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) {
251 error = errno;
252 log_write("%s: %s", filename, strerror(errno));
253 g_strfreev(req);
254 cleanup(client); return send_syserror(ctx, error);
256 new_doc:
257 if ((client->xml = new_document()) == NULL) {
258 error = errno;
259 log_write("%s", strerror(errno));
260 g_strfreev(req);
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));
268 g_strfreev(req);
269 cleanup(client); return send_error(ctx, EPWMD_MAX_SLOTS);
272 client->filename = g_strdup(filename);
274 if (req[1]) {
275 gcry_md_hash_buffer(GCRY_MD_SHA256, shakey, req[1], strlen(req[1]));
276 memset(req[1], 0, strlen(req[1]));
277 goto update_cache;
280 goto done;
283 if ((fd = open_file(filebuf, &st)) == -1) {
284 error = errno;
285 log_write("%s: %s", filename, strerror(errno));
286 g_strfreev(req);
287 cleanup(client); return send_syserror(ctx, error);
290 if (st.st_size == 0)
291 goto new_doc;
293 if (cache_get_key(client->md5file, shakey) == TRUE)
294 cached = 1;
295 else {
297 * No key specified and no matching filename found in the cache.
299 if (!req[1] || !*req[1]) {
300 close(fd);
301 g_strfreev(req);
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);
310 close(fd);
312 again:
313 if (!cached) {
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)))) {
320 gcry_free(inbuf);
321 memset(shakey, 0, sizeof(shakey));
322 log_write("%s(%i): %s", __FUNCTION__, __LINE__, gcry_strerror(gcryerrno));
323 g_strfreev(req);
324 cleanup(client); return gcryerrno;
327 if ((gcryerrno = gcry_cipher_setkey(client->gh, shakey, gcrykeysize))) {
328 gcry_free(inbuf);
329 memset(shakey, 0, sizeof(shakey));
330 log_write("%s(%i): %s", __FUNCTION__, __LINE__, gcry_strerror(gcryerrno));
331 g_strfreev(req);
332 cleanup(client); return gcryerrno;
335 if (decrypt_xml(client->gh, inbuf, insize, NULL, 0) == FALSE) {
336 if (cached) {
337 cached = 0;
338 goto again;
341 memset(shakey, 0, sizeof(shakey));
342 gcry_free(inbuf);
343 log_write("%s(%i): %s", __FUNCTION__, __LINE__, gcry_strerror(gcryerrno));
344 g_strfreev(req);
345 cleanup(client); return gcryerrno;
348 memcpy(tkey, shakey, sizeof(tkey));
349 tkey[0] ^= 1;
351 if ((gcryerrno = gcry_cipher_setkey(client->gh, tkey, gcrykeysize))) {
352 memset(tkey, 0, sizeof(tkey));
353 gcry_free(inbuf);
354 log_write("%s(%i): %s", __FUNCTION__, __LINE__, gcry_strerror(gcryerrno));
355 g_strfreev(req);
356 cleanup(client); return gcryerrno;
359 iter = file_header.iter;
361 while (iter-- > 0) {
362 if ((gcryerrno = gcry_cipher_setiv(client->gh, file_header.iv,
363 sizeof(file_header.iv)))) {
364 memset(tkey, 0, sizeof(tkey));
365 gcry_free(inbuf);
366 log_write("%s(%i): %s", __FUNCTION__, __LINE__, gcry_strerror(gcryerrno));
367 g_strfreev(req);
368 cleanup(client); return gcryerrno;
371 if (decrypt_xml(client->gh, inbuf, insize, NULL, 0) == FALSE) {
372 if (cached) {
373 cached = 0;
374 goto again;
377 memset(tkey, 0, sizeof(tkey));
378 gcry_free(inbuf);
379 log_write("%s(%i): %s", __FUNCTION__, __LINE__, gcry_strerror(gcryerrno));
380 g_strfreev(req);
381 cleanup(client); return gcryerrno;
385 memset(tkey, 0, sizeof(tkey));
386 client->xml = inbuf;
387 client->len = insize;
389 if (g_strncasecmp(client->xml, "<?xml version=\"1.0\"?>", 21) != 0) {
390 g_strfreev(req);
391 cleanup(client); return send_error(ctx, EPWMD_BADKEY);
394 client->filename = g_strdup(filename);
396 update_cache:
397 if (!cached) {
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));
401 g_strfreev(req);
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);
408 else
409 cache_set_timeout(client->md5file, -2);
411 memset(shakey, 0, sizeof(shakey));
413 done:
414 g_strfreev(req);
415 error = parse_xml(ctx);
417 if (client->xml) {
418 gcry_free(client->xml);
419 client->xml = NULL;
422 if (error == 0)
423 client->state = STATE_OPEN;
425 return error;
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)
432 gsize len = insize;
433 gint fd;
434 void *inbuf;
435 guchar tkey[gcrykeysize];
436 gchar *p;
437 gint error;
438 struct file_header_s {
439 guint iter;
440 guchar iv[gcryblocksize];
441 } file_header;
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);
452 insize = len;
453 gcry_create_nonce(file_header.iv, sizeof(file_header.iv));
454 memcpy(tkey, shakey, sizeof(tkey));
455 tkey[0] ^= 1;
457 if ((gcryerrno = gcry_cipher_setkey(gh, tkey, gcrykeysize))) {
458 gcry_free(inbuf);
459 log_write("%s(%i): %s", __FUNCTION__, __LINE__, gcry_strerror(gcryerrno));
460 return gcryerrno;
463 file_header.iter = iter;
465 while (iter-- > 0) {
466 if ((gcryerrno = gcry_cipher_setiv(gh, file_header.iv,
467 sizeof(file_header.iv)))) {
468 gcry_free(inbuf);
469 log_write("%s(%i): %s", __FUNCTION__, __LINE__, gcry_strerror(gcryerrno));
470 return gcryerrno;
473 if (encrypt_xml(gh, inbuf, insize, NULL, 0)
474 == FALSE) {
475 gcry_free(inbuf);
476 log_write("%s(%i): %s", __FUNCTION__, __LINE__, gcry_strerror(gcryerrno));
477 return gcryerrno;
481 if ((gcryerrno = gcry_cipher_setiv(gh, file_header.iv,
482 sizeof(file_header.iv)))) {
483 gcry_free(inbuf);
484 log_write("%s(%i): %s", __FUNCTION__, __LINE__, gcry_strerror(gcryerrno));
485 return gcryerrno;
488 if ((gcryerrno = gcry_cipher_setkey(gh, shakey, gcrykeysize))) {
489 gcry_free(inbuf);
490 log_write("%s(%i): %s", __FUNCTION__, __LINE__, gcry_strerror(gcryerrno));
491 return gcryerrno;
494 if (encrypt_xml(gh, inbuf, insize, NULL, 0)
495 == FALSE) {
496 gcry_free(inbuf);
497 log_write("%s(%i): %s", __FUNCTION__, __LINE__, gcry_strerror(gcryerrno));
498 return gcryerrno;
501 if (filename) {
502 if ((fd = open(filename, O_WRONLY|O_CREAT|O_TRUNC, 0600)) == -1) {
503 error = errno;
504 gcry_free(inbuf);
505 p = strrchr(filename, '/');
506 p++;
507 log_write("%s: %s", p, strerror(errno));
508 return gpg_error_from_errno(error);
511 else
513 * xml_import() from command line.
515 fd = STDOUT_FILENO;
517 write(fd, &file_header, sizeof(struct file_header_s));
518 write(fd, inbuf, insize);
520 if (filename)
521 close(fd);
523 gcry_free(inbuf);
524 return 0;
527 static int save_command(assuan_context_t ctx, char *line)
529 xmlChar *xmlbuf;
530 gint len;
531 gint cached = 0;
532 guchar shakey[gcrykeysize];
533 gint iter;
534 struct stat st;
535 gchar *filepath;
536 struct client_s *client = assuan_get_pointer(ctx);
537 gpg_error_t error;
538 gint timeout;
540 filepath = file_modified(client, &error);
542 if (!filepath) {
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);
551 cached = 1;
553 else {
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)
561 iter = 0;
563 error = do_xml_encrypt(client, client->gh, filepath, xmlbuf, len, shakey, iter);
565 if (error) {
566 memset(shakey, 0, sizeof(shakey));
567 xmlFree(xmlbuf);
568 return send_error(ctx, error);
571 xmlFree(xmlbuf);
572 stat(filepath, &st);
573 client->mtime = st.st_mtime;
574 timeout = get_key_file_integer(client->filename, "cache_timeout");
576 if (cached) {
577 memset(shakey, 0, sizeof(shakey));
578 cache_reset_timeout(client->md5file, timeout);
579 return 0;
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);
590 return 0;
593 static gboolean contains_whitespace(const gchar *str)
595 const gchar *p = str;
597 for (; *p; p++) {
598 if (g_ascii_isspace(*p) == TRUE) {
599 return TRUE;
603 return FALSE;
606 static void delete_node(xmlNodePtr n)
608 xmlUnlinkNode(n);
609 xmlFreeNode(n);
612 static int delete_command(assuan_context_t ctx, char *line)
614 xmlNodePtr n;
615 struct client_s *client = assuan_get_pointer(ctx);
616 gchar **req = split_input_line(line, "\t", 0);
617 gpg_error_t error;
618 gboolean literal;
620 if (!file_modified(client, &error)) {
621 log_write("%s: %s", client->filename, pwmd_strerror(error));
622 g_strfreev(req);
623 return send_error(ctx, error);
626 if (!req || !*req) {
627 g_strfreev(req);
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) {
634 g_strfreev(req);
635 return send_error(ctx, EPWMD_ELEMENT_NOT_FOUND);
638 n = xmlTextReaderCurrentNode(client->reader);
641 * No sub-node defined. Remove the entire node (account).
643 if (!req[1]) {
644 if (n)
645 delete_node(n);
647 g_strfreev(req);
648 return 0;
651 error = find_elements(literal, client->doc, &client->reader, req+1, NULL, NULL);
652 g_strfreev(req);
654 if (error)
655 return error;
657 n = xmlTextReaderCurrentNode(client->reader);
659 if (n)
660 delete_node(n);
662 return 0;
665 static int store_command(assuan_context_t ctx, char *line)
667 struct client_s *client = assuan_get_pointer(ctx);
668 gchar **req;
669 gpg_error_t error;
670 guchar *result;
671 gsize len;
672 gboolean literal;
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);
681 if (error)
682 return send_error(ctx, error);
684 req = split_input_line(result, "\t", 0);
686 if (!req || !*req)
687 return send_error(ctx, EPWMD_COMMAND_SYNTAX);
689 literal = is_literal_req(req);
691 again:
692 if (find_account(literal, client->doc, &client->reader, &req, &error) == FALSE) {
693 if (contains_whitespace(req[0]) == TRUE) {
694 g_strfreev(req);
695 return send_error(ctx, EPWMD_INVALID_ELEMENT);
698 error = new_account(client->doc, req[0]);
700 if (error) {
701 g_strfreev(req);
702 return send_error(ctx, error);
705 goto again;
708 if (req[1])
709 error = find_elements(literal, client->doc, &client->reader, req+1,
710 create_elements_cb, NULL);
712 g_strfreev(req);
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)
719 xmlNodePtr n;
720 xmlErrorPtr xml_error;
721 gboolean literal;
722 gpg_error_t error;
723 struct client_s *client = assuan_get_pointer(ctx);
724 gchar **tmp;
726 literal = is_literal_req(*req);
728 if (find_account(literal, client->doc, reader, req, &error) == FALSE)
729 return error;
731 tmp = *req;
732 error = find_elements(literal, client->doc, reader, tmp+1, NULL, NULL);
734 if (error)
735 return error;
737 if ((n = xmlTextReaderCurrentNode(*reader)) == NULL)
738 return EPWMD_LIBXML_ERROR;
740 switch (xmlTextReaderNext(*reader)) {
741 case -1:
742 xml_error = xmlGetLastError();
743 log_write("%s(%i): %s", __FILE__, __LINE__, xml_error->message);
744 return EPWMD_LIBXML_ERROR;
745 case 0:
746 return EPWMD_EMPTY_ELEMENT;
747 default:
748 break;
751 switch (xmlTextReaderNodeType(*reader)) {
752 case XML_READER_TYPE_END_ELEMENT:
754 * May be an empty element after an ATTR DELETE target command.
756 return 0;
757 case XML_READER_TYPE_TEXT:
758 break;
759 case -1:
760 return EPWMD_LIBXML_ERROR;
761 default:
762 if (!quiet) {
763 if (n->children && !list)
764 return EPWMD_TRAILING_ELEMENT;
765 else if (!n->children && !list)
766 return EPWMD_INVALID_ELEMENT;
769 // FIXME????
770 //return -1;
771 return 0;
774 if ((*content = xmlNodeGetContent(n)) == NULL)
775 return EPWMD_EMPTY_ELEMENT;
777 return 0;
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;
787 gpg_error_t error;
789 error = do_get_command(ctx, reader, req, &content, quiet, 0);
791 if (error) {
792 if (content)
793 xmlFree(content);
795 return error;
798 if (!content)
799 return EPWMD_EMPTY_ELEMENT;
801 error = assuan_send_data(ctx, content, xmlStrlen(content));
802 xmlFree(content);
803 return error;
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);
810 gpg_error_t error;
812 if (!file_modified(client, &error)) {
813 log_write("%s: %s", client->filename, pwmd_strerror(error));
814 g_strfreev(req);
815 return send_error(ctx, error);
818 if (!req || !*req) {
819 g_strfreev(req);
820 return send_error(ctx, EPWMD_COMMAND_SYNTAX);
823 error = get(ctx, &client->reader, &req, 0);
824 g_strfreev(req);
825 return send_error(ctx, error);
828 static gchar *element_path_to_req(const gchar *account, xmlChar *path)
830 xmlChar *p = path;
831 gint n;
832 gchar *buf;
834 if (!p)
835 return NULL;
837 for (n = 0; *p && n < 3; p++) {
838 if (*p == '/')
839 n++;
842 if (strstr((gchar *)p, "text()") != NULL)
843 p[xmlStrlen(p) - 7] = 0;
845 for (n = 0; p[n]; n++) {
846 if (p[n] == '/')
847 p[n] = '\t';
850 buf = g_strdup_printf("%s\t%s", account, p);
851 return buf;
854 static gboolean append_to_array(gchar ***array, gint *total, const gchar *str)
856 gchar **a;
857 gint t = *total;
859 if ((a = g_realloc(*array, (t + 2) * sizeof(gchar *))) == NULL)
860 return FALSE;
862 a[t++] = g_strdup(str);
863 a[t] = NULL;
864 *total = t;
865 *array = a;
866 return TRUE;
869 static int list_command(assuan_context_t ctx, char *str)
871 gchar *dst = NULL;
872 gchar *p = str;
873 gchar **elements = NULL;
874 gchar *account;
875 gint total = 0;
876 xmlNodePtr n;
877 gchar **req = NULL;
878 gchar *line;
879 xmlErrorPtr xml_error;
880 gint depth = 0;
881 struct client_s *client = assuan_get_pointer(ctx);
882 gpg_error_t error;
884 if (!file_modified(client, &error)) {
885 log_write("%s: %s", client->filename, pwmd_strerror(error));
886 return send_error(ctx, error);
889 if (!*p) {
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);
898 else {
899 error = assuan_send_data(ctx, dst, g_utf8_strlen(dst, -1));
900 g_free(dst);
903 return error;
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) {
911 g_strfreev(req);
912 return send_error(ctx, error);
915 if (req[1]) {
916 error = find_elements(FALSE, client->doc, &client->reader, req+1, NULL, NULL);
918 if (error) {
919 g_strfreev(req);
920 return error;
924 depth = xmlTextReaderDepth(client->reader);
926 else {
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]);
942 if (req)
943 g_strfreev(req);
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)
957 break;
959 if (depth && depth == xmlTextReaderDepth(client->reader) &&
960 type == XML_READER_TYPE_END_ELEMENT)
961 break;
963 if (type == XML_READER_TYPE_TEXT) {
964 xmlChar *np = xmlGetNodePath(n);
966 line = element_path_to_req(account, np);
967 xmlFree(np);
968 append_to_array(&elements, &total, line);
969 memset(line, 0, g_utf8_strlen(line, -1));
970 g_free(line);
974 if (!elements) {
975 g_free(account);
976 return send_error(ctx, EPWMD_EMPTY_ELEMENT);
979 g_free(account);
980 line = g_strjoinv("\n", elements);
981 g_strfreev(elements);
982 error = assuan_send_data(ctx, line, g_utf8_strlen(line, -1));
983 g_free(line);
984 return error;
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,
992 const gchar *value)
994 xmlAttrPtr a;
995 xmlNodePtr n;
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);
1006 else
1007 xmlNodeSetContent(a->children, (xmlChar *)value);
1009 return 0;
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;
1019 gint i = 0;
1020 gchar **epath = NULL;
1021 xmlAttrPtr a;
1022 xmlNodePtr n, an;
1023 gchar *line;
1024 xmlErrorPtr xml_error;
1025 gpg_error_t error;
1026 gboolean literal;
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) {
1042 g_strfreev(epath);
1043 return error;
1046 if (epath[1]) {
1047 error = find_elements(literal, client->doc, &client->reader, epath+1, NULL, NULL);
1049 if (error) {
1050 g_strfreev(epath);
1051 return error;
1055 g_strfreev(epath);
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) {
1065 error = errno;
1066 log_write("%s(%i): g_realloc() failed", __FILE__, __LINE__);
1068 if (attrlist)
1069 g_strfreev(attrlist);
1071 return gpg_error_from_errno(error);
1074 an = a->children;
1075 attrlist[i++] = g_strdup_printf("%s\t%s", (gchar *)a->name, (gchar *)an->content);
1076 attrlist[i] = NULL;
1079 if (!attrlist)
1080 return EPWMD_EMPTY_ELEMENT;
1082 line = g_strjoinv("\n", attrlist);
1083 error = assuan_send_data(ctx, line, g_utf8_strlen(line, -1));
1084 g_free(line);
1085 g_strfreev(attrlist);
1086 return error;
1090 * req[0] - attribute
1091 * req[1] - element path
1093 static int attribute_delete(struct client_s *client, gchar **req)
1095 xmlAttrPtr a;
1096 xmlNodePtr n;
1097 gchar **epath = NULL;
1098 xmlErrorPtr xml_error;
1099 gpg_error_t error;
1100 gboolean literal;
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;
1119 goto fail;
1122 literal = is_literal_req(epath);
1124 if (find_account(literal, client->doc, &client->reader, &epath, &error) == FALSE)
1125 goto fail;
1127 if (epath[1]) {
1128 error = find_elements(literal, client->doc, &client->reader, epath+1, NULL, NULL);
1130 if (error)
1131 goto fail;
1134 g_strfreev(epath);
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;
1151 return 0;
1153 fail:
1154 g_strfreev(epath);
1155 return 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;
1171 gpg_error_t error;
1172 gboolean literal;
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;
1191 goto fail;
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)
1201 goto fail;
1203 if (dst[1]) {
1204 error = find_elements(literal, client->doc, &client->reader, dst+1, NULL, NULL);
1206 if (error)
1207 goto fail;
1210 literal = is_literal_req(src);
1212 again:
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]);
1217 if (error) {
1218 if (error == EPWMD_LIBXML_ERROR) {
1219 xml_error = xmlGetLastError();
1220 log_write("%s(%i): %s", __FILE__, __LINE__, xml_error->message);
1223 goto fail;
1226 goto again;
1228 else
1229 goto fail;
1232 if (src[1]) {
1233 error = find_elements(literal, client->doc, &client->reader, src+1,
1234 create_target_elements_cb, NULL);
1236 if (error)
1237 goto fail;
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)
1244 goto fail;
1246 error = find_elements(literal, client->doc, &client->reader, src+1, NULL, NULL);
1248 if (error)
1249 goto fail;
1252 line = g_strjoinv("\t", dst);
1253 error = add_attribute(client->reader, "target", line);
1255 if (error) {
1256 g_free(line);
1257 goto fail;
1260 g_free(line);
1261 g_strfreev(src);
1262 g_strfreev(dst);
1263 return 0;
1265 fail:
1266 g_strfreev(src);
1267 g_strfreev(dst);
1268 return error;
1272 * req[0] - account name
1273 * req[1] - new name
1275 static gpg_error_t name_attribute(struct client_s *client, gchar **req)
1277 gboolean literal;
1278 gpg_error_t error;
1279 gchar **tmp;
1281 literal = is_literal_req(req);
1282 tmp = g_strdupv(req);
1284 if (find_account(literal, client->doc, &client->reader, &tmp, &error) == FALSE) {
1285 g_strfreev(tmp);
1286 return EPWMD_ELEMENT_NOT_FOUND;
1289 g_strfreev(tmp);
1291 if (strcmp(req[0], req[1]) == 0)
1292 return 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) {
1300 g_strfreev(tmp);
1301 return EPWMD_ACCOUNT_EXISTS;
1304 g_strfreev(tmp);
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) {
1315 g_strfreev(tmp);
1316 return EPWMD_ELEMENT_NOT_FOUND;
1319 g_strfreev(tmp);
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);
1332 xmlNodePtr n;
1333 xmlChar *a;
1334 gchar **nreq = NULL;
1335 xmlErrorPtr xml_error;
1336 gpg_error_t error;
1337 gboolean literal;
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;
1346 else {
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)
1354 goto fail;
1356 if (nreq[1]) {
1357 error = find_elements(literal, client->doc, &client->reader, nreq+1, NULL, NULL);
1359 if (error)
1360 goto fail;
1363 g_strfreev(nreq);
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));
1375 xmlFree(a);
1376 return error;
1378 fail:
1379 g_strfreev(nreq);
1380 return error;
1384 * req[0] - attribute
1385 * req[1] - element path
1386 * req[2] - value
1388 static int attribute_set(struct client_s *client, gchar **req)
1390 gchar **epath = NULL;
1391 gpg_error_t error;
1392 gboolean literal;
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
1403 * document.
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)
1422 goto fail;
1424 if (epath[1]) {
1425 error = find_elements(literal, client->doc, &client->reader, epath+1, NULL, NULL);
1427 if (error)
1428 goto fail;
1431 g_strfreev(epath);
1432 return add_attribute(client->reader, req[0], req[2]);
1434 fail:
1435 g_strfreev(epath);
1436 return error;
1440 * req[0] - command
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));
1453 g_strfreev(req);
1454 return send_error(ctx, error);
1457 if (!req || !req[0] || !req[1]) {
1458 g_strfreev(req);
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);
1470 else
1471 error = EPWMD_COMMAND_SYNTAX;
1473 g_strfreev(req);
1474 return send_error(ctx, error);
1477 static gboolean file_exists(const gchar *filename)
1479 struct stat st;
1480 gchar filebuf[PATH_MAX], *p, *p2;
1482 p = get_key_file_string("default", "data_directory");
1483 p2 = expand_homedir(p);
1484 g_free(p);
1485 snprintf(filebuf, sizeof(filebuf), "%s/%s", p2, filename);
1486 g_free(p2);
1488 if (access(filebuf, R_OK) == -1)
1489 return FALSE;
1491 stat(filebuf, &st);
1493 if (st.st_size == 0)
1494 return FALSE;
1496 return TRUE;
1499 static int iscached_command(assuan_context_t ctx, char *line)
1501 gchar **req = split_input_line(line, " ", 0);
1502 guchar md5file[16];
1504 if (!req || !*req) {
1505 g_strfreev(req);
1506 return send_error(ctx, EPWMD_COMMAND_SYNTAX);
1509 if (file_exists(req[0]) == FALSE) {
1510 g_strfreev(req);
1511 return send_error(ctx, EPWMD_FILE_NOT_FOUND);
1514 gcry_md_hash_buffer(GCRY_MD_MD5, md5file, req[0], strlen(req[0]));
1515 g_strfreev(req);
1517 if (cache_iscached(md5file) == FALSE)
1518 return send_error(ctx, EPWMD_CACHE_NOT_FOUND);
1520 return 0;
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);
1527 guchar md5file[16];
1529 if (!req || !*req) {
1530 g_strfreev(req);
1531 cache_clear(client->md5file, 2);
1532 return 0;
1535 gcry_md_hash_buffer(GCRY_MD_MD5, md5file, req[0], strlen(req[0]));
1536 g_strfreev(req);
1538 if (cache_clear(md5file, 1) == FALSE)
1539 return send_error(ctx, EPWMD_CACHE_NOT_FOUND);
1541 return 0;
1544 static int cachetimeout_command(assuan_context_t ctx, char *line)
1546 guchar md5file[16];
1547 glong timeout;
1548 gchar **req = split_input_line(line, " ", 0);
1549 gchar *p;
1551 if (!req || !*req || !req[1]) {
1552 g_strfreev(req);
1553 return send_error(ctx, EPWMD_COMMAND_SYNTAX);
1556 errno = 0;
1557 timeout = strtol(req[0], &p, 10);
1559 if (errno != 0 || *p != 0) {
1560 g_strfreev(req);
1561 return send_error(ctx, EPWMD_COMMAND_SYNTAX);
1564 if (file_exists(req[1]) == FALSE) {
1565 g_strfreev(req);
1566 return send_error(ctx, EPWMD_FILE_NOT_FOUND);
1569 gcry_md_hash_buffer(GCRY_MD_MD5, md5file, req[1], strlen(req[1]));
1570 g_strfreev(req);
1572 if (cache_set_timeout(md5file, timeout) == FALSE)
1573 return send_error(ctx, EPWMD_CACHE_NOT_FOUND);
1575 return 0;
1578 static int dump_command(assuan_context_t ctx, char *line)
1580 xmlChar *xml;
1581 gssize len;
1582 struct client_s *client = assuan_get_pointer(ctx);
1583 gpg_error_t error;
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);
1592 xmlFree(xml);
1593 return error;
1596 static void cleanup_assuan(assuan_context_t ctx)
1598 struct client_s *cl = assuan_get_pointer(ctx);
1600 if (cl->xml)
1601 gcry_free(cl->xml);
1603 if (cl->gh)
1604 gcry_cipher_close(cl->gh);
1606 if (cl->doc)
1607 xmlFreeDoc(cl->doc);
1609 if (cl->reader)
1610 xmlFreeTextReader(cl->reader);
1612 if (cl->filename)
1613 g_free(cl->filename);
1615 g_free(cl);
1618 gpg_error_t register_commands(assuan_context_t ctx)
1620 static struct {
1621 const char *name;
1622 int (*handler)(assuan_context_t, char *line);
1623 } table[] = {
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 },
1635 { "INPUT", NULL },
1636 { "OUTPUT", NULL },
1637 { NULL, NULL }
1639 int i, rc;
1641 for (i=0; table[i].name; i++) {
1642 rc = assuan_register_command (ctx, table[i].name, table[i].handler);
1644 if (rc)
1645 return rc;
1648 return assuan_register_bye_notify(ctx, cleanup_assuan);