Mercurial > projects > pwm
view 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 |
line wrap: on
line source
/* * Copyright (C) 2016 Guido Berhoerster <guido+pwm@berhoerster.name> * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "compat.h" #ifdef HAVE_ERR_H #include <err.h> #endif /* HAVE_ERR_H */ #include <errno.h> #include <stdio.h> #include <string.h> #include <sys/stat.h> #ifdef HAVE_SYS_TREE_H #include <sys/tree.h> #endif #include <unistd.h> #include "pwfile.h" #include "util.h" #define MIN_ARRAY_SIZE 1024 struct record_id_entry { RB_ENTRY(record_id_entry) record_id_entry; unsigned int id; unsigned char uuid[PWS3_UUID_SIZE]; }; RB_HEAD(record_id_tree, record_id_entry); static int record_id_entry_cmp(struct record_id_entry *, struct record_id_entry *); RB_PROTOTYPE_STATIC(record_id_tree, record_id_entry, record_id_entry, record_id_entry_cmp) RB_GENERATE_STATIC(record_id_tree, record_id_entry, record_id_entry, record_id_entry_cmp) static int record_id_entry_cmp(struct record_id_entry *entry1, struct record_id_entry *entry2) { if (entry1->id > entry2->id) { return (-1); } else if (entry1->id < entry2->id) { return (1); } return (0); } static int pws_record_cmp(const void *p1, const void *p2) { int retval; struct pws3_record *record1 = *(struct pws3_record **)p1; struct pws3_record *record2 = *(struct pws3_record **)p2; struct pws3_field *group_field1; const char *group1; struct pws3_field *group_field2; const char *group2; struct pws3_field *title_field1; const char *title1; struct pws3_field *title_field2; const char *title2; group_field1 = pws3_record_get_field(record1, PWS3_RECORD_FIELD_GROUP); group1 = (group_field1 != NULL) ? pws3_field_get_text(group_field1) : ""; group_field2 = pws3_record_get_field(record2, PWS3_RECORD_FIELD_GROUP); group2 = (group_field1 != NULL) ? pws3_field_get_text(group_field2) : ""; retval = strcmp(group1, group2); if (retval != 0) { return (retval); } title_field1 = pws3_record_get_field(record1, PWS3_RECORD_FIELD_TITLE); title1 = (title_field1 != NULL) ? pws3_field_get_text(title_field1) : ""; title_field2 = pws3_record_get_field(record2, PWS3_RECORD_FIELD_TITLE); title2 = (title_field2 != NULL) ? pws3_field_get_text(title_field2) : ""; return (strcmp(title1, title2)); } static void record_id_tree_clear(struct record_id_tree *tree) { struct record_id_entry *entry; struct record_id_entry *entry_tmp; RB_FOREACH_SAFE(entry, record_id_tree, tree, entry_tmp) { RB_REMOVE(record_id_tree, tree, entry); free(entry); } } static void record_id_tree_destroy(struct record_id_tree *tree) { if (tree == NULL) { return; } record_id_tree_clear(tree); free(tree); } static const unsigned char * record_id_tree_get_uuid(struct record_id_tree *tree, unsigned int id) { struct record_id_entry *entry; entry = RB_FIND(record_id_tree, tree, &(struct record_id_entry){ .id = id }); if (entry == NULL) { return (NULL); } return (entry->uuid); } void pwfile_init(struct pwm_ctx *ctx) { ctx->file = pws3_file_create(); if (ctx->file == NULL) { err(1, "pws3_file_create"); } ctx->next_id = 1; ctx->record_id_tree = xmalloc(sizeof (struct record_id_tree)); RB_INIT(ctx->record_id_tree); } void pwfile_destroy(struct pwm_ctx *ctx) { record_id_tree_destroy(ctx->record_id_tree); ctx->record_id_tree = NULL; pws3_file_destroy(ctx->file); ctx->file = NULL; } int pwfile_read_file(struct pwm_ctx *ctx, FILE *fp) { struct pws3_record *pws3_record; struct pws3_record **pws3_record_list; size_t record_list_size = MIN_ARRAY_SIZE; size_t record_list_len = 0; size_t i; struct pws3_field *uuid_field; const unsigned char *uuid; struct record_id_entry *entry; if (pws3_file_read_stream(ctx->file, ctx->password, fp) != 0) { warnx("failed to read password database: %s", pws3_file_get_error_message(ctx->file)); return (-1); } record_id_tree_clear(ctx->record_id_tree); /* sort records by group and title */ pws3_record_list = xmalloc(sizeof (struct pws3_record *) * record_list_size); for (pws3_record = pws3_file_first_record(ctx->file); pws3_record != NULL; pws3_record = pws3_file_next_record(ctx->file, pws3_record)) { if (record_list_len == record_list_size) { record_list_size *= 2; pws3_record_list = xrealloc(pws3_record_list, sizeof (struct pws3_record *) * record_list_size); } pws3_record_list[record_list_len++] = pws3_record; } qsort(pws3_record_list, record_list_len, sizeof (struct pws3_record *), pws_record_cmp); /* build the tree of record IDs */ for (i = 0; i < record_list_len; i++) { uuid_field = pws3_record_get_field(pws3_record_list[i], PWS3_RECORD_FIELD_UUID); uuid = pws3_field_get_uuid(uuid_field); entry = xmalloc(sizeof (struct record_id_entry)); entry->id = ctx->next_id++; memcpy(entry->uuid, uuid, sizeof (entry->uuid)); RB_INSERT(record_id_tree, ctx->record_id_tree, entry); } free(pws3_record_list); return (0); } static int make_backup_copy(const char *filename) { int retval = -1; FILE *fp_orig = NULL; char *backup_filename = NULL; char *tmpfilename = NULL; mode_t old_mode; int fd_backup = -1; unsigned char buf[BUFSIZ]; size_t read_len; FILE *fp_backup = NULL; fp_orig = fopen(filename, "r"); if (fp_orig == NULL) { if (errno != ENOENT) { warn("fopen"); return (-1); } return (0); } xasprintf(&backup_filename, "%s~", filename); xasprintf(&tmpfilename, "%s.XXXXXX", filename); /* create temporary file */ old_mode = umask(S_IRWXG | S_IRWXO); fd_backup = mkstemp(tmpfilename); umask(old_mode); if (fd_backup == -1) { warn("mkstemp"); goto out; } fp_backup = fdopen(fd_backup, "w"); if (fp_backup == NULL) { warn("fdopen"); goto out; } /* copy file contents */ while (!feof(fp_orig)) { read_len = fread(buf, 1, sizeof (buf), fp_orig); if ((read_len < sizeof (buf)) && ferror(fp_orig)) { warn("fread"); goto out; } if (fwrite(buf, 1, read_len, fp_backup) != read_len) { warn("fwrite"); goto out; } } if (fflush(fp_backup) != 0) { warn("fflush"); goto out; } if (fsync(fileno(fp_backup)) != 0) { warn("fsync"); goto out; } retval = 0; out: if ((fd_backup != -1) && (fp_backup == NULL)) { close(fd_backup); } if (fp_backup != NULL) { fclose(fp_backup); } if (fp_orig != NULL) { fclose(fp_orig); } if (retval == 0) { /* rename temporary file and overwrite existing file */ if (rename(tmpfilename, backup_filename) != 0) { warn("rename"); retval = -1; } } if ((retval != 0) && ((fd_backup != -1) || (fp_backup != NULL))) { unlink(tmpfilename); } free(tmpfilename); free(backup_filename); return (retval); } int pwfile_write_file(struct pwm_ctx *ctx) { int retval = -1; char *tmpfilename = NULL; mode_t old_mode; int fd = -1; FILE *fp = NULL; if (make_backup_copy(ctx->filename) != 0) { goto out; } xasprintf(&tmpfilename, "%s.XXXXXX", ctx->filename); /* create temporary file */ old_mode = umask(S_IRWXG | S_IRWXO); fd = mkstemp(tmpfilename); if (fd == -1) { warn("mkstemp"); goto out; } umask(old_mode); fp = fdopen(fd, "w"); if (fp == NULL) { warn("fdopen"); goto out; } /* write contents */ if (pws3_file_write_stream(ctx->file, ctx->password, 10000, fp) != 0) { warnx("pws3_file_write_stream: %s", pws3_file_get_error_message(ctx->file)); goto out; } if (fflush(fp) != 0) { warn("fflush"); goto out; } if (fsync(fileno(fp)) != 0) { warn("fsync"); goto out; } retval = 0; out: if ((fd != -1) && (fp == NULL)) { close(fd); } if (fp != NULL) { fclose(fp); } if (retval == 0) { /* rename temporary file and overwrite existing file */ if (rename(tmpfilename, ctx->filename) != 0) { warn("rename"); retval = -1; } } if ((retval != 0) && ((fd != -1) || (fp != NULL))) { unlink(tmpfilename); } free(tmpfilename); return (retval); } static int list_item_cmp(const void *p1, const void *p2) { int retval; const union list_item *item1 = *(const union list_item **)p1; const union list_item *item2 = *(const union list_item **)p2; const char *group1; const char *group2; const char *title1; const char *title2; /* sort both groups and records first by group name */ group1 = (item1->any.group != NULL) ? item1->any.group : ""; group2 = (item2->any.group != NULL) ? item2->any.group : ""; retval = strcmp(group1, group2); if ((retval != 0) || ((item1->any.type == ITEM_TYPE_GROUP) && (item2->any.type == ITEM_TYPE_GROUP))) { return (retval); } else if ((item1->any.type == ITEM_TYPE_GROUP) && (item2->any.type == ITEM_TYPE_RECORD)) { /* groups come before records belonging to it */ return (-1); } else if ((item1->any.type == ITEM_TYPE_RECORD) && (item2->any.type == ITEM_TYPE_GROUP)) { return (1); } /* sort records also by title */ title1 = (item1->record.title != NULL) ? item1->record.title : ""; title2 = (item2->record.title != NULL) ? item2->record.title : ""; return (strcmp(title1, title2)); } union list_item ** pwfile_create_list(struct pwm_ctx *ctx) { union list_item **list; size_t list_size = MIN_ARRAY_SIZE; size_t list_len = 0; struct record_id_entry *entry; union list_item *item; struct pws3_record *pws3_record; struct pws3_field *group_field; const char *group; struct pws3_field *title_field; const char *title; size_t i; size_t records_len; const char *prev_group = ""; struct pws3_field *empty_group_field; list = xmalloc(sizeof (union list_item *) * list_size); list[0] = NULL; /* build list of records and sort it by group and title */ RB_FOREACH(entry, record_id_tree, ctx->record_id_tree) { if (list_len == list_size - 1) { list_size *= 2; list = xrealloc(list, sizeof (union list_item *) * list_size); } pws3_record = pws3_file_get_record(ctx->file, entry->uuid); group_field = pws3_record_get_field(pws3_record, PWS3_RECORD_FIELD_GROUP); group = (group_field != NULL) ? pws3_field_get_text(group_field) : NULL; title_field = pws3_record_get_field(pws3_record, PWS3_RECORD_FIELD_TITLE); title = (title_field != NULL) ? pws3_field_get_text(title_field) : NULL; item = xmalloc(sizeof (union list_item)); item->record.type = ITEM_TYPE_RECORD; item->record.group = (group != NULL) ? xstrdup(group) : NULL; item->record.title = (title != NULL) ? xstrdup(title) : NULL; item->record.id = entry->id; memcpy(item->record.uuid, entry->uuid, sizeof (item->record.uuid)); list[list_len++] = item; list[list_len] = NULL; } qsort(list, list_len, sizeof (union list_item *), list_item_cmp); /* build list of groups by comparing the groups of the sorted records */ for (i = 0, records_len = list_len; i < records_len; i++) { if (list_len == list_size - 1) { list_size *= 1.5; list = xrealloc(list, sizeof (union list_item *) * list_size); } group = (list[i]->record.group != NULL) ? list[i]->record.group : ""; if (strcmp(prev_group, group) != 0) { item = xmalloc(sizeof (union list_item)); item->record.type = ITEM_TYPE_GROUP; item->record.group = (group != NULL) ? xstrdup(group) : NULL; list[list_len++] = item; list[list_len] = NULL; prev_group = group; } } /* add empty groups to the list */ for (empty_group_field = pws3_file_first_empty_group(ctx->file); empty_group_field != NULL; empty_group_field = pws3_file_next_empty_group(ctx->file, empty_group_field)) { if (list_len == list_size - 1) { list_size *= 1.5; list = xrealloc(list, sizeof (union list_item *) * list_size); } group = pws3_field_get_text(empty_group_field); item = xmalloc(sizeof (union list_item)); item->record.type = ITEM_TYPE_GROUP; item->record.group = xstrdup(group); list[list_len++] = item; list[list_len] = NULL; } list_size = list_len + 2; list = xrealloc(list, sizeof (union list_item *) * list_size); /* sort the final list by group and title */ qsort(list, list_len, sizeof (union list_item *), list_item_cmp); return (list); } void pwfile_destroy_list(union list_item **list) { size_t i; if (list == NULL) { return; } for (i = 0; list[i] != NULL; i++) { if (list[i]->any.type == ITEM_TYPE_RECORD) { free(list[i]->record.title); } free(list[i]->any.group); free(list[i]); } free(list); } static void update_record(struct pws3_record *pws3_record, struct record *record) { struct pws3_field *title_field; struct pws3_field *group_field; struct pws3_field *username_field; struct pws3_field *password_field; struct pws3_field *notes_field; struct pws3_field *url_field; if (record->title != NULL) { title_field = pws3_field_create(0, PWS3_RECORD_FIELD_TITLE); if (title_field == NULL) { err(1, "pws3_record_field_create"); } if (pws3_field_set_text(title_field, record->title) != 0) { err(1, "pws3_field_set_text"); } pws3_record_set_field(pws3_record, title_field); } if (record->group != NULL) { group_field = pws3_field_create(0, PWS3_RECORD_FIELD_GROUP); if (group_field == NULL) { err(1, "pws3_record_field_create"); } if (pws3_field_set_text(group_field, record->group) != 0) { err(1, "pws3_field_set_text"); } pws3_record_set_field(pws3_record, group_field); } if (record->username != NULL) { username_field = pws3_field_create(0, PWS3_RECORD_FIELD_USERNAME); if (username_field == NULL) { err(1, "pws3_record_field_create"); } if (pws3_field_set_text(username_field, record->username) != 0) { err(1, "pws3_field_set_text"); } pws3_record_set_field(pws3_record, username_field); } if (record->password != NULL) { password_field = pws3_field_create(0, PWS3_RECORD_FIELD_PASSWORD); if (password_field == NULL) { err(1, "pws3_record_field_create"); } if (pws3_field_set_text(password_field, record->password) != 0) { err(1, "pws3_field_set_text"); } pws3_record_set_field(pws3_record, password_field); } if (record->notes != NULL) { notes_field = pws3_field_create(0, PWS3_RECORD_FIELD_NOTES); if (notes_field == NULL) { err(1, "pws3_record_field_create"); } if (pws3_field_set_text(notes_field, record->notes) != 0) { err(1, "pws3_field_set_text"); } pws3_record_set_field(pws3_record, notes_field); } if (record->url != NULL) { url_field = pws3_field_create(0, PWS3_RECORD_FIELD_URL); if (url_field == NULL) { err(1, "pws3_record_field_create"); } if (pws3_field_set_text(url_field, record->url) != 0) { err(1, "pws3_field_set_text"); } pws3_record_set_field(pws3_record, url_field); } } int pwfile_create_record(struct pwm_ctx *ctx, struct record *record) { struct pws3_record *pws3_record; const unsigned char *uuid; struct record_id_entry *entry; pws3_record = pws3_record_create(); if (pws3_record == NULL) { err(1, "pws3_record_create"); } update_record(pws3_record, record); pws3_file_insert_record(ctx->file, pws3_record); uuid = pws3_field_get_uuid(pws3_record_get_field(pws3_record, PWS3_RECORD_FIELD_UUID)); entry = xmalloc(sizeof (struct record_id_entry)); entry->id = ctx->next_id++; memcpy(entry->uuid, uuid, sizeof (entry->uuid)); RB_INSERT(record_id_tree, ctx->record_id_tree, entry); return (0); } int pwfile_modify_record(struct pwm_ctx *ctx, unsigned int id, struct record *record) { const unsigned char *uuid; uuid = record_id_tree_get_uuid(ctx->record_id_tree, id); if (uuid == NULL) { return (-1); } update_record(pws3_file_get_record(ctx->file, uuid), record); return (0); } int pwfile_remove_record(struct pwm_ctx *ctx, unsigned int id) { const unsigned char *uuid; struct record_id_entry *entry; uuid = record_id_tree_get_uuid(ctx->record_id_tree, id); if (uuid == NULL) { return (-1); } pws3_record_destroy(pws3_file_remove_record(ctx->file, uuid)); entry = RB_FIND(record_id_tree, ctx->record_id_tree, &(struct record_id_entry){ .id = id }); free(RB_REMOVE(record_id_tree, ctx->record_id_tree, entry)); return (0); } int pwfile_create_group(struct pwm_ctx *ctx, const char *group) { struct pws3_record *pws3_record; struct pws3_field *group_field; struct pws3_field *empty_group_field; /* check for a record in the given group */ for (pws3_record = pws3_file_first_record(ctx->file); pws3_record != NULL; pws3_record = pws3_file_next_record(ctx->file, pws3_record)) { group_field = pws3_record_get_field(pws3_record, PWS3_RECORD_FIELD_GROUP); if ((group_field != NULL) && (strcmp(group, pws3_field_get_text(group_field)) == 0)) { return (-1); } } empty_group_field = pws3_field_create(1, PWS3_HEADER_FIELD_EMPTY_GROUPS); if (empty_group_field == NULL) { err(1, "pws3_field_create"); } if (pws3_field_set_text(empty_group_field, group) != 0) { err(1, "pws3_field_set_text"); } pws3_file_insert_empty_group(ctx->file, empty_group_field); return (0); } int pwfile_remove_group(struct pwm_ctx *ctx, const char *group) { struct pws3_field *empty_group_field; empty_group_field = pws3_file_remove_empty_group(ctx->file, group); if (empty_group_field != NULL) { return (-1); } pws3_field_destroy(empty_group_field); return (0); } struct record * pwfile_get_record(struct pwm_ctx *ctx, unsigned int id) { struct record *record; const unsigned char *uuid; struct pws3_record *pws3_record; struct pws3_field *title_field; struct pws3_field *group_field; struct pws3_field *username_field; struct pws3_field *password_field; struct pws3_field *notes_field; struct pws3_field *url_field; uuid = record_id_tree_get_uuid(ctx->record_id_tree, id); if (uuid == NULL) { return (NULL); } pws3_record = pws3_file_get_record(ctx->file, uuid); record = xmalloc(sizeof (struct record)); title_field = pws3_record_get_field(pws3_record, PWS3_RECORD_FIELD_TITLE); record->title = (title_field != NULL) ? xstrdup(pws3_field_get_text(title_field)) : NULL; group_field = pws3_record_get_field(pws3_record, PWS3_RECORD_FIELD_GROUP); record->group = (group_field != NULL) ? xstrdup(pws3_field_get_text(group_field)) : NULL; username_field = pws3_record_get_field(pws3_record, PWS3_RECORD_FIELD_USERNAME); record->username = (username_field != NULL) ? xstrdup(pws3_field_get_text(username_field)) : NULL; password_field = pws3_record_get_field(pws3_record, PWS3_RECORD_FIELD_PASSWORD); record->password = (password_field != NULL) ? xstrdup(pws3_field_get_text(password_field)) : NULL; notes_field = pws3_record_get_field(pws3_record, PWS3_RECORD_FIELD_NOTES); record->notes = (notes_field != NULL) ? xstrdup(pws3_field_get_text(notes_field)) : NULL; url_field = pws3_record_get_field(pws3_record, PWS3_RECORD_FIELD_URL); record->url = (url_field != NULL) ? xstrdup(pws3_field_get_text(url_field)) : NULL; return (record); } void pwfile_destroy_record(struct record *record) { if (record == NULL) { return; } free(record->title); free(record->group); free(record->username); free(record->password); free(record->notes); free(record->url); free(record); }