From 863c75af92654aac0edb3bb2bf206d55a24c0b2a Mon Sep 17 00:00:00 2001 From: Francois Ferrand Date: Tue, 19 Mar 2013 12:24:15 +0100 Subject: [PATCH] Gnome keyring support. --- keychain_dbus.cpp | 227 ++++++++++++++++++++++++++++++++++++++++++++-- keychain_p.h | 3 +- 2 files changed, 221 insertions(+), 9 deletions(-) diff --git a/keychain_dbus.cpp b/keychain_dbus.cpp index f0ca244..3fab944 100644 --- a/keychain_dbus.cpp +++ b/keychain_dbus.cpp @@ -14,11 +14,184 @@ using namespace QKeychain; +class GnomeKeyring: private QLibrary { +public: + typedef enum { + RESULT_OK, + RESULT_DENIED, + RESULT_NO_KEYRING_DAEMON, + RESULT_ALREADY_UNLOCKED, + RESULT_NO_SUCH_KEYRING, + RESULT_BAD_ARGUMENTS, + RESULT_IO_ERROR, + RESULT_CANCELLED, + RESULT_KEYRING_ALREADY_EXISTS, + RESULT_NO_MATCH + } Result; + typedef enum { + ITEM_GENERIC_SECRET = 0, + ITEM_NETWORK_PASSWORD, + ITEM_NOTE, + ITEM_CHAINED_KEYRING_PASSWORD, + ITEM_ENCRYPTION_KEY_PASSWORD, + ITEM_PK_STORAGE = 0x100 + } ItemType; + typedef enum { + ATTRIBUTE_TYPE_STRING, + ATTRIBUTE_TYPE_UINT32 + } AttributeType; + typedef char gchar; + typedef void* gpointer; + typedef struct { + ItemType item_type; + struct { + const gchar* name; + AttributeType type; + } attributes[32]; + } PasswordSchema; + typedef void ( *OperationGetStringCallback )( Result result, const char* string, gpointer data ); + typedef void ( *OperationDoneCallback )( Result result, gpointer data ); + typedef void ( *GDestroyNotify )( gpointer data ); + + static const char* GNOME_KEYRING_DEFAULT; + static const char* GNOME_KEYRING_SESSION; + + static bool isSupported() + { + GnomeKeyring& keyring = instance(); + return keyring.isLoaded() && + keyring.NETWORK_PASSWORD && + keyring.find_password && + keyring.store_password && + keyring.delete_password; + } + + static gpointer store_network_password( const gchar* keyring, const gchar* display_name, + const gchar* user, const gchar* server, const gchar* password, + OperationDoneCallback callback, gpointer data, GDestroyNotify destroy_data ) + { + if ( !isSupported() ) + return NULL; + return instance().store_password( instance().NETWORK_PASSWORD, + keyring, display_name, password, callback, data, destroy_data, + "user", user, "server", server, NULL ); + } + + static gpointer find_network_password( const gchar* user, const gchar* server, + OperationGetStringCallback callback, gpointer data, GDestroyNotify destroy_data ) + { + if ( !isSupported() ) + return NULL; + return instance().find_password( instance().NETWORK_PASSWORD, + callback, data, destroy_data, + "user", user, "server", server, NULL ); + } + + static gpointer delete_network_password( const gchar* user, const gchar* server, + OperationDoneCallback callback, gpointer data, GDestroyNotify destroy_data ) + { + if ( !isSupported() ) + return NULL; + return instance().delete_password( instance().NETWORK_PASSWORD, + callback, data, destroy_data, + "user", user, "server", server, NULL ); + } + +private: + GnomeKeyring(): QLibrary("gnome-keyring", 0) { + static const PasswordSchema schema = { + ITEM_NETWORK_PASSWORD, + {{ "user", ATTRIBUTE_TYPE_STRING }, + { "server", ATTRIBUTE_TYPE_STRING }, + { NULL, ( AttributeType )0 }} + }; + NETWORK_PASSWORD = &schema; + find_password = reinterpret_cast( resolve( "gnome_keyring_find_password" ) ); + store_password = reinterpret_cast(resolve( "gnome_keyring_store_password" ) ); + delete_password = reinterpret_cast( resolve( "gnome_keyring_delete_password" ) ); + } + + static GnomeKeyring& instance() { + static GnomeKeyring keyring; + return keyring; + } + + const PasswordSchema* NETWORK_PASSWORD; + typedef gpointer ( store_password_fn )( const PasswordSchema* schema, const gchar* keyring, + const gchar* display_name, const gchar* password, + OperationDoneCallback callback, gpointer data, GDestroyNotify destroy_data, + ... ); + typedef gpointer ( find_password_fn )( const PasswordSchema* schema, + OperationGetStringCallback callback, gpointer data, GDestroyNotify destroy_data, + ... ); + typedef gpointer ( delete_password_fn )( const PasswordSchema* schema, + OperationDoneCallback callback, gpointer data, GDestroyNotify destroy_data, + ... ); + find_password_fn* find_password; + store_password_fn* store_password; + delete_password_fn* delete_password; +}; +const char* GnomeKeyring::GNOME_KEYRING_DEFAULT = NULL; +const char* GnomeKeyring::GNOME_KEYRING_SESSION = "session"; + +namespace QKeychain { +enum KeyringBackend { + Backend_GnomeKeyring, + Backend_Kwallet +}; +static KeyringBackend detectKeyringBackend() +{ + if ( getenv( "GNOME_KEYRING_CONTROL" ) && GnomeKeyring::isSupported() ) + return Backend_GnomeKeyring; + return Backend_Kwallet; +} +static KeyringBackend getKeyringBackend() +{ + static KeyringBackend backend = detectKeyringBackend(); + return backend; +} +} + void ReadPasswordJobPrivate::scheduledStart() { - iface = new org::kde::KWallet( QLatin1String("org.kde.kwalletd"), QLatin1String("/modules/kwalletd"), QDBusConnection::sessionBus(), this ); - const QDBusPendingReply reply = iface->open( QLatin1String("kdewallet"), 0, q->service() ); - QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher( reply, this ); - connect( watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), this, SLOT(kwalletOpenFinished(QDBusPendingCallWatcher*)) ); + switch ( getKeyringBackend() ) { + case Backend_GnomeKeyring: + if ( !GnomeKeyring::find_network_password( key.toUtf8().constData(), q->service().toUtf8().constData(), + reinterpret_cast( &ReadPasswordJobPrivate::gnomeKeyring_cb ), + this, NULL ) ) + q->emitFinishedWithError( OtherError, tr("Unknown error") ); + break; + + case Backend_Kwallet: + iface = new org::kde::KWallet( QLatin1String("org.kde.kwalletd"), QLatin1String("/modules/kwalletd"), QDBusConnection::sessionBus(), this ); + const QDBusPendingReply reply = iface->open( QLatin1String("kdewallet"), 0, q->service() ); + QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher( reply, this ); + connect( watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), this, SLOT(kwalletOpenFinished(QDBusPendingCallWatcher*)) ); + break; + } +} + +void ReadPasswordJobPrivate::gnomeKeyring_cb( int result, const char* string, ReadPasswordJobPrivate* self ) +{ + switch ( (GnomeKeyring::Result)result ) { + case GnomeKeyring::RESULT_OK: + if ( self->dataType == ReadPasswordJobPrivate::Text ) + self->data = string; + else + self->data = QByteArray::fromBase64( string ); + self->q->emitFinished(); + break; + + case GnomeKeyring::RESULT_DENIED: self->q->emitFinishedWithError( AccessDenied, tr("Access to keychain denied") ); break; + case GnomeKeyring::RESULT_NO_KEYRING_DAEMON: self->q->emitFinishedWithError( NoBackendAvailable, tr("No keyring daemon") ); break; + case GnomeKeyring::RESULT_ALREADY_UNLOCKED: self->q->emitFinishedWithError( OtherError, tr("Already unlocked") ); break; + case GnomeKeyring::RESULT_NO_SUCH_KEYRING: self->q->emitFinishedWithError( OtherError, tr("No such keyring") ); break; + case GnomeKeyring::RESULT_BAD_ARGUMENTS: self->q->emitFinishedWithError( OtherError, tr("Bad arguments") ); break; + case GnomeKeyring::RESULT_IO_ERROR: self->q->emitFinishedWithError( OtherError, tr("I/O error") ); break; + case GnomeKeyring::RESULT_CANCELLED: self->q->emitFinishedWithError( OtherError, tr("Cancelled") ); break; + case GnomeKeyring::RESULT_KEYRING_ALREADY_EXISTS: self->q->emitFinishedWithError( OtherError, tr("Keyring already exists") ); break; + case GnomeKeyring::RESULT_NO_MATCH: self->q->emitFinishedWithError( EntryNotFound, tr("No match") ); break; + default: self->q->emitFinishedWithError( OtherError, tr("Unknown error") ); break; + } } void ReadPasswordJobPrivate::kwalletOpenFinished( QDBusPendingCallWatcher* watcher ) { @@ -129,10 +302,48 @@ void ReadPasswordJobPrivate::kwalletReadFinished( QDBusPendingCallWatcher* watch } void WritePasswordJobPrivate::scheduledStart() { - iface = new org::kde::KWallet( QLatin1String("org.kde.kwalletd"), QLatin1String("/modules/kwalletd"), QDBusConnection::sessionBus(), this ); - const QDBusPendingReply reply = iface->open( QLatin1String("kdewallet"), 0, q->service() ); - QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher( reply, this ); - connect( watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), this, SLOT(kwalletOpenFinished(QDBusPendingCallWatcher*)) ); + switch ( getKeyringBackend() ) { + case Backend_GnomeKeyring: + if ( mode == WritePasswordJobPrivate::Delete ) { + if ( !GnomeKeyring::delete_network_password( key.toUtf8().constData(), q->service().toUtf8().constData(), + reinterpret_cast( &WritePasswordJobPrivate::gnomeKeyring_cb ), + this, NULL ) ) + q->emitFinishedWithError( OtherError, tr("Unknown error") ); + } else { + QByteArray password = mode == WritePasswordJobPrivate::Text ? textData.toUtf8() : binaryData.toBase64(); + QByteArray service = q->service().toUtf8(); + if ( !GnomeKeyring::store_network_password( GnomeKeyring::GNOME_KEYRING_DEFAULT, service.constData(), + key.toUtf8().constData(), service.constData(), password.constData(), + reinterpret_cast( &WritePasswordJobPrivate::gnomeKeyring_cb ), + this, NULL ) ) + q->emitFinishedWithError( OtherError, tr("Unknown error") ); + } + break; + + case Backend_Kwallet: + iface = new org::kde::KWallet( QLatin1String("org.kde.kwalletd"), QLatin1String("/modules/kwalletd"), QDBusConnection::sessionBus(), this ); + const QDBusPendingReply reply = iface->open( QLatin1String("kdewallet"), 0, q->service() ); + QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher( reply, this ); + connect( watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), this, SLOT(kwalletOpenFinished(QDBusPendingCallWatcher*)) ); + break; + } +} + +void WritePasswordJobPrivate::gnomeKeyring_cb( int result, WritePasswordJobPrivate* self ) +{ + switch ( (GnomeKeyring::Result)result ) { + case GnomeKeyring::RESULT_OK: self->q->emitFinished(); break; + case GnomeKeyring::RESULT_DENIED: self->q->emitFinishedWithError( AccessDenied, tr("Access to keychain denied") ); break; + case GnomeKeyring::RESULT_NO_KEYRING_DAEMON: self->q->emitFinishedWithError( NoBackendAvailable, tr("No keyring daemon") ); break; + case GnomeKeyring::RESULT_ALREADY_UNLOCKED: self->q->emitFinishedWithError( OtherError, tr("Already unlocked") ); break; + case GnomeKeyring::RESULT_NO_SUCH_KEYRING: self->q->emitFinishedWithError( OtherError, tr("No such keyring") ); break; + case GnomeKeyring::RESULT_BAD_ARGUMENTS: self->q->emitFinishedWithError( OtherError, tr("Bad arguments") ); break; + case GnomeKeyring::RESULT_IO_ERROR: self->q->emitFinishedWithError( OtherError, tr("I/O error") ); break; + case GnomeKeyring::RESULT_CANCELLED: self->q->emitFinishedWithError( OtherError, tr("Cancelled") ); break; + case GnomeKeyring::RESULT_KEYRING_ALREADY_EXISTS: self->q->emitFinishedWithError( OtherError, tr("Keyring already exists") ); break; + case GnomeKeyring::RESULT_NO_MATCH: self->q->emitFinishedWithError( EntryNotFound, tr("No match") ); break; + default: self->q->emitFinishedWithError( OtherError, tr("Unknown error") ); break; + } } void WritePasswordJobPrivate::kwalletOpenFinished( QDBusPendingCallWatcher* watcher ) { diff --git a/keychain_p.h b/keychain_p.h index 9b87d08..1f0f1f3 100644 --- a/keychain_p.h +++ b/keychain_p.h @@ -67,6 +67,7 @@ public: #if defined(Q_OS_UNIX) && !defined(Q_OS_DARWIN) org::kde::KWallet* iface; + static void gnomeKeyring_cb( int result, const char* string, ReadPasswordJobPrivate* data ); friend class QKeychain::JobExecutor; private Q_SLOTS: @@ -101,9 +102,9 @@ public: #if defined(Q_OS_UNIX) && !defined(Q_OS_DARWIN) org::kde::KWallet* iface; + static void gnomeKeyring_cb( int result, WritePasswordJobPrivate* self ); friend class QKeychain::JobExecutor; - private Q_SLOTS: void kwalletOpenFinished( QDBusPendingCallWatcher* watcher ); void kwalletWriteFinished( QDBusPendingCallWatcher* watcher );