Added command line option -D and configuration parameter
[pwmd.git] / src / commands.c
bloba0e0508a9a9de06b9c8ffc2b7dc0ab896acc9cd8
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 (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);
892 if (!*p) {
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);
901 else {
902 error = assuan_send_data(ctx, dst, g_utf8_strlen(dst, -1));
903 g_free(dst);
906 return error;
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) {
914 g_strfreev(req);
915 return send_error(ctx, error);
918 if (req[1]) {
919 error = find_elements(FALSE, client->doc, &client->reader, req+1, NULL, NULL);
921 if (error) {
922 g_strfreev(req);
923 return error;
927 depth = xmlTextReaderDepth(client->reader);
929 else {
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]);
945 if (req)
946 g_strfreev(req);
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)
960 break;
962 if (depth && depth == xmlTextReaderDepth(client->reader) &&
963 type == XML_READER_TYPE_END_ELEMENT)
964 break;
966 if (type == XML_READER_TYPE_TEXT) {
967 xmlChar *np = xmlGetNodePath(n);
969 line = element_path_to_req(account, np);
970 xmlFree(np);
971 append_to_array(&elements, &total, line);
972 memset(line, 0, g_utf8_strlen(line, -1));
973 g_free(line);
977 if (!elements) {
978 g_free(account);
979 return send_error(ctx, EPWMD_EMPTY_ELEMENT);
982 g_free(account);
983 line = g_strjoinv("\n", elements);
984 g_strfreev(elements);
985 error = assuan_send_data(ctx, line, g_utf8_strlen(line, -1));
986 g_free(line);
987 return error;
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,
995 const gchar *value)
997 xmlAttrPtr a;
998 xmlNodePtr n;
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);
1009 else
1010 xmlNodeSetContent(a->children, (xmlChar *)value);
1012 return 0;
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;
1022 gint i = 0;
1023 gchar **epath = NULL;
1024 xmlAttrPtr a;
1025 xmlNodePtr n, an;
1026 gchar *line;
1027 xmlErrorPtr xml_error;
1028 gpg_error_t error;
1029 gboolean literal;
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) {
1045 g_strfreev(epath);
1046 return error;
1049 if (epath[1]) {
1050 error = find_elements(literal, client->doc, &client->reader, epath+1, NULL, NULL);
1052 if (error) {
1053 g_strfreev(epath);
1054 return error;
1058 g_strfreev(epath);
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) {
1068 error = errno;
1069 log_write("%s(%i): g_realloc() failed", __FILE__, __LINE__);
1071 if (attrlist)
1072 g_strfreev(attrlist);
1074 return gpg_error_from_errno(error);
1077 an = a->children;
1078 attrlist[i++] = g_strdup_printf("%s\t%s", (gchar *)a->name, (gchar *)an->content);
1079 attrlist[i] = NULL;
1082 if (!attrlist)
1083 return EPWMD_EMPTY_ELEMENT;
1085 line = g_strjoinv("\n", attrlist);
1086 error = assuan_send_data(ctx, line, g_utf8_strlen(line, -1));
1087 g_free(line);
1088 g_strfreev(attrlist);
1089 return error;
1093 * req[0] - attribute
1094 * req[1] - element path
1096 static int attribute_delete(struct client_s *client, gchar **req)
1098 xmlAttrPtr a;
1099 xmlNodePtr n;
1100 gchar **epath = NULL;
1101 xmlErrorPtr xml_error;
1102 gpg_error_t error;
1103 gboolean literal;
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;
1122 goto fail;
1125 literal = is_literal_req(epath);
1127 if (find_account(literal, client->doc, &client->reader, &epath, &error) == FALSE)
1128 goto fail;
1130 if (epath[1]) {
1131 error = find_elements(literal, client->doc, &client->reader, epath+1, NULL, NULL);
1133 if (error)
1134 goto fail;
1137 g_strfreev(epath);
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;
1154 return 0;
1156 fail:
1157 g_strfreev(epath);
1158 return 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;
1174 gpg_error_t error;
1175 gboolean literal;
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;
1194 goto fail;
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)
1204 goto fail;
1206 if (dst[1]) {
1207 error = find_elements(literal, client->doc, &client->reader, dst+1, NULL, NULL);
1209 if (error)
1210 goto fail;
1213 literal = is_literal_req(src);
1215 again:
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]);
1220 if (error) {
1221 if (error == EPWMD_LIBXML_ERROR) {
1222 xml_error = xmlGetLastError();
1223 log_write("%s(%i): %s", __FILE__, __LINE__, xml_error->message);
1226 goto fail;
1229 goto again;
1231 else
1232 goto fail;
1235 if (src[1]) {
1236 error = find_elements(literal, client->doc, &client->reader, src+1,
1237 create_target_elements_cb, NULL);
1239 if (error)
1240 goto fail;
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)
1247 goto fail;
1249 error = find_elements(literal, client->doc, &client->reader, src+1, NULL, NULL);
1251 if (error)
1252 goto fail;
1255 line = g_strjoinv("\t", dst);
1256 error = add_attribute(client->reader, "target", line);
1258 if (error) {
1259 g_free(line);
1260 goto fail;
1263 g_free(line);
1264 g_strfreev(src);
1265 g_strfreev(dst);
1266 return 0;
1268 fail:
1269 g_strfreev(src);
1270 g_strfreev(dst);
1271 return error;
1275 * req[0] - account name
1276 * req[1] - new name
1278 static gpg_error_t name_attribute(struct client_s *client, gchar **req)
1280 gboolean literal;
1281 gpg_error_t error;
1282 gchar **tmp;
1284 literal = is_literal_req(req);
1285 tmp = g_strdupv(req);
1287 if (find_account(literal, client->doc, &client->reader, &tmp, &error) == FALSE) {
1288 g_strfreev(tmp);
1289 return EPWMD_ELEMENT_NOT_FOUND;
1292 g_strfreev(tmp);
1294 if (strcmp(req[0], req[1]) == 0)
1295 return 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) {
1303 g_strfreev(tmp);
1304 return EPWMD_ACCOUNT_EXISTS;
1307 g_strfreev(tmp);
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) {
1318 g_strfreev(tmp);
1319 return EPWMD_ELEMENT_NOT_FOUND;
1322 g_strfreev(tmp);
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);
1335 xmlNodePtr n;
1336 xmlChar *a;
1337 gchar **nreq = NULL;
1338 xmlErrorPtr xml_error;
1339 gpg_error_t error;
1340 gboolean literal;
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;
1349 else {
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)
1357 goto fail;
1359 if (nreq[1]) {
1360 error = find_elements(literal, client->doc, &client->reader, nreq+1, NULL, NULL);
1362 if (error)
1363 goto fail;
1366 g_strfreev(nreq);
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));
1378 xmlFree(a);
1379 return error;
1381 fail:
1382 g_strfreev(nreq);
1383 return error;
1387 * req[0] - attribute
1388 * req[1] - element path
1389 * req[2] - value
1391 static int attribute_set(struct client_s *client, gchar **req)
1393 gchar **epath = NULL;
1394 gpg_error_t error;
1395 gboolean literal;
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
1406 * document.
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)
1425 goto fail;
1427 if (epath[1]) {
1428 error = find_elements(literal, client->doc, &client->reader, epath+1, NULL, NULL);
1430 if (error)
1431 goto fail;
1434 g_strfreev(epath);
1435 return add_attribute(client->reader, req[0], req[2]);
1437 fail:
1438 g_strfreev(epath);
1439 return error;
1443 * req[0] - command
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));
1456 g_strfreev(req);
1457 return send_error(ctx, error);
1460 if (!req || !req[0] || !req[1]) {
1461 g_strfreev(req);
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);
1473 else
1474 error = EPWMD_COMMAND_SYNTAX;
1476 g_strfreev(req);
1477 return send_error(ctx, error);
1480 static gboolean file_exists(const gchar *filename)
1482 struct stat st;
1483 gchar filebuf[PATH_MAX], *p, *p2;
1485 p = get_key_file_string("default", "data_directory");
1486 p2 = expand_homedir(p);
1487 g_free(p);
1488 snprintf(filebuf, sizeof(filebuf), "%s/%s", p2, filename);
1489 g_free(p2);
1491 if (access(filebuf, R_OK) == -1)
1492 return FALSE;
1494 stat(filebuf, &st);
1496 if (st.st_size == 0)
1497 return FALSE;
1499 return TRUE;
1502 static int iscached_command(assuan_context_t ctx, char *line)
1504 gchar **req = split_input_line(line, " ", 0);
1505 guchar md5file[16];
1507 if (!req || !*req) {
1508 g_strfreev(req);
1509 return send_error(ctx, EPWMD_COMMAND_SYNTAX);
1512 if (file_exists(req[0]) == FALSE) {
1513 g_strfreev(req);
1514 return send_error(ctx, EPWMD_FILE_NOT_FOUND);
1517 gcry_md_hash_buffer(GCRY_MD_MD5, md5file, req[0], strlen(req[0]));
1518 g_strfreev(req);
1520 if (cache_iscached(md5file) == FALSE)
1521 return send_error(ctx, EPWMD_CACHE_NOT_FOUND);
1523 return 0;
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);
1530 guchar md5file[16];
1532 if (!req || !*req) {
1533 g_strfreev(req);
1534 cache_clear(client->md5file, 2);
1535 return 0;
1538 gcry_md_hash_buffer(GCRY_MD_MD5, md5file, req[0], strlen(req[0]));
1539 g_strfreev(req);
1541 if (cache_clear(md5file, 1) == FALSE)
1542 return send_error(ctx, EPWMD_CACHE_NOT_FOUND);
1544 return 0;
1547 static int cachetimeout_command(assuan_context_t ctx, char *line)
1549 guchar md5file[16];
1550 glong timeout;
1551 gchar **req = split_input_line(line, " ", 0);
1552 gchar *p;
1554 if (!req || !*req || !req[1]) {
1555 g_strfreev(req);
1556 return send_error(ctx, EPWMD_COMMAND_SYNTAX);
1559 errno = 0;
1560 timeout = strtol(req[0], &p, 10);
1562 if (errno != 0 || *p != 0) {
1563 g_strfreev(req);
1564 return send_error(ctx, EPWMD_COMMAND_SYNTAX);
1567 if (file_exists(req[1]) == FALSE) {
1568 g_strfreev(req);
1569 return send_error(ctx, EPWMD_FILE_NOT_FOUND);
1572 gcry_md_hash_buffer(GCRY_MD_MD5, md5file, req[1], strlen(req[1]));
1573 g_strfreev(req);
1575 if (cache_set_timeout(md5file, timeout) == FALSE)
1576 return send_error(ctx, EPWMD_CACHE_NOT_FOUND);
1578 return 0;
1581 static int dump_command(assuan_context_t ctx, char *line)
1583 xmlChar *xml;
1584 gssize len;
1585 struct client_s *client = assuan_get_pointer(ctx);
1586 gpg_error_t error;
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);
1598 xmlFree(xml);
1599 return error;
1602 static void cleanup_assuan(assuan_context_t ctx)
1604 struct client_s *cl = assuan_get_pointer(ctx);
1606 if (cl->xml)
1607 gcry_free(cl->xml);
1609 if (cl->gh)
1610 gcry_cipher_close(cl->gh);
1612 if (cl->doc)
1613 xmlFreeDoc(cl->doc);
1615 if (cl->reader)
1616 xmlFreeTextReader(cl->reader);
1618 if (cl->filename)
1619 g_free(cl->filename);
1621 g_free(cl);
1624 gpg_error_t register_commands(assuan_context_t ctx)
1626 static struct {
1627 const char *name;
1628 int (*handler)(assuan_context_t, char *line);
1629 } table[] = {
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 },
1641 { "INPUT", NULL },
1642 { "OUTPUT", NULL },
1643 { NULL, NULL }
1645 int i, rc;
1647 for (i=0; table[i].name; i++) {
1648 rc = assuan_register_command (ctx, table[i].name, table[i].handler);
1650 if (rc)
1651 return rc;
1654 return assuan_register_bye_notify(ctx, cleanup_assuan);