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>
33 #include "gtksecentry.h"
34 #include "secmem-util.h"
37 #include "krb5-auth-dialog.h"
38 #include "krb5-auth-applet.h"
39 #include "krb5-auth-gconf.h"
40 #include "krb5-auth-dbus.h"
42 #ifdef ENABLE_NETWORK_MANAGER
43 #include <libnm_glib.h>
46 static krb5_context kcontext;
47 static krb5_principal kprincipal;
48 static krb5_timestamp creds_expiry;
49 static krb5_timestamp canceled_creds_expiry;
50 static gboolean canceled;
51 static gboolean invalid_password;
52 static gboolean always_run;
54 static int grab_credentials (Krb5AuthApplet* applet, gboolean renewable);
55 static gboolean get_tgt_from_ccache (krb5_context context, krb5_creds *creds);
57 /* YAY for different Kerberos implementations */
59 get_cred_forwardable(krb5_creds *creds)
61 #if defined(HAVE_KRB5_CREDS_TICKET_FLAGS) && defined(TKT_FLG_FORWARDABLE)
62 return creds->ticket_flags & TKT_FLG_FORWARDABLE;
63 #elif defined(HAVE_KRB5_CREDS_FLAGS_B_FORWARDABLE)
64 return creds->flags.b.forwardable;
65 #elif defined(HAVE_KRB5_CREDS_FLAGS) && defined(KDC_OPT_FORWARDABLE)
66 return creds->flags & KDC_OPT_FORWARDABLE;
71 get_cred_renewable(krb5_creds *creds)
73 #if defined(HAVE_KRB5_CREDS_TICKET_FLAGS) && defined(TKT_FLG_RENEWABLE)
74 return creds->ticket_flags & TKT_FLG_RENEWABLE;
75 #elif defined(HAVE_KRB5_CREDS_FLAGS_B_RENEWABLE)
76 return creds->flags.b.renewable;
77 #elif defined(HAVE_KRB5_CREDS_FLAGS) && defined(KDC_OPT_RENEWABLE)
78 return creds->flags & KDC_OPT_RENEWABLE;
82 static krb5_error_code
83 get_renewed_creds(krb5_context context,
85 krb5_principal client,
89 #ifdef HAVE_KRB5_GET_RENEWED_CREDS
90 return krb5_get_renewed_creds (context, creds, client, ccache, in_tkt_service);
92 return 1; /* XXX is there something better to return? */
97 get_cred_proxiable(krb5_creds *creds)
99 #if defined(HAVE_KRB5_CREDS_TICKET_FLAGS) && defined(TKT_FLG_PROXIABLE)
100 return creds->ticket_flags & TKT_FLG_PROXIABLE;
101 #elif defined(HAVE_KRB5_CREDS_FLAGS_B_PROXIABLE)
102 return creds->flags.b.proxiable;
103 #elif defined(HAVE_KRB5_CREDS_FLAGS) && defined(KDC_OPT_PROXIABLE)
104 return creds->flags & KDC_OPT_PROXIABLE;
109 get_principal_realm_length(krb5_principal p)
111 #if defined(HAVE_KRB5_PRINCIPAL_REALM_AS_STRING)
112 return strlen(p->realm);
113 #elif defined(HAVE_KRB5_PRINCIPAL_REALM_AS_DATA)
114 return p->realm.length;
119 get_principal_realm_data(krb5_principal p)
121 #if defined(HAVE_KRB5_PRINCIPAL_REALM_AS_STRING)
123 #elif defined(HAVE_KRB5_PRINCIPAL_REALM_AS_DATA)
124 return p->realm.data;
127 /* ***************************************************************** */
128 /* ***************************************************************** */
131 credentials_expiring_real (Krb5AuthApplet* applet, gboolean *renewable)
135 gboolean retval = FALSE;
138 if (!get_tgt_from_ccache (kcontext, &my_creds)) {
144 if (krb5_principal_compare (kcontext, my_creds.client, kprincipal)) {
145 krb5_free_principal(kcontext, kprincipal);
146 krb5_copy_principal(kcontext, my_creds.client, &kprincipal);
148 creds_expiry = my_creds.times.endtime;
149 if ((krb5_timeofday(kcontext, &now) == 0) &&
150 (now + applet->pw_prompt_secs > my_creds.times.endtime))
153 /* If our creds are expiring, determine whether they are renewable */
154 if (retval && get_cred_renewable(&my_creds) && my_creds.times.renew_till > now) {
158 krb5_free_cred_contents (kcontext, &my_creds);
161 ka_update_status(applet, creds_expiry);
166 static gchar* minutes_to_expiry_text (int minutes)
172 expiry_text = g_strdup_printf (ngettext("Your credentials expire in %d minute",
173 "Your credentials expire in %d minutes",
177 expiry_text = g_strdup (_("Your credentials have expired"));
178 tmp = g_strdup_printf ("<span foreground=\"red\">%s</span>", expiry_text);
179 g_free (expiry_text);
188 krb5_auth_dialog_wrong_label_update_expiry (GtkWidget* label)
193 gchar *expiry_markup;
195 g_return_val_if_fail (label!= NULL, FALSE);
197 if (krb5_timeofday(kcontext, &now) != 0) {
201 minutes_left = (creds_expiry - now) / 60;
202 expiry_text = minutes_to_expiry_text (minutes_left);
204 expiry_markup = g_strdup_printf ("<span size=\"smaller\" style=\"italic\">%s</span>", expiry_text);
205 gtk_label_set_markup (GTK_LABEL (label), expiry_markup);
206 g_free (expiry_text);
207 g_free (expiry_markup);
213 /* Check for things we have to do while the password dialog is open */
215 krb5_auth_dialog_do_updates (gpointer data)
217 Krb5AuthApplet* applet = (Krb5AuthApplet*)data;
218 gboolean refreshable;
220 g_return_val_if_fail (applet != NULL, FALSE);
222 /* Update creds_expiry and close the applet if we got the creds by other means (e.g. kinit) */
223 if (!credentials_expiring_real(applet, &refreshable)) {
224 KA_DEBUG("PW Dialog persist is %d", applet->pw_dialog_persist);
225 if (!applet->pw_dialog_persist)
226 gtk_widget_hide(applet->pw_dialog);
229 /* Update the expiry information in the dialog */
230 krb5_auth_dialog_wrong_label_update_expiry (applet->pw_wrong_label);
236 krb5_auth_dialog_setup (Krb5AuthApplet *applet,
237 const gchar *krb5prompt,
238 gboolean hide_password)
248 if (krb5prompt == NULL) {
249 prompt = g_strdup (_("Please enter your Kerberos password."));
251 /* Kerberos's prompts are a mess, and basically impossible to
252 * translate. There's basically no way short of doing a lot of
253 * string parsing to translate them. The most common prompt is
254 * "Password for $uid:". We special case that one at least. We
255 * cannot do any of the fancier strings (like challenges),
257 pw4len = strlen ("Password for ");
258 if (strncmp (krb5prompt, "Password for ", pw4len) == 0) {
259 gchar *uid = (gchar *) (krb5prompt + pw4len);
260 prompt = g_strdup_printf (_("Please enter the password for '%s'"), uid);
262 prompt = g_strdup (krb5prompt);
266 /* Clear the password entry field */
267 entry = glade_xml_get_widget (applet->pw_xml, "krb5_entry");
268 gtk_secure_entry_set_text (GTK_SECURE_ENTRY (entry), "");
270 /* Use the prompt label that krb5 provides us */
271 label = glade_xml_get_widget (applet->pw_xml, "krb5_message_label");
272 gtk_label_set_text (GTK_LABEL (label), prompt);
274 /* Add our extra message hints, if any */
277 if (applet->pw_wrong_label) {
278 if (invalid_password) {
279 wrong_text = g_strdup (_("The password you entered is invalid"));
284 if (krb5_timeofday(kcontext, &now) == 0)
285 minutes_left = (creds_expiry - now) / 60;
288 wrong_text = minutes_to_expiry_text (minutes_left);
293 wrong_markup = g_strdup_printf ("<span size=\"smaller\" style=\"italic\">%s</span>", wrong_text);
294 gtk_label_set_markup (GTK_LABEL (applet->pw_wrong_label), wrong_markup);
296 g_free(wrong_markup);
298 gtk_label_set_text (GTK_LABEL (applet->pw_wrong_label), "");
304 static krb5_error_code
305 auth_dialog_prompter (krb5_context ctx,
310 krb5_prompt prompts[])
312 Krb5AuthApplet* applet = (Krb5AuthApplet*)data;
313 krb5_error_code errcode;
316 errcode = KRB5_LIBOS_CANTREADPWD;
318 canceled_creds_expiry = 0;
320 for (i = 0; i < num_prompts; i++) {
321 const gchar *password = NULL;
322 int password_len = 0;
324 guint32 source_id = 0;
328 errcode = KRB5_LIBOS_CANTREADPWD;
330 krb5_auth_dialog_setup (applet, (gchar *) prompts[i].prompt, prompts[i].hidden);
331 entry = glade_xml_get_widget (applet->pw_xml, "krb5_entry");
332 gtk_widget_grab_focus (entry);
334 source_id = g_timeout_add_seconds (5, (GSourceFunc)krb5_auth_dialog_do_updates, applet);
335 response = gtk_dialog_run (GTK_DIALOG (applet->pw_dialog));
338 case GTK_RESPONSE_OK:
339 password = gtk_secure_entry_get_text (GTK_SECURE_ENTRY (entry));
340 password_len = strlen (password);
343 case GTK_RESPONSE_CANCEL:
346 case GTK_RESPONSE_NONE:
347 case GTK_RESPONSE_DELETE_EVENT:
350 g_warning ("Unknown Response: %d", response);
351 g_assert_not_reached ();
354 g_source_remove (source_id);
356 prompts[i].reply->data = (char *) password;
357 prompts[i].reply->length = password_len;
360 /* Reset this, so we know the next time we get a TRUE value, it is accurate. */
361 gtk_widget_hide (applet->pw_dialog);
362 invalid_password = FALSE;
367 static gboolean is_online = TRUE;
369 #ifdef ENABLE_NETWORK_MANAGER
371 network_state_cb (libnm_glib_ctx *context,
374 gboolean *online = (gboolean*) data;
376 libnm_glib_state state;
378 state = libnm_glib_get_network_state (context);
383 case LIBNM_NO_NETWORKMANAGER:
384 case LIBNM_INVALID_CONTEXT:
387 case LIBNM_NO_NETWORK_CONNECTION:
390 case LIBNM_ACTIVE_NETWORK_CONNECTION:
399 credentials_expiring (gpointer *data)
404 Krb5AuthApplet* applet = (Krb5AuthApplet*) data;
406 KA_DEBUG("Checking expiry: %d", applet->pw_prompt_secs);
407 if (credentials_expiring_real (applet, &renewable) && is_online) {
408 give_up = canceled && (creds_expiry == canceled_creds_expiry);
411 retval = grab_credentials (applet, renewable);
412 give_up = canceled &&
413 (creds_expiry == canceled_creds_expiry);
414 } while ((retval != 0) &&
415 (retval != KRB5_REALM_CANT_RESOLVE) &&
416 (retval != KRB5_KDC_UNREACH) &&
421 ka_update_status(applet, creds_expiry);
427 set_options_using_creds(const Krb5AuthApplet* applet,
428 krb5_context context,
430 krb5_get_init_creds_opt *opts)
432 krb5_deltat renew_lifetime;
435 flag = get_cred_forwardable(creds) != 0;
436 krb5_get_init_creds_opt_set_forwardable(opts, flag);
437 flag = get_cred_proxiable(creds) != 0;
438 krb5_get_init_creds_opt_set_proxiable(opts, flag);
439 flag = get_cred_renewable(creds) != 0;
440 if (flag && (creds->times.renew_till > creds->times.starttime)) {
441 renew_lifetime = creds->times.renew_till -
442 creds->times.starttime;
443 krb5_get_init_creds_opt_set_renew_life(opts,
446 if (creds->times.endtime >
447 creds->times.starttime + applet->pw_prompt_secs) {
448 krb5_get_init_creds_opt_set_tkt_life(opts,
449 creds->times.endtime -
450 creds->times.starttime);
452 /* This doesn't do a deep copy -- fix it later. */
453 /* krb5_get_init_creds_opt_set_address_list(opts, creds->addresses); */
457 grab_credentials (Krb5AuthApplet* applet, gboolean renewable)
459 krb5_error_code retval;
462 krb5_get_init_creds_opt opts;
464 memset(&my_creds, 0, sizeof(my_creds));
466 if (kprincipal == NULL) {
467 retval = krb5_parse_name(kcontext, applet->principal,
474 retval = krb5_cc_default (kcontext, &ccache);
478 krb5_get_init_creds_opt_init (&opts);
479 if (get_tgt_from_ccache (kcontext, &my_creds)) {
480 set_options_using_creds (applet, kcontext, &my_creds, &opts);
481 creds_expiry = my_creds.times.endtime;
484 retval = get_renewed_creds (kcontext, &my_creds, kprincipal, ccache, NULL);
486 /* If we succeeded in renewing the credentials, we store it. */
490 /* Else, try to get new credentials, so just fall through */
492 krb5_free_cred_contents (kcontext, &my_creds);
497 retval = krb5_get_init_creds_password(kcontext, &my_creds, kprincipal,
498 NULL, auth_dialog_prompter, applet,
501 canceled_creds_expiry = creds_expiry;
505 case KRB5KDC_ERR_PREAUTH_FAILED:
506 case KRB5KRB_AP_ERR_BAD_INTEGRITY:
507 /* Invalid password, try again. */
508 invalid_password = TRUE;
517 retval = krb5_cc_initialize(kcontext, ccache, kprincipal);
522 retval = krb5_cc_store_cred(kcontext, ccache, &my_creds);
527 creds_expiry = my_creds.times.endtime;
529 krb5_free_cred_contents (kcontext, &my_creds);
530 krb5_cc_close (kcontext, ccache);
536 get_tgt_from_ccache (krb5_context context, krb5_creds *creds)
540 krb5_principal principal, tgt_principal;
543 memset(&ccache, 0, sizeof(ccache));
545 if (krb5_cc_default(context, &ccache) == 0) {
546 memset(&principal, 0, sizeof(principal));
547 if (krb5_cc_get_principal(context, ccache, &principal) == 0) {
548 memset(&tgt_principal, 0, sizeof(tgt_principal));
549 if (krb5_build_principal_ext(context, &tgt_principal,
550 get_principal_realm_length(principal),
551 get_principal_realm_data(principal),
554 get_principal_realm_length(principal),
555 get_principal_realm_data(principal),
557 memset(creds, 0, sizeof(*creds));
558 memset(&mcreds, 0, sizeof(mcreds));
559 mcreds.client = principal;
560 mcreds.server = tgt_principal;
561 if (krb5_cc_retrieve_cred(context, ccache,
567 memset(creds, 0, sizeof(*creds));
569 krb5_free_principal(context, tgt_principal);
571 krb5_free_principal(context, principal);
573 krb5_cc_close(context, ccache);
582 gboolean have_tgt = FALSE;
585 err = krb5_init_context(&kcontext);
590 have_tgt = get_tgt_from_ccache(kcontext, &creds);
592 krb5_copy_principal(kcontext, creds.client, &kprincipal);
593 krb5_free_cred_contents (kcontext, &creds);
601 ka_destroy_cache (GtkMenuItem *menuitem, gpointer data)
603 Krb5AuthApplet* applet = (Krb5AuthApplet*) data;
609 cache = krb5_cc_default_name(kcontext);
610 ret = krb5_cc_resolve(kcontext, cache, &ccache);
611 ret = krb5_cc_destroy (kcontext, ccache);
613 credentials_expiring_real(applet, &renewable);
618 ka_error_dialog(int err)
620 const char* msg = error_message(err);
621 GtkWidget *dialog = gtk_message_dialog_new (NULL,
622 GTK_DIALOG_DESTROY_WITH_PARENT,
625 _("Couldn't acquire kerberos ticket: '%s'"), msg);
626 gtk_dialog_run (GTK_DIALOG (dialog));
627 gtk_widget_destroy (dialog);
631 /* this is done on leftclick, update the tooltip immediately */
633 ka_grab_credentials (Krb5AuthApplet* applet)
636 gboolean renewable, retry;
638 applet->pw_dialog_persist = TRUE;
641 retval = grab_credentials (applet, FALSE);
643 case KRB5KRB_AP_ERR_BAD_INTEGRITY:
646 case 0: /* success */
647 case KRB5_LIBOS_CANTREADPWD: /* canceled */
650 case KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN:
652 ka_error_dialog(retval);
658 applet->pw_dialog_persist = FALSE;
659 credentials_expiring_real(applet, &renewable);
664 ka_create_gtk_secure_entry (GladeXML *xml, gchar *func_name, gchar *name,
665 gchar *s1, gchar *s2, gint i1, gint i2,
668 GtkWidget* entry = NULL;
670 if (!strcmp(name, "krb5_entry")) {
671 entry = gtk_secure_entry_new ();
672 gtk_secure_entry_set_activates_default(GTK_SECURE_ENTRY(entry), TRUE);
673 gtk_widget_show (entry);
675 g_warning("Don't know anything about widget %s", name);
684 /* Initialize secure memory. 1 is too small, so the default size
687 secmem_set_flags (SECMEM_WARN);
690 if (atexit (secmem_term))
691 g_error("Couln't register atexit handler");
696 main (int argc, char *argv[])
698 Krb5AuthApplet *applet;
699 GOptionContext *context;
700 GError *error = NULL;
703 gboolean run_auto = FALSE, run_always = FALSE;
705 const char *help_msg = "Run '" PACKAGE " --help' to see a full list of available command line options";
706 const GOptionEntry options [] = {
707 {"auto", 'a', 0, G_OPTION_ARG_NONE, &run_auto,
708 "Only run if an initialized ccache is found (default)", NULL},
709 {"always", 'A', 0, G_OPTION_ARG_NONE, &run_always,
711 { NULL, 0, 0, G_OPTION_ARG_NONE, NULL, NULL, NULL }
714 #ifdef ENABLE_NETWORK_MANAGER
715 libnm_glib_ctx *nm_context;
716 guint32 nm_callback_id;
718 context = g_option_context_new ("- Kerberos 5 credential checking");
719 g_option_context_add_main_entries (context, options, NULL);
720 g_option_context_add_group (context, gtk_get_option_group (TRUE));
721 g_option_context_parse (context, &argc, &argv, &error);
726 g_error_free (error);
729 textdomain (PACKAGE);
730 bind_textdomain_codeset (PACKAGE, "UTF-8");
731 bindtextdomain (PACKAGE, LOCALE_DIR);
734 if (!ka_dbus_connect (&status))
737 if (run_always && !run_auto) {
740 if (using_krb5 () || always_run) {
741 applet = ka_create_applet ();
744 if (!ka_gconf_init (applet, argc, argv))
747 /* setup the pw dialog */
748 glade_set_custom_handler (&ka_create_gtk_secure_entry, NULL);
749 applet->pw_xml = glade_xml_new (GLADEDIR "krb5-auth-dialog.glade", NULL, NULL);
750 applet->pw_wrong_label = glade_xml_get_widget (applet->pw_xml, "krb5_wrong_label");
751 applet->pw_dialog = glade_xml_get_widget (applet->pw_xml, "krb5_dialog");
753 g_set_application_name (_("Network Authentication"));
754 gtk_window_set_default_icon_name (applet->icons[1]);
756 #ifdef ENABLE_NETWORK_MANAGER
757 nm_context = libnm_glib_init ();
759 g_warning ("Could not initialize libnm_glib");
761 nm_callback_id = libnm_glib_register_callback (nm_context, network_state_cb, &is_online, NULL);
762 if (nm_callback_id == 0) {
763 libnm_glib_shutdown (nm_context);
766 g_warning ("Could not connect to NetworkManager, connection status will not be managed!");
769 #endif /* ENABLE_NETWORK_MANAGER */
771 if (credentials_expiring ((gpointer)applet)) {
772 g_timeout_add_seconds (CREDENTIAL_CHECK_INTERVAL, (GSourceFunc)credentials_expiring, applet);