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);
275 wrong_label = glade_xml_get_widget (xml, "krb5_wrong_label");
276 source_id = g_timeout_add (5000, (GSourceFunc)krb5_auth_dialog_wrong_label_update_expiry,
279 response = gtk_dialog_run (GTK_DIALOG (dialog));
282 case GTK_RESPONSE_OK:
283 password = gtk_entry_get_text (GTK_ENTRY (entry));
284 password_len = strlen (password);
287 case GTK_RESPONSE_CANCEL:
290 case GTK_RESPONSE_DELETE_EVENT:
293 g_warning ("Unknown Response: %d", response);
294 g_assert_not_reached ();
297 g_source_remove (source_id);
299 prompts[i].reply->data = (char *) password;
300 prompts[i].reply->length = password_len;
303 /* Reset this, so we know the next time we get a TRUE value, it is accurate. */
304 gtk_widget_hide (dialog);
305 invalid_password = FALSE;
310 static gboolean is_online = TRUE;
312 #ifdef ENABLE_NETWORK_MANAGER
314 network_state_cb (libnm_glib_ctx *context,
317 gboolean *online = (gboolean*) data;
319 libnm_glib_state state;
321 state = libnm_glib_get_network_state (context);
326 case LIBNM_NO_NETWORKMANAGER:
327 case LIBNM_INVALID_CONTEXT:
330 case LIBNM_NO_NETWORK_CONNECTION:
333 case LIBNM_ACTIVE_NETWORK_CONNECTION:
341 credentials_expiring_real (gboolean *renewable)
345 gboolean retval = FALSE;
348 if (!get_tgt_from_ccache (kcontext, &my_creds)) {
353 if (krb5_principal_compare (kcontext, my_creds.client, kprincipal)) {
354 krb5_free_principal(kcontext, kprincipal);
355 krb5_copy_principal(kcontext, my_creds.client, &kprincipal);
357 creds_expiry = my_creds.times.endtime;
358 if ((krb5_timeofday(kcontext, &now) == 0) &&
359 (now + MINUTES_BEFORE_PROMPTING * 60 > my_creds.times.endtime))
362 /* If our creds are expiring, determine whether they are renewable */
363 if (retval && get_cred_renewable(&my_creds) && my_creds.times.renew_till > now) {
367 krb5_free_cred_contents (kcontext, &my_creds);
373 credentials_expiring (gpointer *data)
379 if (credentials_expiring_real (&renewable) && is_online) {
380 give_up = canceled && (creds_expiry == canceled_creds_expiry);
383 retval = grab_credentials (renewable);
384 give_up = canceled &&
385 (creds_expiry == canceled_creds_expiry);
386 } while ((retval != 0) &&
387 (retval != KRB5_REALM_CANT_RESOLVE) &&
388 (retval != KRB5_KDC_UNREACH) &&
398 set_options_using_creds(krb5_context context,
400 krb5_get_init_creds_opt *opts)
402 krb5_deltat renew_lifetime;
405 flag = get_cred_forwardable(creds) != 0;
406 krb5_get_init_creds_opt_set_forwardable(opts, flag);
407 flag = get_cred_proxiable(creds) != 0;
408 krb5_get_init_creds_opt_set_proxiable(opts, flag);
409 flag = get_cred_renewable(creds) != 0;
410 if (flag && (creds->times.renew_till > creds->times.starttime)) {
411 renew_lifetime = creds->times.renew_till -
412 creds->times.starttime;
413 krb5_get_init_creds_opt_set_renew_life(opts,
416 if (creds->times.endtime >
417 creds->times.starttime + MINUTES_BEFORE_PROMPTING * 60) {
418 krb5_get_init_creds_opt_set_tkt_life(opts,
419 creds->times.endtime -
420 creds->times.starttime);
422 /* This doesn't do a deep copy -- fix it later. */
423 /* krb5_get_init_creds_opt_set_address_list(opts, creds->addresses); */
427 grab_credentials (gboolean renewable)
429 krb5_error_code retval;
432 krb5_get_init_creds_opt opts;
434 memset(&my_creds, 0, sizeof(my_creds));
436 if (kprincipal == NULL) {
437 retval = krb5_parse_name(kcontext, g_get_user_name (),
444 retval = krb5_cc_default (kcontext, &ccache);
448 krb5_get_init_creds_opt_init (&opts);
449 if (get_tgt_from_ccache (kcontext, &my_creds)) {
450 set_options_using_creds (kcontext, &my_creds, &opts);
451 creds_expiry = my_creds.times.endtime;
454 retval = get_renewed_creds (kcontext, &my_creds, kprincipal, ccache, NULL);
456 /* If we succeeded in renewing the credentials, we store it. */
460 /* Else, try to get new credentials, so just fall through */
462 krb5_free_cred_contents (kcontext, &my_creds);
467 retval = krb5_get_init_creds_password(kcontext, &my_creds, kprincipal,
468 NULL, auth_dialog_prompter, NULL,
471 canceled_creds_expiry = creds_expiry;
475 case KRB5KDC_ERR_PREAUTH_FAILED:
476 case KRB5KRB_AP_ERR_BAD_INTEGRITY:
477 /* Invalid password, try again. */
478 invalid_password = TRUE;
487 retval = krb5_cc_initialize(kcontext, ccache, kprincipal);
492 retval = krb5_cc_store_cred(kcontext, ccache, &my_creds);
497 creds_expiry = my_creds.times.endtime;
499 krb5_free_cred_contents (kcontext, &my_creds);
500 krb5_cc_close (kcontext, ccache);
506 get_tgt_from_ccache (krb5_context context, krb5_creds *creds)
510 krb5_principal principal, tgt_principal;
513 memset(&ccache, 0, sizeof(ccache));
515 if (krb5_cc_default(context, &ccache) == 0) {
516 memset(&principal, 0, sizeof(principal));
517 if (krb5_cc_get_principal(context, ccache, &principal) == 0) {
518 memset(&tgt_principal, 0, sizeof(tgt_principal));
519 if (krb5_build_principal_ext(context, &tgt_principal,
520 get_principal_realm_length(principal),
521 get_principal_realm_data(principal),
524 get_principal_realm_length(principal),
525 get_principal_realm_data(principal),
527 memset(creds, 0, sizeof(*creds));
528 memset(&mcreds, 0, sizeof(mcreds));
529 mcreds.client = principal;
530 mcreds.server = tgt_principal;
531 if (krb5_cc_retrieve_cred(context, ccache,
537 memset(creds, 0, sizeof(*creds));
539 krb5_free_principal(context, tgt_principal);
541 krb5_free_principal(context, principal);
543 krb5_cc_close(context, ccache);
552 gboolean have_tgt = FALSE;
555 err = krb5_init_context(&kcontext);
560 have_tgt = get_tgt_from_ccache(kcontext, &creds);
562 krb5_copy_principal(kcontext, creds.client, &kprincipal);
563 krb5_free_cred_contents (kcontext, &creds);
570 main (int argc, char *argv[])
573 GOptionContext *context;
574 GError *error = NULL;
575 DBusGConnection *session;
576 DBusGProxy *bus_proxy;
577 guint request_name_reply;
579 int run_auto = 0, run_always = 0;
580 const char *help_msg = "Run '" PACKAGE " --help' to see a full list of available command line options";
581 const GOptionEntry options [] = {
582 {"auto", 'a', 0, G_OPTION_ARG_NONE, &run_auto,
583 "Only run if an initialized ccache is found (default)", NULL},
584 {"always", 'A', 0, G_OPTION_ARG_NONE, &run_always,
586 { NULL, 0, 0, G_OPTION_ARG_NONE, NULL, NULL, NULL }
589 #ifdef ENABLE_NETWORK_MANAGER
590 libnm_glib_ctx *nm_context;
591 guint32 nm_callback_id;
593 context = g_option_context_new ("- Kerberos 5 credential checking");
594 g_option_context_add_main_entries (context, options, NULL);
595 g_option_context_add_group (context, gtk_get_option_group (TRUE));
596 g_option_context_parse (context, &argc, &argv, &error);
601 g_error_free (error);
605 /* Connect to the session bus so we get exit-on-disconnect semantics. */
606 session = dbus_g_bus_get(DBUS_BUS_SESSION, &error);
607 if (session == NULL) {
608 g_error ("couldn't connect to session bus: %s", (error) ? error->message : "(null)");
611 flags = DBUS_NAME_FLAG_DO_NOT_QUEUE;
612 bus_proxy = dbus_g_proxy_new_for_name (session,
613 "org.freedesktop.DBus",
614 "/org/freedesktop/DBus",
615 "org.freedesktop.DBus");
617 if (!dbus_g_proxy_call (bus_proxy,
621 "org.gnome.KrbAuthDialog",
628 g_warning ("Failed to invoke RequestName: %s",
631 g_clear_error (&error);
632 g_object_unref (bus_proxy);
634 if (request_name_reply == DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER
635 || request_name_reply == DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER)
637 else if (request_name_reply == DBUS_REQUEST_NAME_REPLY_EXISTS
638 || request_name_reply == DBUS_REQUEST_NAME_REPLY_IN_QUEUE)
641 g_assert_not_reached();
644 if (run_always && !run_auto) {
647 if (using_krb5 () || always_run) {
648 g_set_application_name (_("Network Authentication"));
650 #ifdef ENABLE_NETWORK_MANAGER
651 nm_context = libnm_glib_init ();
653 g_warning ("Could not initialize libnm_glib");
655 nm_callback_id = libnm_glib_register_callback (nm_context, network_state_cb, &is_online, NULL);
656 if (nm_callback_id == 0) {
657 libnm_glib_shutdown (nm_context);
660 g_warning ("Could not connect to NetworkManager, connection status will not be managed!");
663 #endif /* ENABLE_NETWORK_MANAGER */
665 xml = glade_xml_new (GLADEDIR "krb5-auth-dialog.glade", NULL, NULL);
666 dialog = glade_xml_get_widget (xml, "krb5_dialog");
667 gtk_window_set_default_icon_name ("gtk-dialog-authentication");
669 if (credentials_expiring (NULL)) {
670 g_timeout_add (CREDENTIAL_CHECK_INTERVAL * 1000, (GSourceFunc)credentials_expiring, NULL);