From 108067770cd334bae30dc3b6953a5ffbb6eba88e Mon Sep 17 00:00:00 2001 From: Stef Walter Date: Thu, 22 Nov 2012 17:37:47 +0100 Subject: [PATCH] Implement support for using a ccache in the realm command https://bugs.freedesktop.org/show_bug.cgi?id=56022 --- service/realm-adcli-enroll.c | 10 +- service/realm-kerberos-membership.h | 4 +- service/realm-kerberos.c | 85 +++++-- service/realm-samba-enroll.c | 186 +++++++++----- service/realm-samba-enroll.h | 58 +++-- service/realm-samba.c | 18 +- service/realm-sssd-ad.c | 88 ++++++- tools/realm-client.c | 480 +++++++++++++++++++++++++++++++----- tools/realm-client.h | 1 + tools/realm-join.c | 5 +- tools/realm.c | 154 ------------ 11 files changed, 761 insertions(+), 328 deletions(-) diff --git a/service/realm-adcli-enroll.c b/service/realm-adcli-enroll.c index d023679..11c3779 100644 --- a/service/realm-adcli-enroll.c +++ b/service/realm-adcli-enroll.c @@ -124,13 +124,19 @@ realm_adcli_enroll_join_ccache_async (const gchar *realm, GAsyncReadyCallback callback, gpointer user_data) { + char *ccache_arg; + + /* Since this is an optional adcli argument it requires the equals */ + ccache_arg = g_strdup_printf ("--login-ccache=%s", ccache_file); + begin_join_process (NULL, invocation, callback, user_data, "--domain", realm, "--login-type", "user", - "--login-ccache", ccache_file, - "--no-password", + ccache_arg, "--no-password", computer_ou ? "--computer-ou": NULL, computer_ou, NULL); + + free (ccache_arg); } void diff --git a/service/realm-kerberos-membership.h b/service/realm-kerberos-membership.h index 59eba94..427d99a 100644 --- a/service/realm-kerberos-membership.h +++ b/service/realm-kerberos-membership.h @@ -67,7 +67,7 @@ struct _RealmKerberosMembershipIface { gpointer user_data); void (* enroll_ccache_async) (RealmKerberosMembership *realm, - GBytes *ccache, + const gchar *ccache_file, RealmKerberosFlags flags, GVariant *options, GDBusMethodInvocation *invocation, @@ -103,7 +103,7 @@ struct _RealmKerberosMembershipIface { gpointer user_data); void (* unenroll_ccache_async) (RealmKerberosMembership *realm, - GBytes *ccache, + const gchar *ccache_file, RealmKerberosFlags flags, GVariant *options, GDBusMethodInvocation *invocation, diff --git a/service/realm-kerberos.c b/service/realm-kerberos.c index f38cc59..0023d84 100644 --- a/service/realm-kerberos.c +++ b/service/realm-kerberos.c @@ -79,13 +79,14 @@ G_DEFINE_TYPE (RealmKerberos, realm_kerberos, G_TYPE_DBUS_OBJECT_SKELETON); typedef struct { RealmKerberos *self; GDBusMethodInvocation *invocation; + gchar *ccache_file; } MethodClosure; static MethodClosure * method_closure_new (RealmKerberos *self, GDBusMethodInvocation *invocation) { - MethodClosure *method = g_slice_new (MethodClosure); + MethodClosure *method = g_slice_new0 (MethodClosure); method->self = g_object_ref (self); method->invocation = g_object_ref (invocation); return method; @@ -96,6 +97,8 @@ method_closure_free (MethodClosure *closure) { g_object_unref (closure->self); g_object_unref (closure->invocation); + if (closure->ccache_file) + realm_keberos_ccache_delete_and_free (closure->ccache_file); g_slice_free (MethodClosure, closure); } @@ -226,6 +229,57 @@ on_unenroll_complete (GObject *source, method_closure_free (closure); } +static gchar * +write_ccache_file (GVariant *ccache, + GError **error) +{ + const gchar *directory; + gchar *filename; + const guchar *data; + gsize length; + gint fd; + int res; + + data = g_variant_get_fixed_array (ccache, &length, 1); + g_return_val_if_fail (length > 0, NULL); + + directory = g_get_tmp_dir (); + filename = g_build_filename (directory, "realm-ad-kerberos-XXXXXX", NULL); + + fd = g_mkstemp_full (filename, O_WRONLY, 0600); + if (fd < 0) { + g_warning ("couldn't open temporary file in %s directory for kerberos cache: %s", + directory, g_strerror (errno)); + g_set_error (error, REALM_ERROR, REALM_ERROR_INTERNAL, + "Problem writing out the kerberos cache data"); + g_free (filename); + return NULL; + } + + while (length > 0) { + res = write (fd, data, length); + if (res <= 0) { + if (errno == EAGAIN && errno == EINTR) + continue; + g_warning ("couldn't write kerberos cache to file %s: %s", + directory, g_strerror (errno)); + g_set_error (error, REALM_ERROR, REALM_ERROR_INTERNAL, + "Problem writing out the kerberos cache data"); + break; + } else { + length -= res; + data += res; + } + } + + if (length != 0) { + g_free (filename); + return NULL; + } + + return filename; +} + static void enroll_or_unenroll_with_ccache (RealmKerberos *self, RealmKerberosFlags flags, @@ -235,9 +289,9 @@ enroll_or_unenroll_with_ccache (RealmKerberos *self, gboolean enroll) { RealmKerberosMembershipIface *iface = REALM_KERBEROS_MEMBERSHIP_GET_IFACE (self); - const guchar *data; - GBytes *bytes; - gsize length; + MethodClosure *method; + gchar *ccache_file; + GError *error; if ((enroll && iface && iface->enroll_ccache_async == NULL) || (!enroll && iface && iface->unenroll_ccache_async == NULL)) { @@ -254,22 +308,25 @@ enroll_or_unenroll_with_ccache (RealmKerberos *self, return; } - data = g_variant_get_fixed_array (ccache, &length, 1); - bytes = g_bytes_new_with_free_func (data, length, - (GDestroyNotify)g_variant_unref, - g_variant_ref (ccache)); + ccache_file = write_ccache_file (ccache, &error); + if (ccache_file == NULL) { + enroll_method_reply (invocation, error); + g_error_free (error); + return; + } + + method = method_closure_new (self, invocation); + method->ccache_file = ccache_file; if (enroll) { g_return_if_fail (iface->enroll_finish != NULL); - (iface->enroll_ccache_async) (REALM_KERBEROS_MEMBERSHIP (self), bytes, flags, options, invocation, - on_enroll_complete, method_closure_new (self, invocation)); + (iface->enroll_ccache_async) (REALM_KERBEROS_MEMBERSHIP (self), ccache_file, flags, + options, invocation, on_enroll_complete, method); } else { g_return_if_fail (iface->unenroll_finish != NULL); - (iface->unenroll_ccache_async) (REALM_KERBEROS_MEMBERSHIP (self), bytes, flags, options, invocation, - on_unenroll_complete, method_closure_new (self, invocation)); + (iface->unenroll_ccache_async) (REALM_KERBEROS_MEMBERSHIP (self), ccache_file, flags, + options, invocation, on_unenroll_complete, method); } - - g_bytes_unref (bytes); } static void diff --git a/service/realm-samba-enroll.c b/service/realm-samba-enroll.c index abd5117..f4a7c81 100644 --- a/service/realm-samba-enroll.c +++ b/service/realm-samba-enroll.c @@ -43,6 +43,7 @@ typedef struct { GBytes *password_input; RealmIniConfig *config; gchar *custom_smb_conf; + gchar *envvar; } JoinClosure; static void @@ -52,8 +53,8 @@ join_closure_free (gpointer data) g_bytes_unref (join->password_input); g_free (join->user_name); - g_free (join->create_computer_arg); g_free (join->realm); + g_free (join->envvar); g_clear_object (&join->invocation); g_clear_object (&join->config); @@ -66,9 +67,8 @@ join_closure_free (gpointer data) } static JoinClosure * -join_closure_init (const gchar *realm, - const gchar *user_name, - GBytes *password, +join_closure_init (GSimpleAsyncResult *async, + const gchar *realm, GDBusMethodInvocation *invocation) { JoinClosure *join; @@ -79,10 +79,7 @@ join_closure_init (const gchar *realm, join->realm = g_strdup (realm); join->invocation = invocation ? g_object_ref (invocation) : NULL; - if (password) - join->password_input = realm_command_build_password_line (password); - - join->user_name = g_strdup (user_name); + g_simple_async_result_set_op_res_gpointer (async, join, join_closure_free); join->config = realm_ini_config_new (REALM_INI_NO_WATCH | REALM_INI_PRIVATE); realm_ini_config_set (join->config, REALM_SAMBA_CONFIG_GLOBAL, "security", "ads"); @@ -121,7 +118,7 @@ begin_net_process (JoinClosure *join, gpointer user_data, ...) { - gchar *environ[] = { "LANG=C", NULL }; + char *env[] = { "LANG=C", join->envvar, NULL }; GPtrArray *args; gchar *arg; va_list va; @@ -142,7 +139,7 @@ begin_net_process (JoinClosure *join, } while (arg != NULL); va_end (va); - realm_command_runv_async ((gchar **)args->pdata, environ, input, + realm_command_runv_async ((gchar **)args->pdata, env, input, join->invocation, callback, user_data); g_ptr_array_free (args, TRUE); @@ -221,13 +218,21 @@ on_join_do_keytab (GObject *source, if (output) g_string_free (output, TRUE); - if (error == NULL) { + if (error != NULL) { + g_simple_async_result_take_error (res, error); + g_simple_async_result_complete (res); + + /* Do keytab with a user name */ + } else if (join->user_name != NULL) { begin_net_process (join, join->password_input, on_keytab_do_finish, g_object_ref (res), "-U", join->user_name, "ads", "keytab", "create", NULL); + + /* Do keytab with a ccache file */ } else { - g_simple_async_result_take_error (res, error); - g_simple_async_result_complete (res); + begin_net_process (join, NULL, + on_keytab_do_finish, g_object_ref (res), + "-k", "ads", "keytab", "create", NULL); } g_object_unref (res); @@ -268,15 +273,23 @@ begin_config_and_join (JoinClosure *join, /* Write out the config file for various changes */ realm_ini_config_write_file (join->config, NULL, &error); - if (error == NULL) { + if (error != NULL) { + g_simple_async_result_take_error (async, error); + g_simple_async_result_complete (async); + + /* Do join with a user name */ + } else if (join->user_name) { begin_net_process (join, join->password_input, on_join_do_keytab, g_object_ref (async), "-U", join->user_name, "ads", "join", join->realm, join->create_computer_arg, NULL); + /* Do join with a ccache */ } else { - g_simple_async_result_take_error (async, error); - g_simple_async_result_complete (async); + begin_net_process (join, NULL, + on_join_do_keytab, g_object_ref (async), + "-k", "ads", "join", join->realm, + join->create_computer_arg, NULL); } } @@ -387,31 +400,15 @@ on_discover_do_lookup (GObject *source, g_object_unref (async); } -void -realm_samba_enroll_join_async (const gchar *realm, - const gchar *user_name, - GBytes *password, - const gchar *computer_ou, - GHashTable *discovery, - GDBusMethodInvocation *invocation, - GAsyncReadyCallback callback, - gpointer user_data) +static void +begin_join (GSimpleAsyncResult *async, + JoinClosure *join, + const gchar *realm, + const gchar *computer_ou, + GHashTable *discovery) { - GSimpleAsyncResult *res; - JoinClosure *join; - GError *error = NULL; gchar *strange_ou; - - g_return_if_fail (realm != NULL); - g_return_if_fail (user_name != NULL); - g_return_if_fail (password != NULL); - - res = g_simple_async_result_new (NULL, callback, user_data, - realm_samba_enroll_join_async); - - join = join_closure_init (realm, user_name, password, invocation); - - g_simple_async_result_set_op_res_gpointer (res, join, join_closure_free); + GError *error = NULL; if (computer_ou != NULL) { strange_ou = realm_samba_util_build_strange_ou (computer_ou, realm); @@ -426,20 +423,75 @@ realm_samba_enroll_join_async (const gchar *realm, } if (error != NULL) { - g_simple_async_result_take_error (res, error); - g_simple_async_result_complete_in_idle (res); + g_simple_async_result_take_error (async, error); + g_simple_async_result_complete_in_idle (async); } else if (discovery) { - begin_net_lookup (join, res, discovery); + begin_net_lookup (join, async, discovery); } else { realm_kerberos_discover_async (join->realm, join->invocation, - on_discover_do_lookup, g_object_ref (res)); + on_discover_do_lookup, g_object_ref (async)); } +} - g_object_unref (res); +void +realm_samba_enroll_join_password_async (const gchar *realm, + const gchar *user_name, + GBytes *password, + const gchar *computer_ou, + GHashTable *discovery, + GDBusMethodInvocation *invocation, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GSimpleAsyncResult *async; + JoinClosure *join; + + g_return_if_fail (realm != NULL); + g_return_if_fail (user_name != NULL); + g_return_if_fail (password != NULL); + + async = g_simple_async_result_new (NULL, callback, user_data, + realm_samba_enroll_join_finish); + + join = join_closure_init (async, realm, invocation); + + join->password_input = realm_command_build_password_line (password); + join->user_name = g_strdup (user_name); + + begin_join (async, join, realm, computer_ou, discovery); + + g_object_unref (async); } +void +realm_samba_enroll_join_ccache_async (const gchar *realm, + const gchar *ccache_file, + const gchar *computer_ou, + GHashTable *discovery, + GDBusMethodInvocation *invocation, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GSimpleAsyncResult *async; + JoinClosure *join; + + g_return_if_fail (realm != NULL); + g_return_if_fail (ccache_file != NULL); + + async = g_simple_async_result_new (NULL, callback, user_data, + realm_samba_enroll_join_finish); + + join = join_closure_init (async, realm, invocation); + join->envvar = g_strdup_printf ("KRB5CCNAME=%s", ccache_file); + + begin_join (async, join, realm, computer_ou, discovery); + + g_object_unref (async); +} + + gboolean realm_samba_enroll_join_finish (GAsyncResult *result, GHashTable **settings, @@ -448,7 +500,7 @@ realm_samba_enroll_join_finish (GAsyncResult *result, JoinClosure *join; g_return_val_if_fail (g_simple_async_result_is_valid (result, NULL, - realm_samba_enroll_join_async), FALSE); + realm_samba_enroll_join_finish), FALSE); if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result), error)) return FALSE; @@ -484,21 +536,22 @@ on_leave_complete (GObject *source, } void -realm_samba_enroll_leave_async (const gchar *realm, - const gchar *user_name, - GBytes *password, - GDBusMethodInvocation *invocation, - GAsyncReadyCallback callback, - gpointer user_data) +realm_samba_enroll_leave_password_async (const gchar *realm, + const gchar *user_name, + GBytes *password, + GDBusMethodInvocation *invocation, + GAsyncReadyCallback callback, + gpointer user_data) { GSimpleAsyncResult *async; JoinClosure *join; async = g_simple_async_result_new (NULL, callback, user_data, - realm_samba_enroll_leave_async); + realm_samba_enroll_leave_finish); - join = join_closure_init (realm, user_name, password, invocation); - g_simple_async_result_set_op_res_gpointer (async, join, join_closure_free); + join = join_closure_init (async, realm, invocation); + join->password_input = realm_command_build_password_line (password); + join->user_name = g_strdup (user_name); begin_net_process (join, join->password_input, on_leave_complete, g_object_ref (async), @@ -507,12 +560,35 @@ realm_samba_enroll_leave_async (const gchar *realm, g_object_unref (async); } +void +realm_samba_enroll_leave_ccache_async (const gchar *realm, + const gchar *ccache_file, + GDBusMethodInvocation *invocation, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GSimpleAsyncResult *async; + JoinClosure *join; + + async = g_simple_async_result_new (NULL, callback, user_data, + realm_samba_enroll_leave_finish); + + join = join_closure_init (async, realm, invocation); + join->envvar = g_strdup_printf ("KRB5CCNAME=%s", ccache_file); + + begin_net_process (join, join->password_input, + on_leave_complete, g_object_ref (async), + "-k", "ads", "leave", NULL); + + g_object_unref (async); +} + gboolean realm_samba_enroll_leave_finish (GAsyncResult *result, GError **error) { g_return_val_if_fail (g_simple_async_result_is_valid (result, NULL, - realm_samba_enroll_leave_async), FALSE); + realm_samba_enroll_leave_finish), FALSE); if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result), error)) return FALSE; diff --git a/service/realm-samba-enroll.h b/service/realm-samba-enroll.h index 3b44d8f..4d52fd6 100644 --- a/service/realm-samba-enroll.h +++ b/service/realm-samba-enroll.h @@ -23,28 +23,42 @@ G_BEGIN_DECLS -void realm_samba_enroll_join_async (const gchar *realm, - const gchar *user_name, - GBytes *password, - const gchar *computer_ou, - GHashTable *discovery, - GDBusMethodInvocation *invocation, - GAsyncReadyCallback callback, - gpointer user_data); - -gboolean realm_samba_enroll_join_finish (GAsyncResult *result, - GHashTable **settings, - GError **error); - -void realm_samba_enroll_leave_async (const gchar *realm, - const gchar *user_name, - GBytes *password, - GDBusMethodInvocation *invocation, - GAsyncReadyCallback callback, - gpointer user_data); - -gboolean realm_samba_enroll_leave_finish (GAsyncResult *result, - GError **error); +void realm_samba_enroll_join_password_async (const gchar *realm, + const gchar *user_name, + GBytes *password, + const gchar *computer_ou, + GHashTable *discovery, + GDBusMethodInvocation *invocation, + GAsyncReadyCallback callback, + gpointer user_data); + +void realm_samba_enroll_join_ccache_async (const gchar *realm, + const gchar *ccache_file, + const gchar *computer_ou, + GHashTable *discovery, + GDBusMethodInvocation *invocation, + GAsyncReadyCallback callback, + gpointer user_data); + +gboolean realm_samba_enroll_join_finish (GAsyncResult *result, + GHashTable **settings, + GError **error); + +void realm_samba_enroll_leave_password_async (const gchar *realm, + const gchar *user_name, + GBytes *password, + GDBusMethodInvocation *invocation, + GAsyncReadyCallback callback, + gpointer user_data); + +void realm_samba_enroll_leave_ccache_async (const gchar *realm, + const gchar *ccache_file, + GDBusMethodInvocation *invocation, + GAsyncReadyCallback callback, + gpointer user_data); + +gboolean realm_samba_enroll_leave_finish (GAsyncResult *result, + GError **error); G_END_DECLS diff --git a/service/realm-samba.c b/service/realm-samba.c index c591052..1a21856 100644 --- a/service/realm-samba.c +++ b/service/realm-samba.c @@ -154,7 +154,6 @@ lookup_login_prefix (RealmSamba *self) typedef struct { GDBusMethodInvocation *invocation; - gchar *ccache_file; gchar *computer_ou; gchar *realm_name; gchar *user_name; @@ -170,8 +169,6 @@ enroll_closure_free (gpointer data) g_free (enroll->user_name); g_bytes_unref (enroll->password); g_object_unref (enroll->invocation); - if (enroll->ccache_file) - realm_keberos_ccache_delete_and_free (enroll->ccache_file); g_slice_free (EnrollClosure, enroll); } @@ -248,9 +245,9 @@ on_install_do_join (GObject *source, realm_packages_install_finish (result, &error); if (error == NULL) { - realm_samba_enroll_join_async (enroll->realm_name, enroll->user_name, enroll->password, - enroll->computer_ou, realm_kerberos_get_discovery (kerberos), - enroll->invocation, on_join_do_winbind, g_object_ref (res)); + realm_samba_enroll_join_password_async (enroll->realm_name, enroll->user_name, enroll->password, + enroll->computer_ou, realm_kerberos_get_discovery (kerberos), + enroll->invocation, on_join_do_winbind, g_object_ref (res)); } else { g_simple_async_result_take_error (res, error); @@ -334,7 +331,6 @@ realm_samba_enroll_async (RealmKerberosMembership *membership, typedef struct { GDBusMethodInvocation *invocation; gchar *realm_name; - gchar *ccache_file; } LeaveClosure; static void @@ -342,8 +338,6 @@ leave_closure_free (gpointer data) { LeaveClosure *leave = data; g_free (leave->realm_name); - if (leave->ccache_file) - realm_keberos_ccache_delete_and_free (leave->ccache_file); g_object_unref (leave->invocation); g_slice_free (LeaveClosure, leave); } @@ -474,9 +468,9 @@ realm_samba_leave_password_async (RealmKerberosMembership *membership, return; leave = g_simple_async_result_get_op_res_gpointer (async); - realm_samba_enroll_leave_async (leave->realm_name, name, password, - leave->invocation, on_leave_do_deconfigure, - g_object_ref (async)); + realm_samba_enroll_leave_password_async (leave->realm_name, name, password, + leave->invocation, on_leave_do_deconfigure, + g_object_ref (async)); g_object_unref (async); } diff --git a/service/realm-sssd-ad.c b/service/realm-sssd-ad.c index 1fe1281..f8efe13 100644 --- a/service/realm-sssd-ad.c +++ b/service/realm-sssd-ad.c @@ -92,12 +92,14 @@ realm_sssd_ad_constructed (GObject *obj) /* * Each line is a combination of owner and what kind of credentials are supported, - * same for enroll/leave. We can't accept a ccache, because samba3 needs - * to have credentials limited to RC4. + * same for enroll/leave. We can't accept a ccache with samba because of certain + * corner cases. However we do accept ccache for an admin user, and then we use + * adcli with that ccache. */ supported = realm_kerberos_membership_build_supported ( REALM_KERBEROS_CREDENTIAL_PASSWORD, REALM_KERBEROS_OWNER_ADMIN, REALM_KERBEROS_CREDENTIAL_PASSWORD, REALM_KERBEROS_OWNER_USER, + REALM_KERBEROS_CREDENTIAL_CCACHE, REALM_KERBEROS_OWNER_ADMIN, REALM_KERBEROS_CREDENTIAL_AUTOMATIC, REALM_KERBEROS_OWNER_NONE, REALM_KERBEROS_CREDENTIAL_SECRET, REALM_KERBEROS_OWNER_NONE, 0); @@ -106,7 +108,7 @@ realm_sssd_ad_constructed (GObject *obj) /* For leave, we don't support one-time-password (ie: secret/none) */ supported = realm_kerberos_membership_build_supported ( REALM_KERBEROS_CREDENTIAL_PASSWORD, REALM_KERBEROS_OWNER_ADMIN, - REALM_KERBEROS_CREDENTIAL_PASSWORD, REALM_KERBEROS_OWNER_USER, + REALM_KERBEROS_CREDENTIAL_CCACHE, REALM_KERBEROS_OWNER_ADMIN, REALM_KERBEROS_CREDENTIAL_AUTOMATIC, REALM_KERBEROS_OWNER_NONE, 0); realm_kerberos_set_supported_leave_creds (kerberos, supported); @@ -339,9 +341,11 @@ on_install_do_join (GObject *source, } else { g_assert (join->user_name != NULL); g_assert (join->user_password != NULL); - realm_samba_enroll_join_async (join->realm_name, join->user_name, join->user_password, - join->computer_ou, realm_kerberos_get_discovery (kerberos), - join->invocation, on_join_do_sssd, g_object_ref (async)); + realm_samba_enroll_join_password_async (join->realm_name, join->user_name, + join->user_password, join->computer_ou, + realm_kerberos_get_discovery (kerberos), + join->invocation, on_join_do_sssd, + g_object_ref (async)); } } else { @@ -426,6 +430,15 @@ parse_join_options (JoinClosure *join, } /* + * If we are enrolling with a ccache, then prefer to use adcli over samba. + * There have been some strange corner case problems when using samba with + * a ccache. + */ + } else if (cred_type == REALM_KERBEROS_CREDENTIAL_CCACHE) { + if (!software) + software = REALM_DBUS_IDENTIFIER_ADCLI; + + /* * For other supported enrolling credentials, we support either adcli or * samba. But since adcli is pretty immature at this point, we use samba * by default. @@ -586,6 +599,33 @@ realm_sssd_ad_join_password_async (RealmKerberosMembership *membership, } } +static void +realm_sssd_ad_join_ccache_async (RealmKerberosMembership *membership, + const gchar *ccache_file, + RealmKerberosFlags flags, + GVariant *options, + GDBusMethodInvocation *invocation, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GSimpleAsyncResult *async; + JoinClosure *join; + + async = prepare_join_async_result (membership, REALM_KERBEROS_CREDENTIAL_CCACHE, + flags, options, invocation, callback, user_data); + + if (async) { + join = g_simple_async_result_get_op_res_gpointer (async); + + join->ccache_file = g_strdup (ccache_file); + realm_packages_install_async (join->packages, join->invocation, + on_install_do_join, g_object_ref (async)); + + g_object_unref (async); + } + +} + typedef struct { GDBusMethodInvocation *invocation; gchar *realm_name; @@ -672,9 +712,37 @@ realm_sssd_ad_leave_password_async (RealmKerberosMembership *membership, leave->invocation = g_object_ref (invocation); g_simple_async_result_set_op_res_gpointer (async, leave, leave_closure_free); - realm_samba_enroll_leave_async (leave->realm_name, user_name, password, - leave->invocation, on_leave_do_deconfigure, - g_object_ref (async)); + realm_samba_enroll_leave_password_async (leave->realm_name, user_name, password, + leave->invocation, on_leave_do_deconfigure, + g_object_ref (async)); + g_object_unref (async); +} + +static void +realm_sssd_ad_leave_ccache_async (RealmKerberosMembership *membership, + const gchar *ccache_file, + RealmKerberosFlags flags, + GVariant *options, + GDBusMethodInvocation *invocation, + GAsyncReadyCallback callback, + gpointer user_data) +{ + RealmSssdAd *self = REALM_SSSD_AD (membership); + GSimpleAsyncResult *async; + LeaveClosure *leave; + + async = setup_leave (self, options, invocation, callback, user_data); + if (async == NULL) + return; + + leave = g_slice_new0 (LeaveClosure); + leave->realm_name = g_strdup (realm_kerberos_get_realm_name (REALM_KERBEROS (self))); + leave->invocation = g_object_ref (invocation); + g_simple_async_result_set_op_res_gpointer (async, leave, leave_closure_free); + + realm_samba_enroll_leave_ccache_async (leave->realm_name, ccache_file, + leave->invocation, on_leave_do_deconfigure, + g_object_ref (async)); g_object_unref (async); } @@ -726,8 +794,10 @@ realm_sssd_ad_kerberos_membership_iface (RealmKerberosMembershipIface *iface) iface->enroll_automatic_async = realm_sssd_ad_join_automatic_async; iface->enroll_password_async = realm_sssd_ad_join_password_async; iface->enroll_secret_async = realm_sssd_ad_join_secret_async; + iface->enroll_ccache_async = realm_sssd_ad_join_ccache_async; iface->enroll_finish = realm_sssd_ad_generic_finish; iface->unenroll_automatic_async = realm_sssd_ad_leave_automatic_async; iface->unenroll_password_async = realm_sssd_ad_leave_password_async; + iface->unenroll_ccache_async = realm_sssd_ad_leave_ccache_async; iface->unenroll_finish = realm_sssd_ad_generic_finish; } diff --git a/tools/realm-client.c b/tools/realm-client.c index d72e2ec..4212952 100644 --- a/tools/realm-client.c +++ b/tools/realm-client.c @@ -22,11 +22,15 @@ #include "eggdbusobjectmanagerclient.h" #include +#include #include +#include + #include #include +#include #include struct _RealmClient { @@ -464,10 +468,11 @@ realm_client_to_kerberos (RealmClient *self, return realm_client_get_kerberos (self, g_dbus_proxy_get_object_path (proxy)); } -static gboolean -is_credential_supported (GVariant *supported, - const gchar *desired_type, - const gchar **ret_owner) +static const gchar * +are_credentials_supported (GVariant *supported, + const gchar *credential_type_1, + const gchar *credential_type_2, + const gchar **ret_owner) { GVariantIter iter; const gchar *type; @@ -475,13 +480,301 @@ is_credential_supported (GVariant *supported, g_variant_iter_init (&iter, supported); while (g_variant_iter_loop (&iter, "(&s&s)", &type, &owner)) { - if (g_str_equal (desired_type, type)) { + if (g_strcmp0 (credential_type_1, type) == 0 || + g_strcmp0 (credential_type_2, type) == 0) { *ret_owner = owner; - return TRUE; + return type; } } - return FALSE; + return NULL; +} + +static void +propagate_krb5_error (GError **dest, + krb5_context context, + krb5_error_code code, + const gchar *format, + ...) +{ + GString *message; + va_list va; + + message = g_string_new (""); + + if (format) { + va_start (va, format); + g_string_append_vprintf (message, format, va); + va_end (va); + } + + if (code != 0) { + if (format) + g_string_append (message, ": "); + g_string_append (message, krb5_get_error_message (context, code)); + } + + g_set_error_literal (dest, g_quark_from_static_string ("krb5"), + code, message->str); + g_string_free (message, TRUE); +} + +static krb5_ccache +prepare_temporary_ccache (krb5_context krb5, + gchar **filename, + GError **error) +{ + const gchar *directory; + krb5_error_code code; + krb5_ccache ccache; + gchar *temp_name; + gint temp_fd; + int errn; + + directory = g_get_user_runtime_dir (); + if (!g_file_test (directory, G_FILE_TEST_IS_DIR)) + directory = g_get_tmp_dir (); + + if (g_mkdir_with_parents (directory, S_IRWXU) < 0) { + errn = errno; + g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errn), + _("Couldn't create runtime directory: %s: %s"), + directory, g_strerror (errn)); + return NULL; + } + + temp_name = g_build_filename (directory, "realmd-krb5-cache.XXXXXX", NULL); + temp_fd = g_mkstemp_full (temp_name, O_RDWR, S_IRUSR | S_IWUSR); + if (temp_fd == -1) { + errn = errno; + g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errn), + _("Couldn't create credential cache file: %s: %s"), + temp_name, g_strerror (errn)); + g_free (temp_name); + return NULL; + } + + close (temp_fd); + + code = krb5_cc_resolve (krb5, temp_name, &ccache); + if (code != 0) { + propagate_krb5_error (error, krb5, code, _("Couldn't resolve credential cache")); + g_free (temp_name); + return NULL; + } + + *filename = temp_name; + return ccache; +} + +static gboolean +copy_to_ccache (krb5_context krb5, + const gchar *realm_name, + krb5_principal principal, + krb5_ccache ccache) +{ + krb5_principal server; + krb5_ccache def_ccache; + krb5_error_code code; + krb5_creds mcred; + krb5_creds creds; + + code = krb5_cc_default (krb5, &def_ccache); + if (code != 0) { + g_debug ("krb5_cc_default failed: %s", krb5_get_error_message (krb5, code)); + return FALSE; + } + + code = krb5_build_principal (krb5, &server, + strlen (realm_name), realm_name, + KRB5_TGS_NAME, realm_name, NULL); + g_return_val_if_fail (code == 0, FALSE); + + memset (&mcred, 0, sizeof (mcred)); + mcred.client = principal; + mcred.server = server; + mcred.times.starttime = g_get_real_time () / G_TIME_SPAN_MILLISECOND; + mcred.times.endtime = mcred.times.starttime; + + code = krb5_cc_retrieve_cred (krb5, def_ccache, KRB5_TC_MATCH_TIMES, + &mcred, &creds); + + krb5_free_principal (krb5, server); + krb5_cc_close (krb5, def_ccache); + + if (code == KRB5_CC_NOTFOUND) { + g_debug ("no matching principal found in %s", krb5_cc_default_name (krb5)); + return FALSE; + } else if (code != 0) { + g_debug ("krb5_cc_retrieve_cred failed: %s", krb5_get_error_message (krb5, code)); + return FALSE; + } + + code = krb5_cc_store_cred (krb5, ccache, &creds); + krb5_free_cred_contents (krb5, &creds); + + if (code != 0) { + g_debug ("krb5_cc_store_cred failed: %s", krb5_get_error_message (krb5, code)); + return FALSE; + } + + g_debug ("retrieved credentials from: %s", krb5_cc_default_name (krb5)); + return TRUE; +} + +static gboolean +kinit_to_ccache (krb5_context krb5, + krb5_principal principal, + const gchar *name, + krb5_ccache ccache, + GError **error) +{ + krb5_get_init_creds_opt *options = NULL; + krb5_error_code code; + krb5_creds my_creds; + + code = krb5_get_init_creds_opt_alloc (krb5, &options); + g_return_val_if_fail (code == 0, FALSE); + + code = krb5_get_init_creds_opt_set_out_ccache (krb5, options, ccache); + g_return_val_if_fail (code == 0, FALSE); + + code = krb5_get_init_creds_password (krb5, &my_creds, principal, NULL, + krb5_prompter_posix, 0, 0, NULL, options); + + krb5_get_init_creds_opt_free (krb5, options); + + if (code == KRB5KDC_ERR_PREAUTH_FAILED) { + propagate_krb5_error (error, krb5, code, _("Invalid password for %s"), name); + return FALSE; + + } else if (code != 0) { + propagate_krb5_error (error, krb5, code, _("Couldn't authenticate as %s"), name); + return FALSE; + } + + krb5_free_cred_contents (krb5, &my_creds); + return TRUE; +} + +static gboolean +copy_or_kinit_to_ccache (krb5_context krb5, + krb5_ccache ccache, + const gchar *user_name, + const gchar *realm_name, + GError **error) +{ + krb5_principal principal; + krb5_error_code code; + gchar *full_name; + gboolean ret; + + if (strchr (user_name, '@') == NULL) + user_name = full_name = g_strdup_printf ("%s@%s", user_name, realm_name); + + code = krb5_parse_name (krb5, user_name, &principal); + if (code != 0) { + propagate_krb5_error (error, krb5, code, _("Couldn't parse user name: %s"), user_name); + g_free (full_name); + return FALSE; + } + + ret = copy_to_ccache (krb5, realm_name, principal, ccache) || + kinit_to_ccache (krb5, principal, user_name, ccache, error); + + g_free (full_name); + krb5_free_principal (krb5, principal); + + return ret; +} + +static GVariant * +read_file_into_variant (const gchar *filename) +{ + GVariant *variant; + GError *error = NULL; + gchar *contents; + gsize length; + + g_file_get_contents (filename, &contents, &length, &error); + if (error != NULL) { + realm_handle_error (error, _("Couldn't read credential cache")); + return NULL; + } + + variant = g_variant_new_from_data (G_VARIANT_TYPE ("ay"), + contents, length, + TRUE, g_free, contents); + + return g_variant_ref_sink (variant); +} + +static GVariant * +build_ccache_credential (const gchar *user_name, + const gchar *realm_name, + const gchar *credential_owner, + GError **error) +{ + krb5_error_code code; + krb5_context krb5; + gboolean ret = FALSE; + krb5_ccache ccache; + gchar *filename; + GVariant *result; + + code = krb5_init_context (&krb5); + if (code != 0) { + propagate_krb5_error (error, NULL, code, _("Couldn't initialize kerberos")); + return NULL; + } + + ccache = prepare_temporary_ccache (krb5, &filename, error); + if (ccache) { + ret = copy_or_kinit_to_ccache (krb5, ccache, user_name, realm_name, error); + krb5_cc_close (krb5, ccache); + krb5_free_context (krb5); + } + + if (!ret) + return NULL; + + result = read_file_into_variant (filename); + + g_unlink (filename); + g_free (filename); + + if (result) + result = g_variant_new ("(ssv)", "ccache", credential_owner, result); + + return result; +} + +static GVariant * +build_password_credential (const gchar *user_name, + const gchar *credential_owner, + GError **error) +{ + const gchar *password; + GVariant *result; + gchar *prompt; + + prompt = g_strdup_printf (_("Password for %s: "), user_name); + password = getpass (prompt); + g_free (prompt); + + if (password == NULL) { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + _("Couldn't prompt for password: %s"), g_strerror (errno)); + return NULL; + } + + result = g_variant_new ("(ssv)", "password", credential_owner, + g_variant_new ("(ss)", user_name, password)); + + if (password) + memset ((char *)password, 0, strlen (password)); + + return result; } GVariant * @@ -493,22 +786,17 @@ realm_client_build_principal_creds (RealmClient *self, { RealmDbusKerberos *kerberos; const gchar *realm_name; - const gchar *password = NULL; - gboolean use_ccache; - GVariant *contents; GVariant *creds; - const gchar *owner; - gchar *prompt; + const gchar *credential_type; + const gchar *credential_owner; g_return_val_if_fail (REALM_IS_CLIENT (self), NULL); - if (is_credential_supported (supported, "ccache", &owner)) { - use_ccache = TRUE; - - } else if (is_credential_supported (supported, "password", &owner)) { - use_ccache = FALSE; + credential_type = are_credentials_supported (supported, + "ccache", "password", + &credential_owner); - } else { + if (credential_type == NULL) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, _("Realm does not support membership using a password")); return NULL; @@ -519,44 +807,17 @@ realm_client_build_principal_creds (RealmClient *self, if (user_name == NULL || g_str_equal (user_name, "")) user_name = g_get_user_name (); - /* If passing in a credential cache, then let krb5 do the prompting */ - if (use_ccache) { - password = NULL; - - /* Passing in a password, we need to know it */ - } else { - prompt = g_strdup_printf (_("Password for %s: "), user_name); - password = getpass (prompt); - g_free (prompt); - - if (password == NULL) { - g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, - _("Couldn't prompt for password: %s"), g_strerror (errno)); - return NULL; - } - } - - /* Do a kinit for the given realm */ - if (use_ccache) { + /* A credential cache? */ + if (g_str_equal (credential_type, "ccache")) { kerberos = realm_client_to_kerberos (self, membership); realm_name = realm_dbus_kerberos_get_realm_name (kerberos); - contents = realm_kinit_to_kerberos_cache (user_name, realm_name, password, error); - g_object_unref (kerberos); - - if (!contents) - creds = NULL; - else - creds = g_variant_new ("(ssv)", "ccache", owner, contents); + creds = build_ccache_credential (user_name, realm_name, credential_owner, error); - /* Just prompt for a password, and pass it in */ + /* A plain ol password */ } else { - creds = g_variant_new ("(ssv)", "password", owner, - g_variant_new ("(ss)", user_name, password)); + creds = build_password_credential (user_name, credential_owner, error); } - if (password) - memset ((char *)password, 0, strlen (password)); - return creds; } @@ -570,7 +831,7 @@ realm_client_build_otp_creds (RealmClient *self, g_return_val_if_fail (REALM_IS_CLIENT (self), NULL); - if (!is_credential_supported (supported, "secret", &owner)) { + if (!are_credentials_supported (supported, "secret", NULL, &owner)) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, _("Realm does not support membership using a one time password")); return NULL; @@ -583,20 +844,125 @@ realm_client_build_otp_creds (RealmClient *self, sizeof (unsigned char))); } +static krb5_error_code +copy_credential_cache (krb5_context krb5, + krb5_ccache src, + krb5_ccache dst) +{ + krb5_error_code code = 0; + krb5_principal princ = NULL; + + code = krb5_cc_get_principal (krb5, src, &princ); + if (!code) + code = krb5_cc_initialize (krb5, dst, princ); + if (code) + return code; + + code = krb5_cc_copy_creds (krb5, src, dst); + if (princ) + krb5_free_principal (krb5, princ); + + return code; +} + +static GVariant * +lookup_ccache_credential (const gchar *realm_name, + const gchar *credential_owner, + GError **error) +{ + GVariant *result = NULL; + krb5_error_code code; + krb5_context krb5; + krb5_ccache origin = NULL; + krb5_ccache ccache = NULL; + krb5_principal principal; + krb5_principal server; + gchar *filename; + + code = krb5_init_context (&krb5); + if (code != 0) { + propagate_krb5_error (error, NULL, code, _("Couldn't initialize kerberos")); + return NULL; + } + + code = krb5_build_principal (krb5, &server, + strlen (realm_name), realm_name, + KRB5_TGS_NAME, realm_name, NULL); + g_return_val_if_fail (code == 0, FALSE); + + code = krb5_cc_select (krb5, server, &origin, &principal); + + krb5_free_principal (krb5, server); + if (principal) + krb5_free_principal (krb5, principal); + + if (code == KRB5_CC_NOTFOUND) { + origin = NULL; + + } else if (code != 0) { + propagate_krb5_error (error, krb5, code, _("Couldn't select kerberos credentials")); + origin = NULL; + } + + if (origin) { + ccache = prepare_temporary_ccache (krb5, &filename, error); + if (ccache) { + code = copy_credential_cache (krb5, origin, ccache); + krb5_cc_close (krb5, ccache); + + if (code == 0) + result = read_file_into_variant (filename); + else + propagate_krb5_error (error, krb5, code, _("Couldn't read kerberos credentials")); + if (result) + result = g_variant_new ("(ssv)", "ccache", credential_owner, result); + + g_unlink (filename); + g_free (filename); + } + + krb5_cc_close (krb5, origin); + } + + krb5_free_context (krb5); + + return result; +} + + GVariant * realm_client_build_automatic_creds (RealmClient *self, + RealmDbusKerberos *realm, GVariant *supported, GError **error) { - const gchar *owner; + const gchar *credential_owner; + const gchar *realm_name; + GVariant *result; + GError *erra = NULL; g_return_val_if_fail (REALM_IS_CLIENT (self), NULL); - if (!is_credential_supported (supported, "automatic", &owner)) { - g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, - _("Realm does not support automatic membership")); - return NULL; + /* So first check if we have a kerberos ccache that matches */ + if (are_credentials_supported (supported, "ccache", NULL, &credential_owner)) { + realm_name = realm_dbus_kerberos_get_realm_name (realm); + result = lookup_ccache_credential (realm_name, credential_owner, &erra); + + /* If no credentials, then fall through to below */ + if (erra != NULL) { + g_propagate_error (error, erra); + return NULL; + } else if (result != NULL) { + return result; + } + } + + if (are_credentials_supported (supported, "automatic", NULL, &credential_owner)) { + return g_variant_new ("(ssv)", "automatic", credential_owner, + g_variant_new_string ("")); } - return g_variant_new ("(ssv)", "automatic", owner, g_variant_new_string ("")); + g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, + _("Realm does not support automatic membership")); + return NULL; } diff --git a/tools/realm-client.h b/tools/realm-client.h index 0f75a10..0b1926a 100644 --- a/tools/realm-client.h +++ b/tools/realm-client.h @@ -76,6 +76,7 @@ GVariant * realm_client_build_otp_creds (RealmClien GError **error); GVariant * realm_client_build_automatic_creds (RealmClient *self, + RealmDbusKerberos *realm, GVariant *supported, GError **error); diff --git a/tools/realm-join.c b/tools/realm-join.c index e398d08..dfcde3b 100644 --- a/tools/realm-join.c +++ b/tools/realm-join.c @@ -100,6 +100,7 @@ perform_automatic_join (RealmClient *client, GVariant *options, gboolean *try_other) { + RealmDbusKerberos *kerberos; GVariant *supported; GVariant *credentials; GError *error = NULL; @@ -107,7 +108,9 @@ perform_automatic_join (RealmClient *client, int ret; supported = realm_dbus_kerberos_membership_get_supported_join_credentials (membership); - credentials = realm_client_build_automatic_creds (client, supported, &error); + kerberos = realm_client_to_kerberos (client, membership); + + credentials = realm_client_build_automatic_creds (client, kerberos, supported, &error); if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED)) { *try_other = TRUE; return 1; diff --git a/tools/realm.c b/tools/realm.c index d02c9f2..9cb0b62 100644 --- a/tools/realm.c +++ b/tools/realm.c @@ -21,13 +21,10 @@ #include #include -#include #include #include -#include -#include #include static gchar *arg_install = NULL; @@ -135,56 +132,6 @@ realm_build_options (const gchar *first, return options; } -static void -propagate_krb5_error (GError **dest, - krb5_context context, - krb5_error_code code, - const gchar *format, - ...) -{ - GString *message; - va_list va; - - message = g_string_new (""); - - if (format) { - va_start (va, format); - g_string_append_vprintf (message, format, va); - va_end (va); - } - - if (code != 0) { - if (format) - g_string_append (message, ": "); - g_string_append (message, krb5_get_error_message (context, code)); - } - - g_set_error_literal (dest, g_quark_from_static_string ("krb5"), - code, message->str); - g_string_free (message, TRUE); -} - -static GVariant * -read_file_into_variant (const gchar *filename) -{ - GVariant *variant; - GError *error = NULL; - gchar *contents; - gsize length; - - g_file_get_contents (filename, &contents, &length, &error); - if (error != NULL) { - realm_handle_error (error, _("Couldn't read credential cache")); - return NULL; - } - - variant = g_variant_new_from_data (G_VARIANT_TYPE ("ay"), - contents, length, - TRUE, g_free, contents); - - return g_variant_ref_sink (variant); -} - gboolean realm_is_configured (RealmDbusRealm *realm) { @@ -196,107 +143,6 @@ realm_is_configured (RealmDbusRealm *realm) return (configured && !g_str_equal (configured, "")); } -GVariant * -realm_kinit_to_kerberos_cache (const gchar *name, - const gchar *realm, - const gchar *password, - GError **error) -{ - krb5_get_init_creds_opt *options = NULL; - krb5_context context = NULL; - krb5_principal principal = NULL; - krb5_error_code code; - int temp_fd; - gchar *full_name = NULL; - gchar *filename = NULL; - krb5_ccache ccache = NULL; - krb5_creds my_creds; - GVariant *result = NULL; - const gchar *directory; - - code = krb5_init_context (&context); - if (code != 0) { - propagate_krb5_error (error, NULL, code, _("Couldn't initialize kerberos")); - goto cleanup; - } - - if (strchr (name, '@') == NULL) - name = full_name = g_strdup_printf ("%s@%s", name, realm); - - code = krb5_parse_name (context, name, &principal); - if (code != 0) { - propagate_krb5_error (error, context, code, _("Couldn't parse user name: %s"), name); - goto cleanup; - } - - code = krb5_get_init_creds_opt_alloc (context, &options); - if (code != 0) { - propagate_krb5_error (error, context, code, _("Couldn't setup kerberos options")); - goto cleanup; - } - - directory = g_get_user_runtime_dir (); - if (!g_file_test (directory, G_FILE_TEST_IS_DIR)) - directory = g_get_tmp_dir (); - - filename = g_build_filename (directory, "realmd-krb5-cache.XXXXXX", NULL); - temp_fd = g_mkstemp_full (filename, O_RDWR, S_IRUSR | S_IWUSR); - if (temp_fd == -1) { - int errn = errno; - g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errn), - _("Couldn't create credential cache file: %s"), g_strerror (errn)); - goto cleanup; - } - close (temp_fd); - - code = krb5_cc_resolve (context, filename, &ccache); - if (code != 0) { - propagate_krb5_error (error, context, code, _("Couldn't resolve credential cache")); - goto cleanup; - } - - code = krb5_get_init_creds_opt_set_out_ccache (context, options, ccache); - if (code != 0) { - propagate_krb5_error (error, context, code, _("Couldn't setup credential cache")); - goto cleanup; - } - - code = krb5_get_init_creds_password (context, &my_creds, principal, (char *)password, - password ? NULL : krb5_prompter_posix, - 0, 0, NULL, options); - if (code == KRB5KDC_ERR_PREAUTH_FAILED) { - propagate_krb5_error (error, context, code, _("Invalid password for %s"), name); - goto cleanup; - } else if (code != 0) { - propagate_krb5_error (error, context, code, _("Couldn't authenticate as %s"), name); - goto cleanup; - } - - krb5_cc_close (context, ccache); - ccache = NULL; - - result = read_file_into_variant (filename); - krb5_free_cred_contents (context, &my_creds); - -cleanup: - g_free (full_name); - - if (filename) { - g_unlink (filename); - g_free (filename); - } - - if (options) - krb5_get_init_creds_opt_free (context, options); - if (principal) - krb5_free_principal (context, principal); - if (ccache) - krb5_cc_close (context, ccache); - if (context) - krb5_free_context (context); - return result; -} - static int usage (int code) { -- 1.8.1.4