2 * Copyright (C) 2004,2005,2006 Red Hat, Inc.
3 * Authored by Christopher Aillon <caillon@redhat.com>
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, or (at your option)
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.
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
30 #include <glib/gi18n.h>
31 #include <glade/glade.h>
32 #include <dbus/dbus-glib.h>
35 #ifdef ENABLE_NETWORK_MANAGER
36 #include <libnm_glib.h>
39 static GladeXML *xml = NULL;
40 static krb5_context kcontext;
41 static krb5_principal kprincipal;
42 static krb5_timestamp creds_expiry;
43 static krb5_timestamp canceled_creds_expiry;
44 static gboolean canceled;
45 static gboolean invalid_password;
46 static gboolean always_run;
48 static int grab_credentials (gboolean renewable);
49 static gboolean get_tgt_from_ccache (krb5_context context, krb5_creds *creds);
51 /* YAY for different Kerberos implementations */
53 get_cred_forwardable(krb5_creds *creds)
55 #if defined(HAVE_KRB5_CREDS_TICKET_FLAGS) && defined(TKT_FLG_FORWARDABLE)
56 return creds->ticket_flags & TKT_FLG_FORWARDABLE;
57 #elif defined(HAVE_KRB5_CREDS_FLAGS_B_FORWARDABLE)
58 return creds->flags.b.forwardable;
59 #elif defined(HAVE_KRB5_CREDS_FLAGS) && defined(KDC_OPT_FORWARDABLE)
60 return creds->flags & KDC_OPT_FORWARDABLE;
65 get_cred_renewable(krb5_creds *creds)
67 #if defined(HAVE_KRB5_CREDS_TICKET_FLAGS) && defined(TKT_FLG_RENEWABLE)
68 return creds->ticket_flags & TKT_FLG_RENEWABLE;
69 #elif defined(HAVE_KRB5_CREDS_FLAGS_B_RENEWABLE)
70 return creds->flags.b.renewable;
71 #elif defined(HAVE_KRB5_CREDS_FLAGS) && defined(KDC_OPT_RENEWABLE)
72 return creds->flags & KDC_OPT_RENEWABLE;
76 static krb5_error_code
77 get_renewed_creds(krb5_context context,
79 krb5_principal client,
83 #ifdef HAVE_KRB5_GET_RENEWED_CREDS
84 return krb5_get_renewed_creds (context, creds, client, ccache, in_tkt_service);
86 return 1; /* XXX is there something better to return? */
91 get_cred_proxiable(krb5_creds *creds)
93 #if defined(HAVE_KRB5_CREDS_TICKET_FLAGS) && defined(TKT_FLG_PROXIABLE)
94 return creds->ticket_flags & TKT_FLG_PROXIABLE;
95 #elif defined(HAVE_KRB5_CREDS_FLAGS_B_PROXIABLE)
96 return creds->flags.b.proxiable;
97 #elif defined(HAVE_KRB5_CREDS_FLAGS) && defined(KDC_OPT_PROXIABLE)
98 return creds->flags & KDC_OPT_PROXIABLE;
103 get_principal_realm_length(krb5_principal p)
105 #if defined(HAVE_KRB5_PRINCIPAL_REALM_AS_STRING)
106 return strlen(p->realm);
107 #elif defined(HAVE_KRB5_PRINCIPAL_REALM_AS_DATA)
108 return p->realm.length;
113 get_principal_realm_data(krb5_principal p)
115 #if defined(HAVE_KRB5_PRINCIPAL_REALM_AS_STRING)
117 #elif defined(HAVE_KRB5_PRINCIPAL_REALM_AS_DATA)
118 return p->realm.data;
121 /* ***************************************************************** */
122 /* ***************************************************************** */
124 static gchar* minutes_to_expiry_text (int minutes)
130 expiry_text = g_strdup_printf (ngettext("Your credentials expire in %d minute",
131 "Your credentials expire in %d minutes",
135 expiry_text = g_strdup (_("Your credentials have expired"));
136 tmp = g_strdup_printf ("<span foreground=\"red\">%s</span>", expiry_text);
137 g_free (expiry_text);
145 krb5_auth_dialog_wrong_label_update_expiry (gpointer data)
147 GtkWidget *label = GTK_WIDGET(data);
151 gchar *expiry_markup;
153 g_return_val_if_fail (label != NULL, FALSE);
155 if (krb5_timeofday(kcontext, &now) != 0) {
159 minutes_left = (creds_expiry - now) / 60;
160 expiry_text = minutes_to_expiry_text (minutes_left);
162 expiry_markup = g_strdup_printf ("<span size=\"smaller\" style=\"italic\">%s</span>", expiry_text);
163 gtk_label_set_markup (GTK_LABEL (label), expiry_markup);
164 g_free (expiry_text);
165 g_free (expiry_markup);
171 krb5_auth_dialog_setup (GtkWidget *dialog,
172 const gchar *krb5prompt,
173 gboolean hide_password)
177 GtkWidget *wrong_label;
183 if (krb5prompt == NULL) {
184 prompt = g_strdup (_("Please enter your Kerberos password."));
186 /* Kerberos's prompts are a mess, and basically impossible to
187 * translate. There's basically no way short of doing a lot of
188 * string parsing to translate them. The most common prompt is
189 * "Password for $uid:". We special case that one at least. We
190 * cannot do any of the fancier strings (like challenges),
192 pw4len = strlen ("Password for ");
193 if (strncmp (krb5prompt, "Password for ", pw4len) == 0) {
194 gchar *uid = (gchar *) (krb5prompt + pw4len);
195 prompt = g_strdup_printf (_("Please enter the password for '%s'"), uid);
197 prompt = g_strdup (krb5prompt);
201 /* Clear the password entry field */
202 entry = glade_xml_get_widget (xml, "krb5_entry");
203 gtk_entry_set_text (GTK_ENTRY (entry), "");
204 gtk_entry_set_visibility (GTK_ENTRY (entry), !hide_password);
206 /* Use the prompt label that krb5 provides us */
207 label = glade_xml_get_widget (xml, "krb5_message_label");
208 gtk_label_set_text (GTK_LABEL (label), prompt);
210 /* Add our extra message hints, if any */
211 wrong_label = glade_xml_get_widget (xml, "krb5_wrong_label");
215 if (invalid_password) {
216 wrong_text = g_strdup (_("The password you entered is invalid"));
221 if (krb5_timeofday(kcontext, &now) == 0) {
222 minutes_left = (creds_expiry - now) / 60;
227 wrong_text = minutes_to_expiry_text (minutes_left);
232 wrong_markup = g_strdup_printf ("<span size=\"smaller\" style=\"italic\">%s</span>", wrong_text);
233 gtk_label_set_markup (GTK_LABEL (wrong_label), wrong_markup);
235 g_free(wrong_markup);
237 gtk_label_set_text (GTK_LABEL (wrong_label), "");
243 static krb5_error_code
244 auth_dialog_prompter (krb5_context ctx,
249 krb5_prompt prompts[])
252 GtkWidget *wrong_label;
253 krb5_error_code errcode;
256 errcode = KRB5_LIBOS_CANTREADPWD;
258 canceled_creds_expiry = 0;
260 dialog = glade_xml_get_widget (xml, "krb5_dialog");
262 for (i = 0; i < num_prompts; i++) {
263 const gchar *password = NULL;
264 int password_len = 0;
266 guint32 source_id = 0;
270 errcode = KRB5_LIBOS_CANTREADPWD;
272 entry = glade_xml_get_widget(xml, "krb5_entry");
273 krb5_auth_dialog_setup (dialog, (gchar *) prompts[i].prompt, prompts[i].hidden);
274 gtk_widget_grab_focus (entry);
276 wrong_label = glade_xml_get_widget (xml, "krb5_wrong_label");
277 source_id = g_timeout_add (5000, (GSourceFunc)krb5_auth_dialog_wrong_label_update_expiry,
280 response = gtk_dialog_run (GTK_DIALOG (dialog));
283 case GTK_RESPONSE_OK:
284 password = gtk_entry_get_text (GTK_ENTRY (entry));
285 password_len = strlen (password);
288 case GTK_RESPONSE_CANCEL:
291 case GTK_RESPONSE_DELETE_EVENT:
294 g_warning ("Unknown Response: %d", response);
295 g_assert_not_reached ();
298 g_source_remove (source_id);
300 prompts[i].reply->data = (char *) password;
301 prompts[i].reply->length = password_len;
304 /* Reset this, so we know the next time we get a TRUE value, it is accurate. */
305 gtk_widget_hide (dialog);
306 invalid_password = FALSE;
311 static gboolean is_online = TRUE;
313 #ifdef ENABLE_NETWORK_MANAGER
315 network_state_cb (libnm_glib_ctx *context,
318 gboolean *online = (gboolean*) data;
320 libnm_glib_state state;
322 state = libnm_glib_get_network_state (context);
327 case LIBNM_NO_NETWORKMANAGER:
328 case LIBNM_INVALID_CONTEXT:
331 case LIBNM_NO_NETWORK_CONNECTION:
334 case LIBNM_ACTIVE_NETWORK_CONNECTION:
342 credentials_expiring_real (gboolean *renewable)
346 gboolean retval = FALSE;
349 if (!get_tgt_from_ccache (kcontext, &my_creds)) {
354 if (krb5_principal_compare (kcontext, my_creds.client, kprincipal)) {
355 krb5_free_principal(kcontext, kprincipal);
356 krb5_copy_principal(kcontext, my_creds.client, &kprincipal);
358 creds_expiry = my_creds.times.endtime;
359 if ((krb5_timeofday(kcontext, &now) == 0) &&
360 (now + MINUTES_BEFORE_PROMPTING * 60 > my_creds.times.endtime))
363 /* If our creds are expiring, determine whether they are renewable */
364 if (retval && get_cred_renewable(&my_creds) && my_creds.times.renew_till > now) {
368 krb5_free_cred_contents (kcontext, &my_creds);
374 credentials_expiring (gpointer *data)
380 if (credentials_expiring_real (&renewable) && is_online) {
381 give_up = canceled && (creds_expiry == canceled_creds_expiry);
384 retval = grab_credentials (renewable);
385 give_up = canceled &&
386 (creds_expiry == canceled_creds_expiry);
387 } while ((retval != 0) &&
388 (retval != KRB5_REALM_CANT_RESOLVE) &&
389 (retval != KRB5_KDC_UNREACH) &&
399 set_options_using_creds(krb5_context context,
401 krb5_get_init_creds_opt *opts)
403 krb5_deltat renew_lifetime;
406 flag = get_cred_forwardable(creds) != 0;
407 krb5_get_init_creds_opt_set_forwardable(opts, flag);
408 flag = get_cred_proxiable(creds) != 0;
409 krb5_get_init_creds_opt_set_proxiable(opts, flag);
410 flag = get_cred_renewable(creds) != 0;
411 if (flag && (creds->times.renew_till > creds->times.starttime)) {
412 renew_lifetime = creds->times.renew_till -
413 creds->times.starttime;
414 krb5_get_init_creds_opt_set_renew_life(opts,
417 if (creds->times.endtime >
418 creds->times.starttime + MINUTES_BEFORE_PROMPTING * 60) {
419 krb5_get_init_creds_opt_set_tkt_life(opts,
420 creds->times.endtime -
421 creds->times.starttime);
423 /* This doesn't do a deep copy -- fix it later. */
424 /* krb5_get_init_creds_opt_set_address_list(opts, creds->addresses); */
428 grab_credentials (gboolean renewable)
430 krb5_error_code retval;
433 krb5_get_init_creds_opt opts;
435 memset(&my_creds, 0, sizeof(my_creds));
437 if (kprincipal == NULL) {
438 retval = krb5_parse_name(kcontext, g_get_user_name (),
445 retval = krb5_cc_default (kcontext, &ccache);
449 krb5_get_init_creds_opt_init (&opts);
450 if (get_tgt_from_ccache (kcontext, &my_creds)) {
451 set_options_using_creds (kcontext, &my_creds, &opts);
452 creds_expiry = my_creds.times.endtime;
455 retval = get_renewed_creds (kcontext, &my_creds, kprincipal, ccache, NULL);
457 /* If we succeeded in renewing the credentials, we store it. */
461 /* Else, try to get new credentials, so just fall through */
463 krb5_free_cred_contents (kcontext, &my_creds);
468 retval = krb5_get_init_creds_password(kcontext, &my_creds, kprincipal,
469 NULL, auth_dialog_prompter, NULL,
472 canceled_creds_expiry = creds_expiry;
476 case KRB5KDC_ERR_PREAUTH_FAILED:
477 case KRB5KRB_AP_ERR_BAD_INTEGRITY:
478 /* Invalid password, try again. */
479 invalid_password = TRUE;
488 retval = krb5_cc_initialize(kcontext, ccache, kprincipal);
493 retval = krb5_cc_store_cred(kcontext, ccache, &my_creds);
498 creds_expiry = my_creds.times.endtime;
500 krb5_free_cred_contents (kcontext, &my_creds);
501 krb5_cc_close (kcontext, ccache);
507 get_tgt_from_ccache (krb5_context context, krb5_creds *creds)
511 krb5_principal principal, tgt_principal;
514 memset(&ccache, 0, sizeof(ccache));
516 if (krb5_cc_default(context, &ccache) == 0) {
517 memset(&principal, 0, sizeof(principal));
518 if (krb5_cc_get_principal(context, ccache, &principal) == 0) {
519 memset(&tgt_principal, 0, sizeof(tgt_principal));
520 if (krb5_build_principal_ext(context, &tgt_principal,
521 get_principal_realm_length(principal),
522 get_principal_realm_data(principal),
525 get_principal_realm_length(principal),
526 get_principal_realm_data(principal),
528 memset(creds, 0, sizeof(*creds));
529 memset(&mcreds, 0, sizeof(mcreds));
530 mcreds.client = principal;
531 mcreds.server = tgt_principal;
532 if (krb5_cc_retrieve_cred(context, ccache,
538 memset(creds, 0, sizeof(*creds));
540 krb5_free_principal(context, tgt_principal);
542 krb5_free_principal(context, principal);
544 krb5_cc_close(context, ccache);
553 gboolean have_tgt = FALSE;
556 err = krb5_init_context(&kcontext);
561 have_tgt = get_tgt_from_ccache(kcontext, &creds);
563 krb5_copy_principal(kcontext, creds.client, &kprincipal);
564 krb5_free_cred_contents (kcontext, &creds);
571 main (int argc, char *argv[])
574 GOptionContext *context;
575 GError *error = NULL;
576 DBusGConnection *session;
577 DBusGProxy *bus_proxy;
578 guint request_name_reply;
580 gboolean run_auto = FALSE, run_always = FALSE;
581 const char *help_msg = "Run '" PACKAGE " --help' to see a full list of available command line options";
582 const GOptionEntry options [] = {
583 {"auto", 'a', 0, G_OPTION_ARG_NONE, &run_auto,
584 "Only run if an initialized ccache is found (default)", NULL},
585 {"always", 'A', 0, G_OPTION_ARG_NONE, &run_always,
587 { NULL, 0, 0, G_OPTION_ARG_NONE, NULL, NULL, NULL }
590 #ifdef ENABLE_NETWORK_MANAGER
591 libnm_glib_ctx *nm_context;
592 guint32 nm_callback_id;
594 context = g_option_context_new ("- Kerberos 5 credential checking");
595 g_option_context_add_main_entries (context, options, NULL);
596 g_option_context_add_group (context, gtk_get_option_group (TRUE));
597 g_option_context_parse (context, &argc, &argv, &error);
602 g_error_free (error);
605 textdomain (PACKAGE);
606 bind_textdomain_codeset (PACKAGE, "UTF-8");
607 bindtextdomain (PACKAGE, LOCALE_DIR);
609 /* Connect to the session bus so we get exit-on-disconnect semantics. */
610 session = dbus_g_bus_get (DBUS_BUS_SESSION, &error);
611 if (session == NULL) {
612 g_error ("couldn't connect to session bus: %s", (error) ? error->message : "(null)");
615 flags = DBUS_NAME_FLAG_DO_NOT_QUEUE;
616 bus_proxy = dbus_g_proxy_new_for_name (session,
617 "org.freedesktop.DBus",
618 "/org/freedesktop/DBus",
619 "org.freedesktop.DBus");
621 if (!dbus_g_proxy_call (bus_proxy,
625 "org.gnome.KrbAuthDialog",
632 g_warning ("Failed to invoke RequestName: %s",
635 g_clear_error (&error);
636 g_object_unref (bus_proxy);
638 if (request_name_reply == DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER
639 || request_name_reply == DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER)
641 else if (request_name_reply == DBUS_REQUEST_NAME_REPLY_EXISTS
642 || request_name_reply == DBUS_REQUEST_NAME_REPLY_IN_QUEUE)
645 g_assert_not_reached();
648 if (run_always && !run_auto) {
651 if (using_krb5 () || always_run) {
652 g_set_application_name (_("Network Authentication"));
654 #ifdef ENABLE_NETWORK_MANAGER
655 nm_context = libnm_glib_init ();
657 g_warning ("Could not initialize libnm_glib");
659 nm_callback_id = libnm_glib_register_callback (nm_context, network_state_cb, &is_online, NULL);
660 if (nm_callback_id == 0) {
661 libnm_glib_shutdown (nm_context);
664 g_warning ("Could not connect to NetworkManager, connection status will not be managed!");
667 #endif /* ENABLE_NETWORK_MANAGER */
669 xml = glade_xml_new (GLADEDIR "krb5-auth-dialog.glade", NULL, NULL);
670 dialog = glade_xml_get_widget (xml, "krb5_dialog");
671 gtk_window_set_default_icon_name ("gtk-dialog-authentication");
673 if (credentials_expiring (NULL)) {
674 g_timeout_add (CREDENTIAL_CHECK_INTERVAL * 1000, (GSourceFunc)credentials_expiring, NULL);