gnome-control-center/panels/user-accounts/um-realm-manager.c
Ray Strode a416243050 user-accounts: fix crash for incompatible realmd version
The version property may not be available if realmd is
version 0.1 (which lacked the property) or if other
properties changed signatures (causing gdbus to fail to load
cache any property)

This commit fixes a crash where property is NULL in those cases.
2012-08-20 14:04:30 -04:00

828 lines
28 KiB
C

/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
*
* Copyright 2009-2012 Red Hat, Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
* Written by: Matthias Clasen <mclasen@redhat.com>
* Stef Walter <stefw@gnome.org>
*/
#include "config.h"
#include "um-realm-manager.h"
#include <krb5/krb5.h>
#include <glib.h>
#include <glib/gi18n.h>
#include <glib/gstdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
struct _UmRealmManager {
UmRealmObjectManagerClient parent;
UmRealmProvider *provider;
guint diagnostics_sig;
};
typedef struct {
UmRealmProviderProxyClass parent_class;
} UmRealmManagerClass;
enum {
REALM_ADDED,
NUM_SIGNALS,
};
static gint signals[NUM_SIGNALS] = { 0, };
G_DEFINE_TYPE (UmRealmManager, um_realm_manager, UM_REALM_TYPE_OBJECT_MANAGER_CLIENT);
GQuark
um_realm_error_get_quark (void)
{
static GQuark quark = 0;
if (quark == 0)
quark = g_quark_from_static_string ("um-realm-error");
return quark;
}
static gboolean
is_realm_with_kerberos_and_membership (gpointer object)
{
GDBusInterface *interface;
if (!G_IS_DBUS_OBJECT (object))
return FALSE;
interface = g_dbus_object_get_interface (object, "org.freedesktop.realmd.Kerberos");
if (interface == NULL)
return FALSE;
g_object_unref (interface);
interface = g_dbus_object_get_interface (object, "org.freedesktop.realmd.KerberosMembership");
if (interface == NULL)
return FALSE;
g_object_unref (interface);
return TRUE;
}
static void
on_interface_added (GDBusObjectManager *manager,
GDBusObject *object,
GDBusInterface *interface)
{
g_dbus_proxy_set_default_timeout (G_DBUS_PROXY (interface), G_MAXINT);
}
static void
on_object_added (GDBusObjectManager *manager,
GDBusObject *object,
gpointer user_data)
{
if (is_realm_with_kerberos_and_membership (object))
g_signal_emit (user_data, signals[REALM_ADDED], 0, object);
}
static void
um_realm_manager_init (UmRealmManager *self)
{
g_signal_connect (self, "object-added", G_CALLBACK (on_object_added), self);
g_signal_connect (self, "interface-added", G_CALLBACK (on_interface_added), self);
}
static void
um_realm_manager_dispose (GObject *obj)
{
UmRealmManager *self = UM_REALM_MANAGER (obj);
GDBusConnection *connection;
g_clear_object (&self->provider);
if (self->diagnostics_sig) {
connection = g_dbus_object_manager_client_get_connection (G_DBUS_OBJECT_MANAGER_CLIENT (self));
if (connection != NULL)
g_dbus_connection_signal_unsubscribe (connection, self->diagnostics_sig);
self->diagnostics_sig = 0;
}
G_OBJECT_CLASS (um_realm_manager_parent_class)->dispose (obj);
}
static void
um_realm_manager_class_init (UmRealmManagerClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->dispose = um_realm_manager_dispose;
signals[REALM_ADDED] = g_signal_new ("realm-added", UM_TYPE_REALM_MANAGER,
G_SIGNAL_RUN_FIRST, 0, NULL, NULL,
g_cclosure_marshal_generic,
G_TYPE_NONE, 1, UM_REALM_TYPE_OBJECT);
}
static void
on_realm_diagnostics (GDBusConnection *connection,
const gchar *sender_name,
const gchar *object_path,
const gchar *interface_name,
const gchar *signal_name,
GVariant *parameters,
gpointer user_data)
{
const gchar *message;
const gchar *unused;
if (g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(ss)"))) {
/* Data is already formatted appropriately for stderr */
g_variant_get (parameters, "(&s&s)", &message, &unused);
g_printerr ("%s", message);
}
}
static gboolean
number_at_least (const gchar *number,
guint minimum)
{
gchar *end;
if (strtol (number, &end, 10) < (long)minimum)
return FALSE;
if (!end || *end != '\0')
return FALSE;
return TRUE;
}
static gboolean
version_compare (const char *version,
guint req_major,
guint req_minor)
{
gboolean match = FALSE;
gchar **parts;
parts = g_strsplit (version, ".", 2);
if (parts[0] && parts[1]) {
match = number_at_least (parts[0], req_major) &&
number_at_least (parts[1], req_minor);
}
g_strfreev (parts);
return match;
}
void
um_realm_manager_new (GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
g_async_initable_new_async (UM_TYPE_REALM_MANAGER, G_PRIORITY_DEFAULT,
cancellable, callback, user_data,
"flags", G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_NONE,
"name", "org.freedesktop.realmd",
"bus-type", G_BUS_TYPE_SYSTEM,
"object-path", "/org/freedesktop/realmd",
"get-proxy-type-func", um_realm_object_manager_client_get_proxy_type,
NULL);
}
UmRealmManager *
um_realm_manager_new_finish (GAsyncResult *result,
GError **error)
{
UmRealmManager *self;
GDBusConnection *connection;
GObject *source_object;
const gchar *version;
GObject *ret;
guint sig;
source_object = g_async_result_get_source_object (result);
ret = g_async_initable_new_finish (G_ASYNC_INITABLE (source_object), result, error);
g_object_unref (source_object);
if (ret == NULL)
return NULL;
self = UM_REALM_MANAGER (ret);
connection = g_dbus_object_manager_client_get_connection (G_DBUS_OBJECT_MANAGER_CLIENT (self));
/*
* TODO: Remove this version checking. This is temporary code, so
* just use sync here. Shortly we'll be stabilizing the realmd
* interfaces.
*/
self->provider = um_realm_provider_proxy_new_sync (connection, G_DBUS_PROXY_FLAGS_NONE,
"org.freedesktop.realmd",
"/org/freedesktop/realmd",
NULL, error);
if (self->provider == NULL) {
g_object_unref (self);
return NULL;
}
version = um_realm_provider_get_version (self->provider);
if (version == NULL || !version_compare (version, 0, 7)) {
/* No need to bother translators with this temporary message */
g_set_error (error, UM_REALM_ERROR, UM_REALM_ERROR_GENERIC,
"realmd version should be at least 0.7");
g_object_unref (self);
return NULL;
}
sig = g_dbus_connection_signal_subscribe (connection,
"org.freedesktop.realmd",
"org.freedesktop.realmd.Service",
"Diagnostics",
NULL,
NULL,
G_DBUS_SIGNAL_FLAGS_NONE,
on_realm_diagnostics,
NULL,
NULL);
self->diagnostics_sig = sig;
return self;
}
typedef struct {
GDBusObjectManager *manager;
GCancellable *cancellable;
GList *realms;
} DiscoverClosure;
static void
discover_closure_free (gpointer data)
{
DiscoverClosure *discover = data;
g_object_unref (discover->manager);
g_clear_object (&discover->cancellable);
g_list_free_full (discover->realms, g_object_unref);
g_slice_free (DiscoverClosure, discover);
}
static void
on_provider_discover (GObject *source,
GAsyncResult *result,
gpointer user_data)
{
GSimpleAsyncResult *async = G_SIMPLE_ASYNC_RESULT (user_data);
DiscoverClosure *discover = g_simple_async_result_get_op_res_gpointer (async);
GDBusObject *object;
GError *error = NULL;
gchar **realms;
gint relevance;
gint i;
um_realm_provider_call_discover_finish (UM_REALM_PROVIDER (source), &relevance,
&realms, result, &error);
if (error == NULL) {
for (i = 0; realms[i]; i++) {
object = g_dbus_object_manager_get_object (discover->manager, realms[i]);
if (object == NULL)
g_warning ("Realm is not in object manager: %s", realms[i]);
else
discover->realms = g_list_prepend (discover->realms, object);
}
} else {
g_simple_async_result_take_error (async, error);
g_simple_async_result_complete (async);
}
g_object_unref (async);
}
void
um_realm_manager_discover (UmRealmManager *self,
const gchar *input,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
GSimpleAsyncResult *res;
DiscoverClosure *discover;
GVariant *options;
g_return_if_fail (UM_IS_REALM_MANAGER (self));
g_return_if_fail (input != NULL);
g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
res = g_simple_async_result_new (G_OBJECT (self), callback, user_data,
um_realm_manager_discover);
discover = g_slice_new0 (DiscoverClosure);
discover->manager = g_object_ref (self);
discover->cancellable = cancellable ? g_object_ref (cancellable) : NULL;
g_simple_async_result_set_op_res_gpointer (res, discover, discover_closure_free);
options = g_variant_new_array (G_VARIANT_TYPE ("{sv}"), NULL, 0);
um_realm_provider_call_discover (self->provider, input, options, cancellable,
on_provider_discover, g_object_ref (res));
g_object_unref (res);
}
GList *
um_realm_manager_discover_finish (UmRealmManager *self,
GAsyncResult *result,
GError **error)
{
GSimpleAsyncResult *async;
DiscoverClosure *discover;
GList *realms;
g_return_val_if_fail (UM_IS_REALM_MANAGER (self), NULL);
g_return_val_if_fail (g_simple_async_result_is_valid (result, G_OBJECT (self),
um_realm_manager_discover), NULL);
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
async = G_SIMPLE_ASYNC_RESULT (result);
if (g_simple_async_result_propagate_error (async, error))
return NULL;
discover = g_simple_async_result_get_op_res_gpointer (async);
if (!discover->realms) {
g_set_error (error, UM_REALM_ERROR, UM_REALM_ERROR_GENERIC,
_("No such domain or realm found"));
return NULL;
}
realms = g_list_reverse (discover->realms);
discover->realms = NULL;
return realms;
}
GList *
um_realm_manager_get_realms (UmRealmManager *self)
{
GList *objects;
GList *realms = NULL;
GList *l;
g_return_val_if_fail (UM_IS_REALM_MANAGER (self), NULL);
objects = g_dbus_object_manager_get_objects (G_DBUS_OBJECT_MANAGER (self));
for (l = objects; l != NULL; l = g_list_next (l)) {
if (is_realm_with_kerberos_and_membership (l->data))
realms = g_list_prepend (realms, g_object_ref (l->data));
}
g_list_free_full (objects, g_object_unref);
return realms;
}
static void
string_replace (GString *string,
const gchar *find,
const gchar *replace)
{
const gchar *at;
gssize pos;
at = strstr (string->str, find);
if (at != NULL) {
pos = at - string->str;
g_string_erase (string, pos, strlen (find));
g_string_insert (string, pos, replace);
}
}
gchar *
um_realm_calculate_login (UmRealmCommon *realm,
const gchar *username)
{
GString *string;
const gchar *const *formats;
gchar *login = NULL;
formats = um_realm_common_get_login_formats (realm);
if (formats[0] != NULL) {
string = g_string_new (formats[0]);
string_replace (string, "%U", username);
string_replace (string, "%D", um_realm_common_get_name (realm));
login = g_string_free (string, FALSE);
}
return login;
}
gboolean
um_realm_is_configured (UmRealmObject *realm)
{
UmRealmCommon *common;
const gchar *configured;
gboolean is;
common = um_realm_object_get_common (realm);
configured = um_realm_common_get_configured (common);
is = configured != NULL && !g_str_equal (configured, "");
g_object_unref (common);
return is;
}
static const gchar *
find_supported_credentials (UmRealmKerberosMembership *membership,
const gchar *owner)
{
const gchar *cred_owner;
const gchar *cred_type;
GVariant *supported;
GVariantIter iter;
supported = um_realm_kerberos_membership_get_supported_join_credentials (membership);
g_return_val_if_fail (supported != NULL, NULL);
g_variant_iter_init (&iter, supported);
while (g_variant_iter_loop (&iter, "(&s&s)", &cred_type, &cred_owner)) {
if (g_str_equal (owner, cred_owner)) {
if (g_str_equal (cred_type, "ccache") ||
g_str_equal (cred_type, "password")) {
return g_intern_string (cred_type);
}
}
}
return NULL;
}
static void
on_realm_join_complete (GObject *source,
GAsyncResult *result,
gpointer user_data)
{
GSimpleAsyncResult *async = G_SIMPLE_ASYNC_RESULT (user_data);
g_simple_async_result_set_op_res_gpointer (async, g_object_ref (result), g_object_unref);
g_simple_async_result_complete_in_idle (async);
g_object_unref (async);
}
static gboolean
realm_join_as_owner (UmRealmObject *realm,
const gchar *owner,
const gchar *login,
const gchar *password,
GBytes *credentials,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
UmRealmKerberosMembership *membership;
GSimpleAsyncResult *async;
GVariant *contents;
GVariant *options;
GVariant *creds;
const gchar *type;
membership = um_realm_object_get_kerberos_membership (realm);
g_return_val_if_fail (membership != NULL, FALSE);
type = find_supported_credentials (membership, owner);
if (type == NULL) {
g_object_unref (membership);
return FALSE;
}
async = g_simple_async_result_new (G_OBJECT (realm), callback, user_data,
realm_join_as_owner);
if (g_str_equal (type, "ccache")) {
contents = g_variant_new_from_data (G_VARIANT_TYPE ("ay"),
g_bytes_get_data (credentials, NULL),
g_bytes_get_size (credentials),
TRUE, (GDestroyNotify)g_bytes_unref, credentials);
} else if (g_str_equal (type, "password")) {
contents = g_variant_new ("(ss)", login, password);
} else {
g_assert_not_reached ();
}
creds = g_variant_new ("(ssv)", type, owner, contents);
options = g_variant_new_array (G_VARIANT_TYPE ("{sv}"), NULL, 0);
um_realm_kerberos_membership_call_join (membership, creds, options,
cancellable, on_realm_join_complete,
g_object_ref (async));
g_object_unref (async);
g_object_unref (membership);
return TRUE;
}
gboolean
um_realm_join_as_user (UmRealmObject *realm,
const gchar *login,
const gchar *password,
GBytes *credentials,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
g_return_val_if_fail (UM_REALM_IS_OBJECT (realm), FALSE);
g_return_val_if_fail (credentials != NULL, FALSE);
g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), FALSE);
g_return_val_if_fail (login != NULL, FALSE);
g_return_val_if_fail (password != NULL, FALSE);
g_return_val_if_fail (credentials != NULL, FALSE);
return realm_join_as_owner (realm, "user", login, password,
credentials, cancellable, callback, user_data);
}
gboolean
um_realm_join_as_admin (UmRealmObject *realm,
const gchar *login,
const gchar *password,
GBytes *credentials,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
g_return_val_if_fail (UM_REALM_IS_OBJECT (realm), FALSE);
g_return_val_if_fail (credentials != NULL, FALSE);
g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), FALSE);
g_return_val_if_fail (login != NULL, FALSE);
g_return_val_if_fail (password != NULL, FALSE);
g_return_val_if_fail (credentials != NULL, FALSE);
return realm_join_as_owner (realm, "administrator", login, password, credentials,
cancellable, callback, user_data);
}
gboolean
um_realm_join_finish (UmRealmObject *realm,
GAsyncResult *result,
GError **error)
{
UmRealmKerberosMembership *membership;
GError *call_error = NULL;
gchar *dbus_error;
GAsyncResult *async;
g_return_val_if_fail (UM_REALM_IS_OBJECT (realm), FALSE);
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
membership = um_realm_object_get_kerberos_membership (realm);
g_return_val_if_fail (membership != NULL, FALSE);
async = g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (result));
um_realm_kerberos_membership_call_join_finish (membership, async, &call_error);
g_object_unref (membership);
if (call_error == NULL)
return TRUE;
dbus_error = g_dbus_error_get_remote_error (call_error);
if (dbus_error == NULL) {
g_propagate_error (error, call_error);
return FALSE;
}
g_dbus_error_strip_remote_error (call_error);
if (g_str_equal (dbus_error, "org.freedesktop.realmd.Error.AuthenticationFailed")) {
g_set_error (error, UM_REALM_ERROR, UM_REALM_ERROR_BAD_LOGIN,
"%s", call_error->message);
g_error_free (call_error);
} else {
g_propagate_error (error, call_error);
}
g_free (dbus_error);
return FALSE;
}
typedef struct {
gchar *domain;
gchar *realm;
gchar *user;
gchar *password;
GBytes *credentials;
} LoginClosure;
static void
login_closure_free (gpointer data)
{
LoginClosure *login = data;
g_free (login->domain);
g_free (login->realm);
g_free (login->user);
g_free (login->password);
g_bytes_unref (login->credentials);
g_slice_free (LoginClosure, login);
}
static krb5_error_code
login_perform_kinit (krb5_context k5,
const gchar *realm,
const gchar *login,
const gchar *password,
const gchar *filename)
{
krb5_get_init_creds_opt *opts;
krb5_error_code code;
krb5_principal principal;
krb5_ccache ccache;
krb5_creds creds;
gchar *name;
name = g_strdup_printf ("%s@%s", login, realm);
code = krb5_parse_name (k5, name, &principal);
g_free (name);
if (code != 0)
return code;
if (filename == NULL)
code = krb5_cc_default (k5, &ccache);
else
code = krb5_cc_resolve (k5, filename, &ccache);
if (code != 0) {
krb5_free_principal (k5, principal);
return code;
}
code = krb5_get_init_creds_opt_alloc (k5, &opts);
g_return_val_if_fail (code == 0, code);
code = krb5_get_init_creds_opt_set_out_ccache (k5, opts, ccache);
g_return_val_if_fail (code == 0, code);
code = krb5_get_init_creds_password (k5, &creds, principal,
(char *)password,
NULL, 0, 0, NULL, opts);
krb5_get_init_creds_opt_free (k5, opts);
krb5_cc_close (k5, ccache);
krb5_free_principal (k5, principal);
if (code == 0)
krb5_free_cred_contents (k5, &creds);
return code;
}
static void
kinit_thread_func (GSimpleAsyncResult *async,
GObject *object,
GCancellable *cancellable)
{
LoginClosure *login = g_simple_async_result_get_op_res_gpointer (async);
krb5_context k5 = NULL;
krb5_error_code code;
GError *error = NULL;
gchar *filename = NULL;
gchar *contents;
gsize length;
gint temp_fd;
filename = g_build_filename (g_get_user_runtime_dir (),
"um-krb5-creds.XXXXXX", NULL);
temp_fd = g_mkstemp_full (filename, O_RDWR, S_IRUSR | S_IWUSR);
if (temp_fd == -1) {
g_warning ("Couldn't create credential cache file: %s: %s",
filename, g_strerror (errno));
g_free (filename);
filename = NULL;
} else {
close (temp_fd);
}
code = krb5_init_context (&k5);
if (code == 0) {
code = login_perform_kinit (k5, login->realm, login->user,
login->password, filename);
}
switch (code) {
case 0:
if (filename != NULL) {
g_file_get_contents (filename, &contents, &length, &error);
if (error == NULL) {
login->credentials = g_bytes_new_take (contents, length);
} else {
g_warning ("Couldn't read credential cache: %s", error->message);
g_error_free (error);
}
}
break;
case KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN:
case KRB5KDC_ERR_CLIENT_REVOKED:
case KRB5KDC_ERR_KEY_EXP:
case KRB5KDC_ERR_POLICY:
case KRB5KDC_ERR_ETYPE_NOSUPP:
g_simple_async_result_set_error (async, UM_REALM_ERROR, UM_REALM_ERROR_BAD_LOGIN,
_("Cannot log in as %s at the %s domain"),
login->user, login->domain);
break;
case KRB5KDC_ERR_PREAUTH_FAILED:
g_simple_async_result_set_error (async, UM_REALM_ERROR, UM_REALM_ERROR_BAD_PASSWORD,
_("Invalid password, please try again"));
break;
default:
g_simple_async_result_set_error (async, UM_REALM_ERROR, UM_REALM_ERROR_GENERIC,
_("Couldn't connect to the %s domain: %s"),
login->domain, krb5_get_error_message (k5, code));
break;
}
if (filename) {
g_unlink (filename);
g_free (filename);
}
if (k5)
krb5_free_context (k5);
}
void
um_realm_login (UmRealmObject *realm,
const gchar *user,
const gchar *password,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
GSimpleAsyncResult *async;
LoginClosure *login;
UmRealmKerberos *kerberos;
g_return_if_fail (UM_REALM_IS_OBJECT (realm));
g_return_if_fail (user != NULL);
g_return_if_fail (password != NULL);
g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
kerberos = um_realm_object_get_kerberos (realm);
g_return_if_fail (kerberos != NULL);
async = g_simple_async_result_new (NULL, callback, user_data,
um_realm_login);
login = g_slice_new0 (LoginClosure);
login->domain = g_strdup (um_realm_kerberos_get_domain_name (kerberos));
login->realm = g_strdup (um_realm_kerberos_get_realm_name (kerberos));
login->user = g_strdup (user);
login->password = g_strdup (password);
g_simple_async_result_set_op_res_gpointer (async, login, login_closure_free);
g_simple_async_result_set_handle_cancellation (async, TRUE);
g_simple_async_result_run_in_thread (async, kinit_thread_func,
G_PRIORITY_DEFAULT, cancellable);
g_object_unref (async);
g_object_unref (kerberos);
}
gboolean
um_realm_login_finish (GAsyncResult *result,
GBytes **credentials,
GError **error)
{
GSimpleAsyncResult *async;
LoginClosure *login;
g_return_val_if_fail (g_simple_async_result_is_valid (result, NULL,
um_realm_login), FALSE);
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
async = G_SIMPLE_ASYNC_RESULT (result);
if (g_simple_async_result_propagate_error (async, error))
return FALSE;
login = g_simple_async_result_get_op_res_gpointer (async);
if (credentials) {
if (login->credentials)
*credentials = g_bytes_ref (login->credentials);
else
*credentials = NULL;
}
return TRUE;
}