234 lines
8.3 KiB
C++
234 lines
8.3 KiB
C++
/*
|
|
* Copyright (C) 2012 Intel Corporation
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) version 3.
|
|
*
|
|
* This library 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
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
|
* 02110-1301 USA
|
|
*/
|
|
|
|
#include <config.h>
|
|
|
|
#ifdef USE_GNOME_KEYRING
|
|
|
|
extern "C" {
|
|
#include <libsecret/secret.h>
|
|
}
|
|
|
|
#include "GNOMEPlatform.h"
|
|
|
|
#include <syncevo/Exception.h>
|
|
#include <syncevo/UserInterface.h>
|
|
#include <syncevo/SyncConfig.h>
|
|
#include <syncevo/GLibSupport.h>
|
|
|
|
#include <syncevo/declarations.h>
|
|
SE_BEGIN_CXX
|
|
|
|
/**
|
|
* GNOME keyring distinguishes between empty and unset
|
|
* password keys. This function returns NULL for an
|
|
* empty std::string.
|
|
*/
|
|
inline const char *passwdStr(const std::string &str)
|
|
{
|
|
return str.empty() ? NULL : str.c_str();
|
|
}
|
|
|
|
static bool UseGNOMEKeyring(const InitStateTri &keyring)
|
|
{
|
|
// Disabled by user?
|
|
if (keyring.getValue() == InitStateTri::VALUE_FALSE) {
|
|
return false;
|
|
}
|
|
|
|
// If explicitly selected, it must be us.
|
|
if (keyring.getValue() == InitStateTri::VALUE_STRING &&
|
|
!boost::iequals(keyring.get(), "GNOME")) {
|
|
return false;
|
|
}
|
|
|
|
// Use GNOME Keyring.
|
|
return true;
|
|
}
|
|
|
|
class LibSecretHash : public GHashTableCXX
|
|
{
|
|
std::list<std::string> m_buffer;
|
|
|
|
public:
|
|
LibSecretHash(const ConfigPasswordKey &key) :
|
|
GHashTableCXX(g_hash_table_new(g_str_hash, g_str_equal), TRANSFER_REF)
|
|
{
|
|
// see https://developer.gnome.org/libsecret/0.16/libsecret-SecretSchema.html#SECRET-SCHEMA-COMPAT-NETWORK:CAPS
|
|
insert("user", key.user);
|
|
insert("domain", key.domain);
|
|
insert("server", key.server);
|
|
insert("object", key.object);
|
|
insert("protocol", key.protocol);
|
|
insert("authtype", key.authtype);
|
|
if (key.port) {
|
|
std::string value = StringPrintf("%d", key.port);
|
|
insert("port", value);
|
|
}
|
|
}
|
|
|
|
/** Keys are expected to be constants and not copied. Values are copied. */
|
|
void insert(const char *key, const std::string &value)
|
|
{
|
|
if (!value.empty()) {
|
|
m_buffer.push_back(value);
|
|
g_hash_table_insert(get(),
|
|
const_cast<char *>(key),
|
|
const_cast<char *>(m_buffer.back().c_str()));
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Sometimes libsecret and GNOME Keyring fail to initialize
|
|
* the encryption: https://bugzilla.gnome.org/show_bug.cgi?id=778357
|
|
* If that happens, then trying with a new SecretService instance
|
|
* may work.
|
|
*
|
|
* When libsecret detects that, we get a well-defined error.
|
|
* When GNOME Keyring detects it, the error is less obvious.
|
|
*/
|
|
static bool IsSharedSecretError(const GErrorCXX &gerror)
|
|
{
|
|
bool result = gerror.matches(SECRET_ERROR, SECRET_ERROR_PROTOCOL) /* = "received an invalid or unencryptable secret" */ ||
|
|
strstr(gerror->message, "The secret was transferred or encrypted in an invalid way");
|
|
SE_LOG_DEBUG(NULL, "IsSharedSecretError: %d/%d/%s: %s", (int)gerror->domain, (int)gerror->code, gerror->message, result ? "yes" : "no");
|
|
return result;
|
|
}
|
|
|
|
bool GNOMELoadPasswordSlot(const InitStateTri &keyring,
|
|
const std::string &passwordName,
|
|
const std::string &descr,
|
|
const ConfigPasswordKey &key,
|
|
InitStateString &password)
|
|
{
|
|
if (!UseGNOMEKeyring(keyring)) {
|
|
SE_LOG_DEBUG(NULL, "not using GNOME keyring");
|
|
return false;
|
|
}
|
|
|
|
LibSecretHash hash(key);
|
|
for (int i = 0; ; i++ ) {
|
|
GErrorCXX gerror;
|
|
PlainGStr result(secret_password_lookupv_sync(SECRET_SCHEMA_COMPAT_NETWORK,
|
|
hash,
|
|
NULL,
|
|
gerror));
|
|
|
|
// if find password stored in gnome keyring
|
|
if (gerror) {
|
|
/* It is uncertain whether we end up here at all when such
|
|
an error occurs. Check just in case. */
|
|
if (IsSharedSecretError(gerror) &&
|
|
i < 3) {
|
|
SE_LOG_DEBUG(NULL, "disconnecting secret service: %u/%d = %s", gerror->domain, gerror->code, gerror->message);
|
|
secret_service_disconnect();
|
|
} else {
|
|
gerror.throwError(SE_HERE, StringPrintf("looking up password '%s'", descr.c_str()));
|
|
}
|
|
} else if (result.get()) {
|
|
SE_LOG_DEBUG(NULL, "%s: loaded password from GNOME keyring using %s",
|
|
key.description.c_str(),
|
|
key.toString().c_str());
|
|
password = result;
|
|
break;
|
|
} else {
|
|
/*
|
|
* There have been cases where "received an invalid or
|
|
* unencryptable secret" was printed to the console right
|
|
* before we end up here. Apparently the error doesn't
|
|
* get propagated properly to us.
|
|
*
|
|
* To cope with that, we try to disconnect and check again.
|
|
*/
|
|
if (i < 3) {
|
|
SE_LOG_DEBUG(NULL, "disconnecting secret service: password not found");
|
|
secret_service_disconnect();
|
|
} else {
|
|
SE_LOG_DEBUG(NULL, "password not in GNOME keyring using %s",
|
|
key.toString().c_str());
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool GNOMESavePasswordSlot(const InitStateTri &keyring,
|
|
const std::string &passwordName,
|
|
const std::string &password,
|
|
const ConfigPasswordKey &key)
|
|
{
|
|
if (!UseGNOMEKeyring(keyring)) {
|
|
SE_LOG_DEBUG(NULL, "not using GNOME keyring");
|
|
return false;
|
|
}
|
|
|
|
// Cannot store a password for just a user, that's ambiguous.
|
|
// Also, a password without server ("user=foo") somehow removes
|
|
// the password with server ("user=foo server=bar").
|
|
if (key.user.empty() ||
|
|
(key.domain.empty() && key.server.empty() && key.object.empty())) {
|
|
SE_THROW(StringPrintf("%s: cannot store password in GNOME keyring, not enough attributes (%s). Try setting syncURL or remoteDeviceID if this is a sync password.",
|
|
key.description.c_str(),
|
|
key.toString().c_str()));
|
|
}
|
|
|
|
LibSecretHash hash(key);
|
|
std::string label;
|
|
if (!key.user.empty() && !key.server.empty()) {
|
|
// This emulates the behavior of libgnomekeyring.
|
|
label = key.user + "@" + key.server;
|
|
} else {
|
|
label = passwordName;
|
|
}
|
|
for (int i = 0; ; i++) {
|
|
GErrorCXX gerror;
|
|
gboolean result = secret_password_storev_sync(SECRET_SCHEMA_COMPAT_NETWORK,
|
|
hash,
|
|
NULL,
|
|
label.c_str(),
|
|
password.c_str(),
|
|
NULL,
|
|
gerror);
|
|
if (result) {
|
|
SE_LOG_DEBUG(NULL, "saved password in GNOME keyring using %s", key.toString().c_str());
|
|
break;
|
|
}
|
|
|
|
if (IsSharedSecretError(gerror) &&
|
|
i < 3) {
|
|
SE_LOG_DEBUG(NULL, "disconnecting secret service: %u/%d = %s", gerror->domain, gerror->code, gerror->message);
|
|
secret_service_disconnect();
|
|
} else {
|
|
gerror.throwError(SE_HERE, StringPrintf("%s: saving password '%s' in GNOME keyring",
|
|
key.description.c_str(),
|
|
key.toString().c_str()));
|
|
}
|
|
}
|
|
|
|
// handled
|
|
return true;
|
|
}
|
|
|
|
SE_END_CXX
|
|
|
|
#endif // USE_GNOME_KEYRING
|