[almanah] Moved all the encryption stuff to the SQLiteVFS
- From: Álvaro Peña <alvaropg src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [almanah] Moved all the encryption stuff to the SQLiteVFS
- Date: Sat, 27 Sep 2014 17:40:00 +0000 (UTC)
commit 430638ec32f99e9276ad8f62ea8197a58afc8470
Author: Álvaro Peña <alvaropg gmail com>
Date: Sat Sep 27 19:33:02 2014 +0200
Moved all the encryption stuff to the SQLiteVFS
In another step to complete our own SQLiteVFS, the coded that
encrypt/decrypt the database has been moved from StorageManager to the
VFS.
It was required to adapt some APIs and make the call to gpgme_wait in a
blocker way, due it's required to end the encryption before to close the
application.
See Bug #659500 - Prevent the unencrypted database from hitting the disk
https://bugzilla.gnome.org/show_bug.cgi?id=659500
src/application.c | 19 +-
src/export-operation.c | 2 +-
src/import-operation.c | 2 +-
src/storage-manager.c | 466 +-----------------------
src/storage-manager.h | 4 +-
src/vfs.c | 928 ++++++++++++++++++++++++++++++++++--------------
6 files changed, 696 insertions(+), 725 deletions(-)
---
diff --git a/src/application.c b/src/application.c
index 0d1f02e..33092f1 100644
--- a/src/application.c
+++ b/src/application.c
@@ -235,12 +235,9 @@ startup (GApplication *application)
/* Open the DB */
db_filename = g_build_filename (g_get_user_data_dir (), "diary.db", NULL);
- priv->storage_manager = almanah_storage_manager_new (db_filename, NULL);
+ priv->storage_manager = almanah_storage_manager_new (db_filename);
g_free (db_filename);
- g_settings_bind (priv->settings, "encryption-key", priv->storage_manager, "encryption-key",
- G_SETTINGS_BIND_DEFAULT | G_SETTINGS_BIND_NO_SENSITIVITY);
-
if (almanah_storage_manager_connect (priv->storage_manager, &error) == FALSE) {
GtkWidget *dialog = gtk_message_dialog_new (NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR,
GTK_BUTTONS_OK,
_("Error opening database"));
@@ -359,7 +356,7 @@ handle_command_line (GApplication *application, GApplicationCommandLine *command
}
static void
-storage_manager_disconnected_cb (AlmanahStorageManager *self, const gchar *gpgme_error_message, const gchar
*warning_message, GtkApplication *application)
+storage_manager_disconnected_cb (__attribute__ ((unused)) AlmanahStorageManager *storage_manager, const
gchar *gpgme_error_message, const gchar *warning_message, GtkApplication *self)
{
if (gpgme_error_message != NULL || warning_message != NULL) {
GtkWidget *dialog = gtk_message_dialog_new (NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR,
GTK_BUTTONS_OK,
@@ -377,7 +374,15 @@ storage_manager_disconnected_cb (AlmanahStorageManager *self, const gchar *gpgme
}
/* Allow the end of the applaction */
- g_application_release (G_APPLICATION (application));
+ g_application_release (G_APPLICATION (self));
+}
+
+static gboolean
+storage_disconnect_idle_cb (AlmanahStorageManager *storage_manager)
+{
+ almanah_storage_manager_disconnect (storage_manager, NULL);
+
+ return FALSE;
}
static void
@@ -394,7 +399,7 @@ window_removed (GtkApplication *application, GtkWindow *window)
g_application_hold (G_APPLICATION (application));
g_signal_connect (priv->storage_manager, "disconnected", (GCallback)
storage_manager_disconnected_cb, application);
- almanah_storage_manager_disconnect (priv->storage_manager, NULL);
+ g_idle_add ((GSourceFunc) storage_disconnect_idle_cb, priv->storage_manager);
}
GTK_APPLICATION_CLASS (almanah_application_parent_class)->window_removed (application, window);
diff --git a/src/export-operation.c b/src/export-operation.c
index 96e9be2..f32293e 100644
--- a/src/export-operation.c
+++ b/src/export-operation.c
@@ -301,7 +301,7 @@ export_database (AlmanahExportOperation *self, GFile *destination, AlmanahExport
/* We ignore the progress callbacks, since this is a fairly fast operation, and it exports all the
entries at once. */
/* Get the input file (current unencrypted database) */
- source = g_file_new_for_path (almanah_storage_manager_get_filename (self->priv->storage_manager,
TRUE));
+ source = g_file_new_for_path (almanah_storage_manager_get_filename (self->priv->storage_manager));
/* Copy the current database to that location */
success = g_file_copy (source, destination, G_FILE_COPY_OVERWRITE, cancellable, NULL, NULL, error);
diff --git a/src/import-operation.c b/src/import-operation.c
index 27d3156..3434b92 100644
--- a/src/import-operation.c
+++ b/src/import-operation.c
@@ -448,7 +448,7 @@ import_database (AlmanahImportOperation *self, GFile *source, AlmanahImportProgr
/* Open the database */
path = g_file_get_path (source);
- database = almanah_storage_manager_new (path, NULL);
+ database = almanah_storage_manager_new (path);
g_free (path);
/* Connect to the database */
diff --git a/src/storage-manager.c b/src/storage-manager.c
index 7e22879..228a0c1 100644
--- a/src/storage-manager.c
+++ b/src/storage-manager.c
@@ -28,32 +28,24 @@
#include <stdlib.h>
#include <sys/stat.h>
#include <string.h>
-#ifdef ENABLE_ENCRYPTION
-#include <gpgme.h>
-#endif /* ENABLE_ENCRYPTION */
#include "entry.h"
#include "storage-manager.h"
#include "almanah-marshal.h"
#include "vfs.h"
-#define ENCRYPTED_SUFFIX ".encrypted"
-
static void almanah_storage_manager_finalize (GObject *object);
static void almanah_storage_manager_get_property (GObject *object, guint property_id, GValue *value,
GParamSpec *pspec);
static void almanah_storage_manager_set_property (GObject *object, guint property_id, const GValue *value,
GParamSpec *pspec);
static gboolean simple_query (AlmanahStorageManager *self, const gchar *query, GError **error, ...);
struct _AlmanahStorageManagerPrivate {
- gchar *filename, *plain_filename;
- gchar *encryption_key;
+ gchar *filename;
sqlite3 *connection;
- gboolean decrypted;
};
enum {
PROP_FILENAME = 1,
- PROP_ENCRYPTION_KEY,
};
enum {
@@ -94,12 +86,6 @@ almanah_storage_manager_class_init (AlmanahStorageManagerClass *klass)
NULL,
G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE |
G_PARAM_STATIC_STRINGS));
- g_object_class_install_property (gobject_class, PROP_ENCRYPTION_KEY,
- g_param_spec_string ("encryption-key",
- "Encryption key", "The identifier for the
encryption key in the user's keyring.",
- NULL,
- G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
-
storage_manager_signals[SIGNAL_DISCONNECTED] = g_signal_new ("disconnected",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST,
@@ -143,39 +129,26 @@ almanah_storage_manager_init (AlmanahStorageManager *self)
{
self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, ALMANAH_TYPE_STORAGE_MANAGER,
AlmanahStorageManagerPrivate);
self->priv->filename = NULL;
- self->priv->plain_filename = NULL;
- self->priv->encryption_key = NULL;
- self->priv->decrypted = FALSE;
}
/**
* almanah_storage_manager_new:
* @filename: database filename to open
- * @encryption_key: identifier for the encryption key to use in the user's keyring, or %NULL
*
* Creates a new #AlmanahStorageManager, connected to the given database @filename.
*
- * If @filename is for an encrypted database, it will automatically be changed to the canonical filename for
the unencrypted database, even if that
- * file doesn't exist, and even if Almanah was compiled without encryption support. Database filenames are
always passed as the unencrypted filename.
- *
* If @encryption_key is %NULL, encryption will be disabled.
*
* Return value: the new #AlmanahStorageManager
**/
AlmanahStorageManager *
-almanah_storage_manager_new (const gchar *filename, const gchar *encryption_key)
+almanah_storage_manager_new (const gchar *filename)
{
- gchar *new_filename = NULL;
AlmanahStorageManager *sm;
- if (g_str_has_suffix (filename, ENCRYPTED_SUFFIX) == TRUE)
- filename = new_filename = g_strndup (filename, strlen (filename) - strlen (ENCRYPTED_SUFFIX));
-
sm = g_object_new (ALMANAH_TYPE_STORAGE_MANAGER,
"filename", filename,
- "encryption-key", encryption_key,
NULL);
- g_free (new_filename);
return sm;
}
@@ -186,8 +159,6 @@ almanah_storage_manager_finalize (GObject *object)
AlmanahStorageManagerPrivate *priv = ALMANAH_STORAGE_MANAGER (object)->priv;
g_free (priv->filename);
- g_free (priv->plain_filename);
- g_free (priv->encryption_key);
/* Chain up to the parent class */
G_OBJECT_CLASS (almanah_storage_manager_parent_class)->finalize (object);
@@ -202,9 +173,6 @@ almanah_storage_manager_get_property (GObject *object, guint property_id, GValue
case PROP_FILENAME:
g_value_set_string (value, g_strdup (priv->filename));
break;
- case PROP_ENCRYPTION_KEY:
- g_value_set_string (value, priv->encryption_key);
- break;
default:
/* We don't have any other property... */
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
@@ -219,13 +187,9 @@ almanah_storage_manager_set_property (GObject *object, guint property_id, const
switch (property_id) {
case PROP_FILENAME:
- priv->plain_filename = g_strdup (g_value_get_string (value));
- priv->filename = g_strjoin (NULL, priv->plain_filename, ENCRYPTED_SUFFIX, NULL);
- break;
- case PROP_ENCRYPTION_KEY:
- g_free (priv->encryption_key);
- priv->encryption_key = g_value_dup_string (value);
- g_object_notify (object, "encryption-key");
+ if (priv->filename)
+ g_free (priv->filename);
+ priv->filename = g_strdup (g_value_get_string (value));
break;
default:
/* We don't have any other property... */
@@ -256,381 +220,20 @@ create_tables (AlmanahStorageManager *self)
simple_query (self, queries[i++], NULL);
}
-#ifdef ENABLE_ENCRYPTION
-typedef struct {
- AlmanahStorageManager *storage_manager;
- GIOChannel *cipher_io_channel;
- GIOChannel *plain_io_channel;
- gpgme_data_t gpgme_cipher;
- gpgme_data_t gpgme_plain;
- gpgme_ctx_t context;
-} CipherOperation;
-
-static gboolean
-prepare_gpgme (AlmanahStorageManager *self, gboolean encrypting, CipherOperation *operation, GError **error)
-{
- gpgme_error_t error_gpgme;
-
- /* Check for a minimum GPGME version (bgo#599598) */
- if (gpgme_check_version (MIN_GPGME_VERSION) == NULL) {
- g_set_error (error, ALMANAH_STORAGE_MANAGER_ERROR, ALMANAH_STORAGE_MANAGER_ERROR_BAD_VERSION,
- _("GPGME is not at least version %s"),
- MIN_GPGME_VERSION);
- return FALSE;
- }
-
- /* Check OpenPGP's supported */
- error_gpgme = gpgme_engine_check_version (GPGME_PROTOCOL_OpenPGP);
- if (error_gpgme != GPG_ERR_NO_ERROR) {
- g_set_error (error, ALMANAH_STORAGE_MANAGER_ERROR, ALMANAH_STORAGE_MANAGER_ERROR_UNSUPPORTED,
- _("GPGME doesn't support OpenPGP: %s"),
- gpgme_strerror (error_gpgme));
- return FALSE;
- }
-
- /* Set up for the operation */
- error_gpgme = gpgme_new (&(operation->context));
- if (error_gpgme != GPG_ERR_NO_ERROR) {
- g_set_error (error, ALMANAH_STORAGE_MANAGER_ERROR,
ALMANAH_STORAGE_MANAGER_ERROR_CREATING_CONTEXT,
- _("Error creating cipher context: %s"),
- gpgme_strerror (error_gpgme));
- return FALSE;
- }
-
- gpgme_set_protocol (operation->context, GPGME_PROTOCOL_OpenPGP);
- gpgme_set_armor (operation->context, TRUE);
- gpgme_set_textmode (operation->context, FALSE);
-
- return TRUE;
-}
-
-static gboolean
-open_db_files (AlmanahStorageManager *self, gboolean encrypting, CipherOperation *operation, GError **error)
-{
- GError *io_error = NULL;
- gpgme_error_t error_gpgme;
-
- /* Open the encrypted file */
- operation->cipher_io_channel = g_io_channel_new_file (self->priv->filename, encrypting ? "w" : "r",
&io_error);
- if (operation->cipher_io_channel == NULL) {
- g_propagate_error (error, io_error);
- return FALSE;
- }
-
- /* Pass it to GPGME */
- error_gpgme = gpgme_data_new_from_fd (&(operation->gpgme_cipher), g_io_channel_unix_get_fd
(operation->cipher_io_channel));
- if (error_gpgme != GPG_ERR_NO_ERROR) {
- g_set_error (error, ALMANAH_STORAGE_MANAGER_ERROR, ALMANAH_STORAGE_MANAGER_ERROR_OPENING_FILE,
- _("Error opening encrypted database file \"%s\": %s"),
- self->priv->filename, gpgme_strerror (error_gpgme));
- return FALSE;
- }
-
- /* Open/Create the plain file */
- operation->plain_io_channel = g_io_channel_new_file (self->priv->plain_filename, encrypting ? "r" :
"w", &io_error);
- if (operation->plain_io_channel == NULL) {
- g_propagate_error (error, io_error);
- return FALSE;
- }
-
- /* Ensure the permissions are restricted to only the current user */
- fchmod (g_io_channel_unix_get_fd (operation->plain_io_channel), S_IRWXU);
-
- /* Pass it to GPGME */
- error_gpgme = gpgme_data_new_from_fd (&(operation->gpgme_plain), g_io_channel_unix_get_fd
(operation->plain_io_channel));
- if (error_gpgme != GPG_ERR_NO_ERROR) {
- g_set_error (error, ALMANAH_STORAGE_MANAGER_ERROR, ALMANAH_STORAGE_MANAGER_ERROR_OPENING_FILE,
- _("Error opening plain database file \"%s\": %s"),
- self->priv->plain_filename, gpgme_strerror (error_gpgme));
- return FALSE;
- }
-
- return TRUE;
-}
-
-static void
-cipher_operation_free (CipherOperation *operation)
-{
- gpgme_data_release (operation->gpgme_cipher);
- gpgme_data_release (operation->gpgme_plain);
-
- if (operation->cipher_io_channel != NULL) {
- g_io_channel_flush (operation->cipher_io_channel, NULL);
- g_io_channel_unref (operation->cipher_io_channel);
- }
-
- if (operation->plain_io_channel != NULL) {
- g_io_channel_shutdown (operation->plain_io_channel, TRUE, NULL);
- g_io_channel_unref (operation->plain_io_channel);
- }
-
- /* We could free the operation before the context is even created (bgo#599598) */
- if (operation->context != NULL) {
- gpgme_signers_clear (operation->context);
- gpgme_release (operation->context);
- }
-
- g_object_unref (operation->storage_manager);
- g_free (operation);
-}
-
-static gboolean
-database_idle_cb (CipherOperation *operation)
-{
- AlmanahStorageManager *self = operation->storage_manager;
- gpgme_error_t error_gpgme;
-
- if (gpgme_wait (operation->context, &error_gpgme, FALSE) != NULL || error_gpgme != GPG_ERR_NO_ERROR) {
- struct stat db_stat;
- gchar *warning_message = NULL;
-
- /* Check to see if the encrypted file is 0B in size, which isn't good. Not much we can do
about it except quit without deleting the
- * plaintext database. */
- g_stat (self->priv->filename, &db_stat);
- if (g_file_test (self->priv->filename, G_FILE_TEST_IS_REGULAR) == FALSE || db_stat.st_size ==
0) {
- warning_message = g_strdup (_("The encrypted database is empty. The plain database
file has been left undeleted as backup."));
- } else if (g_unlink (self->priv->plain_filename) != 0) {
- /* Delete the plain file */
- warning_message = g_strdup_printf (_("Could not delete plain database file \"%s\"."),
self->priv->plain_filename);
- }
-
- /* A slight assumption that we're disconnecting at this point (we're technically only
encrypting), but a valid one. */
- g_signal_emit (self, storage_manager_signals[SIGNAL_DISCONNECTED], 0,
- (error_gpgme == GPG_ERR_NO_ERROR) ? NULL: gpgme_strerror (error_gpgme),
- warning_message);
- g_free (warning_message);
-
- /* Finished! */
- cipher_operation_free (operation);
-
- return FALSE;
- }
-
- return TRUE;
-}
-
-static gboolean
-decrypt_database (AlmanahStorageManager *self, GError **error)
-{
- GError *preparation_error = NULL;
- CipherOperation *operation;
- gpgme_error_t error_gpgme;
-
- operation = g_new0 (CipherOperation, 1);
- operation->storage_manager = g_object_ref (self);
-
- /* Set up */
- if (prepare_gpgme (self, FALSE, operation, &preparation_error) != TRUE ||
- open_db_files (self, FALSE, operation, &preparation_error) != TRUE) {
- cipher_operation_free (operation);
- g_propagate_error (error, preparation_error);
- return FALSE;
- }
-
- /* Decrypt and verify! */
- error_gpgme = gpgme_op_decrypt_verify (operation->context, operation->gpgme_cipher,
operation->gpgme_plain);
- if (error_gpgme != GPG_ERR_NO_ERROR) {
- cipher_operation_free (operation);
- g_set_error (error, ALMANAH_STORAGE_MANAGER_ERROR, ALMANAH_STORAGE_MANAGER_ERROR_DECRYPTING,
- _("Error decrypting database: %s"),
- gpgme_strerror (error_gpgme));
- return FALSE;
- }
-
- /* Do this one synchronously */
- cipher_operation_free (operation);
-
- return TRUE;
-}
-
-static gboolean
-encrypt_database (AlmanahStorageManager *self, const gchar *encryption_key, GError **error)
-{
- GError *preparation_error = NULL;
- CipherOperation *operation;
- gpgme_error_t error_gpgme;
- gpgme_key_t gpgme_keys[2] = { NULL, };
-
- operation = g_new0 (CipherOperation, 1);
- operation->storage_manager = g_object_ref (self);
-
- /* Set up */
- if (prepare_gpgme (self, TRUE, operation, &preparation_error) != TRUE) {
- cipher_operation_free (operation);
- g_propagate_error (error, preparation_error);
- return FALSE;
- }
-
- /* Set up signing and the recipient */
- error_gpgme = gpgme_get_key (operation->context, encryption_key, &gpgme_keys[0], FALSE);
- if (error_gpgme != GPG_ERR_NO_ERROR || gpgme_keys[0] == NULL) {
- cipher_operation_free (operation);
- g_set_error (error, ALMANAH_STORAGE_MANAGER_ERROR, ALMANAH_STORAGE_MANAGER_ERROR_GETTING_KEY,
- _("Error getting encryption key: %s"),
- gpgme_strerror (error_gpgme));
- return FALSE;
- }
-
- gpgme_signers_add (operation->context, gpgme_keys[0]);
-
- if (open_db_files (self, TRUE, operation, &preparation_error) != TRUE) {
- cipher_operation_free (operation);
- g_propagate_error (error, preparation_error);
- return FALSE;
- }
-
- /* Encrypt and sign! */
- error_gpgme = gpgme_op_encrypt_sign_start (operation->context, gpgme_keys, 0, operation->gpgme_plain,
operation->gpgme_cipher);
- gpgme_key_unref (gpgme_keys[0]);
-
- if (error_gpgme != GPG_ERR_NO_ERROR) {
- cipher_operation_free (operation);
-
- g_set_error (error, ALMANAH_STORAGE_MANAGER_ERROR, ALMANAH_STORAGE_MANAGER_ERROR_ENCRYPTING,
- _("Error encrypting database: %s"),
- gpgme_strerror (error_gpgme));
- return FALSE;
- }
-
- /* The operation will be completed in the idle function */
- g_idle_add ((GSourceFunc) database_idle_cb, operation);
-
- return TRUE;
-}
-
-static gchar *
-get_encryption_key (AlmanahStorageManager *self)
-{
- gchar **key_parts;
- guint i;
- gchar *encryption_key;
-
- encryption_key = g_strdup (self->priv->encryption_key);
- if (encryption_key == NULL || encryption_key[0] == '\0') {
- g_free (encryption_key);
- return NULL;
- }
-
- /* Key is generally in the form openpgp:FOOBARKEY, and GPGME doesn't like the openpgp: prefix, so it
must be removed. */
- key_parts = g_strsplit (encryption_key, ":", 2);
- g_free (encryption_key);
-
- for (i = 0; key_parts[i] != NULL; i++) {
- if (strcmp (key_parts[i], "openpgp") != 0)
- encryption_key = key_parts[i];
- else
- g_free (key_parts[i]);
- }
- g_free (key_parts);
-
- return encryption_key;
-}
-#endif /* ENABLE_ENCRYPTION */
-
-static gboolean
-back_up_file (const gchar *filename, GError **error)
-{
- GFile *original_file, *backup_file;
- gchar *backup_filename;
- gboolean retval = TRUE;
-
- /* Make a backup of the encrypted database file */
- original_file = g_file_new_for_path (filename);
- backup_filename = g_strdup_printf ("%s~", filename);
- backup_file = g_file_new_for_path (backup_filename);
- g_free (backup_filename);
-
- if (g_file_copy (original_file, backup_file, G_FILE_COPY_OVERWRITE, NULL, NULL, NULL, error) ==
FALSE) {
- retval = FALSE;
- }
-
- /* Ensure the backup is only readable to the current user. */
- if (g_chmod (backup_filename, 0600) != 0 && errno != ENOENT) {
- g_set_error (error, ALMANAH_STORAGE_MANAGER_ERROR,
ALMANAH_STORAGE_MANAGER_ERROR_CREATING_CONTEXT,
- _("Error changing database backup file permissions: %s"),
- g_strerror (errno));
- retval = FALSE;
- }
-
- g_object_unref (original_file);
- g_object_unref (backup_file);
-
- return retval;
-}
-
gboolean
almanah_storage_manager_connect (AlmanahStorageManager *self, GError **error)
{
-#ifdef ENABLE_ENCRYPTION
- struct stat encrypted_db_stat, plaintext_db_stat;
- GError *child_error = NULL;
-
- g_stat (self->priv->filename, &encrypted_db_stat);
-
- if (g_chmod (self->priv->filename, 0600) != 0 && errno != ENOENT) {
- g_set_error (error, ALMANAH_STORAGE_MANAGER_ERROR,
ALMANAH_STORAGE_MANAGER_ERROR_CREATING_CONTEXT,
- _("Error changing database file permissions: %s"),
- g_strerror (errno));
- return FALSE;
- }
-
- /* If we're decrypting, don't bother if the cipher file doesn't exist (i.e. the database hasn't yet
been created), or is empty
- * (i.e. corrupt). */
- if (g_file_test (self->priv->filename, G_FILE_TEST_IS_REGULAR) == TRUE && encrypted_db_stat.st_size >
0) {
- /* Make a backup of the encrypted database file */
- back_up_file (self->priv->filename, &child_error);
- if (child_error != NULL) {
- /* Translators: the first parameter is a filename, the second is an error message. */
- g_warning (_("Error backing up file ‘%s’: %s"), self->priv->filename,
child_error->message);
- g_clear_error (&child_error);
- }
-
- g_stat (self->priv->plain_filename, &plaintext_db_stat);
-
- /* Only decrypt the database if the plaintext database doesn't exist or is empty. If the
plaintext database exists and is non-empty,
- * don't decrypt — just use that database. */
- if (g_file_test (self->priv->plain_filename, G_FILE_TEST_IS_REGULAR) != TRUE ||
plaintext_db_stat.st_size == 0) {
- /* Decrypt the database, or display an error if that fails (but not if it fails due
to a missing encrypted DB file — just
- * fall through and try to open the plain DB file in that case). */
- if (decrypt_database (self, &child_error) != TRUE) {
- if (child_error->code != G_FILE_ERROR_NOENT) {
- g_propagate_error (error, child_error);
- return FALSE;
- }
-
- g_error_free (child_error);
- }
- }
- }
-
- self->priv->decrypted = TRUE;
-#else
- /* Make a backup of the plaintext database file */
- back_up_file (self->priv->plain_filename, &child_error);
- if (child_error != NULL) {
- /* Translators: the first parameter is a filename, the second is an error message. */
- g_warning (_("Error backing up file ‘%s’: %s"), self->priv->plain_filename,
child_error->message);
- g_clear_error (&child_error);
- }
- self->priv->decrypted = FALSE;
-#endif /* ENABLE_ENCRYPTION */
-
+ /* Our beautiful SQLite VFS */
almanah_vfs_init();
+
/* Open the plain database */
- if (sqlite3_open_v2 (self->priv->plain_filename, &(self->priv->connection), SQLITE_OPEN_READWRITE |
SQLITE_OPEN_CREATE, "almanah") != SQLITE_OK) {
+ if (sqlite3_open_v2 (self->priv->filename, &(self->priv->connection), SQLITE_OPEN_READWRITE |
SQLITE_OPEN_CREATE, "almanah") != SQLITE_OK) {
g_set_error (error, ALMANAH_STORAGE_MANAGER_ERROR, ALMANAH_STORAGE_MANAGER_ERROR_OPENING_FILE,
_("Could not open database \"%s\". SQLite provided the following error message:
%s"),
self->priv->filename, sqlite3_errmsg (self->priv->connection));
return FALSE;
}
- if (g_chmod (self->priv->plain_filename, 0600) != 0 && errno != ENOENT) {
- g_set_error (error, ALMANAH_STORAGE_MANAGER_ERROR,
ALMANAH_STORAGE_MANAGER_ERROR_CREATING_CONTEXT,
- _("Error changing database file permissions: %s"),
- g_strerror (errno));
- return FALSE;
- }
-
/* Can't hurt to create the tables now */
create_tables (self);
@@ -638,54 +241,19 @@ almanah_storage_manager_connect (AlmanahStorageManager *self, GError **error)
}
gboolean
-almanah_storage_manager_disconnect (AlmanahStorageManager *self, GError **error)
+almanah_storage_manager_disconnect (AlmanahStorageManager *self, __attribute__ ((unused)) GError **error)
{
-#ifdef ENABLE_ENCRYPTION
- gchar *encryption_key;
- GError *child_error = NULL;
-#endif /* ENABLE_ENCRYPTION */
-
- /* Close the DB connection */
- sqlite3_close (self->priv->connection);
-
-#ifdef ENABLE_ENCRYPTION
- /* If the database wasn't encrypted before we opened it, we won't encrypt it when closing. In fact,
we'll go so far as to delete the old
- * encrypted database file. */
- if (self->priv->decrypted == FALSE)
- goto delete_encrypted_db;
-
- /* Get the encryption key */
- encryption_key = get_encryption_key (self);
- if (encryption_key == NULL)
- goto delete_encrypted_db;
-
- /* Encrypt the plain DB file */
- if (encrypt_database (self, encryption_key, &child_error) != TRUE) {
- g_signal_emit (self, storage_manager_signals[SIGNAL_DISCONNECTED], 0, NULL,
child_error->message);
-
- if (g_error_matches (child_error, ALMANAH_STORAGE_MANAGER_ERROR,
ALMANAH_STORAGE_MANAGER_ERROR_GETTING_KEY) == TRUE)
- g_propagate_error (error, child_error);
- else
- g_error_free (child_error);
+ int sqlite_ret;
- g_free (encryption_key);
- return FALSE;
- }
+ sqlite_ret = sqlite3_close (self->priv->connection);
- g_free (encryption_key);
-#else /* ENABLE_ENCRYPTION */
- g_signal_emit (self, storage_manager_signals[SIGNAL_DISCONNECTED], 0, NULL, NULL);
-#endif /* !ENABLE_ENCRYPTION */
+ if (sqlite_ret != SQLITE_OK)
+ g_signal_emit (self, storage_manager_signals[SIGNAL_DISCONNECTED], 0, NULL, "Something goes
wrong closing the database");
+ else
+ g_signal_emit (self, storage_manager_signals[SIGNAL_DISCONNECTED], 0, NULL, NULL);
- return TRUE;
-#ifdef ENABLE_ENCRYPTION
-delete_encrypted_db:
- /* Delete the old encrypted database and return */
- g_unlink (self->priv->filename);
- g_signal_emit (self, storage_manager_signals[SIGNAL_DISCONNECTED], 0, NULL, NULL);
return TRUE;
-#endif /* ENABLE_ENCRYPTION */
}
static gboolean
@@ -1355,9 +923,9 @@ almanah_storage_manager_get_month_important_days (AlmanahStorageManager *self, G
}
const gchar *
-almanah_storage_manager_get_filename (AlmanahStorageManager *self, gboolean plain)
+almanah_storage_manager_get_filename (AlmanahStorageManager *self)
{
- return (plain == TRUE) ? self->priv->plain_filename : self->priv->filename;
+ return self->priv->filename;
}
/**
diff --git a/src/storage-manager.h b/src/storage-manager.h
index 51aad3d..a40a919 100644
--- a/src/storage-manager.h
+++ b/src/storage-manager.h
@@ -68,7 +68,7 @@ typedef void (*AlmanahStorageManagerSearchCallback) (AlmanahStorageManager *stor
GType almanah_storage_manager_get_type (void);
GQuark almanah_storage_manager_error_quark (void);
-AlmanahStorageManager *almanah_storage_manager_new (const gchar *filename, const gchar *encryption_key)
G_GNUC_WARN_UNUSED_RESULT G_GNUC_MALLOC;
+AlmanahStorageManager *almanah_storage_manager_new (const gchar *filename) G_GNUC_WARN_UNUSED_RESULT
G_GNUC_MALLOC;
gboolean almanah_storage_manager_connect (AlmanahStorageManager *self, GError **error);
gboolean almanah_storage_manager_disconnect (AlmanahStorageManager *self, GError **error);
@@ -93,7 +93,7 @@ AlmanahEntry *almanah_storage_manager_get_entries (AlmanahStorageManager *self,
gboolean *almanah_storage_manager_get_month_marked_days (AlmanahStorageManager *self, GDateYear year,
GDateMonth month, guint *num_days);
gboolean *almanah_storage_manager_get_month_important_days (AlmanahStorageManager *self, GDateYear year,
GDateMonth month, guint *num_days);
-const gchar *almanah_storage_manager_get_filename (AlmanahStorageManager *self, gboolean plain);
+const gchar *almanah_storage_manager_get_filename (AlmanahStorageManager *self);
gboolean almanah_storage_manager_entry_add_tag (AlmanahStorageManager *self, AlmanahEntry *entry, const
gchar *tag);
gboolean almanah_storage_manager_entry_remove_tag (AlmanahStorageManager *self, AlmanahEntry *entry, const
gchar *tag);
diff --git a/src/vfs.c b/src/vfs.c
index 7009d75..4c52e65 100644
--- a/src/vfs.c
+++ b/src/vfs.c
@@ -36,9 +36,17 @@
#include <errno.h>
#include <fcntl.h>
+#include <gio/gio.h>
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <glib/gstdio.h>
+
#include <config.h>
+#ifdef ENABLE_ENCRYPTION
+#include <gpgme.h>
#define ENCRYPTED_SUFFIX ".encrypted"
+#endif /* ENABLE_ENCRYPTION */
/*
** Size of the write buffer used by journal files in bytes.
@@ -56,208 +64,530 @@
** When using this VFS, the sqlite3_file* handles that SQLite uses are
** actually pointers to instances of type DemoFile.
*/
-typedef struct DemoFile DemoFile;
-struct DemoFile {
- sqlite3_file base; /* Base class. Must be first. */
- int fd; /* File descriptor */
-
- char *aBuffer; /* Pointer to malloc'd buffer */
- int nBuffer; /* Valid bytes of data in zBuffer */
- sqlite3_int64 iBufferOfst; /* Offset in file of zBuffer[0] */
+typedef struct AlmanahSQLiteVFS AlmanahSQLiteVFS;
+struct AlmanahSQLiteVFS
+{
+ sqlite3_file base; /* Base class. Must be first. */
+ int fd; /* File descriptor */
+
+ char *aBuffer; /* Pointer to malloc'd buffer */
+ int nBuffer; /* Valid bytes of data in zBuffer */
+ sqlite3_int64 iBufferOfst; /* Offset in file of zBuffer[0] */
+
+ gchar *plain_filename;
+#ifdef ENABLE_ENCRYPTION
+ gchar *encrypted_filename;
+ gboolean decrypted;
+#endif /* ENABLE_ENCRYPTION */
};
+#ifdef ENABLE_ENCRYPTION
+
+typedef struct {
+ GIOChannel *cipher_io_channel;
+ GIOChannel *plain_io_channel;
+ gpgme_data_t gpgme_cipher;
+ gpgme_data_t gpgme_plain;
+ gpgme_ctx_t context;
+ AlmanahSQLiteVFS *vfs;
+} CipherOperation;
+
+static gboolean
+prepare_gpgme (CipherOperation *operation)
+{
+ gpgme_error_t error_gpgme;
+
+ /* Check for a minimum GPGME version (bgo#599598) */
+ if (gpgme_check_version (MIN_GPGME_VERSION) == NULL) {
+ g_critical (_("GPGME is not at least version %s"), MIN_GPGME_VERSION);
+ return FALSE;
+ }
+
+ /* Check OpenPGP's supported */
+ error_gpgme = gpgme_engine_check_version (GPGME_PROTOCOL_OpenPGP);
+ if (error_gpgme != GPG_ERR_NO_ERROR) {
+ g_critical (_("GPGME doesn't support OpenPGP: %s"), gpgme_strerror (error_gpgme));
+ return FALSE;
+ }
+
+ /* Set up for the operation */
+ error_gpgme = gpgme_new (&(operation->context));
+ if (error_gpgme != GPG_ERR_NO_ERROR) {
+ g_critical (_("Error creating cipher context: %s"), gpgme_strerror (error_gpgme));
+ return FALSE;
+ }
+
+ gpgme_set_protocol (operation->context, GPGME_PROTOCOL_OpenPGP);
+ gpgme_set_armor (operation->context, TRUE);
+ gpgme_set_textmode (operation->context, FALSE);
+
+ return TRUE;
+}
+
+static gboolean
+open_db_files (AlmanahSQLiteVFS *self, gboolean encrypting, CipherOperation *operation, GError **error)
+{
+ GError *io_error = NULL;
+ gpgme_error_t error_gpgme;
+
+ /* Open the encrypted file */
+ operation->cipher_io_channel = g_io_channel_new_file (self->encrypted_filename, encrypting ? "w" :
"r", &io_error);
+ if (operation->cipher_io_channel == NULL) {
+ g_critical (_("Can't create a new GIOChannel for the encrypted database: %s"),
io_error->message);
+ g_propagate_error (error, io_error);
+ return FALSE;
+ }
+
+ /* Pass it to GPGME */
+ error_gpgme = gpgme_data_new_from_fd (&(operation->gpgme_cipher), g_io_channel_unix_get_fd
(operation->cipher_io_channel));
+ if (error_gpgme != GPG_ERR_NO_ERROR) {
+ g_critical (_("Error opening encrypted database file \"%s\": %s"), self->encrypted_filename,
gpgme_strerror (error_gpgme));
+ return FALSE;
+ }
+
+ /* Open/Create the plain file */
+ operation->plain_io_channel = g_io_channel_new_file (self->plain_filename, encrypting ? "r" : "w",
&io_error);
+ if (operation->plain_io_channel == NULL) {
+ g_critical (_("Can't create a new GIOChannel for the plain database: %s"), io_error->message);
+ g_propagate_error (error, io_error);
+ return FALSE;
+ }
+
+ /* Ensure the permissions are restricted to only the current user */
+ fchmod (g_io_channel_unix_get_fd (operation->plain_io_channel), S_IRWXU);
+
+ /* Pass it to GPGME */
+ error_gpgme = gpgme_data_new_from_fd (&(operation->gpgme_plain), g_io_channel_unix_get_fd
(operation->plain_io_channel));
+ if (error_gpgme != GPG_ERR_NO_ERROR) {
+ g_critical (_("Error opening plain database file \"%s\": %s"), self->plain_filename,
gpgme_strerror (error_gpgme));
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static void
+cipher_operation_free (CipherOperation *operation)
+{
+ gpgme_data_release (operation->gpgme_cipher);
+ gpgme_data_release (operation->gpgme_plain);
+
+ if (operation->cipher_io_channel != NULL) {
+ g_io_channel_flush (operation->cipher_io_channel, NULL);
+ g_io_channel_unref (operation->cipher_io_channel);
+ }
+
+ if (operation->plain_io_channel != NULL) {
+ g_io_channel_shutdown (operation->plain_io_channel, TRUE, NULL);
+ g_io_channel_unref (operation->plain_io_channel);
+ }
+
+ /* We could free the operation before the context is even created (bgo#599598) */
+ if (operation->context != NULL) {
+ gpgme_signers_clear (operation->context);
+ gpgme_release (operation->context);
+ }
+
+ g_free (operation);
+}
+
+static gboolean
+decrypt_database (AlmanahSQLiteVFS *self, GError **error)
+{
+ GError *preparation_error = NULL;
+ CipherOperation *operation;
+ gpgme_error_t error_gpgme;
+
+ operation = g_new0 (CipherOperation, 1);
+
+ /* Set up */
+ if (prepare_gpgme (operation) != TRUE || open_db_files (self, FALSE, operation, &preparation_error)
!= TRUE) {
+ cipher_operation_free (operation);
+ g_propagate_error (error, preparation_error);
+ return FALSE;
+ }
+
+ /* Decrypt and verify! */
+ error_gpgme = gpgme_op_decrypt_verify (operation->context, operation->gpgme_cipher,
operation->gpgme_plain);
+ if (error_gpgme != GPG_ERR_NO_ERROR) {
+ cipher_operation_free (operation);
+ g_critical (_("Error decrypting database: %s"), gpgme_strerror (error_gpgme));
+ return FALSE;
+ }
+
+ /* Do this one synchronously */
+ cipher_operation_free (operation);
+
+ return TRUE;
+}
+
+static gboolean
+encrypt_database (AlmanahSQLiteVFS *self, const gchar *encryption_key, GError **error)
+{
+ GError *preparation_error = NULL;
+ CipherOperation *operation;
+ gpgme_error_t error_gpgme;
+ gpgme_key_t gpgme_keys[2] = { NULL, };
+
+ operation = g_new0 (CipherOperation, 1);
+ operation->vfs = self;
+
+ /* Set up */
+ if (prepare_gpgme (operation) != TRUE) {
+ cipher_operation_free (operation);
+ g_propagate_error (error, preparation_error);
+ return FALSE;
+ }
+
+ /* Set up signing and the recipient */
+ error_gpgme = gpgme_get_key (operation->context, encryption_key, &gpgme_keys[0], FALSE);
+ if (error_gpgme != GPG_ERR_NO_ERROR || gpgme_keys[0] == NULL) {
+ cipher_operation_free (operation);
+ g_critical (_("Error getting encryption key: %s"), gpgme_strerror (error_gpgme));
+ return FALSE;
+ }
+
+ gpgme_signers_add (operation->context, gpgme_keys[0]);
+
+ if (open_db_files (self, TRUE, operation, &preparation_error) != TRUE) {
+ cipher_operation_free (operation);
+ g_propagate_error (error, preparation_error);
+ return FALSE;
+ }
+
+ /* Encrypt and sign! */
+ error_gpgme = gpgme_op_encrypt_sign_start (operation->context, gpgme_keys, 0, operation->gpgme_plain,
operation->gpgme_cipher);
+ gpgme_key_unref (gpgme_keys[0]);
+
+ if (error_gpgme != GPG_ERR_NO_ERROR) {
+ cipher_operation_free (operation);
+ g_critical (_("Error encrypting database: %s"), gpgme_strerror (error_gpgme));
+ return FALSE;
+ }
+
+ if (gpgme_wait (operation->context, &error_gpgme, TRUE) != NULL || error_gpgme != GPG_ERR_NO_ERROR) {
+ struct stat db_stat;
+ gchar *warning_message = NULL;
+
+ /* Check to see if the encrypted file is 0B in size, which isn't good. Not much we can do
about it except quit without deleting the
+ * plaintext database. */
+ g_stat (operation->vfs->encrypted_filename, &db_stat);
+ if (g_file_test (operation->vfs->encrypted_filename, G_FILE_TEST_IS_REGULAR) == FALSE ||
db_stat.st_size == 0) {
+ warning_message = g_strdup (_("The encrypted database is empty. The plain database
file has been left undeleted as backup."));
+ } else if (g_unlink (operation->vfs->plain_filename) != 0) {
+ /* Delete the plain file */
+ warning_message = g_strdup_printf (_("Could not delete plain database file \"%s\"."),
operation->vfs->plain_filename);
+ }
+
+ if (warning_message) {
+ g_critical (warning_message);
+ g_free (warning_message);
+ }
+
+ /* Finished! */
+ cipher_operation_free (operation);
+
+ return FALSE;
+ }
+
+ return TRUE;
+
+ return TRUE;
+}
+
+static gchar *
+get_encryption_key (void)
+{
+ GSettings *settings;
+ gchar *encryption_key;
+ gchar **key_parts;
+ guint i;
+
+ settings = g_settings_new ("org.gnome.almanah");
+ encryption_key = g_settings_get_string (settings, "encryption-key");
+ if (encryption_key == NULL || encryption_key[0] == '\0') {
+ g_free (encryption_key);
+ return NULL;
+ }
+
+ /* Key is generally in the form openpgp:FOOBARKEY, and GPGME doesn't like the openpgp: prefix, so it
must be removed. */
+ key_parts = g_strsplit (encryption_key, ":", 2);
+ g_free (encryption_key);
+
+ for (i = 0; key_parts[i] != NULL; i++) {
+ if (strcmp (key_parts[i], "openpgp") != 0)
+ encryption_key = key_parts[i];
+ else
+ g_free (key_parts[i]);
+ }
+ g_free (key_parts);
+
+ return encryption_key;
+}
+
+#endif /* ENABLE_ENCRYPTION */
+
+static gboolean
+back_up_file (const gchar *filename)
+{
+ GError *error = NULL;
+ GFile *original_file, *backup_file;
+ gchar *backup_filename;
+ gboolean retval = TRUE;
+
+ /* Make a backup of the encrypted database file */
+ original_file = g_file_new_for_path (filename);
+ backup_filename = g_strdup_printf ("%s~", filename);
+ backup_file = g_file_new_for_path (backup_filename);
+ g_free (backup_filename);
+
+ if (g_file_copy (original_file, backup_file, G_FILE_COPY_OVERWRITE, NULL, NULL, NULL, &error) ==
FALSE) {
+ /* Translators: The first and second params are file paths, the last param is an error
message. */
+ g_warning (_("Error copying the file from %s to %s: %s"), original_file, backup_file,
error->message);
+ retval = FALSE;
+ }
+
+ /* Ensure the backup is only readable to the current user. */
+ if (g_chmod (backup_filename, 0600) != 0 && errno != ENOENT) {
+ g_warning (_("Error changing database backup file permissions: %s"), g_strerror (errno));
+ retval = FALSE;
+ }
+
+ g_object_unref (original_file);
+ g_object_unref (backup_file);
+
+ return retval;
+}
+
/*
** Write directly to the file passed as the first argument. Even if the
-** file has a write-buffer (DemoFile.aBuffer), ignore it.
+** file has a write-buffer (AlmanahSQLiteVFS.aBuffer), ignore it.
*/
-static int demoDirectWrite(
- DemoFile *p, /* File handle */
- const void *zBuf, /* Buffer containing data to write */
- int iAmt, /* Size of data to write in bytes */
- sqlite_int64 iOfst /* File offset to write to */
-){
- off_t ofst; /* Return value from lseek() */
- size_t nWrite; /* Return value from write() */
+static int
+demoDirectWrite (AlmanahSQLiteVFS *p, /* File handle */
+ const void *zBuf, /* Buffer containing data to write */
+ int iAmt, /* Size of data to write in bytes */
+ sqlite_int64 iOfst /* File offset to write to */
+ )
+{
+ off_t ofst; /* Return value from lseek() */
+ size_t nWrite; /* Return value from write() */
- ofst = lseek(p->fd, iOfst, SEEK_SET);
- if( ofst!=iOfst ){
- return SQLITE_IOERR_WRITE;
- }
+ ofst = lseek (p->fd, iOfst, SEEK_SET);
+ if (ofst!=iOfst) {
+ return SQLITE_IOERR_WRITE;
+ }
- nWrite = write(p->fd, zBuf, iAmt);
- if( nWrite!=iAmt ){
- return SQLITE_IOERR_WRITE;
- }
+ nWrite = write (p->fd, zBuf, iAmt);
+ if (nWrite != iAmt){
+ return SQLITE_IOERR_WRITE;
+ }
- return SQLITE_OK;
+ return SQLITE_OK;
}
/*
-** Flush the contents of the DemoFile.aBuffer buffer to disk. This is a
+** Flush the contents of the AlmanahSQLiteVFS.aBuffer buffer to disk. This is a
** no-op if this particular file does not have a buffer (i.e. it is not
** a journal file) or if the buffer is currently empty.
*/
-static int demoFlushBuffer(DemoFile *p){
- int rc = SQLITE_OK;
- if( p->nBuffer ){
- rc = demoDirectWrite(p, p->aBuffer, p->nBuffer, p->iBufferOfst);
- p->nBuffer = 0;
- }
- return rc;
+static int demoFlushBuffer(AlmanahSQLiteVFS *p){
+ int rc = SQLITE_OK;
+ if( p->nBuffer ){
+ rc = demoDirectWrite(p, p->aBuffer, p->nBuffer, p->iBufferOfst);
+ p->nBuffer = 0;
+ }
+ return rc;
}
/*
** Close a file.
*/
-static int demoClose(sqlite3_file *pFile){
- int rc;
- DemoFile *p = (DemoFile*)pFile;
- rc = demoFlushBuffer(p);
- sqlite3_free(p->aBuffer);
- close(p->fd);
- return rc;
+static int
+demoClose (sqlite3_file *pFile)
+{
+ int rc;
+ AlmanahSQLiteVFS *self = (AlmanahSQLiteVFS*) pFile;
+#ifdef ENABLE_ENCRYPTION
+ gchar *encryption_key;
+ GError *child_error = NULL;
+#endif /* ENABLE_ENCRYPTION */
+
+ rc = demoFlushBuffer (self);
+ sqlite3_free (self->aBuffer);
+ close (self->fd);
+
+#ifdef ENABLE_ENCRYPTION
+ /* If the database wasn't encrypted before we opened it, we won't encrypt it when closing. In fact,
we'll go so far as to delete the old
+ * encrypted database file. */
+ if (self->decrypted == FALSE)
+ goto delete_encrypted_db;
+
+ /* Get the encryption key */
+ encryption_key = get_encryption_key ();
+ if (encryption_key == NULL)
+ goto delete_encrypted_db;
+
+ /* Encrypt the plain DB file */
+ if (encrypt_database (self, encryption_key, &child_error) != TRUE) {
+ rc = SQLITE_IOERR;
+ }
+
+ g_free (encryption_key);
+#endif /* ENABLE_ENCRYPTION */
+
+ return rc;
+
+#ifdef ENABLE_ENCRYPTION
+ delete_encrypted_db:
+ /* Delete the old encrypted database and return */
+ g_unlink (self->encrypted_filename);
+ return rc;
+#endif /* ENABLE_ENCRYPTION */
}
/*
** Read data from a file.
*/
-static int demoRead(
- sqlite3_file *pFile,
- void *zBuf,
- int iAmt,
- sqlite_int64 iOfst
-){
- DemoFile *p = (DemoFile*)pFile;
- off_t ofst; /* Return value from lseek() */
- int nRead; /* Return value from read() */
- int rc; /* Return code from demoFlushBuffer() */
-
- /* Flush any data in the write buffer to disk in case this operation
- ** is trying to read data the file-region currently cached in the buffer.
- ** It would be possible to detect this case and possibly save an
- ** unnecessary write here, but in practice SQLite will rarely read from
- ** a journal file when there is data cached in the write-buffer.
- */
- rc = demoFlushBuffer(p);
- if( rc!=SQLITE_OK ){
- return rc;
- }
-
- ofst = lseek(p->fd, iOfst, SEEK_SET);
- if( ofst!=iOfst ){
- return SQLITE_IOERR_READ;
- }
- nRead = read(p->fd, zBuf, iAmt);
-
- if( nRead==iAmt ){
- return SQLITE_OK;
- }else if( nRead>=0 ){
- return SQLITE_IOERR_SHORT_READ;
- }
-
- return SQLITE_IOERR_READ;
+static int
+demoRead (sqlite3_file *pFile, void *zBuf, int iAmt, sqlite_int64 iOfst)
+{
+ AlmanahSQLiteVFS *p = (AlmanahSQLiteVFS*)pFile;
+ off_t ofst; /* Return value from lseek() */
+ int nRead; /* Return value from read() */
+ int rc; /* Return code from demoFlushBuffer() */
+
+ /* Flush any data in the write buffer to disk in case this operation
+ ** is trying to read data the file-region currently cached in the buffer.
+ ** It would be possible to detect this case and possibly save an
+ ** unnecessary write here, but in practice SQLite will rarely read from
+ ** a journal file when there is data cached in the write-buffer.
+ */
+ rc = demoFlushBuffer (p);
+ if (rc!=SQLITE_OK) {
+ return rc;
+ }
+
+ ofst = lseek (p->fd, iOfst, SEEK_SET);
+ if (ofst!=iOfst) {
+ return SQLITE_IOERR_READ;
+ }
+ nRead = read (p->fd, zBuf, iAmt);
+
+ if (nRead==iAmt) {
+ return SQLITE_OK;
+ } else if (nRead>=0) {
+ return SQLITE_IOERR_SHORT_READ;
+ }
+
+ return SQLITE_IOERR_READ;
}
/*
** Write data to a crash-file.
*/
-static int demoWrite(
- sqlite3_file *pFile,
- const void *zBuf,
- int iAmt,
- sqlite_int64 iOfst
-){
- DemoFile *p = (DemoFile*)pFile;
+static int
+demoWrite (sqlite3_file *pFile, const void *zBuf, int iAmt, sqlite_int64 iOfst)
+{
+ AlmanahSQLiteVFS *p = (AlmanahSQLiteVFS*)pFile;
- if( p->aBuffer ){
- char *z = (char *)zBuf; /* Pointer to remaining data to write */
- int n = iAmt; /* Number of bytes at z */
- sqlite3_int64 i = iOfst; /* File offset to write to */
-
- while( n>0 ){
- int nCopy; /* Number of bytes to copy into buffer */
-
- /* If the buffer is full, or if this data is not being written directly
- ** following the data already buffered, flush the buffer. Flushing
- ** the buffer is a no-op if it is empty.
- */
- if( p->nBuffer==SQLITE_DEMOVFS_BUFFERSZ || p->iBufferOfst+p->nBuffer!=i ){
- int rc = demoFlushBuffer(p);
- if( rc!=SQLITE_OK ){
- return rc;
- }
- }
- assert( p->nBuffer==0 || p->iBufferOfst+p->nBuffer==i );
- p->iBufferOfst = i - p->nBuffer;
-
- /* Copy as much data as possible into the buffer. */
- nCopy = SQLITE_DEMOVFS_BUFFERSZ - p->nBuffer;
- if( nCopy>n ){
- nCopy = n;
- }
- memcpy(&p->aBuffer[p->nBuffer], z, nCopy);
- p->nBuffer += nCopy;
-
- n -= nCopy;
- i += nCopy;
- z += nCopy;
- }
- }else{
- return demoDirectWrite(p, zBuf, iAmt, iOfst);
- }
-
- return SQLITE_OK;
+ if (p->aBuffer) {
+ char *z = (char *)zBuf; /* Pointer to remaining data to write */
+ int n = iAmt; /* Number of bytes at z */
+ sqlite3_int64 i = iOfst; /* File offset to write to */
+
+ while (n > 0) {
+ int nCopy; /* Number of bytes to copy into buffer */
+
+ /* If the buffer is full, or if this data is not being written directly
+ ** following the data already buffered, flush the buffer. Flushing
+ ** the buffer is a no-op if it is empty.
+ */
+ if (p->nBuffer==SQLITE_DEMOVFS_BUFFERSZ || p->iBufferOfst+p->nBuffer!=i) {
+ int rc = demoFlushBuffer (p);
+ if (rc!=SQLITE_OK) {
+ return rc;
+ }
+ }
+ assert (p->nBuffer==0 || p->iBufferOfst+p->nBuffer==i);
+ p->iBufferOfst = i - p->nBuffer;
+
+ /* Copy as much data as possible into the buffer. */
+ nCopy = SQLITE_DEMOVFS_BUFFERSZ - p->nBuffer;
+ if (nCopy>n) {
+ nCopy = n;
+ }
+ memcpy (&p->aBuffer[p->nBuffer], z, nCopy);
+ p->nBuffer += nCopy;
+
+ n -= nCopy;
+ i += nCopy;
+ z += nCopy;
+ }
+ } else {
+ return demoDirectWrite (p, zBuf, iAmt, iOfst);
+ }
+
+ return SQLITE_OK;
}
/*
** Truncate a file. This is a no-op for this VFS (see header comments at
** the top of the file).
*/
-static int demoTruncate(sqlite3_file *pFile, sqlite_int64 size){
+static int
+demoTruncate(sqlite3_file *pFile, sqlite_int64 size)
+{
#if 0
- if( ftruncate(((DemoFile *)pFile)->fd, size) ) return SQLITE_IOERR_TRUNCATE;
+ if (ftruncate(((AlmanahSQLiteVFS *)pFile)->fd, size))
+ return SQLITE_IOERR_TRUNCATE;
#endif
- return SQLITE_OK;
+ return SQLITE_OK;
}
/*
** Sync the contents of the file to the persistent media.
*/
-static int demoSync(sqlite3_file *pFile, int flags){
- DemoFile *p = (DemoFile*)pFile;
- int rc;
+static int
+demoSync (sqlite3_file *pFile, int flags)
+{
+ AlmanahSQLiteVFS *p = (AlmanahSQLiteVFS*) pFile;
+ int rc;
- rc = demoFlushBuffer(p);
- if( rc!=SQLITE_OK ){
- return rc;
- }
+ rc = demoFlushBuffer (p);
+ if (rc!=SQLITE_OK) {
+ return rc;
+ }
- rc = fsync(p->fd);
- return (rc==0 ? SQLITE_OK : SQLITE_IOERR_FSYNC);
+ rc = fsync (p->fd);
+ return (rc==0 ? SQLITE_OK : SQLITE_IOERR_FSYNC);
}
/*
** Write the size of the file in bytes to *pSize.
*/
-static int demoFileSize(sqlite3_file *pFile, sqlite_int64 *pSize){
- DemoFile *p = (DemoFile*)pFile;
- int rc; /* Return code from fstat() call */
- struct stat sStat; /* Output of fstat() call */
-
- /* Flush the contents of the buffer to disk. As with the flush in the
- ** demoRead() method, it would be possible to avoid this and save a write
- ** here and there. But in practice this comes up so infrequently it is
- ** not worth the trouble.
- */
- rc = demoFlushBuffer(p);
- if( rc!=SQLITE_OK ){
- return rc;
- }
-
- rc = fstat(p->fd, &sStat);
- if( rc!=0 ) return SQLITE_IOERR_FSTAT;
- *pSize = sStat.st_size;
- return SQLITE_OK;
+static int
+demoFileSize (sqlite3_file *pFile, sqlite_int64 *pSize)
+{
+ AlmanahSQLiteVFS *p = (AlmanahSQLiteVFS*)pFile;
+ int rc; /* Return code from fstat() call */
+ struct stat sStat; /* Output of fstat() call */
+
+ /* Flush the contents of the buffer to disk. As with the flush in the
+ ** demoRead() method, it would be possible to avoid this and save a write
+ ** here and there. But in practice this comes up so infrequently it is
+ ** not worth the trouble.
+ */
+ rc = demoFlushBuffer(p);
+ if (rc != SQLITE_OK) {
+ return rc;
+ }
+
+ rc = fstat (p->fd, &sStat);
+ if (rc!=0)
+ return SQLITE_IOERR_FSTAT;
+ *pSize = sStat.st_size;
+ return SQLITE_OK;
}
/*
@@ -267,21 +597,21 @@ static int demoFileSize(sqlite3_file *pFile, sqlite_int64 *pSize){
** file is found in the file-system it is rolled back.
*/
static int demoLock(sqlite3_file *pFile, int eLock){
- return SQLITE_OK;
+ return SQLITE_OK;
}
static int demoUnlock(sqlite3_file *pFile, int eLock){
- return SQLITE_OK;
+ return SQLITE_OK;
}
static int demoCheckReservedLock(sqlite3_file *pFile, int *pResOut){
- *pResOut = 0;
- return SQLITE_OK;
+ *pResOut = 0;
+ return SQLITE_OK;
}
/*
** No xFileControl() verbs are implemented by this VFS.
*/
static int demoFileControl(sqlite3_file *pFile, int op, void *pArg){
- return SQLITE_OK;
+ return SQLITE_OK;
}
/*
@@ -290,21 +620,21 @@ static int demoFileControl(sqlite3_file *pFile, int op, void *pArg){
** access to some extent. But it is also safe to simply return 0.
*/
static int demoSectorSize(sqlite3_file *pFile){
- return 0;
+ return 0;
}
static int demoDeviceCharacteristics(sqlite3_file *pFile){
- return 0;
+ return 0;
}
/*
** Open a file handle.
*/
static int
-demoOpen(sqlite3_vfs *pVfs, /* VFS */
- const char *zName, /* File to open, or 0 for a temp file */
- sqlite3_file *pFile, /* Pointer to DemoFile struct to populate */
- int flags, /* Input SQLITE_OPEN_XXX flags */
- int *pOutFlags /* Output SQLITE_OPEN_XXX flags (or NULL) */
+demoOpen (__attribute__ ((unused)) sqlite3_vfs *pVfs, /* VFS */
+ const char *zName, /* File to open, or 0 for a temp file */
+ sqlite3_file *pFile, /* Pointer to AlmanahSQLiteVFS struct to populate */
+ int flags, /* Input SQLITE_OPEN_XXX flags */
+ int *pOutFlags /* Output SQLITE_OPEN_XXX flags (or NULL) */
)
{
static const sqlite3_io_methods demoio = {
@@ -323,7 +653,7 @@ demoOpen(sqlite3_vfs *pVfs, /* VFS */
demoDeviceCharacteristics /* xDeviceCharacteristics */
};
- DemoFile *p = (DemoFile*) pFile; /* Populate this structure */
+ AlmanahSQLiteVFS *self = (AlmanahSQLiteVFS*) pFile; /* Populate this structure */
int oflags = 0; /* flags to pass to open() call */
char *aBuf = 0;
@@ -338,24 +668,89 @@ demoOpen(sqlite3_vfs *pVfs, /* VFS */
}
}
+ memset(self, 0, sizeof(AlmanahSQLiteVFS));
+
+ self->plain_filename = g_strdup (zName);
+
+#ifdef ENABLE_ENCRYPTION
+ struct stat encrypted_db_stat, plaintext_db_stat;
+ GError *child_error = NULL;
+
+ self->encrypted_filename = g_strdup_printf ("%s%s", self->plain_filename, ENCRYPTED_SUFFIX);
+
+ if (g_chmod (self->encrypted_filename, 0600) != 0 && errno != ENOENT) {
+ return SQLITE_IOERR;
+ }
+
+ g_stat (self->encrypted_filename, &encrypted_db_stat);
+
+ /* If we're decrypting, don't bother if the cipher file doesn't exist (i.e. the database hasn't yet
been created), or is empty
+ * (i.e. corrupt). */
+ if (g_file_test (self->encrypted_filename, G_FILE_TEST_IS_REGULAR) == TRUE &&
encrypted_db_stat.st_size > 0) {
+ /* Make a backup of the encrypted database file */
+ if (back_up_file (self->encrypted_filename) != FALSE) {
+ /* Translators: the first parameter is a filename, the second is an error message. */
+ g_warning (_("Error backing up file ‘%s’"), self->encrypted_filename);
+ g_clear_error (&child_error);
+ }
+
+ g_stat (self->plain_filename, &plaintext_db_stat);
+
+ /* Only decrypt the database if the plaintext database doesn't exist or is empty. If the
plaintext database exists and is non-empty,
+ * don't decrypt — just use that database. */
+ if (g_file_test (self->plain_filename, G_FILE_TEST_IS_REGULAR) != TRUE ||
plaintext_db_stat.st_size == 0) {
+ /* Decrypt the database, or display an error if that fails (but not if it fails due
to a missing encrypted DB file — just
+ * fall through and try to open the plain DB file in that case). */
+ if (decrypt_database (self, &child_error) != TRUE) {
+ if (child_error->code != G_FILE_ERROR_NOENT) {
+ g_free (self->plain_filename);
+ g_free (self->encrypted_filename);
+ return SQLITE_IOERR;
+ }
+
+ g_error_free (child_error);
+ }
+ }
+ }
+
+ self->decrypted = TRUE;
+#else
+ /* Make a backup of the plaintext database file */
+ if (back_up_file (self->plain_filename) != TRUE) {
+ /* Translators: the first parameter is a filename. */
+ g_warning (_("Error backing up file ‘%s’"), self->priv->plain_filename);
+ g_clear_error (&child_error);
+ }
+ self->decrypted = FALSE;
+#endif /* ENABLE_ENCRYPTION */
+
if (flags & SQLITE_OPEN_EXCLUSIVE) oflags |= O_EXCL;
if (flags & SQLITE_OPEN_CREATE) oflags |= O_CREAT;
if (flags & SQLITE_OPEN_READONLY) oflags |= O_RDONLY;
if (flags & SQLITE_OPEN_READWRITE) oflags |= O_RDWR;
- memset(p, 0, sizeof(DemoFile));
- p->fd = open (zName, oflags, 0600);
- if (p->fd < 0) {
+ self->fd = open (self->plain_filename, oflags, 0600);
+ if (self->fd < 0) {
sqlite3_free (aBuf);
return SQLITE_CANTOPEN;
}
- p->aBuffer = aBuf;
+
+ if (g_chmod (self->plain_filename, 0600) != 0 && errno != ENOENT) {
+ g_critical (_("Error changing database file permissions: %s"), g_strerror (errno));
+ sqlite3_free (aBuf);
+ g_free (self->plain_filename);
+ g_free (self->encrypted_filename);
+ close (self->fd);
+ return SQLITE_IOERR;
+ }
+
+ self->aBuffer = aBuf;
if (pOutFlags) {
*pOutFlags = flags;
}
- p->base.pMethods = &demoio;
+ self->base.pMethods = &demoio;
return SQLITE_OK;
}
@@ -366,32 +761,32 @@ demoOpen(sqlite3_vfs *pVfs, /* VFS */
** file has been synced to disk before returning.
*/
static int demoDelete(sqlite3_vfs *pVfs, const char *zPath, int dirSync){
- int rc; /* Return code */
-
- rc = unlink(zPath);
- if( rc!=0 && errno==ENOENT ) return SQLITE_OK;
-
- if( rc==0 && dirSync ){
- int dfd; /* File descriptor open on directory */
- int i; /* Iterator variable */
- char zDir[MAXPATHNAME+1]; /* Name of directory containing file zPath */
-
- /* Figure out the directory name from the path of the file deleted. */
- sqlite3_snprintf(MAXPATHNAME, zDir, "%s", zPath);
- zDir[MAXPATHNAME] = '\0';
- for(i=strlen(zDir); i>1 && zDir[i]!='/'; i++);
- zDir[i] = '\0';
-
- /* Open a file-descriptor on the directory. Sync. Close. */
- dfd = open(zDir, O_RDONLY, 0);
- if( dfd<0 ){
- rc = -1;
- }else{
- rc = fsync(dfd);
- close(dfd);
- }
- }
- return (rc==0 ? SQLITE_OK : SQLITE_IOERR_DELETE);
+ int rc; /* Return code */
+
+ rc = unlink(zPath);
+ if( rc!=0 && errno==ENOENT ) return SQLITE_OK;
+
+ if( rc==0 && dirSync ){
+ int dfd; /* File descriptor open on directory */
+ int i; /* Iterator variable */
+ char zDir[MAXPATHNAME+1]; /* Name of directory containing file zPath */
+
+ /* Figure out the directory name from the path of the file deleted. */
+ sqlite3_snprintf(MAXPATHNAME, zDir, "%s", zPath);
+ zDir[MAXPATHNAME] = '\0';
+ for(i=strlen(zDir); i>1 && zDir[i]!='/'; i++);
+ zDir[i] = '\0';
+
+ /* Open a file-descriptor on the directory. Sync. Close. */
+ dfd = open(zDir, O_RDONLY, 0);
+ if( dfd<0 ){
+ rc = -1;
+ }else{
+ rc = fsync(dfd);
+ close(dfd);
+ }
+ }
+ return (rc==0 ? SQLITE_OK : SQLITE_IOERR_DELETE);
}
#ifndef F_OK
@@ -409,25 +804,25 @@ static int demoDelete(sqlite3_vfs *pVfs, const char *zPath, int dirSync){
** is both readable and writable.
*/
static int demoAccess(
- sqlite3_vfs *pVfs,
- const char *zPath,
- int flags,
- int *pResOut
-){
- int rc; /* access() return code */
- int eAccess = F_OK; /* Second argument to access() */
-
- assert( flags==SQLITE_ACCESS_EXISTS /* access(zPath, F_OK) */
- || flags==SQLITE_ACCESS_READ /* access(zPath, R_OK) */
- || flags==SQLITE_ACCESS_READWRITE /* access(zPath, R_OK|W_OK) */
- );
-
- if( flags==SQLITE_ACCESS_READWRITE ) eAccess = R_OK|W_OK;
- if( flags==SQLITE_ACCESS_READ ) eAccess = R_OK;
-
- rc = access(zPath, eAccess);
- *pResOut = (rc==0);
- return SQLITE_OK;
+ sqlite3_vfs *pVfs,
+ const char *zPath,
+ int flags,
+ int *pResOut
+ ){
+ int rc; /* access() return code */
+ int eAccess = F_OK; /* Second argument to access() */
+
+ assert( flags==SQLITE_ACCESS_EXISTS /* access(zPath, F_OK) */
+ || flags==SQLITE_ACCESS_READ /* access(zPath, R_OK) */
+ || flags==SQLITE_ACCESS_READWRITE /* access(zPath, R_OK|W_OK) */
+ );
+
+ if( flags==SQLITE_ACCESS_READWRITE ) eAccess = R_OK|W_OK;
+ if( flags==SQLITE_ACCESS_READ ) eAccess = R_OK;
+
+ rc = access(zPath, eAccess);
+ *pResOut = (rc==0);
+ return SQLITE_OK;
}
/*
@@ -442,23 +837,23 @@ static int demoAccess(
** 2. Full paths begin with a '/' character.
*/
static int demoFullPathname(
- sqlite3_vfs *pVfs, /* VFS */
- const char *zPath, /* Input path (possibly a relative path) */
- int nPathOut, /* Size of output buffer in bytes */
- char *zPathOut /* Pointer to output buffer */
-){
- char zDir[MAXPATHNAME+1];
- if( zPath[0]=='/' ){
- zDir[0] = '\0';
- }else{
- getcwd(zDir, sizeof(zDir));
- }
- zDir[MAXPATHNAME] = '\0';
+ sqlite3_vfs *pVfs, /* VFS */
+ const char *zPath, /* Input path (possibly a relative path) */
+ int nPathOut, /* Size of output buffer in bytes */
+ char *zPathOut /* Pointer to output buffer */
+ ){
+ char zDir[MAXPATHNAME+1];
+ if( zPath[0]=='/' ){
+ zDir[0] = '\0';
+ }else{
+ getcwd(zDir, sizeof(zDir));
+ }
+ zDir[MAXPATHNAME] = '\0';
- sqlite3_snprintf(nPathOut, zPathOut, "%s/%s", zDir, zPath);
- zPathOut[nPathOut-1] = '\0';
+ sqlite3_snprintf(nPathOut, zPathOut, "%s/%s", zDir, zPath);
+ zPathOut[nPathOut-1] = '\0';
- return SQLITE_OK;
+ return SQLITE_OK;
}
/*
@@ -474,17 +869,17 @@ static int demoFullPathname(
** this functionality, so the following functions are no-ops.
*/
static void *demoDlOpen(sqlite3_vfs *pVfs, const char *zPath){
- return 0;
+ return 0;
}
static void demoDlError(sqlite3_vfs *pVfs, int nByte, char *zErrMsg){
- sqlite3_snprintf(nByte, zErrMsg, "Loadable extensions are not supported");
- zErrMsg[nByte-1] = '\0';
+ sqlite3_snprintf(nByte, zErrMsg, "Loadable extensions are not supported");
+ zErrMsg[nByte-1] = '\0';
}
static void (*demoDlSym(sqlite3_vfs *pVfs, void *pH, const char *z))(void){
- return 0;
+ return 0;
}
static void demoDlClose(sqlite3_vfs *pVfs, void *pHandle){
- return;
+ return;
}
/*
@@ -492,7 +887,7 @@ static void demoDlClose(sqlite3_vfs *pVfs, void *pHandle){
** buffer with pseudo-random data.
*/
static int demoRandomness(sqlite3_vfs *pVfs, int nByte, char *zByte){
- return SQLITE_OK;
+ return SQLITE_OK;
}
/*
@@ -500,9 +895,9 @@ static int demoRandomness(sqlite3_vfs *pVfs, int nByte, char *zByte){
** of microseconds slept for.
*/
static int demoSleep(sqlite3_vfs *pVfs, int nMicro){
- sleep(nMicro / 1000000);
- usleep(nMicro % 1000000);
- return nMicro;
+ sleep(nMicro / 1000000);
+ usleep(nMicro % 1000000);
+ return nMicro;
}
/*
@@ -517,9 +912,9 @@ static int demoSleep(sqlite3_vfs *pVfs, int nMicro){
** "year 2038" problem that afflicts systems that store time this way).
*/
static int demoCurrentTime(sqlite3_vfs *pVfs, double *pTime){
- time_t t = time(0);
- *pTime = t/86400.0 + 2440587.5;
- return SQLITE_OK;
+ time_t t = time(0);
+ *pTime = t/86400.0 + 2440587.5;
+ return SQLITE_OK;
}
/*
@@ -528,31 +923,34 @@ static int demoCurrentTime(sqlite3_vfs *pVfs, double *pTime){
**
** sqlite3_vfs_register(sqlite3_demovfs(), 0);
*/
-sqlite3_vfs *sqlite3_demovfs(void){
- static sqlite3_vfs demovfs = {
- 1, /* iVersion */
- sizeof(DemoFile), /* szOsFile */
- MAXPATHNAME, /* mxPathname */
- 0, /* pNext */
- "almanah", /* zName */
- 0, /* pAppData */
- demoOpen, /* xOpen */
- demoDelete, /* xDelete */
- demoAccess, /* xAccess */
- demoFullPathname, /* xFullPathname */
- demoDlOpen, /* xDlOpen */
- demoDlError, /* xDlError */
- demoDlSym, /* xDlSym */
- demoDlClose, /* xDlClose */
- demoRandomness, /* xRandomness */
- demoSleep, /* xSleep */
- demoCurrentTime, /* xCurrentTime */
- };
- return &demovfs;
+sqlite3_vfs*
+sqlite3_demovfs (void)
+{
+ static sqlite3_vfs demovfs = {
+ 1, /* iVersion */
+ sizeof(AlmanahSQLiteVFS), /* szOsFile */
+ MAXPATHNAME, /* mxPathname */
+ 0, /* pNext */
+ "almanah", /* zName */
+ 0, /* pAppData */
+ demoOpen, /* xOpen */
+ demoDelete, /* xDelete */
+ demoAccess, /* xAccess */
+ demoFullPathname, /* xFullPathname */
+ demoDlOpen, /* xDlOpen */
+ demoDlError, /* xDlError */
+ demoDlSym, /* xDlSym */
+ demoDlClose, /* xDlClose */
+ demoRandomness, /* xRandomness */
+ demoSleep, /* xSleep */
+ demoCurrentTime, /* xCurrentTime */
+ };
+
+ return &demovfs;
}
int
-almanah_vfs_init(void)
+almanah_vfs_init (void)
{
- return sqlite3_vfs_register(sqlite3_demovfs(), 0);
+ return sqlite3_vfs_register (sqlite3_demovfs(), 0);
}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]