Mercurial > projects > pwm
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 } |