7d9d0c21b1590451fdadceaba8fe7e40c5d912cd
[jelmer/krb5-auth-dialog.git] / src / krb5-auth-dialog.c
1 /*
2  * Copyright (C) 2004,2005,2006 Red Hat, Inc.
3  * Authored by Christopher Aillon <caillon@redhat.com>
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, or (at your option)
8  * 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, write to the Free Software
17  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18  *
19  */
20
21 #include "config.h"
22
23 #include <stdlib.h>
24 #include <time.h>
25 #include <krb5.h>
26 #include <stdio.h>
27 #include <sys/wait.h>
28 #include <string.h>
29 #include <gtk/gtk.h>
30 #include <glib/gi18n.h>
31 #include <glade/glade.h>
32 #include <dbus/dbus-glib.h>
33
34
35 #ifdef ENABLE_NETWORK_MANAGER
36 #include <libnm_glib.h>
37 #endif
38
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;
47
48 static int grab_credentials (gboolean renewable);
49 static gboolean get_tgt_from_ccache (krb5_context context, krb5_creds *creds);
50
51 /* YAY for different Kerberos implementations */
52 static int
53 get_cred_forwardable(krb5_creds *creds)
54 {
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;
61 #endif
62 }
63
64 static int
65 get_cred_renewable(krb5_creds *creds)
66 {
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;
73 #endif
74 }
75
76 static krb5_error_code
77 get_renewed_creds(krb5_context context,
78                   krb5_creds *creds,
79                   krb5_principal client,
80                   krb5_ccache ccache,
81                   char *in_tkt_service)
82 {
83 #ifdef HAVE_KRB5_GET_RENEWED_CREDS
84         return krb5_get_renewed_creds (context, creds, client, ccache, in_tkt_service);
85 #else
86         return 1; /* XXX is there something better to return? */
87 #endif
88 }
89
90 static int
91 get_cred_proxiable(krb5_creds *creds)
92 {
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;
99 #endif
100 }
101
102 static size_t
103 get_principal_realm_length(krb5_principal p)
104 {
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;
109 #endif
110 }
111
112 static const char *
113 get_principal_realm_data(krb5_principal p)
114 {
115 #if defined(HAVE_KRB5_PRINCIPAL_REALM_AS_STRING)
116         return p->realm;
117 #elif defined(HAVE_KRB5_PRINCIPAL_REALM_AS_DATA)
118         return p->realm.data;
119 #endif
120 }
121 /* ***************************************************************** */
122 /* ***************************************************************** */
123
124 static gchar* minutes_to_expiry_text (int minutes)
125 {
126         gchar *expiry_text;
127         gchar *tmp;
128
129         if (minutes > 0) {
130                 expiry_text = g_strdup_printf (ngettext("Your credentials expire in %d minute",
131                                                         "Your credentials expire in %d minutes",
132                                                         minutes),
133                                                minutes);
134         } else {
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);
138                 expiry_text = tmp;
139         }
140
141         return expiry_text;
142 }
143
144 static gboolean
145 krb5_auth_dialog_wrong_label_update_expiry (gpointer data)
146 {
147         GtkWidget *label = GTK_WIDGET(data);
148         int minutes_left;
149         krb5_timestamp now;
150         gchar *expiry_text;
151         gchar *expiry_markup;
152
153         g_return_val_if_fail (label != NULL, FALSE);
154
155         if (krb5_timeofday(kcontext, &now) != 0) {
156                 return TRUE;
157         }
158
159         minutes_left = (creds_expiry - now) / 60;
160         expiry_text = minutes_to_expiry_text (minutes_left);
161
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);
166
167         return TRUE;
168 }
169
170 static void
171 krb5_auth_dialog_setup (GtkWidget *dialog,
172                         const gchar *krb5prompt,
173                         gboolean hide_password)
174 {
175         GtkWidget *entry;
176         GtkWidget *label;
177         GtkWidget *wrong_label;
178         gchar *wrong_text;
179         gchar *wrong_markup;
180         gchar *prompt;
181         int pw4len;
182
183         if (krb5prompt == NULL) {
184                 prompt = g_strdup (_("Please enter your Kerberos password."));
185         } else {
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),
191                  * though. */
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);
196                 } else {
197                         prompt = g_strdup (krb5prompt);
198                 }
199         }
200
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);
205
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);
209
210         /* Add our extra message hints, if any */
211         wrong_label = glade_xml_get_widget (xml, "krb5_wrong_label");
212         wrong_text = NULL;
213
214         if (wrong_label) {
215                 if (invalid_password) {
216                         wrong_text = g_strdup (_("The password you entered is invalid"));
217                 } else {
218                         krb5_timestamp now;
219                         int minutes_left;
220
221                         if (krb5_timeofday(kcontext, &now) == 0) {
222                                 minutes_left = (creds_expiry - now) / 60;
223                         } else {
224                                 minutes_left = 0;
225                         }
226
227                         wrong_text = minutes_to_expiry_text (minutes_left);
228                 }
229         }
230
231         if (wrong_text) {
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);
234                 g_free(wrong_text);
235                 g_free(wrong_markup);
236         } else {
237                 gtk_label_set_text (GTK_LABEL (wrong_label), "");
238         }
239
240         g_free (prompt);
241 }
242
243 static krb5_error_code
244 auth_dialog_prompter (krb5_context ctx,
245                       void *data,
246                       const char *name,
247                       const char *banner,
248                       int num_prompts,
249                       krb5_prompt prompts[])
250 {
251         GtkWidget *dialog;
252         GtkWidget *wrong_label;
253         krb5_error_code errcode;
254         int i;
255
256         errcode = KRB5_LIBOS_CANTREADPWD;
257         canceled = FALSE;
258         canceled_creds_expiry = 0;
259
260         dialog = glade_xml_get_widget (xml, "krb5_dialog");
261
262         for (i = 0; i < num_prompts; i++) {
263                 const gchar *password = NULL;
264                 int password_len = 0;
265                 int response;
266                 guint32 source_id = 0;
267
268                 GtkWidget *entry;
269
270                 errcode = KRB5_LIBOS_CANTREADPWD;
271
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);
275
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,
278                                            wrong_label);
279
280                 response = gtk_dialog_run (GTK_DIALOG (dialog));
281                 switch (response)
282                 {
283                         case GTK_RESPONSE_OK:
284                                 password = gtk_entry_get_text (GTK_ENTRY (entry));
285                                 password_len = strlen (password);
286                                 errcode = 0;
287                                 break;
288                         case GTK_RESPONSE_CANCEL:
289                                 canceled = TRUE;
290                                 break;
291                         case GTK_RESPONSE_DELETE_EVENT:
292                                 break;
293                         default:
294                                 g_warning ("Unknown Response: %d", response);
295                                 g_assert_not_reached ();
296                 }
297
298                 g_source_remove (source_id);
299
300                 prompts[i].reply->data = (char *) password;
301                 prompts[i].reply->length = password_len;
302         }
303
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;
307
308         return errcode;
309 }
310
311 static gboolean is_online = TRUE;
312
313 #ifdef ENABLE_NETWORK_MANAGER
314 static void
315 network_state_cb (libnm_glib_ctx *context,
316                   gpointer data)
317 {
318         gboolean *online = (gboolean*) data;
319
320         libnm_glib_state state;
321
322         state = libnm_glib_get_network_state (context);
323
324         switch (state)
325         {
326                 case LIBNM_NO_DBUS:
327                 case LIBNM_NO_NETWORKMANAGER:
328                 case LIBNM_INVALID_CONTEXT:
329                         /* do nothing */
330                         break;
331                 case LIBNM_NO_NETWORK_CONNECTION:
332                         *online = FALSE;
333                         break;
334                 case LIBNM_ACTIVE_NETWORK_CONNECTION:
335                         *online = TRUE;
336                         break;
337         }
338 }
339 #endif
340
341 static gboolean
342 credentials_expiring_real (gboolean *renewable)
343 {
344         krb5_creds my_creds;
345         krb5_timestamp now;
346         gboolean retval = FALSE;
347         *renewable = FALSE;
348
349         if (!get_tgt_from_ccache (kcontext, &my_creds)) {
350                 creds_expiry = 0;
351                 return TRUE;
352         }
353
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);
357         }
358         creds_expiry = my_creds.times.endtime;
359         if ((krb5_timeofday(kcontext, &now) == 0) &&
360             (now + MINUTES_BEFORE_PROMPTING * 60 > my_creds.times.endtime))
361                 retval = TRUE;
362
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) {
365                 *renewable = TRUE;
366         }
367
368         krb5_free_cred_contents (kcontext, &my_creds);
369
370         return retval;
371 }
372
373 static gboolean
374 credentials_expiring (gpointer *data)
375 {
376         int retval;
377         gboolean give_up;
378         gboolean renewable;
379
380         if (credentials_expiring_real (&renewable) && is_online) {
381                 give_up = canceled && (creds_expiry == canceled_creds_expiry);
382                 if (!give_up) {
383                         do {
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) &&
390                                  invalid_password &&
391                                  !give_up);
392                 }
393         }
394
395         return TRUE;
396 }
397
398 static void
399 set_options_using_creds(krb5_context context,
400                         krb5_creds *creds,
401                         krb5_get_init_creds_opt *opts)
402 {
403         krb5_deltat renew_lifetime;
404         int flag;
405
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,
415                                                        renew_lifetime);
416         }
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);
422         }
423         /* This doesn't do a deep copy -- fix it later. */
424         /* krb5_get_init_creds_opt_set_address_list(opts, creds->addresses); */
425 }
426
427 static int
428 grab_credentials (gboolean renewable)
429 {
430         krb5_error_code retval;
431         krb5_creds my_creds;
432         krb5_ccache ccache;
433         krb5_get_init_creds_opt opts;
434
435         memset(&my_creds, 0, sizeof(my_creds));
436
437         if (kprincipal == NULL) {
438                 retval = krb5_parse_name(kcontext, g_get_user_name (),
439                                          &kprincipal);
440                 if (retval) {
441                         return retval;
442                 }
443         }
444
445         retval = krb5_cc_default (kcontext, &ccache);
446         if (retval)
447                 return retval;
448
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;
453
454                 if (renewable) {
455                         retval = get_renewed_creds (kcontext, &my_creds, kprincipal, ccache, NULL);
456
457                         /* If we succeeded in renewing the credentials, we store it. */
458                         if (retval == 0) {
459                                 goto store;
460                         }
461                         /* Else, try to get new credentials, so just fall through */
462                 }
463                 krb5_free_cred_contents (kcontext, &my_creds);
464         } else {
465                 creds_expiry = 0;
466         }
467
468         retval = krb5_get_init_creds_password(kcontext, &my_creds, kprincipal,
469                                               NULL, auth_dialog_prompter, NULL,
470                                               0, NULL, &opts);
471         if (canceled) {
472                 canceled_creds_expiry = creds_expiry;
473         }
474         if (retval) {
475                 switch (retval) {
476                         case KRB5KDC_ERR_PREAUTH_FAILED:
477                         case KRB5KRB_AP_ERR_BAD_INTEGRITY:
478                                 /* Invalid password, try again. */
479                                 invalid_password = TRUE;
480                                 goto out;
481                         default:
482                                 break;
483                 }
484                 goto out;
485         }
486
487 store:
488         retval = krb5_cc_initialize(kcontext, ccache, kprincipal);
489         if (retval) {
490                 goto out;
491         }
492
493         retval = krb5_cc_store_cred(kcontext, ccache, &my_creds);
494         if (retval) {
495                 goto out;
496         }
497
498         creds_expiry = my_creds.times.endtime;
499 out:
500         krb5_free_cred_contents (kcontext, &my_creds);
501         krb5_cc_close (kcontext, ccache);
502
503         return retval;
504 }
505
506 static gboolean
507 get_tgt_from_ccache (krb5_context context, krb5_creds *creds)
508 {
509         krb5_ccache ccache;
510         krb5_creds mcreds;
511         krb5_principal principal, tgt_principal;
512         gboolean ret;
513
514         memset(&ccache, 0, sizeof(ccache));
515         ret = FALSE;
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),
523                                                      KRB5_TGS_NAME_SIZE,
524                                                      KRB5_TGS_NAME,
525                                                      get_principal_realm_length(principal),
526                                                      get_principal_realm_data(principal),
527                                                      0) == 0) {
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,
533                                                           0,
534                                                           &mcreds,
535                                                           creds) == 0) {
536                                         ret = TRUE;
537                                 } else {
538                                         memset(creds, 0, sizeof(*creds));
539                                 }
540                                 krb5_free_principal(context, tgt_principal);
541                         }
542                         krb5_free_principal(context, principal);
543                 }
544                 krb5_cc_close(context, ccache);
545         }
546         return ret;
547 }
548
549 static gboolean
550 using_krb5()
551 {
552         krb5_error_code err;
553         gboolean have_tgt = FALSE;
554         krb5_creds creds;
555
556         err = krb5_init_context(&kcontext);
557         if (err) {
558                 return TRUE;
559         }
560
561         have_tgt = get_tgt_from_ccache(kcontext, &creds);
562         if (have_tgt) {
563                 krb5_copy_principal(kcontext, creds.client, &kprincipal);
564                 krb5_free_cred_contents (kcontext, &creds);
565         }
566
567         return have_tgt;
568 }
569
570 int
571 main (int argc, char *argv[])
572 {
573         GtkWidget *dialog;
574         GOptionContext *context;
575         GError *error = NULL;
576         DBusGConnection *session;
577         DBusGProxy *bus_proxy;
578         guint request_name_reply;
579         unsigned int flags;
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,
586                         "Always run", NULL},
587                 { NULL, 0, 0, G_OPTION_ARG_NONE, NULL, NULL, NULL }
588         };
589
590 #ifdef ENABLE_NETWORK_MANAGER
591         libnm_glib_ctx *nm_context;
592         guint32 nm_callback_id; 
593 #endif
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);
598         if (error) {
599                 g_print ("%s\n%s\n",
600                          error->message,
601                          help_msg);
602                 g_error_free (error);
603                 return 1;
604         }
605         textdomain (PACKAGE);
606         bind_textdomain_codeset (PACKAGE, "UTF-8");
607         bindtextdomain (PACKAGE, LOCALE_DIR);
608
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)");
613                 exit(1);
614         }
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");
620
621         if (!dbus_g_proxy_call (bus_proxy,
622                                 "RequestName",
623                                 &error,
624                                 G_TYPE_STRING,
625                                 "org.gnome.KrbAuthDialog",
626                                 G_TYPE_UINT,
627                                 flags,
628                                 G_TYPE_INVALID,
629                                 G_TYPE_UINT,
630                                 &request_name_reply,
631                                 G_TYPE_INVALID)) {
632                 g_warning ("Failed to invoke RequestName: %s",
633                            error->message);
634         }
635         g_clear_error (&error);
636         g_object_unref (bus_proxy);
637
638         if (request_name_reply == DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER
639             || request_name_reply == DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER)
640                 ;
641         else if (request_name_reply == DBUS_REQUEST_NAME_REPLY_EXISTS
642                  || request_name_reply == DBUS_REQUEST_NAME_REPLY_IN_QUEUE)
643                 exit(0);
644         else {
645                 g_assert_not_reached();
646         }
647
648         if (run_always && !run_auto) {
649                 always_run = TRUE;
650         }
651         if (using_krb5 () || always_run) {
652                 g_set_application_name (_("Network Authentication"));
653
654 #ifdef ENABLE_NETWORK_MANAGER
655                 nm_context = libnm_glib_init ();
656                 if (!nm_context) {
657                         g_warning ("Could not initialize libnm_glib");
658                 } else {
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);
662                                 nm_context = NULL;
663
664                                 g_warning ("Could not connect to NetworkManager, connection status will not be managed!");
665                         }
666                 }
667 #endif /* ENABLE_NETWORK_MANAGER */
668
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");
672
673                 if (credentials_expiring (NULL)) {
674                         g_timeout_add (CREDENTIAL_CHECK_INTERVAL * 1000, (GSourceFunc)credentials_expiring, NULL);
675                 }
676                 gtk_main ();
677         }
678
679         return 0;
680 }