/* * Copyright (C) 2004 Morten Fjord-Larsen * Copyright (C) 2005 Kouji TAKAO * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Library General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifdef HAVE_CONFIG_H # include #endif #include #include #include #include #include #include #include #include "gpass/entry.h" #include "gpass/root-entry.h" #include "gpass/file.h" #include "file-100.h" /*********************************************************** * * GPassFile * ***********************************************************/ #define MAGIC_PREFIX "GPassFile version " #define MAGIC_PREFIX_LENGTH (sizeof(MAGIC_PREFIX) - 1) #define FILE_VERSION "1.1.0" #define FILE_VERSION_LENGTH (sizeof(FILE_VERSION) - 1) #define MAGIC (MAGIC_PREFIX FILE_VERSION) #define MAGIC_LENGTH (MAGIC_PREFIX_LENGTH + FILE_VERSION_LENGTH) static GObjectClass *parent_file_class = NULL; static void gpass_file_instance_init(GTypeInstance *instance, gpointer g_class) { GPassFile *self = GPASS_FILE(instance); self->path = NULL; self->master_password = NULL; } enum { FILE_PROP_0, FILE_PROP_PATH, FILE_PROP_MASTER_PASSWORD, }; static void gpass_file_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { GPassFile *self = GPASS_FILE(object); switch (prop_id) { case FILE_PROP_PATH: g_free(self->path); self->path = g_value_dup_string(value); break; case FILE_PROP_MASTER_PASSWORD: g_free(self->master_password); self->master_password = g_value_dup_string(value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void gpass_file_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { GPassFile *self = GPASS_FILE(object); switch (prop_id) { case FILE_PROP_PATH: g_value_set_string(value, self->path); break; case FILE_PROP_MASTER_PASSWORD: g_value_set_string(value, self->master_password); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void gpass_file_instance_finalize(GObject *object) { GPassFile *self = GPASS_FILE(object); g_free(self->path); g_free(self->master_password); G_OBJECT_CLASS(parent_file_class)->finalize(object); } static void gpass_file_class_init(gpointer g_class, gpointer g_class_data) { GObjectClass *gobject_class = G_OBJECT_CLASS(g_class); parent_file_class = g_type_class_peek_parent(g_class); gobject_class->set_property = gpass_file_set_property; gobject_class->get_property = gpass_file_get_property; gobject_class->finalize = gpass_file_instance_finalize; g_object_class_install_property (gobject_class, FILE_PROP_PATH, g_param_spec_string("path", _("Path"), _("The path of stored password collection file"), NULL, G_PARAM_READWRITE)); g_object_class_install_property (gobject_class, FILE_PROP_MASTER_PASSWORD, g_param_spec_string("master_password", _("Master password"), _("The master password"), NULL, G_PARAM_READWRITE)); } GType gpass_file_get_type(void) { static GType type = 0; if (type == 0) { static const GTypeInfo info = { sizeof(GPassFileClass), NULL, NULL, gpass_file_class_init, NULL, NULL, sizeof(GPassFile), 0, gpass_file_instance_init }; type = g_type_register_static(G_TYPE_OBJECT, "GPassFile", &info, 0); } return type; } static GError * file_open_read(const gchar *path, const gchar *master_password, gchar **version, GPassDecryptStream **decrypt) { GPassDecryptStream *result; FILE *fp; guchar buf[MAGIC_LENGTH]; gsize read_len; GError *error = NULL; if ((fp = fopen(path, "r")) == NULL) { g_set_error(&error, 0, errno, g_strerror(errno)); return error; } error = gpass_decrypt_stream_open(&result, fp, master_password); if (error != NULL) { fclose(fp); return error; } memset(buf, 0, MAGIC_LENGTH); error = gpass_decrypt_stream_read(result, buf, MAGIC_PREFIX_LENGTH, &read_len); if (error != NULL) { goto end; } if (read_len != MAGIC_PREFIX_LENGTH) { g_set_error(&error, 0, 0, _("Premature end of file")); goto end; } if (memcmp(buf, MAGIC_PREFIX, MAGIC_PREFIX_LENGTH) != 0) { g_set_error(&error, 0, 0, _("Incorrect password!")); goto end; } error = gpass_decrypt_stream_read(result, buf, FILE_VERSION_LENGTH, &read_len); if (error != NULL) { goto end; } if (read_len != FILE_VERSION_LENGTH) { g_set_error(&error, 0, 0, _("Premature end of file")); goto end; } *version = g_strndup(buf, FILE_VERSION_LENGTH); end: if (error == NULL) { *decrypt = result; } else { gpass_decrypt_stream_close(result); } return error; } static GError * file_open_write(const gchar *path, const gchar *master_password, const gchar *version, GPassEncryptStream **encrypt) { GPassEncryptStream *result; FILE *fp; int fd; GError *error = NULL; if ((fp = fopen(path, "w")) == NULL) { g_set_error(&error, 0, errno, g_strerror(errno)); return error; } fd = fileno(fp); if (fchmod(fd, 0600)) { g_set_error(&error, 0, errno, g_strerror(errno)); fclose(fp); return error; } error = gpass_encrypt_stream_open(&result, fp, master_password); if (error != NULL) { fclose(fp); return error; } error = gpass_encrypt_stream_write(result, MAGIC_PREFIX, MAGIC_PREFIX_LENGTH); if (error != NULL) { goto end; } error = gpass_encrypt_stream_write(result, version, strlen(version)); if (error != NULL) { goto end; } end: if (error == NULL) { *encrypt = result; } else { gpass_encrypt_stream_close(result); } return error; } GError * gpass_file_create(const gchar *path, const gchar *master_password) { gchar *dirname; GPassEncryptStream *encrypt; GError *error = NULL; dirname = g_path_get_dirname(path); if (!g_file_test(dirname, G_FILE_TEST_IS_DIR)) { if (mkdir(dirname, 0700)) { g_set_error(&error, 0, errno, g_strerror(errno)); goto end; } } error = file_open_write(path, master_password, FILE_VERSION, &encrypt); if (error != NULL) { goto end; } gpass_encrypt_stream_close(encrypt); end: g_free(dirname); return error; } GError * gpass_file_open(GPassFile **self, const gchar *path, const gchar *master_password) { GPassDecryptStream *decrypt; gchar *version; GError *error = NULL; error = file_open_read(path, master_password, &version, &decrypt); if (error == NULL) { *self = g_object_new(GPASS_TYPE_FILE, "path", path, "master_password", master_password, NULL); gpass_decrypt_stream_close(decrypt); g_free(version); } return error; } GError * gpass_file_read(GPassFile *self, GPassEntryFactory *factory, GPassEntry **entries) { GPassEntry *result; GPassDecryptStream *decrypt; GType reader_type; GPassFileReader *reader; gchar *version; GError *error; error = file_open_read(self->path, self->master_password, &version, &decrypt); if (error != NULL) { return error; } if (strcmp(version, "1.1.0") == 0) { reader_type = GPASS_TYPE_FILE_READER; } else if (strcmp(version, "1.0.0") == 0) { reader_type = GPASS_TYPE_FILE_100_READER; } else { g_set_error(&error, 0, 0, _("not support version: %s"), version); g_free(version); return error; } g_free(version); reader = g_object_new(reader_type, "decrypt_stream", decrypt, "entry_factory", factory, NULL); result = g_object_new(GPASS_TYPE_ROOT_ENTRY, NULL); error = gpass_file_reader_read(reader, result); g_object_unref(reader); if (error != NULL) { g_object_unref(result); return error; } *entries = result; return NULL; } GError * gpass_file_backup(GPassFile *self, const gchar *backup_path) { GError *error = NULL; if (g_file_test(backup_path, G_FILE_TEST_EXISTS)) { if (unlink(backup_path) < 0) { g_set_error(&error, 0, errno, g_strerror(errno)); return error; } } if (rename(self->path, backup_path) == -1) { g_set_error(&error, 0, errno, g_strerror(errno)); } return error; } GError * gpass_file_restore(GPassFile *self, const gchar *backup_path) { GError *error = NULL; if (g_file_test(backup_path, G_FILE_TEST_EXISTS)) { if (rename(backup_path, self->path) == -1) { g_set_error(&error, 0, errno, g_strerror(errno)); } } return error; } GError * gpass_file_write_with_version(GPassFile *self, GPassEntry *entries, const gchar *version) { gchar *backup_path; GPassEncryptStream *encrypt; GPassFileWriter *writer; GType writer_type; GError *error; backup_path = g_strdup_printf("%s.bak", self->path); error = gpass_file_backup(self, backup_path); if (error != NULL) { g_free(backup_path); return error; } error = file_open_write(self->path, self->master_password, version, &encrypt); if (error != NULL) { goto end; } if (strcmp(version, "1.1.0") == 0) { writer_type = GPASS_TYPE_FILE_WRITER; } else if (strcmp(version, "1.0.0") == 0) { writer_type = GPASS_TYPE_FILE_100_WRITER; } else { g_set_error(&error, 0, 0, _("not support version: %s"), version); goto end; } writer = g_object_new(writer_type, "encrypt_stream", encrypt, NULL); error = gpass_file_writer_write(writer, entries); g_object_unref(writer); end: if (error != NULL) { gpass_file_restore(self, backup_path); } g_free(backup_path); return error; } GError * gpass_file_write(GPassFile *self, GPassEntry *entries) { return gpass_file_write_with_version(self, entries, FILE_VERSION); } void gpass_file_close(GPassFile *self) { g_object_unref(self); } /*********************************************************** * * GPassFileReader * ***********************************************************/ static GObjectClass *parent_reader_class = NULL; static void gpass_file_reader_instance_init(GTypeInstance *instance, gpointer g_class) { GPassFileReader *self = GPASS_FILE_READER(instance); self->decrypt = NULL; self->factory = NULL; self->entries = g_hash_table_new(g_direct_hash, g_direct_equal); } enum { READER_PROP_0, READER_PROP_DECRYPT_STREAM, READER_PROP_ENTRY_FACTORY }; static void gpass_file_reader_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { GPassFileReader *self = GPASS_FILE_READER(object); switch (prop_id) { case READER_PROP_DECRYPT_STREAM: self->decrypt = g_value_get_object(value); break; case READER_PROP_ENTRY_FACTORY: self->factory = g_value_get_object(value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void gpass_file_reader_instance_finalize(GObject *object) { GPassFileReader *self = GPASS_FILE_READER(object); if (self->decrypt != NULL) { gpass_decrypt_stream_close(self->decrypt); } g_hash_table_destroy(self->entries); G_OBJECT_CLASS(parent_reader_class)->finalize(object); } static GError * reader_read_number(GPassDecryptStream *decrypt, gint *number) { guchar buffer[4]; gsize read_len; GError *error = gpass_decrypt_stream_read(decrypt, buffer, 4, &read_len); if (error != NULL) { return error; } if (read_len != 4) { g_set_error(&error, 0, 0, _("data is too short")); return error; } *number = GINT_FROM_LE(*((gint *) buffer)); return NULL; } static GError * reader_read_string(GPassDecryptStream *decrypt, gchar **str) { gchar *result; gint len; gsize read_len; GError *error = reader_read_number(decrypt, &len); if (error != NULL) { return error; } result = g_malloc(sizeof(gchar) * len + 1); error = gpass_decrypt_stream_read(decrypt, result, len, &read_len); if (error != NULL) { g_free(result); return error; } if (read_len != len) { g_free(result); g_set_error(&error, 0, 0, _("data is too short")); return error; } result[len] = '\0'; *str = result; return NULL; } static GError * reader_read_entry(GPassFileReader *self, guint *id, guint *parent_id, GPassEntry **entry) { gchar *type; GPassEntry *result; GPassAttributeList *attributes; gint len; guchar *buffer; gsize read_len; GError *error = NULL; error = reader_read_number(self->decrypt, id); if (error != NULL) { return error; } error = reader_read_number(self->decrypt, parent_id); if (error != NULL) { return error; } error = reader_read_string(self->decrypt, &type); if (error != NULL) { return error; } error = gpass_entry_factory_create_entry(self->factory, type, &result); g_free(type); if (error != NULL) { return error; } error = reader_read_number(self->decrypt, &len); if (error != NULL) { goto end; } buffer = g_malloc(sizeof(guchar) * len); error = gpass_decrypt_stream_read(self->decrypt, buffer, len, &read_len); if (error != NULL) { g_free(buffer); goto end; } if (read_len != len) { g_free(buffer); g_set_error(&error, 0, 0, _("data is too short")); goto end; } attributes = gpass_entry_class_attributes(GPASS_ENTRY_GET_CLASS(result)); error = gpass_attribute_list_load(attributes, buffer, len, &read_len); g_free(buffer); if (error != NULL) { g_object_unref(attributes); goto end; } if (read_len != len) { g_object_unref(attributes); g_set_error(&error, 0, 0, _("invalid data")); goto end; } gpass_entry_set_attributes(result, attributes); g_object_unref(attributes); end: if (error != NULL) { g_object_unref(result); } else { *entry = result; } return error; } static void gpass_file_reader_class_init(gpointer g_class, gpointer g_class_data) { GObjectClass *gobject_class = G_OBJECT_CLASS(g_class); GPassFileReaderClass *reader_class = GPASS_FILE_READER_CLASS(g_class); parent_reader_class = g_type_class_peek_parent(g_class); gobject_class->set_property = gpass_file_reader_set_property; gobject_class->finalize = gpass_file_reader_instance_finalize; reader_class->read_entry = reader_read_entry; g_object_class_install_property (gobject_class, READER_PROP_DECRYPT_STREAM, g_param_spec_object("decrypt_stream", _("GPassDecryptStream"), _("The object of GPassDecryptStream"), GPASS_TYPE_DECRYPT_STREAM, G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY)); g_object_class_install_property (gobject_class, READER_PROP_ENTRY_FACTORY, g_param_spec_object("entry_factory", _("GPassEntryFactory"), _("The object of GPassEntryFactory"), GPASS_TYPE_ENTRY_FACTORY, G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY)); } GType gpass_file_reader_get_type(void) { static GType type = 0; if (type == 0) { static const GTypeInfo info = { sizeof(GPassFileReaderClass), NULL, NULL, gpass_file_reader_class_init, NULL, NULL, sizeof(GPassFileReader), 0, gpass_file_reader_instance_init }; type = g_type_register_static(G_TYPE_OBJECT, "GPassFileReader", &info, 0); } return type; } GError * gpass_file_reader_read(GPassFileReader *self, GPassEntry *entries) { while (!gpass_decrypt_stream_eof(self->decrypt)) { GPassFileReaderClass *klass = GPASS_FILE_READER_GET_CLASS(self); GPassEntry *entry; guint id, parent_id; GError *error; error = klass->read_entry(self, &id, &parent_id, &entry); if (error != NULL) { return error; } if (parent_id == 0) { gpass_entry_append(entries, entry); } else { GPassEntry *parent = g_hash_table_lookup(self->entries, GINT_TO_POINTER(parent_id)); if (parent == NULL) { const gchar *type; gchar *name; g_object_get(entry, "type", &type, "name", &name, NULL); g_set_error(&error, 0, 0, _("could not find parent entry: " "id=%d parent_id=%d type=%s name=%s"), id, parent_id, type, name); g_free(name); return error; } gpass_entry_append(parent, entry); } g_hash_table_insert(self->entries, GINT_TO_POINTER(id), entry); } return NULL; } /*********************************************************** * * GPassFileWriter * ***********************************************************/ static GObjectClass *parent_writer_class = NULL; static void gpass_file_writer_instance_init(GTypeInstance *instance, gpointer g_class) { GPassFileWriter *self = GPASS_FILE_WRITER(instance); self->encrypt = NULL; } enum { WRITER_PROP_0, WRITER_PROP_ENCRYPT_STREAM }; static void gpass_file_writer_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { GPassFileWriter *self = GPASS_FILE_WRITER(object); switch (prop_id) { case WRITER_PROP_ENCRYPT_STREAM: self->encrypt = g_value_get_object(value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void gpass_file_writer_instance_finalize(GObject *object) { GPassFileWriter *self = GPASS_FILE_WRITER(object); if (self->encrypt != NULL) { gpass_encrypt_stream_close(self->encrypt); } G_OBJECT_CLASS(parent_writer_class)->finalize(object); } static GError * writer_write_number(GPassEncryptStream *encrypt, gint number) { guchar *data; number = GINT_TO_LE(number); data = (guchar *) &number; return gpass_encrypt_stream_write(encrypt, data, sizeof(gint)); } static GError * writer_write_string(GPassEncryptStream *encrypt, const gchar *str) { gint len; GError *error; if (str == NULL || *str == '\0') { return writer_write_number(encrypt, 0); } len = strlen(str); error = writer_write_number(encrypt, len); if (error != NULL) { return error; } return gpass_encrypt_stream_write(encrypt, str, len); } static GError * writer_write_entry(GPassFileWriter *self, guint id, guint parent_id, GPassEntry *entry) { GPassAttributeList *attributes; const gchar *type; GString *buf; GError *error; error = writer_write_number(self->encrypt, id); if (error != NULL) { return error; } error = writer_write_number(self->encrypt, parent_id); if (error != NULL) { return error; } g_object_get(entry, "type", &type, NULL); error = writer_write_string(self->encrypt, type); if (error != NULL) { return error; } attributes = gpass_entry_class_attributes(GPASS_ENTRY_GET_CLASS(entry)); gpass_entry_get_attributes(entry, attributes); buf = g_string_new(NULL); error = gpass_attribute_list_dump(attributes, &buf); g_object_unref(attributes); if (error != NULL) { goto end; } error = writer_write_number(self->encrypt, buf->len); if (error != NULL) { goto end; } error = gpass_encrypt_stream_write(self->encrypt, buf->str, buf->len); end: g_string_free(buf, TRUE); return error; } static void gpass_file_writer_class_init(gpointer g_class, gpointer g_class_data) { GObjectClass *gobject_class = G_OBJECT_CLASS(g_class); GPassFileWriterClass *writer_class = GPASS_FILE_WRITER_CLASS(g_class); parent_writer_class = g_type_class_peek_parent(g_class); gobject_class->set_property = gpass_file_writer_set_property; gobject_class->finalize = gpass_file_writer_instance_finalize; writer_class->write_entry = writer_write_entry; g_object_class_install_property (gobject_class, WRITER_PROP_ENCRYPT_STREAM, g_param_spec_object("encrypt_stream", _("GPassEncryptStream"), _("The object of GPassEncryptStream"), GPASS_TYPE_ENCRYPT_STREAM, G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY)); } GType gpass_file_writer_get_type(void) { static GType type = 0; if (type == 0) { static const GTypeInfo info = { sizeof(GPassFileWriterClass), NULL, NULL, gpass_file_writer_class_init, NULL, NULL, sizeof(GPassFileWriter), 0, gpass_file_writer_instance_init }; type = g_type_register_static(G_TYPE_OBJECT, "GPassFileWriter", &info, 0); } return type; } static GError * writer_write_children(GPassFileWriter *self, guint *current_id, guint parent_id, GPassEntry *entries) { GPassFileWriterClass *klass = GPASS_FILE_WRITER_GET_CLASS(self); GPassEntry *p; for (p = gpass_entry_first_child(entries); p != NULL; p = gpass_entry_next_sibling(p)) { GError *error; (*current_id)++; error = klass->write_entry(self, *current_id, parent_id, p); if (error != NULL) { return error; } if (gpass_entry_has_child(p)) { error = writer_write_children(self, current_id, *current_id, p); if (error != NULL) { return error; } } } return NULL; } GError * gpass_file_writer_write(GPassFileWriter *writer, GPassEntry *entries) { guint current_id = 0; return writer_write_children(writer, ¤t_id, 0, entries); }