aboutsummaryrefslogtreecommitdiff
path: root/src/ka-dialog.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/ka-dialog.c')
-rw-r--r--src/ka-dialog.c1171
1 files changed, 1171 insertions, 0 deletions
diff --git a/src/ka-dialog.c b/src/ka-dialog.c
new file mode 100644
index 0000000..f8d0fea
--- /dev/null
+++ b/src/ka-dialog.c
@@ -0,0 +1,1171 @@
+/*
+ * Copyright (C) 2004,2005,2006 Red Hat, Inc.
+ * Authored by Christopher Aillon <caillon@redhat.com>
+ *
+ * Copyright (C) 2008,2009,2010 Guido Guenther <agx@sigxcpu.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <time.h>
+#include <krb5.h>
+#include <stdio.h>
+#include <sys/wait.h>
+#include <string.h>
+#include <gtk/gtk.h>
+#include <glib/gi18n.h>
+#include <gio/gio.h>
+
+#include "gtksecentry.h"
+#include "secmem-util.h"
+#include "memory.h"
+
+#include "ka-dialog.h"
+#include "ka-applet-priv.h"
+#include "ka-pwdialog.h"
+#include "ka-dbus.h"
+#include "ka-tools.h"
+#include "ka-tickets.h"
+
+#ifdef ENABLE_NETWORK_MANAGER
+#include <libnm_glib.h>
+#endif
+
+#ifdef HAVE_HX509_ERR_H
+# include <hx509_err.h>
+#endif
+
+static krb5_context kcontext;
+static krb5_principal kprincipal;
+static krb5_timestamp creds_expiry;
+static krb5_timestamp canceled_creds_expiry;
+static gboolean canceled;
+static gboolean invalid_auth;
+static gboolean always_run;
+static gboolean is_online = TRUE;
+
+static int grab_credentials (KaApplet* applet);
+static int ka_renew_credentials (KaApplet* applet);
+static gboolean ka_get_tgt_from_ccache (krb5_context context, krb5_creds *creds);
+
+#ifdef ENABLE_NETWORK_MANAGER
+libnm_glib_ctx *nm_context;
+#endif
+
+/* YAY for different Kerberos implementations */
+static int
+get_cred_forwardable(krb5_creds *creds)
+{
+#if defined(HAVE_KRB5_CREDS_TICKET_FLAGS) && defined(TKT_FLG_FORWARDABLE)
+ return creds->ticket_flags & TKT_FLG_FORWARDABLE;
+#elif defined(HAVE_KRB5_CREDS_FLAGS_B_FORWARDABLE)
+ return creds->flags.b.forwardable;
+#elif defined(HAVE_KRB5_CREDS_FLAGS) && defined(KDC_OPT_FORWARDABLE)
+ return creds->flags & KDC_OPT_FORWARDABLE;
+#endif
+}
+
+static int
+get_cred_renewable(krb5_creds *creds)
+{
+#if defined(HAVE_KRB5_CREDS_TICKET_FLAGS) && defined(TKT_FLG_RENEWABLE)
+ return creds->ticket_flags & TKT_FLG_RENEWABLE;
+#elif defined(HAVE_KRB5_CREDS_FLAGS_B_RENEWABLE)
+ return creds->flags.b.renewable;
+#elif defined(HAVE_KRB5_CREDS_FLAGS) && defined(KDC_OPT_RENEWABLE)
+ return creds->flags & KDC_OPT_RENEWABLE;
+#endif
+}
+
+static krb5_error_code
+get_renewed_creds(krb5_context context,
+ krb5_creds *creds,
+ krb5_principal client,
+ krb5_ccache ccache,
+ char *in_tkt_service)
+{
+#ifdef HAVE_KRB5_GET_RENEWED_CREDS
+ return krb5_get_renewed_creds (context, creds, client, ccache, in_tkt_service);
+#else
+ return 1; /* XXX is there something better to return? */
+#endif
+}
+
+static int
+get_cred_proxiable(krb5_creds *creds)
+{
+#if defined(HAVE_KRB5_CREDS_TICKET_FLAGS) && defined(TKT_FLG_PROXIABLE)
+ return creds->ticket_flags & TKT_FLG_PROXIABLE;
+#elif defined(HAVE_KRB5_CREDS_FLAGS_B_PROXIABLE)
+ return creds->flags.b.proxiable;
+#elif defined(HAVE_KRB5_CREDS_FLAGS) && defined(KDC_OPT_PROXIABLE)
+ return creds->flags & KDC_OPT_PROXIABLE;
+#endif
+}
+
+static size_t
+get_principal_realm_length(krb5_principal p)
+{
+#if defined(HAVE_KRB5_PRINCIPAL_REALM_AS_STRING)
+ return strlen(p->realm);
+#elif defined(HAVE_KRB5_PRINCIPAL_REALM_AS_DATA)
+ return p->realm.length;
+#endif
+}
+
+static const char *
+get_principal_realm_data(krb5_principal p)
+{
+#if defined(HAVE_KRB5_PRINCIPAL_REALM_AS_STRING)
+ return p->realm;
+#elif defined(HAVE_KRB5_PRINCIPAL_REALM_AS_DATA)
+ return p->realm.data;
+#endif
+}
+
+static void
+ka_krb5_free_error_message(krb5_context context, const char* msg)
+{
+#if defined(HAVE_KRB5_FREE_ERROR_MESSAGE)
+ krb5_free_error_message(context, msg);
+#elif defined(HAVE_KRB5_FREE_ERROR_STRING)
+ krb5_free_error_string(context, (char *) msg);
+#else
+# error No way to free error string.
+#endif
+}
+
+/*
+ * Returns a descriptive error message or kerberos related error
+ * returned pointer must be freed using g_free().
+ */
+static char*
+ka_get_error_message(krb5_context context, krb5_error_code err)
+{
+ char *msg = NULL;
+#if defined(HAVE_KRB5_GET_ERROR_MESSAGE)
+ const char *krberr;
+
+ krberr = krb5_get_error_message(context, err);
+ msg = g_strdup(krberr);
+ ka_krb5_free_error_message(context, krberr);
+#else
+# error No detailed error message information
+#endif
+ if (msg == NULL)
+ msg = g_strdup(_("unknown error"));
+ return msg;
+}
+
+
+static void
+ka_krb5_cc_clear_mcred(krb5_creds* mcred)
+{
+#if defined HAVE_KRB5_CC_CLEAR_MCRED
+ krb5_cc_clear_mcred(mcred);
+#else
+ memset(mcred, 0, sizeof(krb5_creds));
+#endif
+}
+
+
+/* ***************************************************************** */
+/* ***************************************************************** */
+
+/* log a kerberos error messge */
+static void
+ka_log_error_message(const char* prefix, krb5_context context, krb5_error_code err)
+{
+ char *errmsg = ka_get_error_message(context, err);
+
+ g_warning("%s: %s", prefix, errmsg);
+ g_free (errmsg);
+}
+
+
+static gboolean
+credentials_expiring_real (KaApplet* applet)
+{
+ krb5_creds my_creds;
+ krb5_timestamp now;
+ gboolean retval = FALSE;
+
+ ka_applet_set_tgt_renewable(applet, FALSE);
+ if (!ka_get_tgt_from_ccache (kcontext, &my_creds)) {
+ creds_expiry = 0;
+ retval = TRUE;
+ goto out;
+ }
+
+ /* copy principal from cache if any */
+ if (krb5_principal_compare (kcontext, my_creds.client, kprincipal)) {
+ krb5_free_principal(kcontext, kprincipal);
+ krb5_copy_principal(kcontext, my_creds.client, &kprincipal);
+ }
+ creds_expiry = my_creds.times.endtime;
+ if ((krb5_timeofday(kcontext, &now) == 0) &&
+ (now + ka_applet_get_pw_prompt_secs(applet) > my_creds.times.endtime))
+ retval = TRUE;
+
+ /* If our creds are expiring, determine whether they are renewable */
+ if (retval && get_cred_renewable(&my_creds) && my_creds.times.renew_till > now) {
+ ka_applet_set_tgt_renewable(applet, TRUE);
+ }
+
+ krb5_free_cred_contents (kcontext, &my_creds);
+out:
+ ka_applet_update_status(applet, creds_expiry);
+ return retval;
+}
+
+
+/* time in seconds the tgt will be still valid */
+int
+ka_tgt_valid_seconds()
+{
+ krb5_timestamp now;
+
+ if (krb5_timeofday(kcontext, &now))
+ return 0;
+
+ return (creds_expiry - now);
+}
+
+
+/* return credential cache filename, strip "FILE:" prefix if necessary */
+static const char*
+ka_ccache_filename (void)
+{
+ const gchar *name;
+
+ name = krb5_cc_default_name (kcontext);
+ if (g_str_has_prefix (name, "FILE:"))
+ return strchr(name,':')+1;
+ else if (g_str_has_prefix (name, "SCC:"))
+ g_warning ("Cannot monitor sqlite based cache '%s'", name);
+ else
+ g_warning ("Unsupported cache type for '%s'", name);
+ return NULL;
+}
+
+
+static void
+ka_format_time (time_t t, gchar *ts, size_t len)
+{
+ g_strlcpy(ts, ctime(&t)+ 4, len);
+ ts[15] = 0;
+}
+
+
+/* fill in service tickets data */
+gboolean
+ka_get_service_tickets (GtkListStore *tickets)
+{
+ krb5_cc_cursor cursor;
+ krb5_creds creds;
+ krb5_error_code ret;
+ GtkTreeIter iter;
+ krb5_ccache ccache;
+ char *name;
+ krb5_timestamp sec;
+ gchar start_time[128], end_time[128], end_time_markup[256];
+ gboolean retval = FALSE;
+
+ gtk_list_store_clear(tickets);
+
+ krb5_timeofday (kcontext, &sec);
+ ret = krb5_cc_default (kcontext, &ccache);
+ g_return_val_if_fail (!ret, FALSE);
+
+ ret = krb5_cc_start_seq_get (kcontext, ccache, &cursor);
+ if (ret) {
+ ka_log_error_message("krb5_cc_start_seq_get", kcontext, ret);
+
+ /* if the file doesn't exist, it's not an error if we can't
+ * parse it */
+ if (!g_file_test(ka_ccache_filename (),
+ G_FILE_TEST_EXISTS))
+ retval = TRUE;
+ goto out;
+ }
+
+ while ((ret = krb5_cc_next_cred (kcontext,
+ ccache,
+ &cursor,
+ &creds)) == 0) {
+ gboolean renewable, proxiable, forwardable;
+
+ if (creds.times.starttime)
+ ka_format_time(creds.times.starttime, start_time,
+ sizeof(start_time));
+ else
+ ka_format_time(creds.times.authtime, start_time,
+ sizeof(start_time));
+
+ ka_format_time(creds.times.endtime, end_time,
+ sizeof(end_time));
+ if (creds.times.endtime > sec)
+ strcpy(end_time_markup, end_time);
+ else
+ g_snprintf(end_time_markup, sizeof(end_time_markup),
+ "%s <span foreground=\"red\" style=\"italic\">(%s)</span>",
+ end_time, _("Expired"));
+
+ forwardable = get_cred_forwardable(&creds);
+ renewable = get_cred_renewable(&creds);
+ proxiable = get_cred_proxiable(&creds);
+
+ ret = krb5_unparse_name (kcontext, creds.server, &name);
+ if (!ret) {
+ gtk_list_store_append(tickets, &iter);
+ gtk_list_store_set(tickets, &iter,
+ PRINCIPAL_COLUMN, name,
+ START_TIME_COLUMN, start_time,
+ END_TIME_COLUMN, end_time_markup,
+ FORWARDABLE_COLUMN, forwardable,
+ RENEWABLE_COLUMN, renewable,
+ PROXIABLE_COLUMN, proxiable,
+ -1);
+ free(name);
+ } else
+ ka_log_error_message("krb5_unparse_name", kcontext, ret);
+ krb5_free_cred_contents (kcontext, &creds);
+ }
+ if(ret != KRB5_CC_END)
+ ka_log_error_message("krb5_cc_get_next", kcontext, ret);
+
+ ret = krb5_cc_end_seq_get (kcontext, ccache, &cursor);
+ if (ret)
+ ka_log_error_message("krb5_cc_end_seq_get", kcontext, ret);
+
+ retval = TRUE;
+out:
+ ret = krb5_cc_close (kcontext, ccache);
+ g_return_val_if_fail (!ret, FALSE);
+
+ return retval;
+}
+
+
+/* Check for things we have to do while the password dialog is open */
+static gboolean
+krb5_auth_dialog_do_updates (gpointer data)
+{
+ KaApplet* applet = KA_APPLET(data);
+ KaPwDialog* pwdialog = ka_applet_get_pwdialog(applet);
+
+ g_return_val_if_fail (pwdialog != NULL, FALSE);
+ /* Update creds_expiry and close the applet if we got the creds by other means (e.g. kinit) */
+ if (!credentials_expiring_real(applet))
+ ka_pwdialog_hide(pwdialog, FALSE);
+
+ /* Update the expiry information in the dialog */
+ ka_pwdialog_status_update (pwdialog);
+ return TRUE;
+}
+
+
+static krb5_error_code
+auth_dialog_prompter (krb5_context ctx G_GNUC_UNUSED,
+ void *data,
+ const char *name G_GNUC_UNUSED,
+ const char *banner G_GNUC_UNUSED,
+ int num_prompts,
+ krb5_prompt prompts[])
+{
+ KaApplet *applet = KA_APPLET(data);
+ KaPwDialog *pwdialog = ka_applet_get_pwdialog(applet);
+ krb5_error_code errcode;
+ int i;
+
+ errcode = KRB5KRB_ERR_GENERIC;
+ canceled = FALSE;
+ canceled_creds_expiry = 0;
+
+ for (i = 0; i < num_prompts; i++) {
+ const gchar *password = NULL;
+ int password_len = 0;
+ int response;
+ guint32 source_id;
+
+ errcode = KRB5_LIBOS_CANTREADPWD;
+
+ source_id = g_timeout_add_seconds (5, (GSourceFunc)krb5_auth_dialog_do_updates, applet);
+ ka_pwdialog_setup (pwdialog, (gchar *) prompts[i].prompt, invalid_auth);
+ response = ka_pwdialog_run (pwdialog);
+ switch (response)
+ {
+ case GTK_RESPONSE_OK:
+ password = ka_pwdialog_get_password(pwdialog);
+ password_len = strlen (password);
+ break;
+ case GTK_RESPONSE_DELETE_EVENT:
+ case GTK_RESPONSE_CANCEL:
+ canceled = TRUE;
+ break;
+ case GTK_RESPONSE_NONE:
+ break;
+ default:
+ g_warning ("Unknown Response: %d", response);
+ g_assert_not_reached ();
+ }
+ g_source_remove (source_id);
+
+ if (!password)
+ goto cleanup;
+ if (password_len+1 > prompts[i].reply->length) {
+ g_warning("Password too long %d/%d", password_len+1, prompts[i].reply->length);
+ goto cleanup;
+ }
+
+ memcpy(prompts[i].reply->data, (char *) password, password_len + 1);
+ prompts[i].reply->length = password_len;
+ errcode = 0;
+ }
+cleanup:
+ ka_pwdialog_hide (pwdialog, TRUE);
+ /* Reset this, so we know the next time we get a TRUE value, it is accurate. */
+ invalid_auth = FALSE;
+
+ return errcode;
+}
+
+
+#ifdef ENABLE_NETWORK_MANAGER
+static void
+network_state_cb (libnm_glib_ctx *context,
+ gpointer data)
+{
+ gboolean *online = (gboolean*) data;
+
+ libnm_glib_state state;
+
+ state = libnm_glib_get_network_state (context);
+
+ switch (state)
+ {
+ case LIBNM_NO_DBUS:
+ case LIBNM_NO_NETWORKMANAGER:
+ case LIBNM_INVALID_CONTEXT:
+ /* do nothing */
+ break;
+ case LIBNM_NO_NETWORK_CONNECTION:
+ *online = FALSE;
+ break;
+ case LIBNM_ACTIVE_NETWORK_CONNECTION:
+ *online = TRUE;
+ break;
+ }
+}
+#endif
+
+/* credentials expiring timer */
+static gboolean
+credentials_expiring (gpointer *data)
+{
+ int retval;
+ gboolean give_up;
+ KaApplet* applet = KA_APPLET(data);
+
+ KA_DEBUG("Checking expiry <%ds", ka_applet_get_pw_prompt_secs(applet));
+ if (credentials_expiring_real (applet) && is_online) {
+ KA_DEBUG("Expiry @ %ld", creds_expiry);
+
+ if (!ka_renew_credentials (applet)) {
+ KA_DEBUG("Credentials renewed");
+ goto out;
+ }
+
+ /* no popup when using a trayicon */
+ if (ka_applet_get_show_trayicon(applet))
+ goto out;
+
+ give_up = canceled && (creds_expiry == canceled_creds_expiry);
+ if (!give_up) {
+ do {
+ retval = grab_credentials (applet);
+ give_up = canceled &&
+ (creds_expiry == canceled_creds_expiry);
+ } while ((retval != 0) &&
+ (retval != KRB5_REALM_CANT_RESOLVE) &&
+ (retval != KRB5_KDC_UNREACH) &&
+ invalid_auth &&
+ !give_up);
+ }
+ }
+out:
+ ka_applet_update_status(applet, creds_expiry);
+ return TRUE;
+}
+
+
+/*
+ * set ticket options by looking at krb5.conf and gconf
+ */
+static void
+ka_set_ticket_options(KaApplet* applet, krb5_context context,
+ krb5_get_init_creds_opt *out,
+ const char* pk_userid G_GNUC_UNUSED,
+ const char* pk_anchors G_GNUC_UNUSED)
+{
+ gboolean flag;
+#ifdef HAVE_KRB5_GET_INIT_CREDS_OPT_SET_DEFAULT_FLAGS
+ krb5_get_init_creds_opt_set_default_flags(context, PACKAGE,
+ krb5_principal_get_realm(context, kprincipal), out);
+#endif
+ g_object_get(applet, "tgt-forwardable", &flag, NULL);
+ if (flag)
+ krb5_get_init_creds_opt_set_forwardable(out, flag);
+ g_object_get(applet, "tgt-proxiable", &flag, NULL);
+ if (flag)
+ krb5_get_init_creds_opt_set_proxiable(out, flag);
+ g_object_get(applet, "tgt-renewable", &flag, NULL);
+ if (flag) {
+ krb5_deltat r = 3600*24*30; /* 1 month */
+ krb5_get_init_creds_opt_set_renew_life (out, r);
+ }
+
+#if ENABLE_PKINIT && HAVE_KRB5_GET_INIT_CREDS_OPT_SET_PA
+ /* pkinit optins for MIT Kerberos */
+ if (pk_userid && strlen(pk_userid)) {
+ KA_DEBUG("pkinit with '%s'", pk_userid);
+ krb5_get_init_creds_opt_set_pa(context, out,
+ "X509_user_identity", pk_userid);
+ if (pk_anchors && strlen(pk_anchors)) {
+ KA_DEBUG("pkinit anchors '%s'", pk_anchors);
+ krb5_get_init_creds_opt_set_pa(context, out,
+ "X509_anchors", pk_anchors);
+ }
+ }
+#endif /* HAVE_KRB5_GET_INIT_CREDS_OPT_SET_PA */
+}
+
+
+/*
+ * set ticket options
+ * by looking at krb5.conf, the passed in creds and gconf
+ */
+static void
+set_options_from_creds(const KaApplet* applet,
+ krb5_context context,
+ krb5_creds *in,
+ krb5_get_init_creds_opt *out)
+{
+ krb5_deltat renew_lifetime;
+ int flag;
+
+#ifdef HAVE_KRB5_GET_INIT_CREDS_OPT_SET_DEFAULT_FLAGS
+ krb5_get_init_creds_opt_set_default_flags(context, PACKAGE,
+ krb5_principal_get_realm(context, kprincipal), out);
+#endif
+
+ flag = get_cred_forwardable(in) != 0;
+ krb5_get_init_creds_opt_set_forwardable(out, flag);
+ flag = get_cred_proxiable(in) != 0;
+ krb5_get_init_creds_opt_set_proxiable(out, flag);
+ flag = get_cred_renewable(in) != 0;
+ if (flag && (in->times.renew_till > in->times.starttime)) {
+ renew_lifetime = in->times.renew_till -
+ in->times.starttime;
+ krb5_get_init_creds_opt_set_renew_life(out,
+ renew_lifetime);
+ }
+ if (in->times.endtime >
+ in->times.starttime + ka_applet_get_pw_prompt_secs(applet)) {
+ krb5_get_init_creds_opt_set_tkt_life(out,
+ in->times.endtime -
+ in->times.starttime);
+ }
+ /* This doesn't do a deep copy -- fix it later. */
+ /* krb5_get_init_creds_opt_set_address_list(out, creds->addresses); */
+}
+
+
+#if ENABLE_PKINIT && HAVE_KRB5_GET_INIT_CREDS_OPT_SET_PKINIT
+static krb5_error_code
+ka_auth_heimdal_pkinit(KaApplet* applet, krb5_creds* creds,
+ const char* pk_userid, const char* pk_anchors)
+{
+ krb5_get_init_creds_opt *opts = NULL;
+ krb5_error_code retval;
+ const char* pkinit_anchors = NULL;
+
+ KA_DEBUG("pkinit with '%s'", pk_userid);
+ if (pk_anchors && strlen (pk_anchors)) {
+ pkinit_anchors = pk_anchors;
+ KA_DEBUG("pkinit anchors '%s'", pkinit_anchors);
+ }
+
+ if ((retval = krb5_get_init_creds_opt_alloc (kcontext, &opts)))
+ goto out;
+
+ ka_set_ticket_options (applet, kcontext, opts, NULL, NULL);
+ retval = krb5_get_init_creds_opt_set_pkinit(kcontext, opts,
+ kprincipal,
+ pk_userid,
+ pkinit_anchors,
+ NULL,
+ NULL,
+ 0, /* pk_use_enc_key */
+ auth_dialog_prompter,
+ applet, /* data */
+ NULL); /* passwd */
+ KA_DEBUG("pkinit returned with %d", retval);
+ if (retval)
+ goto out;
+
+ retval = krb5_get_init_creds_password(kcontext, creds, kprincipal,
+ NULL, auth_dialog_prompter, applet,
+ 0, NULL, opts);
+out:
+ if (opts)
+ krb5_get_init_creds_opt_free(kcontext, opts);
+ return retval;
+}
+#endif /* ! ENABLE_PKINIT */
+
+static krb5_error_code
+ka_auth_password(KaApplet* applet, krb5_creds* creds,
+ const char* pk_userid, const char* pk_anchors)
+{
+ krb5_error_code retval;
+ krb5_get_init_creds_opt *opts = NULL;
+
+ if ((retval = krb5_get_init_creds_opt_alloc (kcontext, &opts)))
+ goto out;
+ ka_set_ticket_options (applet, kcontext, opts,
+ pk_userid, pk_anchors);
+
+ retval = krb5_get_init_creds_password(kcontext, creds, kprincipal,
+ NULL, auth_dialog_prompter, applet,
+ 0, NULL, opts);
+out:
+ if (opts)
+ krb5_get_init_creds_opt_free(kcontext, opts);
+ return retval;
+}
+
+static krb5_error_code
+ka_parse_name(KaApplet* applet, krb5_context krbcontext, krb5_principal* kprinc)
+{
+ krb5_error_code ret;
+ gchar *principal = NULL;
+
+ if (*kprinc != NULL)
+ krb5_free_principal(krbcontext, *kprinc);
+
+ g_object_get(applet, "principal", &principal, NULL);
+ ret = krb5_parse_name(krbcontext, principal, kprinc);
+
+ g_free(principal);
+ return ret;
+}
+
+
+/*
+ * return current principal in text form
+ *
+ * caller needs to free the returned result using g_free();
+ */
+char*
+ka_unparse_name ()
+{
+ char *princ, *gprinc = NULL;
+ krb5_error_code err;
+
+ if (!kprincipal)
+ goto out;
+
+ if ((err = krb5_unparse_name (kcontext, kprincipal, &princ))) {
+ ka_log_error_message(__func__, kcontext, err);
+ goto out;
+ }
+
+ gprinc = g_strdup (princ);
+ free (princ);
+out:
+ return gprinc;
+}
+
+
+static void
+ccache_changed_cb (GFileMonitor *monitor G_GNUC_UNUSED,
+ GFile *file,
+ GFile *other_file G_GNUC_UNUSED,
+ GFileMonitorEvent event_type,
+ gpointer data)
+{
+ KaApplet *applet = KA_APPLET(data);
+ gchar *ccache_name = g_file_get_path(file);
+
+ switch (event_type) {
+ case G_FILE_MONITOR_EVENT_DELETED:
+ case G_FILE_MONITOR_EVENT_CREATED:
+ case G_FILE_MONITOR_EVENT_CHANGED:
+ KA_DEBUG ("%s changed", ccache_name);
+ credentials_expiring ((gpointer)applet);
+ break;
+ default:
+ KA_DEBUG ("%s unhandled event: %d", ccache_name, event_type);
+ }
+ g_free (ccache_name);
+}
+
+
+static gboolean
+monitor_ccache(KaApplet *applet)
+{
+ const gchar *ccache_name;
+ GFile *ccache;
+ GFileMonitor *monitor;
+ GError *err = NULL;
+ gboolean ret = FALSE;
+
+ ccache_name = ka_ccache_filename ();
+ g_return_val_if_fail (ccache_name != NULL, FALSE);
+
+ ccache = g_file_new_for_path (ccache_name);
+ monitor = g_file_monitor_file (ccache, G_FILE_MONITOR_NONE, NULL, &err);
+ g_assert ((!monitor && err) || (monitor && !err));
+ if (!monitor) {
+ /* cache disappeared? */
+ if (err->code == G_FILE_ERROR_NOENT)
+ credentials_expiring ((gpointer)applet);
+ else
+ g_warning ("Failed to monitor %s: %s", ccache_name, err->message);
+ goto out;
+ } else {
+ /* g_file_monitor_set_rate_limit(monitor, 10*1000); */
+ g_signal_connect (monitor, "changed", G_CALLBACK (ccache_changed_cb), applet);
+ KA_DEBUG ("Monitoring %s", ccache_name);
+ ret = TRUE;
+ }
+out:
+ g_object_unref (ccache);
+ if (err)
+ g_error_free (err);
+ return ret;
+}
+
+
+/* grab credentials interactively */
+static int
+grab_credentials (KaApplet* applet)
+{
+ krb5_error_code retval = KRB5_KDC_UNREACH;
+ krb5_creds my_creds;
+ krb5_ccache ccache;
+ gchar *pk_userid = NULL;
+ gchar *pk_anchors = NULL;
+ gchar *errmsg = NULL;
+ gboolean pw_auth = TRUE;
+
+ memset(&my_creds, 0, sizeof(my_creds));
+
+ retval = ka_parse_name(applet, kcontext, &kprincipal);
+ if (retval)
+ goto out2;
+
+ retval = krb5_cc_default (kcontext, &ccache);
+ if (retval)
+ goto out2;
+
+ g_object_get(applet, "pk-userid", &pk_userid,
+ "pk-anchors", &pk_anchors,
+ NULL);
+#if ENABLE_PKINIT && HAVE_HX509_ERR_H && HAVE_KRB5_GET_INIT_CREDS_OPT_SET_PKINIT
+ /* pk_userid set: try pkinit */
+ if (pk_userid && strlen(pk_userid)) {
+ retval = ka_auth_heimdal_pkinit(applet, &my_creds,
+ pk_userid, pk_anchors);
+ /* other error than: "no token found" - no need to try password auth: */
+ if (retval != HX509_PKCS11_NO_TOKEN && retval != HX509_PKCS11_NO_SLOT)
+ pw_auth = FALSE;
+ }
+#endif /* ENABLE_PKINIT */
+ if (pw_auth)
+ retval = ka_auth_password(applet, &my_creds,
+ pk_userid, pk_anchors);
+
+ creds_expiry = my_creds.times.endtime;
+ if (canceled)
+ canceled_creds_expiry = creds_expiry;
+ if (retval) {
+ switch (retval) {
+ case KRB5KDC_ERR_PREAUTH_FAILED:
+ case KRB5KRB_AP_ERR_BAD_INTEGRITY:
+#ifdef HAVE_HX509_ERR_H
+ case HX509_PKCS11_LOGIN:
+#endif /* Invalid password/pin, try again. */
+ invalid_auth = TRUE;
+ break;
+ default:
+ errmsg = ka_get_error_message(kcontext, retval);
+ KA_DEBUG("Auth failed with %d: %s", retval,
+ errmsg);
+ g_free(errmsg);
+ break;
+ }
+ goto out;
+ }
+ retval = krb5_cc_initialize(kcontext, ccache, kprincipal);
+ if (retval)
+ goto out;
+
+ retval = krb5_cc_store_cred(kcontext, ccache, &my_creds);
+ if (retval)
+ goto out;
+out:
+ krb5_free_cred_contents (kcontext, &my_creds);
+ krb5_cc_close (kcontext, ccache);
+out2:
+ g_free(pk_userid);
+ return retval;
+}
+
+/* try to renew the credentials noninteractively */
+static int
+ka_renew_credentials (KaApplet* applet)
+{
+ krb5_error_code retval;
+ krb5_creds my_creds;
+ krb5_ccache ccache;
+ krb5_get_init_creds_opt opts;
+
+ if (kprincipal == NULL) {
+ retval = ka_parse_name(applet, kcontext, &kprincipal);
+ if (retval)
+ return retval;
+ }
+
+ retval = krb5_cc_default (kcontext, &ccache);
+ if (retval)
+ return retval;
+
+ retval = ka_get_tgt_from_ccache (kcontext, &my_creds);
+ if (!retval) {
+ krb5_cc_close (kcontext, ccache);
+ return -1;
+ }
+
+ krb5_get_init_creds_opt_init (&opts);
+ set_options_from_creds (applet, kcontext, &my_creds, &opts);
+
+ if (ka_applet_get_tgt_renewable(applet)) {
+
+ retval = get_renewed_creds (kcontext, &my_creds, kprincipal, ccache, NULL);
+ if (retval)
+ goto out;
+
+ retval = krb5_cc_initialize(kcontext, ccache, kprincipal);
+ if(retval) {
+ ka_log_error_message("krb5_cc_initialize", kcontext, retval);
+ goto out;
+ }
+ retval = krb5_cc_store_cred(kcontext, ccache, &my_creds);
+ if (retval) {
+ ka_log_error_message("krb5_cc_store_cred", kcontext, retval);
+ goto out;
+ }
+ ka_applet_signal_emit (applet, KA_SIGNAL_RENEWED_TGT,
+ my_creds.times.endtime);
+ }
+out:
+ creds_expiry = my_creds.times.endtime;
+ krb5_free_cred_contents (kcontext, &my_creds);
+ krb5_cc_close (kcontext, ccache);
+ return retval;
+}
+
+
+/* get principal associated with the default credentials cache - if found store
+ * it in *creds, return FALSE otwerwise */
+static gboolean
+ka_get_tgt_from_ccache (krb5_context context, krb5_creds *creds)
+{
+ krb5_ccache ccache;
+ krb5_creds pattern;
+ krb5_principal principal;
+ gboolean ret = FALSE;
+
+ ka_krb5_cc_clear_mcred(&pattern);
+
+ if (krb5_cc_default(context, &ccache))
+ return FALSE;
+
+ if (krb5_cc_get_principal(context, ccache, &principal))
+ goto out;
+
+ if (krb5_build_principal_ext(context, &pattern.server,
+ get_principal_realm_length(principal),
+ get_principal_realm_data(principal),
+ KRB5_TGS_NAME_SIZE,
+ KRB5_TGS_NAME,
+ get_principal_realm_length(principal),
+ get_principal_realm_data(principal), 0)) {
+ goto out_free_princ;
+ }
+
+ pattern.client = principal;
+ if (!krb5_cc_retrieve_cred(context, ccache, 0, &pattern, creds))
+ ret = TRUE;
+ krb5_free_principal(context, pattern.server);
+
+out_free_princ:
+ krb5_free_principal(context, principal);
+out:
+ krb5_cc_close(context, ccache);
+ return ret;
+}
+
+static gboolean
+using_krb5(void)
+{
+ krb5_error_code err;
+ gboolean have_tgt = FALSE;
+ krb5_creds creds;
+
+ err = krb5_init_context(&kcontext);
+ if (err)
+ return FALSE;
+
+ have_tgt = ka_get_tgt_from_ccache(kcontext, &creds);
+ if (have_tgt) {
+ krb5_copy_principal(kcontext, creds.client, &kprincipal);
+ krb5_free_cred_contents (kcontext, &creds);
+ }
+ return have_tgt;
+}
+
+
+gboolean
+ka_destroy_ccache (KaApplet *applet)
+{
+ krb5_ccache ccache;
+ const char* cache;
+ krb5_error_code ret;
+
+ cache = krb5_cc_default_name(kcontext);
+ ret = krb5_cc_resolve(kcontext, cache, &ccache);
+ ret = krb5_cc_destroy (kcontext, ccache);
+
+ credentials_expiring_real(applet);
+
+ if (ret)
+ return FALSE;
+ else
+ return TRUE;
+}
+
+
+/*
+ * check if we have valid credentials for the requested principal - if not, grab them
+ * principal: requested principal - if empty use default
+ */
+gboolean
+ka_check_credentials (KaApplet *applet, const char* newprincipal)
+{
+ gboolean success = FALSE;
+ int retval;
+ char* principal;
+
+ g_object_get(applet, "principal", &principal, NULL);
+
+ if (strlen(newprincipal)) {
+ krb5_principal knewprinc;
+
+ /* no ticket cache: is requested princ the one from our config? */
+ if (!kprincipal && g_strcmp0(principal, newprincipal)) {
+ KA_DEBUG("Requested principal %s not %s", principal, newprincipal);
+ goto out;
+ }
+
+ /* ticket cache: check if the requested principal is the one we have */
+ retval = krb5_parse_name(kcontext, newprincipal, &knewprinc);
+ if (retval) {
+ g_warning ("Cannot parse principal '%s'", newprincipal);
+ goto out;
+ }
+ if (kprincipal && !krb5_principal_compare (kcontext, kprincipal, knewprinc)) {
+ KA_DEBUG("Current Principal '%s' not '%s'", principal, newprincipal);
+ krb5_free_principal(kcontext, knewprinc);
+ goto out;
+ }
+ krb5_free_principal(kcontext, knewprinc);
+ }
+
+ if (credentials_expiring_real (applet)) {
+ if (!is_online)
+ success = FALSE;
+ else
+ success = ka_grab_credentials (applet);
+ } else
+ success = TRUE;
+out:
+ g_free (principal);
+ return success;
+}
+
+
+/* initiate grabbing of credentials (e.g. on leftclick of tray icon) */
+gboolean
+ka_grab_credentials (KaApplet* applet)
+{
+ int retval;
+ int success = FALSE;
+ KaPwDialog *pwdialog = ka_applet_get_pwdialog(applet);
+
+ ka_pwdialog_set_persist(pwdialog, TRUE);
+ do {
+ retval = grab_credentials (applet);
+ if (invalid_auth)
+ continue;
+ if (canceled)
+ break;
+ if (retval) {
+ gchar *errmsg;
+
+ errmsg = ka_get_error_message(kcontext, retval);
+ ka_pwdialog_error(pwdialog, errmsg);
+ g_free (errmsg);
+ break;
+ } else {
+ success = TRUE;
+ break;
+ }
+ } while(TRUE);
+
+ ka_pwdialog_set_persist(pwdialog, FALSE);
+ credentials_expiring_real(applet);
+
+ return success;
+}
+
+
+static void
+ka_secmem_init (void)
+{
+ /* Initialize secure memory. 1 is too small, so the default size
+ will be used. */
+ secmem_init (1);
+ secmem_set_flags (SECMEM_WARN);
+ drop_privs ();
+
+ if (atexit (secmem_term))
+ g_error("Couln't register atexit handler");
+}
+
+
+static void
+ka_nm_shutdown(void)
+{
+#ifdef ENABLE_NETWORK_MANAGER
+ if (nm_context) {
+ libnm_glib_shutdown (nm_context);
+ nm_context = NULL;
+ }
+#endif
+}
+
+
+static gboolean
+ka_nm_init(void)
+{
+#ifdef ENABLE_NETWORK_MANAGER
+ guint32 nm_callback_id;
+
+ nm_context = libnm_glib_init ();
+ if (!nm_context) {
+ g_warning ("Could not initialize libnm_glib");
+ } else {
+ nm_callback_id = libnm_glib_register_callback (nm_context, network_state_cb, &is_online, NULL);
+ if (nm_callback_id == 0) {
+ ka_nm_shutdown ();
+
+ g_warning ("Could not connect to NetworkManager, connection status will not be managed!");
+ }
+ }
+#endif /* ENABLE_NETWORK_MANAGER */
+ return TRUE;
+}
+
+
+int
+main (int argc, char *argv[])
+{
+ KaApplet *applet;
+ GOptionContext *context;
+ GError *error = NULL;
+
+ guint status = 0;
+ gboolean run_auto = FALSE, run_always = FALSE;
+
+ const char *help_msg = "Run '" PACKAGE " --help' to see a full list of available command line options";
+ const GOptionEntry options [] = {
+ {"auto", 'a', 0, G_OPTION_ARG_NONE, &run_auto,
+ "Only run if an initialized ccache is found (default)", NULL},
+ {"always", 'A', 0, G_OPTION_ARG_NONE, &run_always,
+ "Always run", NULL},
+ { NULL, 0, 0, G_OPTION_ARG_NONE, NULL, NULL, NULL }
+ };
+
+ context = g_option_context_new ("- Kerberos 5 credential checking");
+ g_option_context_add_main_entries (context, options, NULL);
+ g_option_context_add_group (context, gtk_get_option_group (TRUE));
+ g_option_context_parse (context, &argc, &argv, &error);
+
+ if (error) {
+ g_print ("%s\n%s\n",
+ error->message,
+ help_msg);
+ g_error_free (error);
+ return 1;
+ }
+ g_option_context_free (context);
+
+ textdomain (PACKAGE);
+ bind_textdomain_codeset (PACKAGE, "UTF-8");
+ bindtextdomain (PACKAGE, LOCALE_DIR);
+ ka_secmem_init();
+
+ if (!ka_dbus_connect (&status))
+ exit(status);
+
+ if (run_always && !run_auto) {
+ always_run = TRUE;
+ }
+
+ if (using_krb5 () || always_run) {
+ g_set_application_name (KA_NAME);
+
+ applet = ka_applet_create ();
+ if (!applet)
+ return 1;
+ ka_nm_init();
+
+ if (credentials_expiring ((gpointer)applet)) {
+ g_timeout_add_seconds (CREDENTIAL_CHECK_INTERVAL, (GSourceFunc)credentials_expiring, applet);
+ monitor_ccache (applet);
+ }
+ ka_dbus_service(applet);
+ gtk_main ();
+ }
+ ka_nm_shutdown();
+ return 0;
+}
bgstack15