comparison pwfile.c @ 0:a7e41e1a79c8

Initial revision
author Guido Berhoerster <guido+pwm@berhoerster.name>
date Thu, 19 Jan 2017 22:39:51 +0100
parents
children 5cd0debdb7d8
comparison
equal deleted inserted replaced
-1:000000000000 0:a7e41e1a79c8
1 /*
2 * Copyright (C) 2016 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 */
23
24 #include "compat.h"
25
26 #ifdef HAVE_ERR_H
27 #include <err.h>
28 #endif /* HAVE_ERR_H */
29 #include <errno.h>
30 #include <stdio.h>
31 #include <string.h>
32 #include <sys/stat.h>
33 #ifdef HAVE_SYS_TREE_H
34 #include <sys/tree.h>
35 #endif
36 #include <unistd.h>
37
38 #include "pwfile.h"
39 #include "util.h"
40
41 #define MIN_ARRAY_SIZE 1024
42
43 struct record_id_entry {
44 RB_ENTRY(record_id_entry) record_id_entry;
45 unsigned int id;
46 unsigned char uuid[PWS3_UUID_SIZE];
47 };
48
49 RB_HEAD(record_id_tree, record_id_entry);
50
51 static int record_id_entry_cmp(struct record_id_entry *,
52 struct record_id_entry *);
53 RB_PROTOTYPE_STATIC(record_id_tree, record_id_entry, record_id_entry,
54 record_id_entry_cmp)
55 RB_GENERATE_STATIC(record_id_tree, record_id_entry, record_id_entry,
56 record_id_entry_cmp)
57
58 static int
59 record_id_entry_cmp(struct record_id_entry *entry1,
60 struct record_id_entry *entry2)
61 {
62 if (entry1->id > entry2->id) {
63 return (-1);
64 } else if (entry1->id < entry2->id) {
65 return (1);
66 }
67 return (0);
68 }
69
70 static int
71 pws_record_cmp(const void *p1, const void *p2)
72 {
73 int retval;
74 struct pws3_record *record1 = *(struct pws3_record **)p1;
75 struct pws3_record *record2 = *(struct pws3_record **)p2;
76 struct pws3_field *group_field1;
77 const char *group1;
78 struct pws3_field *group_field2;
79 const char *group2;
80 struct pws3_field *title_field1;
81 const char *title1;
82 struct pws3_field *title_field2;
83 const char *title2;
84
85 group_field1 = pws3_record_get_field(record1, PWS3_RECORD_FIELD_GROUP);
86 group1 = (group_field1 != NULL) ? pws3_field_get_text(group_field1) :
87 "";
88 group_field2 = pws3_record_get_field(record2, PWS3_RECORD_FIELD_GROUP);
89 group2 = (group_field1 != NULL) ? pws3_field_get_text(group_field2) :
90 "";
91 retval = strcmp(group1, group2);
92 if (retval != 0) {
93 return (retval);
94 }
95
96 title_field1 = pws3_record_get_field(record1, PWS3_RECORD_FIELD_TITLE);
97 title1 = (title_field1 != NULL) ? pws3_field_get_text(title_field1) :
98 "";
99 title_field2 = pws3_record_get_field(record2, PWS3_RECORD_FIELD_TITLE);
100 title2 = (title_field2 != NULL) ? pws3_field_get_text(title_field2) :
101 "";
102 return (strcmp(title1, title2));
103 }
104
105 static void
106 record_id_tree_clear(struct record_id_tree *tree)
107 {
108 struct record_id_entry *entry;
109 struct record_id_entry *entry_tmp;
110
111 RB_FOREACH_SAFE(entry, record_id_tree, tree, entry_tmp) {
112 RB_REMOVE(record_id_tree, tree, entry);
113 free(entry);
114 }
115 }
116
117 static void
118 record_id_tree_destroy(struct record_id_tree *tree)
119 {
120 if (tree == NULL) {
121 return;
122 }
123
124 record_id_tree_clear(tree);
125 free(tree);
126 }
127
128 static const unsigned char *
129 record_id_tree_get_uuid(struct record_id_tree *tree, unsigned int id)
130 {
131 struct record_id_entry *entry;
132
133 entry = RB_FIND(record_id_tree, tree,
134 &(struct record_id_entry){ .id = id });
135 if (entry == NULL) {
136 return (NULL);
137 }
138
139 return (entry->uuid);
140 }
141
142 void
143 pwfile_init(struct pwm_ctx *ctx)
144 {
145 ctx->file = pws3_file_create();
146 if (ctx->file == NULL) {
147 err(1, "pws3_file_create");
148 }
149
150 ctx->next_id = 1;
151
152 ctx->record_id_tree = xmalloc(sizeof (struct record_id_tree));
153 RB_INIT(ctx->record_id_tree);
154 }
155
156 void
157 pwfile_destroy(struct pwm_ctx *ctx)
158 {
159 record_id_tree_destroy(ctx->record_id_tree);
160 ctx->record_id_tree = NULL;
161 pws3_file_destroy(ctx->file);
162 ctx->file = NULL;
163 }
164
165 int
166 pwfile_read_file(struct pwm_ctx *ctx, FILE *fp)
167 {
168 struct pws3_record *pws3_record;
169 struct pws3_record **pws3_record_list;
170 size_t record_list_size = MIN_ARRAY_SIZE;
171 size_t record_list_len = 0;
172 size_t i;
173 struct pws3_field *uuid_field;
174 const unsigned char *uuid;
175 struct record_id_entry *entry;
176
177 if (pws3_file_read_stream(ctx->file, ctx->password, fp) != 0) {
178 warnx("failed to read password database: %s",
179 pws3_file_get_error_message(ctx->file));
180 return (-1);
181 }
182
183 record_id_tree_clear(ctx->record_id_tree);
184
185 /* sort records by group and title */
186 pws3_record_list = xmalloc(sizeof (struct pws3_record *) *
187 record_list_size);
188 for (pws3_record = pws3_file_first_record(ctx->file);
189 pws3_record != NULL; pws3_record = pws3_file_next_record(ctx->file,
190 pws3_record)) {
191 if (record_list_len == record_list_size) {
192 record_list_size *= 2;
193 pws3_record_list = xrealloc(pws3_record_list,
194 sizeof (struct pws3_record *) * record_list_size);
195 }
196 pws3_record_list[record_list_len++] = pws3_record;
197 }
198 qsort(pws3_record_list, record_list_len, sizeof (struct pws3_record *),
199 pws_record_cmp);
200
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);
206
207 entry = xmalloc(sizeof (struct record_id_entry));
208 entry->id = ctx->next_id++;
209 memcpy(entry->uuid, uuid, sizeof (entry->uuid));
210
211 RB_INSERT(record_id_tree, ctx->record_id_tree, entry);
212 }
213
214 free(pws3_record_list);
215
216 return (0);
217 }
218
219 static int
220 make_backup_copy(const char *filename)
221 {
222 int retval = -1;
223 FILE *fp_orig = NULL;
224 char *backup_filename = NULL;
225 char *tmpfilename = NULL;
226 mode_t old_mode;
227 int fd_backup = -1;
228 unsigned char buf[BUFSIZ];
229 size_t read_len;
230 FILE *fp_backup = NULL;
231
232 fp_orig = fopen(filename, "r");
233 if (fp_orig == NULL) {
234 if (errno != ENOENT) {
235 warn("fopen");
236 return (-1);
237 }
238 return (0);
239 }
240
241 xasprintf(&backup_filename, "%s~", filename);
242 xasprintf(&tmpfilename, "%s.XXXXXX", filename);
243
244 /* create temporary file */
245 old_mode = umask(S_IRWXG | S_IRWXO);
246 fd_backup = mkstemp(tmpfilename);
247 umask(old_mode);
248 if (fd_backup == -1) {
249 warn("mkstemp");
250 goto out;
251 }
252 fp_backup = fdopen(fd_backup, "w");
253 if (fp_backup == NULL) {
254 warn("fdopen");
255 goto out;
256 }
257
258 /* copy file contents */
259 while (!feof(fp_orig)) {
260 read_len = fread(buf, 1, sizeof (buf), fp_orig);
261 if ((read_len < sizeof (buf)) && ferror(fp_orig)) {
262 warn("fread");
263 goto out;
264 }
265 if (fwrite(buf, 1, read_len, fp_backup) != read_len) {
266 warn("fwrite");
267 goto out;
268 }
269 }
270 if (fflush(fp_backup) != 0) {
271 warn("fflush");
272 goto out;
273 }
274 if (fsync(fileno(fp_backup)) != 0) {
275 warn("fsync");
276 goto out;
277 }
278
279 retval = 0;
280
281 out:
282 if ((fd_backup != -1) && (fp_backup == NULL)) {
283 close(fd_backup);
284 }
285 if (fp_backup != NULL) {
286 fclose(fp_backup);
287 }
288 if (fp_orig != NULL) {
289 fclose(fp_orig);
290 }
291 if (retval == 0) {
292 /* rename temporary file and overwrite existing file */
293 if (rename(tmpfilename, backup_filename) != 0) {
294 warn("rename");
295 retval = -1;
296 }
297 }
298 if ((retval != 0) && ((fd_backup != -1) || (fp_backup != NULL))) {
299 unlink(tmpfilename);
300 }
301 free(tmpfilename);
302 free(backup_filename);
303
304 return (retval);
305 }
306
307 int
308 pwfile_write_file(struct pwm_ctx *ctx)
309 {
310 int retval = -1;
311 char *tmpfilename = NULL;
312 mode_t old_mode;
313 int fd = -1;
314 FILE *fp = NULL;
315
316 if (make_backup_copy(ctx->filename) != 0) {
317 goto out;
318 }
319
320 xasprintf(&tmpfilename, "%s.XXXXXX", ctx->filename);
321
322 /* create temporary file */
323 old_mode = umask(S_IRWXG | S_IRWXO);
324 fd = mkstemp(tmpfilename);
325 if (fd == -1) {
326 warn("mkstemp");
327 goto out;
328 }
329 umask(old_mode);
330 fp = fdopen(fd, "w");
331 if (fp == NULL) {
332 warn("fdopen");
333 goto out;
334 }
335
336 /* write contents */
337 if (pws3_file_write_stream(ctx->file, ctx->password, 10000, fp) != 0) {
338 warnx("pws3_file_write_stream: %s",
339 pws3_file_get_error_message(ctx->file));
340 goto out;
341 }
342 if (fflush(fp) != 0) {
343 warn("fflush");
344 goto out;
345 }
346 if (fsync(fileno(fp)) != 0) {
347 warn("fsync");
348 goto out;
349 }
350
351 retval = 0;
352
353 out:
354 if ((fd != -1) && (fp == NULL)) {
355 close(fd);
356 }
357 if (fp != NULL) {
358 fclose(fp);
359 }
360 if (retval == 0) {
361 /* rename temporary file and overwrite existing file */
362 if (rename(tmpfilename, ctx->filename) != 0) {
363 warn("rename");
364 retval = -1;
365 }
366 }
367 if ((retval != 0) && ((fd != -1) || (fp != NULL))) {
368 unlink(tmpfilename);
369 }
370 free(tmpfilename);
371
372 return (retval);
373 }
374
375 static int
376 list_item_cmp(const void *p1, const void *p2)
377 {
378 int retval;
379 const union list_item *item1 = *(const union list_item **)p1;
380 const union list_item *item2 = *(const union list_item **)p2;
381 const char *group1;
382 const char *group2;
383 const char *title1;
384 const char *title2;
385
386 /* sort both groups and records first by group name */
387 group1 = (item1->any.group != NULL) ? item1->any.group : "";
388 group2 = (item2->any.group != NULL) ? item2->any.group : "";
389 retval = strcmp(group1, group2);
390 if ((retval != 0) || ((item1->any.type == ITEM_TYPE_GROUP) &&
391 (item2->any.type == ITEM_TYPE_GROUP))) {
392 return (retval);
393 } else if ((item1->any.type == ITEM_TYPE_GROUP) &&
394 (item2->any.type == ITEM_TYPE_RECORD)) {
395 /* groups come before records belonging to it */
396 return (-1);
397 } else if ((item1->any.type == ITEM_TYPE_RECORD) &&
398 (item2->any.type == ITEM_TYPE_GROUP)) {
399 return (1);
400 }
401
402 /* sort records also by title */
403 title1 = (item1->record.title != NULL) ? item1->record.title : "";
404 title2 = (item2->record.title != NULL) ? item2->record.title : "";
405 return (strcmp(title1, title2));
406 }
407
408 union list_item **
409 pwfile_create_list(struct pwm_ctx *ctx)
410 {
411 union list_item **list;
412 size_t list_size = MIN_ARRAY_SIZE;
413 size_t list_len = 0;
414 struct record_id_entry *entry;
415 union list_item *item;
416 struct pws3_record *pws3_record;
417 struct pws3_field *group_field;
418 const char *group;
419 struct pws3_field *title_field;
420 const char *title;
421 size_t i;
422 size_t records_len;
423 const char *prev_group = "";
424 struct pws3_field *empty_group_field;
425
426 list = xmalloc(sizeof (union list_item *) * list_size);
427 list[0] = NULL;
428
429 /* build list of records and sort it by group and title */
430 RB_FOREACH(entry, record_id_tree, ctx->record_id_tree) {
431 if (list_len == list_size - 1) {
432 list_size *= 2;
433 list = xrealloc(list, sizeof (union list_item *) *
434 list_size);
435 }
436
437 pws3_record = pws3_file_get_record(ctx->file, entry->uuid);
438 group_field = pws3_record_get_field(pws3_record,
439 PWS3_RECORD_FIELD_GROUP);
440 group = (group_field != NULL) ?
441 pws3_field_get_text(group_field) : NULL;
442 title_field = pws3_record_get_field(pws3_record,
443 PWS3_RECORD_FIELD_TITLE);
444 title = (title_field != NULL) ?
445 pws3_field_get_text(title_field) : NULL;
446
447 item = xmalloc(sizeof (union list_item));
448 item->record.type = ITEM_TYPE_RECORD;
449 item->record.group = (group != NULL) ? xstrdup(group) : NULL;
450 item->record.title = (title != NULL) ? xstrdup(title) : NULL;
451 item->record.id = entry->id;
452 memcpy(item->record.uuid, entry->uuid,
453 sizeof (item->record.uuid));
454
455 list[list_len++] = item;
456 list[list_len] = NULL;
457 }
458 qsort(list, list_len, sizeof (union list_item *), list_item_cmp);
459
460 /* build list of groups by comparing the groups of the sorted records */
461 for (i = 0, records_len = list_len; i < records_len; i++) {
462 if (list_len == list_size - 1) {
463 list_size *= 1.5;
464 list = xrealloc(list, sizeof (union list_item *) *
465 list_size);
466 }
467
468 group = (list[i]->record.group != NULL) ?
469 list[i]->record.group : "";
470 if (strcmp(prev_group, group) != 0) {
471 item = xmalloc(sizeof (union list_item));
472 item->record.type = ITEM_TYPE_GROUP;
473 item->record.group = (group != NULL) ? xstrdup(group) :
474 NULL;
475
476 list[list_len++] = item;
477 list[list_len] = NULL;
478
479 prev_group = group;
480 }
481 }
482
483 /* add empty groups to the list */
484 for (empty_group_field = pws3_file_first_empty_group(ctx->file);
485 empty_group_field != NULL;
486 empty_group_field = pws3_file_next_empty_group(ctx->file,
487 empty_group_field)) {
488 if (list_len == list_size - 1) {
489 list_size *= 1.5;
490 list = xrealloc(list, sizeof (union list_item *) *
491 list_size);
492 }
493
494 group = pws3_field_get_text(empty_group_field);
495
496 item = xmalloc(sizeof (union list_item));
497 item->record.type = ITEM_TYPE_GROUP;
498 item->record.group = xstrdup(group);
499
500 list[list_len++] = item;
501 list[list_len] = NULL;
502 }
503
504 list_size = list_len + 2;
505 list = xrealloc(list, sizeof (union list_item *) * list_size);
506 /* sort the final list by group and title */
507 qsort(list, list_len, sizeof (union list_item *), list_item_cmp);
508
509 return (list);
510 }
511
512 void
513 pwfile_destroy_list(union list_item **list)
514 {
515 size_t i;
516
517 if (list == NULL) {
518 return;
519 }
520
521 for (i = 0; list[i] != NULL; i++) {
522 if (list[i]->any.type == ITEM_TYPE_RECORD) {
523 free(list[i]->record.title);
524 }
525 free(list[i]->any.group);
526 free(list[i]);
527 }
528
529 free(list);
530 }
531
532 static void
533 update_record(struct pws3_record *pws3_record, struct record *record)
534 {
535 struct pws3_field *title_field;
536 struct pws3_field *group_field;
537 struct pws3_field *username_field;
538 struct pws3_field *password_field;
539 struct pws3_field *notes_field;
540 struct pws3_field *url_field;
541
542 if (record->title != NULL) {
543 title_field = pws3_field_create(0, PWS3_RECORD_FIELD_TITLE);
544 if (title_field == NULL) {
545 err(1, "pws3_record_field_create");
546 }
547 if (pws3_field_set_text(title_field,
548 record->title) != 0) {
549 err(1, "pws3_field_set_text");
550 }
551 pws3_record_set_field(pws3_record, title_field);
552 }
553 if (record->group != NULL) {
554 group_field = pws3_field_create(0, PWS3_RECORD_FIELD_GROUP);
555 if (group_field == NULL) {
556 err(1, "pws3_record_field_create");
557 }
558 if (pws3_field_set_text(group_field,
559 record->group) != 0) {
560 err(1, "pws3_field_set_text");
561 }
562 pws3_record_set_field(pws3_record, group_field);
563 }
564 if (record->username != NULL) {
565 username_field = pws3_field_create(0,
566 PWS3_RECORD_FIELD_USERNAME);
567 if (username_field == NULL) {
568 err(1, "pws3_record_field_create");
569 }
570 if (pws3_field_set_text(username_field,
571 record->username) != 0) {
572 err(1, "pws3_field_set_text");
573 }
574 pws3_record_set_field(pws3_record, username_field);
575 }
576 if (record->password != NULL) {
577 password_field = pws3_field_create(0,
578 PWS3_RECORD_FIELD_PASSWORD);
579 if (password_field == NULL) {
580 err(1, "pws3_record_field_create");
581 }
582 if (pws3_field_set_text(password_field,
583 record->password) != 0) {
584 err(1, "pws3_field_set_text");
585 }
586 pws3_record_set_field(pws3_record, password_field);
587 }
588 if (record->notes != NULL) {
589 notes_field = pws3_field_create(0, PWS3_RECORD_FIELD_NOTES);
590 if (notes_field == NULL) {
591 err(1, "pws3_record_field_create");
592 }
593 if (pws3_field_set_text(notes_field, record->notes) != 0) {
594 err(1, "pws3_field_set_text");
595 }
596 pws3_record_set_field(pws3_record, notes_field);
597 }
598 if (record->url != NULL) {
599 url_field = pws3_field_create(0, PWS3_RECORD_FIELD_URL);
600 if (url_field == NULL) {
601 err(1, "pws3_record_field_create");
602 }
603 if (pws3_field_set_text(url_field, record->url) != 0) {
604 err(1, "pws3_field_set_text");
605 }
606 pws3_record_set_field(pws3_record, url_field);
607 }
608 }
609
610 int
611 pwfile_create_record(struct pwm_ctx *ctx, struct record *record)
612 {
613 struct pws3_record *pws3_record;
614 const unsigned char *uuid;
615 struct record_id_entry *entry;
616
617 pws3_record = pws3_record_create();
618 if (pws3_record == NULL) {
619 err(1, "pws3_record_create");
620 }
621 update_record(pws3_record, record);
622 pws3_file_insert_record(ctx->file, pws3_record);
623
624 uuid = pws3_field_get_uuid(pws3_record_get_field(pws3_record,
625 PWS3_RECORD_FIELD_UUID));
626 entry = xmalloc(sizeof (struct record_id_entry));
627 entry->id = ctx->next_id++;
628 memcpy(entry->uuid, uuid, sizeof (entry->uuid));
629 RB_INSERT(record_id_tree, ctx->record_id_tree, entry);
630
631 return (0);
632 }
633
634 int
635 pwfile_modify_record(struct pwm_ctx *ctx, unsigned int id,
636 struct record *record)
637 {
638 const unsigned char *uuid;
639
640 uuid = record_id_tree_get_uuid(ctx->record_id_tree, id);
641 if (uuid == NULL) {
642 return (-1);
643 }
644
645 update_record(pws3_file_get_record(ctx->file, uuid), record);
646
647 return (0);
648 }
649
650 int
651 pwfile_remove_record(struct pwm_ctx *ctx, unsigned int id)
652 {
653 const unsigned char *uuid;
654 struct record_id_entry *entry;
655
656 uuid = record_id_tree_get_uuid(ctx->record_id_tree, id);
657 if (uuid == NULL) {
658 return (-1);
659 }
660
661 pws3_record_destroy(pws3_file_remove_record(ctx->file, uuid));
662
663 entry = RB_FIND(record_id_tree, ctx->record_id_tree,
664 &(struct record_id_entry){ .id = id });
665 free(RB_REMOVE(record_id_tree, ctx->record_id_tree, entry));
666
667 return (0);
668 }
669
670 int
671 pwfile_create_group(struct pwm_ctx *ctx, const char *group)
672 {
673 struct pws3_record *pws3_record;
674 struct pws3_field *group_field;
675 struct pws3_field *empty_group_field;
676
677 /* check for a record in the given group */
678 for (pws3_record = pws3_file_first_record(ctx->file);
679 pws3_record != NULL; pws3_record = pws3_file_next_record(ctx->file,
680 pws3_record)) {
681 group_field = pws3_record_get_field(pws3_record,
682 PWS3_RECORD_FIELD_GROUP);
683 if ((group_field != NULL) &&
684 (strcmp(group, pws3_field_get_text(group_field)) == 0)) {
685 return (-1);
686 }
687 }
688
689 empty_group_field = pws3_field_create(1,
690 PWS3_HEADER_FIELD_EMPTY_GROUPS);
691 if (empty_group_field == NULL) {
692 err(1, "pws3_field_create");
693 }
694 if (pws3_field_set_text(empty_group_field, group) != 0) {
695 err(1, "pws3_field_set_text");
696 }
697 pws3_file_insert_empty_group(ctx->file, empty_group_field);
698
699 return (0);
700 }
701
702 int
703 pwfile_remove_group(struct pwm_ctx *ctx, const char *group)
704 {
705 struct pws3_field *empty_group_field;
706
707 empty_group_field = pws3_file_remove_empty_group(ctx->file, group);
708 if (empty_group_field != NULL) {
709 return (-1);
710 }
711 pws3_field_destroy(empty_group_field);
712
713 return (0);
714 }
715
716 struct record *
717 pwfile_get_record(struct pwm_ctx *ctx, unsigned int id)
718 {
719 struct record *record;
720 const unsigned char *uuid;
721 struct pws3_record *pws3_record;
722 struct pws3_field *title_field;
723 struct pws3_field *group_field;
724 struct pws3_field *username_field;
725 struct pws3_field *password_field;
726 struct pws3_field *notes_field;
727 struct pws3_field *url_field;
728
729 uuid = record_id_tree_get_uuid(ctx->record_id_tree, id);
730 if (uuid == NULL) {
731 return (NULL);
732 }
733 pws3_record = pws3_file_get_record(ctx->file, uuid);
734
735 record = xmalloc(sizeof (struct record));
736
737 title_field = pws3_record_get_field(pws3_record,
738 PWS3_RECORD_FIELD_TITLE);
739 record->title = (title_field != NULL) ?
740 xstrdup(pws3_field_get_text(title_field)) : NULL;
741
742 group_field = pws3_record_get_field(pws3_record,
743 PWS3_RECORD_FIELD_GROUP);
744 record->group = (group_field != NULL) ?
745 xstrdup(pws3_field_get_text(group_field)) : NULL;
746
747 username_field = pws3_record_get_field(pws3_record,
748 PWS3_RECORD_FIELD_USERNAME);
749 record->username = (username_field != NULL) ?
750 xstrdup(pws3_field_get_text(username_field)) : NULL;
751
752 password_field = pws3_record_get_field(pws3_record,
753 PWS3_RECORD_FIELD_PASSWORD);
754 record->password = (password_field != NULL) ?
755 xstrdup(pws3_field_get_text(password_field)) : NULL;
756
757 notes_field = pws3_record_get_field(pws3_record,
758 PWS3_RECORD_FIELD_NOTES);
759 record->notes = (notes_field != NULL) ?
760 xstrdup(pws3_field_get_text(notes_field)) : NULL;
761
762 url_field = pws3_record_get_field(pws3_record, PWS3_RECORD_FIELD_URL);
763 record->url = (url_field != NULL) ?
764 xstrdup(pws3_field_get_text(url_field)) : NULL;
765
766 return (record);
767 }
768
769 void
770 pwfile_destroy_record(struct record *record)
771 {
772 if (record == NULL) {
773 return;
774 }
775
776 free(record->title);
777 free(record->group);
778 free(record->username);
779 free(record->password);
780 free(record->notes);
781 free(record->url);
782 free(record);
783 }