qtkeychain/keychain_dbus.cpp

539 lines
21 KiB
C++
Raw Normal View History

2011-11-14 09:56:55 +00:00
/******************************************************************************
2013-07-22 18:22:39 +00:00
* Copyright (C) 2011-2013 Frank Osterfeld <frank.osterfeld@gmail.com> *
2011-11-14 09:56:55 +00:00
* *
* 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. For licensing and distribution *
* details, check the accompanying file 'COPYING'. *
*****************************************************************************/
#include "keychain_p.h"
#include <QSettings>
#include <QScopedPointer>
2011-11-14 09:56:55 +00:00
using namespace QKeychain;
static QString typeKey( const QString& key )
{
return QString::fromLatin1( "%1/type" ).arg( key );
}
static QString dataKey( const QString& key )
{
return QString::fromLatin1( "%1/data" ).arg( key );
}
class GnomeKeyring : private QLibrary {
2013-03-19 11:24:15 +00:00
public:
enum Result {
2013-03-19 11:24:15 +00:00
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
};
enum ItemType {
2013-03-19 11:24:15 +00:00
ITEM_GENERIC_SECRET = 0,
ITEM_NETWORK_PASSWORD,
ITEM_NOTE,
ITEM_CHAINED_KEYRING_PASSWORD,
ITEM_ENCRYPTION_KEY_PASSWORD,
ITEM_PK_STORAGE = 0x100
};
enum AttributeType {
2013-03-19 11:24:15 +00:00
ATTRIBUTE_TYPE_STRING,
ATTRIBUTE_TYPE_UINT32
};
2013-03-19 11:24:15 +00:00
typedef char gchar;
typedef void* gpointer;
typedef struct {
ItemType item_type;
struct {
const gchar* name;
AttributeType type;
} attributes[32];
} PasswordSchema;
2013-03-19 11:24:15 +00:00
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 bool isSupported()
{
const GnomeKeyring& keyring = instance();
2013-03-19 11:24:15 +00:00
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 0;
2013-03-19 11:24:15 +00:00
return instance().store_password( instance().NETWORK_PASSWORD,
keyring, display_name, password, callback, data, destroy_data,
"user", user, "server", server, static_cast<char*>(0) );
2013-03-19 11:24:15 +00:00
}
static gpointer find_network_password( const gchar* user, const gchar* server,
OperationGetStringCallback callback, gpointer data, GDestroyNotify destroy_data )
{
2013-03-19 11:24:15 +00:00
if ( !isSupported() )
return 0;
2013-03-19 11:24:15 +00:00
return instance().find_password( instance().NETWORK_PASSWORD,
callback, data, destroy_data,
"user", user, "server", server, static_cast<char*>(0) );
}
2013-03-19 11:24:15 +00:00
static gpointer delete_network_password( const gchar* user, const gchar* server,
OperationDoneCallback callback, gpointer data, GDestroyNotify destroy_data )
{
2013-03-19 11:24:15 +00:00
if ( !isSupported() )
return 0;
2013-03-19 11:24:15 +00:00
return instance().delete_password( instance().NETWORK_PASSWORD,
callback, data, destroy_data,
"user", user, "server", server, static_cast<char*>(0) );
2013-03-19 11:24:15 +00:00
}
private:
GnomeKeyring(): QLibrary("gnome-keyring", 0) {
static const PasswordSchema schema = {
ITEM_NETWORK_PASSWORD,
{{ "user", ATTRIBUTE_TYPE_STRING },
{ "server", ATTRIBUTE_TYPE_STRING },
{ 0, static_cast<AttributeType>( 0 ) }}
2013-03-19 11:24:15 +00:00
};
2013-03-19 11:24:15 +00:00
NETWORK_PASSWORD = &schema;
find_password = reinterpret_cast<find_password_fn*>( resolve( "gnome_keyring_find_password" ) );
store_password = reinterpret_cast<store_password_fn*>( resolve( "gnome_keyring_store_password" ) );
2013-03-19 11:24:15 +00:00
delete_password = reinterpret_cast<delete_password_fn*>( 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;
};
2013-03-19 11:24:15 +00:00
const char* GnomeKeyring::GNOME_KEYRING_DEFAULT = NULL;
enum KeyringBackend {
Backend_GnomeKeyring,
Backend_Kwallet
};
2013-03-19 11:24:15 +00:00
static KeyringBackend detectKeyringBackend()
{
if ( !qgetenv( "GNOME_KEYRING_CONTROL" ).isNull() && GnomeKeyring::isSupported() )
2013-03-19 11:24:15 +00:00
return Backend_GnomeKeyring;
else
return Backend_Kwallet;
2013-03-19 11:24:15 +00:00
}
2013-03-19 11:24:15 +00:00
static KeyringBackend getKeyringBackend()
{
static KeyringBackend backend = detectKeyringBackend();
return backend;
}
void ReadPasswordJobPrivate::scheduledStart() {
2013-03-19 11:24:15 +00:00
switch ( getKeyringBackend() ) {
case Backend_GnomeKeyring:
if ( !GnomeKeyring::find_network_password( key.toUtf8().constData(), q->service().toUtf8().constData(),
reinterpret_cast<GnomeKeyring::OperationGetStringCallback>( &ReadPasswordJobPrivate::gnomeKeyring_cb ),
this, 0 ) )
2013-03-19 11:24:15 +00:00
q->emitFinishedWithError( OtherError, tr("Unknown error") );
break;
case Backend_Kwallet:
if ( QDBusConnection::sessionBus().isConnected() )
{
iface = new org::kde::KWallet( QLatin1String("org.kde.kwalletd"), QLatin1String("/modules/kwalletd"), QDBusConnection::sessionBus(), this );
const QDBusPendingReply<QString> reply = iface->networkWallet();
QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher( reply, this );
connect( watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), this, SLOT(kwalletWalletFound(QDBusPendingCallWatcher*)) );
}
else
{
// D-Bus is not reachable so none can tell us something about KWalletd
QDBusError err( QDBusError::NoServer, tr("D-Bus is not running") );
fallbackOnError( err );
}
2013-03-19 11:24:15 +00:00
break;
}
}
void ReadPasswordJobPrivate::kwalletWalletFound(QDBusPendingCallWatcher *watcher)
{
watcher->deleteLater();
const QDBusPendingReply<QString> reply = *watcher;
const QDBusPendingReply<int> pendingReply = iface->open( reply.value(), 0, q->service() );
QDBusPendingCallWatcher* pendingWatcher = new QDBusPendingCallWatcher( pendingReply, this );
connect( pendingWatcher, SIGNAL(finished(QDBusPendingCallWatcher*)), this, SLOT(kwalletOpenFinished(QDBusPendingCallWatcher*)) );
}
static QPair<Error, QString> mapGnomeKeyringError( int result )
2013-03-19 11:24:15 +00:00
{
Q_ASSERT( result != GnomeKeyring::RESULT_OK );
2013-03-19 11:24:15 +00:00
switch ( result ) {
case GnomeKeyring::RESULT_DENIED:
return qMakePair( AccessDenied, QObject::tr("Access to keychain denied") );
case GnomeKeyring::RESULT_NO_KEYRING_DAEMON:
return qMakePair( NoBackendAvailable, QObject::tr("No keyring daemon") );
case GnomeKeyring::RESULT_ALREADY_UNLOCKED:
return qMakePair( OtherError, QObject::tr("Already unlocked") );
case GnomeKeyring::RESULT_NO_SUCH_KEYRING:
return qMakePair( OtherError, QObject::tr("No such keyring") );
case GnomeKeyring::RESULT_BAD_ARGUMENTS:
return qMakePair( OtherError, QObject::tr("Bad arguments") );
case GnomeKeyring::RESULT_IO_ERROR:
return qMakePair( OtherError, QObject::tr("I/O error") );
case GnomeKeyring::RESULT_CANCELLED:
return qMakePair( OtherError, QObject::tr("Cancelled") );
case GnomeKeyring::RESULT_KEYRING_ALREADY_EXISTS:
return qMakePair( OtherError, QObject::tr("Keyring already exists") );
case GnomeKeyring::RESULT_NO_MATCH:
return qMakePair( EntryNotFound, QObject::tr("No match") );
default:
break;
}
return qMakePair( OtherError, QObject::tr("Unknown error") );
}
void ReadPasswordJobPrivate::gnomeKeyring_cb( int result, const char* string, ReadPasswordJobPrivate* self )
{
if ( result == GnomeKeyring::RESULT_OK ) {
if ( self->dataType == ReadPasswordJobPrivate::Text )
self->data = string;
else
self->data = QByteArray::fromBase64( string );
self->q->emitFinished();
} else {
const QPair<Error, QString> errorResult = mapGnomeKeyringError( result );
self->q->emitFinishedWithError( errorResult.first, errorResult.second );
}
2012-05-08 08:47:38 +00:00
}
void ReadPasswordJobPrivate::fallbackOnError(const QDBusError& err )
{
QScopedPointer<QSettings> local( !q->settings() ? new QSettings( q->service() ) : 0 );
QSettings* actual = q->settings() ? q->settings() : local.data();
if ( q->insecureFallback() && actual->contains( dataKey( key ) ) ) {
const WritePasswordJobPrivate::Mode mode = WritePasswordJobPrivate::stringToMode( actual->value( typeKey( key ) ).toString() );
if (mode == WritePasswordJobPrivate::Binary)
dataType = Binary;
else
dataType = Text;
data = actual->value( dataKey( key ) ).toByteArray();
q->emitFinished();
} else {
if ( err.type() == QDBusError::ServiceUnknown ) //KWalletd not running
q->emitFinishedWithError( NoBackendAvailable, tr("No keychain service available") );
else
q->emitFinishedWithError( OtherError, tr("Could not open wallet: %1; %2").arg( QDBusError::errorString( err.type() ), err.message() ) );
}
}
void ReadPasswordJobPrivate::kwalletOpenFinished( QDBusPendingCallWatcher* watcher ) {
watcher->deleteLater();
const QDBusPendingReply<int> reply = *watcher;
QScopedPointer<QSettings> local( !q->settings() ? new QSettings( q->service() ) : 0 );
QSettings* actual = q->settings() ? q->settings() : local.data();
if ( reply.isError() ) {
fallbackOnError( reply.error() );
return;
}
if ( actual->contains( dataKey( key ) ) ) {
// We previously stored data in the insecure QSettings, but now have KWallet available.
// Do the migration
data = actual->value( dataKey( key ) ).toByteArray();
const WritePasswordJobPrivate::Mode mode = WritePasswordJobPrivate::stringToMode( actual->value( typeKey( key ) ).toString() );
actual->remove( key );
q->emitFinished();
WritePasswordJob* j = new WritePasswordJob( q->service(), 0 );
j->setSettings( q->settings() );
j->setKey( key );
j->setAutoDelete( true );
if ( mode == WritePasswordJobPrivate::Binary )
j->setBinaryData( data );
else if ( mode == WritePasswordJobPrivate::Text )
j->setTextData( QString::fromUtf8( data ) );
else
Q_ASSERT( false );
j->start();
2012-05-08 08:47:38 +00:00
return;
}
walletHandle = reply.value();
if ( walletHandle < 0 ) {
q->emitFinishedWithError( AccessDenied, tr("Access to keychain denied") );
return;
}
2012-05-08 08:47:38 +00:00
const QDBusPendingReply<int> nextReply = iface->entryType( walletHandle, q->service(), key, q->service() );
QDBusPendingCallWatcher* nextWatcher = new QDBusPendingCallWatcher( nextReply, this );
connect( nextWatcher, SIGNAL(finished(QDBusPendingCallWatcher*)), this, SLOT(kwalletEntryTypeFinished(QDBusPendingCallWatcher*)) );
}
//Must be in sync with KWallet::EntryType (kwallet.h)
enum KWalletEntryType {
Unknown=0,
Password,
Stream,
Map
};
void ReadPasswordJobPrivate::kwalletEntryTypeFinished( QDBusPendingCallWatcher* watcher ) {
2012-05-08 08:47:38 +00:00
watcher->deleteLater();
if ( watcher->isError() ) {
const QDBusError err = watcher->error();
q->emitFinishedWithError( OtherError, tr("Could not determine data type: %1; %2").arg( QDBusError::errorString( err.type() ), err.message() ) );
return;
}
const QDBusPendingReply<int> reply = *watcher;
const int value = reply.value();
2012-05-08 08:47:38 +00:00
switch ( value ) {
case Unknown:
q->emitFinishedWithError( EntryNotFound, tr("Entry not found") );
return;
case Password:
dataType = Text;
break;
case Stream:
dataType = Binary;
break;
case Map:
q->emitFinishedWithError( EntryNotFound, tr("Unsupported entry type 'Map'") );
return;
default:
q->emitFinishedWithError( OtherError, tr("Unknown kwallet entry type '%1'").arg( value ) );
return;
}
2012-05-08 08:47:38 +00:00
const QDBusPendingCall nextReply = dataType == Text
? QDBusPendingCall( iface->readPassword( walletHandle, q->service(), key, q->service() ) )
: QDBusPendingCall( iface->readEntry( walletHandle, q->service(), key, q->service() ) );
QDBusPendingCallWatcher* nextWatcher = new QDBusPendingCallWatcher( nextReply, this );
connect( nextWatcher, SIGNAL(finished(QDBusPendingCallWatcher*)), this, SLOT(kwalletReadFinished(QDBusPendingCallWatcher*)) );
}
void ReadPasswordJobPrivate::kwalletReadFinished( QDBusPendingCallWatcher* watcher ) {
2012-05-08 08:47:38 +00:00
watcher->deleteLater();
if ( watcher->isError() ) {
const QDBusError err = watcher->error();
q->emitFinishedWithError( OtherError, tr("Could not read password: %1; %2").arg( QDBusError::errorString( err.type() ), err.message() ) );
return;
}
if ( dataType == Binary ) {
QDBusPendingReply<QByteArray> reply = *watcher;
data = reply.value();
} else {
QDBusPendingReply<QString> reply = *watcher;
data = reply.value().toUtf8();
}
q->emitFinished();
2011-11-14 09:56:55 +00:00
}
void WritePasswordJobPrivate::scheduledStart() {
2013-03-19 11:24:15 +00:00
switch ( getKeyringBackend() ) {
case Backend_GnomeKeyring:
if ( mode == WritePasswordJobPrivate::Delete ) {
if ( !GnomeKeyring::delete_network_password( key.toUtf8().constData(), q->service().toUtf8().constData(),
reinterpret_cast<GnomeKeyring::OperationDoneCallback>( &WritePasswordJobPrivate::gnomeKeyring_cb ),
this, 0 ) )
2013-03-19 11:24:15 +00:00
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<GnomeKeyring::OperationDoneCallback>( &WritePasswordJobPrivate::gnomeKeyring_cb ),
this, 0 ) )
2013-03-19 11:24:15 +00:00
q->emitFinishedWithError( OtherError, tr("Unknown error") );
}
break;
case Backend_Kwallet:
if ( QDBusConnection::sessionBus().isConnected() )
{
iface = new org::kde::KWallet( QLatin1String("org.kde.kwalletd"), QLatin1String("/modules/kwalletd"), QDBusConnection::sessionBus(), this );
const QDBusPendingReply<int> reply = iface->open( QLatin1String("kdewallet"), 0, q->service() );
QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher( reply, this );
connect( watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), this, SLOT(kwalletOpenFinished(QDBusPendingCallWatcher*)) );
}
else
{
// D-Bus is not reachable so none can tell us something about KWalletd
QDBusError err( QDBusError::NoServer, tr("D-Bus is not running") );
fallbackOnError( err );
}
}
}
QString WritePasswordJobPrivate::modeToString(Mode m)
{
switch (m) {
case Delete:
return QLatin1String("Delete");
case Text:
return QLatin1String("Text");
case Binary:
return QLatin1String("Binary");
}
Q_ASSERT_X(false, Q_FUNC_INFO, "Unhandled Mode value");
return QString();
}
WritePasswordJobPrivate::Mode WritePasswordJobPrivate::stringToMode(const QString& s)
{
if (s == QLatin1String("Delete") || s == QLatin1String("0"))
return Delete;
if (s == QLatin1String("Text") || s == QLatin1String("1"))
return Text;
if (s == QLatin1String("Binary") || s == QLatin1String("2"))
return Binary;
2013-11-11 19:34:44 +00:00
qCritical("Unexpected mode string '%s'", qPrintable(s));
return Text;
}
void WritePasswordJobPrivate::fallbackOnError(const QDBusError &err)
{
QScopedPointer<QSettings> local( !q->settings() ? new QSettings( q->service() ) : 0 );
QSettings* actual = q->settings() ? q->settings() : local.data();
2013-07-22 18:01:37 +00:00
if ( !q->insecureFallback() ) {
q->emitFinishedWithError( OtherError, tr("Could not open wallet: %1; %2").arg( QDBusError::errorString( err.type() ), err.message() ) );
return;
}
2013-07-22 18:01:37 +00:00
if ( mode == Delete ) {
actual->remove( key );
actual->sync();
q->emitFinished();
2013-07-22 18:01:37 +00:00
return;
}
2013-07-22 18:01:37 +00:00
actual->setValue( QString::fromLatin1( "%1/type" ).arg( key ), mode );
2013-07-22 18:01:37 +00:00
if ( mode == Text )
actual->setValue( QString::fromLatin1( "%1/data" ).arg( key ), textData.toUtf8() );
2013-07-22 18:01:37 +00:00
else if ( mode == Binary )
actual->setValue( QString::fromLatin1( "%1/data" ).arg( key ), binaryData );
2013-07-22 18:01:37 +00:00
actual->sync();
q->emitFinished();
}
2013-03-19 11:24:15 +00:00
void WritePasswordJobPrivate::gnomeKeyring_cb( int result, WritePasswordJobPrivate* self )
{
if ( result == GnomeKeyring::RESULT_OK ) {
self->q->emitFinished();
} else {
const QPair<Error, QString> errorResult = mapGnomeKeyringError( result );
self->q->emitFinishedWithError( errorResult.first, errorResult.second );
}
2012-05-07 16:21:22 +00:00
}
void WritePasswordJobPrivate::kwalletOpenFinished( QDBusPendingCallWatcher* watcher ) {
2012-05-07 16:21:22 +00:00
watcher->deleteLater();
QDBusPendingReply<int> reply = *watcher;
QScopedPointer<QSettings> local( !q->settings() ? new QSettings( q->service() ) : 0 );
QSettings* actual = q->settings() ? q->settings() : local.data();
2012-05-07 16:21:22 +00:00
if ( reply.isError() ) {
fallbackOnError( reply.error() );
2012-05-07 16:21:22 +00:00
return;
}
if ( actual->contains( key ) )
{
// If we had previously written to QSettings, but we now have a kwallet available, migrate and delete old insecure data
actual->remove( key );
actual->sync();
}
2012-05-07 16:21:22 +00:00
const int handle = reply.value();
if ( handle < 0 ) {
q->emitFinishedWithError( AccessDenied, tr("Access to keychain denied") );
return;
}
2012-05-07 16:21:22 +00:00
QDBusPendingReply<int> nextReply;
if ( !textData.isEmpty() )
nextReply = iface->writePassword( handle, q->service(), key, textData, q->service() );
else if ( !binaryData.isEmpty() )
nextReply = iface->writeEntry( handle, q->service(), key, binaryData, q->service() );
else
nextReply = iface->removeEntry( handle, q->service(), key, q->service() );
QDBusPendingCallWatcher* nextWatcher = new QDBusPendingCallWatcher( nextReply, this );
connect( nextWatcher, SIGNAL(finished(QDBusPendingCallWatcher*)), this, SLOT(kwalletWriteFinished(QDBusPendingCallWatcher*)) );
}
void WritePasswordJobPrivate::kwalletWriteFinished( QDBusPendingCallWatcher* watcher ) {
2012-05-07 16:21:22 +00:00
watcher->deleteLater();
QDBusPendingReply<int> reply = *watcher;
if ( reply.isError() ) {
const QDBusError err = reply.error();
q->emitFinishedWithError( OtherError, tr("Could not open wallet: %1; %2").arg( QDBusError::errorString( err.type() ), err.message() ) );
return;
}
q->emitFinished();
2011-11-14 09:56:55 +00:00
}