projects/pwm

view pwfile.c @ 20:efef93e54c5f

Automatically save the database when receiving a fatal signal
author Guido Berhoerster <guido+pwm@berhoerster.name>
date Wed Sep 06 13:56:11 2017 +0200 (2017-09-06)
parents cf81eb0c2d5a
children ec01c579024a
line source
1 /*
2 * Copyright (C) 2017 Guido Berhoerster <guido+pwm@berhoerster.name>
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining
5 * a copy of this software and associated documentation files (the
6 * "Software"), to deal in the Software without restriction, including
7 * without limitation the rights to use, copy, modify, merge, publish,
8 * distribute, sublicense, and/or sell copies of the Software, and to
9 * permit persons to whom the Software is furnished to do so, subject to
10 * the following conditions:
11 *
12 * The above copyright notice and this permission notice shall be included
13 * in all copies or substantial portions of the Software.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
18 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
19 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
20 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
21 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 */
24 #include "compat.h"
26 #include <ctype.h>
27 #ifdef HAVE_ERR_H
28 #include <err.h>
29 #endif /* HAVE_ERR_H */
30 #include <errno.h>
31 #include <stdio.h>
32 #include <string.h>
33 #include <sys/stat.h>
34 #ifdef HAVE_SYS_TREE_H
35 #include <sys/tree.h>
36 #endif
37 #include <sys/utsname.h>
38 #include <unistd.h>
39 #include <time.h>
41 #include "pwfile.h"
42 #include "util.h"
44 struct record_id_entry {
45 RB_ENTRY(record_id_entry) record_id_entry;
46 unsigned int id;
47 unsigned char uuid[PWS3_UUID_SIZE];
48 };
50 RB_HEAD(record_id_tree, record_id_entry);
52 static int record_id_entry_cmp(struct record_id_entry *,
53 struct record_id_entry *);
54 RB_PROTOTYPE_STATIC(record_id_tree, record_id_entry, record_id_entry,
55 record_id_entry_cmp)
56 RB_GENERATE_STATIC(record_id_tree, record_id_entry, record_id_entry,
57 record_id_entry_cmp)
59 static int
60 record_id_entry_cmp(struct record_id_entry *entry1,
61 struct record_id_entry *entry2)
62 {
63 if (entry1->id > entry2->id) {
64 return (-1);
65 } else if (entry1->id < entry2->id) {
66 return (1);
67 }
68 return (0);
69 }
71 static int
72 pws_record_cmp(const void *p1, const void *p2)
73 {
74 int retval;
75 struct pws3_record *record1 = *(struct pws3_record **)p1;
76 struct pws3_record *record2 = *(struct pws3_record **)p2;
77 struct pws3_field *group_field1;
78 const char *group1;
79 struct pws3_field *group_field2;
80 const char *group2;
81 struct pws3_field *title_field1;
82 const char *title1;
83 struct pws3_field *title_field2;
84 const char *title2;
86 group_field1 = pws3_record_get_field(record1, PWS3_RECORD_FIELD_GROUP);
87 group1 = (group_field1 != NULL) ? pws3_field_get_text(group_field1) :
88 "";
89 group_field2 = pws3_record_get_field(record2, PWS3_RECORD_FIELD_GROUP);
90 group2 = (group_field2 != NULL) ? pws3_field_get_text(group_field2) :
91 "";
92 retval = strcmp(group1, group2);
93 if (retval != 0) {
94 return (retval);
95 }
97 title_field1 = pws3_record_get_field(record1, PWS3_RECORD_FIELD_TITLE);
98 title1 = (title_field1 != NULL) ? pws3_field_get_text(title_field1) :
99 "";
100 title_field2 = pws3_record_get_field(record2, PWS3_RECORD_FIELD_TITLE);
101 title2 = (title_field2 != NULL) ? pws3_field_get_text(title_field2) :
102 "";
103 return (strcmp(title1, title2));
104 }
106 static void
107 record_id_tree_clear(struct record_id_tree *tree)
108 {
109 struct record_id_entry *entry;
110 struct record_id_entry *entry_tmp;
112 RB_FOREACH_SAFE(entry, record_id_tree, tree, entry_tmp) {
113 RB_REMOVE(record_id_tree, tree, entry);
114 free(entry);
115 }
116 }
118 static void
119 record_id_tree_destroy(struct record_id_tree *tree)
120 {
121 if (tree == NULL) {
122 return;
123 }
125 record_id_tree_clear(tree);
126 free(tree);
127 }
129 static const unsigned char *
130 record_id_tree_get_uuid(struct record_id_tree *tree, unsigned int id)
131 {
132 struct record_id_entry *entry;
134 entry = RB_FIND(record_id_tree, tree,
135 &(struct record_id_entry){ .id = id });
136 if (entry == NULL) {
137 return (NULL);
138 }
140 return (entry->uuid);
141 }
143 void
144 pwfile_init(struct pwm_ctx *ctx)
145 {
146 ctx->file = pws3_file_create();
147 if (ctx->file == NULL) {
148 err(1, "pws3_file_create");
149 }
151 ctx->next_id = 1;
153 ctx->record_id_tree = xmalloc(sizeof (struct record_id_tree));
154 RB_INIT(ctx->record_id_tree);
155 }
157 void
158 pwfile_destroy(struct pwm_ctx *ctx)
159 {
160 record_id_tree_destroy(ctx->record_id_tree);
161 ctx->record_id_tree = NULL;
162 pws3_file_destroy(ctx->file);
163 ctx->file = NULL;
164 }
166 int
167 pwfile_read_file(struct pwm_ctx *ctx, FILE *fp)
168 {
169 struct pws3_record *pws3_record;
170 size_t record_list_len = 0;
171 struct pws3_record **pws3_record_list;
172 size_t i;
173 struct pws3_field *uuid_field;
174 const unsigned char *uuid;
175 struct record_id_entry *entry;
177 if (pws3_file_read_stream(ctx->file, ctx->password, fp) != 0) {
178 fprintf(stderr, "failed to read password database: %s\n",
179 pws3_file_get_error_message(ctx->file));
180 return (-1);
181 }
183 record_id_tree_clear(ctx->record_id_tree);
185 /* sort records by group and title */
186 for (pws3_record = pws3_file_first_record(ctx->file);
187 pws3_record != NULL;
188 pws3_record = pws3_file_next_record(ctx->file, pws3_record)) {
189 record_list_len++;
190 }
191 pws3_record_list = xmalloc(sizeof (struct pws3_record *) *
192 record_list_len);
193 for (pws3_record = pws3_file_first_record(ctx->file), i = 0;
194 pws3_record != NULL;
195 pws3_record = pws3_file_next_record(ctx->file, pws3_record)) {
196 pws3_record_list[i++] = pws3_record;
197 }
198 qsort(pws3_record_list, record_list_len,
199 sizeof (struct pws3_record *), pws_record_cmp);
201 /* build the tree of record IDs */
202 for (i = 0; i < record_list_len; i++) {
203 uuid_field = pws3_record_get_field(pws3_record_list[i],
204 PWS3_RECORD_FIELD_UUID);
205 uuid = pws3_field_get_uuid(uuid_field);
207 entry = xmalloc(sizeof (struct record_id_entry));
208 entry->id = ctx->next_id++;
209 memcpy(entry->uuid, uuid, sizeof (entry->uuid));
211 RB_INSERT(record_id_tree, ctx->record_id_tree, entry);
212 }
214 free(pws3_record_list);
216 ctx->unsaved_changes = 0;
218 return (0);
219 }
221 static int
222 make_backup_copy(const char *filename)
223 {
224 int retval = -1;
225 FILE *fp_orig = NULL;
226 char *backup_filename = NULL;
227 char *tmpfilename = NULL;
228 mode_t old_mode;
229 int fd_backup = -1;
230 unsigned char buf[BUFSIZ];
231 size_t read_len;
232 FILE *fp_backup = NULL;
234 fp_orig = fopen(filename, "r");
235 if (fp_orig == NULL) {
236 if (errno != ENOENT) {
237 warn("fopen");
238 return (-1);
239 }
240 return (0);
241 }
243 xasprintf(&backup_filename, "%s~", filename);
244 xasprintf(&tmpfilename, "%s.XXXXXX", filename);
246 /* create temporary file */
247 old_mode = umask(S_IRWXG | S_IRWXO);
248 fd_backup = mkstemp(tmpfilename);
249 umask(old_mode);
250 if (fd_backup == -1) {
251 warn("mkstemp");
252 goto out;
253 }
254 fp_backup = fdopen(fd_backup, "w");
255 if (fp_backup == NULL) {
256 warn("fdopen");
257 goto out;
258 }
260 /* copy file contents */
261 while (!feof(fp_orig)) {
262 read_len = fread(buf, 1, sizeof (buf), fp_orig);
263 if ((read_len < sizeof (buf)) && ferror(fp_orig)) {
264 warn("fread");
265 goto out;
266 }
267 if (fwrite(buf, 1, read_len, fp_backup) != read_len) {
268 warn("fwrite");
269 goto out;
270 }
271 }
272 if (fflush(fp_backup) != 0) {
273 warn("fflush");
274 goto out;
275 }
276 if (fsync(fileno(fp_backup)) != 0) {
277 warn("fsync");
278 goto out;
279 }
281 retval = 0;
283 out:
284 if ((fd_backup != -1) && (fp_backup == NULL)) {
285 close(fd_backup);
286 }
287 if (fp_backup != NULL) {
288 fclose(fp_backup);
289 }
290 if (fp_orig != NULL) {
291 fclose(fp_orig);
292 }
293 if (retval == 0) {
294 /* rename temporary file and overwrite existing file */
295 if (rename(tmpfilename, backup_filename) != 0) {
296 warn("rename");
297 retval = -1;
298 }
299 }
300 if ((retval != 0) && ((fd_backup != -1) || (fp_backup != NULL))) {
301 unlink(tmpfilename);
302 }
303 free(tmpfilename);
304 free(backup_filename);
306 return (retval);
307 }
309 static void
310 update_file_metadata(struct pws3_file *file)
311 {
312 struct pws3_field *save_app_field;
313 struct pws3_field *save_timestamp_field;
314 char *logname;
315 const char default_username[] = "unknown user";
316 const char *username;
317 size_t username_len;
318 struct utsname utsn;
319 const char default_hostname[] = "unknown host";
320 const char *hostname;
321 char user_host[1024];
322 struct pws3_field *save_user_host_field;
323 struct pws3_field *save_user_field;
324 struct pws3_field *save_host_field;
326 save_app_field =
327 pws3_field_create(1, PWS3_HEADER_FIELD_SAVE_APPLICATION);
328 if (save_app_field == NULL) {
329 err(1, "pws3_field_create");
330 }
331 if (pws3_field_set_text(save_app_field, PACKAGE " V" VERSION) !=
332 0) {
333 err(1, "pws3_field_set_text");
334 }
335 pws3_file_set_header_field(file, save_app_field);
337 save_timestamp_field =
338 pws3_field_create(1, PWS3_HEADER_FIELD_SAVE_TIMESTAMP);
339 if (save_timestamp_field == NULL) {
340 err(1, "pws3_field_create");
341 }
342 pws3_field_set_time(save_timestamp_field, time(NULL));
343 pws3_file_set_header_field(file, save_timestamp_field);
345 logname = getenv("LOGNAME");
346 if (logname == NULL) {
347 logname = getlogin();
348 }
349 username = (logname != NULL) ? logname : default_username;
350 username_len = MIN(strlen(username), sizeof (user_host) - 4 - 1);
351 hostname = (uname(&utsn) == 0) ? utsn.nodename : default_hostname;
352 snprintf(user_host, sizeof (user_host), "%04zx%s%s", username_len,
353 username, hostname);
355 save_user_host_field =
356 pws3_field_create(1, PWS3_HEADER_FIELD_SAVE_USER_HOST);
357 if (save_user_host_field == NULL) {
358 err(1, "pws3_field_create");
359 }
360 if (pws3_field_set_text(save_user_host_field, user_host) != 0) {
361 err(1, "pws3_field_set_text");
362 }
363 pws3_file_set_header_field(file, save_user_host_field);
365 save_user_field = pws3_field_create(1, PWS3_HEADER_FIELD_SAVE_USER);
366 if (save_user_field == NULL) {
367 err(1, "pws3_field_create");
368 }
369 if (pws3_field_set_text(save_user_field, logname) != 0) {
370 err(1, "pws3_field_set_text");
371 }
372 pws3_file_set_header_field(file, save_user_field);
374 save_host_field = pws3_field_create(1, PWS3_HEADER_FIELD_SAVE_HOST);
375 if (save_host_field == NULL) {
376 err(1, "pws3_field_create");
377 }
378 if (pws3_field_set_text(save_host_field, hostname) != 0) {
379 err(1, "pws3_field_set_text");
380 }
381 pws3_file_set_header_field(file, save_host_field);
382 }
384 static int
385 write_file(struct pwm_ctx *ctx, const char *filename)
386 {
387 int retval = -1;
388 char *tmpfilename = NULL;
389 mode_t old_mode;
390 int fd = -1;
391 FILE *fp = NULL;
393 xasprintf(&tmpfilename, "%s.XXXXXX", filename);
395 /* create temporary file */
396 old_mode = umask(S_IRWXG | S_IRWXO);
397 fd = mkstemp(tmpfilename);
398 if (fd == -1) {
399 warn("mkstemp");
400 goto out;
401 }
402 umask(old_mode);
403 fp = fdopen(fd, "w");
404 if (fp == NULL) {
405 warn("fdopen");
406 goto out;
407 }
409 /* write contents */
410 if (pws3_file_write_stream(ctx->file, ctx->password, 10000, fp) != 0) {
411 warnx("pws3_file_write_stream: %s",
412 pws3_file_get_error_message(ctx->file));
413 goto out;
414 }
415 if (fflush(fp) != 0) {
416 warn("fflush");
417 goto out;
418 }
419 if (fsync(fileno(fp)) != 0) {
420 warn("fsync");
421 goto out;
422 }
424 retval = 0;
426 out:
427 if ((fd != -1) && (fp == NULL)) {
428 close(fd);
429 }
430 if (fp != NULL) {
431 fclose(fp);
432 }
433 if (retval == 0) {
434 /* rename temporary file and overwrite existing file */
435 if (rename(tmpfilename, filename) != 0) {
436 warn("rename");
437 retval = -1;
438 }
439 }
440 if ((retval != 0) && ((fd != -1) || (fp != NULL))) {
441 unlink(tmpfilename);
442 }
443 free(tmpfilename);
445 return (retval);
446 }
448 int
449 pwfile_write_autosave_file(struct pwm_ctx *ctx)
450 {
451 int retval;
452 char *autosave_filename;
454 xasprintf(&autosave_filename, "%s/autosave.psafe3", ctx->dirname);
456 retval = write_file(ctx, autosave_filename);
458 free(autosave_filename);
460 return (retval);
461 }
463 int
464 pwfile_write_file(struct pwm_ctx *ctx)
465 {
466 int retval;
468 /* update password file metadata */
469 update_file_metadata(ctx->file);
471 /* make a backup copy of the existing password file */
472 if (make_backup_copy(ctx->filename) != 0) {
473 return (-1);
474 }
476 retval = write_file(ctx, ctx->filename);
478 ctx->unsaved_changes = !!retval;
480 return (retval);
481 }
483 static int
484 list_item_cmp(const void *p1, const void *p2)
485 {
486 int retval;
487 const union list_item *item1 = *(const union list_item **)p1;
488 const union list_item *item2 = *(const union list_item **)p2;
489 const char *group1;
490 const char *group2;
491 const char *title1;
492 const char *title2;
494 /* sort both groups and records first by group name */
495 group1 = (item1->any.group != NULL) ? item1->any.group : "";
496 group2 = (item2->any.group != NULL) ? item2->any.group : "";
497 retval = strcmp(group1, group2);
498 if ((retval != 0) || ((item1->any.type == ITEM_TYPE_GROUP) &&
499 (item2->any.type == ITEM_TYPE_GROUP))) {
500 return (retval);
501 } else if ((item1->any.type == ITEM_TYPE_GROUP) &&
502 (item2->any.type == ITEM_TYPE_RECORD)) {
503 /* groups come before records belonging to it */
504 return (-1);
505 } else if ((item1->any.type == ITEM_TYPE_RECORD) &&
506 (item2->any.type == ITEM_TYPE_GROUP)) {
507 return (1);
508 }
510 /* sort records also by title */
511 title1 = (item1->record.title != NULL) ? item1->record.title : "";
512 title2 = (item2->record.title != NULL) ? item2->record.title : "";
513 return (strcmp(title1, title2));
514 }
516 union list_item **
517 pwfile_create_list(struct pwm_ctx *ctx)
518 {
519 union list_item **list;
520 struct record_id_entry *entry;
521 size_t list_capacity = 0;
522 struct pws3_field *empty_group_field;
523 struct pws3_record *pws3_record;
524 struct pws3_field *group_field;
525 const char *group;
526 struct pws3_field *title_field;
527 const char *title;
528 union list_item *item;
529 size_t list_len = 0;
530 size_t i;
531 size_t j;
532 const char *prev_group = NULL;
534 RB_FOREACH(entry, record_id_tree, ctx->record_id_tree) {
535 list_capacity++;
536 }
537 list_capacity *= 2; /* maximum number of group items */
538 for (empty_group_field = pws3_file_first_empty_group(ctx->file);
539 empty_group_field != NULL;
540 empty_group_field = pws3_file_next_empty_group(ctx->file,
541 empty_group_field)) {
542 list_capacity++;
543 }
544 list_capacity++; /* terminating NULL */
545 list = xmalloc(sizeof (union list_item *) * list_capacity);
547 RB_FOREACH(entry, record_id_tree, ctx->record_id_tree) {
548 pws3_record = pws3_file_get_record(ctx->file, entry->uuid);
549 group_field = pws3_record_get_field(pws3_record,
550 PWS3_RECORD_FIELD_GROUP);
551 group = (group_field != NULL) ?
552 pws3_field_get_text(group_field) : NULL;
553 title_field = pws3_record_get_field(pws3_record,
554 PWS3_RECORD_FIELD_TITLE);
555 title = (title_field != NULL) ?
556 pws3_field_get_text(title_field) : NULL;
558 item = xmalloc(sizeof (union list_item));
559 item->record.type = ITEM_TYPE_RECORD;
560 item->record.group = (group != NULL) ? xstrdup(group) : NULL;
561 item->record.title = (title != NULL) ? xstrdup(title) : NULL;
562 item->record.id = entry->id;
563 memcpy(item->record.uuid, entry->uuid,
564 sizeof (item->record.uuid));
566 list[list_len++] = item;
567 }
568 /* sort records by group and title in order to find unqiue groups */
569 qsort(list, list_len, sizeof (union list_item *), list_item_cmp);
571 /* add groups based on the sorted records */
572 for (i = 0, j = list_len; i < list_len; i++) {
573 group = list[i]->record.group;
574 if ((group != NULL) && ((prev_group == NULL) ||
575 (strcmp(prev_group, group) != 0))) {
576 item = xmalloc(sizeof (union list_item));
577 item->group.type = ITEM_TYPE_GROUP;
578 item->group.group = (group != NULL) ? xstrdup(group) :
579 NULL;
580 list[j++] = item;
581 prev_group = group;
582 }
583 }
584 list_len = j;
586 /* add empty groups to the list */
587 for (empty_group_field = pws3_file_first_empty_group(ctx->file);
588 empty_group_field != NULL;
589 empty_group_field = pws3_file_next_empty_group(ctx->file,
590 empty_group_field)) {
591 group = pws3_field_get_text(empty_group_field);
593 item = xmalloc(sizeof (union list_item));
594 item->group.type = ITEM_TYPE_GROUP;
595 item->group.group = xstrdup(group);
597 list[list_len++] = item;
598 }
600 /* terminate the list */
601 list[list_len] = NULL;
602 list = xrealloc(list, sizeof (union list_item *) * (list_len + 1));
604 /* sort the final list by group and title */
605 qsort(list, list_len, sizeof (union list_item *), list_item_cmp);
607 return (list);
608 }
610 void
611 pwfile_destroy_list(union list_item **list)
612 {
613 size_t i;
615 if (list == NULL) {
616 return;
617 }
619 for (i = 0; list[i] != NULL; i++) {
620 if (list[i]->any.type == ITEM_TYPE_RECORD) {
621 free(list[i]->record.title);
622 }
623 free(list[i]->any.group);
624 free(list[i]);
625 }
627 free(list);
628 }
630 static int
631 parse_user_host(const char *user_host, char **userp, char **hostp)
632 {
633 size_t user_host_len;
634 size_t i;
635 unsigned int user_len;
637 user_host_len = strlen(user_host);
638 if (user_host_len < 4) {
639 return (-1);
640 }
641 for (i = 0; i < 4; i++) {
642 if (!isxdigit(user_host[i])) {
643 return (-1);
644 }
645 }
646 if (sscanf(user_host, "%04x", &user_len) != 1) {
647 return (-1);
648 }
649 if (4 + (size_t)user_len > user_host_len) {
650 return (-1);
651 }
653 xasprintf(userp, "%.*s", (int)user_len, user_host + 4);
654 xasprintf(hostp, "%s", user_host + 4 + user_len);
656 return (0);
657 }
659 struct metadata *
660 pwfile_get_metadata(struct pwm_ctx *ctx)
661 {
662 struct metadata *metadata;
663 struct pws3_field *version_field;
664 struct pws3_field *save_app_field;
665 struct pws3_field *save_timestamp_field;
666 struct pws3_field *save_user_field;
667 struct pws3_field *save_host_field;
668 struct pws3_field *save_user_host_field;
670 metadata = xmalloc(sizeof (struct metadata));
672 version_field = pws3_file_get_header_field(ctx->file,
673 PWS3_HEADER_FIELD_VERSION);
674 metadata->version = pws3_field_get_uint16(version_field);
676 save_app_field = pws3_file_get_header_field(ctx->file,
677 PWS3_HEADER_FIELD_SAVE_APPLICATION);
678 metadata->application = (save_app_field != NULL) ?
679 xstrdup(pws3_field_get_text(save_app_field)) : NULL;
681 save_timestamp_field = pws3_file_get_header_field(ctx->file,
682 PWS3_HEADER_FIELD_SAVE_TIMESTAMP);
683 metadata->timestamp = (save_timestamp_field != NULL) ?
684 pws3_field_get_time(save_timestamp_field) : 0;
686 save_user_field = pws3_file_get_header_field(ctx->file,
687 PWS3_HEADER_FIELD_SAVE_USER);
688 save_host_field = pws3_file_get_header_field(ctx->file,
689 PWS3_HEADER_FIELD_SAVE_HOST);
690 save_user_host_field = pws3_file_get_header_field(ctx->file,
691 PWS3_HEADER_FIELD_SAVE_USER_HOST);
692 metadata->user = NULL;
693 metadata->host = NULL;
694 if ((save_user_field != NULL) && (save_host_field != NULL)) {
695 metadata->user = xstrdup(pws3_field_get_text(save_user_field));
696 metadata->host = xstrdup(pws3_field_get_text(save_host_field));
697 } else if (save_user_host_field != NULL) {
698 parse_user_host(pws3_field_get_text(save_user_host_field),
699 &metadata->user, &metadata->host);
700 }
702 return (metadata);
703 }
705 void
706 pwfile_destroy_metadata(struct metadata *metadata)
707 {
708 if (metadata == NULL) {
709 return;
710 }
712 free(metadata->user);
713 free(metadata->host);
714 free(metadata->application);
715 free(metadata);
716 }
718 static void
719 update_record(struct pws3_record *pws3_record, struct record *record)
720 {
721 time_t now;
722 struct pws3_field *ctime_field;
723 struct pws3_field *mtime_field;
724 struct pws3_field *title_field;
725 struct pws3_field *group_field;
726 struct pws3_field *username_field;
727 struct pws3_field *password_field;
728 struct pws3_field *password_mtime_field;
729 struct pws3_field *notes_field;
730 struct pws3_field *url_field;
732 now = time(NULL);
734 ctime_field = pws3_record_get_field(pws3_record,
735 PWS3_RECORD_FIELD_CREATION_TIME);
736 if (ctime_field == NULL) {
737 ctime_field = pws3_field_create(0,
738 PWS3_RECORD_FIELD_CREATION_TIME);
739 if (ctime_field == NULL) {
740 err(1, "pws3_field_create");
741 }
742 pws3_field_set_time(ctime_field, now);
743 pws3_record_set_field(pws3_record, ctime_field);
744 }
746 mtime_field = pws3_field_create(0, PWS3_RECORD_FIELD_MODIFICATION_TIME);
747 if (mtime_field == NULL) {
748 err(1, "pws3_field_create");
749 }
750 pws3_field_set_time(mtime_field, now);
751 pws3_record_set_field(pws3_record, mtime_field);
753 if (record->title != NULL) {
754 title_field = pws3_field_create(0, PWS3_RECORD_FIELD_TITLE);
755 if (title_field == NULL) {
756 err(1, "pws3_field_create");
757 }
758 if (pws3_field_set_text(title_field,
759 record->title) != 0) {
760 err(1, "pws3_field_set_text");
761 }
762 pws3_record_set_field(pws3_record, title_field);
763 }
764 if (record->group != NULL) {
765 group_field = pws3_field_create(0, PWS3_RECORD_FIELD_GROUP);
766 if (group_field == NULL) {
767 err(1, "pws3_field_create");
768 }
769 if (pws3_field_set_text(group_field,
770 record->group) != 0) {
771 err(1, "pws3_field_set_text");
772 }
773 pws3_record_set_field(pws3_record, group_field);
774 }
775 if (record->username != NULL) {
776 username_field = pws3_field_create(0,
777 PWS3_RECORD_FIELD_USERNAME);
778 if (username_field == NULL) {
779 err(1, "pws3_field_create");
780 }
781 if (pws3_field_set_text(username_field,
782 record->username) != 0) {
783 err(1, "pws3_field_set_text");
784 }
785 pws3_record_set_field(pws3_record, username_field);
786 }
787 if (record->password != NULL) {
788 password_field = pws3_field_create(0,
789 PWS3_RECORD_FIELD_PASSWORD);
790 if (password_field == NULL) {
791 err(1, "pws3_field_create");
792 }
793 if (pws3_field_set_text(password_field,
794 record->password) != 0) {
795 err(1, "pws3_field_set_text");
796 }
797 pws3_record_set_field(pws3_record, password_field);
799 password_mtime_field = pws3_field_create(0,
800 PWS3_RECORD_FIELD_PASSWORD_MODIFICATION_TIME);
801 if (password_mtime_field == NULL) {
802 err(1, "pws3_field_create");
803 }
804 pws3_field_set_time(password_mtime_field, now);
805 pws3_record_set_field(pws3_record, password_mtime_field);
806 }
807 if (record->notes != NULL) {
808 notes_field = pws3_field_create(0, PWS3_RECORD_FIELD_NOTES);
809 if (notes_field == NULL) {
810 err(1, "pws3_field_create");
811 }
812 if (pws3_field_set_text(notes_field, record->notes) != 0) {
813 err(1, "pws3_field_set_text");
814 }
815 pws3_record_set_field(pws3_record, notes_field);
816 }
817 if (record->url != NULL) {
818 url_field = pws3_field_create(0, PWS3_RECORD_FIELD_URL);
819 if (url_field == NULL) {
820 err(1, "pws3_field_create");
821 }
822 if (pws3_field_set_text(url_field, record->url) != 0) {
823 err(1, "pws3_field_set_text");
824 }
825 pws3_record_set_field(pws3_record, url_field);
826 }
827 }
829 int
830 pwfile_create_record(struct pwm_ctx *ctx, struct record *record)
831 {
832 struct pws3_record *pws3_record;
833 const unsigned char *uuid;
834 struct record_id_entry *entry;
836 pws3_record = pws3_record_create();
837 if (pws3_record == NULL) {
838 err(1, "pws3_record_create");
839 }
840 update_record(pws3_record, record);
841 pws3_file_insert_record(ctx->file, pws3_record);
843 uuid = pws3_field_get_uuid(pws3_record_get_field(pws3_record,
844 PWS3_RECORD_FIELD_UUID));
845 entry = xmalloc(sizeof (struct record_id_entry));
846 entry->id = ctx->next_id++;
847 memcpy(entry->uuid, uuid, sizeof (entry->uuid));
848 RB_INSERT(record_id_tree, ctx->record_id_tree, entry);
850 ctx->unsaved_changes = 1;
852 return (0);
853 }
855 int
856 pwfile_modify_record(struct pwm_ctx *ctx, unsigned int id,
857 struct record *record)
858 {
859 const unsigned char *uuid;
861 uuid = record_id_tree_get_uuid(ctx->record_id_tree, id);
862 if (uuid == NULL) {
863 return (-1);
864 }
866 update_record(pws3_file_get_record(ctx->file, uuid), record);
868 ctx->unsaved_changes = 1;
870 return (0);
871 }
873 int
874 pwfile_remove_record(struct pwm_ctx *ctx, unsigned int id)
875 {
876 const unsigned char *uuid;
877 struct record_id_entry *entry;
879 uuid = record_id_tree_get_uuid(ctx->record_id_tree, id);
880 if (uuid == NULL) {
881 return (-1);
882 }
884 pws3_record_destroy(pws3_file_remove_record(ctx->file, uuid));
886 entry = RB_FIND(record_id_tree, ctx->record_id_tree,
887 &(struct record_id_entry){ .id = id });
888 free(RB_REMOVE(record_id_tree, ctx->record_id_tree, entry));
890 ctx->unsaved_changes = 1;
892 return (0);
893 }
895 int
896 pwfile_create_group(struct pwm_ctx *ctx, const char *group)
897 {
898 struct pws3_record *pws3_record;
899 struct pws3_field *group_field;
900 struct pws3_field *empty_group_field;
902 /* check for a record in the given group */
903 for (pws3_record = pws3_file_first_record(ctx->file);
904 pws3_record != NULL; pws3_record = pws3_file_next_record(ctx->file,
905 pws3_record)) {
906 group_field = pws3_record_get_field(pws3_record,
907 PWS3_RECORD_FIELD_GROUP);
908 if ((group_field != NULL) &&
909 (strcmp(group, pws3_field_get_text(group_field)) == 0)) {
910 return (-1);
911 }
912 }
914 empty_group_field = pws3_field_create(1,
915 PWS3_HEADER_FIELD_EMPTY_GROUPS);
916 if (empty_group_field == NULL) {
917 err(1, "pws3_field_create");
918 }
919 if (pws3_field_set_text(empty_group_field, group) != 0) {
920 err(1, "pws3_field_set_text");
921 }
922 pws3_file_insert_empty_group(ctx->file, empty_group_field);
924 ctx->unsaved_changes = 1;
926 return (0);
927 }
929 int
930 pwfile_remove_group(struct pwm_ctx *ctx, const char *group)
931 {
932 struct pws3_field *empty_group_field;
934 empty_group_field = pws3_file_remove_empty_group(ctx->file, group);
935 if (empty_group_field == NULL) {
936 return (-1);
937 }
938 pws3_field_destroy(empty_group_field);
940 ctx->unsaved_changes = 1;
942 return (0);
943 }
945 struct record *
946 pwfile_get_record(struct pwm_ctx *ctx, unsigned int id)
947 {
948 struct record *record;
949 const unsigned char *uuid;
950 struct pws3_record *pws3_record;
951 struct pws3_field *ctime_field;
952 struct pws3_field *mtime_field;
953 struct pws3_field *title_field;
954 struct pws3_field *group_field;
955 struct pws3_field *username_field;
956 struct pws3_field *password_field;
957 struct pws3_field *notes_field;
958 struct pws3_field *url_field;
960 uuid = record_id_tree_get_uuid(ctx->record_id_tree, id);
961 if (uuid == NULL) {
962 return (NULL);
963 }
964 pws3_record = pws3_file_get_record(ctx->file, uuid);
966 record = xmalloc(sizeof (struct record));
968 ctime_field = pws3_record_get_field(pws3_record,
969 PWS3_RECORD_FIELD_CREATION_TIME);
970 record->ctime = (ctime_field != NULL) ?
971 pws3_field_get_time(ctime_field) : (time_t)0;
973 mtime_field = pws3_record_get_field(pws3_record,
974 PWS3_RECORD_FIELD_MODIFICATION_TIME);
975 record->mtime = (mtime_field != NULL) ?
976 pws3_field_get_time(mtime_field) : (time_t)0;
978 title_field = pws3_record_get_field(pws3_record,
979 PWS3_RECORD_FIELD_TITLE);
980 record->title = (title_field != NULL) ?
981 xstrdup(pws3_field_get_text(title_field)) : NULL;
983 group_field = pws3_record_get_field(pws3_record,
984 PWS3_RECORD_FIELD_GROUP);
985 record->group = (group_field != NULL) ?
986 xstrdup(pws3_field_get_text(group_field)) : NULL;
988 username_field = pws3_record_get_field(pws3_record,
989 PWS3_RECORD_FIELD_USERNAME);
990 record->username = (username_field != NULL) ?
991 xstrdup(pws3_field_get_text(username_field)) : NULL;
993 password_field = pws3_record_get_field(pws3_record,
994 PWS3_RECORD_FIELD_PASSWORD);
995 record->password = (password_field != NULL) ?
996 xstrdup(pws3_field_get_text(password_field)) : NULL;
998 notes_field = pws3_record_get_field(pws3_record,
999 PWS3_RECORD_FIELD_NOTES);
1000 record->notes = (notes_field != NULL) ?
1001 xstrdup(pws3_field_get_text(notes_field)) : NULL;
1003 url_field = pws3_record_get_field(pws3_record, PWS3_RECORD_FIELD_URL);
1004 record->url = (url_field != NULL) ?
1005 xstrdup(pws3_field_get_text(url_field)) : NULL;
1007 return (record);
1010 void
1011 pwfile_destroy_record(struct record *record)
1013 if (record == NULL) {
1014 return;
1017 free(record->title);
1018 free(record->group);
1019 free(record->username);
1020 free(record->password);
1021 free(record->notes);
1022 free(record->url);
1023 free(record);