1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
2  *
3  * Copyright 2009-2012  Red Hat, Inc.
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, see <http://www.gnu.org/licenses/>.
17  *
18  * Written by: Stef Walter <stefw@gnome.org>
19  */
20 
21 #include "config.h"
22 
23 #include "cc-realm-manager.h"
24 
25 #include <krb5.h>
26 
27 #include <glib.h>
28 #include <glib/gi18n.h>
29 #include <glib/gstdio.h>
30 
31 #include <sys/types.h>
32 #include <sys/stat.h>
33 #include <errno.h>
34 #include <fcntl.h>
35 
36 
37 struct _CcRealmManager {
38         CcRealmObjectManagerClient parent_instance;
39 
40         CcRealmProvider *provider;
41         guint diagnostics_sig;
42 };
43 
44 enum {
45         REALM_ADDED,
46         NUM_SIGNALS,
47 };
48 
49 static gint signals[NUM_SIGNALS] = { 0, };
50 
51 G_DEFINE_TYPE (CcRealmManager, cc_realm_manager, CC_REALM_TYPE_OBJECT_MANAGER_CLIENT);
52 
53 GQuark
cc_realm_error_get_quark(void)54 cc_realm_error_get_quark (void)
55 {
56         static GQuark quark = 0;
57         if (quark == 0)
58                 quark = g_quark_from_static_string ("cc-realm-error");
59         return quark;
60 }
61 
62 static gboolean
is_realm_with_kerberos_and_membership(gpointer object)63 is_realm_with_kerberos_and_membership (gpointer object)
64 {
65         g_autoptr(GDBusInterface) kerberos_interface = NULL;
66         g_autoptr(GDBusInterface) kerberos_membership_interface = NULL;
67 
68         if (!G_IS_DBUS_OBJECT (object))
69                 return FALSE;
70 
71         kerberos_interface = g_dbus_object_get_interface (object, "org.freedesktop.realmd.Kerberos");
72         if (kerberos_interface == NULL)
73                 return FALSE;
74 
75         kerberos_membership_interface = g_dbus_object_get_interface (object, "org.freedesktop.realmd.KerberosMembership");
76         if (kerberos_membership_interface == NULL)
77                 return FALSE;
78 
79         return TRUE;
80 }
81 
82 static void
on_interface_added(CcRealmManager * self,GDBusObject * object,GDBusInterface * interface)83 on_interface_added (CcRealmManager *self,
84                     GDBusObject *object,
85                     GDBusInterface *interface)
86 {
87         g_dbus_proxy_set_default_timeout (G_DBUS_PROXY (interface), G_MAXINT);
88 }
89 
90 static void
on_object_added(CcRealmManager * self,GDBusObject * object)91 on_object_added (CcRealmManager *self,
92                  GDBusObject *object)
93 {
94         GList *interfaces, *l;
95 
96         interfaces = g_dbus_object_get_interfaces (object);
97         for (l = interfaces; l != NULL; l = g_list_next (l))
98                 on_interface_added (self, object, l->data);
99         g_list_free_full (interfaces, g_object_unref);
100 
101         if (is_realm_with_kerberos_and_membership (object)) {
102                 g_debug ("Saw realm: %s", g_dbus_object_get_object_path (object));
103                 g_signal_emit (self, signals[REALM_ADDED], 0, object);
104         }
105 }
106 
107 static void
cc_realm_manager_init(CcRealmManager * self)108 cc_realm_manager_init (CcRealmManager *self)
109 {
110         g_signal_connect (self, "object-added", G_CALLBACK (on_object_added), NULL);
111         g_signal_connect (self, "interface-added", G_CALLBACK (on_interface_added), NULL);
112 }
113 
114 static void
cc_realm_manager_dispose(GObject * obj)115 cc_realm_manager_dispose (GObject *obj)
116 {
117         CcRealmManager *self = CC_REALM_MANAGER (obj);
118         GDBusConnection *connection;
119 
120         g_clear_object (&self->provider);
121 
122         if (self->diagnostics_sig) {
123                 connection = g_dbus_object_manager_client_get_connection (G_DBUS_OBJECT_MANAGER_CLIENT (self));
124                 if (connection != NULL)
125                         g_dbus_connection_signal_unsubscribe (connection, self->diagnostics_sig);
126                 self->diagnostics_sig = 0;
127         }
128 
129         G_OBJECT_CLASS (cc_realm_manager_parent_class)->dispose (obj);
130 }
131 
132 static void
cc_realm_manager_class_init(CcRealmManagerClass * klass)133 cc_realm_manager_class_init (CcRealmManagerClass *klass)
134 {
135         GObjectClass *object_class = G_OBJECT_CLASS (klass);
136 
137         object_class->dispose = cc_realm_manager_dispose;
138 
139         signals[REALM_ADDED] = g_signal_new ("realm-added", CC_TYPE_REALM_MANAGER,
140                                              G_SIGNAL_RUN_FIRST, 0, NULL, NULL,
141                                              g_cclosure_marshal_generic,
142                                              G_TYPE_NONE, 1, CC_REALM_TYPE_OBJECT);
143 }
144 
145 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)146 on_realm_diagnostics (GDBusConnection *connection,
147                       const gchar *sender_name,
148                       const gchar *object_path,
149                       const gchar *interface_name,
150                       const gchar *signal_name,
151                       GVariant *parameters,
152                       gpointer user_data)
153 {
154         const gchar *message;
155         const gchar *unused;
156 
157         if (g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(ss)"))) {
158                 /* Data is already formatted appropriately for stderr */
159                 g_variant_get (parameters, "(&s&s)", &message, &unused);
160                 g_printerr ("%s", message);
161         }
162 }
163 
164 static void
on_provider_new(GObject * source,GAsyncResult * result,gpointer user_data)165 on_provider_new (GObject *source,
166                  GAsyncResult *result,
167                  gpointer user_data)
168 {
169         g_autoptr(GTask) task = G_TASK (user_data);
170         CcRealmManager *manager = g_task_get_task_data (task);
171         GError *error = NULL;
172 
173         manager->provider = cc_realm_provider_proxy_new_finish (result, &error);
174         if (error == NULL) {
175                 g_dbus_proxy_set_default_timeout (G_DBUS_PROXY (manager->provider), -1);
176                 g_debug ("Created realm manager");
177                 g_task_return_pointer (task, g_object_ref (manager), g_object_unref);
178         } else {
179                 g_task_return_error (task, error);
180         }
181 }
182 
183 static void
on_manager_new(GObject * source,GAsyncResult * result,gpointer user_data)184 on_manager_new (GObject *source,
185                 GAsyncResult *result,
186                 gpointer user_data)
187 {
188         g_autoptr(GTask) task = G_TASK (user_data);
189         CcRealmManager *manager;
190         GDBusConnection *connection;
191         GError *error = NULL;
192         GObject *object;
193         guint sig;
194 
195         object = g_async_initable_new_finish (G_ASYNC_INITABLE (source), result, &error);
196         if (error == NULL) {
197                 manager = CC_REALM_MANAGER (object);
198                 connection = g_dbus_object_manager_client_get_connection (G_DBUS_OBJECT_MANAGER_CLIENT (object));
199 
200                 g_debug ("Connected to realmd");
201 
202                 sig = g_dbus_connection_signal_subscribe (connection,
203                                                           "org.freedesktop.realmd",
204                                                           "org.freedesktop.realmd.Service",
205                                                           "Diagnostics",
206                                                           NULL,
207                                                           NULL,
208                                                           G_DBUS_SIGNAL_FLAGS_NONE,
209                                                           on_realm_diagnostics,
210                                                           NULL,
211                                                           NULL);
212                 manager->diagnostics_sig = sig;
213 
214                 g_task_set_task_data (task, manager, g_object_unref);
215 
216                 cc_realm_provider_proxy_new (connection,
217                                              G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES,
218                                              "org.freedesktop.realmd",
219                                              "/org/freedesktop/realmd",
220                                              g_task_get_cancellable (task),
221                                              on_provider_new, task);
222                 g_steal_pointer (&task);
223         } else {
224                 g_task_return_error (task, error);
225         }
226 }
227 
228 void
cc_realm_manager_new(GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)229 cc_realm_manager_new (GCancellable *cancellable,
230                       GAsyncReadyCallback callback,
231                       gpointer user_data)
232 {
233         GTask *task;
234 
235         g_debug ("Connecting to realmd...");
236 
237         task = g_task_new (NULL, cancellable, callback, user_data);
238         g_task_set_source_tag (task, cc_realm_manager_new);
239 
240         g_async_initable_new_async (CC_TYPE_REALM_MANAGER, G_PRIORITY_DEFAULT,
241                                     cancellable, on_manager_new, task,
242                                     "flags", G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_NONE,
243                                     "name", "org.freedesktop.realmd",
244                                     "bus-type", G_BUS_TYPE_SYSTEM,
245                                     "object-path", "/org/freedesktop/realmd",
246                                     "get-proxy-type-func", cc_realm_object_manager_client_get_proxy_type,
247                                     NULL);
248 }
249 
250 CcRealmManager *
cc_realm_manager_new_finish(GAsyncResult * result,GError ** error)251 cc_realm_manager_new_finish (GAsyncResult *result,
252                              GError **error)
253 {
254         g_return_val_if_fail (g_task_is_valid (result, NULL), NULL);
255         g_return_val_if_fail (g_async_result_is_tagged (result, cc_realm_manager_new), NULL);
256 
257         return g_task_propagate_pointer (G_TASK (result), error);
258 }
259 
260 static void
realms_free(gpointer data)261 realms_free (gpointer data)
262 {
263         g_list_free_full (data, g_object_unref);
264 }
265 
266 static void
on_provider_discover(GObject * source,GAsyncResult * result,gpointer user_data)267 on_provider_discover (GObject *source,
268                       GAsyncResult *result,
269                       gpointer user_data)
270 {
271         g_autoptr(GTask) task = G_TASK (user_data);
272         CcRealmManager *manager = g_task_get_source_object (task);
273         GError *error = NULL;
274         gboolean no_membership = FALSE;
275         gchar **realms;
276         gint relevance;
277         gint i;
278         GList *kerberos_realms = NULL;
279 
280         cc_realm_provider_call_discover_finish (CC_REALM_PROVIDER (source), &relevance,
281                                                 &realms, result, &error);
282         if (error == NULL) {
283                 for (i = 0; realms[i]; i++) {
284                         g_autoptr(GDBusObject) object = NULL;
285 
286                         object = g_dbus_object_manager_get_object (G_DBUS_OBJECT_MANAGER (manager), realms[i]);
287                         if (object == NULL) {
288                                 g_warning ("Realm is not in object manager: %s", realms[i]);
289                         } else {
290                                 if (is_realm_with_kerberos_and_membership (object)) {
291                                         g_debug ("Discovered realm: %s", realms[i]);
292                                         kerberos_realms = g_list_prepend (kerberos_realms, g_steal_pointer (&object));
293                                 } else {
294                                         g_debug ("Realm does not support kerberos membership: %s", realms[i]);
295                                         no_membership = TRUE;
296                                 }
297                         }
298                 }
299                 g_strfreev (realms);
300 
301                 if (!kerberos_realms && no_membership) {
302                         g_task_return_new_error (task, CC_REALM_ERROR, CC_REALM_ERROR_GENERIC,
303                                                  _("Cannot automatically join this type of domain"));
304                 } else if (!kerberos_realms) {
305                         g_task_return_new_error (task, CC_REALM_ERROR, CC_REALM_ERROR_GENERIC,
306                                                  _("No such domain or realm found"));
307                 } else {
308                         kerberos_realms = g_list_reverse (kerberos_realms);
309                         g_task_return_pointer (task, kerberos_realms, realms_free);
310                 }
311         } else {
312                 g_task_return_error (task, error);
313         }
314 }
315 
316 void
cc_realm_manager_discover(CcRealmManager * self,const gchar * input,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)317 cc_realm_manager_discover (CcRealmManager *self,
318                            const gchar *input,
319                            GCancellable *cancellable,
320                            GAsyncReadyCallback callback,
321                            gpointer user_data)
322 {
323         GTask *task;
324         GVariant *options;
325 
326         g_return_if_fail (CC_IS_REALM_MANAGER (self));
327         g_return_if_fail (input != NULL);
328         g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
329 
330         g_debug ("Discovering realms for: %s", input);
331 
332         task = g_task_new (G_OBJECT (self), cancellable, callback, user_data);
333         g_task_set_source_tag (task, cc_realm_manager_discover);
334 
335         options = g_variant_new_array (G_VARIANT_TYPE ("{sv}"), NULL, 0);
336 
337         cc_realm_provider_call_discover (self->provider, input, options, cancellable,
338                                          on_provider_discover, task);
339 }
340 
341 GList *
cc_realm_manager_discover_finish(CcRealmManager * self,GAsyncResult * result,GError ** error)342 cc_realm_manager_discover_finish (CcRealmManager *self,
343                                   GAsyncResult *result,
344                                   GError **error)
345 {
346         g_return_val_if_fail (CC_IS_REALM_MANAGER (self), NULL);
347         g_return_val_if_fail (g_task_is_valid (result, G_OBJECT (self)), NULL);
348         g_return_val_if_fail (g_async_result_is_tagged (result, cc_realm_manager_discover), NULL);
349         g_return_val_if_fail (error == NULL || *error == NULL, NULL);
350 
351         return g_task_propagate_pointer (G_TASK (result), error);
352 }
353 
354 GList *
cc_realm_manager_get_realms(CcRealmManager * self)355 cc_realm_manager_get_realms (CcRealmManager *self)
356 {
357         GList *objects;
358         GList *realms = NULL;
359         GList *l;
360 
361         g_return_val_if_fail (CC_IS_REALM_MANAGER (self), NULL);
362 
363         objects = g_dbus_object_manager_get_objects (G_DBUS_OBJECT_MANAGER (self));
364         for (l = objects; l != NULL; l = g_list_next (l)) {
365                 if (is_realm_with_kerberos_and_membership (l->data))
366                         realms = g_list_prepend (realms, g_object_ref (l->data));
367         }
368 
369         g_list_free_full (objects, g_object_unref);
370         return realms;
371 }
372 
373 static void
string_replace(GString * string,const gchar * find,const gchar * replace)374 string_replace (GString *string,
375                 const gchar *find,
376                 const gchar *replace)
377 {
378         const gchar *at;
379         gssize pos;
380 
381         at = strstr (string->str, find);
382         if (at != NULL) {
383                 pos = at - string->str;
384                 g_string_erase (string, pos, strlen (find));
385                 g_string_insert (string, pos, replace);
386         }
387 }
388 
389 gchar *
cc_realm_calculate_login(CcRealmCommon * realm,const gchar * username)390 cc_realm_calculate_login (CcRealmCommon *realm,
391                           const gchar *username)
392 {
393         const gchar *const *formats;
394 
395         formats = cc_realm_common_get_login_formats (realm);
396         if (formats[0] != NULL) {
397                 GString *string = g_string_new (formats[0]);
398                 string_replace (string, "%U", username);
399                 string_replace (string, "%D", cc_realm_common_get_name (realm));
400                 return g_string_free (string, FALSE);
401         }
402 
403         return NULL;
404 }
405 
406 gboolean
cc_realm_is_configured(CcRealmObject * realm)407 cc_realm_is_configured (CcRealmObject *realm)
408 {
409         g_autoptr(CcRealmCommon) common = NULL;
410         const gchar *configured;
411         gboolean is = FALSE;
412 
413         common = cc_realm_object_get_common (realm);
414         if (common != NULL) {
415                 configured = cc_realm_common_get_configured (common);
416                 is = configured != NULL && !g_str_equal (configured, "");
417         }
418 
419         return is;
420 }
421 
422 static const gchar *
find_supported_credentials(CcRealmKerberosMembership * membership,const gchar * owner)423 find_supported_credentials (CcRealmKerberosMembership *membership,
424                             const gchar *owner)
425 {
426         const gchar *cred_owner;
427         const gchar *cred_type;
428         GVariant *supported;
429         GVariantIter iter;
430 
431         supported = cc_realm_kerberos_membership_get_supported_join_credentials (membership);
432         g_return_val_if_fail (supported != NULL, NULL);
433 
434         g_variant_iter_init (&iter, supported);
435         while (g_variant_iter_loop (&iter, "(&s&s)", &cred_type, &cred_owner)) {
436                 if (g_str_equal (owner, cred_owner)) {
437                         if (g_str_equal (cred_type, "ccache") ||
438                             g_str_equal (cred_type, "password")) {
439                                 return g_intern_string (cred_type);
440                         }
441                 }
442         }
443 
444         return NULL;
445 }
446 
447 static gboolean
realm_join_as_owner(CcRealmObject * realm,const gchar * owner,const gchar * login,const gchar * password,GBytes * credentials,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)448 realm_join_as_owner (CcRealmObject *realm,
449                      const gchar *owner,
450                      const gchar *login,
451                      const gchar *password,
452                      GBytes *credentials,
453                      GCancellable *cancellable,
454                      GAsyncReadyCallback callback,
455                      gpointer user_data)
456 {
457         g_autoptr(CcRealmKerberosMembership) membership = NULL;
458         GVariant *contents;
459         GVariant *options;
460         GVariant *option;
461         GVariant *creds;
462         const gchar *type;
463 
464         membership = cc_realm_object_get_kerberos_membership (realm);
465         g_return_val_if_fail (membership != NULL, FALSE);
466 
467         type = find_supported_credentials (membership, owner);
468         if (type == NULL) {
469                 g_debug ("Couldn't find supported credential type for owner: %s", owner);
470                 return FALSE;
471         }
472 
473         if (g_str_equal (type, "ccache")) {
474                 g_debug ("Using a kerberos credential cache to join the realm");
475                 contents = g_variant_new_from_data (G_VARIANT_TYPE ("ay"),
476                                                     g_bytes_get_data (credentials, NULL),
477                                                     g_bytes_get_size (credentials),
478                                                     TRUE, (GDestroyNotify)g_bytes_unref, credentials);
479 
480         } else if (g_str_equal (type, "password")) {
481                 g_debug ("Using a user/password to join the realm");
482                 contents = g_variant_new ("(ss)", login, password);
483 
484         } else {
485                 g_assert_not_reached ();
486         }
487 
488         creds = g_variant_new ("(ssv)", type, owner, contents);
489         option = g_variant_new ("{sv}", "manage-system", g_variant_new_boolean (FALSE));
490         options = g_variant_new_array (G_VARIANT_TYPE ("{sv}"), &option, 1);
491 
492         g_debug ("Calling the Join() method with %s credentials", owner);
493 
494         cc_realm_kerberos_membership_call_join (membership, creds, options,
495                                                 cancellable, callback, user_data);
496 
497         return TRUE;
498 }
499 
500 gboolean
cc_realm_join_as_user(CcRealmObject * realm,const gchar * login,const gchar * password,GBytes * credentials,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)501 cc_realm_join_as_user (CcRealmObject *realm,
502                        const gchar *login,
503                        const gchar *password,
504                        GBytes *credentials,
505                        GCancellable *cancellable,
506                        GAsyncReadyCallback callback,
507                        gpointer user_data)
508 {
509         g_return_val_if_fail (CC_REALM_IS_OBJECT (realm), FALSE);
510         g_return_val_if_fail (credentials != NULL, FALSE);
511         g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), FALSE);
512         g_return_val_if_fail (login != NULL, FALSE);
513         g_return_val_if_fail (password != NULL, FALSE);
514         g_return_val_if_fail (credentials != NULL, FALSE);
515 
516         return realm_join_as_owner (realm, "user", login, password,
517                                     credentials, cancellable, callback, user_data);
518 }
519 
520 gboolean
cc_realm_join_as_admin(CcRealmObject * realm,const gchar * login,const gchar * password,GBytes * credentials,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)521 cc_realm_join_as_admin (CcRealmObject *realm,
522                         const gchar *login,
523                         const gchar *password,
524                         GBytes *credentials,
525                         GCancellable *cancellable,
526                         GAsyncReadyCallback callback,
527                         gpointer user_data)
528 {
529         g_return_val_if_fail (CC_REALM_IS_OBJECT (realm), FALSE);
530         g_return_val_if_fail (credentials != NULL, FALSE);
531         g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), FALSE);
532         g_return_val_if_fail (login != NULL, FALSE);
533         g_return_val_if_fail (password != NULL, FALSE);
534         g_return_val_if_fail (credentials != NULL, FALSE);
535 
536         return realm_join_as_owner (realm, "administrator", login, password, credentials,
537                                     cancellable, callback, user_data);
538 }
539 
540 gboolean
cc_realm_join_finish(CcRealmObject * realm,GAsyncResult * result,GError ** error)541 cc_realm_join_finish (CcRealmObject *realm,
542                       GAsyncResult *result,
543                       GError **error)
544 {
545         g_autoptr(CcRealmKerberosMembership) membership = NULL;
546         g_autoptr(GError) call_error = NULL;
547         g_autofree gchar *dbus_error = NULL;
548 
549         g_return_val_if_fail (CC_REALM_IS_OBJECT (realm), FALSE);
550         g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
551 
552         membership = cc_realm_object_get_kerberos_membership (realm);
553         g_return_val_if_fail (membership != NULL, FALSE);
554 
555         if (cc_realm_kerberos_membership_call_join_finish (membership, result, &call_error)) {
556                 g_debug ("Completed Join() method call");
557                 return TRUE;
558         }
559 
560         dbus_error = g_dbus_error_get_remote_error (call_error);
561         if (dbus_error == NULL) {
562                 g_debug ("Join() failed because of %s", call_error->message);
563                 g_propagate_error (error, g_steal_pointer (&call_error));
564                 return FALSE;
565         }
566 
567         g_dbus_error_strip_remote_error (call_error);
568 
569         if (g_str_equal (dbus_error, "org.freedesktop.realmd.Error.AuthenticationFailed")) {
570                 g_debug ("Join() failed because of invalid/insufficient credentials");
571                 g_set_error (error, CC_REALM_ERROR, CC_REALM_ERROR_BAD_LOGIN,
572                              "%s", call_error->message);
573         } else {
574                 g_debug ("Join() failed because of %s", call_error->message);
575                 g_propagate_error (error, g_steal_pointer (&call_error));
576         }
577 
578         return FALSE;
579 }
580 
581 typedef struct {
582         gchar *domain;
583         gchar *realm;
584         gchar *user;
585         gchar *password;
586 } LoginClosure;
587 
588 static void
login_closure_free(gpointer data)589 login_closure_free (gpointer data)
590 {
591         LoginClosure *login = data;
592         g_clear_pointer (&login->domain, g_free);
593         g_clear_pointer (&login->realm, g_free);
594         g_clear_pointer (&login->user, g_free);
595         g_clear_pointer (&login->password, g_free);
596         g_slice_free (LoginClosure, login);
597 }
598 
599 static krb5_error_code
login_perform_kinit(krb5_context k5,const gchar * realm,const gchar * login,const gchar * password,const gchar * filename)600 login_perform_kinit (krb5_context k5,
601                      const gchar *realm,
602                      const gchar *login,
603                      const gchar *password,
604                      const gchar *filename)
605 {
606         krb5_get_init_creds_opt *opts;
607         krb5_error_code code;
608         krb5_principal principal;
609         krb5_ccache ccache;
610         krb5_creds creds;
611         g_autofree gchar *name = NULL;
612 
613         name = g_strdup_printf ("%s@%s", login, realm);
614         code = krb5_parse_name (k5, name, &principal);
615 
616         if (code != 0) {
617                 g_debug ("Couldn't parse principal name: %s: %s",
618                          name, krb5_get_error_message (k5, code));
619                 return code;
620         }
621 
622         g_debug ("Using principal name to kinit: %s", name);
623 
624         if (filename == NULL)
625                 code = krb5_cc_default (k5, &ccache);
626         else
627                 code = krb5_cc_resolve (k5, filename, &ccache);
628 
629         if (code != 0) {
630                 krb5_free_principal (k5, principal);
631                 g_debug ("Couldn't open credential cache: %s: %s",
632                          filename ? filename : "<default>",
633                          krb5_get_error_message (k5, code));
634                 return code;
635         }
636 
637         code = krb5_get_init_creds_opt_alloc (k5, &opts);
638         g_return_val_if_fail (code == 0, code);
639 
640 #ifdef HAVE_KRB5_GET_INIT_CREDS_OPT_SET_OUT_CCACHE
641         code = krb5_get_init_creds_opt_set_out_ccache (k5, opts, ccache);
642         g_return_val_if_fail (code == 0, code);
643 #endif
644 
645         code = krb5_get_init_creds_password (k5, &creds, principal,
646                                              (char *)password,
647                                              NULL, 0, 0, NULL, opts);
648 
649         krb5_get_init_creds_opt_free (k5, opts);
650         krb5_cc_close (k5, ccache);
651         krb5_free_principal (k5, principal);
652 
653         if (code == 0) {
654                 g_debug ("kinit succeeded");
655                 krb5_free_cred_contents (k5, &creds);
656         } else {
657                 g_debug ("kinit failed: %s", krb5_get_error_message (k5, code));
658         }
659 
660         return code;
661 }
662 
663 static void
kinit_thread_func(GTask * t,gpointer object,gpointer task_data,GCancellable * cancellable)664 kinit_thread_func (GTask *t,
665                    gpointer object,
666                    gpointer task_data,
667                    GCancellable *cancellable)
668 {
669         g_autoptr(GTask) task = t;
670         LoginClosure *login = task_data;
671         krb5_context k5 = NULL;
672         krb5_error_code code;
673         g_autofree gchar *filename = NULL;
674         gchar *contents;
675         gsize length;
676         gint temp_fd;
677 
678         filename = g_build_filename (g_get_user_runtime_dir (),
679                                      "um-krb5-creds.XXXXXX", NULL);
680         temp_fd = g_mkstemp_full (filename, O_RDWR, S_IRUSR | S_IWUSR);
681         if (temp_fd == -1) {
682                 g_warning ("Couldn't create credential cache file: %s: %s",
683                            filename, g_strerror (errno));
684                 g_clear_pointer (&filename, g_free);
685         } else {
686                 close (temp_fd);
687         }
688 
689         code = krb5_init_context (&k5);
690         if (code == 0) {
691                 code = login_perform_kinit (k5, login->realm, login->user,
692                                             login->password, filename);
693         }
694 
695         switch (code) {
696         case 0:
697                 if (filename != NULL) {
698                         g_autoptr(GError) error = NULL;
699 
700                         if (g_file_get_contents (filename, &contents, &length, &error)) {
701                                 g_debug ("Read in credential cache: %s", filename);
702                         } else {
703                                 g_warning ("Couldn't read credential cache: %s: %s",
704                                            filename, error->message);
705                         }
706 
707                         g_task_return_pointer (task, g_bytes_new_take (contents, length), (GDestroyNotify) g_bytes_unref);
708                 }
709                 break;
710 
711         case KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN:
712         case KRB5KDC_ERR_POLICY:
713                 g_task_return_new_error (task, CC_REALM_ERROR, CC_REALM_ERROR_BAD_LOGIN,
714                                          _("Cannot log in as %s at the %s domain"),
715                                          login->user, login->domain);
716                 break;
717         case KRB5KDC_ERR_PREAUTH_FAILED:
718         case KRB5KRB_AP_ERR_BAD_INTEGRITY:
719                 g_task_return_new_error (task, CC_REALM_ERROR, CC_REALM_ERROR_BAD_PASSWORD,
720                                          _("Invalid password, please try again"));
721                 break;
722         case KRB5_PREAUTH_FAILED:
723         case KRB5KDC_ERR_KEY_EXP:
724         case KRB5KDC_ERR_CLIENT_REVOKED:
725         case KRB5KDC_ERR_ETYPE_NOSUPP:
726         case KRB5_PROG_ETYPE_NOSUPP:
727                 g_task_return_new_error (task, CC_REALM_ERROR, CC_REALM_ERROR_CANNOT_AUTH,
728                                          _("Cannot log in as %s at the %s domain"),
729                                          login->user, login->domain);
730                 break;
731         default:
732                 g_task_return_new_error (task, CC_REALM_ERROR, CC_REALM_ERROR_GENERIC,
733                                          _("Couldn’t connect to the %s domain: %s"),
734                                          login->domain, krb5_get_error_message (k5, code));
735                 break;
736         }
737 
738         if (filename) {
739                 g_unlink (filename);
740                 g_debug ("Deleted credential cache: %s", filename);
741         }
742 
743         if (k5)
744                 krb5_free_context (k5);
745 }
746 
747 void
cc_realm_login(CcRealmObject * realm,const gchar * user,const gchar * password,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)748 cc_realm_login (CcRealmObject *realm,
749                 const gchar *user,
750                 const gchar *password,
751                 GCancellable *cancellable,
752                 GAsyncReadyCallback callback,
753                 gpointer user_data)
754 {
755         GTask *task;
756         LoginClosure *login;
757         g_autoptr(CcRealmKerberos) kerberos = NULL;
758 
759         g_return_if_fail (CC_REALM_IS_OBJECT (realm));
760         g_return_if_fail (user != NULL);
761         g_return_if_fail (password != NULL);
762         g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
763 
764         kerberos = cc_realm_object_get_kerberos (realm);
765         g_return_if_fail (kerberos != NULL);
766 
767         task = g_task_new (NULL, cancellable, callback, user_data);
768         g_task_set_source_tag (task, cc_realm_login);
769 
770         login = g_slice_new0 (LoginClosure);
771         login->domain = g_strdup (cc_realm_kerberos_get_domain_name (kerberos));
772         login->realm = g_strdup (cc_realm_kerberos_get_realm_name (kerberos));
773         login->user = g_strdup (user);
774         login->password = g_strdup (password);
775         g_task_set_task_data (task, login, login_closure_free);
776 
777         g_task_set_return_on_cancel (task, TRUE);
778         g_task_run_in_thread (task, kinit_thread_func);
779 }
780 
781 GBytes *
cc_realm_login_finish(GAsyncResult * result,GError ** error)782 cc_realm_login_finish (GAsyncResult *result,
783                        GError **error)
784 {
785         g_return_val_if_fail (g_task_is_valid (result, NULL), FALSE);
786         g_return_val_if_fail (g_async_result_is_tagged (result, cc_realm_login), FALSE);
787         g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
788 
789         return g_task_propagate_pointer (G_TASK (result), error);
790 }
791